From 22beb587ab6b0730cc91eaf2403e04120c0a20f4 Mon Sep 17 00:00:00 2001 From: Yvette Carlisle Date: Wed, 13 May 2026 15:50:50 +0800 Subject: [PATCH 1/2] {"schema":"decodex/commit/1","summary":"Sync latest Codex Radar signals","authority":"manual"} --- .../openai-codex-pr-22414.analysis.json | 26 ++++ .../openai-codex-pr-22425.analysis.json | 23 +++ .../github/bundles/openai-codex-pr-22414.json | 142 ++++++++++++++++++ .../github/bundles/openai-codex-pr-22425.json | 93 ++++++++++++ .../release-deltas/openai-codex-latest.json | 2 +- .../signals/openai-codex-pr-22414.json | 50 ++++++ .../signals/openai-codex-pr-22425.json | 45 ++++++ 7 files changed, 380 insertions(+), 1 deletion(-) create mode 100644 artifacts/github/analysis/openai-codex-pr-22414.analysis.json create mode 100644 artifacts/github/analysis/openai-codex-pr-22425.analysis.json create mode 100644 artifacts/github/bundles/openai-codex-pr-22414.json create mode 100644 artifacts/github/bundles/openai-codex-pr-22425.json create mode 100644 site/src/content/signals/openai-codex-pr-22414.json create mode 100644 site/src/content/signals/openai-codex-pr-22425.json diff --git a/artifacts/github/analysis/openai-codex-pr-22414.analysis.json b/artifacts/github/analysis/openai-codex-pr-22414.analysis.json new file mode 100644 index 0000000..31a8262 --- /dev/null +++ b/artifacts/github/analysis/openai-codex-pr-22414.analysis.json @@ -0,0 +1,26 @@ +{ + "caveats": [ + "This is upstream merged code evidence; availability still depends on the Codex build or prerelease channel that includes PR #22414.", + "The PR keeps remote auth-token transport restricted to `wss://` and loopback `ws://` endpoints, so UDS support should not be framed as broad remote-auth relaxation." + ], + "confidence": "confirmed", + "config_flags": [ + "--remote" + ], + "expected_effect": "The remote Codex client connects to the app-server over the Unix socket while reusing the same JSON-RPC protocol; the TUI can also prefer the local daemon socket and fall back to its embedded app-server if the daemon is absent or unresponsive.", + "how_to_try": "On a Codex build that includes PR #22414, point the remote client at a Unix socket endpoint with `codex --remote unix://PATH`, or use `unix://` for the default daemon socket path when the daemon is configured for UDS.", + "impact": "medium", + "kind": "capability", + "proof_points": [ + "The PR body says `codex --remote` now accepts `ws://host:port`, `wss://host:port`, `unix://`, and `unix://PATH`.", + "`codex-rs/app-server-client/src/remote.rs` adds endpoint parsing and transport handling for WebSocket and Unix socket app-server endpoints.", + "`codex-rs/app-server-client/src/lib.rs` reuses the existing JSON-RPC client protocol over either a WebSocket stream or a Unix socket stream.", + "`codex-rs/tui/src/lib.rs` adds a fast startup probe for the default daemon socket and falls back to the embedded app-server when the daemon is missing or unresponsive.", + "The PR records focused coverage for UDS remote round trips, WebSocket auth headers, auth-token transport policy, remote address parsing, and missing-daemon fallback." + ], + "slug": "remote-codex-can-use-unix-sockets", + "summary": "Merged PR #22414 adds Unix domain socket endpoints to `codex --remote`. The remote app-server client can now use `unix://` or `unix://PATH` in addition to WebSocket URLs, while the TUI probes the default daemon socket before falling back to an embedded app-server.", + "title": "Remote Codex can use Unix socket endpoints", + "watch_state": "Track when this lands in a public prerelease, because Decodex Control Plane and app-server integrations may be able to prefer local UDS transport over loopback WebSocket wiring.", + "why_it_matters": "This is directly useful for Decodex Control Plane work built on the Codex app-server: local Unix sockets can reduce exposed loopback surface area and make daemon-style local integrations cleaner without changing the JSON-RPC protocol shape." +} diff --git a/artifacts/github/analysis/openai-codex-pr-22425.analysis.json b/artifacts/github/analysis/openai-codex-pr-22425.analysis.json new file mode 100644 index 0000000..b804d7b --- /dev/null +++ b/artifacts/github/analysis/openai-codex-pr-22425.analysis.json @@ -0,0 +1,23 @@ +{ + "caveats": [ + "This is a plugin/app-server payload behavior change, not evidence of a broader plugin UI redesign.", + "The bundle has no docs or examples refs, so the public framing should stay tied to PR and test evidence." + ], + "confidence": "confirmed", + "config_flags": [], + "expected_effect": null, + "how_to_try": null, + "impact": "low", + "kind": "behavior_change", + "proof_points": [ + "The PR body states that `shared-with-me` remains the request kind while private plugins return under `workspace-shared-with-me-private` and UNLISTED installed workspace plugins return under `workspace-shared-with-me-unlisted`.", + "`codex-rs/core-plugins/src/remote.rs` replaces the old shared-with-me marketplace constant with separate private and unlisted workspace-shared marketplace names.", + "`codex-rs/core-plugins/src/remote/remote_installed_plugin_sync.rs` initializes installed-plugin tracking for both new workspace-shared buckets.", + "App-server and core plugin tests update `plugin/list`, `plugin/read`, `plugin/share`, and share-list expectations from `shared-with-me` IDs to `workspace-shared-with-me-private`, with list coverage for the unlisted bucket." + ], + "slug": "split-shared-workspace-plugins-by-discoverability", + "summary": "Merged PR #22425 keeps `shared-with-me` as the `plugin/list` request kind, but changes the returned workspace-shared plugin buckets: private shares now use `workspace-shared-with-me-private`, and installed unlisted workspace plugins can use `workspace-shared-with-me-unlisted`.", + "title": "Shared workspace plugins are split by discoverability", + "watch_state": "Watch whether downstream plugin clients or cached IDs still assume all shared workspace plugins live under `shared-with-me`.", + "why_it_matters": "App-server and plugin clients get less ambiguous marketplace identities for workspace-shared plugins. That makes it easier to distinguish private shares from unlisted workspace installs without treating every shared workspace plugin as one generic `shared-with-me` bucket." +} diff --git a/artifacts/github/bundles/openai-codex-pr-22414.json b/artifacts/github/bundles/openai-codex-pr-22414.json new file mode 100644 index 0000000..a7f62c0 --- /dev/null +++ b/artifacts/github/bundles/openai-codex-pr-22414.json @@ -0,0 +1,142 @@ +{ + "analysis_mode": "pr_first", + "commits": [ + { + "author": "etraut-openai", + "committed_at": "2026-05-13T02:38:21Z", + "message": "Support UDS and websocket remotes", + "sha": "f31f1b6804de3bf47b89d55886e573e0f314ef6e", + "url": "https://github.com/openai/codex/commit/f31f1b6804de3bf47b89d55886e573e0f314ef6e" + } + ], + "default_branch": "main", + "docs_refs": [], + "examples_refs": [], + "extracted_flags": [ + "UDS", + "--remote", + "TUI", + "JSON", + "TCP", + "PATH", + "--remote-auth-token-env", + "DEFAULT_IN_PROCESS_CHANNEL_CAPACITY", + "JSONRPCM", + "JSONRPCR", + "URL", + "CONNECT_TIMEOUT", + "INITIALIZE_TIMEOUT", + "REMOTE_APP_SERVER_MAX_WEBSOCKET_MESSAGE_SIZE", + "HTTP", + "URI", + "UDS_WEBSOCKET_HANDSHAKE_URL", + "AUTHORIZATION", + "ADDR", + "AUTO_CONNECT_DAEMON_CONNECT_TIMEOUT", + "CODEX_HOME", + "CARGO_PKG_VERSION", + "NUX" + ], + "files": [ + { + "additions": 2, + "deletions": 0, + "patch_excerpt": "@@ -1970,6 +1970,8 @@ dependencies = [\n \"codex-exec-server\",\n \"codex-feedback\",\n \"codex-protocol\",\n+ \"codex-uds\",\n+ \"codex-utils-absolute-path\",\n \"codex-utils-rustls-provider\",\n \"futures\",\n \"pretty_assertions\",", + "path": "codex-rs/Cargo.lock", + "status": "modified" + }, + { + "additions": 2, + "deletions": 0, + "patch_excerpt": "@@ -21,6 +21,8 @@ codex-core = { workspace = true }\n codex-exec-server = { workspace = true }\n codex-feedback = { workspace = true }\n codex-protocol = { workspace = true }\n+codex-uds = { workspace = true }\n+codex-utils-absolute-path = { workspace = true }\n codex-utils-rustls-provider = { workspace = true }\n futures = { workspace = true }\n serde = { workspace = true }", + "path": "codex-rs/app-server-client/Cargo.toml", + "status": "modified" + }, + { + "additions": 101, + "deletions": 22, + "patch_excerpt": "@@ -25,6 +25,7 @@ use std::io::Result as IoResult;\n use std::sync::Arc;\n use std::time::Duration;\n \n+pub use codex_app_server::app_server_control_socket_path;\n pub use codex_app_server::in_process::DEFAULT_IN_PROCESS_CHANNEL_CAPACITY;\n pub use codex_app_server::in_process::InProcessServerEvent;\n use codex_app_server::in_process::InProcessStartArgs;\n@@ -61,6 +62,7 @@ use tracing::warn;\n \n pub use crate::remote::RemoteAppServerClient;\n pub use crate::remote::RemoteAppServerConnectArgs;\n+pub use crate::remote::RemoteAppServerEndpoint;\n \n /// Transitional access to core-only embedded app-server types.\n ///\n@@ -952,6 +954,8 @@ mod tests {\n use codex_app_server_protocol::ToolRequestUserInputQuestion;\n use codex_core::config::ConfigBuilder;\n use codex_core::init_state_db;\n+ use codex_uds::UnixListener;\n+ use codex_utils_absolute_path::AbsolutePathBuf;\n use futures::SinkExt...", + "path": "codex-rs/app-server-client/src/lib.rs", + "status": "modified" + }, + { + "additions": 226, + "deletions": 99, + "patch_excerpt": "@@ -1,11 +1,13 @@\n /*\n-This module implements the websocket-backed app-server client transport.\n+This module implements the remote app-server client transport.\n \n It owns the remote connection lifecycle, including the initialize/initialized\n handshake, JSON-RPC request/response routing, server-request resolution, and\n-notification streaming. The rest of the crate uses the same `AppServerEvent`\n-surface for both in-process and remote transports, so callers such as the TUI\n-can switch between them without changing their higher-level session logic.\n+notification streaming. Remote connections always carry WebSocket frames, over\n+either TCP WebSocket URLs or local Unix sockets. The rest of the crate uses the\n+same `AppServerEvent` surface for both in-process and remote transports, so\n+callers such as the TUI can switch between them without changing their\n+higher-level session logic.\n */\n \n us...", + "path": "codex-rs/app-server-client/src/remote.rs", + "status": "modified" + }, + { + "additions": 33, + "deletions": 26, + "patch_excerpt": "@@ -722,9 +722,9 @@ struct FeatureToggles {\n \n #[derive(Debug, Default, Parser, Clone)]\n struct InteractiveRemoteOptions {\n- /// Connect the TUI to a remote app server websocket endpoint.\n+ /// Connect the TUI to a remote app server endpoint.\n ///\n- /// Accepted forms: `ws://host:port` or `wss://host:port`.\n+ /// Accepted forms: `ws://host:port`, `wss://host:port`, `unix://`, or `unix://PATH`.\n #[arg(long = \"remote\", value_name = \"ADDR\")]\n remote: Option,\n \n@@ -1709,27 +1709,39 @@ async fn run_interactive_tui(\n }\n }\n \n- let normalized_remote = remote\n+ let mut remote_endpoint = remote\n .as_deref()\n- .map(codex_tui::normalize_remote_addr)\n+ .map(codex_tui::resolve_remote_addr)\n .transpose()\n .map_err(std::io::Error::other)?;\n- if remote_auth_token_env.is_some() && normalized_remote.is_none() {\n- ...", + "path": "codex-rs/cli/src/main.rs", + "status": "modified" + }, + { + "additions": 4, + "deletions": 6, + "patch_excerpt": "@@ -80,6 +80,7 @@ use crate::workspace_command::AppServerWorkspaceCommandRunner;\n use crate::workspace_command::WorkspaceCommandRunner;\n use codex_ansi_escape::ansi_escape_line;\n use codex_app_server_client::AppServerRequestHandle;\n+use codex_app_server_client::RemoteAppServerEndpoint;\n use codex_app_server_client::TypedRequestError;\n use codex_app_server_protocol::AddCreditsNudgeCreditType;\n use codex_app_server_protocol::AskForApproval;\n@@ -498,8 +499,7 @@ pub(crate) struct App {\n pub(crate) feedback: codex_feedback::CodexFeedback,\n feedback_audience: FeedbackAudience,\n environment_manager: Arc,\n- remote_app_server_url: Option,\n- remote_app_server_auth_token: Option,\n+ remote_app_server_endpoint: Option,\n /// Set when the user confirms an update; propagated on exit.\n pub(crate) pending_update_acti...", + "path": "codex-rs/tui/src/app.rs", + "status": "modified" + }, + { + "additions": 2, + "deletions": 5, + "patch_excerpt": "@@ -57,11 +57,8 @@ impl App {\n AppEvent::OpenResumePicker => {\n let picker_app_server = match crate::start_app_server_for_picker(\n &self.config,\n- &match self.remote_app_server_url.clone() {\n- Some(websocket_url) => crate::AppServerTarget::Remote {\n- websocket_url,\n- auth_token: self.remote_app_server_auth_token.clone(),\n- },\n+ &match self.remote_app_server_endpoint.clone() {\n+ Some(endpoint) => crate::AppServerTarget::Remote { endpoint },\n None => crate::AppServerTarget::Embedded,\n },\n self.state_db.clone(),", + "path": "codex-rs/tui/src/app/event_dispatch.rs", + "status": "modified" + }, + { + "additions": 1, + "deletions": 1, + "patch_excerpt": "@@ -633,7 +633,7 @@ impl App {\n }\n \n let current_cwd = self.config.cwd.to_path_buf();\n- let resume_cwd = if self.remote_app_server_url.is_some() {\n+ let resume_cwd = if self.remote_app_server_endpoint.is_some() {\n current_cwd.clone()\n } else {\n match crate::session_resume::resolve_cwd_for_resume_or_fork(", + "path": "codex-rs/tui/src/app/session_lifecycle.rs", + "status": "modified" + }, + { + "additions": 1, + "deletions": 2, + "patch_excerpt": "@@ -44,8 +44,7 @@ pub(super) async fn make_test_app() -> App {\n feedback: codex_feedback::CodexFeedback::new(),\n feedback_audience: FeedbackAudience::External,\n environment_manager: Arc::new(EnvironmentManager::default_for_tests()),\n- remote_app_server_url: None,\n- remote_app_server_auth_token: None,\n+ remote_app_server_endpoint: None,\n pending_update_action: None,\n pending_shutdown_exit_thread_id: None,\n windows_sandbox: WindowsSandboxState::default(),", + "path": "codex-rs/tui/src/app/test_support.rs", + "status": "modified" + }, + { + "additions": 2, + "deletions": 4, + "patch_excerpt": "@@ -3992,8 +3992,7 @@ async fn make_test_app() -> App {\n feedback: codex_feedback::CodexFeedback::new(),\n feedback_audience: FeedbackAudience::External,\n environment_manager: Arc::new(EnvironmentManager::default_for_tests()),\n- remote_app_server_url: None,\n- remote_app_server_auth_token: None,\n+ remote_app_server_endpoint: None,\n pending_update_action: None,\n pending_shutdown_exit_thread_id: None,\n windows_sandbox: WindowsSandboxState::default(),\n@@ -4055,8 +4054,7 @@ async fn make_test_app_with_channels() -> (\n feedback: codex_feedback::CodexFeedback::new(),\n feedback_audience: FeedbackAudience::External,\n environment_manager: Arc::new(EnvironmentManager::default_for_tests()),\n- remote_app_server_url: None,\n- remote_app_server_auth_token: None,\n+ remo...", + "path": "codex-rs/tui/src/app/tests.rs", + "status": "modified" + }, + { + "additions": 185, + "deletions": 122, + "patch_excerpt": "@@ -25,6 +25,7 @@ use codex_app_server_client::InProcessAppServerClient;\n use codex_app_server_client::InProcessClientStartArgs;\n use codex_app_server_client::RemoteAppServerClient;\n use codex_app_server_client::RemoteAppServerConnectArgs;\n+pub use codex_app_server_client::RemoteAppServerEndpoint;\n use codex_app_server_protocol::Account as AppServerAccount;\n use codex_app_server_protocol::AskForApproval;\n use codex_app_server_protocol::AuthMode as AppServerAuthMode;\n@@ -272,6 +273,10 @@ pub use public_widgets::composer_input::ComposerAction;\n pub use public_widgets::composer_input::ComposerInput;\n // (tests access modules directly within the crate)\n \n+#[cfg(unix)]\n+const AUTO_CONNECT_DAEMON_CONNECT_TIMEOUT: std::time::Duration =\n+ std::time::Duration::from_millis(50);\n+\n #[allow(clippy::too_many_arguments)]\n async fn start_embedded_app_server(\n arg0_paths: Arg0DispatchPaths,\n@@ -3...", + "path": "codex-rs/tui/src/lib.rs", + "status": "modified" + }, + { + "additions": 1, + "deletions": 2, + "patch_excerpt": "@@ -57,8 +57,7 @@ fn main() -> anyhow::Result<()> {\n inner,\n arg0_paths,\n LoaderOverrides::default(),\n- /*remote*/ None,\n- /*remote_auth_token*/ None,\n+ /*explicit_remote_endpoint*/ None,\n )\n .await?;\n match exit_info.exit_reason {", + "path": "codex-rs/tui/src/main.rs", + "status": "modified" + } + ], + "linked_issues": [], + "notes": [ + "Built from GitHub pull-request, commits, files, and repo endpoints.", + "Discovered via continuous upstream commit sync: https://github.com/openai/codex/commit/ad572709aba1cdaac5ffe181f9cb64dbe07ba239" + ], + "primary_pr": { + "body": "## Why\r\n\r\nAdded support for UDS connections in `codex --remote`.\r\n\r\nTUI also now connects to local app-server using UDS by default if it is running and set to listen to UDS connection. \r\n\r\n## What Changed\r\n\r\n- Introduced `RemoteAppServerEndpoint` with `WebSocket` and `UnixSocket` variants.\r\n- Reused the existing JSON-RPC-over-WebSocket protocol over either a TCP WebSocket stream or a UDS stream.\r\n- Updated `codex --remote` to accept `ws://host:port`, `wss://host:port`, `unix://`, and `unix://PATH`.\r\n- Kept `--remote-auth-token-env` restricted to `wss://` and loopback `ws://` remotes.\r\n- Added a fast TUI startup probe for the default daemon socket, falling back to the embedded app server when the daemon is absent or unresponsive.\r\n\r\n## Verification\r\n\r\n- Manually verified that the updated remote flow works.\r\n- Added coverage for UDS remote round trips, WebSocket auth headers, auth-token transport policy, remote address parsing, and missing-daemon fallback.\r\n- Ran focused remote test coverage locally.\r\n", + "labels": [], + "merged_at": "2026-05-13T04:17:20Z", + "number": 22414, + "state": "merged", + "title": "Add support for UDS in `codex --remote`", + "url": "https://github.com/openai/codex/pull/22414" + }, + "repo": "openai/codex", + "schema": "github_change_bundle/v1" +} diff --git a/artifacts/github/bundles/openai-codex-pr-22425.json b/artifacts/github/bundles/openai-codex-pr-22425.json new file mode 100644 index 0000000..94219fa --- /dev/null +++ b/artifacts/github/bundles/openai-codex-pr-22425.json @@ -0,0 +1,93 @@ +{ + "analysis_mode": "pr_first", + "commits": [ + { + "author": "xl-openai", + "committed_at": "2026-05-13T03:24:01Z", + "message": "feat: Split shared workspace plugins by discoverability", + "sha": "4456c20bd0c8d4fa61e5714dccdf5e65e197b5e8", + "url": "https://github.com/openai/codex/commit/4456c20bd0c8d4fa61e5714dccdf5e65e197b5e8" + } + ], + "default_branch": "main", + "docs_refs": [], + "examples_refs": [], + "extracted_flags": [ + "UNLISTED", + "PRIVATE", + "WORKSPACE", + "REMOTE_GLOBAL_MARKETPLACE_NAME", + "REMOTE_WORKSPACE_MARKETPLACE_NAME", + "REMOTE_SHARED_WITH_ME_MARKETPLACE_NAME", + "REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME", + "REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME", + "REMOTE_GLOBAL_MARKETPLACE_DISPLAY_NAME", + "REMOTE_WORKSPACE_MARKETPLACE_DISPLAY_NAME", + "REMOTE_SHARED_WITH_ME_MARKETPLACE_DISPLAY_NAME", + "REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_DISPLAY_NAME", + "REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_DISPLAY_NAME", + "REMOTE_PLUGIN_CATALOG_TIMEOUT", + "REMOTE_PLUGIN_LIST_PAGE_LIMIT", + "PLUGINS_CACHE_DIR" + ], + "files": [ + { + "additions": 69, + "deletions": 11, + "patch_excerpt": "@@ -1824,13 +1824,27 @@ async fn plugin_list_fetches_shared_with_me_kind() -> Result<()> {\n ))?;\n shared_plugin_body[\"plugins\"][0][\"share_principals\"] = serde_json::Value::Null;\n let shared_plugin_body = serde_json::to_string(&shared_plugin_body)?;\n- let workspace_installed_body = workspace_remote_plugin_page_body(\n- \"plugins~Plugin_22222222222222222222222222222222\",\n- \"shared-linear\",\n- \"Shared Linear\",\n- \"PRIVATE\",\n- /*enabled*/ Some(true),\n- );\n+ let mut workspace_installed_body: serde_json::Value =\n+ serde_json::from_str(&workspace_remote_plugin_page_body(\n+ \"plugins~Plugin_22222222222222222222222222222222\",\n+ \"shared-linear\",\n+ \"Shared Linear\",\n+ \"PRIVATE\",\n+ /*enabled*/ Some(true),\n+ ))?;\n+ let unlisted_installed_body: serde_json::Value =\n+ serd...", + "path": "codex-rs/app-server/tests/suite/v2/plugin_list.rs", + "status": "modified" + }, + { + "additions": 9, + "deletions": 3, + "patch_excerpt": "@@ -303,7 +303,7 @@ async fn plugin_read_returns_share_context_for_shared_remote_plugin() -> Result<\n let request_id = mcp\n .send_plugin_read_request(PluginReadParams {\n marketplace_path: None,\n- remote_marketplace_name: Some(\"shared-with-me\".to_string()),\n+ remote_marketplace_name: Some(\"workspace-shared-with-me-private\".to_string()),\n plugin_name: \"plugins~Plugin_11111111111111111111111111111111\".to_string(),\n })\n .await?;\n@@ -315,8 +315,14 @@ async fn plugin_read_returns_share_context_for_shared_remote_plugin() -> Result<\n .await??;\n let response: PluginReadResponse = to_response(response)?;\n \n- assert_eq!(response.plugin.marketplace_name, \"shared-with-me\");\n- assert_eq!(response.plugin.summary.id, \"shared-linear@shared-with-me\");\n+ assert_eq!(\n+ response.plugin.marketplace_name,\n+ ...", + "path": "codex-rs/app-server/tests/suite/v2/plugin_read.rs", + "status": "modified" + }, + { + "additions": 3, + "deletions": 3, + "patch_excerpt": "@@ -162,7 +162,7 @@ async fn plugin_share_save_uploads_local_plugin() -> Result<()> {\n PluginShareListResponse {\n data: vec![PluginShareListItem {\n plugin: PluginSummary {\n- id: \"demo-plugin@shared-with-me\".to_string(),\n+ id: \"demo-plugin@workspace-shared-with-me-private\".to_string(),\n remote_plugin_id: Some(\"plugins_123\".to_string()),\n local_version: None,\n name: \"demo-plugin\".to_string(),\n@@ -566,7 +566,7 @@ async fn plugin_share_list_returns_created_workspace_plugins() -> Result<()> {\n PluginShareListResponse {\n data: vec![PluginShareListItem {\n plugin: PluginSummary {\n- id: \"demo-plugin@shared-with-me\".to_string(),\n+ id: \"demo-plugin@workspace-shared-with-me-private\".to_string(),...", + "path": "codex-rs/app-server/tests/suite/v2/plugin_share.rs", + "status": "modified" + }, + { + "additions": 68, + "deletions": 23, + "patch_excerpt": "@@ -47,10 +47,15 @@ pub use share::update_remote_plugin_share_targets;\n \n pub const REMOTE_GLOBAL_MARKETPLACE_NAME: &str = \"chatgpt-global\";\n pub const REMOTE_WORKSPACE_MARKETPLACE_NAME: &str = \"workspace-directory\";\n-pub const REMOTE_SHARED_WITH_ME_MARKETPLACE_NAME: &str = \"shared-with-me\";\n+pub const REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME: &str =\n+ \"workspace-shared-with-me-private\";\n+pub const REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME: &str =\n+ \"workspace-shared-with-me-unlisted\";\n pub const REMOTE_GLOBAL_MARKETPLACE_DISPLAY_NAME: &str = \"ChatGPT Plugins\";\n pub const REMOTE_WORKSPACE_MARKETPLACE_DISPLAY_NAME: &str = \"Workspace Directory\";\n-pub const REMOTE_SHARED_WITH_ME_MARKETPLACE_DISPLAY_NAME: &str = \"Shared with me\";\n+pub const REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_DISPLAY_NAME: &str = \"Shared with me\";\n+pub const REMOTE_WORKSP...", + "path": "codex-rs/core-plugins/src/remote.rs", + "status": "modified" + }, + { + "additions": 26, + "deletions": 9, + "patch_excerpt": "@@ -1,6 +1,7 @@\n use super::REMOTE_GLOBAL_MARKETPLACE_NAME;\n-use super::REMOTE_SHARED_WITH_ME_MARKETPLACE_NAME;\n use super::REMOTE_WORKSPACE_MARKETPLACE_NAME;\n+use super::REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME;\n+use super::REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME;\n use super::RemotePluginCatalogError;\n use super::RemotePluginScope;\n use super::RemotePluginServiceConfig;\n@@ -153,7 +154,11 @@ pub async fn sync_remote_installed_plugin_bundles_once(\n BTreeSet::new(),\n ),\n (\n- REMOTE_SHARED_WITH_ME_MARKETPLACE_NAME.to_string(),\n+ REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME.to_string(),\n+ BTreeSet::new(),\n+ ),\n+ (\n+ REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME.to_string(),\n BTreeSet::new(),\n ...", + "path": "codex-rs/core-plugins/src/remote/remote_installed_plugin_sync.rs", + "status": "modified" + }, + { + "additions": 2, + "deletions": 2, + "patch_excerpt": "@@ -586,7 +586,7 @@ async fn list_remote_plugin_shares_fetches_created_workspace_plugins() {\n vec![\n RemotePluginShareSummary {\n summary: RemotePluginSummary {\n- id: \"demo-plugin@shared-with-me\".to_string(),\n+ id: \"demo-plugin@workspace-shared-with-me-private\".to_string(),\n remote_plugin_id: \"plugins_123\".to_string(),\n name: \"demo-plugin\".to_string(),\n share_context: Some(RemotePluginShareContext {\n@@ -625,7 +625,7 @@ async fn list_remote_plugin_shares_fetches_created_workspace_plugins() {\n },\n RemotePluginShareSummary {\n summary: RemotePluginSummary {\n- id: \"demo-plugin@shared-with-me\".to_string(),\n+ id: \"demo-plugin@workspace-shared-with-me-private\".to_string(),\n ...", + "path": "codex-rs/core-plugins/src/remote/share/tests.rs", + "status": "modified" + } + ], + "linked_issues": [], + "notes": [ + "Built from GitHub pull-request, commits, files, and repo endpoints.", + "Discovered via continuous upstream commit sync: https://github.com/openai/codex/commit/7bf95b39aa3c649605f643a24a76b16bc45de917" + ], + "primary_pr": { + "body": " - Keep shared-with-me as the plugin/list request kind, but return private plugins under workspace-shared-with-me-private.\r\n - Add workspace-shared-with-me-unlisted for installed workspace plugins with UNLISTED discoverability,", + "labels": [], + "merged_at": "2026-05-13T04:11:19Z", + "number": 22425, + "state": "merged", + "title": "feat: Split shared workspace plugins by discoverability", + "url": "https://github.com/openai/codex/pull/22425" + }, + "repo": "openai/codex", + "schema": "github_change_bundle/v1" +} diff --git a/site/src/content/release-deltas/openai-codex-latest.json b/site/src/content/release-deltas/openai-codex-latest.json index f4750b3..d621d70 100644 --- a/site/src/content/release-deltas/openai-codex-latest.json +++ b/site/src/content/release-deltas/openai-codex-latest.json @@ -9800,7 +9800,7 @@ ] } ], - "generated_at": "2026-05-12T04:33:20Z", + "generated_at": "2026-05-13T07:48:40Z", "prerelease": { "name": "0.131.0-alpha.9", "prerelease": true, diff --git a/site/src/content/signals/openai-codex-pr-22414.json b/site/src/content/signals/openai-codex-pr-22414.json new file mode 100644 index 0000000..dcf8e9e --- /dev/null +++ b/site/src/content/signals/openai-codex-pr-22414.json @@ -0,0 +1,50 @@ +{ + "caveats": [ + "This is upstream merged code evidence; availability still depends on the Codex build or prerelease channel that includes PR #22414.", + "The PR keeps remote auth-token transport restricted to `wss://` and loopback `ws://` endpoints, so UDS support should not be framed as broad remote-auth relaxation." + ], + "confidence": "confirmed", + "config_flags": [ + "--remote" + ], + "expected_effect": "The remote Codex client connects to the app-server over the Unix socket while reusing the same JSON-RPC protocol; the TUI can also prefer the local daemon socket and fall back to its embedded app-server if the daemon is absent or unresponsive.", + "how_to_try": "On a Codex build that includes PR #22414, point the remote client at a Unix socket endpoint with `codex --remote unix://PATH`, or use `unix://` for the default daemon socket path when the daemon is configured for UDS.", + "impact": "medium", + "kind": "capability", + "lane": "github", + "proof_points": [ + "The PR body says `codex --remote` now accepts `ws://host:port`, `wss://host:port`, `unix://`, and `unix://PATH`.", + "`codex-rs/app-server-client/src/remote.rs` adds endpoint parsing and transport handling for WebSocket and Unix socket app-server endpoints.", + "`codex-rs/app-server-client/src/lib.rs` reuses the existing JSON-RPC client protocol over either a WebSocket stream or a Unix socket stream.", + "`codex-rs/tui/src/lib.rs` adds a fast startup probe for the default daemon socket and falls back to the embedded app-server when the daemon is missing or unresponsive.", + "The PR records focused coverage for UDS remote round trips, WebSocket auth headers, auth-token transport policy, remote address parsing, and missing-daemon fallback." + ], + "published_at": "2026-05-13T04:17:20Z", + "schema": "signal_entry/v1", + "slug": "remote-codex-can-use-unix-sockets", + "source_refs": { + "commit_urls": [ + "https://github.com/openai/codex/commit/f31f1b6804de3bf47b89d55886e573e0f314ef6e" + ], + "items": [ + { + "kind": "pull_request", + "meta": "#22414", + "title": "Add support for UDS in `codex --remote`", + "url": "https://github.com/openai/codex/pull/22414" + }, + { + "kind": "commit", + "meta": "f31f1b6", + "title": "Support UDS and websocket remotes", + "url": "https://github.com/openai/codex/commit/f31f1b6804de3bf47b89d55886e573e0f314ef6e" + } + ], + "pr_url": "https://github.com/openai/codex/pull/22414", + "repo": "openai/codex" + }, + "summary": "Merged PR #22414 adds Unix domain socket endpoints to `codex --remote`. The remote app-server client can now use `unix://` or `unix://PATH` in addition to WebSocket URLs, while the TUI probes the default daemon socket before falling back to an embedded app-server.", + "title": "Remote Codex can use Unix socket endpoints", + "watch_state": "Track when this lands in a public prerelease, because Decodex Control Plane and app-server integrations may be able to prefer local UDS transport over loopback WebSocket wiring.", + "why_it_matters": "This is directly useful for Decodex Control Plane work built on the Codex app-server: local Unix sockets can reduce exposed loopback surface area and make daemon-style local integrations cleaner without changing the JSON-RPC protocol shape." +} diff --git a/site/src/content/signals/openai-codex-pr-22425.json b/site/src/content/signals/openai-codex-pr-22425.json new file mode 100644 index 0000000..f3059c8 --- /dev/null +++ b/site/src/content/signals/openai-codex-pr-22425.json @@ -0,0 +1,45 @@ +{ + "caveats": [ + "This is a plugin/app-server payload behavior change, not evidence of a broader plugin UI redesign.", + "The bundle has no docs or examples refs, so the public framing should stay tied to PR and test evidence." + ], + "confidence": "confirmed", + "config_flags": [], + "impact": "low", + "kind": "behavior_change", + "lane": "github", + "proof_points": [ + "The PR body states that `shared-with-me` remains the request kind while private plugins return under `workspace-shared-with-me-private` and UNLISTED installed workspace plugins return under `workspace-shared-with-me-unlisted`.", + "`codex-rs/core-plugins/src/remote.rs` replaces the old shared-with-me marketplace constant with separate private and unlisted workspace-shared marketplace names.", + "`codex-rs/core-plugins/src/remote/remote_installed_plugin_sync.rs` initializes installed-plugin tracking for both new workspace-shared buckets.", + "App-server and core plugin tests update `plugin/list`, `plugin/read`, `plugin/share`, and share-list expectations from `shared-with-me` IDs to `workspace-shared-with-me-private`, with list coverage for the unlisted bucket." + ], + "published_at": "2026-05-13T04:11:19Z", + "schema": "signal_entry/v1", + "slug": "split-shared-workspace-plugins-by-discoverability", + "source_refs": { + "commit_urls": [ + "https://github.com/openai/codex/commit/4456c20bd0c8d4fa61e5714dccdf5e65e197b5e8" + ], + "items": [ + { + "kind": "pull_request", + "meta": "#22425", + "title": "feat: Split shared workspace plugins by discoverability", + "url": "https://github.com/openai/codex/pull/22425" + }, + { + "kind": "commit", + "meta": "4456c20", + "title": "feat: Split shared workspace plugins by discoverability", + "url": "https://github.com/openai/codex/commit/4456c20bd0c8d4fa61e5714dccdf5e65e197b5e8" + } + ], + "pr_url": "https://github.com/openai/codex/pull/22425", + "repo": "openai/codex" + }, + "summary": "Merged PR #22425 keeps `shared-with-me` as the `plugin/list` request kind, but changes the returned workspace-shared plugin buckets: private shares now use `workspace-shared-with-me-private`, and installed unlisted workspace plugins can use `workspace-shared-with-me-unlisted`.", + "title": "Shared workspace plugins are split by discoverability", + "watch_state": "Watch whether downstream plugin clients or cached IDs still assume all shared workspace plugins live under `shared-with-me`.", + "why_it_matters": "App-server and plugin clients get less ambiguous marketplace identities for workspace-shared plugins. That makes it easier to distinguish private shares from unlisted workspace installs without treating every shared workspace plugin as one generic `shared-with-me` bucket." +} From 4ad9679a93c46341bec9ee7f57970282a61c402d Mon Sep 17 00:00:00 2001 From: Yvette Carlisle Date: Wed, 13 May 2026 15:54:27 +0800 Subject: [PATCH 2/2] {"schema":"decodex/commit/1","summary":"Archive old Radar raw artifacts","authority":"manual"} --- .../index/2026-05-13-pre-2026-04-13.json | 1142 +++++++++++++++++ .../openai-codex-pr-12334.analysis.json | 24 - .../openai-codex-pr-13494.analysis.json | 21 - .../openai-codex-pr-14632.analysis.json | 21 - .../openai-codex-pr-14847.analysis.json | 21 - .../openai-codex-pr-14867.analysis.json | 20 - .../openai-codex-pr-14888.analysis.json | 20 - .../openai-codex-pr-14945.analysis.json | 22 - .../openai-codex-pr-14970.analysis.json | 21 - .../openai-codex-pr-14988.analysis.json | 23 - .../openai-codex-pr-15011.analysis.json | 23 - .../openai-codex-pr-15021.analysis.json | 23 - .../openai-codex-pr-15041.analysis.json | 23 - .../openai-codex-pr-15042.analysis.json | 21 - .../openai-codex-pr-15056.analysis.json | 20 - .../openai-codex-pr-15072.analysis.json | 21 - .../openai-codex-pr-15075.analysis.json | 19 - .../openai-codex-pr-15076.analysis.json | 21 - .../openai-codex-pr-15077.analysis.json | 19 - .../openai-codex-pr-15088.analysis.json | 22 - .../openai-codex-pr-15089.analysis.json | 24 - .../openai-codex-pr-15090.analysis.json | 24 - .../openai-codex-pr-15092.analysis.json | 22 - .../openai-codex-pr-15100.analysis.json | 22 - .../openai-codex-pr-15102.analysis.json | 22 - .../openai-codex-pr-15103.analysis.json | 27 - .../openai-codex-pr-15104.analysis.json | 20 - .../openai-codex-pr-15111.analysis.json | 23 - .../openai-codex-pr-15118.analysis.json | 21 - .../openai-codex-pr-15119.analysis.json | 23 - .../openai-codex-pr-15125.analysis.json | 24 - .../openai-codex-pr-15150.analysis.json | 23 - .../openai-codex-pr-15154.analysis.json | 25 - .../openai-codex-pr-15163.analysis.json | 21 - .../openai-codex-pr-15175.analysis.json | 21 - .../openai-codex-pr-15180.analysis.json | 21 - .../openai-codex-pr-15185.analysis.json | 25 - .../openai-codex-pr-15190.analysis.json | 24 - .../openai-codex-pr-15195.analysis.json | 16 - .../openai-codex-pr-15196.analysis.json | 23 - .../openai-codex-pr-15198.analysis.json | 23 - .../openai-codex-pr-15199.analysis.json | 23 - .../openai-codex-pr-15201.analysis.json | 20 - .../openai-codex-pr-15203.analysis.json | 22 - .../openai-codex-pr-15206.analysis.json | 26 - .../openai-codex-pr-15207.analysis.json | 22 - .../openai-codex-pr-15215.analysis.json | 20 - .../openai-codex-pr-15216.analysis.json | 23 - .../openai-codex-pr-15217.analysis.json | 25 - .../openai-codex-pr-15220.analysis.json | 16 - .../openai-codex-pr-15222.analysis.json | 17 - .../openai-codex-pr-15223.analysis.json | 16 - .../openai-codex-pr-15232.analysis.json | 26 - .../openai-codex-pr-15233.analysis.json | 23 - .../openai-codex-pr-15252.analysis.json | 22 - .../openai-codex-pr-15253.analysis.json | 23 - .../openai-codex-pr-15254.analysis.json | 25 - .../openai-codex-pr-15259.analysis.json | 22 - .../openai-codex-pr-15262.analysis.json | 22 - .../openai-codex-pr-15263.analysis.json | 22 - .../openai-codex-pr-15264.analysis.json | 16 - .../openai-codex-pr-15275.analysis.json | 23 - .../openai-codex-pr-15279.analysis.json | 19 - .../openai-codex-pr-15348.analysis.json | 22 - .../openai-codex-pr-15376.analysis.json | 22 - .../openai-codex-pr-15390.analysis.json | 23 - .../openai-codex-pr-15409.analysis.json | 21 - .../openai-codex-pr-15414.analysis.json | 22 - .../openai-codex-pr-15443.analysis.json | 23 - .../openai-codex-pr-15659.analysis.json | 22 - .../openai-codex-pr-15759.analysis.json | 25 - .../openai-codex-pr-15785.analysis.json | 24 - .../openai-codex-pr-15802.analysis.json | 21 - .../openai-codex-pr-16010.analysis.json | 23 - .../openai-codex-pr-16082.analysis.json | 23 - .../openai-codex-pr-16153.analysis.json | 23 - .../openai-codex-pr-16204.analysis.json | 22 - .../openai-codex-pr-16508.analysis.json | 23 - .../openai-codex-pr-16631.analysis.json | 23 - .../openai-codex-pr-16633.analysis.json | 22 - .../openai-codex-pr-16946.analysis.json | 25 - .../github/bundles/openai-codex-pr-12334.json | 194 --- .../github/bundles/openai-codex-pr-13494.json | 204 --- .../github/bundles/openai-codex-pr-14632.json | 128 -- .../github/bundles/openai-codex-pr-14847.json | 230 ---- .../github/bundles/openai-codex-pr-14867.json | 437 ------- .../github/bundles/openai-codex-pr-14888.json | 82 -- .../github/bundles/openai-codex-pr-14945.json | 107 -- .../github/bundles/openai-codex-pr-14970.json | 181 --- .../github/bundles/openai-codex-pr-14988.json | 475 ------- .../github/bundles/openai-codex-pr-15011.json | 138 -- .../github/bundles/openai-codex-pr-15021.json | 328 ----- .../github/bundles/openai-codex-pr-15041.json | 309 ----- .../github/bundles/openai-codex-pr-15042.json | 277 ---- .../github/bundles/openai-codex-pr-15056.json | 186 --- .../github/bundles/openai-codex-pr-15072.json | 164 --- .../github/bundles/openai-codex-pr-15075.json | 127 -- .../github/bundles/openai-codex-pr-15076.json | 51 - .../github/bundles/openai-codex-pr-15077.json | 65 - .../github/bundles/openai-codex-pr-15088.json | 213 --- .../github/bundles/openai-codex-pr-15089.json | 305 ----- .../github/bundles/openai-codex-pr-15090.json | 190 --- .../github/bundles/openai-codex-pr-15092.json | 48 - .../github/bundles/openai-codex-pr-15100.json | 62 - .../github/bundles/openai-codex-pr-15102.json | 346 ----- .../github/bundles/openai-codex-pr-15103.json | 52 - .../github/bundles/openai-codex-pr-15104.json | 161 --- .../github/bundles/openai-codex-pr-15111.json | 41 - .../github/bundles/openai-codex-pr-15118.json | 92 -- .../github/bundles/openai-codex-pr-15119.json | 285 ---- .../github/bundles/openai-codex-pr-15125.json | 179 --- .../github/bundles/openai-codex-pr-15150.json | 468 ------- .../github/bundles/openai-codex-pr-15154.json | 142 -- .../github/bundles/openai-codex-pr-15163.json | 48 - .../github/bundles/openai-codex-pr-15175.json | 55 - .../github/bundles/openai-codex-pr-15180.json | 60 - .../github/bundles/openai-codex-pr-15185.json | 96 -- .../github/bundles/openai-codex-pr-15190.json | 191 --- .../github/bundles/openai-codex-pr-15195.json | 119 -- .../github/bundles/openai-codex-pr-15196.json | 87 -- .../github/bundles/openai-codex-pr-15198.json | 87 -- .../github/bundles/openai-codex-pr-15199.json | 291 ----- .../github/bundles/openai-codex-pr-15201.json | 50 - .../github/bundles/openai-codex-pr-15203.json | 260 ---- .../github/bundles/openai-codex-pr-15206.json | 71 - .../github/bundles/openai-codex-pr-15207.json | 81 -- .../github/bundles/openai-codex-pr-15215.json | 186 --- .../github/bundles/openai-codex-pr-15216.json | 245 ---- .../github/bundles/openai-codex-pr-15217.json | 121 -- .../github/bundles/openai-codex-pr-15220.json | 181 --- .../github/bundles/openai-codex-pr-15222.json | 99 -- .../github/bundles/openai-codex-pr-15223.json | 285 ---- .../github/bundles/openai-codex-pr-15232.json | 180 --- .../github/bundles/openai-codex-pr-15233.json | 175 --- .../github/bundles/openai-codex-pr-15252.json | 41 - .../github/bundles/openai-codex-pr-15253.json | 1019 --------------- .../github/bundles/openai-codex-pr-15254.json | 161 --- .../github/bundles/openai-codex-pr-15259.json | 355 ----- .../github/bundles/openai-codex-pr-15262.json | 72 -- .../github/bundles/openai-codex-pr-15263.json | 65 - .../github/bundles/openai-codex-pr-15264.json | 104 -- .../github/bundles/openai-codex-pr-15275.json | 110 -- .../github/bundles/openai-codex-pr-15279.json | 86 -- .../github/bundles/openai-codex-pr-15348.json | 96 -- .../github/bundles/openai-codex-pr-15376.json | 51 - .../github/bundles/openai-codex-pr-15390.json | 71 - .../github/bundles/openai-codex-pr-15409.json | 66 - .../github/bundles/openai-codex-pr-15414.json | 63 - .../github/bundles/openai-codex-pr-15443.json | 102 -- .../github/bundles/openai-codex-pr-15659.json | 128 -- .../github/bundles/openai-codex-pr-15759.json | 82 -- .../github/bundles/openai-codex-pr-15785.json | 314 ----- .../github/bundles/openai-codex-pr-15802.json | 98 -- .../github/bundles/openai-codex-pr-16010.json | 142 -- .../github/bundles/openai-codex-pr-16082.json | 320 ----- .../github/bundles/openai-codex-pr-16153.json | 119 -- .../github/bundles/openai-codex-pr-16204.json | 43 - .../github/bundles/openai-codex-pr-16508.json | 855 ------------ .../github/bundles/openai-codex-pr-16631.json | 113 -- .../github/bundles/openai-codex-pr-16633.json | 40 - .../github/bundles/openai-codex-pr-16946.json | 125 -- 161 files changed, 1142 insertions(+), 15837 deletions(-) create mode 100644 artifacts/archive/index/2026-05-13-pre-2026-04-13.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-12334.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-13494.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-14632.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-14847.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-14867.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-14888.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-14945.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-14970.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-14988.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15011.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15021.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15041.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15042.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15056.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15072.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15075.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15076.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15077.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15088.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15089.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15090.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15092.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15100.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15102.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15103.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15104.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15111.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15118.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15119.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15125.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15150.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15154.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15163.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15175.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15180.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15185.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15190.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15195.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15196.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15198.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15199.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15201.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15203.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15206.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15207.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15215.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15216.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15217.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15220.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15222.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15223.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15232.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15233.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15252.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15253.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15254.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15259.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15262.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15263.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15264.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15275.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15279.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15348.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15376.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15390.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15409.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15414.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15443.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15659.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15759.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15785.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-15802.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-16010.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-16082.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-16153.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-16204.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-16508.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-16631.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-16633.analysis.json delete mode 100644 artifacts/github/analysis/openai-codex-pr-16946.analysis.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-12334.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-13494.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-14632.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-14847.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-14867.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-14888.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-14945.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-14970.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-14988.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15011.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15021.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15041.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15042.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15056.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15072.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15075.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15076.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15077.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15088.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15089.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15090.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15092.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15100.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15102.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15103.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15104.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15111.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15118.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15119.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15125.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15150.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15154.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15163.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15175.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15180.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15185.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15190.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15195.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15196.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15198.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15199.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15201.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15203.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15206.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15207.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15215.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15216.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15217.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15220.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15222.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15223.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15232.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15233.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15252.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15253.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15254.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15259.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15262.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15263.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15264.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15275.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15279.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15348.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15376.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15390.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15409.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15414.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15443.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15659.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15759.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15785.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-15802.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-16010.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-16082.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-16153.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-16204.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-16508.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-16631.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-16633.json delete mode 100644 artifacts/github/bundles/openai-codex-pr-16946.json diff --git a/artifacts/archive/index/2026-05-13-pre-2026-04-13.json b/artifacts/archive/index/2026-05-13-pre-2026-04-13.json new file mode 100644 index 0000000..1dd9f26 --- /dev/null +++ b/artifacts/archive/index/2026-05-13-pre-2026-04-13.json @@ -0,0 +1,1142 @@ +{ + "archive_asset": { + "name": "decodex-radar-archive-2026-05-13-pre-2026-04-13.tar.zst", + "sha256": "95c0b0067e727ff4c55d242e82b439c8cbef43cfd2c0e5c2660b5d2e261567e3", + "size_bytes": 226256 + }, + "archive_id": "2026-05-13-pre-2026-04-13", + "checksum_asset": { + "name": "SHA256SUMS", + "sha256": "e5b1781b2adf874b453c069385319080a6f3daea909eff1a6ea715096c2eadc9" + }, + "created_at": "2026-05-13T07:52:56Z", + "cutoff_published_before": "2026-04-13T00:00:00Z", + "files": [ + { + "evidence_date": "2026-03-19T19:26:37Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-12334.json", + "sha256": "1bad183656f2c175b09aeb827e2bc2853d174b401dd45ae0c5d44dbd98240472", + "size_bytes": 23416 + }, + { + "evidence_date": "2026-03-19T19:26:37Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-12334.analysis.json", + "sha256": "b8c096f2cc587a8fbb65a7aed0c01a1d44e5106686644cc329da20e80db85967", + "size_bytes": 2153 + }, + { + "evidence_date": "2026-03-18T22:44:31Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-13494.json", + "sha256": "1a2a1ee1543a8ab4cb4909ec433f9571e7a8d2cc547f97d819fe1997d42d90bb", + "size_bytes": 12573 + }, + { + "evidence_date": "2026-03-18T22:44:31Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-13494.analysis.json", + "sha256": "d1fea212e9fcd4401493c1ca54bc74e7d81a710a488fd98d5e598e3e33c633c9", + "size_bytes": 2007 + }, + { + "evidence_date": "2026-03-19T03:41:07Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-14632.json", + "sha256": "2a3c81d178ec88e2b375be1ded8dc9733fe3420a5082f91b7d522e1cfd12b3bc", + "size_bytes": 10856 + }, + { + "evidence_date": "2026-03-19T03:41:07Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-14632.analysis.json", + "sha256": "d33d25358563eebb25cf3b4f012d0bb5fe9c509f611f44d7e541a3a0a88d6b07", + "size_bytes": 1804 + }, + { + "evidence_date": "2026-03-25T19:35:57Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-14847.json", + "sha256": "f37682357e5899989c37e8bdb94330eff423cbeb609813ef1ec1732abfb77b50", + "size_bytes": 18549 + }, + { + "evidence_date": "2026-03-25T19:35:57Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-14847.analysis.json", + "sha256": "8f6c452ebf71504e2ea45e9459de902de58d9938d06ddbc3aa021fc865973da0", + "size_bytes": 2312 + }, + { + "evidence_date": "2026-03-19T17:53:09Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-14867.json", + "sha256": "ff446056e958b1b8b18191562e87040944953cde3822589dc30a4a8d6cdc3449", + "size_bytes": 51307 + }, + { + "evidence_date": "2026-03-19T17:53:09Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-14867.analysis.json", + "sha256": "a90a77610b783457d2cb2e05a35b00edc51619f885d8d082d23c66eaeaae48cb", + "size_bytes": 1905 + }, + { + "evidence_date": "2026-03-18T22:45:17Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-14888.json", + "sha256": "c0acc0718264a6afc76c0416669b817d4dbd9fb90c8b572ed3531caadf9620d9", + "size_bytes": 5541 + }, + { + "evidence_date": "2026-03-18T22:45:17Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-14888.analysis.json", + "sha256": "445229c00f6f7093d668ca2f12b101b19249c6f69a1927fbd28f1228665d534e", + "size_bytes": 1742 + }, + { + "evidence_date": "2026-03-18T17:54:12Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-14945.json", + "sha256": "a4f12370d9398c76befade78e696691872ec73a1f85db4424cb8ea6371f8097c", + "size_bytes": 14293 + }, + { + "evidence_date": "2026-03-18T17:54:12Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-14945.analysis.json", + "sha256": "1c7ac6c2a65d3a00f91a43a9e2a768890c02e50c09f8b2b290aeb300a658bf0e", + "size_bytes": 1961 + }, + { + "evidence_date": "2026-03-19T05:24:10Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-14970.json", + "sha256": "8dbea4092d712a7d2951cbf974f3d5f74ac65fe3f8185bfb93e9a55294066077", + "size_bytes": 15570 + }, + { + "evidence_date": "2026-03-19T05:24:10Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-14970.analysis.json", + "sha256": "671cafd96ae0c82e172637bac8616f843337abac25b04d92a29dd392ee5baeee", + "size_bytes": 2130 + }, + { + "evidence_date": "2026-03-19T05:42:41Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-14988.json", + "sha256": "091a87a6d5862527dfd19fed61b6a879d06c24dfb5e8f0d7c5686d86f4c541fd", + "size_bytes": 48347 + }, + { + "evidence_date": "2026-03-19T05:42:41Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-14988.analysis.json", + "sha256": "4ec15046c1835e61863a79a6179155a9fd854cf28506fc37c1e5ad77590e59ac", + "size_bytes": 2131 + }, + { + "evidence_date": "2026-03-19T04:29:37Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15011.json", + "sha256": "e7d0bfbb37b1af2391cf74b14a5a05121847f4625c08582dac2749a4c34066f1", + "size_bytes": 10197 + }, + { + "evidence_date": "2026-03-19T04:29:37Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15011.analysis.json", + "sha256": "e5e9906509f848ca9dd3ce5e2ec81ccd9790520f0bca018199c26018177bf990", + "size_bytes": 1972 + }, + { + "evidence_date": "2026-03-20T01:05:24Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15021.json", + "sha256": "0de15354477d3a6e0f5d9c265f363fe115d6c8243b97ee6a46fde5d1343417e9", + "size_bytes": 21805 + }, + { + "evidence_date": "2026-03-20T01:05:24Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15021.analysis.json", + "sha256": "688b720d01bc42c9d0e83cf70ef7db85fd4b9c0a16ad8e7fdebbde643bf22670", + "size_bytes": 1972 + }, + { + "evidence_date": "2026-03-19T07:46:16Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15041.json", + "sha256": "8a15cc56793868e0acf7551527515521c466d0b11ce2c3f23eb1fe708c6b5426", + "size_bytes": 32327 + }, + { + "evidence_date": "2026-03-19T07:46:16Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15041.analysis.json", + "sha256": "02a8adb8251588f9a301ebbaeeaf04bc5bdffb646f0ef99558be085029591978", + "size_bytes": 2170 + }, + { + "evidence_date": "2026-03-19T00:45:31Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15042.json", + "sha256": "0eb1901375b63eb04b642a07463b01527564bf716c016e9ec96507eba046c9e3", + "size_bytes": 19508 + }, + { + "evidence_date": "2026-03-19T00:45:31Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15042.analysis.json", + "sha256": "405a6ecd41c79421987706caeccae3b2b1c0384833476dea9ee223abc87d93b8", + "size_bytes": 1958 + }, + { + "evidence_date": "2026-03-19T10:21:26Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15056.json", + "sha256": "70514c4fb49c182b7454e7f736a97dd4a8af4b6bea75c9e1faaa8bea0600d3ac", + "size_bytes": 14939 + }, + { + "evidence_date": "2026-03-19T10:21:26Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15056.analysis.json", + "sha256": "b857dde6356633482a1e854675908dc3cea173c830d27a40910b81dc6f6eb667", + "size_bytes": 1491 + }, + { + "evidence_date": "2026-03-18T20:58:21Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15072.json", + "sha256": "1fe724f8dbc227d3e6f0d515257023cda08b8f2620724311b242a77ade149ed5", + "size_bytes": 14884 + }, + { + "evidence_date": "2026-03-18T20:58:21Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15072.analysis.json", + "sha256": "0ddc9e5ca378816267430ec94947ae0eaaeba5f2a1e4ca73529a7172b6b1461a", + "size_bytes": 2080 + }, + { + "evidence_date": "2026-03-18T20:57:56Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15075.json", + "sha256": "501a13171a88eb7664b77b310cfb697544138af272ee8658ba084a53c56b56f3", + "size_bytes": 11339 + }, + { + "evidence_date": "2026-03-18T20:57:56Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15075.analysis.json", + "sha256": "ee7bce301b4a60c37f07b948dff5948deb175411ab501cf8e0a8a996b26b9c91", + "size_bytes": 1672 + }, + { + "evidence_date": "2026-03-18T21:21:31Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15076.json", + "sha256": "b36952596090bacee1c4493b5a168a00205ff75dfb91463d46f3a18ca7eb2be8", + "size_bytes": 2923 + }, + { + "evidence_date": "2026-03-18T21:21:31Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15076.analysis.json", + "sha256": "8035c9c408adeb1057ef7b914dc31627cab43e34a00b8eea9c78d229f0035eb2", + "size_bytes": 2089 + }, + { + "evidence_date": "2026-03-18T22:19:50Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15077.json", + "sha256": "8a0e25aac67a124336425a6955ca8d67c15229112181bb3be9efb1330a49074b", + "size_bytes": 4781 + }, + { + "evidence_date": "2026-03-18T22:19:50Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15077.analysis.json", + "sha256": "b3133d690917b8f98671e4094179fcc49773f5cfdc255c533fdfa4ed1c47af05", + "size_bytes": 1771 + }, + { + "evidence_date": "2026-03-19T00:57:48Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15088.json", + "sha256": "feab670298ad6f2c2db828a4283fd8731e0756715ecb68a5b9231876b4c327c7", + "size_bytes": 18338 + }, + { + "evidence_date": "2026-03-19T00:57:48Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15088.analysis.json", + "sha256": "bc6a716e101d066f4dddb89341312c306b1d13dd4559b10967a33445c347ffb6", + "size_bytes": 2105 + }, + { + "evidence_date": "2026-03-19T00:30:05Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15089.json", + "sha256": "e9074ac51ec59ea6d4d9a7fa7d7d07a97dc4245d625051b4d6931ea399689a73", + "size_bytes": 26119 + }, + { + "evidence_date": "2026-03-19T00:30:05Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15089.analysis.json", + "sha256": "def8ef6662a5ec5711aa79312ea8328727eeb5ddd2120f216721f29cd1079f2b", + "size_bytes": 2239 + }, + { + "evidence_date": "2026-03-19T19:00:37Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15090.json", + "sha256": "185ad19b7061e6db7059a66ba8c2ae95e385ed18224b69651f859366c2ef04de", + "size_bytes": 17912 + }, + { + "evidence_date": "2026-03-19T19:00:37Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15090.analysis.json", + "sha256": "5a541addce9edcd7b29b101e2b4b530feeef95872a5c5fff13b26098d09186ec", + "size_bytes": 2719 + }, + { + "evidence_date": "2026-03-18T20:52:33Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15092.json", + "sha256": "88cea150d9e76aa176641ba6e577af94f0bc9247ef0d8c86e8cbf44994c84a9f", + "size_bytes": 2330 + }, + { + "evidence_date": "2026-03-18T20:52:33Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15092.analysis.json", + "sha256": "8834598a14f07718ac810be7a87e986b0651b2b29513d76cd4ff75a355eff81d", + "size_bytes": 1681 + }, + { + "evidence_date": "2026-03-18T23:11:11Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15100.json", + "sha256": "82d302c7025094263520beb8c78c42d2374b7bdfd1a3a12017a606c7b5a0163e", + "size_bytes": 3995 + }, + { + "evidence_date": "2026-03-18T23:11:11Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15100.analysis.json", + "sha256": "bb3201f0038f8c406dd1f385b837be62f7cef1b6f117794b12c40543c9cfa646", + "size_bytes": 1620 + }, + { + "evidence_date": "2026-03-18T22:19:29Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15102.json", + "sha256": "814ce70ff12d158bf373ee26eecc1ba9b08ecd2f58487e80009c4695373ab4fa", + "size_bytes": 35675 + }, + { + "evidence_date": "2026-03-18T22:19:29Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15102.analysis.json", + "sha256": "b83cb15889db7c8073d9933863ce78ff8777119dd831b657cb5d639b6144247b", + "size_bytes": 1690 + }, + { + "evidence_date": "2026-03-18T23:10:51Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15103.json", + "sha256": "58a737cbc6f9444866c40cb1c1bfbc46f3952032cd1eaebaa46dcd501fb5a2af", + "size_bytes": 3243 + }, + { + "evidence_date": "2026-03-18T23:10:51Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15103.analysis.json", + "sha256": "aa59f0033484c405559c8784ea7360a91b8255fdc7aff155fc5f0f157d2fde19", + "size_bytes": 2252 + }, + { + "evidence_date": "2026-03-19T00:03:37Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15104.json", + "sha256": "06b2cccc689184450680d10ee22f63038ad045c494a3c92f162276b79bc60b3e", + "size_bytes": 15565 + }, + { + "evidence_date": "2026-03-19T00:03:37Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15104.analysis.json", + "sha256": "4fd8e6d59f39f2bf04f6b22055ea158c7e438966ca22e7918a32d92bff5eecb2", + "size_bytes": 1808 + }, + { + "evidence_date": "2026-03-18T22:54:13Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15111.json", + "sha256": "1ecbb0f32736eae89a4a0fa9cd6e2ef121b424132c47cc01640f02b3313ca8b1", + "size_bytes": 2202 + }, + { + "evidence_date": "2026-03-18T22:54:13Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15111.analysis.json", + "sha256": "83a6b24c27a7f4bef0efd32249d5fc1961cfe6727dcd0ffe2f56ec079a3ecceb", + "size_bytes": 1880 + }, + { + "evidence_date": "2026-03-19T04:48:31Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15118.json", + "sha256": "f4a0bb514705f10ecfb817b650f6ff6cc60359d7e662a590fe82848120e0edc0", + "size_bytes": 9992 + }, + { + "evidence_date": "2026-03-19T04:48:31Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15118.analysis.json", + "sha256": "eda5b80a47e8d143c3bb0d90ed41c9bf009b3964e7adb94cd2d9a90e1a52d323", + "size_bytes": 1731 + }, + { + "evidence_date": "2026-03-19T01:00:36Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15119.json", + "sha256": "e26df8674baf138f3b0629331600645da239e2fe6c4b84fc0a54cc693fe49610", + "size_bytes": 23417 + }, + { + "evidence_date": "2026-03-19T01:00:36Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15119.analysis.json", + "sha256": "6e579da116038645c90327ce7396448e103d699e93ade85ea902db371cf45bf7", + "size_bytes": 1783 + }, + { + "evidence_date": "2026-03-19T15:31:15Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15125.json", + "sha256": "951a8233a45246f317bae11fd1c735a5fd4a7fcf52404a10695d175a518240d8", + "size_bytes": 12998 + }, + { + "evidence_date": "2026-03-19T15:31:15Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15125.analysis.json", + "sha256": "bf20cc0c0ecc3aa1b473a8106862be482584d2d740ba0934255c45a0e17c77fa", + "size_bytes": 2141 + }, + { + "evidence_date": "2026-03-20T01:58:18Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15150.json", + "sha256": "a1341e9ead0b7eb237ed4bcd9930a1e6e151a0b1b4f34e8c8a941d266d39290c", + "size_bytes": 37070 + }, + { + "evidence_date": "2026-03-20T01:58:18Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15150.analysis.json", + "sha256": "81da8e7e2ee334e01aafddfd5dde891c76edbf8be4c53fd25df966acd79ce821", + "size_bytes": 1796 + }, + { + "evidence_date": "2026-03-19T21:29:23Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15154.json", + "sha256": "f8dfcfee27a7841c9ac4ed676426846ea79cb45f435332fef9c9c8d2524f1e7c", + "size_bytes": 9748 + }, + { + "evidence_date": "2026-03-19T21:29:23Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15154.analysis.json", + "sha256": "f77326558f8f11f24d89cd6540f4fb55961c0530305a39d0ba68a6cb467c87b3", + "size_bytes": 2396 + }, + { + "evidence_date": "2026-03-19T12:12:51Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15163.json", + "sha256": "edbf63a1a1c058d6a0ce4b8404305f15ef9a3cb38f164785002ff1f61fe814f6", + "size_bytes": 3064 + }, + { + "evidence_date": "2026-03-19T12:12:51Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15163.analysis.json", + "sha256": "9ef5c79833474ac61a6501fcfea4548d24609541813bb3ffa56d792f8d3188fe", + "size_bytes": 1810 + }, + { + "evidence_date": "2026-03-19T15:48:28Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15175.json", + "sha256": "ef765d7fdd5467900d4888edcaf5c10d1f9bf32458729c0c3004c613f0b793d1", + "size_bytes": 3017 + }, + { + "evidence_date": "2026-03-19T15:48:28Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15175.analysis.json", + "sha256": "68c7cfea3ed15e453818e983002be290f7c13363c133bcaa5f79b87739233e79", + "size_bytes": 1504 + }, + { + "evidence_date": "2026-03-19T15:48:02Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15180.json", + "sha256": "7d89f27263d7f49f011f91165277f3bf34c5b59ff8e9be5a5689e75815999a01", + "size_bytes": 3179 + }, + { + "evidence_date": "2026-03-19T15:48:02Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15180.analysis.json", + "sha256": "9bf79a309cc4b58005eb50d7d7ac0d870dc275cd898994517a886a85ada97ba5", + "size_bytes": 1410 + }, + { + "evidence_date": "2026-03-19T17:38:54Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15185.json", + "sha256": "f27183f04ad63d5e2bfecc8d9092c10b534ad8afa7f5d03685f28740c1cc9b10", + "size_bytes": 7823 + }, + { + "evidence_date": "2026-03-19T17:38:54Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15185.analysis.json", + "sha256": "72b21420e6c34c9c70663077c3e040a4ce503e95000958373c2dee97478b04c1", + "size_bytes": 2083 + }, + { + "evidence_date": "2026-03-19T22:05:14Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15190.json", + "sha256": "01c48cdeb54a7888f9f7fde5ed6d5ba2b6b2d65df967fca977bdca75a89d6cc8", + "size_bytes": 15192 + }, + { + "evidence_date": "2026-03-19T22:05:14Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15190.analysis.json", + "sha256": "184d67ffff9c41387a5c04938193113305668eecfc8f223cf171f1d2d418040c", + "size_bytes": 2386 + }, + { + "evidence_date": "2026-03-20T02:36:59Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15195.json", + "sha256": "3403bf47b2bf9dab7a099516f2e8fb474bdd66a07722e1d3e88f4ea15652fe11", + "size_bytes": 8521 + }, + { + "evidence_date": "2026-03-20T02:36:59Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15195.analysis.json", + "sha256": "20eb2e04382e52999c8b91542c2878f305238637e0bc309ab0f3f07f2c5093ba", + "size_bytes": 1394 + }, + { + "evidence_date": "2026-03-19T18:25:12Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15196.json", + "sha256": "2935d6c7491a5830445b8b973d0b61052136bfcc97a4ec21a4ef6af64f113d4c", + "size_bytes": 7278 + }, + { + "evidence_date": "2026-03-19T18:25:12Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15196.analysis.json", + "sha256": "b3dae5d545d360c5fbfda0d53aa4519a39a03aab2d0dd203f30abc13418ee01c", + "size_bytes": 1928 + }, + { + "evidence_date": "2026-03-19T18:59:03Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15198.json", + "sha256": "82d127092542e20d9934cf40c72fc4e126725e12b5314f8006bf451eb2297972", + "size_bytes": 8559 + }, + { + "evidence_date": "2026-03-19T18:59:03Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15198.analysis.json", + "sha256": "b5b0aae985e611d3dbbabac54f5a8a47cec8f59f968852bd4cc9a2c0d17c6255", + "size_bytes": 2047 + }, + { + "evidence_date": "2026-03-20T03:19:22Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15199.json", + "sha256": "eefc1ebfc94117f60e3d25d4e930bc08a283824146f5e4c82fb50ece959b2f71", + "size_bytes": 29994 + }, + { + "evidence_date": "2026-03-20T03:19:22Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15199.analysis.json", + "sha256": "bd26bed0ee5f93c1de42c7e4519cf34e497ff42520a51936cc453c019ba3e710", + "size_bytes": 2498 + }, + { + "evidence_date": "2026-03-19T19:10:42Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15201.json", + "sha256": "f920a0c644dfd057f4c0e24e69baceaf68019a604218a0127c31da04c29dbd69", + "size_bytes": 3135 + }, + { + "evidence_date": "2026-03-19T19:10:42Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15201.analysis.json", + "sha256": "e70ee4a09fe3534bb9f30ea3115afca20310e9afdacaf12c6bef9c76d0f43b3a", + "size_bytes": 1524 + }, + { + "evidence_date": "2026-03-20T19:08:25Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15203.json", + "sha256": "b88370bb7f29788f5fea875c10eac940bbc81ca4ad8860048eda8cc32c6a5f8f", + "size_bytes": 19662 + }, + { + "evidence_date": "2026-03-20T19:08:25Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15203.analysis.json", + "sha256": "bca45f2888c393f746bf60ff1b50b5d599c43a4b7b4b6bb7b58966e4580ab726", + "size_bytes": 1721 + }, + { + "evidence_date": "2026-03-19T20:07:19Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15206.json", + "sha256": "88effee79e5773918fa566c27fbffe27fb7d503d5e171fb3289085e17210cf90", + "size_bytes": 5113 + }, + { + "evidence_date": "2026-03-19T20:07:19Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15206.analysis.json", + "sha256": "a2a66c3212a7b404b280a61f1747df5c21a0cca58ad1ba06a82aaf132da96be9", + "size_bytes": 1665 + }, + { + "evidence_date": "2026-03-19T21:09:35Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15207.json", + "sha256": "3b881babd618cd24b49400bfb04847c8089ee856da4185d9b1b3e6208e69940e", + "size_bytes": 8383 + }, + { + "evidence_date": "2026-03-19T21:09:35Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15207.analysis.json", + "sha256": "a02d9da00b1979939b80a870a437fe338ed9c482c8aa2e153ac087d2760c1957", + "size_bytes": 2077 + }, + { + "evidence_date": "2026-03-20T04:28:33Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15215.json", + "sha256": "016fb6f9ae60813fa1d4be85df4ca5546560d1d21dc1577e4c10d20a0806f25b", + "size_bytes": 18154 + }, + { + "evidence_date": "2026-03-20T04:28:33Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15215.analysis.json", + "sha256": "8dfb630d870100affd72f4c095ff9cc28a8c249c920335c8da7188a0c9761d81", + "size_bytes": 1491 + }, + { + "evidence_date": "2026-03-19T21:08:05Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15216.json", + "sha256": "e40ecf79e03b9b916cfb3b3a17f0430a3135310a44378e6fd09a4a53810e08b0", + "size_bytes": 21289 + }, + { + "evidence_date": "2026-03-19T21:08:05Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15216.analysis.json", + "sha256": "5659a2e0d7b8fbe5acda6d3581edb7f173748bb036ae84c8680bde8754b63aab", + "size_bytes": 2310 + }, + { + "evidence_date": "2026-03-19T22:02:45Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15217.json", + "sha256": "e512b4320acac72552c8c490f6b4b5a15cab6921bdb8d8f302e7a6e193702888", + "size_bytes": 9287 + }, + { + "evidence_date": "2026-03-19T22:02:45Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15217.analysis.json", + "sha256": "0c8904e3bb987da0269505edc4b434e43d13b59a82a57a84f659f53b0132c9d4", + "size_bytes": 2199 + }, + { + "evidence_date": "2026-03-19T22:09:59Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15220.json", + "sha256": "358237270b7c62af62047f7703c8a7fcc4db5d37e8ee060c52f299e90adf19a4", + "size_bytes": 17361 + }, + { + "evidence_date": "2026-03-19T22:09:59Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15220.analysis.json", + "sha256": "332c565090e5623f955196b2c1fe9314a15a182aa956ffdc5da85d509cc8af3d", + "size_bytes": 1275 + }, + { + "evidence_date": "2026-03-19T22:16:26Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15222.json", + "sha256": "cbffc9b580740354d8c048baedded59dd73f5fe836138a5d6a3ee14349114d51", + "size_bytes": 7924 + }, + { + "evidence_date": "2026-03-19T22:16:26Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15222.analysis.json", + "sha256": "9a808f0f296a397e9840e7594ad8f1afaf4f0da1b96e28e1fe2ae44335e4de43", + "size_bytes": 1465 + }, + { + "evidence_date": "2026-03-20T05:57:17Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15223.json", + "sha256": "6f95010541f9a59fba406c181bd561af71bd26594daa5089960f509fcf1ec5fb", + "size_bytes": 21413 + }, + { + "evidence_date": "2026-03-20T05:57:17Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15223.analysis.json", + "sha256": "4ca0a51429e6aabfa12f8c12f536e37e87008747e98b389a55d03fe5e22b1dd2", + "size_bytes": 1385 + }, + { + "evidence_date": "2026-03-20T00:08:04Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15232.json", + "sha256": "e8ca0cf66a7de7fa3a977450881d6eee4b129fd1991a9c0bf8ed89b547f81950", + "size_bytes": 15014 + }, + { + "evidence_date": "2026-03-20T00:08:04Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15232.analysis.json", + "sha256": "01a4b7d561838806a937e2b8b772d7c6e2c545cd086ead021a37174b77cfec92", + "size_bytes": 2337 + }, + { + "evidence_date": "2026-03-20T03:13:08Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15233.json", + "sha256": "ded8f9c1e38973b4ff41825adb056d1aef4db050875f4e917241d1822c77fd31", + "size_bytes": 15531 + }, + { + "evidence_date": "2026-03-20T03:13:08Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15233.analysis.json", + "sha256": "eccf6b82d1644f18602ee76befde5980bf89baad8d7916396313c73032b12b89", + "size_bytes": 1960 + }, + { + "evidence_date": "2026-03-20T04:31:57Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15252.json", + "sha256": "cf6fcd63a72d727a2c334b0de29b4f1747229e994ee77a9ec514d2f8e5c57f9a", + "size_bytes": 1812 + }, + { + "evidence_date": "2026-03-20T04:31:57Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15252.analysis.json", + "sha256": "f6dd8bf0f18bce9d0c7f99d57fb54aeeef413736bac540e740c4aa97d73f9b1b", + "size_bytes": 1699 + }, + { + "evidence_date": "2026-03-20T03:12:07Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15253.json", + "sha256": "7e33fb5fd4d966a0954f5979b66411f0f00abb04c0331546b6131ae9ffaecc1a", + "size_bytes": 91193 + }, + { + "evidence_date": "2026-03-20T03:12:07Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15253.analysis.json", + "sha256": "4d4f9325c01a5fb6e3fad1cb3d1b05005bc39576571f6695f1441ac67e89ccfd", + "size_bytes": 2264 + }, + { + "evidence_date": "2026-03-20T02:38:12Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15254.json", + "sha256": "e8d988e4b8346ae21581726bd0c7b25289ccc98369d8d1524bc89ebe0208111b", + "size_bytes": 18893 + }, + { + "evidence_date": "2026-03-20T02:38:12Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15254.analysis.json", + "sha256": "1de2e4b7e5f90ca16bd523b7c1b0e9fcee7465b7da83a0d42e7f673d7919e2d7", + "size_bytes": 2500 + }, + { + "evidence_date": "2026-03-23T17:19:45Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15259.json", + "sha256": "58cfe946cc8606983a5f031d72f57e236f287c0112613e2e131a7422ad3d26d0", + "size_bytes": 47564 + }, + { + "evidence_date": "2026-03-23T17:19:45Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15259.analysis.json", + "sha256": "ae9822e3cc28906032b48ca852803973dfe1439a1210fa99321ff97b0424fa7f", + "size_bytes": 2349 + }, + { + "evidence_date": "2026-03-20T05:35:52Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15262.json", + "sha256": "8f66c151421e5246e089415ebab38446a94d926730adec2477dce12507c8ce3d", + "size_bytes": 5048 + }, + { + "evidence_date": "2026-03-20T05:35:52Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15262.analysis.json", + "sha256": "049da6191793f3666305b7c8f36d29fb182d071bc81035785a7bf82024bbdfc8", + "size_bytes": 1842 + }, + { + "evidence_date": "2026-03-20T03:02:40Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15263.json", + "sha256": "91ed00552c2e3af02a313dc41ea188dd4212d39572094e5ac3ccfaf7976b58bf", + "size_bytes": 5471 + }, + { + "evidence_date": "2026-03-20T03:02:40Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15263.analysis.json", + "sha256": "988178c4dbc19a41b0425463616f39125c5f5b533fe61a7486116e4f55acbc87", + "size_bytes": 2069 + }, + { + "evidence_date": "2026-03-20T05:01:39Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15264.json", + "sha256": "8164c41b8244bcf5491a4aec05eea11f4e65a8bf0254972ae2a5acd12e0eadbc", + "size_bytes": 8646 + }, + { + "evidence_date": "2026-03-20T05:01:39Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15264.analysis.json", + "sha256": "1aef91f79b3737a5eb1591a2cce2fb4b9698c80d17373c7d46c80f4458d6ceec", + "size_bytes": 1350 + }, + { + "evidence_date": "2026-03-20T07:06:24Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15275.json", + "sha256": "a4493123482233e1b0e2c2e9a60cbf57dff366bd93f2b3f36fdff8d0d08cc8d9", + "size_bytes": 8796 + }, + { + "evidence_date": "2026-03-20T07:06:24Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15275.analysis.json", + "sha256": "3ee2cde3f4528534a85379c7304fadf1564190763a0f1dac738e8884b3e46957", + "size_bytes": 1990 + }, + { + "evidence_date": "2026-03-23T17:10:17Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15279.json", + "sha256": "ad5ddef42780a55a3b9abfb5c83b9df9e628d3cee2bb539e5e465c720fcf0aff", + "size_bytes": 8463 + }, + { + "evidence_date": "2026-03-23T17:10:17Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15279.analysis.json", + "sha256": "5a16b661aace583af8dc3dd29498ef854a7badb181b36508efe7ac3c526ce9db", + "size_bytes": 2005 + }, + { + "evidence_date": "2026-03-21T01:42:41Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15348.json", + "sha256": "224cea51c0c946fe670639428235f4450a79721c497bad56038040258f264c04", + "size_bytes": 6600 + }, + { + "evidence_date": "2026-03-21T01:42:41Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15348.analysis.json", + "sha256": "e8149475b1b3449cd8a61568625aeb9efed6f1f10b98a67f045e48a5c2a02d01", + "size_bytes": 1723 + }, + { + "evidence_date": "2026-03-22T07:17:48Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15376.json", + "sha256": "dfdf9ea307b3713a919239d6efeac2b30b18bf51a6f9ccfb2e5745badb50b223", + "size_bytes": 2764 + }, + { + "evidence_date": "2026-03-22T07:17:48Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15376.analysis.json", + "sha256": "646f777adc290a9e1caee6e2dc9fd172c3c74c0f77661b6a2a736180598c35c1", + "size_bytes": 1898 + }, + { + "evidence_date": "2026-03-21T18:29:33Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15390.json", + "sha256": "436687b531c4a6d02441a95a13b4ca252c5c480340ebe97544456e670ca9239e", + "size_bytes": 5124 + }, + { + "evidence_date": "2026-03-21T18:29:33Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15390.analysis.json", + "sha256": "c6e1b8a8c2755985b2794090b3ae03b51e06cb70a6e4a7236fea54ec31ec7d26", + "size_bytes": 2065 + }, + { + "evidence_date": "2026-03-21T20:43:15Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15409.json", + "sha256": "775d6ecf51ffc14cdd44705df5eb7876d446f43748078edfd414dd79395cf574", + "size_bytes": 4497 + }, + { + "evidence_date": "2026-03-21T20:43:15Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15409.analysis.json", + "sha256": "61cc2b1f3a8a4dcd35f1a66f6c77a6625a66a65541886edf91bd832536dc429c", + "size_bytes": 1467 + }, + { + "evidence_date": "2026-03-21T21:06:11Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15414.json", + "sha256": "a58855457ff8e312a2859d957e569883fded4ce2d4513887ec969c154451dbbf", + "size_bytes": 3936 + }, + { + "evidence_date": "2026-03-21T21:06:11Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15414.analysis.json", + "sha256": "bea41079064b05715750f83e8433b6af52e67edfbb4df3252bcb949c487e675a", + "size_bytes": 1987 + }, + { + "evidence_date": "2026-03-23T01:24:14Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15443.json", + "sha256": "24291775f698cc7e0b255db0e14995d256d8b4305f4611fd3bb5bebb008e1e79", + "size_bytes": 5518 + }, + { + "evidence_date": "2026-03-23T01:24:14Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15443.analysis.json", + "sha256": "91c9f1fafa3e081b07205383aa494e04916bc62323f300612923278e1630e6e9", + "size_bytes": 2195 + }, + { + "evidence_date": "2026-03-25T22:13:02Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15659.json", + "sha256": "54d1dcda9df008e7f056a8d67869d0de7182808d7f46434bde2ec2807aff4f82", + "size_bytes": 7370 + }, + { + "evidence_date": "2026-03-25T22:13:02Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15659.analysis.json", + "sha256": "bdaae2bfe3af51c0ac8c9b5b9e896df29edbc137cb69625b3c8f3ec3d4b867d6", + "size_bytes": 1829 + }, + { + "evidence_date": "2026-03-25T19:50:40Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15759.json", + "sha256": "c8dfcb6feec86c4af0946ae96639db5537c8b77c85d36e28a13c5ee04df6dabd", + "size_bytes": 8817 + }, + { + "evidence_date": "2026-03-25T19:50:40Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15759.analysis.json", + "sha256": "5f1068abbb166edb6763fc47ed3d49370cd9d6b274ce0fd75251e1687e5f03af", + "size_bytes": 2526 + }, + { + "evidence_date": "2026-03-25T23:14:36Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15785.json", + "sha256": "1f42abf63a51b648a70f5c8f6e9f761c820e784c368b027435d498b430583dfb", + "size_bytes": 32283 + }, + { + "evidence_date": "2026-03-25T23:14:36Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15785.analysis.json", + "sha256": "a6582cc2ee858f6a68f15d484649ce15d2030992da4135fc4a6cbcd91e0e8d06", + "size_bytes": 3026 + }, + { + "evidence_date": "2026-03-25T23:09:20Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-15802.json", + "sha256": "053af5370e3564e9180c043ee2c9f304328d2f9f43f7c1fafbeb57056aa73f2b", + "size_bytes": 6169 + }, + { + "evidence_date": "2026-03-25T23:09:20Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-15802.analysis.json", + "sha256": "984a6adf57961821fe87e1e2c2f5a494f7f831cb35730cfd03d02e97b489fa40", + "size_bytes": 2333 + }, + { + "evidence_date": "2026-03-30T09:47:21Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-16010.json", + "sha256": "0a8e4c8f0f0f809ac4dedff301f6b06af3fc308780e1a53cb10910749072fad5", + "size_bytes": 12886 + }, + { + "evidence_date": "2026-03-30T09:47:21Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-16010.analysis.json", + "sha256": "21186cca84d394477c49f66ba2116456fde31a58a2c97ad03b9fb6a142a0d223", + "size_bytes": 1976 + }, + { + "evidence_date": "2026-04-07T02:17:14Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-16082.json", + "sha256": "ae47437f9b8329ab7bb92ca0ff948aab2af9ca4125c00d15a1bb2ee3054b275b", + "size_bytes": 25516 + }, + { + "evidence_date": "2026-04-07T02:17:14Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-16082.analysis.json", + "sha256": "af03e4e5aa2909ee51045ee577b3669647c94ae25601f5fdaa4c8db710ecb2ea", + "size_bytes": 2281 + }, + { + "evidence_date": "2026-04-07T00:46:28Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-16153.json", + "sha256": "e7bc7e7f4c07ca263fae09b7d1af580630e3ff586ef435b5084e070f23e5e8f2", + "size_bytes": 9391 + }, + { + "evidence_date": "2026-04-07T00:46:28Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-16153.analysis.json", + "sha256": "e7f423f56f9cc44cdc8fc35bcdb6188bd826c0a73f80918933613f36574b1c80", + "size_bytes": 2258 + }, + { + "evidence_date": "2026-03-29T23:54:18Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-16204.json", + "sha256": "52a657060f66fc8bd9eee5c91e43b153fe983fbc7e94d797251361e87da96f0d", + "size_bytes": 1880 + }, + { + "evidence_date": "2026-03-29T23:54:18Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-16204.analysis.json", + "sha256": "73edfb0913e9f19bf56f6c634674576f48374fe0581b409a5460d4ee908599bf", + "size_bytes": 1771 + }, + { + "evidence_date": "2026-04-03T06:00:02Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-16508.json", + "sha256": "fc8bd4422afe1ca0ba2e5757b3bc2382be626523d6ff255dde71f88c3b7b9026", + "size_bytes": 85238 + }, + { + "evidence_date": "2026-04-03T06:00:02Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-16508.analysis.json", + "sha256": "57b7715cdedd1c6f1a85b6173620a0f7da57a5dbac0eb8c4765b24bad4dc344b", + "size_bytes": 2138 + }, + { + "evidence_date": "2026-04-02T23:39:57Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-16631.json", + "sha256": "1462dad9565a368407c16054456d05474925772071289c3b390e34a5d462673f", + "size_bytes": 11801 + }, + { + "evidence_date": "2026-04-02T23:39:57Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-16631.analysis.json", + "sha256": "8030b9c8857c628df322f46862870b5eb00f9ff105b5dbdb3f4b855c19b91521", + "size_bytes": 2081 + }, + { + "evidence_date": "2026-04-03T00:05:45Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-16633.json", + "sha256": "acfc0b9178a63a3915081aca7012bafa49cdb683d25e0b65b9dcac4140ddc696", + "size_bytes": 1742 + }, + { + "evidence_date": "2026-04-03T00:05:45Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-16633.analysis.json", + "sha256": "48a50b95dfb915b626068801218b2e95b1ada246f56af9b21096d9b151d7e44c", + "size_bytes": 1735 + }, + { + "evidence_date": "2026-04-07T02:38:51Z", + "kind": "bundle", + "path": "artifacts/github/bundles/openai-codex-pr-16946.json", + "sha256": "95a5f37525bfa226d78ec6bd22b697d1009c56b97056cbf51a481930c3f37ce9", + "size_bytes": 13670 + }, + { + "evidence_date": "2026-04-07T02:38:51Z", + "kind": "analysis", + "path": "artifacts/github/analysis/openai-codex-pr-16946.analysis.json", + "sha256": "c0b7fb7a12bd53d5fc7c124cacc54e02ec64c6615d9de0fa4a251b882dabae21", + "size_bytes": 2693 + } + ], + "release_tag": "radar-archive-2026-05-13-pre-2026-04-13", + "release_url": "https://github.com/hack-ink/decodex/releases/tag/radar-archive-2026-05-13-pre-2026-04-13", + "retention_days": 28, + "schema": "radar_archive_manifest/v1", + "selected_older_than_days": 30, + "source_commit": "22beb587ab6b0730cc91eaf2403e04120c0a20f4" +} diff --git a/artifacts/github/analysis/openai-codex-pr-12334.analysis.json b/artifacts/github/analysis/openai-codex-pr-12334.analysis.json deleted file mode 100644 index a7ca4d1..0000000 --- a/artifacts/github/analysis/openai-codex-pr-12334.analysis.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "caveats": [ - "Behavior depends on terminal/OS support for OSC title sequences; some terminals or environments may ignore title updates.", - "Feature changes terminal metadata only, so session distinction is improved in terminal tabs/windows but not in contexts that hide or override title rendering." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "capability", - "proof_points": [ - "The PR description explicitly frames the change as solving multi-session identification and documents `/title` as a new, separate terminal-title surface.", - "`codex-rs/core/src/config/types.rs` and `codex-rs/core/src/config/mod.rs` add `tui.terminal_title` mapped into runtime config, with defaults in docs/schema (`config.schema.json`) to `spinner` and `project` when unset.", - "`codex-rs/tui/src/slash_command.rs` adds the `/title` command, and `codex-rs/tui/src/bottom_pane/title_setup.rs` introduces the in-TUI selection/reordering UI, wired through `bottom_pane/mod.rs` and `app_event.rs`.", - "`codex-rs/tui/src/terminal_title.rs` adds dedicated OSC terminal-title output and sanitization logic, while `codex-rs/tui/src/chatwidget/status_surfaces.rs` and `chatwidget.rs` integrate title refresh with the existing status pipeline.", - "The bundle includes added/updated tests and snapshots (`title_setup` snapshot + chatwidget/config tests), indicating behavior verification for the new user-facing flow rather than purely internal cleanup." - ], - "slug": "feat-tui-title-terminal-title-configuration", - "summary": "PR #12334 introduces a new TUI `/title` command and config path so users can control which compact items appear in the terminal window/tab title, with a default `spinner,project` layout when unset.", - "title": "`/title` adds configurable terminal-title support in the TUI", - "watch_state": null, - "why_it_matters": "This gives users a practical way to distinguish multiple active Codex sessions at the terminal level, improving session visibility and reducing \u201cwhich tab is which\u201d confusion without requiring app focus." -} diff --git a/artifacts/github/analysis/openai-codex-pr-13494.analysis.json b/artifacts/github/analysis/openai-codex-pr-13494.analysis.json deleted file mode 100644 index 4fffe27..0000000 --- a/artifacts/github/analysis/openai-codex-pr-13494.analysis.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "caveats": [], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "PR body explains the core behavior change: store a pre-rendered `feedback_log_body` in SQLite so `/feedback` exports keep span prefixes and structured event fields, and perform SQL-side budgeted loading followed by whole-line Rust truncation.", - "`codex-rs/state/logs_migrations/0002_logs_feedback_log_body.sql` renames the old `logs` table, creates a new schema with `feedback_log_body`, backfills from existing `message` values, and reinserts rows into the updated layout.", - "`codex-rs/state/src/runtime/logs.rs` begins writing `feedback_log_body` and bases `estimated_bytes` on persisted export-facing content for partition retention accounting.", - "`codex-rs/state/src/log_db.rs` and `codex-rs/state/src/model/log.rs` expand log persistence structures to keep formatter-relevant fields (rendered names/fields and `feedback_log_body`) available for export workflows.", - "Tests and client-side handling (`codex-rs/core/tests/suite/sqlite_state.rs`, `codex-rs/state/src/bin/logs_client.rs`) were updated to match rendered-body matching/assertions and wording, confirming behavior-level alignment rather than backend-only refactoring only." - ], - "slug": "openai-codex-pr-13494", - "summary": "The PR updates Codex\u2019s SQLite logging path so feedback exports persist a rendered log body and apply retention/truncation against that rendered content, aligning exported log output with existing in-memory formatting semantics.", - "title": "SQLite feedback logs now preserve rendered output behavior", - "watch_state": null, - "why_it_matters": "Users relying on `/feedback` now get clearer, more consistent exports: structured log fields and span prefixes are retained, newlines are preserved, and truncation works on export content rather than silently cutting useful context from retained rows." -} diff --git a/artifacts/github/analysis/openai-codex-pr-14632.analysis.json b/artifacts/github/analysis/openai-codex-pr-14632.analysis.json deleted file mode 100644 index a13d184..0000000 --- a/artifacts/github/analysis/openai-codex-pr-14632.analysis.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "caveats": [ - "The change is mainly operational: it improves tracing fidelity for websocket-based turns rather than adding a new end-user feature." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR description says websocket tracing was previously attached only at connection time and that this change propagates the current tracing context on every `response.create` request.", - "Bundle evidence in `codex-rs/codex-api/src/common.rs` adds `ws_request_header_traceparent` and `ws_request_header_tracestate` metadata keys plus request-building support for websocket create calls.", - "The changed core client and websocket test files add `W3cTraceContext`/`current_span_w3c_trace_context` usage and new tracing test support, which indicates the active span context is now wired into runtime websocket traffic and validated in tests." - ], - "slug": "websocket-turn-requests-now-propagate-trace-context", - "summary": "Merged PR #14632 updates Codex websocket tracing so each `response.create` request carries W3C trace context in `client_metadata`, instead of only attaching tracing metadata when the websocket connection starts.", - "title": "Websocket turn requests now propagate per-turn trace context", - "watch_state": "Dependent backend and Responses API work is referenced in the PR body, so real-world end-to-end value depends on those paired changes remaining aligned.", - "why_it_matters": "This closes an observability gap in persistent websocket sessions. Teams tracing Codex turn execution can now follow individual turns end to end, which makes latency and failure analysis more reliable when a single socket is reused across many requests." -} diff --git a/artifacts/github/analysis/openai-codex-pr-14847.analysis.json b/artifacts/github/analysis/openai-codex-pr-14847.analysis.json deleted file mode 100644 index 1781dbd..0000000 --- a/artifacts/github/analysis/openai-codex-pr-14847.analysis.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "caveats": [ - "Websocket transport is still described in the README as experimental and unsupported for production workloads." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": "The unauthenticated websocket upgrade should be rejected, while the request carrying the configured bearer token should complete the handshake and proceed to normal app-server initialization.", - "how_to_try": "Start a build that includes PR #14847 with `codex app-server --listen ws://127.0.0.1:PORT --ws-auth capability-token --ws-token-file /absolute/path/token.txt`, then attempt one websocket connection without `Authorization: Bearer ` and one with it.", - "impact": "low", - "kind": "capability", - "proof_points": [ - "The PR body says websocket auth is enforced before JSON-RPC `initialize`, and that unauthenticated clients are rejected during the websocket handshake rather than after a connection is already open.", - "The README adds a new security note and user-facing auth options: `--ws-auth capability-token --ws-token-file ...` and `--ws-auth signed-bearer-token --ws-shared-secret-file ...`, with optional issuer, audience, and clock-skew checks, and it documents `Authorization: Bearer ` for clients.", - "The change adds a dedicated `transport/auth.rs` module and expands websocket transport tests, backing the feature with real transport-layer enforcement, constant-time capability-token checks, JWT verification via `jsonwebtoken`, and `403` rejection for `Origin`-bearing browser-style handshakes." - ], - "slug": "app-server-websocket-auth", - "summary": "Merged PR #14847 adds websocket authentication for Codex app-server, so websocket clients can be challenged at upgrade time instead of being admitted first and checked later.", - "title": "App-server websocket mode now supports bearer-token auth", - "watch_state": "During rollout, non-loopback websocket listeners still remain unauthenticated by default unless `--ws-auth` is explicitly configured.", - "why_it_matters": "If you expose `codex app-server` over websockets beyond localhost, you now have a built-in way to require credentials at the transport boundary. That closes a meaningful security gap for remote deployments without forcing an immediate breaking default on existing clients." -} diff --git a/artifacts/github/analysis/openai-codex-pr-14867.analysis.json b/artifacts/github/analysis/openai-codex-pr-14867.analysis.json deleted file mode 100644 index 451b2cc..0000000 --- a/artifacts/github/analysis/openai-codex-pr-14867.analysis.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "caveats": [], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "PR 14867 explicitly states the intent: persist stop-hook continuation prompts as `user` messages rather than hidden `developer` messages to match turn-loop training behavior.", - "`core/src/event_mapping.rs` now parses user-role items via `parse_visible_hook_prompt_message` into `TurnItem::HookPrompt`, and `core/src/compact_remote.rs` preserves `TurnItem::HookPrompt` during compaction filtering.", - "Protocol/model updates add a new `hookPrompt`/`HookPromptFragment` surface (`HookRunId` + `text`) across Rust and generated TypeScript schema (`app-server-protocol` and `protocol/src/items.rs`), making continuation prompts a first-class event payload.", - "Hook execution now stores continuation data as `continuation_fragments: Vec` (`hooks/src/events/stop.rs`) with tests added for hook prompt fragment parsing/escaping and multi stop-hook continuation behavior in core hook and contextual user message tests." - ], - "slug": "openai-codex-pr-14867", - "summary": "This PR changes stop-hook continuation behavior so hook-provided continuation text is carried as structured user-context messages (`HookPrompt`) instead of being treated like hidden developer instruction content, improving turn-loop handling when hooks run multiple times.", - "title": "Stop-hook continuation prompts are now persisted as user-visible hook prompts", - "watch_state": null, - "why_it_matters": "Users are less likely to hit unstable stop-hook replay behavior: continuation prompts from hooks are now part of normal user-visible context and survive history compaction, which helps avoid inconsistent or looping hook-driven turns while keeping prompt continuity aligned with turn-loop expectations." -} diff --git a/artifacts/github/analysis/openai-codex-pr-14888.analysis.json b/artifacts/github/analysis/openai-codex-pr-14888.analysis.json deleted file mode 100644 index eec5f68..0000000 --- a/artifacts/github/analysis/openai-codex-pr-14888.analysis.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "caveats": [], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "Primary PR 14888 states the behavioral change explicitly: `thread/resume` now reads persisted thread metadata and uses persisted `model` and `model_reasoning_effort` as resume-time defaults before normal config resolution.", - "`codex-rs/app-server/src/codex_message_processor.rs` was modified in this bundle, including the resume/config path (`request_overrides` handling and persisted metadata reuse path), which is the runtime site for the documented precedence behavior.", - "`codex-rs/app-server/README.md` was updated with resumed-session behavior and docs around `thread/resume`, providing user-facing evidence of the intended behavior change.", - "The change is supported by five related commits in PR 14888 (`0c30c5`, `ed0708`, `719028`, `634de3`, `82375e`) and merged on 2026-03-18, showing this behavior update was implemented as a coherent thread-resume fix rather than isolated cleanup." - ], - "slug": null, - "summary": "This PR changes `thread/resume` to prefer persisted thread metadata over current config when no explicit resume overrides are provided, so resumed threads inherit the prior `model` and `model_reasoning_effort` values by default. It also updates the README to document the resume fallback behavior.", - "title": "thread/resume now preserves persisted model defaults", - "watch_state": null, - "why_it_matters": "Resuming a thread now continues with the same model behavior it used before, reducing surprising drift in output style/performance and avoiding accidental configuration shifts between sessions." -} diff --git a/artifacts/github/analysis/openai-codex-pr-14945.analysis.json b/artifacts/github/analysis/openai-codex-pr-14945.analysis.json deleted file mode 100644 index 38a2aae..0000000 --- a/artifacts/github/analysis/openai-codex-pr-14945.analysis.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "caveats": [ - "The change is scoped to the app-server TUI; the legacy TUI already used this history path.", - "History I/O failures are still logged and swallowed rather than surfaced directly in the UI." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": "Previously entered prompts should be available through composer history in the app-server TUI, including entries persisted in `$CODEX_HOME/history.jsonl` from earlier sessions.", - "how_to_try": "Run a Codex build that includes PR #14945 in app-server TUI mode, submit a prompt, then press Up in the composer or restart the session and press Up again to recall earlier entries.", - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR description says the app-server TUI previously showed a stub for Up/Down history recall and dropped new submissions from the shared history file, and that this change restores both behaviors locally.", - "`codex-rs/tui_app_server/src/app.rs` and related TUI files add local handling for `GetHistoryEntryRequest` and `AddToHistory`, routing history lookups and writes through `codex_core::message_history` instead of the unsupported RPC path.", - "Tests were updated to verify async history navigation, per-thread routing of history responses, and bootstrap population of `history_log_id` / `history_entry_count`, confirming the restored behavior is exercised end to end." - ], - "slug": "app-server-tui-restores-composer-history", - "summary": "Merged PR #14945 brings prompt-history recall and persistence to the app-server TUI, so Up/Down navigation and cross-session history now work like the legacy TUI.", - "title": "App-server TUI now restores composer history", - "watch_state": null, - "why_it_matters": "Users on the app-server TUI can recover previous prompts instead of hitting a stubbed \"not available\" path, and new submissions now persist to the shared history file for later sessions." -} diff --git a/artifacts/github/analysis/openai-codex-pr-14970.analysis.json b/artifacts/github/analysis/openai-codex-pr-14970.analysis.json deleted file mode 100644 index c0889fa..0000000 --- a/artifacts/github/analysis/openai-codex-pr-14970.analysis.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "caveats": [ - "The PR body says Codex App and VS Code need a minor follow-up change to recognize directory mentions and adjust link behavior, so downstream client UX may lag the protocol change." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": "Directory matches should be marked distinctly from file matches, with folder mentions rendered using a trailing slash and available for client-specific link behavior.", - "how_to_try": "On a build that includes PR #14970, trigger fuzzy file search or mention completion for a known folder and a similarly named file, then inspect the returned mention/search result payloads or UI labels for directory-specific handling.", - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR title and body explicitly frame the change as adding simple directory-mention support in the TUI, with trailing slashes used to distinguish directories from extensionless files.", - "The Rust search model in `codex-rs/file-search` adds a `MatchType` on search results, and the app-server mapping forwards that as `FuzzyFileSearchMatchType` with `file` and `directory` variants.", - "Generated JSON/TypeScript protocol schemas now require `match_type` on `FuzzyFileSearchResult`, and fuzzy-file-search tests were updated to expect the new field, confirming this is a real behavior-contract change rather than internal cleanup." - ], - "slug": "directory-mentions-distinguish-files-and-folders", - "summary": "Merged PR #14970 adds basic directory-mention support by carrying directory vs. file match metadata through fuzzy file search results and related protocol schemas.", - "title": "Codex now distinguishes directory mentions from file mentions", - "watch_state": "Watch for follow-up client updates that consume `match_type` and fully differentiate folder links outside the TUI.", - "why_it_matters": "This makes path mentions less ambiguous in the TUI and gives clients enough information to treat folders differently from files. For users, that should reduce mistaken links for extensionless paths and make folder references clearer." -} diff --git a/artifacts/github/analysis/openai-codex-pr-14988.analysis.json b/artifacts/github/analysis/openai-codex-pr-14988.analysis.json deleted file mode 100644 index 354a4d8..0000000 --- a/artifacts/github/analysis/openai-codex-pr-14988.analysis.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "caveats": [ - "Merged protocol and README text describe `thread/shellCommand` as running unsandboxed with full access, which is stricter and more consequential than the PR body's earlier sandbox wording." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "capability", - "proof_points": [ - "The PR title and body frame the change around a new `thread/shellCommand` API plus TUI support for `!` shell commands, making the PR itself a clear user-facing capability addition.", - "Protocol/schema files add a dedicated `ThreadShellCommand` request shape and `ThreadShellCommandParams`, while command items gain a `CommandExecutionSource` field, showing this is a first-class app-server surface rather than an internal workaround.", - "`codex_message_processor.rs`, `app_server_session.rs`, and `tui_app_server/src/app.rs` wire request handling and submission for `thread/shellCommand`, connecting the new API to the app-server-backed TUI path.", - "`tui_app_server/src/chatwidget.rs` removes the prior disabled-error behavior for `!` commands and instead dispatches `run_user_shell_command`, while `core/src/tasks/user_shell.rs` adds rollout materialization for standalone shell-output persistence.", - "The new test suite `app-server/tests/suite/v2/thread_shell_command.rs` and updated `chatwidget` tests provide direct evidence that the request path and TUI behavior were intentionally added and verified." - ], - "slug": null, - "summary": "Merged PR #14988 adds a new `thread/shellCommand` app-server request and wires app-server-backed TUI `!` shell commands through that API, with command execution persisted into thread history.", - "title": "App server adds `thread/shellCommand` support for TUI `!` commands", - "watch_state": null, - "why_it_matters": "This turns app-server-backed `!` shell commands from a blocked path into a supported workflow. Users on the app-server TUI can run shell shortcuts again and have the command activity show up in the same thread history and execution-event flow as other command items." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15011.analysis.json b/artifacts/github/analysis/openai-codex-pr-15011.analysis.json deleted file mode 100644 index 242f207..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15011.analysis.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "caveats": [ - "There is no direct end-to-end runtime trace in the bundle, so the evidence is limited to the PR description, changed files, and commit history.", - "The PR notes mention unrelated pre-existing `codex-core` test failures elsewhere, so confidence should stay below `confirmed` even though the MCP-specific changes are well supported." - ], - "confidence": "likely", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR body explicitly says the change forwards task headers through MCP metadata lookups and tool calls, including streamable HTTP `initialize`, `tools/list`, and `tools/call` requests.", - "The diff evidence in `codex.rs` and `tasks/mod.rs` adds turn-lifecycle header sync and cleanup hooks, showing the headers are scoped to the active turn instead of left ambient.", - "The MCP client stack files (`mcp_connection_manager.rs`, `rmcp_client.rs`, and related tests) add request-header plumbing and updated test setup, which supports this as a real behavior change rather than a docs-only update.", - "The commit trail includes follow-up fixes for turn scoping and CI regressions before the PR merged on 2026-03-19, indicating an incremental rollout of the new header propagation behavior." - ], - "slug": "openai-codex-pr-15011", - "summary": "PR #15011 updates Codex's MCP HTTP flow to forward request-scoped headers from the current session/turn into MCP tool metadata lookups and tool calls, with turn-bound lifecycle cleanup and fixes for regressions introduced during rollout.", - "title": "MCP HTTP requests now carry turn- and session-scoped headers", - "watch_state": null, - "why_it_matters": "This improves the correctness of MCP interactions that depend on per-turn or per-session context, reducing cross-turn header leakage and making tool calls and recovery behavior more predictable in streamable HTTP MCP clients." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15021.analysis.json b/artifacts/github/analysis/openai-codex-pr-15021.analysis.json deleted file mode 100644 index 8b716b0..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15021.analysis.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "caveats": [ - "No direct user-facing application behavior changes are shown; impact is primarily build/release infrastructure." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "capability", - "proof_points": [ - "The PR body explicitly says the approach is to use `rusty_v8` everywhere possible and to build a musl V8 from source for x86/aarch64, with a release step for consumption.", - "Added `.github/scripts/rusty_v8_bazel.py` introduces the musl-oriented Bazel build orchestration and packaging metadata used by the new release path.", - "Added `.github/workflows/v8-canary.yml` and `.github/workflows/rusty-v8-release.yml`, creating a dedicated canary/release lane (including optional GitHub release publishing) for V8 artifacts instead of coupling them to standard CI.", - "Updated build metadata in `MODULE.bazel`, `MODULE.bazel.lock`, and new Bazel patches under `patches/` to pin/use V8 14.6.202.9 plus required dependency/workspace overrides.", - "Adjusted `.github/workflows/bazel.yml` to exclude `//third_party/v8:all` from ordinary Bazel test runs, matching the PR\u2019s \u201ckeep V8 out of ordinary CI\u201d intent and evidence trail in commit history." - ], - "slug": "openai-codex-pr-15021", - "summary": "PR #15021 adds a new Bazel-based workflow for building and packaging `rusty_v8`, including musl targets for x86_64/aarch64, while removing `third_party/v8` from the default CI test path so V8 work is handled by dedicated canary/release flows.", - "title": "Add a dedicated Bazel pipeline for building and releasing rusty_v8 artifacts", - "watch_state": null, - "why_it_matters": "This gives the project a reliable way to produce and publish prebuilt V8 artifacts for additional toolchains (notably musl), improving build reproducibility for dependents and reducing pressure on normal CI by isolating heavy V8 builds into explicit workflows." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15041.analysis.json b/artifacts/github/analysis/openai-codex-pr-15041.analysis.json deleted file mode 100644 index b5b8e65..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15041.analysis.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "caveats": [ - "The exact behavior depends on how session sources map to products; custom sources only help if that mapping resolves to the intended product restriction." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": "Plugins and skills whose product policy does not match the active session source should be filtered out or reported as unavailable, while unrestricted or matching items remain visible.", - "how_to_try": "Start app-server with `--session-source chatgpt` or another custom source, then list or install marketplace plugins that declare `policy.products` restrictions.", - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR body explicitly frames the change around three behaviors: adding `SessionSource::Custom(String)` plus `--session-source`, enforcing plugin and skill products by `session_source`, and applying the same filtering to curated background refresh.", - "Protocol and schema changes add a custom session-source shape, which shows this is a supported runtime input rather than an internal one-off.", - "The runtime wiring passes the chosen session source through app-server setup instead of hardcoding a fixed source, making product-aware filtering active at execution time.", - "Plugin and skill loading paths now check product restrictions against the session-derived product, so mismatched items are hidden or treated as unavailable.", - "The test additions cover disallowed product plugins and product-scoped skill visibility, confirming this is a deliberate user-visible behavior change." - ], - "slug": null, - "summary": "PR 15041 makes session source a first-class input with `--session-source` and uses it to filter plugin installation and skill visibility by product, so different Codex surfaces can expose different plugin catalogs.", - "title": "Product-scoped plugins and skills via session source", - "watch_state": null, - "why_it_matters": "Users on one product surface are less likely to see or install incompatible plugins or skills. That reduces confusion and helps keep plugin availability aligned with the product context the session came from." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15042.analysis.json b/artifacts/github/analysis/openai-codex-pr-15042.analysis.json deleted file mode 100644 index 8d615ab..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15042.analysis.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "caveats": [ - "The evidence points to a ChatGPT-authenticated remote fetch path, so unauthenticated or offline environments may still return an empty featured list." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": "The response should include a `featuredPluginIds` array alongside the normal marketplace data, giving clients a machine-readable set of plugins to highlight.", - "how_to_try": "Call `plugin/list` from a Codex build that includes PR #15042 while remote plugin sync is available and the session is authenticated, then inspect the response payload.", - "impact": "low", - "kind": "capability", - "proof_points": [ - "The PR title is `Support featured plugins`, and the protocol schema, Rust type, and generated TypeScript type for `PluginListResponse` all add a `featuredPluginIds` array.", - "The core plugin remote layer adds a dedicated `fetch_remote_featured_plugin_ids` path with a separate featured-plugin fetch timeout, showing this is backed by a distinct remote data source rather than local-only metadata.", - "The app-server plugin-list test now mocks an authenticated `GET /backend-api/plugins/featured` call returning IDs like `linear@openai-curated` and `calendar@openai-curated`, which ties the new response field to real plugin-list behavior." - ], - "slug": "featured-plugin-ids-in-plugin-list", - "summary": "Merged PR #15042 adds a `featuredPluginIds` array to `plugin/list` responses and wires it to the curated remote plugin service.", - "title": "Codex plugin listings now expose featured plugin IDs", - "watch_state": "Whether downstream UIs actually use `featuredPluginIds` to rank or badge plugins; this PR adds the metadata path, not a visible marketplace redesign.", - "why_it_matters": "Clients can separate recommended plugins from the full marketplace list without hardcoding picks. That makes plugin discovery and onboarding easier, especially for curated marketplaces." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15056.analysis.json b/artifacts/github/analysis/openai-codex-pr-15056.analysis.json deleted file mode 100644 index 4e91a2b..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15056.analysis.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "caveats": [], - "confidence": "likely", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR body says the new agent-graph representation is now used for both cascading agent close and cascading resume.", - "The bundle adds a new `thread_spawn_edges` table plus `DirectionalThreadSpawnEdgeStatus` (`Open` and `Closed`), which shows parent-child agent relationships are now persisted as first-class state.", - "The `close_agent` tool path switches to `agent_control.close_agent(...)`, and the tool description was updated to say it closes an agent \"and any open descendants,\" making descendant shutdown part of the user-visible contract.", - "`agent/control.rs` and `control_tests.rs` add graph-aware lifecycle and resume coverage, which supports the PR's claim that subtree resume now uses the persisted agent network." - ], - "slug": "agent-trees-now-cascade-close-and-resume", - "summary": "Merged PR #15056 adds persisted parent-child agent graph state so close and resume flows can apply to descendant agents instead of treating each agent as an isolated thread.", - "title": "Agent trees now cascade close and resume", - "watch_state": null, - "why_it_matters": "Users working with nested agents are less likely to leave orphaned child agents behind when closing a parent, and resumed sessions can restore the same subtree structure instead of losing those relationships." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15072.analysis.json b/artifacts/github/analysis/openai-codex-pr-15072.analysis.json deleted file mode 100644 index 4ce54e0..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15072.analysis.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "caveats": [ - "This is scoped to code mode and the `view_image` tool path; it is not a broad change to every image-related API in Codex." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": "`view_image` should return a structured payload that `image()` accepts directly, so the image is attached successfully and any returned detail hint can flow through with it.", - "how_to_try": "Run a Codex build that includes PR #15072 in code mode, call `const img = await tools.view_image({ path: \"/absolute/path/to/local-image.png\" }); image(img);`, and confirm the script can attach the local image without manually pulling out `img.image_url`.", - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR title and body frame the change as a semantic cleanup: `view_image` now returns an object with `image_url`, and `image()` now accepts either a string or that structured object.", - "The changed tool docs and spec add a typed `view_image` output schema with `image_url` and `detail`, and update the code-mode helper signature from `image(string)` to `image(string | { image_url, detail? })`.", - "The bundle includes a dedicated test, `code_mode_can_use_view_image_result_with_image_helper`, which verifies the direct handoff path, and companion `view_image` test updates switch non-image inputs from placeholder output to an error, tightening the behavior around invalid files." - ], - "slug": "code-mode-image-helper-accepts-view-image-results", - "summary": "Merged PR #15072 makes Codex code mode treat `view_image` as a structured image producer, so scripts can hand its result directly to `image()` instead of manually extracting a URL string.", - "title": "Code mode can now pass `view_image` results straight into `image()`", - "watch_state": null, - "why_it_matters": "This removes glue code from image-handling scripts and makes local-image flows more predictable. Users can reuse a `view_image` result directly, with detail metadata preserved when available, instead of reformatting the tool output by hand." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15075.analysis.json b/artifacts/github/analysis/openai-codex-pr-15075.analysis.json deleted file mode 100644 index d941fe6..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15075.analysis.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "caveats": null, - "confidence": "confirmed", - "config_flags": [], - "expected_effect": "The failing nested tool call should throw into the script's `catch` path with the tool error message, rather than returning a normal result payload.", - "how_to_try": "Run a Codex build that includes PR #15075, execute a code-mode script that wraps an invalid nested tool call such as `await tools.exec_command({})` in `try/catch`, and confirm the catch block receives the error.", - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "PR #15075 explicitly says it pushes `FunctionCallError` up to the dispatcher so code mode can surface failures as exceptions.", - "The code-mode protocol adds an `error_text` field, and `runner.cjs` rejects the pending call when that field is present, which turns backend tool failures into script exceptions instead of resolved values.", - "The router and parallel tool runtime switch to a code-mode-aware dispatch path, and the added test `code_mode_exec_surfaces_handler_errors_as_exceptions` verifies a failing `tools.exec_command({})` call is caught by `try/catch`." - ], - "slug": "code-mode-surfaces-tool-failures-as-exceptions", - "summary": "Merged PR #15075 changes Codex code mode so nested tool-call failures propagate as errors you can catch, instead of being folded into normal tool output.", - "title": "Code mode now surfaces tool failures as exceptions", - "watch_state": null, - "why_it_matters": "Code-mode scripts can now distinguish real tool failures from successful results. That makes error handling more predictable and avoids silently treating a broken nested tool call as ordinary output." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15076.analysis.json b/artifacts/github/analysis/openai-codex-pr-15076.analysis.json deleted file mode 100644 index bae9cdd..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15076.analysis.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "caveats": [ - "This warning is scoped to the TUI startup path and only appears when Codex detects custom prompts in `$CODEX_HOME/prompts`." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": "The TUI should show a startup warning only when one or more custom prompts are present, including the detected prompt count and migration guidance toward skills.", - "how_to_try": "Run a Codex build that includes PR #15076, place at least one custom prompt file under `$CODEX_HOME/prompts`, start the TUI, and confirm a deprecation notice appears at startup telling you to convert prompts into skills with `$skill-creator`.", - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR title and body explicitly frame the change as a startup deprecation warning for custom prompts, shown only when prompts are detected in `$CODEX_HOME/prompts`.", - "The main code change is in `tui/src/app.rs`, where startup now counts discovered custom prompts and inserts a deprecation notice that tells users to use the `$skill-creator` skill.", - "The bundle includes a new snapshot showing the rendered warning text, and the PR body says tests were added for present, missing, and empty prompts directories, which supports this as an intentional user-visible behavior change." - ], - "slug": "custom-prompts-show-startup-deprecation-warning", - "summary": "Merged PR #15076 adds a startup deprecation notice in the Codex TUI when it finds custom prompts under `$CODEX_HOME/prompts`, with guidance to migrate them to skills.", - "title": "Codex TUI now warns at startup when legacy custom prompts are present", - "watch_state": "Watch for the follow-up release that actually removes custom prompt support, since this PR is an advance warning rather than the removal itself.", - "why_it_matters": "Users still relying on legacy custom prompts now get an explicit migration warning before that path disappears. That reduces surprise from the deprecation and points them at the supported replacement instead of leaving the breakage to a later release." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15077.analysis.json b/artifacts/github/analysis/openai-codex-pr-15077.analysis.json deleted file mode 100644 index 0121f8d..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15077.analysis.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "caveats": null, - "confidence": "confirmed", - "config_flags": [], - "expected_effect": "The handoff text payload should begin with `\"Agent Final Message\":` followed by a blank line before the assistant's actual message content.", - "how_to_try": "Run a Codex build that includes PR #15077, trigger a realtime conversation handoff, and inspect the emitted `conversation.handoff.append` payload in either the v1 or Realtime v2 websocket stream.", - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR title and body explicitly say realtime handoff output should be prefixed with the agent final message label for both realtime v1 and v2.", - "`methods_common.rs` adds a shared `AGENT_FINAL_MESSAGE_PREFIX` constant and prepends it inside `conversation_handoff_append_message`, which is the code path that builds the outbound handoff message.", - "Realtime websocket and core conversation tests were updated to expect `\"Agent Final Message\":\\n\\n...` in both `output_text` and delegated output fields, confirming this is an intentional behavior change rather than a test-only refactor." - ], - "slug": "realtime-handoff-output-labels-agent-final-message", - "summary": "Merged PR #15077 changes realtime handoff payloads so the emitted assistant text is prefixed with an `\"Agent Final Message\":` label instead of sending the raw message body alone.", - "title": "Realtime handoff output now labels the agent's final message", - "watch_state": null, - "why_it_matters": "Clients that display or post-process realtime handoff output now get an explicit label marking the delegated agent's final response. This is a small but user-visible protocol change, and integrations that expect the old raw string format may need to adjust." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15088.analysis.json b/artifacts/github/analysis/openai-codex-pr-15088.analysis.json deleted file mode 100644 index baa3908..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15088.analysis.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "caveats": [ - "`final_response` is optional and may be `null` when the turn completes without a final-answer style assistant message.", - "The lower-level `thread.turn(...)` path still matters for streaming, steering, interrupting, and direct access to the raw `Turn` object." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": "The call should return a `RunResult` object with the collected thread items, optional token usage, and a `final_response` string when the turn produces a final-answer or phase-less assistant message.", - "how_to_try": "In a Codex build that includes PR #15088, start a Python SDK thread with `codex.thread_start(...)` and call `thread.run(\"Say hello in one sentence.\")` or `await thread.run(\"Say hello in one sentence.\")` in the async API.", - "impact": "low", - "kind": "capability", - "proof_points": [ - "The PR title and body explicitly frame this as new Python SDK convenience methods: `thread.run(...)` and `async thread.run(...)` for the common case.", - "The code adds new `_inputs.py` and `_run.py` helpers, including `RunInput` and a `RunResult` dataclass with `final_response`, collected `items`, and optional `usage`.", - "The README, getting-started guide, quickstart examples, and runtime/signature tests were updated to use `thread.run(...)`, which shows the new flow is intended for public use rather than internal refactoring." - ], - "slug": "python-sdk-thread-run-convenience-methods", - "summary": "Merged PR #15088 adds `thread.run(...)` and `async thread.run(...)` to the Python SDK, giving users a simpler path for common prompt-response flows without manually creating and running a turn handle.", - "title": "Python SDK adds `thread.run()` convenience methods for one-shot turns", - "watch_state": null, - "why_it_matters": "This lowers the amount of SDK code needed for basic request-response usage. Python users can now send a string or input bundle directly from a thread and get a compact result object back, while the lower-level turn API remains available for streaming and more advanced control." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15089.analysis.json b/artifacts/github/analysis/openai-codex-pr-15089.analysis.json deleted file mode 100644 index 0da5db6..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15089.analysis.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "caveats": [ - "The PR intentionally stops at transport, protocol, and initialize-handshake scaffolding; exec and filesystem RPC methods are still stubbed.", - "The new server is not yet wired into the main Codex CLI or unified execution path, so immediate end-user impact is limited." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "capability", - "proof_points": [ - "The PR title and body explicitly frame this as an \"initialize-only\" exec-server stub slice with protocol docs, not a full execution feature.", - "`codex-rs/Cargo.toml`, `codex-rs/exec-server/Cargo.toml`, and `codex-rs/Cargo.lock` add a new `codex-exec-server` workspace crate and binary target.", - "`codex-rs/exec-server/README.md` documents a standalone server, shared JSON-RPC wire protocol, and two supported transports: `ws://IP:PORT` and `stdio://`.", - "`codex-rs/exec-server/src/bin/codex-exec-server.rs`, `src/protocol.rs`, `src/rpc.rs`, and `src/server/transport.rs` provide the new server entrypoint, initialize protocol types, RPC plumbing, and transport parsing/runtime support.", - "Smoke tests in `codex-rs/exec-server/tests/stdio_smoke.rs` and `codex-rs/exec-server/tests/websocket_smoke.rs` verify the initialize flow over both transport modes, which confirms the stub is functional at the protocol layer." - ], - "slug": "standalone-exec-server-json-rpc-scaffolding", - "summary": "PR #15089 adds a new `codex-exec-server` crate and standalone binary with shared protocol types, Rust client APIs, and stdio/websocket transport support, while keeping exec and filesystem methods stubbed for later PRs.", - "title": "Added a standalone exec-server binary with JSON-RPC protocol scaffolding", - "watch_state": "Watch follow-up PRs for actual exec/filesystem method implementation and integration into the main Codex execution flow.", - "why_it_matters": "This creates a separate execution-server boundary that future Codex integrations can build on without coupling everything to the main CLI process. It is a real new capability, but still an early one: the transport and handshake are in place before the full remote execution surface lands." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15090.analysis.json b/artifacts/github/analysis/openai-codex-pr-15090.analysis.json deleted file mode 100644 index ccd2cd0..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15090.analysis.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "caveats": [], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "capability", - "proof_points": [ - "The PR body explicitly states that it carries surviving `exec-server` process and filesystem RPC implementation on top of existing websocket-based scaffolding and calls out the macOS SCCACHE regression workaround.", - "Primary PR narrative + commits show a feature rollout: initial implementation (`Add exec-server process and filesystem RPCs`) plus follow-up lifecycle fix (`exec-server: report false after retained exit`).", - "`codex-rs/exec-server/src/protocol.rs` adds method constants for process (`process/start`, `process/read`, `process/write`, `process/terminate`, `process/output`, `process/exited`) and filesystem RPCs (`fs/readFile`, `fs/writeFile`, `fs/createDirectory`, etc.).", - "Server-side expansion is added in `codex-rs/exec-server/src/server/filesystem.rs` and `codex-rs/exec-server/src/server/registry.rs`, with substantial handler updates, indicating the new RPC surface is actually dispatched and handled.", - "Client-facing layers were updated (`client.rs`, `client_api.rs`, `lib.rs`, `local_backend.rs`) to expose/read new RPC/notification types, while transport/routing internals in `rpc.rs`/`processor.rs` were refactored to support the new request/notification paths.", - "Regression-hardening is included via `.github/workflows/rust-ci.yml`: SCCACHE is disabled for `windows` and `macos-15-xlarge` + `x86_64-apple-darwin` to avoid mixed-architecture `ring/cc-rs` cache failures.", - "New/updated tests in `codex-rs/exec-server/src/server/handler/tests.rs` and `codex-rs/exec-server/tests/process.rs` show validation of the new behavior path.", - "Build metadata changes (`base64` and `codex-utils-pty` dependencies) and `Cargo.lock` updates support the newly added process and FS implementation paths." - ], - "slug": null, - "summary": "PR #15090 expands exec-server from a stub-oriented shape into a real process and filesystem RPC implementation, adding protocol methods, routing, client/server event plumbing, and tests. It also includes a CI cache guard for a specific macOS cross-target build issue.", - "title": "exec-server adds process and filesystem JSON-RPC capabilities", - "watch_state": null, - "why_it_matters": "This is a user-facing capability increase: clients can now drive process lifecycle and file operations through exec-server with structured RPCs and event notifications (`output`/`exited`), which is a prerequisite for richer automation and tool integration workflows. The CI tweak reduces environment-specific instability during release/integration builds." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15092.analysis.json b/artifacts/github/analysis/openai-codex-pr-15092.analysis.json deleted file mode 100644 index 39b8b12..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15092.analysis.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "caveats": [ - "This is a CI-only maintenance change, not a new CLI or runtime capability for Codex users.", - "The workflow still depends on a hardcoded retained release artifact, so a similar refresh may be needed again later if that source ages out." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR body explicitly says the failing CI step is fixed by switching from release `0.74.0` to `0.115.0` because the older workflow history was likely reaped.", - "The only changed file is `.github/workflows/ci.yml`, where `CODEX_VERSION` for the \"Stage npm package\" step is bumped from `0.74.0` to `0.115.0`.", - "The bundle contains a single merged commit and no product-code changes, which supports reading this as a scoped CI behavior fix rather than a broader feature update." - ], - "slug": "ci-stage-npm-package-uses-newer-release-artifacts", - "summary": "Merged PR #15092 updates the PR CI workflow so the \"Stage npm package\" step pulls artifacts from Codex `0.115.0` instead of the older `0.74.0` release whose workflow history appears to have aged out.", - "title": "Codex CI now stages npm packages from a newer retained release artifact", - "watch_state": "Watch for follow-up PRs that remove or automate this pinned release-artifact dependency in the npm-package staging job.", - "why_it_matters": "This is mainly a contributor-facing reliability fix: PRs should be less likely to fail in the npm-package staging check just because the workflow was pinned to an older release artifact that GitHub no longer retains." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15100.analysis.json b/artifacts/github/analysis/openai-codex-pr-15100.analysis.json deleted file mode 100644 index 09f6645..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15100.analysis.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "caveats": [ - "This is a narrow behavioral change for code-mode automation, not a broad end-user feature or UI change." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR-level narrative is explicit: `openai/codex#15100` is titled \"Add apply_patch code mode result\" and shipped as a merged PR-first change on 2026-03-18.", - "`codex-rs/core/src/tools/context.rs` adds `ApplyPatchToolOutput`, including a dedicated `code_mode_result` implementation that returns an empty JSON object for code-mode callers.", - "`codex-rs/core/src/tools/handlers/apply_patch.rs` switches `ApplyPatchHandler` from `FunctionToolOutput` to `ApplyPatchToolOutput`, wiring the new result behavior into the actual tool handler.", - "`codex-rs/core/tests/suite/code_mode.rs` adds an integration assertion that the nested tool's second text item is `{}`, confirming the new result shape is intentional and tested." - ], - "slug": "apply-patch-code-mode-result-object", - "summary": "Merged PR #15100 changes Codex's code-mode `apply_patch` path to return a dedicated result object instead of flowing through the generic function-tool output path.", - "title": "Code-mode `apply_patch` now returns an explicit result object", - "watch_state": null, - "why_it_matters": "This makes nested `apply_patch` calls easier to handle programmatically in code mode. Automation can treat a successful patch as a deterministic tool result rather than relying only on side effects or generic output handling." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15102.analysis.json b/artifacts/github/analysis/openai-codex-pr-15102.analysis.json deleted file mode 100644 index a28e479..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15102.analysis.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "caveats": [ - "Because this is a revert of an accidentally merged change, the same area may change again in a follow-up PR with a narrower design." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR title and body explicitly mark this as a revert of PR #15020 because changes were merged before review was finished.", - "The protocol and generated schemas remove `SessionSource::Custom(...)`, drop the `custom` thread source kind, and delete the `--session-source` app-server CLI argument.", - "App-server docs now say the default interactive `sourceKinds` set is only `cli` and `vscode`, no longer including custom product sources.", - "Core and app-server changes remove session-source-based skill and curated-plugin filtering helpers, and related plugin-list/plugin-install tests are deleted." - ], - "slug": "custom-app-server-session-sources-rolled-back", - "summary": "Merged PR #15102 removes the newly introduced custom `session_source` path from Codex's app-server, protocol schemas, and plugin/skill filtering flow.", - "title": "Custom app-server session sources were rolled back", - "watch_state": "Watch for a replacement PR that reintroduces product-aware plugin or skill gating without the reverted custom session-source plumbing.", - "why_it_matters": "This closes off a just-landed integration path for tagging app-server threads as custom products and using that product identity to filter plugins or skills. Most users will see no change, but anyone testing custom app-server product sources loses that behavior for now." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15103.analysis.json b/artifacts/github/analysis/openai-codex-pr-15103.analysis.json deleted file mode 100644 index 38f2c23..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15103.analysis.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "caveats": [ - "The bundle contains truncated patch excerpts, so implementation detail is summarized from available hunks and test intent rather than a full raw diff.", - "This is an incremental internal-API behavior fix with no user-facing UI changes, so operational impact is limited to code-mode automation." - ], - "confidence": "confirmed", - "config_flags": [ - "PLAN_UPDATED_MESSAGE", - "PLAN_TOOL", - "JSON" - ], - "expected_effect": "If the behavior is present, code-mode workflows can consume `update_plan` return data programmatically instead of only relying on side effects.", - "how_to_try": "In code mode, run an update_plan call like `const result = await tools.update_plan({ plan: [{ step: 'Run update_plan', status: 'in_progress' }] }); text(JSON.stringify(result));` and check the response parses as JSON.", - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "PR `openai/codex#15103` is explicitly titled \"Add update_plan code mode result\" and the sole commit is message \"Implement code-mode plan result,\" indicating a focused behavioral change.", - "`codex-rs/core/src/tools/handlers/plan.rs` changed imports and added `PlanToolOutput`, with new payload/output/model types (`FunctionCallOutputPayload`, `ResponseInputItem`, `ToolOutput`, `serde_json::Value`) tied to plan/update handling.", - "`codex-rs/core/tests/suite/code_mode.rs` adds `code_mode_update_plan_nested_tool_result_is_empty_object`, explicitly executing `tools.update_plan(...)` from code mode and asserting the nested tool call is successful plus parseable JSON handling.", - "The change is confined to plan-tool and code-mode test surfaces, matching the PR title and no unrelated files in the bundle." - ], - "slug": "openai-codex-pr-15103", - "summary": "This merged PR updates the plan tool path so code-mode calls to `tools.update_plan` now yield a parseable result payload, and adds a regression test for the nested call shape.", - "title": "Code-mode `update_plan` now returns a structured result", - "watch_state": null, - "why_it_matters": "It makes scripted workflow updates in code mode more reliable by allowing the caller to inspect and act on `update_plan` output instead of treating the call as opaque." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15104.analysis.json b/artifacts/github/analysis/openai-codex-pr-15104.analysis.json deleted file mode 100644 index aff063b..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15104.analysis.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "caveats": [], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR body states three user-visible objectives: use requirement-resolved `config.features` as the plugin gate, guard plugin list/read flows behind that gate, and skip bad `marketplace.json` files instead of failing the entire list.", - "`codex-rs/core/src/plugins/marketplace.rs` now wraps marketplace loading in a match that warns and continues on failure, and adds a test for skipping failing marketplaces.", - "`codex-rs/app-server/src/codex_message_processor.rs` changes plugin request handling to load latest config (`load_latest_config`) before continuing and treats plugin-disabled as a distinct case instead of treating marketplace/config issues as a hard plugin failure.", - "`codex-rs/core/src/skills/manager.rs` and call sites in `codex-rs/core/src/codex.rs`, `codex-rs/core/src/codex_tests.rs`, and `codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs` now pass config into `skills_for_cwd`, making behavior follow the active config state." - ], - "slug": "openai-codex-pr-15104", - "summary": "PR #15104 updates plugin-related flows to use requirement-resolved config for the plugin feature gate, passes config-aware context through skill loading, and makes marketplace discovery tolerate malformed files instead of failing hard.", - "title": "Harden plugin listing/read behavior with resolved feature gating", - "watch_state": null, - "why_it_matters": "This change makes plugin commands more reliable for users by preventing a single broken marketplace file from breaking plugin list/read operations, while ensuring plugin enablement is consistently driven by resolved configuration." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15111.analysis.json b/artifacts/github/analysis/openai-codex-pr-15111.analysis.json deleted file mode 100644 index ccbe632..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15111.analysis.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "caveats": [ - "The PR description in the bundle body is boilerplate and does not add extra context, so the conclusion relies on patch semantics plus the PR title and commit diff.", - "No behavioral test evidence is included in the bundle, so runtime impact is inferred from code-level change rather than validated execution traces." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "Primary PR metadata shows the intent explicitly: #15111 is titled \u201cdon't add transcript for v2 realtime\u201d and was merged on 2026-03-18.", - "The changed file is `codex-rs/codex-api/src/endpoint/realtime_websocket/methods.rs`, in the `RealtimeEvent::HandoffRequested` branch that previously always moved `active_transcript.entries` into `handoff.active_transcript`.", - "Patch logic now wraps that move in `if self.event_parser == RealtimeEventParser::V1`, so the transcript move is conditional on V1 instead of always happening.", - "The bundle contains a single small commit (`e8232d32911ee5d246ecc482010c748021bc065e`) and no other changed files, which supports this as a narrow behavior fix rather than a broader feature." - ], - "slug": "realtime-v2-handoff-now-skips-transcript-handoff", - "summary": "This PR changes realtime handoff handling so transcript entries are only assigned during V1 handoff flow, leaving V2 handoff events to preserve existing transcript state.", - "title": "Realtime V2 handoff now skips transcript handoff", - "watch_state": null, - "why_it_matters": "It prevents v2 realtime sessions from clearing transcript buffers when a handoff is requested, reducing the chance of transcript disruptions during handoff transitions and keeping message context more stable for applications that consume realtime events." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15118.analysis.json b/artifacts/github/analysis/openai-codex-pr-15118.analysis.json deleted file mode 100644 index 0890c80..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15118.analysis.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "caveats": [ - "The new field is a Codex-specific extension and only applies to the Stop and UserPromptSubmit hook payloads covered by this PR." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": "Each Stop and UserPromptSubmit invocation should include a `turn_id` string, and the value should differ between separate turns so your hook can correlate events within the same turn.", - "how_to_try": "Enable Stop or UserPromptSubmit hooks in a build that includes PR #15118, print the incoming JSON payload in your hook script, and run two prompts in the same session.", - "impact": "low", - "kind": "capability", - "proof_points": [ - "The PR title and body explicitly frame the change as exposing `turn_id` to hook scripts so runs can be connected to particular turns.", - "The generated hook input schemas for `stop.command.input` and `user-prompt-submit.command.input` now require a string `turn_id` field.", - "The Rust hook event code now serializes `request.turn_id` into both Stop and UserPromptSubmit payloads, and the test suite was expanded to log hook inputs and verify turn-scoped behavior." - ], - "slug": "hooks-stop-userpromptsubmit-turn-id", - "summary": "Merged PR #15118 adds a Codex-specific `turn_id` field to the Stop and UserPromptSubmit hook payloads so hook scripts can tie each invocation to a specific conversation turn.", - "title": "Stop and UserPromptSubmit hooks now receive the active `turn_id`", - "watch_state": null, - "why_it_matters": "This makes hook automation more precise. If you log, correlate, or enforce turn-scoped behavior in custom hooks, you can now distinguish one turn from the next instead of inferring that from prompt text or timestamps." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15119.analysis.json b/artifacts/github/analysis/openai-codex-pr-15119.analysis.json deleted file mode 100644 index 52217a9..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15119.analysis.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "caveats": [ - "Integrations still using `stdio://`, local spawn helpers, or stdio connect APIs will need to migrate to websocket URLs.", - "The new coverage is websocket-focused, so this PR narrows the supported operational model rather than expanding capability." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR title and body explicitly frame the change as deleting deprecated stdio transport plumbing and replacing it with websocket-oriented test utilities.", - "`codex-rs/exec-server/src/bin/codex-exec-server.rs` changes `--listen` from a transport enum to a raw URL string and documents only `ws://IP:PORT` as supported.", - "`codex-rs/exec-server/src/server/transport.rs` replaces the old transport enum with listen-URL parsing centered on `DEFAULT_LISTEN_URL = \"ws://127.0.0.1:0\"`.", - "The bundle shows stdio-specific public/client paths being removed, including `src/local.rs`, stdio smoke tests, and stdio connect helpers, while new websocket tests are added for initialize, process, and malformed JSON handling." - ], - "slug": "openai-codex-pr-15119", - "summary": "Merged PR #15119 removes deprecated stdio transport support from Codex exec-server and standardizes the binary, library surface, docs, and tests around websocket listen URLs.", - "title": "Exec server switches from stdio plumbing to websocket-only transport", - "watch_state": null, - "why_it_matters": "This is a user-visible contract change for anyone integrating with exec-server. The transport story is simpler and more consistent, but setups that depended on `stdio://` or local stdio spawn helpers now need to move to websocket-based usage." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15125.analysis.json b/artifacts/github/analysis/openai-codex-pr-15125.analysis.json deleted file mode 100644 index 215ae8d..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15125.analysis.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "caveats": [ - "There is no explicit user-facing feature or flag introduced in the PR body; impact is inferred from code structure changes.", - "The refactor appears mostly internal, so user-visible behavior is expected to stay the same for now." - ], - "confidence": "likely", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "capability", - "proof_points": [ - "The PR body states the goal explicitly: the `Environment` abstraction now lives behind `codex-exec-server`, with construction-time differences hidden from `core`.", - "`codex-rs/environment` is removed (crate file + Bazel target + source deletion) while `codex-exec-server` gains exports for `Environment` and filesystem types.", - "Dependencies in `codex-rs/Cargo.toml`, `app-server/Cargo.toml`, and `core/Cargo.toml` were updated from `codex-environment` to `codex-exec-server`, and imports in app-server/core moved accordingly.", - "Filesystem and environment usage sites such as `app-server/src/fs_api.rs` and `core/src/codex.rs` now pull `Environment`/`ExecutorFileSystem` APIs from `codex_exec_server`, showing behavior is now mediated through the exec-server crate boundary.", - "`Cargo.lock` changes replace `codex-environment` with `codex-exec-server` dependencies and move execution-related deps into the latter, confirming the migration at the workspace dependency graph level." - ], - "slug": "openai-codex-pr-15125", - "summary": "PR #15125 migrates Codex\u2019s environment abstraction from the standalone `codex-environment` crate into `codex-exec-server`, rewiring core and server crates to import `Environment`, `ExecutorFileSystem`, and related helpers from the exec-server boundary instead.", - "title": "Move environment abstraction into the exec server crate", - "watch_state": null, - "why_it_matters": "This is mainly an architectural refactor that reduces cross-crate coupling: execution environment concerns are now centralized in the exec-server API surface, which makes it easier to support different execution backends (local vs remote) without changing core/app-server logic." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15150.analysis.json b/artifacts/github/analysis/openai-codex-pr-15150.analysis.json deleted file mode 100644 index f942005..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15150.analysis.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "caveats": [ - "Primary value is architectural; no clear new end-user feature is introduced directly in this PR.", - "The observable behavior should remain mostly stable because compatibility re-exports and updated matches were added for auth mode handling." - ], - "confidence": "likely", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "capability", - "proof_points": [ - "PR body explicitly states the intent: move auth implementation and token data into `codex-login`, while keeping `codex-core` re-exporting that surface.", - "New auth-focused files were added under `codex-rs/login/src/auth` (including `error.rs` and `util.rs`), and logic previously in `core` files (for example `core/src/error.rs` and `core/src/util.rs`) was moved to those modules.", - "`codex-rs/core/src/lib.rs` now re-exports `codex_login` auth/default-client modules so downstream crates can continue using core-facing auth APIs after the move.", - "Consumers were updated across `app-server`, `exec`, `tui`, and `cli` to import auth structures from `codex_login` compatibility paths, showing an intentional dependency re-alignment rather than a feature addition." - ], - "slug": "move-auth-stack-to-codex-login", - "summary": "PR #15150 refactors auth ownership in `openai/codex` by relocating auth implementation details into `codex-login` and making `codex-core` re-export a compatibility surface for existing callers.", - "title": "Auth stack moved into dedicated `codex-login` crate", - "watch_state": null, - "why_it_matters": "The split reduces cross-crate coupling around authentication and keeps legacy call sites working, which makes future auth changes and maintenance less risky while preserving current CLI/TUI/app-server behavior." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15154.analysis.json b/artifacts/github/analysis/openai-codex-pr-15154.analysis.json deleted file mode 100644 index c718112..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15154.analysis.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "caveats": [ - "Behavior is explicitly scoped to VSCode Terminal per PR body; other terminals may not auto-handle `file://` links the same way.", - "The change is primarily a UX/path-format improvement, not a new image generation capability.", - "A small unrelated filesystem initialization change (`Environment::default().get_filesystem()`) is present but not the user-facing headline." - ], - "confidence": "likely", - "config_flags": [], - "expected_effect": "Generated-image entries now present `Saved to` values as URI links, enabling easier direct opening of files in supported terminals and reducing manual path-copy steps.", - "how_to_try": "Generate an image in the TUI workflow and inspect the resulting history row; verify the saved location is shown as a `file://...` path (e.g., `file:///tmp/ig-1.png`) in place of `/tmp/...`, then open it in VSCode Terminal.", - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "Primary PR title/body says it adds full image paths so generated images are open-able in TUI, currently limited to VSCode Terminal.", - "In `codex-rs/tui/src/chatwidget.rs` and `codex-rs/tui_app_server/src/chatwidget.rs`, image generation end handling now maps saved paths through `url::Url::from_file_path(...).to_string()`, producing URI-formatted paths.", - "In both `codex-rs/tui/src/history_cell.rs` and `codex-rs/tui_app_server/src/history_cell.rs`, rendering changed from `saved_to` to `saved_path` and directly displays the formatted path string after `Saved to:`.", - "Snapshot and test updates in TUI history outputs/tests replace `/tmp/ig-1.png` with `file:///tmp/ig-1.png`, showing the visible output change.", - "Additional identical updates in both `codex-rs/tui` and `codex-rs/tui_app_server` indicate the behavior applies to both terminal UI variants." - ], - "slug": null, - "summary": "PR #15154 updates Codex TUI output so image-generation history entries render saved image locations as full `file://` URIs (for example `file:///tmp/...`) instead of plain local paths, across both TUI binaries.", - "title": "TUI image history now shows generated files as file URIs", - "watch_state": null, - "why_it_matters": "This makes generated images easier to open directly from the terminal history in environments that treat `file://` links as clickable resources, improving discoverability right after generation." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15163.analysis.json b/artifacts/github/analysis/openai-codex-pr-15163.analysis.json deleted file mode 100644 index 43ac372..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15163.analysis.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "caveats": [], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "PR #15163 is titled \"fix: case where agent is already closed\", matching the observed behavior correction.", - "In `codex-rs/core/src/agent/control.rs`, `shutdown_live_agent` now checks `thread.agent_status()` and returns `Ok(String::new())` when already `Shutdown`, instead of always sending `Op::Shutdown {}`.", - "The same function still materializes/flushes rollout state before returning, preserving teardown-side effects while avoiding duplicate shutdown operations.", - "In `codex-rs/core/src/tools/handlers/multi_agents/close_agent.rs`, the pre-check that skipped close handling when status was already `Shutdown` was removed, so close requests now consistently delegate to control-layer shutdown logic that now safely handles already-closed threads.", - "The change is a single-commit update touching two core agent lifecycle paths (`agent/control.rs` and `tools/handlers/multi_agents/close_agent.rs`) with `9` insertions and `14` deletions, indicating a focused behavioral fix." - ], - "slug": "openai-codex-pr-15163", - "summary": "This PR fixes the close/shutdown path for agents by making shutdown operations idempotent when an agent thread is already closed, preventing unnecessary follow-up op dispatch in already-finished states.", - "title": "Agent shutdown now handles already-closed sessions safely", - "watch_state": null, - "why_it_matters": "Users and orchestrators can issue close/shutdown actions without triggering avoidable shutdown failures or noisy errors if an agent is already in the shutdown state, which improves robustness for repeated or race-prone multi-agent workflows." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15175.analysis.json b/artifacts/github/analysis/openai-codex-pr-15175.analysis.json deleted file mode 100644 index 5f917b6..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15175.analysis.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "caveats": [ - "No direct user interface behavior is shown in the bundle; this is an internal control-flow change inferred from code/config updates." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "PR #15175 is explicitly framed as \"morpheus does not generate memories\" and is merged in `openai/codex` as a PR-first bundle (`analysis_mode: pr_first`).", - "`codex-rs/core/src/memories/phase2.rs` adds `agent_config.memories.generate_memories = false` when creating the consolidation sub-agent config, directly changing runtime behavior for that path.", - "`codex-rs/core/src/memories/tests.rs` now binds the consolidation thread id before fetching the thread and reads its `config_snapshot`, indicating test-level verification tied to the consolidation thread behavior." - ], - "slug": null, - "summary": "The PR updates phase-2 memory consolidation so the internal sub-agent is configured with `generate_memories` disabled, and test coverage is adjusted to validate the configured consolidation thread path.", - "title": "Disable memory generation during consolidation runs", - "watch_state": null, - "why_it_matters": "This prevents consolidation workers from writing memory entries while they are meant to consolidate context only, reducing the chance of recursive or noisy memory writes and helping keep stored memory cleaner and more stable across runs." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15180.analysis.json b/artifacts/github/analysis/openai-codex-pr-15180.analysis.json deleted file mode 100644 index b5f215f..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15180.analysis.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "caveats": [ - "No direct user-visible behavior changes; value appears only in exported telemetry/observability consumers" - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "capability", - "proof_points": [ - "PR #15180 is titled `chore: add metrics for profile` and has a committed change set tied to a single merged commit (`959dc2a4d88ebe8caad796b05e5a76096ba20f2e`)", - "`codex-rs/otel/src/metrics/names.rs` now defines `PROFILE_USAGE_METRIC` as `codex.profile.usage`, establishing a new named metric in the shared telemetry namespace", - "`codex-rs/otel/src/events/session_telemetry.rs` imports `PROFILE_USAGE_METRIC` and increments it via `self.counter(..., 1, &[])` whenever `active_profile.is_some()` is true" - ], - "slug": "add-profile-usage-metric", - "summary": "PR #15180 adds a new OpenTelemetry counter that increments when a session starts with an active profile, enabling explicit tracking of profile usage in telemetry pipelines.", - "title": "Added `codex.profile.usage` metric for active profile sessions", - "watch_state": null, - "why_it_matters": "This gives operators and maintainers a direct signal for how often active coding profiles are used in live sessions. It helps with visibility into feature adoption and debugging profile-related behavior without changing runtime user flows." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15185.analysis.json b/artifacts/github/analysis/openai-codex-pr-15185.analysis.json deleted file mode 100644 index 6c81ba0..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15185.analysis.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "caveats": [ - "No functional tests are shown in the bundle, so integration impact is inferred from the reverted diff and surrounding headers workflow.", - "This is explicitly a cleanup/revert; user-visible effects are likely subtle and environment-dependent." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "Primary PR narrative is explicit: title is `Revert \"Forward session and turn headers to MCP HTTP requests\"` and body states it reverts `openai/codex#15011`.", - "The revert commit is `bab2a28f26223da6a3a6f61bb9656d2f8d6cc4ee`, whose message matches the PR title and confirms intent.", - "`codex-rs/core/src/codex.rs` removes `sync_mcp_request_headers_for_turn`, which was responsible for building/stashing turn- and session-derived request headers.", - "`codex-rs/core/src/tasks/mod.rs` removes both the per-turn header sync call and turn-state header-clear calls, indicating the rollback of that lifecycle behavior.", - "`codex-rs/core/src/mcp_connection_manager.rs` and `codex-rs/core/src/mcp_connection_manager_tests.rs` remove request-header storage/carrying state in `AsyncManagedClient` and related tests.", - "`codex-rs/rmcp-client/src/rmcp_client.rs` removes request-scoped-header handling for JSON-RPC client messages, including the `message_uses_request_scoped_headers` logic." - ], - "slug": null, - "summary": "This PR rolls back PR #15011\u2019s change that forwarded session and turn metadata as headers on MCP HTTP requests, restoring prior MCP request behavior.", - "title": "Reverted MCP session/turn header forwarding", - "watch_state": null, - "why_it_matters": "It is a stabilization rollback: the revert removes a recently introduced request-header path that could alter MCP behavior for downstream MCP servers. Treating this as a behavior change is appropriate because it reverses a protocol-level transmission change that may have affected integrations, even though it does not add new capability." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15190.analysis.json b/artifacts/github/analysis/openai-codex-pr-15190.analysis.json deleted file mode 100644 index 99aba7c..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15190.analysis.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "caveats": [ - "No explicit user action path is introduced in the PR; impact is primarily on downstream analytics/tracing that consumes MCP `_meta`." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR body explicitly states the goal: group MCP tool calls with Codex sessions/turns and switch from header-based assumptions to `_meta` for `tools/call` due to shared rmcp-client concurrency constraints.", - "`codex-rs/core/src/mcp_tool_call.rs` now builds MCP request metadata via `build_mcp_tool_call_request_meta(turn_context, ...)`, explicitly plumbing turn/session context into request metadata while preserving existing `_codex_apps` handling.", - "`codex-rs/core/src/turn_metadata.rs` adds `session_id` to the metadata bag and wires it through metadata construction, and `codex-rs/core/src/codex.rs` passes `conversation_id` into turn-context creation.", - "`codex-rs/rmcp-client/src/rmcp_client.rs` updates `CallToolRequestParams` transport to send per-request metadata explicitly through RMCP request options instead of relying on mutable shared client header state.", - "`codex-rs/core/src/mcp_tool_call_tests.rs` and `codex-rs/core/tests/suite/search_tool.rs` add assertions confirming `_meta` includes expected turn/session info (including existing `resource_uri`/`_codex_apps` expectations) for custom MCP and codex-apps flows.", - "The file changes are mostly additive metadata/transport plumbing with no removal of prior user-facing tool-call features, matching an incremental internal behavior update for observability and traceability." - ], - "slug": null, - "summary": "This PR shifts MCP `tools/call` metadata handling so MCP tool calls now carry both session and turn context in the request `_meta` payload, restoring end-to-end traceability across MCP interactions that previously lacked it.", - "title": "Propagate MCP tool-call context through `_meta`", - "watch_state": null, - "why_it_matters": "By putting turn/session identifiers into MCP call metadata, the change enables better correlation of tool calls with user turns and sessions in downstream processing and observability systems, which improves debugging and incident triage for multi-step MCP workflows without changing user-facing command behavior." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15195.analysis.json b/artifacts/github/analysis/openai-codex-pr-15195.analysis.json deleted file mode 100644 index d9788e0..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15195.analysis.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "confidence": "confirmed", - "expected_effect": "A plugin install that bundles MCP servers should make those servers available immediately after install, with auth surfaced as part of the install flow when needed.", - "how_to_try": "Enable plugins, call `plugin/install` for a plugin that ships an `.mcp.json`, then issue a follow-up plugin or MCP read in the same session.", - "impact": "medium", - "kind": "try_now", - "proof_points": [ - "The app-server README now documents `plugin/install` as installing bundled MCPs in addition to returning auth policy information.", - "The new `plugin_mcp_oauth.rs` path wires OAuth login handling for MCP servers discovered during plugin installation.", - "The integration test `plugin_install_makes_bundled_mcp_servers_available_to_followup_requests` verifies that bundled MCP servers become usable right after install." - ], - "slug": "plugin-install-now-installs-bundled-mcps", - "summary": "Merged PR #15195 extends `plugin/install` so marketplace plugins can install their bundled MCP servers during the install flow instead of leaving that as a separate manual step.", - "title": "`plugin/install` now installs bundled MCP servers", - "why_it_matters": "Plugin setup becomes much closer to one step. Users and client builders get fewer post-install surprises when a plugin depends on MCP servers or requires OAuth to complete setup." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15196.analysis.json b/artifacts/github/analysis/openai-codex-pr-15196.analysis.json deleted file mode 100644 index 12ae87d..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15196.analysis.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "caveats": [ - "The new setting is marked \"Experimental / do not use,\" so behavior and stability are intentionally less guaranteed than stable config fields." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "capability", - "proof_points": [ - "The PR metadata identifies the change as \"Add experimental exec server URL handling\" and documents it as merged in `openai/codex` PR 15196, with commit `1f0ed50a125c758bb8d4d2f71e425e9eef3519f4`.", - "`codex-rs/core/src/config/mod.rs` adds `experimental_exec_server_url: Option` to both `Config` and `ConfigToml`, with doc text explicitly describing it as an override for the remote exec server URL.", - "`codex-rs/core/src/config.schema.json` adds `experimental_exec_server_url` as a schema field, exposing it as a configurable runtime input.", - "`codex-rs/core/src/codex.rs` now constructs `environment` via `Environment::create(config.experimental_exec_server_url.clone()).await?`, directly consuming the new config value during session setup.", - "`codex-rs/exec-server/src/environment.rs` replaces a unit `Environment` with a stateful struct that stores the optional override URL and a possible `remote_exec_server_client`, supporting the configurable connection path." - ], - "slug": "openai-codex-pr-15196", - "summary": "PR #15196 introduces an experimental `experimental_exec_server_url` configuration key and wires it into environment/session creation, allowing the remote exec server endpoint to be overridden from defaults.", - "title": "Add experimental exec server URL override for Codex sessions", - "watch_state": null, - "why_it_matters": "It gives operators a configurable escape hatch for deployment-specific or proxied exec-server setups without requiring code changes, improving flexibility while keeping existing behavior unchanged by default." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15198.analysis.json b/artifacts/github/analysis/openai-codex-pr-15198.analysis.json deleted file mode 100644 index 99183f0..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15198.analysis.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "caveats": [ - "The PR does not yet switch `just clippy` or other routine CI/dev hooks to use this package automatically, so behavior in existing daily commands is not fully transitioned yet." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "capability", - "proof_points": [ - "Primary PR context states the previous linter flow required building from source and that PRs commonly failed later in CI because violations were not checked until later, motivating a prebuilt artifact distribution.", - "The new `.github/dotslash-argument-comment-lint-config.json` defines packaged outputs for `macos-aarch64`, `linux-x86_64`, `linux-aarch64`, and `windows-x86_64`, each pointing to `argument-comment-lint` executables under a packaged `bin/` path.", - "A new `.github/workflows/rust-release-argument-comment-lint.yml` workflow is added to build host-specific release artifacts (archives/zips), and `rust-release.yml` is updated to invoke it and publish the new DotSlash manifest on releases.", - "`tools/argument-comment-lint/src/bin/argument-comment-lint.rs` adds a dedicated runner binary that resolves sibling packaged binaries/libraries and invokes `cargo-dylint` with the bundled library path, making the release artifact directly runnable.", - "Docs were updated (`tools/argument-comment-lint/README.md`) to describe the released `argument-comment-lint` DotSlash package layout and target coverage." - ], - "slug": "openai-codex-pr-15198", - "summary": "The PR adds cross-platform release packaging for the argument-comment linter so teams can use a runnable artifact instead of building the linting tool from source.", - "title": "argument-comment lint is now shipped as a runnable DotSlash package", - "watch_state": null, - "why_it_matters": "This changes the developer workflow from rebuild-required to consume-ready, which reduces setup time and makes the lint more likely to be run earlier in day-to-day development and review loops." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15199.analysis.json b/artifacts/github/analysis/openai-codex-pr-15199.analysis.json deleted file mode 100644 index a7f34de..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15199.analysis.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "caveats": [ - "Primarily affects contributor and CI lint infrastructure rather than end-user runtime behavior.", - "Behavioral value depends on developers consuming the updated `just` targets and CI workflows." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": "Linting uses the checked-in prebuilt package path, so normal local/CI runs avoid rebuilding the lint from source and no longer fail due to the Windows `RUSTUP_HOME`/toolchain naming assumptions the PR fixes.", - "how_to_try": "Check out the PR version, run `just argument-comment-lint` (or CI `argument_comment_lint_prebuilt` path in `rust-ci.yml`), and confirm it executes via `tools/argument-comment-lint/run-prebuilt-linter.sh` with the DotSlash manifest.", - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "PR #15199 body explicitly states the lint path is moving to the released DotSlash artifact for normal CI and `just` workflows, while preserving source-build for active lint development.", - "The `files` changes include adding `tools/argument-comment-lint/argument-comment-lint` and `run-prebuilt-linter.sh`, plus wrapper behavior updates in `tools/argument-comment-lint/run.sh` and `src/bin/argument-comment-lint.rs` to normalize nightly toolchain naming and infer `RUSTUP_HOME` when needed.", - "CI and task wiring changed to enforce this path: `.github/workflows/rust-ci.yml` is split into dedicated lint jobs and `justfile` routes `argument-comment-lint` to the prebuilt wrapper instead of the source wrapper.", - "A follow-up evidence bundle commit (`a1b8effb15edfc337ba58215adc4d70f60c548ac`) adds compatibility fixes across Windows sandbox call sites to make the stricter lint clean on current code, supporting the PR\u2019s stated goal of a green prebuilt lint run." - ], - "slug": null, - "summary": "PR #15199 retools the repository's standard argument-comment lint path to use a checked-in DotSlash package and introduces a separate source-build wrapper path, while fixing rustup/toolchain compatibility in the prebuilt runner.", - "title": "Argument-comment lint now uses the released DotSlash artifact by default", - "watch_state": null, - "why_it_matters": "This reduces lint turnaround in normal dev and CI workflows by avoiding repeated source builds and making the lint job more deterministic. It also lowers environment-fragility on CI, especially Windows, by standardizing toolchain context handling for the shipped prebuilt lint package." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15201.analysis.json b/artifacts/github/analysis/openai-codex-pr-15201.analysis.json deleted file mode 100644 index 3dd6c61..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15201.analysis.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "caveats": [], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The pull request description explicitly says it logs guardian-reviewed tool approvals as `source=automated_reviewer` while keeping user approvals as `source=user` and config approvals as `source=config`.", - "`codex-rs/core/src/tools/orchestrator.rs` adds `ToolDecisionSource::AutomatedReviewer` and chooses it when `routes_approval_to_guardian(turn_ctx)` is true before calling `otel.tool_decision(...)`.", - "`codex-rs/otel/src/lib.rs` extends `ToolDecisionSource` with an `AutomatedReviewer` enum variant so the new telemetry source is represented end-to-end.", - "The single commit `a3dcc1d69412c7f72ef738a8ef86a84bf7915c69` contains the focused code changes in both the orchestrator and OpenTelemetry enum definitions." - ], - "slug": "openai-codex-pr-15201", - "summary": "The change introduces a new `automated_reviewer` approval source in Codex\u2019s tool decision telemetry so guardian-reviewed approvals are emitted distinctly from user and config approvals.", - "title": "Add automated reviewer as separate source for tool approval telemetry", - "watch_state": null, - "why_it_matters": "This improves observability and operator workflows by making approval provenance explicit in `codex.tool_decision` logs, reducing ambiguity when debugging policy decisions and investigating approval behavior in production." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15203.analysis.json b/artifacts/github/analysis/openai-codex-pr-15203.analysis.json deleted file mode 100644 index bfd35f8..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15203.analysis.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "caveats": [ - "The PR describes this as a dummy proof-of-concept crate reserved for future V8 experiments, not a shipped user feature.", - "The PR body notes likely compatibility drift in the Windows Bazel builder and says Windows Bazel support still needs follow-up work." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "capability", - "proof_points": [ - "The PR body explicitly says the new dummy `v8-poc` project demonstrates that Codex can successfully link and use V8 on the platforms it wants to target.", - "`codex-rs/Cargo.toml` adds a new `v8-poc` workspace member and pins `v8 = =146.4.0`, while `codex-rs/v8-poc/src/lib.rs` adds a proof-of-concept crate with tests that initialize V8 and evaluate an expression.", - "`MODULE.bazel`, `third_party/v8/BUILD.bazel`, and the Rust CI and release workflows add target-specific V8 artifact wiring, including musl overrides and Bazel-side source-built archives for shipped platforms." - ], - "slug": "codex-adds-cross-platform-v8-proof-of-concept", - "summary": "Merged PR #15203 adds a `v8-poc` crate plus Bazel and CI wiring that proves Codex can link and run V8 across the main supported non-Windows target platforms.", - "title": "Codex adds a cross-platform V8 integration proof of concept", - "watch_state": "Watch for follow-up PRs that replace the dummy crate with a real V8-backed feature or re-enable the Windows Bazel path.", - "why_it_matters": "This does not expose a new end-user feature yet, but it gives Codex a verified integration path for embedding V8 in future runtime or tooling work without treating cross-platform linking as an open risk." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15206.analysis.json b/artifacts/github/analysis/openai-codex-pr-15206.analysis.json deleted file mode 100644 index 3a5811d..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15206.analysis.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "caveats": [ - "This is an internal observability change; user-facing product behavior is unchanged for normal clients.", - "Signal mainly helps people consuming traces, not end users directly." - ], - "confidence": "confirmed", - "config_flags": [ - "API" - ], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "capability", - "proof_points": [ - "PR #15206 explicitly sets the goal to tag app-server turn spans with `turn.id` for `turn/start`, `turn/steer`, and `turn/interrupt`.", - "`primary_pr.title` and the two commits (`7f9f93e057d7c73a84ea071aabcc88ddfe743150`, `8e042a093cceac78a48d1d8cabd6f6247e51532f`) are narrowly scoped to tracing and turn-id propagation.", - "`app_server_tracing.rs` adds a `turn.id` field into the server request span template.", - "`codex_message_processor.rs` records turn IDs into request context for both turn start and turn interrupt request flows.", - "`outgoing_message.rs` introduces a shared context method that stores turn IDs by `request_id`, and tracing tests assert `turn.id` equals the returned turn ID." - ], - "slug": "openai-codex-pr-15206", - "summary": "This PR adds instrumentation to propagate `turn.id` into app-server request tracing for `turn/start`, `turn/steer`, and `turn/interrupt`, plus tests to verify the attribute is present.", - "title": "Trace spans now include turn IDs for app-server turn APIs", - "watch_state": null, - "why_it_matters": "It gives operators and maintainers a reliable way to correlate all spans for a single turn across the app-server lifecycle, making debugging and latency triage more precise without changing API payloads." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15207.analysis.json b/artifacts/github/analysis/openai-codex-pr-15207.analysis.json deleted file mode 100644 index 54b6f39..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15207.analysis.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "caveats": [ - "This is primarily guidance-level metadata and tests; it improves documented behavior contracts but does not itself hard-enforce all destructive-command prevention at runtime." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR\u2019s main narrative is a documentation-focused change: `primary_pr.body` adds explicit Windows destructive command rules for `exec_command`, `shell`, and `shell_command` (avoid cross-shell composition, prefer native PowerShell cmdlets, validate absolute workspace-local targets before recursive delete/move).", - "In `codex-rs/core/src/tools/spec.rs`, the bundle shows a new `windows_destructive_filesystem_guidance()` helper plus `windows_shell_safety_description()` integration, indicating the safety guidance is part of tool-spec text generation.", - "`codex-rs/core/src/tools/spec_tests.rs` was updated to include the revised Windows text in expected shell spec output, tying the change to public-facing schema/help behavior and protecting against regression.", - "`codex-rs/exec-server/src/server/filesystem.rs` includes `Environment::default().get_filesystem()` and the commit sequence includes compile/test fixes, supporting the same PR as a cohesive maintenance pass around rollout stability." - ], - "slug": "openai-codex-pr-15207", - "summary": "This PR updates Codex tool descriptions and related tests to give stronger, consistent Windows safety guidance for filesystem-destructive commands, while a small supporting server change fixes construction/default behavior for filesystem access.", - "title": "Windows shell guidance now includes explicit destructive-command safety rules", - "watch_state": null, - "why_it_matters": "It reduces the chance of dangerous cross-shell command patterns on Windows by clearly documenting safer practices in the tool specs users see before execution, which helps prevent accidental deletes or moves from mis-composed shell pipelines." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15215.analysis.json b/artifacts/github/analysis/openai-codex-pr-15215.analysis.json deleted file mode 100644 index f3c63e1..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15215.analysis.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config_flags": [ - "--enable plugins", - "--enable tui_app_server" - ], - "confidence": "confirmed", - "expected_effect": "The TUI should expose a searchable `/plugins` flow with loading states, plugin detail views, and back navigation, and the same flow should work under `tui_app_server`.", - "how_to_try": "Launch Codex CLI with `--enable plugins`, run `/plugins`, search the curated marketplace, and open a plugin detail view. Optionally repeat with `--enable tui_app_server`.", - "impact": "medium", - "kind": "try_now", - "proof_points": [ - "The PR body explicitly describes a preliminary `/plugins` menu in both `tui` and `tui_app_server`, including loading, search, and detail-view behavior.", - "The testing section in the PR body documents the exact `/plugins` flow to verify, including disabled behavior when plugins are not enabled.", - "The patch adds plugin list and plugin read wiring in `tui/src/app.rs` and `tui/src/app_event.rs`, which is the concrete UI plumbing for the new menu." - ], - "slug": "plugins-browser-lands-in-tui", - "summary": "Merged PR #15215 adds a first `/plugins` browser to both `tui` and `tui_app_server`, with listing, search, and read-only plugin detail views.", - "title": "A first `/plugins` browser lands in the TUI", - "why_it_matters": "Plugin discovery moves into the terminal itself. Early users no longer need to hunt for plugin metadata outside Codex just to browse what is available and inspect what a plugin contains." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15216.analysis.json b/artifacts/github/analysis/openai-codex-pr-15216.analysis.json deleted file mode 100644 index c2581b7..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15216.analysis.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "caveats": [ - "Evidence points to significant internal modularity work, but no explicit user-facing behavioral delta is documented in the PR body.", - "Patch excerpts are partial for some file moves, so end-to-end semantic equivalence is inferred from import/manifest rewiring rather than full method-by-method diff parity." - ], - "confidence": "likely", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "Primary PR body states the refactor goal: move `core/src/terminal.rs` and tests into a standalone `terminal-detection` crate and update direct consumers to depend on `codex-terminal-detection`.", - "`codex-rs/Cargo.toml`, `codex-rs/Cargo.lock`, and multiple crate manifests (`codex-rs/cli/Cargo.toml`, `codex-rs/core/Cargo.toml`, `codex-rs/tui/Cargo.toml`, `codex-rs/tui_app_server/Cargo.toml`) now include `terminal-detection` as a workspace member/dependency.", - "`codex-rs/core/src/lib.rs` removes the previous `pub mod terminal`, while `cli`, `core`, `tui`, `tui_app_server`, and MCP test code replace `codex_core::terminal::*` imports with `codex_terminal_detection::*`.", - "`codex-rs/terminal-detection/BUILD.bazel` and `codex-rs/terminal-detection/Cargo.toml` are added, and terminal-related source/tests are moved (`src/lib.rs`, `src/terminal_tests.rs`) indicating the new crate boundary is active in repository structure." - ], - "slug": "openai-codex-pr-15216", - "summary": "This PR refactors terminal-related code by extracting `core/src/terminal.rs` and its tests into a new `terminal-detection` workspace crate, then rewires CLI, core, TUI, TUI app server, and MCP-related code to consume the new crate through existing imports and dependency entries.", - "title": "Move terminal utilities into a dedicated terminal-detection crate", - "watch_state": null, - "why_it_matters": "The change centralizes terminal detection and user-agent/terminal-type logic behind a single shared crate, which reduces duplicate terminal dependencies across binaries and should make terminal behavior updates easier to maintain consistently across the CLI and UI stacks. User-visible behavior is expected to remain unchanged, so the practical impact is mostly engineering hygiene and modularity." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15217.analysis.json b/artifacts/github/analysis/openai-codex-pr-15217.analysis.json deleted file mode 100644 index 5195642..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15217.analysis.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "caveats": [ - "Only plugin schema objects and test-covered flows in this patch explicitly reflect the new field; other client surfaces may need follow-up updates to render or consume `needsAuth`.", - "When the auth-status lookup is not ready or fails, behavior follows existing fallback paths in helper logic.", - "The PR body is brief, so downstream UI behavior is an integration responsibility, not part of this patch alone." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "capability", - "proof_points": [ - "The PR narrative is explicitly `feat: expose needs_auth for plugin/read` and the merge record is `openai/codex#15217`.", - "Schema updates add `needsAuth: boolean` and require it in `codex_app_server_protocol.schemas.json`, `codex_app_server_protocol.v2.schemas.json`, `v2/PluginInstallResponse.json`, and `v2/PluginReadResponse.json`, indicating a contract-level API field addition.", - "Protocol type definitions were updated in `v2/AppSummary.ts` and `protocol/v2.rs` to carry a `needs_auth`/`needsAuth` field in `AppSummary`, with conversion paths updated to populate it.", - "`plugin_app_helpers.rs` now queries connector access/auth status before emitting plugin summaries, and returns early with plugin summaries when the auth-status service is unavailable, showing runtime handling for the new metadata.", - "Tests in `plugin_install.rs` were updated with `needs_auth: true` expectations for auth-required plugin cases, anchoring the new behavior in executable behavior checks." - ], - "slug": "openai-codex-pr-15217", - "summary": "PR #15217 extends Codex plugin summary payloads with a new required `needsAuth` boolean so `plugin/read`/`plugin/list` style responses can explicitly signal whether an app requires authorization.", - "title": "Expose `needsAuth` in plugin app summaries", - "watch_state": null, - "why_it_matters": "Clients can now distinguish auth-gated plugins from directly usable ones before launching a plugin flow, which enables cleaner UI behavior (e.g., showing auth prompts or disabled actions) without guessing from installer metadata alone." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15220.analysis.json b/artifacts/github/analysis/openai-codex-pr-15220.analysis.json deleted file mode 100644 index 6d00c0d..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15220.analysis.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "confidence": "confirmed", - "expected_effect": "API v2 clients should receive `starting`, `ready`, `failed`, or `cancelled` MCP startup updates, including the server name and optional error details.", - "how_to_try": "Connect to the app-server v2 notification stream, trigger an MCP server startup or refresh, and listen for `mcpServer/startupStatus/updated` events.", - "impact": "medium", - "kind": "try_now", - "proof_points": [ - "The PR body explicitly says the legacy MCP startup update event is now exposed as an API v2 notification.", - "The protocol schemas add both `McpServerStartupState` and `McpServerStatusUpdatedNotification`, which defines the new event contract.", - "The server notification schema now registers `mcpServer/startupStatus/updated` as a first-class v2 notification method." - ], - "slug": "mcp-startup-status-updated-notification", - "summary": "Merged PR #15220 promotes MCP startup progress to a first-class API v2 notification instead of leaving it as a legacy internal event.", - "title": "`mcpServer/startupStatus/updated` lands in API v2", - "why_it_matters": "Clients can track MCP readiness without scraping legacy events. That makes plugin UIs, status surfaces, and startup diagnostics easier to build and less brittle." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15222.analysis.json b/artifacts/github/analysis/openai-codex-pr-15222.analysis.json deleted file mode 100644 index 3baf15b..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15222.analysis.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config_flags": [], - "confidence": "confirmed", - "expected_effect": "New image-generation outputs should land under `codex_home/generated_images//...` instead of an OS temp directory.", - "how_to_try": "Generate an image from a Codex build that includes PR #15222, then inspect the active `codex_home` directory for a `generated_images//` subtree.", - "impact": "medium", - "kind": "behavior_change", - "proof_points": [ - "The PR body explicitly says the default image-generation save directory now points at `codex_home` with a per-thread path.", - "The Rust helper in `stream_events_utils.rs` now builds image artifact paths from `codex_home` and a dedicated `generated_images` directory instead of `std::env::temp_dir()`.", - "Tests in `codex_tests.rs` and `stream_events_utils_tests.rs` were updated to expect the new codex-home path, which confirms this is a behavior change rather than a comment-only cleanup." - ], - "slug": "generated-images-save-under-codex-home", - "summary": "Merged PR #15222 moves generated-image artifacts out of transient temp paths and into a predictable location under `codex_home`.", - "title": "Generated images now save under `codex_home` instead of a temp directory", - "why_it_matters": "This makes image outputs easier to find, persist, and script against. Users who rely on generated artifacts no longer have to guess where a temp file landed or whether it will disappear between runs." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15223.analysis.json b/artifacts/github/analysis/openai-codex-pr-15223.analysis.json deleted file mode 100644 index 94fb78f..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15223.analysis.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "confidence": "confirmed", - "expected_effect": "Resumed threads should show earlier image-generation items and preserve saved image paths instead of dropping that part of the history.", - "how_to_try": "Resume a thread that previously generated images, then inspect the thread history and completed items for restored image entries and saved paths.", - "impact": "medium", - "kind": "behavior_change", - "proof_points": [ - "The PR body explicitly says image-generation items should be restored in resumed thread history.", - "The protocol schemas add a `savedPath` field to image-related notifications and thread responses so image history can carry the persisted file location.", - "The commit sequence includes `Persist image generation end events for history replay` and `Add tests for resumed image history preservation`, which confirms this is an intended behavior change." - ], - "slug": "resumed-threads-restore-image-history", - "summary": "Merged PR #15223 restores image-generation items in resumed thread history and preserves the saved paths tied to those earlier outputs.", - "title": "Resumed threads now restore generated image history", - "why_it_matters": "Image work no longer disappears when you come back to an older thread. That makes long-running image sessions easier to resume, audit, and copy from without losing the earlier generation context." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15232.analysis.json b/artifacts/github/analysis/openai-codex-pr-15232.analysis.json deleted file mode 100644 index 0531808..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15232.analysis.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "caveats": [ - "Most of the change is structural and test/implementation cleanup around existing exec-server behavior, not a large outward feature addition.", - "Remote behavior is now explicit in codepaths, which is an internal architecture change that may still need operational validation in real remote deployments." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "capability", - "proof_points": [ - "PR narrative states the change pattern per feature: trait on environment, local implementation, remote implementation via network proxy, and handler integration for PRC requests.", - "`codex-rs/exec-server/src/file_system.rs` introduces the shared `ExecutorFileSystem` interface and supporting option/type definitions.", - "`codex-rs/exec-server/src/environment.rs` switches `get_filesystem` to return `RemoteFileSystem` when a remote client exists, otherwise `LocalFileSystem`.", - "`codex-rs/exec-server/src/remote_file_system.rs` adds a new remote implementation that fulfills the shared trait with protocol-level filesystem operations.", - "`codex-rs/exec-server/tests/file_system.rs` adds a large parameterized test module covering filesystem operations for both local and remote file systems.", - "`codex-rs/exec-server/src/server/file_system_handler.rs` and `codex-rs/exec-server/src/server/handler.rs` are updated to use the new module split and handler naming.", - "`codex-rs/exec-server/src/server/transport.rs` adds websocket URL output support used by the test harness (`exec_server.rs`)." - ], - "slug": "openai-codex-pr-15232", - "summary": "The PR refactors filesystem handling in codex-exec-server behind a shared trait with separate local and remote implementations, and routes execution environment selection through that abstraction.", - "title": "Split ExecServer filesystem into local and remote backends", - "watch_state": null, - "why_it_matters": "This reduces coupling between local and remote execution paths, making remote file operations a first-class implementation path rather than ad-hoc logic and improving confidence the same filesystem behavior is available in both modes. It also sets up safer future changes by isolating responsibilities and extending test coverage to both backends." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15233.analysis.json b/artifacts/github/analysis/openai-codex-pr-15233.analysis.json deleted file mode 100644 index 9830d0a..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15233.analysis.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "caveats": [ - "The bundle provides architectural evidence but does not include a direct user-facing demo or runtime benchmark, so effects are inferred from implementation/test structure rather than explicit end-user behavior docs." - ], - "confidence": "likely", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR body states the new structure explicitly: `LocalProcess` is the real implementation, `RemoteProcess` is a thin network proxy, and `Environment` now exposes `ExecProcess`.", - "`local_backend.rs` is removed and replaced by new `process.rs`, `local_process.rs`, and `remote_process.rs`, indicating a concrete local/remote split behind a shared `ExecProcess` trait.", - "`environment.rs` is changed to hold an `Arc` and initialize an execution strategy (local default with optional remote override), so consumers depend on a common process API.", - "Server wiring adds `process_handler.rs` and updates `server/handler.rs` to delegate execution handling through that adapter, centralizing execution semantics.", - "The new `exec_process.rs` test exercises process behavior for both local and remote modes, directly validating parity across backends." - ], - "slug": null, - "summary": "PR #15233 refactors the exec-server process stack by extracting a shared process interface, wiring `Environment` and handlers to it, and adding explicit local and remote implementations plus shared execution tests.", - "title": "Exec process layer now uses a shared local/remote abstraction", - "watch_state": null, - "why_it_matters": "The change reduces the chance of local and remote command-execution behavior diverging by enforcing a single process contract for both backends. That gives users more reliable command execution behavior and makes future runtime backend changes safer and less likely to introduce regressions." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15252.analysis.json b/artifacts/github/analysis/openai-codex-pr-15252.analysis.json deleted file mode 100644 index 38d6c7a..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15252.analysis.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "caveats": [ - "This is explicitly temporary behavior with explicit intent to re-enable later once Windows support is verified." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "Primary PR metadata identifies the change as \u201cDisable hooks on windows for now\u201d and includes a temporary-status body (\u201cWe'll verify a bit later... and re-enable\u201d).", - "The modified file `codex-rs/hooks/src/engine/mod.rs` adds a `cfg!(windows)` early return that initializes `handlers` as empty and sets a `warnings` message stating hooks are disabled because lifecycle hooks are not yet supported on Windows.", - "Bundle evidence shows exactly one commit (`933ef3ccbdeb1877f2f9ba45d1379826f4c63088`) with message \u201cdisable hooks on windows for now,\u201d and that commit is tied to the merged PR 15252.", - "The patch shows no deletions and only additions around hook startup logic, indicating a targeted behavioral gate rather than broad refactoring." - ], - "slug": "openai-codex-pr-15252", - "summary": "The PR changes hook initialization so Windows sessions skip lifecycle hook discovery and return with no hooks plus a warning, reflecting that `codex_hooks` is not supported on Windows yet.", - "title": "Hooks are disabled on Windows sessions in Codex Rust hook engine", - "watch_state": null, - "why_it_matters": "This prevents Windows users from hitting unsupported-hook execution paths by failing safely with a clear warning, which is a user-facing behavior change that keeps sessions usable while hook support is incomplete on that platform." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15253.analysis.json b/artifacts/github/analysis/openai-codex-pr-15253.analysis.json deleted file mode 100644 index 538d89a..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15253.analysis.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "caveats": [ - "No direct user-visible behavior is documented in the PR body; impact is primarily architectural and should be treated as internal technical debt reduction.", - "Some features-related call sites changed import paths (`codex-core` to `codex-features`), so regressions in behavior are unlikely but should be monitored during integration." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR body explicitly states the intent: \"Split the feature system into a new `codex-features` crate\" and migrate `codex-core` and workspace consumers to new config and warning APIs.", - "Primary PR metadata includes commit `dc4350ff7d118af85cb2eff28447ce9c440f0610` with title `Split features into codex-features crate` and merged state.", - "New crate wiring was added in `codex-rs/features/Cargo.toml` plus `codex-rs/features/BUILD.bazel`, and the workspace now references it from `codex-rs/Cargo.toml` and multiple component `Cargo.toml` files.", - "File-level patches repeatedly replace imports like `codex_core::features::...` with `codex_features::...` across app server, core, CLI, TUI, and MCP crates, confirming the migration is system-wide rather than localized." - ], - "slug": "openai-codex-pr-15253", - "summary": "This PR reorganizes feature flag definitions and APIs into a new `codex-features` crate and rewires existing binaries, libraries, and tests to consume that crate instead of feature symbols from `codex-core`. The change also updates workspace dependencies so `features` participates as a first-class workspace member and dependency across the Rust workspace.", - "title": "Refactor: move feature-flag plumbing into a dedicated `codex-features` crate", - "watch_state": null, - "why_it_matters": "Decoupling feature gating from `codex-core` reduces cross-crate coupling and creates a cleaner ownership boundary for feature control, which improves maintainability as feature definitions and warning behavior can evolve without being tied to core runtime changes. It also helps avoid broad feature-related churn inside core, making future CLI/server/TUI edits safer and easier to release." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15254.analysis.json b/artifacts/github/analysis/openai-codex-pr-15254.analysis.json deleted file mode 100644 index 2603d0e..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15254.analysis.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "caveats": [ - "The new `FullBuffer` behavior is internal to the codepath and does not expose a user-facing toggle in this PR.", - "The visible impact is mostly internal execution behavior and resilience rather than an immediate end-user feature change." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "`primary_pr.body` states the change was introduced to separate shell-style output capping from internal-helper execution needs and to avoid output truncation/hang risks for trusted helpers.", - "`codex-rs/core/src/exec.rs` adds `ExecCapturePolicy` with `ShellTool` (default) and `FullBuffer`, making capture strategy explicit in `ExecParams`.", - "`codex-rs/app-server/src/codex_message_processor.rs` wires `disable_output_cap` to `ExecCapturePolicy::FullBuffer`, so callers can opt into uncapped buffering when appropriate.", - "The exec and sandbox paths now accept and propagate `capture_policy` through structs in `codex-rs/core/src/sandboxing/mod.rs`, `codex-rs/core/src/tools/runtimes/unix_escalation.rs`, and related handlers/tests, indicating cross-cutting behavior change rather than a single-site tweak.", - "`codex-rs/core/src/exec_tests.rs` and related test updates rename coverage to `read_output_limits_retained_bytes_for_shell_capture` and add full-buffer/inherited-pipe considerations, showing guard-preservation (`IO_DRAIN_TIMEOUT_MS`) for edge cases.", - "Change set includes updates across many construction sites (`shell.rs`, `user_shell.rs`, `apply_patch.rs`, `js_repl/mod.rs`, etc.), consistent with PR-first evidence that semantics are applied consistently where exec is invoked." - ], - "slug": "openai-codex-pr-15254", - "summary": "PR #15254 updates the exec pipeline to make output-capture semantics explicit via an `ExecCapturePolicy`, adding a `FullBuffer` mode for trusted internal helpers while preserving existing shell-style capped behavior by default.", - "title": "Added a full-buffer exec capture mode with safer pipe draining", - "watch_state": null, - "why_it_matters": "This improves reliability and correctness for internal tool invocations by preventing accidental truncation on trusted helpers that need complete output, while keeping the long-standing capped/timeout behavior for normal shell-style tool calls and avoiding hangs when child processes leave inherited stdout/stderr pipes open after exit." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15259.analysis.json b/artifacts/github/analysis/openai-codex-pr-15259.analysis.json deleted file mode 100644 index 9d85c44..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15259.analysis.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "caveats": [ - "The user-facing retry flow is scoped to interactive `tui` and `tui_app_server` handling of non-steerable turns such as manual `/compact` and `/review`." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": "The follow-up should appear under `Messages to be submitted at end of turn` while the current turn is active, then submit automatically once `/compact` or `/review` finishes.", - "how_to_try": "Run a Codex build that includes PR #15259, start a manual `/compact` in the TUI, submit another prompt before compact finishes, and wait for the turn to complete. Optionally repeat in `tui_app_server` or while `/review` is running.", - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR body explicitly says the goal is to queue input after the user submits `/compact` until that manual compact turn ends, mirror the behavior in the app-server TUI, and add regressions for messages queued before and during compact.", - "`codex-rs/protocol/src/protocol.rs` adds `NonSteerableTurnKind::{Review, Compact}` plus an `ActiveTurnNotSteerable` error, showing `/review` and manual `/compact` now take a distinct retryable path instead of generic steering.", - "Both `codex-rs/tui/src/chatwidget.rs` and `codex-rs/tui_app_server/src/chatwidget.rs` add a `rejected_steers_queue`, and the pending-input preview snapshots add a new `Messages to be submitted at end of turn` section for those deferred prompts.", - "New and updated snapshots for `compact_queues_user_messages` and `review_queues_user_messages`, along with `codex-rs/core/src/codex_tests.rs`, verify the queued-retry behavior and visible messaging in both frontends." - ], - "slug": "manual-compact-now-queues-follow-up-prompts-until-turn-end", - "summary": "PR #15259 changes both `tui` and `tui_app_server` so input submitted around a manual `/compact` is held and retried after that non-steerable turn finishes, instead of being treated like a normal same-turn steer.", - "title": "Manual `/compact` now queues follow-up prompts until the turn ends", - "watch_state": null, - "why_it_matters": "This makes maintenance flows less brittle. Users can keep typing during a manual compact or review turn without losing the follow-up or having to re-enter it, and the UI now makes that deferred submission explicit." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15262.analysis.json b/artifacts/github/analysis/openai-codex-pr-15262.analysis.json deleted file mode 100644 index 668b853..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15262.analysis.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "caveats": [ - "`patch_excerpt` for the main follow-up logic is partial, so implementation details like exact caching behavior are inferred from PR body plus supporting file-level edits." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "PR body says this change adds a guardian follow-up reminder and caches prior-review state on the guardian session, indicating user-visible follow-up behavior is the intent.", - "`commits` show a two-step PR implementation focused on the same feature (`Add guardian follow-up reminder` and wording refinement), supporting a cohesive behavior change rather than unrelated refactors.", - "`codex-rs/core/src/guardian/review_session.rs` introduces `GUARDIAN_FOLLOWUP_REVIEW_REMINDER` and related session-level plumbing, directly aligning with the stated PR intent to alter follow-up request messaging.", - "`codex-rs/core/src/guardian/snapshots/...guardian_followup_review_request_layout.snap` and `codex-rs/core/src/guardian/tests.rs` were updated to expect/verify the new reminder text in follow-up request output." - ], - "slug": null, - "summary": "PR 15262 refines guardian follow-up review behavior by adding a short reminder to follow-up prompts and updating tests/snapshots so reused prior-review context is surfaced more consistently during repeat approvals.", - "title": "Guardian follow-up reviews now include a reusable review-context reminder", - "watch_state": null, - "why_it_matters": "For users hitting repeated guardian follow-up requests, the system now explicitly states how to proceed with previously rejected actions, reducing ambiguity and making the second-pass approval flow clearer and safer without changing core functionality elsewhere." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15263.analysis.json b/artifacts/github/analysis/openai-codex-pr-15263.analysis.json deleted file mode 100644 index b30e14d..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15263.analysis.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "caveats": [ - "Any plugin descriptors that accidentally serialize an explicit empty products array for unrestricted plugins will now be treated as disallowed and may need migration to `products: null` or omission." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR title/body (`fix: Distinguish missing and empty plugin products`), plus merge commit `93e87d1e8182afdac575d679564be6d677d4a42f`, explicitly define the semantic change.", - "`codex-rs/core/src/plugins/marketplace.rs` now matches `policy.products` as `None => true`, `Some([]) => false`, and only allows a match for non-empty product lists, replacing the prior `is_empty`-based rule.", - "`codex-rs/core/src/plugins/manager.rs` mirrors this with `Option<&[Product]>` handling (`None => true`, `Some([]) => false`) so manager-level checks follow the same missing-vs-empty contract.", - "Plugin model and tests (`codex-rs/core/src/plugins/marketplace.rs`, `manager.rs`, `marketplace_tests.rs`, `manager_tests.rs`) were updated to carry `Option>` and use `products: None` for unrestricted plugin entries, confirming intended usage." - ], - "slug": "openai-codex-pr-15263", - "summary": "This PR changes plugin product restrictions from a single empty-list semantics to explicit tri-state behavior: omitted product constraints now mean unrestricted, while an explicit empty list means no products are allowed. That fixes ambiguous plugin marketplace eligibility behavior.", - "title": "Plugin product gating now distinguishes missing from explicitly empty product lists", - "watch_state": null, - "why_it_matters": "When product restrictions are serialized, missing and empty data previously collapsed in practice, creating unclear plugin eligibility rules. This change makes plugin access decisions explicit and safer by preventing accidental all-products access for entries that should be blocked, and by making restrictive intent expressible." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15264.analysis.json b/artifacts/github/analysis/openai-codex-pr-15264.analysis.json deleted file mode 100644 index 77c4fec..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15264.analysis.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "confidence": "confirmed", - "expected_effect": "When the app server starts with plugin sync configured, remote curated plugin state should be reconciled automatically on first startup instead of waiting for a later explicit sync path.", - "how_to_try": "Start an app-server session in a `codex_home` with plugins enabled and remote plugin sync configured, then open the plugin list on first startup.", - "impact": "medium", - "kind": "behavior_change", - "proof_points": [ - "The PR body says early users with enabled apps should have plugins enabled as part of initial setup.", - "The core plugin manager now routes startup work through `startup_sync.rs` and renames the old curated-repo sync path into broader startup plugin tasks.", - "The integration test `app_server_startup_remote_plugin_sync_runs_once` verifies that the remote plugin sync happens once at startup." - ], - "slug": "startup-remote-plugin-sync-runs-once", - "summary": "Merged PR #15264 adds a one-time startup remote plugin sync so app-server startup can reconcile curated plugin state automatically.", - "title": "App server now syncs remote plugins once at startup", - "why_it_matters": "Plugin-enabled setups become closer to `it just shows up`. Early users do not have to rely on a later manual sync path before the plugin list reflects remote curated state." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15275.analysis.json b/artifacts/github/analysis/openai-codex-pr-15275.analysis.json deleted file mode 100644 index 3b970cf..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15275.analysis.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "caveats": [ - "Exact runtime preference semantics come from the PR description and module-level evidence; full function bodies are not fully expanded in the bundle excerpts." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The primary PR is explicitly about this behavior: title `feat: prefer git for curated plugin sync` and body `start with git clone, fallback to http.`", - "`codex-rs/core/src/plugins/startup_sync.rs` is heavily expanded and includes the curated-sync pipeline, while `codex-rs/core/src/plugins/curated_repo.rs` is removed, indicating the sync mechanism was relocated and updated.", - "`codex-rs/core/src/plugins/mod.rs` now re-exports `curated_plugins_repo_path`, `read_curated_plugins_sha`, and `sync_openai_plugins_repo` from `startup_sync`, showing the canonical call path for curated sync now goes through that module.", - "Commit metadata includes `fallback in all errors`, matching the PR intent to use HTTP as a fallback path when sync encounters failures.", - "`codex-rs/core/src/plugins/manager.rs` adds a `remote_sync_lock: Mutex<()>`, suggesting startup sync is serialized to reduce concurrent sync races at runtime. Each change set is backed by moved and newly added tests in `startup_sync_tests.rs`, replacing the removed `curated_repo_tests.rs`." - ], - "slug": "prefer-git-for-curated-plugin-sync", - "summary": "PR #15275 moves curated plugin sync into startup synchronization and changes it so startup sync tries git-based curated-plugin fetching first, with a fallback path for failures.", - "title": "Curated plugin sync now prefers Git with HTTP fallback", - "watch_state": null, - "why_it_matters": "This improves startup resilience for plugin discovery by broadening transport options during curated plugin refresh, reducing the chance that a temporary Git-only failure blocks plugin sync behavior." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15279.analysis.json b/artifacts/github/analysis/openai-codex-pr-15279.analysis.json deleted file mode 100644 index 032dcda..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15279.analysis.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "caveats": [], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": "You should see a single plugin-labeled result instead of duplicate app/plugin entries, and the popup should show no more than eight filtered rows.", - "how_to_try": "On a build that includes PR #15279, trigger mention completion in the TUI or `tui_app_server` for a plugin-backed connector, especially one that would previously also appear as an app-style entry, and inspect the filtered picker list.", - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR body explicitly says duplicate app mentions are suppressed for plugin-backed entries with the same display name, remaining connector mentions are labeled `[Plugin]` instead of `[App]` when plugin metadata exists, and mention result lists are capped to 8 rows in both `tui` and `tui_app_server`.", - "`chat_composer.rs` in both `codex-rs/tui` and `codex-rs/tui_app_server` now builds a set of plugin display names and checks `connector.plugin_display_names`, which is the concrete path for deduping and relabeling plugin-backed mention items.", - "`file_search_popup.rs` and `skill_popup.rs` in both frontends now truncate filtered results to `MAX_POPUP_ROWS`, and the added tests assert that only the first page of matches is kept after filtering." - ], - "slug": "plugin-backed-mentions-are-labeled-and-deduped", - "summary": "Merged PR #15279 updates mention and picker UI in both `tui` and `tui_app_server` so plugin-backed connectors are labeled as `[Plugin]`, duplicate app-style entries are suppressed, and filtered popup lists stop at eight rows.", - "title": "Plugin-backed mentions now show as `[Plugin]` and hide duplicate app entries", - "watch_state": null, - "why_it_matters": "Plugin-heavy pickers should be less confusing to scan. Users get more accurate labels, fewer duplicate entries for the same underlying plugin-backed connector, and a predictable first page of results instead of long filtered lists." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15348.analysis.json b/artifacts/github/analysis/openai-codex-pr-15348.analysis.json deleted file mode 100644 index f89b6c4..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15348.analysis.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "caveats": [ - "The user-visible plugin list only changes if the backend actually serves different featured-plugin results for each platform." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR itself is titled `Pass platform param to featured plugins`, which frames the change as product-aware featured-plugin fetching rather than a refactor.", - "`core/src/plugins/manager.rs` now passes `self.restriction_product` into `fetch_remote_featured_plugin_ids`, so the featured-plugin request is tied to the active product context.", - "`core/src/plugins/remote.rs` adds a `platform` query parameter to the featured-plugin request, defaulting to `codex`, and `protocol.rs` adds explicit mappings for `chat`, `codex`, and `atlas`.", - "Tests in `manager_tests.rs` and `app-server/tests/suite/v2/plugin_list.rs` now assert `platform=chat` and `platform=codex`, confirming the request shape changed in both manager and app-server flows." - ], - "slug": "featured-plugin-sync-now-requests-product-specific-results", - "summary": "Merged PR #15348 adds a `platform` query when Codex fetches featured plugins, so curated plugin lists can now vary by product instead of using one generic request path.", - "title": "Featured plugin sync now requests product-specific results", - "watch_state": null, - "why_it_matters": "This reduces the chance of showing a featured plugin set that was curated for a different product surface. If the backend returns different lists for Codex, ChatGPT, or Atlas, users should get recommendations that better match the product they are actually using." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15376.analysis.json b/artifacts/github/analysis/openai-codex-pr-15376.analysis.json deleted file mode 100644 index 15b254d..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15376.analysis.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "caveats": [ - "The bundle does not include timing data, so any startup-speed improvement is inferred from the reduced request count rather than directly measured.", - "The patch excerpt for `on_mcp_startup_complete` is truncated, so the exact sequencing of the new loading path is only partially visible in the bundle." - ], - "confidence": "likely", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR title is `[apps] Improve app tools loading for TUI`, and the PR body explicitly says the TUI app-tools copy was removed so `tools/list` calls drop from 4 to 1.", - "The main code change is concentrated in `codex-rs/tui/src/chatwidget.rs`, where the bundle shows new MCP-loading state (`pending_mcp_output_requests`) and removal of the earlier eager connector prefetch path, which fits a consolidated startup flow.", - "`codex-rs/tui/src/chatwidget/tests.rs` is updated alongside the runtime state change, which supports this as an intentional behavior adjustment in TUI loading rather than a docs-only cleanup." - ], - "slug": "tui-app-tool-loading-cuts-mcp-tools-list-calls", - "summary": "Merged PR #15376 refactors TUI app-tool loading to reuse the core MCP tool set instead of maintaining a separate TUI-side copy, cutting the startup `tools/list` fan-out from four requests to one.", - "title": "TUI app-tool startup now avoids repeated MCP `tools/list` calls", - "watch_state": "Whether users with slower MCP backends report a noticeable improvement in TUI startup or tool availability, since the bundle proves the request reduction but not the end-to-end latency gain.", - "why_it_matters": "This should reduce duplicate MCP startup work in app-enabled TUI sessions, which can make tool discovery less noisy and lower avoidable load on slower or rate-limited tool servers." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15390.analysis.json b/artifacts/github/analysis/openai-codex-pr-15390.analysis.json deleted file mode 100644 index 701ab46..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15390.analysis.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "caveats": [ - "The PR body scopes this to very old rollouts, so direct user-visible impact for current Codex users should be limited.", - "The lower-level app-server and app-server-client legacy plumbing is intentionally left in place for follow-up work, so this PR removes only the TUI-facing compatibility layer." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR body explicitly says the removed handling was temporary, only needed for very old rollouts, and not supported by the IDE extension or app clients either.", - "`codex-rs/tui_app_server/src/app/app_server_adapter.rs` removes the legacy notification translation layer, including `LegacyThreadNotification` and the old `JSONRPCNotification`-based handling path.", - "`codex-rs/tui_app_server/src/app.rs` deletes `LegacyWarning`, `LegacyRollback`, the pending rollback queue, and related buffering/session-refresh logic, showing the TUI app-server no longer preserves or replays those events.", - "`codex-rs/tui_app_server/src/chatwidget.rs` removes the warning-message hook used by this path, completing the TUI-side cleanup." - ], - "slug": null, - "summary": "Merged PR #15390 removes the TUI app-server's compatibility handling for older `codex/event/*` notifications, deleting the warning and rollback buffering paths that were kept only for legacy rollouts.", - "title": "TUI app-server drops legacy warning and rollback notification support", - "watch_state": "Watch the follow-up work referenced in the PR body for whether the remaining lower-level legacy event plumbing is removed too.", - "why_it_matters": "This is mostly a low-impact cleanup rather than a new feature. For current users, the main value is that the app-server TUI now stays closer to the newer client model with less obsolete compatibility code in the way; anyone still depending on very old rollout-era warning or rollback notifications should treat that path as removed." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15409.analysis.json b/artifacts/github/analysis/openai-codex-pr-15409.analysis.json deleted file mode 100644 index 78884f1..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15409.analysis.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "caveats": [ - "This is specific to hardened macOS release binaries; it does not imply a broader cross-platform behavior change." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR body says hardened macOS release binaries failed to allocate executable memory for JIT-compiled JS without this entitlement, and reports that the same exec path succeeds once the entitlement is added.", - "The signing action in `.github/actions/macos-code-sign/action.yml` now passes `--entitlements` to `codesign` for the `codex` and `codex-responses-api-proxy` binaries.", - "The new `.github/actions/macos-code-sign/codex.entitlements.plist` enables Apple's `com.apple.security.cs.allow-jit` entitlement, which is the concrete behavior change introduced by the PR." - ], - "slug": null, - "summary": "PR #15409 updates the macOS signing flow so hardened release binaries can allocate executable memory for JIT-compiled JavaScript instead of crashing during `codex exec` runs.", - "title": "Signed macOS Codex binaries now include the JIT entitlement", - "watch_state": null, - "why_it_matters": "This fixes a platform-specific failure in shipped macOS builds. If you use a signed or notarized Codex binary on macOS, exec flows that rely on JIT-compiled JS should work where they previously died with a low-level CodeRange memory error." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15414.analysis.json b/artifacts/github/analysis/openai-codex-pr-15414.analysis.json deleted file mode 100644 index 3d711e3..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15414.analysis.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "caveats": [ - "This is scoped to the legacy `tui_app_server` compatibility path, not a broad auth change for standard Codex CLI or TUI usage.", - "The bundle excerpt clearly shows the legacy handler removal, but the exact runtime failure mode for stale clients is inferred because the deleted request branch is truncated in the patch excerpt." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR title and body explicitly say `tui_app_server` is removing legacy app-server notifications and dropping the local ChatGPT auth refresh request path, and frame it as cleanup split out from the larger app-server migration.", - "`codex-rs/tui_app_server/src/app/app_server_adapter.rs` removes imports for `load_local_chatgpt_auth`, `ChatgptAuthTokensRefreshParams`, and `RequestId`, and the patch excerpt shows the old `AppServerEvent::ServerRequest` handling branch being deleted.", - "`codex-rs/tui_app_server/src/local_chatgpt_auth.rs` removes `ChatgptAuthTokensRefreshResponse` plus the `to_refresh_response` helper, which is concrete evidence that this adapter no longer builds the legacy refresh response payload." - ], - "slug": "tui-app-server-drops-legacy-chatgpt-auth-refresh", - "summary": "Merged PR #15414 removes legacy app-server notification handling and the old local ChatGPT auth-refresh response path from `tui_app_server`, narrowing that compatibility layer as Codex moves toward the newer app-server stack.", - "title": "`tui_app_server` drops legacy ChatGPT auth-refresh handling", - "watch_state": null, - "why_it_matters": "This is a low-impact contract change for anyone still depending on the older `tui_app_server` path. Legacy clients should no longer expect this TUI-side adapter to service ChatGPT token-refresh requests, while current Codex development sheds outdated auth plumbing ahead of the broader migration." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15443.analysis.json b/artifacts/github/analysis/openai-codex-pr-15443.analysis.json deleted file mode 100644 index ba1f672..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15443.analysis.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "caveats": [ - "The bundle shows test and snapshot additions only, and the PR body explicitly says runtime logic is unchanged.", - "The main `codex_tests.rs` patch excerpt is truncated in the bundle, so the helper implementation details are only partially visible." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR body explicitly says it adds a snapshot-style core test for fork startup context injection followed by first-turn diff injection, and that it captures current behavior without changing runtime logic.", - "The bundle changes only `codex-rs/core/src/codex_tests.rs` plus a new snapshot file, and the main test file's patch excerpt adds test-only imports such as `context_snapshot`, `CollaborationMode`, `ModeKind`, `Settings`, and `AskForApproval`.", - "The new snapshot records the first forked request sequence with duplicated `PERMISSIONS_INSTRUCTIONS` and `ENVIRONMENT_CONTEXT` entries before a fork-turn `collaboration_mode` instruction, confirming the exact behavior this PR locks down.", - "The commit series stays focused on narrowing, stabilizing, and snippetizing the fork snapshot fixture, which reinforces that this PR is about test coverage and behavior capture rather than a runtime feature rollout." - ], - "slug": null, - "summary": "Merged PR #15443 adds a core snapshot test that captures the current first request after a fork, including startup-context replay and first-turn collaboration-mode injection, without changing runtime logic.", - "title": "Forked-session startup context ordering is now pinned by a snapshot test", - "watch_state": "Watch for follow-up fork-runtime PRs that intentionally deduplicate or reorder startup context injection, since this change mainly freezes today's behavior for regression detection.", - "why_it_matters": "This is mainly a reliability signal rather than a new feature. Forked-session prompt assembly affects approval and planning flows, and the new snapshot makes future regressions in that ordering easier to catch before they leak into user-visible fork behavior." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15659.analysis.json b/artifacts/github/analysis/openai-codex-pr-15659.analysis.json deleted file mode 100644 index 4dd86f6..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15659.analysis.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "caveats": [ - "This is primarily an observability change for tracing consumers; the PR does not describe a new end-user MCP workflow or a concrete try-now path." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR body explicitly says the change adds an `mcp.tools.call` span around MCP tool execution and inlines `conversation.id`, `session.id`, and `turn.id` in the span initializer.", - "`codex-rs/core/src/mcp_tool_call.rs` is the main implementation file touched, and its diff adds tracing imports plus new connector/server metadata lookup before the MCP call, matching the PR's span-focused scope.", - "`codex-rs/core/src/mcp_tool_call_tests.rs` adds a new `mcp_tool_call_span_records_expected_fields` test and configures a tracing subscriber with `FmtSpan::FULL`, which directly supports the claim that the new span records the expected correlation and server fields.", - "The commit stack narrows the final scope to MCP spans and moves validation into a local unit test, reinforcing that this landed as a bounded observability behavior change rather than a broader MCP feature rollout." - ], - "slug": null, - "summary": "Merged PR #15659 adds an explicit tracing span around MCP tool execution in Codex core and records turn/session correlation plus server metadata on that span.", - "title": "MCP tool calls now emit a dedicated `mcp.tools.call` tracing span", - "watch_state": null, - "why_it_matters": "This makes MCP traces easier to inspect when a tool call is slow, fails, or needs auditability. Operators and contributors can correlate an individual MCP call with the owning conversation, session, turn, and server without inferring it from broader trace context." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15759.analysis.json b/artifacts/github/analysis/openai-codex-pr-15759.analysis.json deleted file mode 100644 index a35f612..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15759.analysis.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "caveats": [ - "This is scoped to the app-server TUI path rather than the default legacy TUI.", - "The change preserves transcript integrity under backpressure, but it does not remove backpressure or address the root cause of a slow consumer." - ], - "confidence": "confirmed", - "config_flags": [ - "features.tui_app_server = true" - ], - "expected_effect": "The streamed answer should stay readable and complete instead of ending with mangled or missing paragraphs even if the app-server TUI briefly falls behind.", - "how_to_try": "Run a Codex build that includes PR #15759 with `features.tui_app_server = true`, trigger a response that streams a large amount of markdown or text, and watch the output while the UI is under load.", - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR description explicitly says `-c features.tui_app_server=true` could corrupt large streamed output because `AgentMessageDelta` and `ItemCompleted` were previously droppable under backpressure, leaving incomplete markdown on screen.", - "The main file change in `codex-rs/app-server-client/src/lib.rs` expands must-deliver handling from terminal-only events to shared transcript-and-terminal notification classification, with comments calling out corrupted markdown and incomplete paragraphs as the failure mode.", - "`codex-rs/app-server-client/src/remote.rs` now delegates to the same shared `server_notification_requires_delivery` logic, which is evidence that both in-process and remote app-server transports were aligned around the new lossless transcript policy.", - "The commit set includes dedicated regression coverage for backpressure preservation plus follow-up docs and test adjustments, matching the PR's claim that transcript notifications now survive saturated queues." - ], - "slug": "app-server-tui-preserves-transcript-output-under-backpressure", - "summary": "PR #15759 changes the app-server TUI transport so transcript-bearing events are treated as must-deliver under backpressure, preventing large streamed responses from degrading into corrupted or incomplete paragraphs.", - "title": "App-server TUI now keeps streamed transcript output intact under backpressure", - "watch_state": null, - "why_it_matters": "If you use Codex with the app-server TUI, long streamed answers are less likely to end up visibly broken when the UI falls behind. The fix prioritizes preserving the assistant\u2019s actual transcript over dropping text fragments just to keep the queue moving." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15785.analysis.json b/artifacts/github/analysis/openai-codex-pr-15785.analysis.json deleted file mode 100644 index 06eb3a0..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15785.analysis.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "caveats": [ - "This is mainly relevant for remote or standalone exec-server setups; users on the default local execution path may not notice a visible difference.", - "Any workflow still relying on `experimental_exec_server_url` in config will need to migrate to the `CODEX_EXEC_SERVER_URL` environment variable." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": "Codex should connect through the already running exec server without relying on the removed `experimental_exec_server_url` config key, and spawned or delegated work should stay pointed at that same remote execution endpoint.", - "how_to_try": "Run a Codex build that includes PR #15785 by starting `codex-exec-server` manually and launching the TUI or app-server path with `CODEX_EXEC_SERVER_URL=ws://...`, or use the new `scripts/run_tui_with_exec_server.sh` helper to do the handoff automatically.", - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR body says the change adds a singleton environment manager created early in app-server startup and uses an environment variable to point Codex at a running exec server.", - "`codex-rs/exec-server/src/environment.rs` adds `CODEX_EXEC_SERVER_URL_ENV_VAR = \"CODEX_EXEC_SERVER_URL\"` plus an `EnvironmentManager` backed by `OnceCell`, which is direct evidence that exec-server selection is now cached and env-driven.", - "`codex-rs/core/src/config/mod.rs` and `codex-rs/core/config.schema.json` remove `experimental_exec_server_url`, showing the old config-surface override is no longer the supported path.", - "`codex-rs/app-server/src/lib.rs`, `codex-rs/app-server/src/in_process.rs`, and `codex-rs/tui/src/app.rs` now construct `EnvironmentManager::from_env()` early, while `codex-rs/core/src/codex_delegate.rs` passes the parent exec-server URL into delegated threads.", - "The new `scripts/run_tui_with_exec_server.sh` helper starts `codex-exec-server`, and the bundle's extracted flags include `CODEX_EXEC_SERVER_URL=\"$exec_server_url\"`, which supports the new env-based TUI handoff workflow." - ], - "slug": "remote-exec-server-routing-now-uses-codex-exec-server-url", - "summary": "Merged PR #15785 replaces the old experimental config-based exec-server override with a cached `EnvironmentManager` and the `CODEX_EXEC_SERVER_URL` environment variable, so app-server and TUI flows can reuse the same running exec server more consistently.", - "title": "Remote exec-server routing now uses `CODEX_EXEC_SERVER_URL`", - "watch_state": "Whether operator docs and surrounding tooling fully migrate from the removed `experimental_exec_server_url` knob to `CODEX_EXEC_SERVER_URL`, especially outside the new helper script.", - "why_it_matters": "If you run Codex against a separate exec server, the setup path is simpler and more reliable: the override moves out of `config.toml`, the app-server creates the environment manager early, and delegated work inherits the same exec-server URL instead of rebuilding that routing piecemeal." -} diff --git a/artifacts/github/analysis/openai-codex-pr-15802.analysis.json b/artifacts/github/analysis/openai-codex-pr-15802.analysis.json deleted file mode 100644 index 95f750f..0000000 --- a/artifacts/github/analysis/openai-codex-pr-15802.analysis.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "caveats": [ - "This is limited to `tui` and `tui_app_server` polish; it does not add new plugin capabilities or change backend install mechanics." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": "The UI should show `OpenAI Curated` instead of the raw `openai-curated` name, the slash-command menu should no longer surface `/apps`, and uninstalled plugin states should tell you to install the required Apps in ChatGPT.", - "how_to_try": "Run a build that includes PR #15802 with plugins enabled, open the TUI or `tui_app_server` plugin flow, inspect the curated marketplace label and an uninstalled plugin state, and check the slash-command picker for `/apps`.", - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR body frames the change as three user-facing polish items: rename the `openai-curated` marketplace to `OpenAI Curated`, hide the `/apps` menu entry, and change the app-install-phase display text.", - "Both `codex-rs/tui` and `codex-rs/tui_app_server` update `bottom_pane/command_popup.rs` to filter out `SlashCommand::Apps`, and both `chatwidget/plugins.rs` files replace `Not installed yet.` with `Install the required Apps in ChatGPT to continue:`.", - "`codex-rs/core/src/plugins/manager.rs` adds `OPENAI_CURATED_MARKETPLACE_DISPLAY_NAME = \"OpenAI Curated\"` and uses it when the marketplace name is `openai-curated`, while `manager_tests.rs` is updated to expect that display name." - ], - "slug": "tui-plugin-ui-drops-apps-command-and-renames-curated-marketplace", - "summary": "Merged PR #15802 makes a small but visible plugin-UI cleanup in `tui` and `tui_app_server`: the curated marketplace gets a clearer name, `/apps` is hidden from the command menu, and uninstalled plugin states now show a more actionable ChatGPT install prompt.", - "title": "TUI plugin flows now use `OpenAI Curated` and drop the `/apps` command", - "watch_state": "Whether any remaining plugin surfaces or docs still reference the older `/apps` command or the raw `openai-curated` marketplace name.", - "why_it_matters": "This reduces terminology friction in the plugin experience. Users see a clearer curated marketplace label, less mixed app-versus-plugin wording in the TUI, and a better hint about what to do when a plugin depends on ChatGPT-side app installs." -} diff --git a/artifacts/github/analysis/openai-codex-pr-16010.analysis.json b/artifacts/github/analysis/openai-codex-pr-16010.analysis.json deleted file mode 100644 index 953aef3..0000000 --- a/artifacts/github/analysis/openai-codex-pr-16010.analysis.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "caveats": [ - "This is mainly relevant to advanced multi-agent orchestration flows, not ordinary single-agent Codex usage.", - "The wait result is still only a completion-or-timeout summary; it does not surface the child message body itself." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": "`wait_agent` should return a simple completion result once fresh mailbox traffic arrives after the wait begins, even though no target list was provided.", - "how_to_try": "In a Codex build that includes PR #16010 with MultiAgentV2 enabled, spawn a child agent, call `wait_agent({\"timeout_ms\":1000})` from the root, then have any child send a message back without passing `targets`.", - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR narrative says a mailbox now backs inter-agent communication and that `wait` no longer takes a target argument.", - "The `wait.rs` handler removes `targets` from `WaitArgs` and replaces per-agent status waiting with a mailbox-sequence watch, so fresh mailbox traffic becomes the completion signal.", - "Session/task wiring was updated to treat mailbox items as pending input, which is the runtime path that makes the new wait behavior observable.", - "Tests were rewritten to accept timeout-only `wait_agent` calls and to confirm that new mailbox activity after the wait starts completes the call, while already-queued mail does not." - ], - "slug": "wait-agent-no-longer-needs-targets", - "summary": "Merged PR #16010 changes multi-agent `wait_agent` so it wakes on new mailbox activity instead of requiring callers to name specific child targets.", - "title": "`wait_agent` no longer needs target lists in MultiAgentV2", - "watch_state": null, - "why_it_matters": "This trims orchestration bookkeeping for advanced Codex users. If workers are spawned dynamically, the caller can now wait for the next inter-agent reply without first resolving thread IDs or task names." -} diff --git a/artifacts/github/analysis/openai-codex-pr-16082.analysis.json b/artifacts/github/analysis/openai-codex-pr-16082.analysis.json deleted file mode 100644 index 5ad2107..0000000 --- a/artifacts/github/analysis/openai-codex-pr-16082.analysis.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "kind": "capability", - "title": "App-server v2 can now read MCP resources directly", - "summary": "Merged PR #16082 adds the `mcpServer/resource/read` request to the app-server protocol, so clients can fetch MCP resource contents by `threadId`, server name, and URI instead of only listing servers, tools, and templates.", - "why_it_matters": "This fills a real MCP Apps integration gap. Client builders can now move from discovering MCP resources to actually reading them through the same app-server surface, which makes richer MCP-backed UIs and workflows easier to build.", - "confidence": "confirmed", - "impact": "medium", - "proof_points": [ - "The PR body states the new goal directly: add an `mcpResource/read` method to read an MCP resource.", - "The protocol bundle adds `McpResourceReadParams`, `McpResourceReadResponse`, and `ResourceContent` types plus a new `mcpServer/resource/read` request shape in both JSON schema and generated TypeScript outputs.", - "The app-server implementation gains request handling in `codex_message_processor.rs`, and the shared test harness adds a dedicated `send_mcp_resource_read_request` helper for the new method.", - "A new integration suite file, `codex-rs/app-server/tests/suite/v2/mcp_resource.rs`, provides focused end-to-end coverage for reading MCP resources through the v2 app-server path." - ], - "slug": "app-server-v2-can-read-mcp-resources-directly", - "config_flags": [], - "how_to_try": "Start an app-server-backed Codex session with an MCP server that exposes resources, then issue `mcpServer/resource/read` with a live `threadId`, the MCP server name, and a known resource URI.", - "expected_effect": "The response should include a `contents` array with text or blob resource payloads for the requested MCP resource instead of only metadata about the server.", - "caveats": [ - "This is app-server protocol work, so the immediate payoff is strongest for client and integration developers rather than ordinary chat-only usage.", - "The PR is labeled part 1, which suggests more MCP Apps work is expected on top of this read path." - ], - "watch_state": "Watch for follow-up MCP Apps work that builds on resource reads with richer UI wiring, resource-template flows, or higher-level client helpers." -} diff --git a/artifacts/github/analysis/openai-codex-pr-16153.analysis.json b/artifacts/github/analysis/openai-codex-pr-16153.analysis.json deleted file mode 100644 index 524aaab..0000000 --- a/artifacts/github/analysis/openai-codex-pr-16153.analysis.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "kind": "behavior_change", - "title": "Code mode now supports `setTimeout` and `clearTimeout`", - "summary": "Merged PR #16153 adds timer support to code mode by exposing `setTimeout` and `clearTimeout`, so scripts can schedule delayed callbacks inside an `exec` run.", - "why_it_matters": "This makes code mode feel more like a real scripting runtime for lightweight orchestration. Delayed retries, timeout-backed promises, and simple event-loop style flows no longer need awkward manual polling just to wait a few milliseconds.", - "confidence": "confirmed", - "impact": "medium", - "proof_points": [ - "The code-mode description string now documents `setTimeout(callback, delayMs?)` and `clearTimeout(timeoutId?)` as built-in helpers available to runtime scripts.", - "The runtime adds dedicated timeout callbacks, installs both globals, and introduces a new `timers.rs` module that schedules callbacks and tracks timeout IDs.", - "Core integration coverage adds `code_mode_can_resume_after_set_timeout`, which awaits a promise built on `setTimeout` and verifies the `exec` call returns the expected output.", - "The PR body notes the current implementation uses one thread per timer, which supports reading this as a real new capability with an acknowledged implementation tradeoff rather than a docs-only change." - ], - "slug": "code-mode-now-supports-settimeout-and-cleartimeout", - "config_flags": [], - "how_to_try": "In code mode, run something like `await new Promise((resolve) => setTimeout(resolve, 10)); text(\"timer done\");` inside `exec` and confirm the delayed callback resumes the script.", - "expected_effect": "The script should pause briefly, then continue and emit the final output after the timeout callback fires.", - "caveats": [ - "The PR body explicitly says the current implementation starts a thread per timer, so this should be treated as functional support rather than the final scalability story.", - "Pending timeouts do not keep `exec` alive by themselves; the script still needs an explicit promise or other wait path if it wants to pause for the timer." - ], - "watch_state": "Watch for a follow-up runtime implementation that moves timers onto a shared async scheduler instead of per-timeout threads." -} diff --git a/artifacts/github/analysis/openai-codex-pr-16204.analysis.json b/artifacts/github/analysis/openai-codex-pr-16204.analysis.json deleted file mode 100644 index a96f073..0000000 --- a/artifacts/github/analysis/openai-codex-pr-16204.analysis.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "caveats": [ - "The bundle shows only a test assertion change; no MCP startup runtime logic or warning text changed.", - "The value here is mainly contributor and CI reliability, not a directly noticeable end-user workflow change." - ], - "confidence": "confirmed", - "config_flags": [], - "expected_effect": null, - "how_to_try": null, - "impact": "low", - "kind": "behavior_change", - "proof_points": [ - "The PR body explicitly says a Windows-only snapshot assertion was comparing the raw rendered path, causing CI to see `C:\\tmp\\project` instead of the normalized `/tmp/project` fixture.", - "The only code change is in `codex-rs/tui/src/chatwidget/tests.rs`, where the snapshot assertion now routes `term.backend().vt100().screen().contents()` through the existing `normalize_snapshot_paths(...)` helper.", - "The bundle contains a single merged commit and a one-line test diff with no runtime-file changes, which supports reading this as a scoped cross-platform test-stability fix rather than a new MCP feature." - ], - "slug": null, - "summary": "Merged PR #16204 fixes a Windows-only MCP startup-warning snapshot assertion so Codex compares normalized paths instead of raw `C:\\tmp\\project` output in the TUI test harness.", - "title": "Windows MCP startup-warning snapshots now normalize rendered paths", - "watch_state": "Watch for future MCP startup-warning changes that alter rendered path text on Windows, since this PR only hardens the snapshot comparison rather than changing the warning output itself.", - "why_it_matters": "This does not change MCP runtime behavior, but it makes the MCP startup-warning test stable across platforms. That reduces Windows-only CI noise and makes real regressions in warning rendering easier to catch." -} diff --git a/artifacts/github/analysis/openai-codex-pr-16508.analysis.json b/artifacts/github/analysis/openai-codex-pr-16508.analysis.json deleted file mode 100644 index 76eaa77..0000000 --- a/artifacts/github/analysis/openai-codex-pr-16508.analysis.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "kind": "behavior_change", - "title": "Model and provider ownership moves out of codex-core", - "summary": "Merged PR #16508 splits `models-manager` out of `core`, moves several auth/provider/protocol helpers to owner crates, and keeps temporary re-exports so the workspace can adopt the new boundaries without one giant import rewrite.", - "why_it_matters": "This is primarily a contributor-facing architecture shift. Codex model metadata, provider config, auth glue, and response-debug helpers now have clearer crate ownership, which should make follow-on work in those areas less entangled with `codex-core`.", - "confidence": "confirmed", - "impact": "medium", - "proof_points": [ - "The PR summary explicitly says model metadata paths stop depending on `core::Config` and lists new owners such as `codex-models-manager`, `codex-model-provider-info`, `codex-api`, `codex-protocol`, and `codex-response-debug-context`.", - "The patch adds new workspace members and rewires Cargo manifests plus moved source files, which matches the claim that crate ownership is being redistributed rather than just lightly renamed.", - "Follow-up commits in the same PR restore bundled-model fallback behavior and avoid a giant import-only rewrite, which supports reading the change as a staged ownership extraction instead of a breaking immediate surface reset.", - "The touched files span core, api, app-server, protocol-adjacent code, and multiple new crates, so the main value is structural decoupling across the Codex Rust workspace." - ], - "slug": "model-and-provider-ownership-moves-out-of-codex-core", - "config_flags": [], - "how_to_try": null, - "expected_effect": null, - "caveats": [ - "Most readers will not see an immediate CLI behavior change from this PR alone; the payoff is cleaner crate boundaries for later work.", - "The PR intentionally keeps temporary re-exports and defers some mechanical import cleanup to follow-up changes." - ], - "watch_state": "Watch for follow-up PRs that remove the temporary re-exports and finish migrating imports to the new crate owners, since this change is deliberately staged." -} diff --git a/artifacts/github/analysis/openai-codex-pr-16631.analysis.json b/artifacts/github/analysis/openai-codex-pr-16631.analysis.json deleted file mode 100644 index af72f45..0000000 --- a/artifacts/github/analysis/openai-codex-pr-16631.analysis.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "kind": "behavior_change", - "title": "codex-core SessionTask builds get materially lighter", - "summary": "Merged PR #16631 replaces `async_trait` on `SessionTask` with native async futures plus a boxed adapter at the storage boundary, and the PR reports about a 48% package-clean `codex-core` rebuild reduction.", - "why_it_matters": "This is mainly a contributor-facing change, not a new CLI feature. People working in Codex core task execution should see faster rebuilds and less async-trait-generated glue when touching the session task path.", - "confidence": "confirmed", - "impact": "medium", - "proof_points": [ - "The PR body says this change continues the compile-time cleanup from #16630 and cites package-clean `codex-core` rebuilds as the benchmark that showed the real gain.", - "The patch removes `#[async_trait]` from `SessionTask` and concrete task implementations, switches the trait to native RPITIT futures, and adds an `AnySessionTask` adapter to keep boxing only at the heterogeneous storage boundary.", - "The state layer changes `RunningTask` from `Arc` to `Arc`, which matches the PR narrative that task lifecycle semantics stay the same while the abstraction boundary moves.", - "The touched files are all in the core task/state/test surface, which supports reading this as a scoped compile-time and maintainability change rather than a new end-user workflow." - ], - "slug": "codex-core-sessiontask-builds-get-materially-lighter", - "config_flags": [], - "how_to_try": null, - "expected_effect": null, - "caveats": [ - "The PR body notes the win showed up on package-clean `codex-core` rebuilds, not on a warm incremental benchmark based on touching one task module.", - "This does not claim a runtime speedup for normal Codex usage; the main payoff is contributor build latency and trait-bound simplification." - ], - "watch_state": "Watch for follow-up core task-trait cleanups around the remaining boxed adapter boundary, since this PR deliberately keeps the heterogeneous `dyn` storage model." -} diff --git a/artifacts/github/analysis/openai-codex-pr-16633.analysis.json b/artifacts/github/analysis/openai-codex-pr-16633.analysis.json deleted file mode 100644 index 0e6acac..0000000 --- a/artifacts/github/analysis/openai-codex-pr-16633.analysis.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "kind": "behavior_change", - "title": "Windows TUI builds stop tripping a cfg-mismatched local binding", - "summary": "Merged PR #16633 moves a `terminal.viewport_area` binding inside the Unix-only branch in `tui.rs`, so Windows builds no longer see an unused local introduced by the earlier zellij redraw work.", - "why_it_matters": "This is a small Windows-only build hygiene fix, but it removes avoidable compiler noise from the TUI path while broader Bazel-on-Windows work is still being stabilized.", - "confidence": "confirmed", - "impact": "low", - "proof_points": [ - "The PR body says the unused-variable issue slipped in during PR #16578 and explicitly frames this as part of ongoing Windows bring-up work.", - "The bundle contains a single-file diff in `codex-rs/tui/src/tui.rs` where `let area = terminal.viewport_area;` is moved under `#[cfg(unix)]`, which directly matches the stated fix.", - "No runtime logic, command paths, or user-visible rendering branches changed outside that binding placement, so the evidence supports calling this a narrow compile-path correction rather than a new TUI feature." - ], - "slug": "windows-tui-builds-stop-tripping-cfg-mismatched-local-binding", - "config_flags": [], - "how_to_try": null, - "expected_effect": null, - "caveats": [ - "This does not change TUI behavior at runtime for ordinary users; it only prevents a Windows-side compile warning/error path.", - "The broader Windows and Bazel compatibility work remains in flight, so this should be read as one cleanup step, not the whole story." - ], - "watch_state": "Watch for more `cfg(unix)` and Windows-specific TUI follow-ups near the redraw and viewport code paths as the cross-platform polish continues." -} diff --git a/artifacts/github/analysis/openai-codex-pr-16946.analysis.json b/artifacts/github/analysis/openai-codex-pr-16946.analysis.json deleted file mode 100644 index b3b6c25..0000000 --- a/artifacts/github/analysis/openai-codex-pr-16946.analysis.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "kind": "behavior_change", - "title": "Danger-full-access can now run under a managed denylist-only network policy", - "summary": "Merged PR #16946 adds `experimental_network.danger_full_access_denylist_only`, letting danger-full-access sessions keep broad network access while still enforcing centrally managed deny entries.", - "why_it_matters": "This is aimed at org-managed Codex deployments that want a less restrictive yolo mode without dropping network policy entirely. It gives administrators a middle ground between full allowlist enforcement and completely unmanaged network access.", - "confidence": "confirmed", - "impact": "medium", - "proof_points": [ - "The PR summary explicitly introduces `experimental_network.danger_full_access_denylist_only` and says it applies only to `SandboxPolicy::DangerFullAccess` sessions.", - "The bundle shows the new field wired through config requirements parsing, app-server protocol/schema output, config API mapping, and the TUI debug config surface, which confirms this is a real operator-facing config addition rather than an internal constant.", - "The core network proxy spec changes add a wildcard allowlist path plus dedicated tests for enforcing managed denied domains in danger-full-access mode.", - "The PR body includes a concrete TOML example under `[experimental_network]` and repeatedly warns that the denylist remains best effort only, which is the key behavioral caveat for anyone enabling it." - ], - "slug": "danger-full-access-can-run-under-managed-denylist-only-network-policy", - "config_flags": [ - "experimental_network.danger_full_access_denylist_only = true" - ], - "how_to_try": "In a Codex build that includes PR #16946, add `danger_full_access_denylist_only = true` under `[experimental_network]` together with one or more denied domains, start a danger-full-access session, and verify allowed outbound access still works while the denied domains are blocked by policy.", - "expected_effect": "Danger-full-access sessions should retain broad network reach, but requests to managed denied domains should still be refused by the proxy policy surface.", - "caveats": [ - "The PR is explicit that this is not a hard security boundary: a danger-full-access session may still bypass the proxy denylist through allowed sockets or other local/private paths.", - "Read-only and workspace-write sandbox modes keep their existing network behavior; this flag is intentionally scoped to danger-full-access only." - ], - "watch_state": "Watch for follow-up hardening around bypass paths and non-loopback proxy listener behavior, since this mode deliberately trades strict isolation for broader connectivity." -} diff --git a/artifacts/github/bundles/openai-codex-pr-12334.json b/artifacts/github/bundles/openai-codex-pr-12334.json deleted file mode 100644 index 4231bda..0000000 --- a/artifacts/github/bundles/openai-codex-pr-12334.json +++ /dev/null @@ -1,194 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "yvolovich-cyber", - "committed_at": "2026-03-18T23:46:23Z", - "message": "feat(tui): add /title terminal title configuration", - "sha": "db4b7fe0be3d777a14f75f5db585aa52c7bfd3a2", - "url": "https://github.com/openai/codex/commit/db4b7fe0be3d777a14f75f5db585aa52c7bfd3a2" - }, - { - "author": "yvolovich-cyber", - "committed_at": "2026-03-19T00:19:20Z", - "message": "codex: fix CI failure on PR #12334", - "sha": "5d242724a75a97c1fc5ab0770ca880cda93c4432", - "url": "https://github.com/openai/codex/commit/5d242724a75a97c1fc5ab0770ca880cda93c4432" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "TUI", - "OSC", - "CWE", - "CERT", - "UAX", - "UTR", - "CODEX_HOME", - "CODEX_CLI_VERSION", - "DEFAULT_MODEL_DISPLAY_NAME", - "PLAN_IMPLEMENTATION_TITLE", - "TERMINAL_TITLE_SPINNER_INTERVAL", - "CWD", - "DEFAULT_STATUS_LINE_ITEMS", - "DEFAULT_TERMINAL_TITLE_ITEMS", - "TERMINAL_TITLE_SPINNER_FRAMES", - "MAX_TERMINAL_TITLE_CHARS", - "ESC", - "BEL", - "ANSI", - "ASCII", - "FE00", - "FE0F", - "FEFF", - "FFF9", - "FFFB", - "E0100", - "E01EF" - ], - "files": [ - { - "additions": 8, - "deletions": 0, - "patch_excerpt": "@@ -1670,6 +1670,14 @@\n },\n \"type\": \"array\"\n },\n+ \"terminal_title\": {\n+ \"default\": null,\n+ \"description\": \"Ordered list of terminal title item identifiers.\\n\\nWhen set, the TUI renders the selected items into the terminal window/tab title. When unset, the TUI defaults to: `spinner` and `project`.\",\n+ \"items\": {\n+ \"type\": \"string\"\n+ },\n+ \"type\": \"array\"\n+ },\n \"theme\": {\n \"default\": null,\n \"description\": \"Syntax highlighting theme name (kebab-case).\\n\\nWhen set, overrides automatic light/dark theme detection. Use `/theme` in the TUI or see `$CODEX_HOME/themes` for custom themes.\",", - "path": "codex-rs/core/config.schema.json", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -237,6 +237,7 @@ fn config_toml_deserializes_model_availability_nux() {\n show_tooltips: true,\n alternate_screen: AltScreenMode::default(),\n status_line: None,\n+ terminal_title: None,\n theme: None,\n model_availability_nux: ModelAvailabilityNuxConfig {\n shown_count: HashMap::from([\n@@ -921,6 +922,7 @@ fn tui_config_missing_notifications_field_defaults_to_enabled() {\n show_tooltips: true,\n alternate_screen: AltScreenMode::Auto,\n status_line: None,\n+ terminal_title: None,\n theme: None,\n model_availability_nux: ModelAvailabilityNuxConfig::default(),\n }\n@@ -4349,6 +4351,7 @@ fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {\n tool_suggest: ToolSuggestConfig::default(),\n tui_alternate_s...", - "path": "codex-rs/core/src/config/config_tests.rs", - "status": "modified" - }, - { - "additions": 19, - "deletions": 5, - "patch_excerpt": "@@ -60,26 +60,40 @@ pub enum ConfigEdit {\n ClearPath { segments: Vec },\n }\n \n-/// Produces a config edit that sets `[tui] theme = \"\"`.\n+/// Produces a config edit that sets `[tui].theme = \"\"`.\n pub fn syntax_theme_edit(name: &str) -> ConfigEdit {\n ConfigEdit::SetPath {\n segments: vec![\"tui\".to_string(), \"theme\".to_string()],\n value: value(name.to_string()),\n }\n }\n \n+/// Produces a config edit that sets `[tui].status_line` to an explicit ordered list.\n+///\n+/// The array is written even when it is empty so \"hide the status line\" stays\n+/// distinct from \"unset, so use defaults\".\n pub fn status_line_items_edit(items: &[String]) -> ConfigEdit {\n- let mut array = toml_edit::Array::new();\n- for item in items {\n- array.push(item.clone());\n- }\n+ let array = items.iter().cloned().collect::();\n \n ConfigEdit::...", - "path": "codex-rs/core/src/config/edit.rs", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -359,6 +359,11 @@ pub struct Config {\n /// `current-dir`.\n pub tui_status_line: Option>,\n \n+ /// Ordered list of terminal title item identifiers for the TUI.\n+ ///\n+ /// When unset, the TUI defaults to: `project` and `spinner`.\n+ pub tui_terminal_title: Option>,\n+\n /// Syntax highlighting theme override (kebab-case name).\n pub tui_theme: Option,\n \n@@ -2823,6 +2828,7 @@ impl Config {\n .map(|t| t.alternate_screen)\n .unwrap_or_default(),\n tui_status_line: cfg.tui.as_ref().and_then(|t| t.status_line.clone()),\n+ tui_terminal_title: cfg.tui.as_ref().and_then(|t| t.terminal_title.clone()),\n tui_theme: cfg.tui.as_ref().and_then(|t| t.theme.clone()),\n otel: {\n let t: OtelConfigToml = cfg.otel.unwrap_or_default();", - "path": "codex-rs/core/src/config/mod.rs", - "status": "modified" - }, - { - "additions": 7, - "deletions": 0, - "patch_excerpt": "@@ -752,6 +752,13 @@ pub struct Tui {\n #[serde(default)]\n pub status_line: Option>,\n \n+ /// Ordered list of terminal title item identifiers.\n+ ///\n+ /// When set, the TUI renders the selected items into the terminal window/tab title.\n+ /// When unset, the TUI defaults to: `spinner` and `project`.\n+ #[serde(default)]\n+ pub terminal_title: Option>,\n+\n /// Syntax highlighting theme name (kebab-case).\n ///\n /// When set, overrides automatic light/dark theme detection.", - "path": "codex-rs/core/src/config/types.rs", - "status": "modified" - }, - { - "additions": 109, - "deletions": 18, - "patch_excerpt": "@@ -724,6 +724,8 @@ pub(crate) struct App {\n pub(crate) commit_anim_running: Arc,\n // Shared across ChatWidget instances so invalid status-line config warnings only emit once.\n status_line_invalid_items_warned: Arc,\n+ // Shared across ChatWidget instances so invalid terminal-title config warnings only emit once.\n+ terminal_title_invalid_items_warned: Arc,\n \n // Esc-backtracking state grouped\n pub(crate) backtrack: crate::app_backtrack::BacktrackState,\n@@ -811,6 +813,7 @@ impl App {\n startup_tooltip_override: None,\n status_line_invalid_items_warned: self.status_line_invalid_items_warned.clone(),\n session_telemetry: self.session_telemetry.clone(),\n+ terminal_title_invalid_items_warned: self.terminal_title_invalid_items_warned.clone(),\n }\n }\n \n@@ -1783,8 +1786,7 @@ impl A...", - "path": "codex-rs/tui/src/app.rs", - "status": "modified" - }, - { - "additions": 11, - "deletions": 0, - "patch_excerpt": "@@ -20,6 +20,7 @@ use codex_utils_approval_presets::ApprovalPreset;\n \n use crate::bottom_pane::ApprovalRequest;\n use crate::bottom_pane::StatusLineItem;\n+use crate::bottom_pane::TerminalTitleItem;\n use crate::history_cell::HistoryCell;\n \n use codex_core::config::types::ApprovalsReviewer;\n@@ -451,6 +452,16 @@ pub(crate) enum AppEvent {\n },\n /// Dismiss the status-line setup UI without changing config.\n StatusLineSetupCancelled,\n+ /// Apply a user-confirmed terminal-title item ordering/selection.\n+ TerminalTitleSetup {\n+ items: Vec,\n+ },\n+ /// Apply a temporary terminal-title preview while the setup UI is open.\n+ TerminalTitleSetupPreview {\n+ items: Vec,\n+ },\n+ /// Dismiss the terminal-title setup UI without changing config.\n+ TerminalTitleSetupCancelled,\n \n /// Apply a user-confirmed syntax theme s...", - "path": "codex-rs/tui/src/app_event.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 0, - "patch_excerpt": "@@ -49,6 +49,7 @@ mod request_user_input;\n mod status_line_setup;\n pub(crate) use app_link_view::AppLinkElicitationTarget;\n pub(crate) use app_link_view::AppLinkSuggestionType;\n+mod title_setup;\n pub(crate) use app_link_view::AppLinkView;\n pub(crate) use app_link_view::AppLinkViewParams;\n pub(crate) use approval_overlay::ApprovalOverlay;\n@@ -100,6 +101,8 @@ pub(crate) use skills_toggle_view::SkillsToggleView;\n pub(crate) use status_line_setup::StatusLineItem;\n pub(crate) use status_line_setup::StatusLinePreviewData;\n pub(crate) use status_line_setup::StatusLineSetupView;\n+pub(crate) use title_setup::TerminalTitleItem;\n+pub(crate) use title_setup::TerminalTitleSetupView;\n mod paste_burst;\n mod pending_input_preview;\n mod pending_thread_approvals;", - "path": "codex-rs/tui/src/bottom_pane/mod.rs", - "status": "modified" - }, - { - "additions": 21, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,21 @@\n+---\n+source: tui/src/bottom_pane/title_setup.rs\n+expression: \"render_lines(&view, 84)\"\n+---\n+ \n+ Configure Terminal Title \n+ Select which items to display in the terminal title. \n+ \n+ Type to search \n+ > \n+\u203a [x] project Project name (falls back to current directory name) \n+ [x] spinner Animated task spinner (omitted while idle or when animations\u2026 \n+ [x] status Compact session status text (Ready, Working, Thinking) \n+ [x] thread ...", - "path": "codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__title_setup__tests__terminal_title_setup_basic.snap", - "status": "added" - }, - { - "additions": 298, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,298 @@\n+//! Terminal title configuration view for customizing the terminal window/tab title.\n+//!\n+//! This module provides an interactive picker for selecting which items appear\n+//! in the terminal title. Users can:\n+//!\n+//! - Select items\n+//! - Reorder items\n+//! - Preview the rendered title\n+\n+use itertools::Itertools;\n+use ratatui::buffer::Buffer;\n+use ratatui::layout::Rect;\n+use ratatui::text::Line;\n+use strum::IntoEnumIterator;\n+use strum_macros::Display;\n+use strum_macros::EnumIter;\n+use strum_macros::EnumString;\n+\n+use crate::app_event::AppEvent;\n+use crate::app_event_sender::AppEventSender;\n+use crate::bottom_pane::CancellationEvent;\n+use crate::bottom_pane::bottom_pane_view::BottomPaneView;\n+use crate::bottom_pane::multi_select_picker::MultiSelectItem;\n+use crate::bottom_pane::multi_select_picker::MultiSelectPicker;\n+use crate::render::renderable::Renderable;\n+\n+/...", - "path": "codex-rs/tui/src/bottom_pane/title_setup.rs", - "status": "added" - }, - { - "additions": 161, - "deletions": 261, - "patch_excerpt": "@@ -44,10 +44,15 @@ use crate::audio_device::list_realtime_audio_device_names;\n use crate::bottom_pane::StatusLineItem;\n use crate::bottom_pane::StatusLinePreviewData;\n use crate::bottom_pane::StatusLineSetupView;\n+use crate::bottom_pane::TerminalTitleItem;\n+use crate::bottom_pane::TerminalTitleSetupView;\n use crate::status::RateLimitWindowDisplay;\n use crate::status::format_directory_display;\n use crate::status::format_tokens_compact;\n use crate::status::rate_limit_snapshot_display_for_limit;\n+use crate::terminal_title::SetTerminalTitleResult;\n+use crate::terminal_title::clear_terminal_title;\n+use crate::terminal_title::set_terminal_title;\n use crate::text_formatting::proper_join;\n use crate::version::CODEX_CLI_VERSION;\n use codex_app_server_protocol::ConfigLayerSource;\n@@ -169,6 +174,7 @@ use tokio::sync::mpsc::UnboundedSender;\n use tokio::task::JoinHandle;\n use tracing::debug;\n use tr...", - "path": "codex-rs/tui/src/chatwidget.rs", - "status": "modified" - }, - { - "additions": 660, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,660 @@\n+//! Status-line and terminal-title rendering helpers for `ChatWidget`.\n+//!\n+//! Keeping this logic in a focused submodule makes the additive title/status\n+//! behavior easier to review without paging through the rest of `chatwidget.rs`.\n+\n+use super::*;\n+\n+pub(super) const DEFAULT_TERMINAL_TITLE_ITEMS: [&str; 2] = [\"spinner\", \"project\"];\n+pub(super) const TERMINAL_TITLE_SPINNER_FRAMES: [&str; 10] =\n+ [\"\u280b\", \"\u2819\", \"\u2839\", \"\u2838\", \"\u283c\", \"\u2834\", \"\u2826\", \"\u2827\", \"\u2807\", \"\u280f\"];\n+pub(super) const TERMINAL_TITLE_SPINNER_INTERVAL: Duration = Duration::from_millis(100);\n+\n+#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]\n+/// Compact runtime states that can be rendered into the terminal title.\n+///\n+/// This is intentionally smaller than the full status-header vocabulary. The\n+/// title needs short, stable labels, so callers map richer lifecycle events\n+/// onto one of these buckets before ...", - "path": "codex-rs/tui/src/chatwidget/status_surfaces.rs", - "status": "added" - }, - { - "additions": 338, - "deletions": 10, - "patch_excerpt": "@@ -1777,6 +1777,7 @@ async fn helpers_are_available_and_do_not_panic() {\n model: Some(resolved_model),\n startup_tooltip_override: None,\n status_line_invalid_items_warned: Arc::new(AtomicBool::new(false)),\n+ terminal_title_invalid_items_warned: Arc::new(AtomicBool::new(false)),\n session_telemetry,\n };\n let mut w = ChatWidget::new(init, thread_manager);\n@@ -1896,6 +1897,7 @@ async fn make_chatwidget_manual(\n reasoning_buffer: String::new(),\n full_reasoning_buffer: String::new(),\n current_status: StatusIndicatorState::working(),\n+ terminal_title_status_kind: TerminalTitleStatusKind::Working,\n retry_status_header: None,\n pending_status_indicator_restore: false,\n suppress_queue_autosend: false,\n@@ -1919,6 +1921,7 @@ async fn make_chatwidget_manual(\n had_work_activity: false,\n ...", - "path": "codex-rs/tui/src/chatwidget/tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -125,6 +125,7 @@ mod status_indicator_widget;\n mod streaming;\n mod style;\n mod terminal_palette;\n+mod terminal_title;\n mod text_formatting;\n mod theme_picker;\n mod tooltips;", - "path": "codex-rs/tui/src/lib.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 0, - "patch_excerpt": "@@ -38,6 +38,7 @@ pub enum SlashCommand {\n Mention,\n Status,\n DebugConfig,\n+ Title,\n Statusline,\n Theme,\n Mcp,\n@@ -85,6 +86,7 @@ impl SlashCommand {\n SlashCommand::Skills => \"use skills to improve how Codex performs specific tasks\",\n SlashCommand::Status => \"show current session configuration and token usage\",\n SlashCommand::DebugConfig => \"show config layers and requirement sources for debugging\",\n+ SlashCommand::Title => \"configure which items appear in the terminal title\",\n SlashCommand::Statusline => \"configure which items appear in the status line\",\n SlashCommand::Theme => \"choose a syntax highlighting theme\",\n SlashCommand::Ps => \"list background terminals\",\n@@ -177,6 +179,7 @@ impl SlashCommand {\n SlashCommand::Agent | SlashCommand::MultiAgents => true,\n ...", - "path": "codex-rs/tui/src/slash_command.rs", - "status": "modified" - }, - { - "additions": 205, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,205 @@\n+//! Terminal-title output helpers for the TUI.\n+//!\n+//! This module owns the low-level OSC title write path and the sanitization\n+//! that happens immediately before we emit it. It is intentionally narrow:\n+//! callers decide when the title should change and whether an empty title means\n+//! \"leave the old title alone\" or \"clear the title Codex last wrote\".\n+//! This module does not attempt to read or restore the terminal's previous\n+//! title because that is not portable across terminals.\n+//!\n+//! Sanitization is necessary because title content is assembled from untrusted\n+//! text sources such as model output, thread names, project paths, and config.\n+//! Before we place that text inside an OSC sequence, we strip:\n+//! - control characters that could terminate or reshape the escape sequence\n+//! - bidi/invisible formatting codepoints that can visually reorder or hi...", - "path": "codex-rs/tui/src/terminal_title.rs", - "status": "added" - }, - { - "additions": 11, - "deletions": 0, - "patch_excerpt": "@@ -65,6 +65,17 @@ impl FrameRequester {\n frame_schedule_tx: tx,\n }\n }\n+\n+ /// Create a requester and expose its raw schedule queue for assertions.\n+ pub(crate) fn test_observable() -> (Self, mpsc::UnboundedReceiver) {\n+ let (tx, rx) = mpsc::unbounded_channel();\n+ (\n+ FrameRequester {\n+ frame_schedule_tx: tx,\n+ },\n+ rx,\n+ )\n+ }\n }\n \n /// A scheduler for coalescing frame draw requests and notifying the TUI event loop.", - "path": "codex-rs/tui/src/tui/frame_requester.rs", - "status": "modified" - } - ], - "linked_issues": [ - "#9", - "#36", - "#12334" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "## Problem\n\nWhen multiple Codex sessions are open at once, terminal tabs and windows are hard to distinguish from each other. The existing status line only helps once the TUI is already focused, so it does not solve the \"which tab is this?\" problem.\n\nThis PR adds a first-class `/title` command so the terminal window or tab title can carry a short, configurable summary of the current session.\n\n## Screenshot\n\n\"image\"\n\n## Mental model\n\n`/statusline` and `/title` are separate status surfaces with different constraints. The status line is an in-app footer that can be denser and more detailed. The terminal title is external terminal metadata, so it needs short, stable segments that still make multiple sessions easy to tell apart.\n\nThe `/title` configuration is an ordered list of compact items. By default it renders `spinner,project`, so active sessions show lightweight progress first while idle sessions still stay easy to disambiguate. Each configured item is omitted when its value is not currently available rather than forcing a placeholder.\n\n## Non-goals\n\nThis does not merge `/title` into `/statusline`, and it does not add an arbitrary free-form title string. The feature is intentionally limited to a small set of structured items so the title stays short and reviewable.\n\nThis also does not attempt to restore whatever title the terminal or shell had before Codex started. When Codex clears the title, it clears the title Codex last wrote.\n\n## Tradeoffs\n\nA separate `/title` command adds some conceptual overlap with `/statusline`, but it keeps title-specific constraints explicit instead of forcing the status line model to cover two different surfaces.\n\nTitle refresh can happen frequently, so the implementation now shares parsing and git-branch orchestration between the status line and title paths, and caches the derived project-root name by cwd. That keeps the hot path cheap without introducing background polling.\n\n## Architecture\n\nThe TUI gets a new `/title` slash command and a dedicated picker UI for selecting and ordering terminal-title items. The chosen ids are persisted in `tui.terminal_title`, with `spinner` and `project` as the default when the config is unset. `status` remains available as a separate text item, so configurations like `spinner,status` render compact progress like `\u280b Working`.\n\n`ChatWidget` now refreshes both status surfaces through a shared `refresh_status_surfaces()` path. That shared path parses configured items once, warns on invalid ids once, synchronizes shared cached state such as git-branch lookup, then renders the footer status line and terminal title from the same snapshot.\n\nLow-level OSC title writes live in `codex-rs/tui/src/terminal_title.rs`, which owns the terminal write path and last-mile sanitization before emitting OSC 0.\n\n## Security\n\nTerminal-title text is treated as untrusted display content before Codex emits it. The write path strips control characters, removes invisible and bidi formatting characters that can make the title visually misleading, normalizes whitespace, and caps the emitted length.\n\nReferences used while implementing this:\n\n- [xterm control sequences](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html)\n- [WezTerm escape sequences](https://wezterm.org/escape-sequences.html)\n- [CWE-150: Improper Neutralization of Escape, Meta, or Control Sequences](https://cwe.mitre.org/data/definitions/150.html)\n- [CERT VU#999008 (Trojan Source)](https://kb.cert.org/vuls/id/999008)\n- [Trojan Source disclosure site](https://trojansource.codes/)\n- [Unicode Bidirectional Algorithm (UAX #9)](https://www.unicode.org/reports/tr9/)\n- [Unicode Security Considerations (UTR #36)](https://www.unicode.org/reports/tr36/)\n\n## Observability\n\nUnknown configured title item ids are warned about once instead of repeatedly spamming the transcript. Live preview applies immediately while the `/title` picker is open, and cancel rolls the in-memory title selection back to the pre-picker value.\n\nIf terminal title writes fail, the TUI emits debug logs around set and clear attempts. The rendered status label intentionally collapses richer internal states into compact title text such as `Starting...`, `Ready`, `Thinking...`, `Working...`, `Waiting...`, and `Undoing...` when `status` is configured.\n\n## Tests\n\nRan:\n\n- `just fmt`\n- `cargo test -p codex-tui`\n\nAt the moment, the red Windows `rust-ci` failures are due to existing `codex-core` `apply_patch_cli` stack-overflow tests that also reproduce on `main`. The `/title`-specific `codex-tui` suite is green.\n", - "labels": [ - "oai" - ], - "merged_at": "2026-03-19T19:26:37Z", - "number": 12334, - "state": "merged", - "title": "feat(tui): add /title terminal title configuration", - "url": "https://github.com/openai/codex/pull/12334" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-13494.json b/artifacts/github/bundles/openai-codex-pr-13494.json deleted file mode 100644 index 1f68beb..0000000 --- a/artifacts/github/bundles/openai-codex-pr-13494.json +++ /dev/null @@ -1,204 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "charley-oai", - "committed_at": "2026-03-04T19:31:27Z", - "message": "Align SQLite feedback logs with feedback formatter", - "sha": "611620980b62be2239cf03e01f09b1e2ddec8a43", - "url": "https://github.com/openai/codex/commit/611620980b62be2239cf03e01f09b1e2ddec8a43" - }, - { - "author": "charley-oai", - "committed_at": "2026-03-04T19:53:47Z", - "message": "Count feedback_log_body in pruning budget", - "sha": "63b06d20f7f149aa99ff2b739e7d62012d77fc4d", - "url": "https://github.com/openai/codex/commit/63b06d20f7f149aa99ff2b739e7d62012d77fc4d" - }, - { - "author": "charley-oai", - "committed_at": "2026-03-04T20:06:02Z", - "message": "Simplify feedback log export truncation", - "sha": "e739af2796d0bc5ce0c35d52842ee1f64d3ff5f8", - "url": "https://github.com/openai/codex/commit/e739af2796d0bc5ce0c35d52842ee1f64d3ff5f8" - }, - { - "author": "charley-oai", - "committed_at": "2026-03-04T20:20:35Z", - "message": "Clarify retained log content budget", - "sha": "6191162e4fe91f72d3cafd64dc19986d7a726309", - "url": "https://github.com/openai/codex/commit/6191162e4fe91f72d3cafd64dc19986d7a726309" - }, - { - "author": "charley-oai", - "committed_at": "2026-03-04T21:46:25Z", - "message": "Clarify estimated_bytes comment", - "sha": "9e3e50f4a1fc056c383e3599da011f52cc1e61cc", - "url": "https://github.com/openai/codex/commit/9e3e50f4a1fc056c383e3599da011f52cc1e61cc" - }, - { - "author": "charley-oai", - "committed_at": "2026-03-04T22:15:54Z", - "message": "Preserve feedback export trailing newlines", - "sha": "460b8771511b17fd71e575e17498d041c3b2a894", - "url": "https://github.com/openai/codex/commit/460b8771511b17fd71e575e17498d041c3b2a894" - }, - { - "author": "charley-oai", - "committed_at": "2026-03-06T19:54:20Z", - "message": "Adapt feedback log migration to dedicated logs DB", - "sha": "81b8f9e04df703e06c9552fe599873528c50e342", - "url": "https://github.com/openai/codex/commit/81b8f9e04df703e06c9552fe599873528c50e342" - }, - { - "author": "charley-oai", - "committed_at": "2026-03-17T11:09:14Z", - "message": "Drop message column from logs DB", - "sha": "b86efe6a1000a03242295a2e8e658c149b4d219e", - "url": "https://github.com/openai/codex/commit/b86efe6a1000a03242295a2e8e658c149b4d219e" - }, - { - "author": "charley-oai", - "committed_at": "2026-03-18T04:39:58Z", - "message": "codex: address PR review feedback (#13494)", - "sha": "608118afd83114832882f1ba13d3646b970baf90", - "url": "https://github.com/openai/codex/commit/608118afd83114832882f1ba13d3646b970baf90" - }, - { - "author": "charley-oai", - "committed_at": "2026-03-18T05:07:40Z", - "message": "codex: fix rendered ToolCall log test (#13494)", - "sha": "f62896afc692ce5f2c2bcd9c19387df798996ad5", - "url": "https://github.com/openai/codex/commit/f62896afc692ce5f2c2bcd9c19387df798996ad5" - }, - { - "author": "charley-oai", - "committed_at": "2026-03-18T22:14:40Z", - "message": "Drop redundant logs process_uuid index", - "sha": "46ceae1a096461f8d27e134b5c98e1d23a507975", - "url": "https://github.com/openai/codex/commit/46ceae1a096461f8d27e134b5c98e1d23a507975" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "SQL", - "LOG_PARTITION_SIZE_LIMIT_BYTES", - "ALTER", - "TABLE", - "RENAME", - "CREATE", - "INTEGER", - "PRIMARY", - "KEY", - "AUTOINCREMENT", - "NOT", - "NULL", - "TEXT", - "DEFAULT", - "INSERT", - "INTO", - "SELECT", - "FROM", - "DROP", - "INDEX", - "DESC", - "WHERE", - "UNIX_EPOCH", - "PROCESS_LOG_UUID", - "LOG_BATCH_SIZE", - "TRACE", - "LOG_PARTITION_ROW_LIMIT", - "TODO", - "MAX", - "WITH", - "LIMIT", - "CASE", - "WHEN", - "THEN", - "ELSE", - "END", - "CAST", - "BLOB", - "AND", - "SUM", - "OVER", - "ORDER", - "ASC", - "INSTR", - "COALESCE", - "LOGS_MIGRATOR", - "INFO", - "VALUES" - ], - "files": [ - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -482,7 +482,7 @@ async fn tool_call_logs_include_thread_id() -> Result<()> {\n if let Some(row) = rows.into_iter().find(|row| {\n row.message\n .as_deref()\n- .is_some_and(|m| m.starts_with(\"ToolCall:\"))\n+ .is_some_and(|m| m.contains(\"ToolCall:\"))\n }) {\n let thread_id = row.thread_id;\n let message = row.message;\n@@ -497,7 +497,7 @@ async fn tool_call_logs_include_thread_id() -> Result<()> {\n assert!(\n message\n .as_deref()\n- .is_some_and(|text| text.starts_with(\"ToolCall:\")),\n+ .is_some_and(|text| text.contains(\"ToolCall:\")),\n \"expected ToolCall message, got {message:?}\"\n );", - "path": "codex-rs/core/tests/suite/sqlite_state.rs", - "status": "modified" - }, - { - "additions": 53, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,53 @@\n+ALTER TABLE logs RENAME TO logs_old;\n+\n+CREATE TABLE logs (\n+ id INTEGER PRIMARY KEY AUTOINCREMENT,\n+ ts INTEGER NOT NULL,\n+ ts_nanos INTEGER NOT NULL,\n+ level TEXT NOT NULL,\n+ target TEXT NOT NULL,\n+ feedback_log_body TEXT,\n+ module_path TEXT,\n+ file TEXT,\n+ line INTEGER,\n+ thread_id TEXT,\n+ process_uuid TEXT,\n+ estimated_bytes INTEGER NOT NULL DEFAULT 0\n+);\n+\n+INSERT INTO logs (\n+ id,\n+ ts,\n+ ts_nanos,\n+ level,\n+ target,\n+ feedback_log_body,\n+ module_path,\n+ file,\n+ line,\n+ thread_id,\n+ process_uuid,\n+ estimated_bytes\n+)\n+SELECT\n+ id,\n+ ts,\n+ ts_nanos,\n+ level,\n+ target,\n+ message,\n+ module_path,\n+ file,\n+ line,\n+ thread_id,\n+ process_uuid,\n+ estimated_bytes\n+FROM logs_old;\n+\n+DROP TABLE logs_old;\n+\n+CREATE INDEX idx_logs_ts ON logs(ts DESC, ts_nanos DESC, id DE...", - "path": "codex-rs/state/logs_migrations/0002_logs_feedback_log_body.sql", - "status": "added" - }, - { - "additions": 3, - "deletions": 3, - "patch_excerpt": "@@ -46,7 +46,7 @@ struct Args {\n #[arg(long = \"thread-id\")]\n thread_id: Vec,\n \n- /// Substring match against the log message.\n+ /// Substring match against the rendered log body.\n #[arg(long)]\n search: Option,\n \n@@ -62,7 +62,7 @@ struct Args {\n #[arg(long, default_value_t = 500)]\n poll_ms: u64,\n \n- /// Show compact output with only time, level, and message.\n+ /// Show compact output with only time, level, and rendered log body.\n #[arg(long)]\n compact: bool,\n }\n@@ -295,7 +295,7 @@ fn heuristic_formatting(message: &str) -> String {\n \n mod matcher {\n pub(super) fn apply_patch(message: &str) -> bool {\n- message.starts_with(\"ToolCall: apply_patch\")\n+ message.contains(\"ToolCall: apply_patch\")\n }\n }", - "path": "codex-rs/state/src/bin/logs_client.rs", - "status": "modified" - }, - { - "additions": 77, - "deletions": 26, - "patch_excerpt": "@@ -34,6 +34,10 @@ use tracing::span::Attributes;\n use tracing::span::Id;\n use tracing::span::Record;\n use tracing_subscriber::Layer;\n+use tracing_subscriber::field::RecordFields;\n+use tracing_subscriber::fmt::FormatFields;\n+use tracing_subscriber::fmt::FormattedFields;\n+use tracing_subscriber::fmt::format::DefaultFields;\n use tracing_subscriber::registry::LookupSpan;\n use uuid::Uuid;\n \n@@ -95,6 +99,8 @@ where\n \n if let Some(span) = ctx.span(id) {\n span.extensions_mut().insert(SpanLogContext {\n+ name: span.metadata().name().to_string(),\n+ formatted_fields: format_fields(attrs),\n thread_id: visitor.thread_id,\n });\n }\n@@ -109,16 +115,17 @@ where\n let mut visitor = SpanFieldVisitor::default();\n values.record(&mut visitor);\n \n- if visitor.thread_id.is_none() {\n- return;\n- ...", - "path": "codex-rs/state/src/log_db.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -8,6 +8,7 @@ pub struct LogEntry {\n pub level: String,\n pub target: String,\n pub message: Option,\n+ pub feedback_log_body: Option,\n pub thread_id: Option,\n pub process_uuid: Option,\n pub module_path: Option,", - "path": "codex-rs/state/src/model/log.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 1, - "patch_excerpt": "@@ -57,10 +57,12 @@ mod memories;\n mod test_support;\n mod threads;\n \n-// \"Partition\" is the retention bucket we cap at 10 MiB:\n+// \"Partition\" is the retained-log-content bucket we cap at 10 MiB:\n // - one bucket per non-null thread_id\n // - one bucket per threadless (thread_id IS NULL) non-null process_uuid\n // - one bucket for threadless rows with process_uuid IS NULL\n+// This budget tracks each row's persisted rendered log body plus non-body\n+// metadata, rather than the exact sum of all persisted SQLite column bytes.\n const LOG_PARTITION_SIZE_LIMIT_BYTES: i64 = 10 * 1024 * 1024;\n const LOG_PARTITION_ROW_LIMIT: i64 = 1_000;", - "path": "codex-rs/state/src/runtime.rs", - "status": "modified" - }, - { - "additions": 247, - "deletions": 58, - "patch_excerpt": "@@ -13,10 +13,15 @@ impl StateRuntime {\n \n let mut tx = self.logs_pool.begin().await?;\n let mut builder = QueryBuilder::::new(\n- \"INSERT INTO logs (ts, ts_nanos, level, target, message, thread_id, process_uuid, module_path, file, line, estimated_bytes) \",\n+ \"INSERT INTO logs (ts, ts_nanos, level, target, feedback_log_body, thread_id, process_uuid, module_path, file, line, estimated_bytes) \",\n );\n builder.push_values(entries, |mut row, entry| {\n- let estimated_bytes = entry.message.as_ref().map_or(0, String::len) as i64\n+ let feedback_log_body = entry.feedback_log_body.as_ref().or(entry.message.as_ref());\n+ // Keep about 10 MiB of reader-visible log content per partition.\n+ // Both `query_logs` and `/feedback` read the persisted\n+ // `feedback_log_body`, while `LogEntry.mes...", - "path": "codex-rs/state/src/runtime/logs.rs", - "status": "modified" - } - ], - "linked_issues": [ - "#13494" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "## Summary\n- store a pre-rendered `feedback_log_body` in SQLite so `/feedback` exports keep span prefixes and structured event fields\n- render SQLite feedback exports with timestamps and level prefixes to match the old in-memory feedback formatter, while preserving existing trailing newlines\n- count `feedback_log_body` in the SQLite retention budget so structured or span-prefixed rows still prune correctly\n- bound `/feedback` row loading in SQL with the retention estimate, then apply exact whole-line truncation in Rust so uploads stay capped without splitting lines\n\n## Details\n- add a `feedback_log_body` column to `logs` and backfill it from `message` for existing rows\n- capture span names plus formatted span and event fields at write time, since SQLite does not retain enough structure to reconstruct the old formatter later\n- keep SQLite feedback queries scoped to the requested thread plus same-process threadless rows\n- restore a SQL-side cumulative `estimated_bytes` cap for feedback export queries so over-retained partitions do not load every matching row before truncation\n- add focused formatting coverage for exported feedback lines and parity coverage against `tracing_subscriber`\n\n## Testing\n- cargo test -p codex-state\n- just fix -p codex-state\n- just fmt\n\ncodex author: `codex resume 019ca1b0-0ecc-78b1-85eb-6befdd7e4f1f`\n", - "labels": [], - "merged_at": "2026-03-18T22:44:31Z", - "number": 13494, - "state": "merged", - "title": "Align SQLite feedback logs with feedback formatter", - "url": "https://github.com/openai/codex/pull/13494" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-14632.json b/artifacts/github/bundles/openai-codex-pr-14632.json deleted file mode 100644 index 14ab4a1..0000000 --- a/artifacts/github/bundles/openai-codex-pr-14632.json +++ /dev/null @@ -1,128 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "owenlin0", - "committed_at": "2026-03-13T21:23:06Z", - "message": "feat(core, tracing): create turn spans over websockets", - "sha": "c17855e16bda5ae24b2d1ef8791d2567f59fb311", - "url": "https://github.com/openai/codex/commit/c17855e16bda5ae24b2d1ef8791d2567f59fb311" - }, - { - "author": "owenlin0", - "committed_at": "2026-03-18T21:02:20Z", - "message": "update", - "sha": "6cd99318b4350a43f1280d9cb94f21c7a1dcd318", - "url": "https://github.com/openai/codex/commit/6cd99318b4350a43f1280d9cb94f21c7a1dcd318" - }, - { - "author": "owenlin0", - "committed_at": "2026-03-18T21:07:32Z", - "message": "clean up", - "sha": "b6671aa1e7c465e3c838377f6363906498a9e6fb", - "url": "https://github.com/openai/codex/commit/b6671aa1e7c465e3c838377f6363906498a9e6fb" - }, - { - "author": "owenlin0", - "committed_at": "2026-03-19T03:11:41Z", - "message": "clean up", - "sha": "4c5596c9647a49dfc2213482ec7e08c93fd8acde", - "url": "https://github.com/openai/codex/commit/4c5596c9647a49dfc2213482ec7e08c93fd8acde" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "API", - "WS_REQUEST_HEADER_TRACEPARENT_CLIENT_METADATA_KEY", - "WS_REQUEST_HEADER_TRACESTATE_CLIENT_METADATA_KEY", - "INIT", - "X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER", - "MODEL", - "OPENAI_BETA_HEADER", - "WS_V2_BETA_HEADER_VALUE", - "X_CLIENT_REQUEST_ID_HEADER" - ], - "files": [ - { - "additions": 5, - "deletions": 0, - "patch_excerpt": "@@ -3068,6 +3068,8 @@ dependencies = [\n \"ctor 0.6.3\",\n \"futures\",\n \"notify\",\n+ \"opentelemetry\",\n+ \"opentelemetry_sdk\",\n \"pretty_assertions\",\n \"regex-lite\",\n \"reqwest\",\n@@ -3076,6 +3078,9 @@ dependencies = [\n \"tempfile\",\n \"tokio\",\n \"tokio-tungstenite\",\n+ \"tracing\",\n+ \"tracing-opentelemetry\",\n+ \"tracing-subscriber\",\n \"walkdir\",\n \"wiremock\",\n \"zstd\",", - "path": "codex-rs/Cargo.lock", - "status": "modified" - }, - { - "additions": 26, - "deletions": 0, - "patch_excerpt": "@@ -5,6 +5,7 @@ use codex_protocol::models::ResponseItem;\n use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;\n use codex_protocol::protocol::RateLimitSnapshot;\n use codex_protocol::protocol::TokenUsage;\n+use codex_protocol::protocol::W3cTraceContext;\n use futures::Stream;\n use serde::Deserialize;\n use serde::Serialize;\n@@ -15,6 +16,9 @@ use std::task::Context;\n use std::task::Poll;\n use tokio::sync::mpsc;\n \n+pub const WS_REQUEST_HEADER_TRACEPARENT_CLIENT_METADATA_KEY: &str = \"ws_request_header_traceparent\";\n+pub const WS_REQUEST_HEADER_TRACESTATE_CLIENT_METADATA_KEY: &str = \"ws_request_header_tracestate\";\n+\n /// Canonical input payload for the compaction endpoint.\n #[derive(Debug, Clone, Serialize)]\n pub struct CompactionInput<'a> {\n@@ -215,6 +219,28 @@ pub struct ResponseCreateWsRequest {\n pub client_metadata: Option>,\n }\n \n+pub fn r...", - "path": "codex-rs/codex-api/src/common.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 0, - "patch_excerpt": "@@ -23,7 +23,10 @@ pub use crate::common::ResponseCreateWsRequest;\n pub use crate::common::ResponseEvent;\n pub use crate::common::ResponseStream;\n pub use crate::common::ResponsesApiRequest;\n+pub use crate::common::WS_REQUEST_HEADER_TRACEPARENT_CLIENT_METADATA_KEY;\n+pub use crate::common::WS_REQUEST_HEADER_TRACESTATE_CLIENT_METADATA_KEY;\n pub use crate::common::create_text_param_for_request;\n+pub use crate::common::response_create_client_metadata;\n pub use crate::endpoint::compact::CompactClient;\n pub use crate::endpoint::memories::MemoriesClient;\n pub use crate::endpoint::models::ModelsClient;", - "path": "codex-rs/codex-api/src/lib.rs", - "status": "modified" - }, - { - "additions": 11, - "deletions": 1, - "patch_excerpt": "@@ -59,7 +59,9 @@ use codex_api::common::ResponsesWsRequest;\n use codex_api::create_text_param_for_request;\n use codex_api::error::ApiError;\n use codex_api::requests::responses::Compression;\n+use codex_api::response_create_client_metadata;\n use codex_otel::SessionTelemetry;\n+use codex_otel::current_span_w3c_trace_context;\n \n use codex_protocol::ThreadId;\n use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;\n@@ -69,6 +71,7 @@ use codex_protocol::models::ResponseItem;\n use codex_protocol::openai_models::ModelInfo;\n use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;\n use codex_protocol::protocol::SessionSource;\n+use codex_protocol::protocol::W3cTraceContext;\n use eventsource_stream::Event;\n use eventsource_stream::EventStreamError;\n use futures::StreamExt;\n@@ -1099,6 +1102,7 @@ impl ModelClientSession {\n service_tier: Option TestTracingContext {\n+ global::set_text_map_propagator(TraceContextPropagator::new());\n+\n+ let provider = SdkTracerProvider::builder().build();\n+ let tracer = provider.tracer(tracer_name.to_string());\n+ let subscriber =\n+ tracing_subscriber::registry().with(tracing_opentelemetry::layer().with_tracer(tracer));\n+\n+ TestTracingContext {\n+ _provider: provider,\n+ _guard: s...", - "path": "codex-rs/core/tests/common/tracing.rs", - "status": "added" - }, - { - "additions": 138, - "deletions": 0, - "patch_excerpt": "@@ -1,4 +1,6 @@\n #![allow(clippy::expect_used, clippy::unwrap_used)]\n+use codex_api::WS_REQUEST_HEADER_TRACEPARENT_CLIENT_METADATA_KEY;\n+use codex_api::WS_REQUEST_HEADER_TRACESTATE_CLIENT_METADATA_KEY;\n use codex_core::CodexAuth;\n use codex_core::ModelClient;\n use codex_core::ModelClientSession;\n@@ -10,6 +12,7 @@ use codex_core::X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER;\n use codex_core::features::Feature;\n use codex_otel::SessionTelemetry;\n use codex_otel::TelemetryAuthMode;\n+use codex_otel::current_span_w3c_trace_context;\n use codex_otel::metrics::MetricsClient;\n use codex_otel::metrics::MetricsConfig;\n use codex_protocol::ThreadId;\n@@ -24,6 +27,7 @@ use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;\n use codex_protocol::protocol::EventMsg;\n use codex_protocol::protocol::Op;\n use codex_protocol::protocol::SessionSource;\n+use codex_protocol::protocol::W3cTra...", - "path": "codex-rs/core/tests/suite/client_websockets.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "## Description\r\n\r\nDependent on:\r\n- [responsesapi] https://github.com/openai/openai/pull/760991 \r\n- [codex-backend] https://github.com/openai/openai/pull/760985\r\n\r\n`codex app-server -> codex-backend -> responsesapi` now reuses a persistent websocket connection across many turns. This PR updates tracing when using websockets so that each `response.create` websocket request propagates the current tracing context, so we can get a holistic end-to-end trace for each turn. \r\n\r\nTracing is propagated via special keys (`ws_request_header_traceparent`, `ws_request_header_tracestate`) set in the `client_metadata` param in Responses API.\r\n\r\nCurrently tracing on websockets is a bit broken because we only set tracing context on ws connection time, so it's detached from a `turn/start` request.\r\n", - "labels": [], - "merged_at": "2026-03-19T03:41:07Z", - "number": 14632, - "state": "merged", - "title": "feat(core, tracing): create turn spans over websockets", - "url": "https://github.com/openai/codex/pull/14632" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-14847.json b/artifacts/github/bundles/openai-codex-pr-14847.json deleted file mode 100644 index 1b35841..0000000 --- a/artifacts/github/bundles/openai-codex-pr-14847.json +++ /dev/null @@ -1,230 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "viyatb-oai", - "committed_at": "2026-03-16T21:04:59Z", - "message": "feat: add websocket auth flags for app-server", - "sha": "22d51ec7a5a1c25854ee78245a02469d40402d5c", - "url": "https://github.com/openai/codex/commit/22d51ec7a5a1c25854ee78245a02469d40402d5c" - }, - { - "author": "viyatb-oai", - "committed_at": "2026-03-16T21:35:30Z", - "message": "refactor: rename transport auth module", - "sha": "a19f46da14dae04ea3d20acd27c2e252fa04d23d", - "url": "https://github.com/openai/codex/commit/a19f46da14dae04ea3d20acd27c2e252fa04d23d" - }, - { - "author": "viyatb-oai", - "committed_at": "2026-03-16T22:01:21Z", - "message": "fix: harden websocket auth verification", - "sha": "35cff1b44b0e435e9dec2889ea03b8d3b69a6f8b", - "url": "https://github.com/openai/codex/commit/35cff1b44b0e435e9dec2889ea03b8d3b69a6f8b" - }, - { - "author": "viyatb-oai", - "committed_at": "2026-03-16T22:45:31Z", - "message": "fix: reject browser-origin websocket handshakes without auth", - "sha": "c9e706fce2332e4c967c2decf8331202e3ecd1e9", - "url": "https://github.com/openai/codex/commit/c9e706fce2332e4c967c2decf8331202e3ecd1e9" - }, - { - "author": "viyatb-oai", - "committed_at": "2026-03-17T07:04:24Z", - "message": "fix: make websocket auth opt-in during rollout", - "sha": "c1f1b6d7083bcbce6783082dd4b510f7ba93eb49", - "url": "https://github.com/openai/codex/commit/c1f1b6d7083bcbce6783082dd4b510f7ba93eb49" - }, - { - "author": "viyatb-oai", - "committed_at": "2026-03-18T00:46:33Z", - "message": "fix: use jwt for signed websocket auth", - "sha": "3b8432914eba1c8bf36e93401c1b1cf37d6044ff", - "url": "https://github.com/openai/codex/commit/3b8432914eba1c8bf36e93401c1b1cf37d6044ff" - }, - { - "author": "viyatb-oai", - "committed_at": "2026-03-18T00:58:52Z", - "message": "refactor: remove insecure websocket rollout flag", - "sha": "61baaee51e9ada1285220b635a463c0c2d6bf65d", - "url": "https://github.com/openai/codex/commit/61baaee51e9ada1285220b635a463c0c2d6bf65d" - }, - { - "author": "etraut-openai", - "committed_at": "2026-03-24T15:26:33Z", - "message": "codex: address PR review feedback (#14847)", - "sha": "4e69aa84c935d6a7e37ee485555e1990118948f7", - "url": "https://github.com/openai/codex/commit/4e69aa84c935d6a7e37ee485555e1990118948f7" - }, - { - "author": "viyatb-oai", - "committed_at": "2026-03-24T19:05:22Z", - "message": "refactor: use jsonwebtoken for websocket jwt auth", - "sha": "7b79230e3a53eaef73d897827e2ae376b0cfce68", - "url": "https://github.com/openai/codex/commit/7b79230e3a53eaef73d897827e2ae376b0cfce68" - }, - { - "author": "viyatb-oai", - "committed_at": "2026-03-25T17:14:44Z", - "message": "Merge remote-tracking branch 'origin/main' into codex/viyatb/app-server-websocket-auth", - "sha": "a91c332189fb569579570161c556d1d92b217a12", - "url": "https://github.com/openai/codex/commit/a91c332189fb569579570161c556d1d92b217a12" - }, - { - "author": "viyatb-oai", - "committed_at": "2026-03-25T18:08:04Z", - "message": "Merge remote-tracking branch 'origin/main' into codex/viyatb/app-server-websocket-auth", - "sha": "b60f64b99036972ba8354c964bbc22875f5e0912", - "url": "https://github.com/openai/codex/commit/b60f64b99036972ba8354c964bbc22875f5e0912" - } - ], - "default_branch": "main", - "docs_refs": [ - "codex-rs/app-server/README.md" - ], - "examples_refs": [], - "extracted_flags": [ - "JSON", - "--ws-auth", - "HMAC", - "JWT", - "JWS", - "--all-targets", - "--listen", - "PORT", - "SSH", - "--ws-token-file", - "--ws-shared-secret-file", - "--ws-issuer", - "--ws-audience", - "--ws-max-clock-skew-seconds", - "RUST_LOG", - "CHANNEL_CAPACITY", - "INPUT_TOO_LARGE_ERROR_CODE", - "INVALID_PARAMS_ERROR_CODE", - "LOG_FORMAT_ENV_VAR", - "LOG_FORMAT", - "VSC", - "OVERLOADED_ERROR_CODE", - "ORIGIN", - "TLS", - "AUTHORIZATION", - "DEFAULT_MAX_CLOCK_SKEW_SECONDS", - "MIN_SIGNED_BEARER_SECRET_BYTES", - "INVALID_AUTHORIZATION_HEADER_MESSAGE", - "MODE", - "PATH", - "ISSUER", - "AUDIENCE", - "SECONDS", - "HS256", - "UNAUTHORIZED", - "URL_SAFE_NO_PAD", - "JSONRPCE", - "JSONRPCR", - "DEFAULT_READ_TIMEOUT", - "GET", - "FORBIDDEN", - "HTTP", - "CODEX_HOME", - "--allow-unauthenticated-non-loopback-ws" - ], - "files": [ - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -971,6 +971,7 @@\n \"jni_0.21.1\": \"{\\\"dependencies\\\":[{\\\"name\\\":\\\"cesu8\\\",\\\"req\\\":\\\"^1.1.0\\\"},{\\\"name\\\":\\\"cfg-if\\\",\\\"req\\\":\\\"^1.0.0\\\"},{\\\"name\\\":\\\"combine\\\",\\\"req\\\":\\\"^4.1.0\\\"},{\\\"name\\\":\\\"java-locator\\\",\\\"optional\\\":true,\\\"req\\\":\\\"^0.1\\\"},{\\\"name\\\":\\\"jni-sys\\\",\\\"req\\\":\\\"^0.3.0\\\"},{\\\"name\\\":\\\"libloading\\\",\\\"optional\\\":true,\\\"req\\\":\\\"^0.7\\\"},{\\\"name\\\":\\\"log\\\",\\\"req\\\":\\\"^0.4.4\\\"},{\\\"name\\\":\\\"thiserror\\\",\\\"req\\\":\\\"^1.0.20\\\"},{\\\"kind\\\":\\\"dev\\\",\\\"name\\\":\\\"assert_matches\\\",\\\"req\\\":\\\"^1.5.0\\\"},{\\\"kind\\\":\\\"dev\\\",\\\"name\\\":\\\"lazy_static\\\",\\\"req\\\":\\\"^1\\\"},{\\\"kind\\\":\\\"dev\\\",\\\"name\\\":\\\"rusty-fork\\\",\\\"req\\\":\\\"^0.3.0\\\"},{\\\"kind\\\":\\\"build\\\",\\\"name\\\":\\\"walkdir\\\",\\\"req\\\":\\\"^2\\\"},{\\\"features\\\":[\\\"Win32_Globalization\\\"],\\\"name\\\":\\\"windows-sys\\\",\\\"req\\\":\\\"^0.45.0\\\",\\\"target\\\":\\\"cfg(windows)\\\"},{\\\"kind\\\":\\\"dev\\\",\\\"name\\\":\\\"bytemuck\\\",\\\"req\\\":\\\"^1.13.0\\\",\\\"target\\\":\\\"cfg(windows)\\\"}],\\\"features\\\":{\\\"def...", - "path": "MODULE.bazel.lock", - "status": "modified" - }, - { - "additions": 31, - "deletions": 0, - "patch_excerpt": "@@ -1457,8 +1457,11 @@ dependencies = [\n \"codex-utils-cli\",\n \"codex-utils-json-to-toml\",\n \"codex-utils-pty\",\n+ \"constant_time_eq\",\n \"core_test_support\",\n \"futures\",\n+ \"hmac\",\n+ \"jsonwebtoken\",\n \"opentelemetry\",\n \"opentelemetry_sdk\",\n \"owo-colors\",\n@@ -1468,6 +1471,7 @@ dependencies = [\n \"serde\",\n \"serde_json\",\n \"serial_test\",\n+ \"sha2\",\n \"shlex\",\n \"tempfile\",\n \"time\",\n@@ -5742,6 +5746,21 @@ dependencies = [\n \"wasm-bindgen\",\n ]\n \n+[[package]]\n+name = \"jsonwebtoken\"\n+version = \"9.3.1\"\n+source = \"registry+https://github.com/rust-lang/crates.io-index\"\n+checksum = \"5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde\"\n+dependencies = [\n+ \"base64 0.22.1\",\n+ \"js-sys\",\n+ \"pem\",\n+ \"ring\",\n+ \"serde\",\n+ \"serde_json\",\n+ \"simple_asn1\",\n+]\n+\n [[package]]\n name = \"keyring\"\n version = \"3.6.3\"\n@@ -9232,6 +9251,18 @@ version = \"2.7.0\"\n source = \"registry+https://github.com/rust-l...", - "path": "codex-rs/Cargo.lock", - "status": "modified" - }, - { - "additions": 3, - "deletions": 0, - "patch_excerpt": "@@ -200,6 +200,7 @@ chrono = \"0.4.43\"\n clap = \"4\"\n clap_complete = \"4\"\n color-eyre = \"0.6.3\"\n+constant_time_eq = \"0.3.1\"\n crossbeam-channel = \"0.5.15\"\n crossterm = \"0.28.1\"\n csv = \"1.3.1\"\n@@ -218,6 +219,7 @@ flate2 = \"1.1.4\"\n futures = { version = \"0.3\", default-features = false }\n gethostname = \"1.1.0\"\n globset = \"0.4\"\n+hmac = \"0.12.1\"\n http = \"1.3.1\"\n icu_decimal = \"2.1\"\n icu_locale_core = \"2.1\"\n@@ -230,6 +232,7 @@ indexmap = \"2.12.0\"\n insta = \"1.46.3\"\n inventory = \"0.3.19\"\n itertools = \"0.14.0\"\n+jsonwebtoken = \"9.3.1\"\n keyring = { version = \"3.6\", default-features = false }\n landlock = \"0.4.4\"\n lazy_static = \"1\"", - "path": "codex-rs/Cargo.toml", - "status": "modified" - }, - { - "additions": 4, - "deletions": 0, - "patch_excerpt": "@@ -53,10 +53,14 @@ codex-utils-absolute-path = { workspace = true }\n codex-utils-json-to-toml = { workspace = true }\n chrono = { workspace = true }\n clap = { workspace = true, features = [\"derive\"] }\n+constant_time_eq = { workspace = true }\n futures = { workspace = true }\n+hmac = { workspace = true }\n+jsonwebtoken = { workspace = true }\n owo-colors = { workspace = true, features = [\"supports-colors\"] }\n serde = { workspace = true, features = [\"derive\"] }\n serde_json = { workspace = true }\n+sha2 = { workspace = true }\n tempfile = { workspace = true }\n time = { workspace = true }\n toml = { workspace = true }", - "path": "codex-rs/app-server/Cargo.toml", - "status": "modified" - }, - { - "additions": 9, - "deletions": 0, - "patch_excerpt": "@@ -34,6 +34,15 @@ When running with `--listen ws://IP:PORT`, the same listener also serves basic H\n \n Websocket transport is currently experimental and unsupported. Do not rely on it for production workloads.\n \n+Security note:\n+\n+- Loopback websocket listeners (`ws://127.0.0.1:PORT`) remain appropriate for localhost and SSH port-forwarding workflows.\n+- Non-loopback websocket listeners currently allow unauthenticated connections by default during rollout. If you expose one remotely, configure websocket auth explicitly now.\n+- Supported auth modes are app-server flags:\n+ - `--ws-auth capability-token --ws-token-file /absolute/path`\n+ - `--ws-auth signed-bearer-token --ws-shared-secret-file /absolute/path` for HMAC-signed JWT/JWS bearer tokens, with optional `--ws-issuer`, `--ws-audience`, `--ws-max-clock-skew-seconds`\n+- Clients present the credential as `Authorization: Bearer `...", - "path": "codex-rs/app-server/README.md", - "status": "modified" - }, - { - "additions": 7, - "deletions": 0, - "patch_excerpt": "@@ -27,6 +27,7 @@ use crate::transport::CHANNEL_CAPACITY;\n use crate::transport::ConnectionState;\n use crate::transport::OutboundConnectionState;\n use crate::transport::TransportEvent;\n+use crate::transport::auth::policy_from_settings;\n use crate::transport::route_outgoing_envelope;\n use crate::transport::start_stdio_connection;\n use crate::transport::start_websocket_acceptor;\n@@ -81,6 +82,9 @@ mod transport;\n pub use crate::error_code::INPUT_TOO_LARGE_ERROR_CODE;\n pub use crate::error_code::INVALID_PARAMS_ERROR_CODE;\n pub use crate::transport::AppServerTransport;\n+pub use crate::transport::auth::AppServerWebsocketAuthArgs;\n+pub use crate::transport::auth::AppServerWebsocketAuthSettings;\n+pub use crate::transport::auth::WebsocketAuthCliMode;\n \n const LOG_FORMAT_ENV_VAR: &str = \"LOG_FORMAT\";\n \n@@ -337,6 +341,7 @@ pub async fn run_main(\n default_analytics_enabled,\n AppServe...", - "path": "codex-rs/app-server/src/lib.rs", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -1,5 +1,6 @@\n use clap::Parser;\n use codex_app_server::AppServerTransport;\n+use codex_app_server::AppServerWebsocketAuthArgs;\n use codex_app_server::run_main_with_transport;\n use codex_arg0::Arg0DispatchPaths;\n use codex_arg0::arg0_dispatch_or_else;\n@@ -31,6 +32,9 @@ struct AppServerArgs {\n value_parser = SessionSource::from_startup_arg\n )]\n session_source: SessionSource,\n+\n+ #[command(flatten)]\n+ auth: AppServerWebsocketAuthArgs,\n }\n \n fn main() -> anyhow::Result<()> {\n@@ -43,6 +47,7 @@ fn main() -> anyhow::Result<()> {\n };\n let transport = args.listen;\n let session_source = args.session_source;\n+ let auth = args.auth.try_into_settings()?;\n \n run_main_with_transport(\n arg0_paths,\n@@ -51,6 +56,7 @@ fn main() -> anyhow::Result<()> {\n /*default_analytics_enabled*/ false,\n transport,\n ...", - "path": "codex-rs/app-server/src/main.rs", - "status": "modified" - }, - { - "additions": 30, - "deletions": 4, - "patch_excerpt": "@@ -1,3 +1,8 @@\n+pub(crate) mod auth;\n+\n+use self::auth::WebsocketAuthPolicy;\n+use self::auth::authorize_upgrade;\n+use self::auth::should_warn_about_unauthenticated_non_loopback_listener;\n use crate::error_code::OVERLOADED_ERROR_CODE;\n use crate::message_processor::ConnectionSessionState;\n use crate::outgoing_message::ConnectionId;\n@@ -12,6 +17,7 @@ use axum::extract::State;\n use axum::extract::ws::Message as WebSocketMessage;\n use axum::extract::ws::WebSocket;\n use axum::extract::ws::WebSocketUpgrade;\n+use axum::http::HeaderMap;\n use axum::http::Request;\n use axum::http::StatusCode;\n use axum::http::header::ORIGIN;\n@@ -83,7 +89,7 @@ fn print_websocket_startup_banner(addr: SocketAddr) {\n );\n } else {\n eprintln!(\n- \" {note_label} this is a raw WS server; consider running behind TLS/auth for real remote use\"\n+ \" {note_label} websocket auth is opt...", - "path": "codex-rs/app-server/src/transport.rs", - "status": "modified" - }, - { - "additions": 583, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,583 @@\n+use anyhow::Context;\n+use axum::http::HeaderMap;\n+use axum::http::StatusCode;\n+use axum::http::header::AUTHORIZATION;\n+use clap::Args;\n+use clap::ValueEnum;\n+use codex_utils_absolute_path::AbsolutePathBuf;\n+use constant_time_eq::constant_time_eq_32;\n+use jsonwebtoken::Algorithm;\n+use jsonwebtoken::DecodingKey;\n+use jsonwebtoken::Validation;\n+use jsonwebtoken::decode;\n+use serde::Deserialize;\n+use sha2::Digest;\n+use sha2::Sha256;\n+use std::io;\n+use std::io::ErrorKind;\n+use std::net::SocketAddr;\n+use std::path::Path;\n+use std::path::PathBuf;\n+use time::OffsetDateTime;\n+\n+const DEFAULT_MAX_CLOCK_SKEW_SECONDS: u64 = 30;\n+const MIN_SIGNED_BEARER_SECRET_BYTES: usize = 32;\n+const INVALID_AUTHORIZATION_HEADER_MESSAGE: &str = \"invalid authorization header\";\n+\n+#[derive(Debug, Clone, Default, PartialEq, Eq, Args)]\n+pub struct AppServerWebsocketAuthArgs {\n+ /// Websocket auth ...", - "path": "codex-rs/app-server/src/transport/auth.rs", - "status": "added" - }, - { - "additions": 335, - "deletions": 40, - "patch_excerpt": "@@ -2,6 +2,8 @@ use anyhow::Context;\n use anyhow::Result;\n use anyhow::bail;\n use app_test_support::create_mock_responses_server_sequence_unchecked;\n+use base64::Engine;\n+use base64::engine::general_purpose::URL_SAFE_NO_PAD;\n use codex_app_server_protocol::ClientInfo;\n use codex_app_server_protocol::InitializeParams;\n use codex_app_server_protocol::JSONRPCError;\n@@ -12,12 +14,16 @@ use codex_app_server_protocol::JSONRPCResponse;\n use codex_app_server_protocol::RequestId;\n use futures::SinkExt;\n use futures::StreamExt;\n+use hmac::Hmac;\n+use hmac::Mac;\n use reqwest::StatusCode;\n use serde_json::json;\n+use sha2::Sha256;\n use std::net::SocketAddr;\n use std::path::Path;\n use std::process::Stdio;\n use tempfile::TempDir;\n+use time::OffsetDateTime;\n use tokio::io::AsyncBufReadExt;\n use tokio::io::BufReader;\n use tokio::process::Child;\n@@ -29,15 +35,17 @@ use tokio::time::timeout;\n use tokio_tung...", - "path": "codex-rs/app-server/tests/suite/v2/connection_handling_websocket.rs", - "status": "modified" - }, - { - "additions": 120, - "deletions": 42, - "patch_excerpt": "@@ -353,6 +353,9 @@ struct AppServerCommand {\n /// See https://developers.openai.com/codex/config-advanced/#metrics for more details.\n #[arg(long = \"analytics-default-enabled\")]\n analytics_default_enabled: bool,\n+\n+ #[command(flatten)]\n+ auth: codex_app_server::AppServerWebsocketAuthArgs,\n }\n \n #[derive(Debug, clap::Subcommand)]\n@@ -643,49 +646,59 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {\n prepend_config_flags(&mut mcp_cli.config_overrides, root_config_overrides.clone());\n mcp_cli.run().await?;\n }\n- Some(Subcommand::AppServer(app_server_cli)) => match app_server_cli.subcommand {\n- None => {\n- reject_remote_mode_for_subcommand(root_remote.as_deref(), \"app-server\")?;\n- let transport = app_server_cli.listen;\n- codex_app_server::run_main_with_tran...", - "path": "codex-rs/cli/src/main.rs", - "status": "modified" - } - ], - "linked_issues": [ - "#14847" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Discovered via hourly merged-PR sync: https://github.com/openai/codex/pull/14847" - ], - "primary_pr": { - "body": "## Summary\nThis change adds websocket authentication at the app-server transport boundary and enforces it before JSON-RPC `initialize`, so authenticated deployments reject unauthenticated clients during the websocket handshake rather than after a connection has already been admitted.\n\nDuring rollout, websocket auth is opt-in for non-loopback listeners so we do not break existing remote clients. If `--ws-auth ...` is configured, the server enforces auth during websocket upgrade. If auth is not configured, non-loopback listeners still start, but app-server logs a warning and the startup banner calls out that auth should be configured before real remote use.\n\nThe server supports two auth modes: a file-backed capability token, and a standard HMAC-signed JWT/JWS bearer token verified with the `jsonwebtoken` crate, with optional issuer, audience, and clock-skew validation. Capability tokens are normalized, hashed, and compared in constant time. Short shared secrets for signed bearer tokens are rejected at startup. Requests carrying an `Origin` header are rejected with `403` by transport middleware, and authenticated clients present credentials as `Authorization: Bearer ` during websocket upgrade.\n\n## Validation\n- `cargo test -p codex-app-server transport::auth`\n- `cargo test -p codex-cli app_server_`\n- `cargo clippy -p codex-app-server --all-targets -- -D warnings`\n- `just bazel-lock-check`\n\nNote: in the broad `cargo test -p codex-app-server connection_handling_websocket` run, the touched websocket auth cases passed, but unrelated Unix shutdown tests failed with a timeout in this environment.\n", - "labels": [], - "merged_at": "2026-03-25T19:35:57Z", - "number": 14847, - "state": "merged", - "title": "feat: add websocket auth for app-server", - "url": "https://github.com/openai/codex/pull/14847" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-14867.json b/artifacts/github/bundles/openai-codex-pr-14867.json deleted file mode 100644 index a8f118b..0000000 --- a/artifacts/github/bundles/openai-codex-pr-14867.json +++ /dev/null @@ -1,437 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "eternal-openai", - "committed_at": "2026-03-16T23:29:01Z", - "message": "use a user message > developer message for prompt continuation", - "sha": "9ad2b001f04b4cfb6dcd8f025d16e90d0a5ac99d", - "url": "https://github.com/openai/codex/commit/9ad2b001f04b4cfb6dcd8f025d16e90d0a5ac99d" - }, - { - "author": "eternal-openai", - "committed_at": "2026-03-17T00:33:54Z", - "message": "tui_app_server: handle hook prompt items", - "sha": "e837ce9e02e7dbe88702cdce33d727c089c0d246", - "url": "https://github.com/openai/codex/commit/e837ce9e02e7dbe88702cdce33d727c089c0d246" - }, - { - "author": "eternal-openai", - "committed_at": "2026-03-17T00:57:49Z", - "message": "codex: fix CI failure on PR #14867", - "sha": "94d66dc4212c9cb4ff4a58008908d6c5a235b579", - "url": "https://github.com/openai/codex/commit/94d66dc4212c9cb4ff4a58008908d6c5a235b579" - }, - { - "author": "eternal-openai", - "committed_at": "2026-03-17T19:08:21Z", - "message": "tui_app_server: fix hook prompt replay build after rebase", - "sha": "7b8c000a7305690d6d97e74f95edcac98333a5da", - "url": "https://github.com/openai/codex/commit/7b8c000a7305690d6d97e74f95edcac98333a5da" - }, - { - "author": "eternal-openai", - "committed_at": "2026-03-17T19:28:12Z", - "message": "tui_app_server: remove dead replay helpers", - "sha": "796438281e8661db8ea2bacc969500f85692b960", - "url": "https://github.com/openai/codex/commit/796438281e8661db8ea2bacc969500f85692b960" - }, - { - "author": "eternal-openai", - "committed_at": "2026-03-17T20:16:07Z", - "message": "remove unneeded hook_run_ids plural variant", - "sha": "ae25f38428b2f9ed3972bfdf98205903eae91e9c", - "url": "https://github.com/openai/codex/commit/ae25f38428b2f9ed3972bfdf98205903eae91e9c" - }, - { - "author": "eternal-openai", - "committed_at": "2026-03-17T20:41:44Z", - "message": "use quick-xml instead of hand roll", - "sha": "8824ebaab11b685d1e2442cc57eb04c07ac95044", - "url": "https://github.com/openai/codex/commit/8824ebaab11b685d1e2442cc57eb04c07ac95044" - }, - { - "author": "eternal-openai", - "committed_at": "2026-03-17T21:50:01Z", - "message": "tests: fix flaky CI expectations", - "sha": "8e59ce15d7f98aa146aa460f609627e065519ac0", - "url": "https://github.com/openai/codex/commit/8e59ce15d7f98aa146aa460f609627e065519ac0" - }, - { - "author": "eternal-openai", - "committed_at": "2026-03-17T22:12:15Z", - "message": "core: fix merge-ref auth telemetry compile", - "sha": "f664f40d73dfbf8bd696d46db27e538b702656f9", - "url": "https://github.com/openai/codex/commit/f664f40d73dfbf8bd696d46db27e538b702656f9" - }, - { - "author": "eternal-openai", - "committed_at": "2026-03-17T22:27:11Z", - "message": "Merge origin/main into user_message_xml", - "sha": "3c358c72af9c8b4ba8ab57cc3fbff9850915beb7", - "url": "https://github.com/openai/codex/commit/3c358c72af9c8b4ba8ab57cc3fbff9850915beb7" - }, - { - "author": "eternal-openai", - "committed_at": "2026-03-19T05:28:12Z", - "message": "Merge origin/main into user_message_xml", - "sha": "30dc1b0391338f77f7e34775599d8cb199d0825c", - "url": "https://github.com/openai/codex/commit/30dc1b0391338f77f7e34775599d8cb199d0825c" - }, - { - "author": "eternal-openai", - "committed_at": "2026-03-19T05:53:57Z", - "message": "Merge origin/main into user_message_xml", - "sha": "7f63b99841ec26e93d854a813cd017198dc16950", - "url": "https://github.com/openai/codex/commit/7f63b99841ec26e93d854a813cd017198dc16950" - }, - { - "author": "eternal-openai", - "committed_at": "2026-03-19T06:12:37Z", - "message": "tui_app_server: ignore hook prompt snapshot items", - "sha": "2518b8f728262fcb66eb8176fb569d1487d43559", - "url": "https://github.com/openai/codex/commit/2518b8f728262fcb66eb8176fb569d1487d43559" - }, - { - "author": "eternal-openai", - "committed_at": "2026-03-19T06:43:10Z", - "message": "app-server-protocol: refresh generated typescript schema", - "sha": "4034f28b8c45632cc2d53b25bedc0e45bf27fd5d", - "url": "https://github.com/openai/codex/commit/4034f28b8c45632cc2d53b25bedc0e45bf27fd5d" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "GENERATED", - "CODE", - "NOT", - "MODIFY", - "HAND", - "JSONRPCE", - "CHANNEL_CAPACITY", - "INPUT_TOO_LARGE_ERROR_CODE", - "INVALID_PARAMS_ERROR_CODE", - "CHILD_PROMPT", - "REQUESTED_MODEL", - "REQUESTED_REASONING_EFFORT", - "DEFAULT_READ_TIMEOUT", - "ROLE_MODEL", - "ROLE_REASONING_EFFORT", - "ENVIRONMENT_CONTEXT_CLOSE_TAG", - "CONTEXTUAL_USER_FRAGMENTS", - "SUBAGENT_NOTIFICATION_FRAGMENT", - "AGENTS_MD_FRAGMENT", - "SKILL_FRAGMENT", - "FIRST_CONTINUATION_PROMPT", - "SECOND_CONTINUATION_PROMPT" - ], - "files": [ - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -2346,6 +2346,7 @@ dependencies = [\n \"icu_locale_core\",\n \"icu_provider\",\n \"pretty_assertions\",\n+ \"quick-xml\",\n \"schemars 0.8.22\",\n \"serde\",\n \"serde_json\",\n@@ -7271,6 +7272,7 @@ source = \"registry+https://github.com/rust-lang/crates.io-index\"\n checksum = \"b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c\"\n dependencies = [\n \"memchr\",\n+ \"serde\",\n ]\n \n [[package]]", - "path": "codex-rs/Cargo.lock", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -233,6 +233,7 @@ portable-pty = \"0.9.0\"\n predicates = \"3\"\n pretty_assertions = \"1.4.1\"\n pulldown-cmark = \"0.10\"\n+quick-xml = \"0.38.4\"\n rand = \"0.9\"\n ratatui = \"0.29.0\"\n ratatui-macros = \"0.6.0\"", - "path": "codex-rs/Cargo.toml", - "status": "modified" - }, - { - "additions": 42, - "deletions": 0, - "patch_excerpt": "@@ -1201,6 +1201,21 @@\n ],\n \"type\": \"string\"\n },\n+ \"HookPromptFragment\": {\n+ \"properties\": {\n+ \"hookRunId\": {\n+ \"type\": \"string\"\n+ },\n+ \"text\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"hookRunId\",\n+ \"text\"\n+ ],\n+ \"type\": \"object\"\n+ },\n \"HookRunStatus\": {\n \"enum\": [\n \"running\",\n@@ -2244,6 +2259,33 @@\n \"title\": \"UserMessageThreadItem\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"fragments\": {\n+ \"items\": {\n+ \"$ref\": \"#/definitions/HookPromptFragment\"\n+ },\n+ \"type\": \"array\"\n+ },\n+ \"id\": {\n+ \"type\": \"string\"\n+ },\n+ \"type\": {\n+ \"enum\": [\n+ \"hookPrompt\"\n+ ],...", - "path": "codex-rs/app-server-protocol/schema/json/ServerNotification.json", - "status": "modified" - }, - { - "additions": 42, - "deletions": 0, - "patch_excerpt": "@@ -7971,6 +7971,21 @@\n ],\n \"type\": \"string\"\n },\n+ \"HookPromptFragment\": {\n+ \"properties\": {\n+ \"hookRunId\": {\n+ \"type\": \"string\"\n+ },\n+ \"text\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"hookRunId\",\n+ \"text\"\n+ ],\n+ \"type\": \"object\"\n+ },\n \"HookRunStatus\": {\n \"enum\": [\n \"running\",\n@@ -11941,6 +11956,33 @@\n \"title\": \"UserMessageThreadItem\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"fragments\": {\n+ \"items\": {\n+ \"$ref\": \"#/definitions/v2/HookPromptFragment\"\n+ },\n+ \"type\": \"array\"\n+ },\n+ \"id\": {\n+ \"type\": \"string\"\n+ },\n+ \"type...", - "path": "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json", - "status": "modified" - }, - { - "additions": 42, - "deletions": 0, - "patch_excerpt": "@@ -4715,6 +4715,21 @@\n ],\n \"type\": \"string\"\n },\n+ \"HookPromptFragment\": {\n+ \"properties\": {\n+ \"hookRunId\": {\n+ \"type\": \"string\"\n+ },\n+ \"text\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"hookRunId\",\n+ \"text\"\n+ ],\n+ \"type\": \"object\"\n+ },\n \"HookRunStatus\": {\n \"enum\": [\n \"running\",\n@@ -9701,6 +9716,33 @@\n \"title\": \"UserMessageThreadItem\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"fragments\": {\n+ \"items\": {\n+ \"$ref\": \"#/definitions/HookPromptFragment\"\n+ },\n+ \"type\": \"array\"\n+ },\n+ \"id\": {\n+ \"type\": \"string\"\n+ },\n+ \"type\": {\n+ \"enum\": [\n+ \"hookPrompt\"\n+ ],...", - "path": "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json", - "status": "modified" - }, - { - "additions": 42, - "deletions": 0, - "patch_excerpt": "@@ -266,6 +266,21 @@\n ],\n \"type\": \"object\"\n },\n+ \"HookPromptFragment\": {\n+ \"properties\": {\n+ \"hookRunId\": {\n+ \"type\": \"string\"\n+ },\n+ \"text\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"hookRunId\",\n+ \"text\"\n+ ],\n+ \"type\": \"object\"\n+ },\n \"McpToolCallError\": {\n \"properties\": {\n \"message\": {\n@@ -496,6 +511,33 @@\n \"title\": \"UserMessageThreadItem\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"fragments\": {\n+ \"items\": {\n+ \"$ref\": \"#/definitions/HookPromptFragment\"\n+ },\n+ \"type\": \"array\"\n+ },\n+ \"id\": {\n+ \"type\": \"string\"\n+ },\n+ \"type\": {\n+ \"enum\": [\n+ \"hookPrompt\"\n+ ...", - "path": "codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json", - "status": "modified" - }, - { - "additions": 42, - "deletions": 0, - "patch_excerpt": "@@ -266,6 +266,21 @@\n ],\n \"type\": \"object\"\n },\n+ \"HookPromptFragment\": {\n+ \"properties\": {\n+ \"hookRunId\": {\n+ \"type\": \"string\"\n+ },\n+ \"text\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"hookRunId\",\n+ \"text\"\n+ ],\n+ \"type\": \"object\"\n+ },\n \"McpToolCallError\": {\n \"properties\": {\n \"message\": {\n@@ -496,6 +511,33 @@\n \"title\": \"UserMessageThreadItem\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"fragments\": {\n+ \"items\": {\n+ \"$ref\": \"#/definitions/HookPromptFragment\"\n+ },\n+ \"type\": \"array\"\n+ },\n+ \"id\": {\n+ \"type\": \"string\"\n+ },\n+ \"type\": {\n+ \"enum\": [\n+ \"hookPrompt\"\n+ ...", - "path": "codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json", - "status": "modified" - }, - { - "additions": 42, - "deletions": 0, - "patch_excerpt": "@@ -380,6 +380,21 @@\n ],\n \"type\": \"object\"\n },\n+ \"HookPromptFragment\": {\n+ \"properties\": {\n+ \"hookRunId\": {\n+ \"type\": \"string\"\n+ },\n+ \"text\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"hookRunId\",\n+ \"text\"\n+ ],\n+ \"type\": \"object\"\n+ },\n \"McpToolCallError\": {\n \"properties\": {\n \"message\": {\n@@ -610,6 +625,33 @@\n \"title\": \"UserMessageThreadItem\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"fragments\": {\n+ \"items\": {\n+ \"$ref\": \"#/definitions/HookPromptFragment\"\n+ },\n+ \"type\": \"array\"\n+ },\n+ \"id\": {\n+ \"type\": \"string\"\n+ },\n+ \"type\": {\n+ \"enum\": [\n+ \"hookPrompt\"\n+ ...", - "path": "codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json", - "status": "modified" - }, - { - "additions": 42, - "deletions": 0, - "patch_excerpt": "@@ -465,6 +465,21 @@\n },\n \"type\": \"object\"\n },\n+ \"HookPromptFragment\": {\n+ \"properties\": {\n+ \"hookRunId\": {\n+ \"type\": \"string\"\n+ },\n+ \"text\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"hookRunId\",\n+ \"text\"\n+ ],\n+ \"type\": \"object\"\n+ },\n \"McpToolCallError\": {\n \"properties\": {\n \"message\": {\n@@ -1090,6 +1105,33 @@\n \"title\": \"UserMessageThreadItem\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"fragments\": {\n+ \"items\": {\n+ \"$ref\": \"#/definitions/HookPromptFragment\"\n+ },\n+ \"type\": \"array\"\n+ },\n+ \"id\": {\n+ \"type\": \"string\"\n+ },\n+ \"type\": {\n+ \"enum\": [\n+ \"hookPrompt\"\n+ ...", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json", - "status": "modified" - }, - { - "additions": 42, - "deletions": 0, - "patch_excerpt": "@@ -403,6 +403,21 @@\n },\n \"type\": \"object\"\n },\n+ \"HookPromptFragment\": {\n+ \"properties\": {\n+ \"hookRunId\": {\n+ \"type\": \"string\"\n+ },\n+ \"text\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"hookRunId\",\n+ \"text\"\n+ ],\n+ \"type\": \"object\"\n+ },\n \"McpToolCallError\": {\n \"properties\": {\n \"message\": {\n@@ -848,6 +863,33 @@\n \"title\": \"UserMessageThreadItem\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"fragments\": {\n+ \"items\": {\n+ \"$ref\": \"#/definitions/HookPromptFragment\"\n+ },\n+ \"type\": \"array\"\n+ },\n+ \"id\": {\n+ \"type\": \"string\"\n+ },\n+ \"type\": {\n+ \"enum\": [\n+ \"hookPrompt\"\n+ ...", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json", - "status": "modified" - }, - { - "additions": 42, - "deletions": 0, - "patch_excerpt": "@@ -403,6 +403,21 @@\n },\n \"type\": \"object\"\n },\n+ \"HookPromptFragment\": {\n+ \"properties\": {\n+ \"hookRunId\": {\n+ \"type\": \"string\"\n+ },\n+ \"text\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"hookRunId\",\n+ \"text\"\n+ ],\n+ \"type\": \"object\"\n+ },\n \"McpToolCallError\": {\n \"properties\": {\n \"message\": {\n@@ -848,6 +863,33 @@\n \"title\": \"UserMessageThreadItem\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"fragments\": {\n+ \"items\": {\n+ \"$ref\": \"#/definitions/HookPromptFragment\"\n+ },\n+ \"type\": \"array\"\n+ },\n+ \"id\": {\n+ \"type\": \"string\"\n+ },\n+ \"type\": {\n+ \"enum\": [\n+ \"hookPrompt\"\n+ ...", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json", - "status": "modified" - }, - { - "additions": 42, - "deletions": 0, - "patch_excerpt": "@@ -403,6 +403,21 @@\n },\n \"type\": \"object\"\n },\n+ \"HookPromptFragment\": {\n+ \"properties\": {\n+ \"hookRunId\": {\n+ \"type\": \"string\"\n+ },\n+ \"text\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"hookRunId\",\n+ \"text\"\n+ ],\n+ \"type\": \"object\"\n+ },\n \"McpToolCallError\": {\n \"properties\": {\n \"message\": {\n@@ -848,6 +863,33 @@\n \"title\": \"UserMessageThreadItem\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"fragments\": {\n+ \"items\": {\n+ \"$ref\": \"#/definitions/HookPromptFragment\"\n+ },\n+ \"type\": \"array\"\n+ },\n+ \"id\": {\n+ \"type\": \"string\"\n+ },\n+ \"type\": {\n+ \"enum\": [\n+ \"hookPrompt\"\n+ ...", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json", - "status": "modified" - }, - { - "additions": 42, - "deletions": 0, - "patch_excerpt": "@@ -465,6 +465,21 @@\n },\n \"type\": \"object\"\n },\n+ \"HookPromptFragment\": {\n+ \"properties\": {\n+ \"hookRunId\": {\n+ \"type\": \"string\"\n+ },\n+ \"text\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"hookRunId\",\n+ \"text\"\n+ ],\n+ \"type\": \"object\"\n+ },\n \"McpToolCallError\": {\n \"properties\": {\n \"message\": {\n@@ -1090,6 +1105,33 @@\n \"title\": \"UserMessageThreadItem\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"fragments\": {\n+ \"items\": {\n+ \"$ref\": \"#/definitions/HookPromptFragment\"\n+ },\n+ \"type\": \"array\"\n+ },\n+ \"id\": {\n+ \"type\": \"string\"\n+ },\n+ \"type\": {\n+ \"enum\": [\n+ \"hookPrompt\"\n+ ...", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json", - "status": "modified" - }, - { - "additions": 42, - "deletions": 0, - "patch_excerpt": "@@ -403,6 +403,21 @@\n },\n \"type\": \"object\"\n },\n+ \"HookPromptFragment\": {\n+ \"properties\": {\n+ \"hookRunId\": {\n+ \"type\": \"string\"\n+ },\n+ \"text\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"hookRunId\",\n+ \"text\"\n+ ],\n+ \"type\": \"object\"\n+ },\n \"McpToolCallError\": {\n \"properties\": {\n \"message\": {\n@@ -848,6 +863,33 @@\n \"title\": \"UserMessageThreadItem\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"fragments\": {\n+ \"items\": {\n+ \"$ref\": \"#/definitions/HookPromptFragment\"\n+ },\n+ \"type\": \"array\"\n+ },\n+ \"id\": {\n+ \"type\": \"string\"\n+ },\n+ \"type\": {\n+ \"enum\": [\n+ \"hookPrompt\"\n+ ...", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json", - "status": "modified" - }, - { - "additions": 42, - "deletions": 0, - "patch_excerpt": "@@ -465,6 +465,21 @@\n },\n \"type\": \"object\"\n },\n+ \"HookPromptFragment\": {\n+ \"properties\": {\n+ \"hookRunId\": {\n+ \"type\": \"string\"\n+ },\n+ \"text\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"hookRunId\",\n+ \"text\"\n+ ],\n+ \"type\": \"object\"\n+ },\n \"McpToolCallError\": {\n \"properties\": {\n \"message\": {\n@@ -1090,6 +1105,33 @@\n \"title\": \"UserMessageThreadItem\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"fragments\": {\n+ \"items\": {\n+ \"$ref\": \"#/definitions/HookPromptFragment\"\n+ },\n+ \"type\": \"array\"\n+ },\n+ \"id\": {\n+ \"type\": \"string\"\n+ },\n+ \"type\": {\n+ \"enum\": [\n+ \"hookPrompt\"\n+ ...", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json", - "status": "modified" - }, - { - "additions": 42, - "deletions": 0, - "patch_excerpt": "@@ -403,6 +403,21 @@\n },\n \"type\": \"object\"\n },\n+ \"HookPromptFragment\": {\n+ \"properties\": {\n+ \"hookRunId\": {\n+ \"type\": \"string\"\n+ },\n+ \"text\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"hookRunId\",\n+ \"text\"\n+ ],\n+ \"type\": \"object\"\n+ },\n \"McpToolCallError\": {\n \"properties\": {\n \"message\": {\n@@ -848,6 +863,33 @@\n \"title\": \"UserMessageThreadItem\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"fragments\": {\n+ \"items\": {\n+ \"$ref\": \"#/definitions/HookPromptFragment\"\n+ },\n+ \"type\": \"array\"\n+ },\n+ \"id\": {\n+ \"type\": \"string\"\n+ },\n+ \"type\": {\n+ \"enum\": [\n+ \"hookPrompt\"\n+ ...", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json", - "status": "modified" - }, - { - "additions": 42, - "deletions": 0, - "patch_excerpt": "@@ -403,6 +403,21 @@\n },\n \"type\": \"object\"\n },\n+ \"HookPromptFragment\": {\n+ \"properties\": {\n+ \"hookRunId\": {\n+ \"type\": \"string\"\n+ },\n+ \"text\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"hookRunId\",\n+ \"text\"\n+ ],\n+ \"type\": \"object\"\n+ },\n \"McpToolCallError\": {\n \"properties\": {\n \"message\": {\n@@ -848,6 +863,33 @@\n \"title\": \"UserMessageThreadItem\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"fragments\": {\n+ \"items\": {\n+ \"$ref\": \"#/definitions/HookPromptFragment\"\n+ },\n+ \"type\": \"array\"\n+ },\n+ \"id\": {\n+ \"type\": \"string\"\n+ },\n+ \"type\": {\n+ \"enum\": [\n+ \"hookPrompt\"\n+ ...", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json", - "status": "modified" - }, - { - "additions": 42, - "deletions": 0, - "patch_excerpt": "@@ -380,6 +380,21 @@\n ],\n \"type\": \"object\"\n },\n+ \"HookPromptFragment\": {\n+ \"properties\": {\n+ \"hookRunId\": {\n+ \"type\": \"string\"\n+ },\n+ \"text\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"hookRunId\",\n+ \"text\"\n+ ],\n+ \"type\": \"object\"\n+ },\n \"McpToolCallError\": {\n \"properties\": {\n \"message\": {\n@@ -610,6 +625,33 @@\n \"title\": \"UserMessageThreadItem\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"fragments\": {\n+ \"items\": {\n+ \"$ref\": \"#/definitions/HookPromptFragment\"\n+ },\n+ \"type\": \"array\"\n+ },\n+ \"id\": {\n+ \"type\": \"string\"\n+ },\n+ \"type\": {\n+ \"enum\": [\n+ \"hookPrompt\"\n+ ...", - "path": "codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json", - "status": "modified" - }, - { - "additions": 42, - "deletions": 0, - "patch_excerpt": "@@ -380,6 +380,21 @@\n ],\n \"type\": \"object\"\n },\n+ \"HookPromptFragment\": {\n+ \"properties\": {\n+ \"hookRunId\": {\n+ \"type\": \"string\"\n+ },\n+ \"text\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"hookRunId\",\n+ \"text\"\n+ ],\n+ \"type\": \"object\"\n+ },\n \"McpToolCallError\": {\n \"properties\": {\n \"message\": {\n@@ -610,6 +625,33 @@\n \"title\": \"UserMessageThreadItem\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"fragments\": {\n+ \"items\": {\n+ \"$ref\": \"#/definitions/HookPromptFragment\"\n+ },\n+ \"type\": \"array\"\n+ },\n+ \"id\": {\n+ \"type\": \"string\"\n+ },\n+ \"type\": {\n+ \"enum\": [\n+ \"hookPrompt\"\n+ ...", - "path": "codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json", - "status": "modified" - }, - { - "additions": 42, - "deletions": 0, - "patch_excerpt": "@@ -380,6 +380,21 @@\n ],\n \"type\": \"object\"\n },\n+ \"HookPromptFragment\": {\n+ \"properties\": {\n+ \"hookRunId\": {\n+ \"type\": \"string\"\n+ },\n+ \"text\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"hookRunId\",\n+ \"text\"\n+ ],\n+ \"type\": \"object\"\n+ },\n \"McpToolCallError\": {\n \"properties\": {\n \"message\": {\n@@ -610,6 +625,33 @@\n \"title\": \"UserMessageThreadItem\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"fragments\": {\n+ \"items\": {\n+ \"$ref\": \"#/definitions/HookPromptFragment\"\n+ },\n+ \"type\": \"array\"\n+ },\n+ \"id\": {\n+ \"type\": \"string\"\n+ },\n+ \"type\": {\n+ \"enum\": [\n+ \"hookPrompt\"\n+ ...", - "path": "codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json", - "status": "modified" - }, - { - "additions": 5, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,5 @@\n+// GENERATED CODE! DO NOT MODIFY BY HAND!\n+\n+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n+\n+export type HookPromptFragment = { text: string, hookRunId: string, };", - "path": "codex-rs/app-server-protocol/schema/typescript/v2/HookPromptFragment.ts", - "status": "added" - }, - { - "additions": 2, - "deletions": 1, - "patch_excerpt": "@@ -13,6 +13,7 @@ import type { CommandExecutionStatus } from \"./CommandExecutionStatus\";\n import type { DynamicToolCallOutputContentItem } from \"./DynamicToolCallOutputContentItem\";\n import type { DynamicToolCallStatus } from \"./DynamicToolCallStatus\";\n import type { FileUpdateChange } from \"./FileUpdateChange\";\n+import type { HookPromptFragment } from \"./HookPromptFragment\";\n import type { McpToolCallError } from \"./McpToolCallError\";\n import type { McpToolCallResult } from \"./McpToolCallResult\";\n import type { McpToolCallStatus } from \"./McpToolCallStatus\";\n@@ -21,7 +22,7 @@ import type { PatchApplyStatus } from \"./PatchApplyStatus\";\n import type { UserInput } from \"./UserInput\";\n import type { WebSearchAction } from \"./WebSearchAction\";\n \n-export type ThreadItem = { \"type\": \"userMessage\", id: string, content: Array, } | { \"type\": \"agentMessage\", id: string, text: string, p...", - "path": "codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -126,6 +126,7 @@ export type { HookExecutionMode } from \"./HookExecutionMode\";\n export type { HookHandlerType } from \"./HookHandlerType\";\n export type { HookOutputEntry } from \"./HookOutputEntry\";\n export type { HookOutputEntryKind } from \"./HookOutputEntryKind\";\n+export type { HookPromptFragment } from \"./HookPromptFragment\";\n export type { HookRunStatus } from \"./HookRunStatus\";\n export type { HookRunSummary } from \"./HookRunSummary\";\n export type { HookScope } from \"./HookScope\";", - "path": "codex-rs/app-server-protocol/schema/typescript/v2/index.ts", - "status": "modified" - }, - { - "additions": 109, - "deletions": 3, - "patch_excerpt": "@@ -18,6 +18,7 @@ use crate::protocol::v2::TurnError;\n use crate::protocol::v2::TurnStatus;\n use crate::protocol::v2::UserInput;\n use crate::protocol::v2::WebSearchAction;\n+use codex_protocol::items::parse_hook_prompt_message;\n use codex_protocol::models::MessagePhase;\n use codex_protocol::protocol::AgentReasoningEvent;\n use codex_protocol::protocol::AgentReasoningRawContentEvent;\n@@ -184,12 +185,37 @@ impl ThreadHistoryBuilder {\n match item {\n RolloutItem::EventMsg(event) => self.handle_event(event),\n RolloutItem::Compacted(payload) => self.handle_compacted(payload),\n- RolloutItem::TurnContext(_)\n- | RolloutItem::SessionMeta(_)\n- | RolloutItem::ResponseItem(_) => {}\n+ RolloutItem::ResponseItem(item) => self.handle_response_item(item),\n+ RolloutItem::TurnContext(_) | RolloutItem::SessionMeta(_) => {}\n ...", - "path": "codex-rs/app-server-protocol/src/protocol/thread_history.rs", - "status": "modified" - }, - { - "additions": 32, - "deletions": 0, - "patch_excerpt": "@@ -4124,6 +4124,12 @@ pub enum ThreadItem {\n UserMessage { id: String, content: Vec },\n #[serde(rename_all = \"camelCase\")]\n #[ts(rename_all = \"camelCase\")]\n+ HookPrompt {\n+ id: String,\n+ fragments: Vec,\n+ },\n+ #[serde(rename_all = \"camelCase\")]\n+ #[ts(rename_all = \"camelCase\")]\n AgentMessage {\n id: String,\n text: String,\n@@ -4257,10 +4263,19 @@ pub enum ThreadItem {\n ContextCompaction { id: String },\n }\n \n+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]\n+#[serde(rename_all = \"camelCase\")]\n+#[ts(rename_all = \"camelCase\", export_to = \"v2/\")]\n+pub struct HookPromptFragment {\n+ pub text: String,\n+ pub hook_run_id: String,\n+}\n+\n impl ThreadItem {\n pub fn id(&self) -> &str {\n match self {\n ThreadItem::UserMessage { id, .. }\n+ | Th...", - "path": "codex-rs/app-server-protocol/src/protocol/v2.rs", - "status": "modified" - }, - { - "additions": 109, - "deletions": 0, - "patch_excerpt": "@@ -111,6 +111,7 @@ use codex_core::sandboxing::intersect_permission_profiles;\n use codex_protocol::ThreadId;\n use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem as CoreDynamicToolCallOutputContentItem;\n use codex_protocol::dynamic_tools::DynamicToolResponse as CoreDynamicToolResponse;\n+use codex_protocol::items::parse_hook_prompt_message;\n use codex_protocol::plan_tool::UpdatePlanArgs;\n use codex_protocol::protocol::ApplyPatchApprovalRequestEvent;\n use codex_protocol::protocol::CodexErrorInfo as CoreCodexErrorInfo;\n@@ -1484,6 +1485,14 @@ pub(crate) async fn apply_bespoke_event_handling(\n .await;\n }\n EventMsg::RawResponseItem(raw_response_item_event) => {\n+ maybe_emit_hook_prompt_item_completed(\n+ api_version,\n+ conversation_id,\n+ &event_turn_id,\n+ &raw_response_item_ev...", - "path": "codex-rs/app-server/src/bespoke_event_handling.rs", - "status": "modified" - }, - { - "additions": 22, - "deletions": 19, - "patch_excerpt": "@@ -13,7 +13,6 @@ use codex_app_server::INPUT_TOO_LARGE_ERROR_CODE;\n use codex_app_server::INVALID_PARAMS_ERROR_CODE;\n use codex_app_server_protocol::ByteRange;\n use codex_app_server_protocol::ClientInfo;\n-use codex_app_server_protocol::CollabAgentState;\n use codex_app_server_protocol::CollabAgentStatus;\n use codex_app_server_protocol::CollabAgentTool;\n use codex_app_server_protocol::CollabAgentToolCallStatus;\n@@ -1826,16 +1825,18 @@ async fn turn_start_emits_spawn_agent_item_with_model_metadata_v2() -> Result<()\n assert_eq!(prompt, Some(CHILD_PROMPT.to_string()));\n assert_eq!(model, Some(REQUESTED_MODEL.to_string()));\n assert_eq!(reasoning_effort, Some(REQUESTED_REASONING_EFFORT));\n- assert_eq!(\n- agents_states,\n- HashMap::from([(\n- receiver_thread_id,\n- CollabAgentState {\n- status: CollabAgentStatus::PendingInit,\n- ...", - "path": "codex-rs/app-server/tests/suite/v2/turn_start.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 4, - "patch_excerpt": "@@ -87,6 +87,7 @@ use codex_protocol::dynamic_tools::DynamicToolSpec;\n use codex_protocol::items::PlanItem;\n use codex_protocol::items::TurnItem;\n use codex_protocol::items::UserMessageItem;\n+use codex_protocol::items::build_hook_prompt_message;\n use codex_protocol::mcp::CallToolResult;\n use codex_protocol::models::BaseInstructions;\n use codex_protocol::models::PermissionProfile;\n@@ -5775,13 +5776,12 @@ pub(crate) async fn run_turn(\n .await;\n }\n if stop_outcome.should_block {\n- if let Some(continuation_prompt) = stop_outcome.continuation_prompt.clone()\n+ if let Some(hook_prompt_message) =\n+ build_hook_prompt_message(&stop_outcome.continuation_fragments)\n {\n- let developer_message: ResponseItem =\n- ...", - "path": "codex-rs/core/src/codex.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -196,7 +196,7 @@ pub(crate) async fn process_compacted_history(\n /// - `developer` messages because remote output can include stale/duplicated\n /// instruction content.\n /// - non-user-content `user` messages (session prefix/instruction wrappers),\n-/// keeping only real user messages as parsed by `parse_turn_item`.\n+/// while preserving real user messages and persisted hook prompts.\n ///\n /// This intentionally keeps:\n /// - `assistant` messages (future remote compaction models may emit them)\n@@ -208,7 +208,7 @@ fn should_keep_compacted_history_item(item: &ResponseItem) -> bool {\n ResponseItem::Message { role, .. } if role == \"user\" => {\n matches!(\n crate::event_mapping::parse_turn_item(item),\n- Some(TurnItem::UserMessage(_))\n+ Some(TurnItem::UserMessage(_) | TurnItem::HookPrompt(_))\n )\n }\n ...", - "path": "codex-rs/core/src/compact_remote.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 8, - "patch_excerpt": "@@ -177,8 +177,7 @@ impl ContextManager {\n /// Returns true when a tool image was replaced, false otherwise.\n pub(crate) fn replace_last_turn_images(&mut self, placeholder: &str) -> bool {\n let Some(index) = self.items.iter().rposition(|item| {\n- matches!(item, ResponseItem::FunctionCallOutput { .. })\n- || matches!(item, ResponseItem::Message { role, .. } if role == \"user\")\n+ matches!(item, ResponseItem::FunctionCallOutput { .. }) || is_user_turn_boundary(item)\n }) else {\n return false;\n };\n@@ -200,7 +199,7 @@ impl ContextManager {\n }\n replaced\n }\n- ResponseItem::Message { role, .. } if role == \"user\" => false,\n+ ResponseItem::Message { .. } => false,\n _ => false,\n }\n }\n@@ -250,11 +249,7 @@ impl ContextManager {\n \n ...", - "path": "codex-rs/core/src/context_manager/history.rs", - "status": "modified" - }, - { - "additions": 37, - "deletions": 4, - "patch_excerpt": "@@ -1,3 +1,5 @@\n+use codex_protocol::items::HookPromptItem;\n+use codex_protocol::items::parse_hook_prompt_fragment;\n use codex_protocol::models::ContentItem;\n use codex_protocol::models::ResponseItem;\n use codex_protocol::protocol::ENVIRONMENT_CONTEXT_CLOSE_TAG;\n@@ -94,10 +96,7 @@ const CONTEXTUAL_USER_FRAGMENTS: &[ContextualUserFragmentDefinition] = &[\n SUBAGENT_NOTIFICATION_FRAGMENT,\n ];\n \n-pub(crate) fn is_contextual_user_fragment(content_item: &ContentItem) -> bool {\n- let ContentItem::InputText { text } = content_item else {\n- return false;\n- };\n+fn is_standard_contextual_user_text(text: &str) -> bool {\n CONTEXTUAL_USER_FRAGMENTS\n .iter()\n .any(|definition| definition.matches_text(text))\n@@ -118,6 +117,40 @@ pub(crate) fn is_memory_excluded_contextual_user_fragment(content_item: &Content\n AGENTS_MD_FRAGMENT.matches_text(text) || SKILL_FRAGMEN...", - "path": "codex-rs/core/src/contextual_user_message.rs", - "status": "modified" - }, - { - "additions": 35, - "deletions": 0, - "patch_excerpt": "@@ -1,4 +1,6 @@\n use super::*;\n+use codex_protocol::items::HookPromptFragment;\n+use codex_protocol::items::build_hook_prompt_message;\n \n #[test]\n fn detects_environment_context_fragment() {\n@@ -61,3 +63,36 @@ fn classifies_memory_excluded_fragments() {\n );\n }\n }\n+\n+#[test]\n+fn detects_hook_prompt_fragment_and_roundtrips_escaping() {\n+ let message = build_hook_prompt_message(&[HookPromptFragment::from_single_hook(\n+ r#\"Retry with \"waves\" & \"#,\n+ \"hook-run-1\",\n+ )])\n+ .expect(\"hook prompt message\");\n+\n+ let ResponseItem::Message { content, .. } = message else {\n+ panic!(\"expected hook prompt response item\");\n+ };\n+\n+ let [content_item] = content.as_slice() else {\n+ panic!(\"expected a single content item\");\n+ };\n+\n+ assert!(is_contextual_user_fragment(content_item));\n+\n+ let ContentItem::InputText { text } = content_i...", - "path": "codex-rs/core/src/contextual_user_message_tests.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 1, - "patch_excerpt": "@@ -19,6 +19,7 @@ use tracing::warn;\n use uuid::Uuid;\n \n use crate::contextual_user_message::is_contextual_user_fragment;\n+use crate::contextual_user_message::parse_visible_hook_prompt_message;\n use crate::web_search::web_search_action_detail;\n \n pub(crate) fn is_contextual_user_message_content(message: &[ContentItem]) -> bool {\n@@ -100,7 +101,9 @@ pub fn parse_turn_item(item: &ResponseItem) -> Option {\n phase,\n ..\n } => match role.as_str() {\n- \"user\" => parse_user_message(content).map(TurnItem::UserMessage),\n+ \"user\" => parse_visible_hook_prompt_message(id.as_ref(), content)\n+ .map(TurnItem::HookPrompt)\n+ .or_else(|| parse_user_message(content).map(TurnItem::UserMessage)),\n \"assistant\" => Some(TurnItem::AgentMessage(parse_agent_message(\n id.as_ref(),\n ...", - "path": "codex-rs/core/src/event_mapping.rs", - "status": "modified" - }, - { - "additions": 63, - "deletions": 0, - "patch_excerpt": "@@ -1,7 +1,9 @@\n use super::parse_turn_item;\n use codex_protocol::items::AgentMessageContent;\n+use codex_protocol::items::HookPromptFragment;\n use codex_protocol::items::TurnItem;\n use codex_protocol::items::WebSearchItem;\n+use codex_protocol::items::build_hook_prompt_message;\n use codex_protocol::models::ContentItem;\n use codex_protocol::models::ReasoningItemContent;\n use codex_protocol::models::ReasoningItemReasoningSummary;\n@@ -208,6 +210,67 @@ fn skips_user_instructions_and_env() {\n }\n }\n \n+#[test]\n+fn parses_hook_prompt_message_as_distinct_turn_item() {\n+ let item = build_hook_prompt_message(&[HookPromptFragment::from_single_hook(\n+ \"Retry with exactly the phrase meow meow meow.\",\n+ \"hook-run-1\",\n+ )])\n+ .expect(\"hook prompt message\");\n+\n+ let turn_item = parse_turn_item(&item).expect(\"expected hook prompt turn item\");\n+\n+ match turn_item {\n+ ...", - "path": "codex-rs/core/src/event_mapping_tests.rs", - "status": "modified" - }, - { - "additions": 143, - "deletions": 28, - "patch_excerpt": "@@ -4,6 +4,7 @@ use std::path::Path;\n use anyhow::Context;\n use anyhow::Result;\n use codex_core::features::Feature;\n+use codex_protocol::items::parse_hook_prompt_fragment;\n use codex_protocol::models::ContentItem;\n use codex_protocol::models::ResponseItem;\n use codex_protocol::protocol::EventMsg;\n@@ -82,6 +83,48 @@ else:\n Ok(())\n }\n \n+fn write_parallel_stop_hooks(home: &Path, prompts: &[&str]) -> Result<()> {\n+ let hook_entries = prompts\n+ .iter()\n+ .enumerate()\n+ .map(|(index, prompt)| {\n+ let script_path = home.join(format!(\"stop_hook_{index}.py\"));\n+ let script = format!(\n+ r#\"import json\n+import sys\n+\n+payload = json.load(sys.stdin)\n+if payload[\"stop_hook_active\"]:\n+ print(json.dumps({{\"systemMessage\": \"done\"}}))\n+else:\n+ print(json.dumps({{\"decision\": \"block\", \"reason\": {prompt:?}}}))\n+\"#\n+ );\n+ ...", - "path": "codex-rs/core/tests/suite/hooks.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -683,6 +683,8 @@ async fn remote_models_do_not_append_removed_builtin_presets() -> Result<()> {\n 1,\n \"expected a single /models request\"\n );\n+ // Keep the mock server alive until after async assertions complete.\n+ drop(server);\n \n Ok(())\n }", - "path": "codex-rs/core/tests/suite/remote_models.rs", - "status": "modified" - }, - { - "additions": 44, - "deletions": 21, - "patch_excerpt": "@@ -1,6 +1,7 @@\n use std::path::PathBuf;\n \n use codex_protocol::ThreadId;\n+use codex_protocol::items::HookPromptFragment;\n use codex_protocol::protocol::HookCompletedEvent;\n use codex_protocol::protocol::HookEventName;\n use codex_protocol::protocol::HookOutputEntry;\n@@ -36,7 +37,7 @@ pub struct StopOutcome {\n pub stop_reason: Option,\n pub should_block: bool,\n pub block_reason: Option,\n- pub continuation_prompt: Option,\n+ pub continuation_fragments: Vec,\n }\n \n #[derive(Debug, Default, PartialEq, Eq)]\n@@ -45,7 +46,7 @@ struct StopHandlerData {\n stop_reason: Option,\n should_block: bool,\n block_reason: Option,\n- continuation_prompt: Option,\n+ continuation_fragments: Vec,\n }\n \n pub(crate) fn preview(\n@@ -72,7 +73,7 @@ pub(crate) async fn run(\n stop_reason: No...", - "path": "codex-rs/hooks/src/events/stop.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -19,6 +19,7 @@ codex-utils-image = { workspace = true }\n icu_decimal = { workspace = true }\n icu_locale_core = { workspace = true }\n icu_provider = { workspace = true, features = [\"sync\"] }\n+quick-xml = { workspace = true, features = [\"serialize\"] }\n schemars = { workspace = true }\n serde = { workspace = true, features = [\"derive\"] }\n serde_json = { workspace = true }", - "path": "codex-rs/protocol/Cargo.toml", - "status": "modified" - }, - { - "additions": 153, - "deletions": 0, - "patch_excerpt": "@@ -1,5 +1,7 @@\n use crate::memory_citation::MemoryCitation;\n+use crate::models::ContentItem;\n use crate::models::MessagePhase;\n+use crate::models::ResponseItem;\n use crate::models::WebSearchAction;\n use crate::protocol::AgentMessageEvent;\n use crate::protocol::AgentReasoningEvent;\n@@ -12,6 +14,8 @@ use crate::protocol::WebSearchEndEvent;\n use crate::user_input::ByteRange;\n use crate::user_input::TextElement;\n use crate::user_input::UserInput;\n+use quick_xml::de::from_str as from_xml_str;\n+use quick_xml::se::to_string as to_xml_string;\n use schemars::JsonSchema;\n use serde::Deserialize;\n use serde::Serialize;\n@@ -22,6 +26,7 @@ use ts_rs::TS;\n #[ts(tag = \"type\")]\n pub enum TurnItem {\n UserMessage(UserMessageItem),\n+ HookPrompt(HookPromptItem),\n AgentMessage(AgentMessageItem),\n Plan(PlanItem),\n Reasoning(ReasoningItem),\n@@ -36,6 +41,29 @@ pub struct UserMessageItem {\n ...", - "path": "codex-rs/protocol/src/items.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -870,6 +870,7 @@ fn turn_snapshot_events(\n }),\n );\n }\n+ TurnItem::HookPrompt(_) => {}\n }\n }\n \n@@ -1010,6 +1011,7 @@ fn thread_item_to_core(item: &ThreadItem) -> Option {\n | ThreadItem::McpToolCall { .. }\n | ThreadItem::DynamicToolCall { .. }\n | ThreadItem::CollabAgentToolCall { .. }\n+ | ThreadItem::HookPrompt { .. }\n | ThreadItem::ImageView { .. }\n | ThreadItem::EnteredReviewMode { .. }\n | ThreadItem::ExitedReviewMode { .. } => {", - "path": "codex-rs/tui_app_server/src/app/app_server_adapter.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -5733,6 +5733,7 @@ impl ChatWidget {\n ThreadItem::ContextCompaction { .. } => {\n self.on_agent_message(\"Context compacted\".to_owned());\n }\n+ ThreadItem::HookPrompt { .. } => {}\n ThreadItem::CollabAgentToolCall {\n id,\n tool,", - "path": "codex-rs/tui_app_server/src/chatwidget.rs", - "status": "modified" - } - ], - "linked_issues": [ - "#14867" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "## Summary\r\n\r\nPersist Stop-hook continuation prompts as `user` messages instead of hidden `developer` messages + some requested integration tests\r\n\r\nThis is a followup to @pakrym 's comment in https://github.com/openai/codex/pull/14532 to make sure stop-block continuation prompts match training for turn loops\r\n\r\n- Stop continuation now writes `stop hook's user prompt` \r\n- Introduces quick-xml dependency, though we already indirectly depended on it anyway via syntect\r\n- This PR only has about 500 lines of actual logic changes, the rest is tests/schema\r\n\r\n## Testing\r\n\r\nExample run (with a sessionstart hook and 3 stop hooks) - this shows context added by session start, then two stop hooks sending their own additional prompts in a new turn. The model responds with a single message addressing both. Then when that turn ends, the hooks detect that they just ran using `stop_hook_active` and decide not to infinite loop\r\n\r\ntest files for this (unzip, move codex -> .codex): [codex.zip](https://github.com/user-attachments/files/26075806/codex.zip)\r\n\r\n```\r\n\u203a cats\r\n\r\n\r\n\u2022 Running SessionStart hook: lighting the observatory\r\n\r\nSessionStart hook (completed)\r\n warning: Hi, I'm a session start hook for wizard-tower (startup).\r\n hook context: A wimboltine stonpet is an exotic cuisine from hyperspace\r\n\r\n\u2022 Cats are tiny zen wizards, my friend: equal parts nap, mystery, and chaos. If you want, we can talk\r\n cat facts, cat breeds, cat names, or build something cat-themed in this repo.\r\n\r\n\u2022 Running Stop hook: checking the tower wards\r\n\r\n\u2022 Running Stop hook: sacking the guards\r\n\r\n\u2022 Running Stop hook: hiring the guards\r\n\r\nStop hook (completed)\r\n warning: Wizard Tower Stop hook reviewed the completed reply (177 chars).\r\n\r\nStop hook (blocked)\r\n warning: Wizard Tower Stop hook continuing conversation\r\n feedback: cook the stonpet\r\n\r\nStop hook (blocked)\r\n warning: Wizard Tower Stop hook continuing conversation\r\n feedback: eat the cooked stonpet\r\n\r\n\u2022 Stonpet\u2019s cooked, aloha style: flash-seared over a blue quasiflame, glazed with nebula salt, and\r\n rested until the hyperspace juices settle.\r\n\r\n Now we eat with gratitude, my friend. One mindful bite in, and the flavor is pure cosmic surf:\r\n smoky, bright, and totally out of this dimension.\r\n\r\n\u2022 Running Stop hook: checking the tower wards\r\n\r\n\u2022 Running Stop hook: sacking the guards\r\n\r\n\u2022 Running Stop hook: hiring the guards\r\n\r\nStop hook (completed)\r\n warning: Wizard Tower Stop hook reviewed the completed reply (285 chars).\r\n\r\nStop hook (completed)\r\n warning: Wizard Tower Stop hook saw a second pass and stayed calm to avoid a loop.\r\n\r\nStop hook (completed)\r\n warning: Wizard Tower Stop hook saw a second pass and stayed calm to avoid a loop.\r\n```", - "labels": [], - "merged_at": "2026-03-19T17:53:09Z", - "number": 14867, - "state": "merged", - "title": "[hooks] use a user message > developer message for prompt continuation", - "url": "https://github.com/openai/codex/pull/14867" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-14888.json b/artifacts/github/bundles/openai-codex-pr-14888.json deleted file mode 100644 index fc38176..0000000 --- a/artifacts/github/bundles/openai-codex-pr-14888.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "shijie-oai", - "committed_at": "2026-03-16T22:31:42Z", - "message": "Reuse persisted model and reasoning effort on thread resume", - "sha": "0c30c52556b4a44425a644dbd0bc30e7a643773c", - "url": "https://github.com/openai/codex/commit/0c30c52556b4a44425a644dbd0bc30e7a643773c" - }, - { - "author": "shijie-oai", - "committed_at": "2026-03-17T15:57:01Z", - "message": "cleanup", - "sha": "ed07087b4cca63164a89e2ace3d776d58abefa50", - "url": "https://github.com/openai/codex/commit/ed07087b4cca63164a89e2ace3d776d58abefa50" - }, - { - "author": "shijie-oai", - "committed_at": "2026-03-18T03:08:01Z", - "message": "Address review comments", - "sha": "719028d9640274c4a80d5467e09dd23b8e5792ad", - "url": "https://github.com/openai/codex/commit/719028d9640274c4a80d5467e09dd23b8e5792ad" - }, - { - "author": "shijie-oai", - "committed_at": "2026-03-18T17:34:02Z", - "message": "Address review comments", - "sha": "634de340246b2492ccef59e01b3c1a2a9c0d3289", - "url": "https://github.com/openai/codex/commit/634de340246b2492ccef59e01b3c1a2a9c0d3289" - }, - { - "author": "shijie-oai", - "committed_at": "2026-03-18T18:20:20Z", - "message": "Address review comment", - "sha": "82375e834f88d6fb106ad9265cb018d9ee369b8e", - "url": "https://github.com/openai/codex/commit/82375e834f88d6fb106ad9265cb018d9ee369b8e" - } - ], - "default_branch": "main", - "docs_refs": [ - "codex-rs/app-server/README.md" - ], - "examples_refs": [], - "extracted_flags": [ - "SQL", - "README", - "MAX_USER_INPUT_TEXT_CHARS" - ], - "files": [ - { - "additions": 13, - "deletions": 14, - "patch_excerpt": "@@ -115,10 +115,7 @@ Example with notification opt-out:\n },\n \"capabilities\": {\n \"experimentalApi\": true,\n- \"optOutNotificationMethods\": [\n- \"thread/started\",\n- \"item/agentMessage/delta\"\n- ]\n+ \"optOutNotificationMethods\": [\"thread/started\", \"item/agentMessage/delta\"]\n }\n }\n }\n@@ -228,7 +225,11 @@ Start a fresh thread when you need a new Codex conversation.\n \n Valid `personality` values are `\"friendly\"`, `\"pragmatic\"`, and `\"none\"`. When `\"none\"` is selected, the personality placeholder is replaced with an empty string.\n \n-To continue a stored session, call `thread/resume` with the `thread.id` you previously recorded. The response shape matches `thread/start`, and no additional notifications are emitted. You can also pass the same configuration overrides supported by `thread/start`, including `approvalsReviewer`:\n+To continue a stored sessio...", - "path": "codex-rs/app-server/README.md", - "status": "modified" - }, - { - "additions": 279, - "deletions": 43, - "patch_excerpt": "@@ -271,6 +271,7 @@ use codex_protocol::user_input::MAX_USER_INPUT_TEXT_CHARS;\n use codex_protocol::user_input::UserInput as CoreInputItem;\n use codex_rmcp_client::perform_oauth_login_return_url;\n use codex_state::StateRuntime;\n+use codex_state::ThreadMetadata;\n use codex_state::ThreadMetadataBuilder;\n use codex_state::log_db::LogDbLayer;\n use codex_utils_json_to_toml::json_to_toml;\n@@ -3349,7 +3350,7 @@ impl CodexMessageProcessor {\n approval_policy,\n approvals_reviewer,\n sandbox,\n- config: request_overrides,\n+ config: mut request_overrides,\n base_instructions,\n developer_instructions,\n personality,\n@@ -3375,7 +3376,7 @@ impl CodexMessageProcessor {\n };\n \n let history_cwd = thread_history.session_cwd();\n- let typesafe_overrides = self.build_thread_config_overrides(\n+ ...", - "path": "codex-rs/app-server/src/codex_message_processor.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "## Summary\r\n\r\nThis PR makes `thread/resume` reuse persisted thread model metadata when the caller does not explicitly override it.\r\n\r\nChanges:\r\n- read persisted thread metadata from SQLite during `thread/resume`\r\n- reuse persisted `model` and `model_reasoning_effort` as resume-time defaults\r\n- fetch persisted metadata once and reuse it later in the resume response path\r\n- keep thread summary loading on the existing rollout path, while reusing persisted metadata when available\r\n- document the resume fallback behavior in the app-server README\r\n\r\n## Why\r\n\r\nBefore this change, resuming a thread without explicit overrides derived `model` and `model_reasoning_effort` from current config, which could drift from the thread\u2019s last persisted values. That meant a resumed thread could report and run with different model settings than the ones it previously used.\r\n\r\n## Behavior\r\n\r\nPrecedence on `thread/resume` is now:\r\n1. explicit resume overrides\r\n2. persisted SQLite metadata for the thread\r\n3. normal config resolution for the resumed cwd", - "labels": [], - "merged_at": "2026-03-18T22:45:17Z", - "number": 14888, - "state": "merged", - "title": "Feat: reuse persisted model and reasoning effort on thread resume", - "url": "https://github.com/openai/codex/pull/14888" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-14945.json b/artifacts/github/bundles/openai-codex-pr-14945.json deleted file mode 100644 index d88fc17..0000000 --- a/artifacts/github/bundles/openai-codex-pr-14945.json +++ /dev/null @@ -1,107 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "fcoury", - "committed_at": "2026-03-17T23:47:07Z", - "message": "feat(tui): restore composer history in app-server tui", - "sha": "4d02185bf38942352b45d9efc633e9949a981aed", - "url": "https://github.com/openai/codex/commit/4d02185bf38942352b45d9efc633e9949a981aed" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "TUI", - "CODEX_HOME", - "RPC", - "API", - "MAX_RETRIES", - "JSON", - "HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG", - "HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG", - "MAX" - ], - "files": [ - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -63,7 +63,7 @@ mod mcp_tool_call;\n mod memories;\n pub mod mention_syntax;\n mod mentions;\n-mod message_history;\n+pub mod message_history;\n mod model_provider_info;\n pub mod path_utils;\n pub mod personality_migration;", - "path": "codex-rs/core/src/lib.rs", - "status": "modified" - }, - { - "additions": 34, - "deletions": 19, - "patch_excerpt": "@@ -66,14 +66,22 @@ fn history_filepath(config: &Config) -> PathBuf {\n path\n }\n \n-/// Append a `text` entry associated with `conversation_id` to the history file. Uses\n-/// advisory file locking to ensure that concurrent writes do not interleave,\n-/// which entails a small amount of blocking I/O internally.\n-pub(crate) async fn append_entry(\n- text: &str,\n- conversation_id: &ThreadId,\n- config: &Config,\n-) -> Result<()> {\n+/// Append a `text` entry associated with `conversation_id` to the history file.\n+///\n+/// Uses advisory file locking (`File::try_lock`) with a retry loop to ensure\n+/// concurrent writes from multiple TUI processes do not interleave. The lock\n+/// acquisition and write are performed inside `spawn_blocking` so the caller's\n+/// async runtime is not blocked.\n+///\n+/// The entry is silently skipped when `config.history.persistence` is\n+/// [`HistoryPersisten...", - "path": "codex-rs/core/src/message_history.rs", - "status": "modified" - }, - { - "additions": 176, - "deletions": 18, - "patch_excerpt": "@@ -69,6 +69,7 @@ use codex_core::config::types::ApprovalsReviewer;\n use codex_core::config::types::ModelAvailabilityNuxConfig;\n use codex_core::config_loader::ConfigLayerStackOrdering;\n use codex_core::features::Feature;\n+use codex_core::message_history;\n use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;\n use codex_core::models_manager::model_presets::HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG;\n use codex_core::models_manager::model_presets::HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG;\n@@ -86,10 +87,10 @@ use codex_protocol::openai_models::ModelUpgrade;\n use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;\n use codex_protocol::protocol::AskForApproval;\n use codex_protocol::protocol::FinalOutput;\n+use codex_protocol::protocol::GetHistoryEntryResponseEvent;\n use codex_protocol::protocol::ListSkillsResponseEvent;\n #[cfg(test)]\n use co...", - "path": "codex-rs/tui_app_server/src/app.rs", - "status": "modified" - }, - { - "additions": 7, - "deletions": 0, - "patch_excerpt": "@@ -15,6 +15,7 @@ use codex_chatgpt::connectors::AppInfo;\n use codex_file_search::FileMatch;\n use codex_protocol::ThreadId;\n use codex_protocol::openai_models::ModelPreset;\n+use codex_protocol::protocol::GetHistoryEntryResponseEvent;\n use codex_protocol::protocol::Op;\n use codex_protocol::protocol::RateLimitSnapshot;\n use codex_utils_approval_presets::ApprovalPreset;\n@@ -81,6 +82,12 @@ pub(crate) enum AppEvent {\n op: Op,\n },\n \n+ /// Deliver a synthetic history lookup response to a specific thread channel.\n+ ThreadHistoryEntryResponse {\n+ thread_id: ThreadId,\n+ event: GetHistoryEntryResponseEvent,\n+ },\n+\n /// Start a new session.\n NewSession,", - "path": "codex-rs/tui_app_server/src/app_event.rs", - "status": "modified" - }, - { - "additions": 81, - "deletions": 25, - "patch_excerpt": "@@ -54,6 +54,7 @@ use codex_app_server_protocol::TurnStartResponse;\n use codex_app_server_protocol::TurnSteerParams;\n use codex_app_server_protocol::TurnSteerResponse;\n use codex_core::config::Config;\n+use codex_core::message_history;\n use codex_otel::TelemetryAuthMode;\n use codex_protocol::ThreadId;\n use codex_protocol::openai_models::ModelAvailabilityNux;\n@@ -277,7 +278,7 @@ impl AppServerSession {\n })\n .await\n .wrap_err(\"thread/start failed during TUI bootstrap\")?;\n- started_thread_from_start_response(response)\n+ started_thread_from_start_response(response, config).await\n }\n \n pub(crate) async fn resume_thread(\n@@ -291,14 +292,14 @@ impl AppServerSession {\n .request_typed(ClientRequest::ThreadResume {\n request_id,\n params: thread_resume_params_from_config(\n- config,...", - "path": "codex-rs/tui_app_server/src/app_server_session.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 1, - "patch_excerpt": "@@ -740,7 +740,6 @@ impl ChatComposer {\n /// composer rehydrates the entry immediately. This path intentionally routes through\n /// [`Self::apply_history_entry`] so cursor placement remains aligned with keyboard history\n /// recall semantics.\n- #[cfg(test)]\n pub(crate) fn on_history_entry_response(\n &mut self,\n log_id: u64,", - "path": "codex-rs/tui_app_server/src/bottom_pane/chat_composer.rs", - "status": "modified" - }, - { - "additions": 22, - "deletions": 28, - "patch_excerpt": "@@ -4,10 +4,9 @@ use std::path::PathBuf;\n use crate::app_event::AppEvent;\n use crate::app_event_sender::AppEventSender;\n use crate::bottom_pane::MentionBinding;\n-use crate::history_cell;\n use crate::mention_codec::decode_history_mentions;\n+use codex_protocol::protocol::Op;\n use codex_protocol::user_input::TextElement;\n-use tracing::warn;\n \n /// A composer history entry that can rehydrate draft state.\n #[derive(Debug, Clone, PartialEq)]\n@@ -237,7 +236,6 @@ impl ChatComposerHistory {\n }\n \n /// Integrate a GetHistoryEntryResponse event.\n- #[cfg(test)]\n pub fn on_entry_response(\n &mut self,\n log_id: u64,\n@@ -280,16 +278,10 @@ impl ChatComposerHistory {\n self.last_history_text = Some(entry.text.clone());\n return Some(entry);\n } else if let Some(log_id) = self.history_log_id {\n- warn!(\n+ app_event_tx.send(AppE...", - "path": "codex-rs/tui_app_server/src/bottom_pane/chat_composer_history.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 1, - "patch_excerpt": "@@ -1073,7 +1073,6 @@ impl BottomPane {\n || self.composer.is_in_paste_burst()\n }\n \n- #[cfg(test)]\n pub(crate) fn on_history_entry_response(\n &mut self,\n log_id: u64,", - "path": "codex-rs/tui_app_server/src/bottom_pane/mod.rs", - "status": "modified" - }, - { - "additions": 16, - "deletions": 5, - "patch_excerpt": "@@ -46,6 +46,8 @@ use crate::audio_device::list_realtime_audio_device_names;\n use crate::bottom_pane::StatusLineItem;\n use crate::bottom_pane::StatusLinePreviewData;\n use crate::bottom_pane::StatusLineSetupView;\n+use crate::mention_codec::LinkedMention;\n+use crate::mention_codec::encode_history_mentions;\n use crate::model_catalog::ModelCatalog;\n use crate::multi_agents;\n use crate::status::RateLimitWindowDisplay;\n@@ -3474,8 +3476,7 @@ impl ChatWidget {\n }\n }\n \n- #[cfg(test)]\n- fn on_get_history_entry_response(\n+ pub(crate) fn handle_history_entry_response(\n &mut self,\n event: codex_protocol::protocol::GetHistoryEntryResponseEvent,\n ) {\n@@ -5316,9 +5317,19 @@ impl ChatWidget {\n return;\n }\n \n- // Persist the text to cross-session message history.\n+ // Persist the text to cross-session message history. Mentions are\n+...", - "path": "codex-rs/tui_app_server/src/chatwidget.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "## Problem\n\nThe app-server TUI (`tui_app_server`) lacked composer history support. Pressing Up/Down to recall previous prompts hit a stub that logged a warning and displayed \"Not available in app-server TUI yet.\" New submissions were silently dropped from the shared history file, so nothing persisted for future sessions.\n\n## Mental model\n\nCodex maintains a single, append-only history file (`$CODEX_HOME/history.jsonl`) shared across all TUI processes on the same machine. The legacy (in-process) TUI already reads/writes this file through `codex_core::message_history`. The app-server TUI delegates most operations to a separate process over RPC, but history is intentionally *not* an RPC concern \u2014 it's a client-local file.\n\nThis PR makes the app-server TUI access the same history file directly, bypassing the app-server process entirely. The composer's Up/Down navigation and submit-time persistence now follow the same code paths as the legacy TUI, with the only difference being *where* the call is dispatched (locally in `App`, rather than inside `CodexThread`).\n\nThe branch is rebuilt directly on top of `upstream/main`, so it keeps the\nexisting app-server restore architecture intact. `AppServerStartedThread`\nstill restores transcript history from the server `Thread` snapshot via\n`thread_snapshot_events`; this PR only adds composer-history support.\n\n## Non-goals\n\n- Adding history support to the app-server protocol. History remains client-local.\n- Changing the on-disk format or location of `history.jsonl`.\n- Surfacing history I/O errors to the user (failures are logged and silently swallowed, matching the legacy TUI).\n\n## Tradeoffs\n\n| Decision | Why | Risk |\n|----------|-----|------|\n| Widen `message_history` from `pub(crate)` to `pub` | Avoids duplicating file I/O logic; the module already has a clean, minimal API surface. | Other workspace crates can now call these functions \u2014 the contract is no longer crate-private. However, this is consistent with recent precedent: `590cfa617` exposed `mention_syntax` for TUI consumption, `752402c4f` exposed plugin APIs (`PluginsManager`), and `14fcb6645`/`edacbf7b6` widened internal core APIs for other crates. These were all narrow, intentional exposures of specific APIs \u2014 not broad \"make internals public\" moves. `1af2a37ad` even went the other direction, reducing broad re-exports to tighten boundaries. This change follows the same pattern: a small, deliberate API surface (3 functions) rather than a wholesale visibility change. |\n| Intercept `AddToHistory` / `GetHistoryEntryRequest` in `App` before RPC fallback | Keeps history ops out of the \"unsupported op\" error path without changing app-server protocol. | This now routes through a single `submit_thread_op` entry point, which is safer than the original duplicated dispatch. The remaining risk is organizational: future thread-op submission paths need to keep using that shared entry point. |\n| `session_configured_from_thread_response` is now `async` | Needs `await` on `history_metadata()` to populate real `history_log_id` / `history_entry_count`. | Adds an async file-stat + full-file newline scan to the session bootstrap path. The scan is bounded by `history.max_bytes` and matches the legacy TUI's cost profile, but startup latency still scales with file size. |\n\n## Architecture\n\n```\nUser presses Up User submits a prompt\n \u2502 \u2502\n \u25bc \u25bc\nChatComposerHistory ChatWidget::do_submit_turn\n navigate_up() encode_history_mentions()\n \u2502 \u2502\n \u25bc \u25bc\n AppEvent::CodexOp Op::AddToHistory { text }\n (GetHistoryEntryRequest) \u2502\n \u2502 \u25bc\n \u25bc App::try_handle_local_history_op\n App::try_handle_local_history_op message_history::append_entry()\n spawn_blocking { \u2502\n message_history::lookup() \u25bc\n } $CODEX_HOME/history.jsonl\n \u2502\n \u25bc\n AppEvent::ThreadEvent\n (GetHistoryEntryResponse)\n \u2502\n \u25bc\n ChatComposerHistory::on_entry_response()\n```\n\n## Observability\n\n- `tracing::warn` on `append_entry` failure (includes thread ID).\n- `tracing::warn` on `spawn_blocking` lookup join error.\n- `tracing::warn` from `message_history` internals on file-open, lock, or parse failures.\n\n## Tests\n\n- `chat_composer_history::tests::navigation_with_async_fetch` \u2014 verifies that Up emits `Op::GetHistoryEntryRequest` (was: checked for stub error cell).\n- `app::tests::history_lookup_response_is_routed_to_requesting_thread` \u2014 verifies multi-thread composer recall routes the lookup result back to the originating thread.\n- `app_server_session::tests::resume_response_relies_on_snapshot_replay_not_initial_messages` \u2014 verifies app-server session restore still uses the upstream thread-snapshot path.\n- `app_server_session::tests::session_configured_populates_history_metadata` \u2014 verifies bootstrap sets nonzero `history_log_id` / `history_entry_count` from the shared local history file.\n", - "labels": [], - "merged_at": "2026-03-18T17:54:12Z", - "number": 14945, - "state": "merged", - "title": "feat(tui): restore composer history in app-server tui", - "url": "https://github.com/openai/codex/pull/14945" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-14970.json b/artifacts/github/bundles/openai-codex-pr-14970.json deleted file mode 100644 index 0956f50..0000000 --- a/artifacts/github/bundles/openai-codex-pr-14970.json +++ /dev/null @@ -1,181 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "canvrno-oai", - "committed_at": "2026-03-17T18:19:14Z", - "message": "Simple folder mentions", - "sha": "a4277b0b92ff0cf793a41879f051d0912c30d34f", - "url": "https://github.com/openai/codex/commit/a4277b0b92ff0cf793a41879f051d0912c30d34f" - }, - { - "author": "canvrno-oai", - "committed_at": "2026-03-17T18:55:40Z", - "message": "Use trailing slash", - "sha": "df3a8b403d87c047663741ffe0c901a73ba77897", - "url": "https://github.com/openai/codex/commit/df3a8b403d87c047663741ffe0c901a73ba77897" - }, - { - "author": "canvrno-oai", - "committed_at": "2026-03-17T20:56:16Z", - "message": "Add match type", - "sha": "385be6b39574ec76e04bd9f35494e16fefdd908c", - "url": "https://github.com/openai/codex/commit/385be6b39574ec76e04bd9f35494e16fefdd908c" - }, - { - "author": "canvrno-oai", - "committed_at": "2026-03-17T22:08:28Z", - "message": "Added match_type to indicate file/directory mentions", - "sha": "c0617a57700f2dbf19a3b2301683afcb6d808d53", - "url": "https://github.com/openai/codex/commit/c0617a57700f2dbf19a3b2301683afcb6d808d53" - }, - { - "author": "canvrno-oai", - "committed_at": "2026-03-18T20:28:13Z", - "message": "Merge conflicts", - "sha": "f9634cb19921f2d3ffac486c1afe7cc0252ea1c3", - "url": "https://github.com/openai/codex/commit/f9634cb19921f2d3ffac486c1afe7cc0252ea1c3" - }, - { - "author": "canvrno-oai", - "committed_at": "2026-03-18T21:42:27Z", - "message": "Merge branch 'main' into canvrno/folder_mentions_v2", - "sha": "bbdfbb91a2baaea670862c39e837815681d7203e", - "url": "https://github.com/openai/codex/commit/bbdfbb91a2baaea670862c39e837815681d7203e" - }, - { - "author": "canvrno-oai", - "committed_at": "2026-03-19T00:53:17Z", - "message": "Fix test in windows envs", - "sha": "0dbfa40452624b4e0119a4f346362c239405cb34", - "url": "https://github.com/openai/codex/commit/0dbfa40452624b4e0119a4f346362c239405cb34" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "TUI", - "GENERATED", - "CODE", - "NOT", - "MODIFY", - "HAND" - ], - "files": [ - { - "additions": 11, - "deletions": 0, - "patch_excerpt": "@@ -1,6 +1,13 @@\n {\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"definitions\": {\n+ \"FuzzyFileSearchMatchType\": {\n+ \"enum\": [\n+ \"file\",\n+ \"directory\"\n+ ],\n+ \"type\": \"string\"\n+ },\n \"FuzzyFileSearchResult\": {\n \"description\": \"Superset of [`codex_file_search::FileMatch`]\",\n \"properties\": {\n@@ -18,6 +25,9 @@\n \"null\"\n ]\n },\n+ \"match_type\": {\n+ \"$ref\": \"#/definitions/FuzzyFileSearchMatchType\"\n+ },\n \"path\": {\n \"type\": \"string\"\n },\n@@ -32,6 +42,7 @@\n },\n \"required\": [\n \"file_name\",\n+ \"match_type\",\n \"path\",\n \"root\",\n \"score\"", - "path": "codex-rs/app-server-protocol/schema/json/FuzzyFileSearchResponse.json", - "status": "modified" - }, - { - "additions": 11, - "deletions": 0, - "patch_excerpt": "@@ -1,6 +1,13 @@\n {\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"definitions\": {\n+ \"FuzzyFileSearchMatchType\": {\n+ \"enum\": [\n+ \"file\",\n+ \"directory\"\n+ ],\n+ \"type\": \"string\"\n+ },\n \"FuzzyFileSearchResult\": {\n \"description\": \"Superset of [`codex_file_search::FileMatch`]\",\n \"properties\": {\n@@ -18,6 +25,9 @@\n \"null\"\n ]\n },\n+ \"match_type\": {\n+ \"$ref\": \"#/definitions/FuzzyFileSearchMatchType\"\n+ },\n \"path\": {\n \"type\": \"string\"\n },\n@@ -32,6 +42,7 @@\n },\n \"required\": [\n \"file_name\",\n+ \"match_type\",\n \"path\",\n \"root\",\n \"score\"", - "path": "codex-rs/app-server-protocol/schema/json/FuzzyFileSearchSessionUpdatedNotification.json", - "status": "modified" - }, - { - "additions": 11, - "deletions": 0, - "patch_excerpt": "@@ -964,6 +964,13 @@\n ],\n \"type\": \"object\"\n },\n+ \"FuzzyFileSearchMatchType\": {\n+ \"enum\": [\n+ \"file\",\n+ \"directory\"\n+ ],\n+ \"type\": \"string\"\n+ },\n \"FuzzyFileSearchResult\": {\n \"description\": \"Superset of [`codex_file_search::FileMatch`]\",\n \"properties\": {\n@@ -981,6 +988,9 @@\n \"null\"\n ]\n },\n+ \"match_type\": {\n+ \"$ref\": \"#/definitions/FuzzyFileSearchMatchType\"\n+ },\n \"path\": {\n \"type\": \"string\"\n },\n@@ -995,6 +1005,7 @@\n },\n \"required\": [\n \"file_name\",\n+ \"match_type\",\n \"path\",\n \"root\",\n \"score\"", - "path": "codex-rs/app-server-protocol/schema/json/ServerNotification.json", - "status": "modified" - }, - { - "additions": 11, - "deletions": 0, - "patch_excerpt": "@@ -2034,6 +2034,13 @@\n \"title\": \"FileChangeRequestApprovalResponse\",\n \"type\": \"object\"\n },\n+ \"FuzzyFileSearchMatchType\": {\n+ \"enum\": [\n+ \"file\",\n+ \"directory\"\n+ ],\n+ \"type\": \"string\"\n+ },\n \"FuzzyFileSearchParams\": {\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"properties\": {\n@@ -2093,6 +2100,9 @@\n \"null\"\n ]\n },\n+ \"match_type\": {\n+ \"$ref\": \"#/definitions/FuzzyFileSearchMatchType\"\n+ },\n \"path\": {\n \"type\": \"string\"\n },\n@@ -2107,6 +2117,7 @@\n },\n \"required\": [\n \"file_name\",\n+ \"match_type\",\n \"path\",\n \"root\",\n \"score\"", - "path": "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json", - "status": "modified" - }, - { - "additions": 11, - "deletions": 0, - "patch_excerpt": "@@ -4327,6 +4327,13 @@\n }\n ]\n },\n+ \"FuzzyFileSearchMatchType\": {\n+ \"enum\": [\n+ \"file\",\n+ \"directory\"\n+ ],\n+ \"type\": \"string\"\n+ },\n \"FuzzyFileSearchParams\": {\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"properties\": {\n@@ -4370,6 +4377,9 @@\n \"null\"\n ]\n },\n+ \"match_type\": {\n+ \"$ref\": \"#/definitions/FuzzyFileSearchMatchType\"\n+ },\n \"path\": {\n \"type\": \"string\"\n },\n@@ -4384,6 +4394,7 @@\n },\n \"required\": [\n \"file_name\",\n+ \"match_type\",\n \"path\",\n \"root\",\n \"score\"", - "path": "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json", - "status": "modified" - }, - { - "additions": 5, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,5 @@\n+// GENERATED CODE! DO NOT MODIFY BY HAND!\n+\n+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n+\n+export type FuzzyFileSearchMatchType = \"file\" | \"directory\";", - "path": "codex-rs/app-server-protocol/schema/typescript/FuzzyFileSearchMatchType.ts", - "status": "added" - }, - { - "additions": 2, - "deletions": 1, - "patch_excerpt": "@@ -1,8 +1,9 @@\n // GENERATED CODE! DO NOT MODIFY BY HAND!\n \n // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n+import type { FuzzyFileSearchMatchType } from \"./FuzzyFileSearchMatchType\";\n \n /**\n * Superset of [`codex_file_search::FileMatch`]\n */\n-export type FuzzyFileSearchResult = { root: string, path: string, file_name: string, score: number, indices: Array | null, };\n+export type FuzzyFileSearchResult = { root: string, path: string, match_type: FuzzyFileSearchMatchType, file_name: string, score: number, indices: Array | null, };", - "path": "codex-rs/app-server-protocol/schema/typescript/FuzzyFileSearchResult.ts", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -18,6 +18,7 @@ export type { FileChange } from \"./FileChange\";\n export type { ForcedLoginMethod } from \"./ForcedLoginMethod\";\n export type { FunctionCallOutputBody } from \"./FunctionCallOutputBody\";\n export type { FunctionCallOutputContentItem } from \"./FunctionCallOutputContentItem\";\n+export type { FuzzyFileSearchMatchType } from \"./FuzzyFileSearchMatchType\";\n export type { FuzzyFileSearchParams } from \"./FuzzyFileSearchParams\";\n export type { FuzzyFileSearchResponse } from \"./FuzzyFileSearchResponse\";\n export type { FuzzyFileSearchResult } from \"./FuzzyFileSearchResult\";", - "path": "codex-rs/app-server-protocol/schema/typescript/index.ts", - "status": "modified" - }, - { - "additions": 9, - "deletions": 0, - "patch_excerpt": "@@ -800,11 +800,20 @@ pub struct FuzzyFileSearchParams {\n pub struct FuzzyFileSearchResult {\n pub root: String,\n pub path: String,\n+ pub match_type: FuzzyFileSearchMatchType,\n pub file_name: String,\n pub score: u32,\n pub indices: Option>,\n }\n \n+#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]\n+#[serde(rename_all = \"camelCase\")]\n+#[ts(rename_all = \"camelCase\")]\n+pub enum FuzzyFileSearchMatchType {\n+ File,\n+ Directory,\n+}\n+\n #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]\n pub struct FuzzyFileSearchResponse {\n pub files: Vec,", - "path": "codex-rs/app-server-protocol/src/protocol/common.rs", - "status": "modified" - }, - { - "additions": 9, - "deletions": 0, - "patch_excerpt": "@@ -5,6 +5,7 @@ use std::sync::Mutex;\n use std::sync::atomic::AtomicBool;\n use std::sync::atomic::Ordering;\n \n+use codex_app_server_protocol::FuzzyFileSearchMatchType;\n use codex_app_server_protocol::FuzzyFileSearchResult;\n use codex_app_server_protocol::FuzzyFileSearchSessionCompletedNotification;\n use codex_app_server_protocol::FuzzyFileSearchSessionUpdatedNotification;\n@@ -60,6 +61,10 @@ pub(crate) async fn run_fuzzy_file_search(\n FuzzyFileSearchResult {\n root: m.root.to_string_lossy().to_string(),\n path: m.path.to_string_lossy().to_string(),\n+ match_type: match m.match_type {\n+ file_search::MatchType::File => FuzzyFileSearchMatchType::File,\n+ file_search::MatchType::Directory => FuzzyFileSearchMatchType::Directory,\n+ },\n file_...", - "path": "codex-rs/app-server/src/fuzzy_file_search.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 0, - "patch_excerpt": "@@ -257,20 +257,23 @@ async fn test_fuzzy_file_search_sorts_and_includes_indices() -> Result<()> {\n {\n \"root\": root_path.clone(),\n \"path\": \"abexy\",\n+ \"match_type\": \"file\",\n \"file_name\": \"abexy\",\n \"score\": 84,\n \"indices\": [0, 1, 2],\n },\n {\n \"root\": root_path.clone(),\n \"path\": sub_abce_rel,\n+ \"match_type\": \"file\",\n \"file_name\": \"abce\",\n \"score\": expected_score,\n \"indices\": [4, 5, 7],\n },\n {\n \"root\": root_path.clone(),\n \"path\": \"abcde\",\n+ \"match_type\": \"file\",\n \"file_name\": \"abcde\",\n ...", - "path": "codex-rs/app-server/tests/suite/fuzzy_file_search.rs", - "status": "modified" - }, - { - "additions": 45, - "deletions": 5, - "patch_excerpt": "@@ -41,7 +41,9 @@ pub use cli::Cli;\n /// A single match result returned from the search.\n ///\n /// * `score` \u2013 Relevance score returned by `nucleo`.\n-/// * `path` \u2013 Path to the matched file (relative to the search directory).\n+/// * `path` \u2013 Path to the matched entry (file or directory), relative to the\n+/// search directory.\n+/// * `match_type` \u2013 Whether this match is a file or directory.\n /// * `indices` \u2013 Optional list of character indices that matched the query.\n /// These are only filled when the caller of [`run`] sets\n /// `options.compute_indices` to `true`. The indices vector follows the\n@@ -52,11 +54,19 @@ pub use cli::Cli;\n pub struct FileMatch {\n pub score: u32,\n pub path: PathBuf,\n+ pub match_type: MatchType,\n pub root: PathBuf,\n #[serde(skip_serializing_if = \"Option::is_none\")]\n pub indices: Option>, // Sorted & deduplicated when prese...", - "path": "codex-rs/file-search/src/lib.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -7253,6 +7253,7 @@ mod tests {\n vec![FileMatch {\n score: 1,\n path: PathBuf::from(\"src/main.rs\"),\n+ match_type: codex_file_search::MatchType::File,\n root: PathBuf::from(\"/tmp\"),\n indices: None,\n }],", - "path": "codex-rs/tui/src/bottom_pane/chat_composer.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -7268,6 +7268,7 @@ mod tests {\n vec![FileMatch {\n score: 1,\n path: PathBuf::from(\"src/main.rs\"),\n+ match_type: codex_file_search::MatchType::File,\n root: PathBuf::from(\"/tmp\"),\n indices: None,\n }],", - "path": "codex-rs/tui_app_server/src/bottom_pane/chat_composer.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "- Adds simple support for directory mentions in the TUI.\r\n- Codex App/VS Code will require minor change to recognize a directory mention as such and change the link behavior.\r\n- Directory mentions have a trailing slash to differentiate from extensionless files\r\n\r\n\r\n\"image\"\r\n\"image\"\r\n\r\n\r\n\r\n", - "labels": [], - "merged_at": "2026-03-19T05:24:10Z", - "number": 14970, - "state": "merged", - "title": "Simple directory mentions", - "url": "https://github.com/openai/codex/pull/14970" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-14988.json b/artifacts/github/bundles/openai-codex-pr-14988.json deleted file mode 100644 index 8ffb6f3..0000000 --- a/artifacts/github/bundles/openai-codex-pr-14988.json +++ /dev/null @@ -1,475 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "etraut-openai", - "committed_at": "2026-03-17T20:55:19Z", - "message": "Add thread shellCommand support to tui app server", - "sha": "eb6a6977074331cd2c212d2b1a0c97f8c695ab36", - "url": "https://github.com/openai/codex/commit/eb6a6977074331cd2c212d2b1a0c97f8c695ab36" - }, - { - "author": "etraut-openai", - "committed_at": "2026-03-17T21:20:44Z", - "message": "codex: address PR review feedback (#14988)", - "sha": "e14a72a9f939a099478c80cbddf159d2f4c963d4", - "url": "https://github.com/openai/codex/commit/e14a72a9f939a099478c80cbddf159d2f4c963d4" - }, - { - "author": "etraut-openai", - "committed_at": "2026-03-17T21:43:50Z", - "message": "codex: fix CI failure on PR #14988", - "sha": "aac5786ae192885865194141d345de63f757379c", - "url": "https://github.com/openai/codex/commit/aac5786ae192885865194141d345de63f757379c" - }, - { - "author": "etraut-openai", - "committed_at": "2026-03-17T21:57:05Z", - "message": "codex: revert broad rollout persistence change", - "sha": "3aadef3b43dbfea31be17647e3ad88af98e65f02", - "url": "https://github.com/openai/codex/commit/3aadef3b43dbfea31be17647e3ad88af98e65f02" - }, - { - "author": "etraut-openai", - "committed_at": "2026-03-17T22:11:02Z", - "message": "codex: fix protocol clippy default derivation", - "sha": "6fa40a7ed721b57ddcd06b82d3e99bae7a7cfc5c", - "url": "https://github.com/openai/codex/commit/6fa40a7ed721b57ddcd06b82d3e99bae7a7cfc5c" - }, - { - "author": "etraut-openai", - "committed_at": "2026-03-17T23:06:26Z", - "message": "codex: address PR review feedback (#14988)", - "sha": "1d0e8577a254649321aeb0b197d347ee8bd1b5a0", - "url": "https://github.com/openai/codex/commit/1d0e8577a254649321aeb0b197d347ee8bd1b5a0" - }, - { - "author": "etraut-openai", - "committed_at": "2026-03-17T23:14:20Z", - "message": "codex: fix CI failure on PR #14988", - "sha": "27b0848622d8e04786d51768869a05d13eac65bb", - "url": "https://github.com/openai/codex/commit/27b0848622d8e04786d51768869a05d13eac65bb" - }, - { - "author": "etraut-openai", - "committed_at": "2026-03-17T23:20:27Z", - "message": "codex: fix CI failure on PR #14988", - "sha": "d573ab01a7eddc69f9d40ccf1252152d4457ebda", - "url": "https://github.com/openai/codex/commit/d573ab01a7eddc69f9d40ccf1252152d4457ebda" - }, - { - "author": "etraut-openai", - "committed_at": "2026-03-17T23:31:23Z", - "message": "codex: fix CI failure on PR #14988", - "sha": "675f35194bf1bc4c3ff289e3aff122ff30184e87", - "url": "https://github.com/openai/codex/commit/675f35194bf1bc4c3ff289e3aff122ff30184e87" - }, - { - "author": "etraut-openai", - "committed_at": "2026-03-18T17:24:05Z", - "message": "codex: fix CI formatting on PR #14988", - "sha": "03dd6550d7748bd1334f6b476a6a4a0181712f04", - "url": "https://github.com/openai/codex/commit/03dd6550d7748bd1334f6b476a6a4a0181712f04" - }, - { - "author": "etraut-openai", - "committed_at": "2026-03-18T17:34:20Z", - "message": "codex: clarify standalone shell turn docs on PR #14988", - "sha": "37d04c649cd3931f7979b7073290883a329859b5", - "url": "https://github.com/openai/codex/commit/37d04c649cd3931f7979b7073290883a329859b5" - }, - { - "author": "etraut-openai", - "committed_at": "2026-03-18T17:58:09Z", - "message": "codex: fix CI failure on PR #14988", - "sha": "e9b1a7b2daa5c2874a907774d3bfd5f9db3ed09b", - "url": "https://github.com/openai/codex/commit/e9b1a7b2daa5c2874a907774d3bfd5f9db3ed09b" - }, - { - "author": "etraut-openai", - "committed_at": "2026-03-18T18:08:25Z", - "message": "codex: fix CI failure on PR #14988", - "sha": "8a3d8c5c16b7d3f571f5b59d9787232a880b8c93", - "url": "https://github.com/openai/codex/commit/8a3d8c5c16b7d3f571f5b59d9787232a880b8c93" - }, - { - "author": "etraut-openai", - "committed_at": "2026-03-18T18:40:22Z", - "message": "codex: address PR review feedback (#14988)", - "sha": "e2b5dbcb650e9a864214a117ec32e6d57aa7eff5", - "url": "https://github.com/openai/codex/commit/e2b5dbcb650e9a864214a117ec32e6d57aa7eff5" - }, - { - "author": "etraut-openai", - "committed_at": "2026-03-18T18:57:51Z", - "message": "codex: revert thread command output delta raw-bytes followup (#14988)", - "sha": "f4d42d019bbb6e0710907907a2a70d7d0c36ae94", - "url": "https://github.com/openai/codex/commit/f4d42d019bbb6e0710907907a2a70d7d0c36ae94" - }, - { - "author": "etraut-openai", - "committed_at": "2026-03-18T20:06:55Z", - "message": "codex: address PR review feedback (#14988)", - "sha": "11a0166fe77e45597924af3af347071a991bc44d", - "url": "https://github.com/openai/codex/commit/11a0166fe77e45597924af3af347071a991bc44d" - }, - { - "author": "etraut-openai", - "committed_at": "2026-03-18T20:15:10Z", - "message": "codex: address PR review feedback (#14988)", - "sha": "acb0aa06b3097e9fe362c5849fdf04c9c0763f44", - "url": "https://github.com/openai/codex/commit/acb0aa06b3097e9fe362c5849fdf04c9c0763f44" - } - ], - "default_branch": "main", - "docs_refs": [ - "codex-rs/app-server/README.md" - ], - "examples_refs": [], - "extracted_flags": [ - "API", - "TUI", - "GENERATED", - "CODE", - "NOT", - "MODIFY", - "HAND", - "PTY", - "--short", - "V2P", - "JSONRPCE", - "INVALID_REQUEST_ERROR_CODE", - "JSON", - "JSONRPCR", - "V2U", - "FEATURES", - "DEFAULT_READ_TIMEOUT", - "JSONRPCN", - "UUID", - "JWT", - "URL_SAFE_NO_PAD", - "TODO", - "NONE" - ], - "files": [ - { - "additions": 40, - "deletions": 0, - "patch_excerpt": "@@ -2881,6 +2881,22 @@\n ],\n \"type\": \"object\"\n },\n+ \"ThreadShellCommandParams\": {\n+ \"properties\": {\n+ \"command\": {\n+ \"description\": \"Shell command string evaluated by the thread's configured shell. Unlike `command/exec`, this intentionally preserves shell syntax such as pipes, redirects, and quoting. This runs unsandboxed with full access rather than inheriting the thread sandbox policy.\",\n+ \"type\": \"string\"\n+ },\n+ \"threadId\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"command\",\n+ \"threadId\"\n+ ],\n+ \"type\": \"object\"\n+ },\n \"ThreadSortKey\": {\n \"enum\": [\n \"created_at\",\n@@ -3587,6 +3603,30 @@\n \"title\": \"Thread/compact/startRequest\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"id\": {\n+ \"$ref\": \"#/definitions/Reques...", - "path": "codex-rs/app-server-protocol/schema/json/ClientRequest.json", - "status": "modified" - }, - { - "additions": 17, - "deletions": 0, - "patch_excerpt": "@@ -745,6 +745,15 @@\n ],\n \"type\": \"object\"\n },\n+ \"CommandExecutionSource\": {\n+ \"enum\": [\n+ \"agent\",\n+ \"userShell\",\n+ \"unifiedExecStartup\",\n+ \"unifiedExecInteraction\"\n+ ],\n+ \"type\": \"string\"\n+ },\n \"CommandExecutionStatus\": {\n \"enum\": [\n \"inProgress\",\n@@ -2392,6 +2401,14 @@\n \"null\"\n ]\n },\n+ \"source\": {\n+ \"allOf\": [\n+ {\n+ \"$ref\": \"#/definitions/CommandExecutionSource\"\n+ }\n+ ],\n+ \"default\": \"agent\"\n+ },\n \"status\": {\n \"$ref\": \"#/definitions/CommandExecutionStatus\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/ServerNotification.json", - "status": "modified" - }, - { - "additions": 64, - "deletions": 0, - "patch_excerpt": "@@ -499,6 +499,30 @@\n \"title\": \"Thread/compact/startRequest\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"id\": {\n+ \"$ref\": \"#/definitions/v2/RequestId\"\n+ },\n+ \"method\": {\n+ \"enum\": [\n+ \"thread/shellCommand\"\n+ ],\n+ \"title\": \"Thread/shellCommandRequestMethod\",\n+ \"type\": \"string\"\n+ },\n+ \"params\": {\n+ \"$ref\": \"#/definitions/v2/ThreadShellCommandParams\"\n+ }\n+ },\n+ \"required\": [\n+ \"id\",\n+ \"method\",\n+ \"params\"\n+ ],\n+ \"title\": \"Thread/shellCommandRequest\",\n+ \"type\": \"object\"\n+ },\n {\n \"properties\": {\n \"id\": {\n@@ -6110,6 +6134,15 @@\n \"title\": \"CommandExecutionOutputDeltaNoti...", - "path": "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json", - "status": "modified" - }, - { - "additions": 64, - "deletions": 0, - "patch_excerpt": "@@ -1026,6 +1026,30 @@\n \"title\": \"Thread/compact/startRequest\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"id\": {\n+ \"$ref\": \"#/definitions/RequestId\"\n+ },\n+ \"method\": {\n+ \"enum\": [\n+ \"thread/shellCommand\"\n+ ],\n+ \"title\": \"Thread/shellCommandRequestMethod\",\n+ \"type\": \"string\"\n+ },\n+ \"params\": {\n+ \"$ref\": \"#/definitions/ThreadShellCommandParams\"\n+ }\n+ },\n+ \"required\": [\n+ \"id\",\n+ \"method\",\n+ \"params\"\n+ ],\n+ \"title\": \"Thread/shellCommandRequest\",\n+ \"type\": \"object\"\n+ },\n {\n \"properties\": {\n \"id\": {\n@@ -2754,6 +2778,15 @@\n \"title\": \"CommandExecutionOutputDeltaNotificati...", - "path": "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json", - "status": "modified" - }, - { - "additions": 17, - "deletions": 0, - "patch_excerpt": "@@ -177,6 +177,15 @@\n }\n ]\n },\n+ \"CommandExecutionSource\": {\n+ \"enum\": [\n+ \"agent\",\n+ \"userShell\",\n+ \"unifiedExecStartup\",\n+ \"unifiedExecInteraction\"\n+ ],\n+ \"type\": \"string\"\n+ },\n \"CommandExecutionStatus\": {\n \"enum\": [\n \"inProgress\",\n@@ -642,6 +651,14 @@\n \"null\"\n ]\n },\n+ \"source\": {\n+ \"allOf\": [\n+ {\n+ \"$ref\": \"#/definitions/CommandExecutionSource\"\n+ }\n+ ],\n+ \"default\": \"agent\"\n+ },\n \"status\": {\n \"$ref\": \"#/definitions/CommandExecutionStatus\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json", - "status": "modified" - }, - { - "additions": 17, - "deletions": 0, - "patch_excerpt": "@@ -177,6 +177,15 @@\n }\n ]\n },\n+ \"CommandExecutionSource\": {\n+ \"enum\": [\n+ \"agent\",\n+ \"userShell\",\n+ \"unifiedExecStartup\",\n+ \"unifiedExecInteraction\"\n+ ],\n+ \"type\": \"string\"\n+ },\n \"CommandExecutionStatus\": {\n \"enum\": [\n \"inProgress\",\n@@ -642,6 +651,14 @@\n \"null\"\n ]\n },\n+ \"source\": {\n+ \"allOf\": [\n+ {\n+ \"$ref\": \"#/definitions/CommandExecutionSource\"\n+ }\n+ ],\n+ \"default\": \"agent\"\n+ },\n \"status\": {\n \"$ref\": \"#/definitions/CommandExecutionStatus\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json", - "status": "modified" - }, - { - "additions": 17, - "deletions": 0, - "patch_excerpt": "@@ -291,6 +291,15 @@\n }\n ]\n },\n+ \"CommandExecutionSource\": {\n+ \"enum\": [\n+ \"agent\",\n+ \"userShell\",\n+ \"unifiedExecStartup\",\n+ \"unifiedExecInteraction\"\n+ ],\n+ \"type\": \"string\"\n+ },\n \"CommandExecutionStatus\": {\n \"enum\": [\n \"inProgress\",\n@@ -756,6 +765,14 @@\n \"null\"\n ]\n },\n+ \"source\": {\n+ \"allOf\": [\n+ {\n+ \"$ref\": \"#/definitions/CommandExecutionSource\"\n+ }\n+ ],\n+ \"default\": \"agent\"\n+ },\n \"status\": {\n \"$ref\": \"#/definitions/CommandExecutionStatus\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json", - "status": "modified" - }, - { - "additions": 17, - "deletions": 0, - "patch_excerpt": "@@ -353,6 +353,15 @@\n }\n ]\n },\n+ \"CommandExecutionSource\": {\n+ \"enum\": [\n+ \"agent\",\n+ \"userShell\",\n+ \"unifiedExecStartup\",\n+ \"unifiedExecInteraction\"\n+ ],\n+ \"type\": \"string\"\n+ },\n \"CommandExecutionStatus\": {\n \"enum\": [\n \"inProgress\",\n@@ -1249,6 +1258,14 @@\n \"null\"\n ]\n },\n+ \"source\": {\n+ \"allOf\": [\n+ {\n+ \"$ref\": \"#/definitions/CommandExecutionSource\"\n+ }\n+ ],\n+ \"default\": \"agent\"\n+ },\n \"status\": {\n \"$ref\": \"#/definitions/CommandExecutionStatus\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json", - "status": "modified" - }, - { - "additions": 17, - "deletions": 0, - "patch_excerpt": "@@ -291,6 +291,15 @@\n }\n ]\n },\n+ \"CommandExecutionSource\": {\n+ \"enum\": [\n+ \"agent\",\n+ \"userShell\",\n+ \"unifiedExecStartup\",\n+ \"unifiedExecInteraction\"\n+ ],\n+ \"type\": \"string\"\n+ },\n \"CommandExecutionStatus\": {\n \"enum\": [\n \"inProgress\",\n@@ -1007,6 +1016,14 @@\n \"null\"\n ]\n },\n+ \"source\": {\n+ \"allOf\": [\n+ {\n+ \"$ref\": \"#/definitions/CommandExecutionSource\"\n+ }\n+ ],\n+ \"default\": \"agent\"\n+ },\n \"status\": {\n \"$ref\": \"#/definitions/CommandExecutionStatus\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json", - "status": "modified" - }, - { - "additions": 17, - "deletions": 0, - "patch_excerpt": "@@ -291,6 +291,15 @@\n }\n ]\n },\n+ \"CommandExecutionSource\": {\n+ \"enum\": [\n+ \"agent\",\n+ \"userShell\",\n+ \"unifiedExecStartup\",\n+ \"unifiedExecInteraction\"\n+ ],\n+ \"type\": \"string\"\n+ },\n \"CommandExecutionStatus\": {\n \"enum\": [\n \"inProgress\",\n@@ -1007,6 +1016,14 @@\n \"null\"\n ]\n },\n+ \"source\": {\n+ \"allOf\": [\n+ {\n+ \"$ref\": \"#/definitions/CommandExecutionSource\"\n+ }\n+ ],\n+ \"default\": \"agent\"\n+ },\n \"status\": {\n \"$ref\": \"#/definitions/CommandExecutionStatus\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json", - "status": "modified" - }, - { - "additions": 17, - "deletions": 0, - "patch_excerpt": "@@ -291,6 +291,15 @@\n }\n ]\n },\n+ \"CommandExecutionSource\": {\n+ \"enum\": [\n+ \"agent\",\n+ \"userShell\",\n+ \"unifiedExecStartup\",\n+ \"unifiedExecInteraction\"\n+ ],\n+ \"type\": \"string\"\n+ },\n \"CommandExecutionStatus\": {\n \"enum\": [\n \"inProgress\",\n@@ -1007,6 +1016,14 @@\n \"null\"\n ]\n },\n+ \"source\": {\n+ \"allOf\": [\n+ {\n+ \"$ref\": \"#/definitions/CommandExecutionSource\"\n+ }\n+ ],\n+ \"default\": \"agent\"\n+ },\n \"status\": {\n \"$ref\": \"#/definitions/CommandExecutionStatus\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json", - "status": "modified" - }, - { - "additions": 17, - "deletions": 0, - "patch_excerpt": "@@ -353,6 +353,15 @@\n }\n ]\n },\n+ \"CommandExecutionSource\": {\n+ \"enum\": [\n+ \"agent\",\n+ \"userShell\",\n+ \"unifiedExecStartup\",\n+ \"unifiedExecInteraction\"\n+ ],\n+ \"type\": \"string\"\n+ },\n \"CommandExecutionStatus\": {\n \"enum\": [\n \"inProgress\",\n@@ -1249,6 +1258,14 @@\n \"null\"\n ]\n },\n+ \"source\": {\n+ \"allOf\": [\n+ {\n+ \"$ref\": \"#/definitions/CommandExecutionSource\"\n+ }\n+ ],\n+ \"default\": \"agent\"\n+ },\n \"status\": {\n \"$ref\": \"#/definitions/CommandExecutionStatus\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json", - "status": "modified" - }, - { - "additions": 17, - "deletions": 0, - "patch_excerpt": "@@ -291,6 +291,15 @@\n }\n ]\n },\n+ \"CommandExecutionSource\": {\n+ \"enum\": [\n+ \"agent\",\n+ \"userShell\",\n+ \"unifiedExecStartup\",\n+ \"unifiedExecInteraction\"\n+ ],\n+ \"type\": \"string\"\n+ },\n \"CommandExecutionStatus\": {\n \"enum\": [\n \"inProgress\",\n@@ -1007,6 +1016,14 @@\n \"null\"\n ]\n },\n+ \"source\": {\n+ \"allOf\": [\n+ {\n+ \"$ref\": \"#/definitions/CommandExecutionSource\"\n+ }\n+ ],\n+ \"default\": \"agent\"\n+ },\n \"status\": {\n \"$ref\": \"#/definitions/CommandExecutionStatus\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json", - "status": "modified" - }, - { - "additions": 18, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,18 @@\n+{\n+ \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n+ \"properties\": {\n+ \"command\": {\n+ \"description\": \"Shell command string evaluated by the thread's configured shell. Unlike `command/exec`, this intentionally preserves shell syntax such as pipes, redirects, and quoting. This runs unsandboxed with full access rather than inheriting the thread sandbox policy.\",\n+ \"type\": \"string\"\n+ },\n+ \"threadId\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"command\",\n+ \"threadId\"\n+ ],\n+ \"title\": \"ThreadShellCommandParams\",\n+ \"type\": \"object\"\n+}\n\\ No newline at end of file", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadShellCommandParams.json", - "status": "added" - }, - { - "additions": 5, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,5 @@\n+{\n+ \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n+ \"title\": \"ThreadShellCommandResponse\",\n+ \"type\": \"object\"\n+}\n\\ No newline at end of file", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadShellCommandResponse.json", - "status": "added" - }, - { - "additions": 17, - "deletions": 0, - "patch_excerpt": "@@ -353,6 +353,15 @@\n }\n ]\n },\n+ \"CommandExecutionSource\": {\n+ \"enum\": [\n+ \"agent\",\n+ \"userShell\",\n+ \"unifiedExecStartup\",\n+ \"unifiedExecInteraction\"\n+ ],\n+ \"type\": \"string\"\n+ },\n \"CommandExecutionStatus\": {\n \"enum\": [\n \"inProgress\",\n@@ -1249,6 +1258,14 @@\n \"null\"\n ]\n },\n+ \"source\": {\n+ \"allOf\": [\n+ {\n+ \"$ref\": \"#/definitions/CommandExecutionSource\"\n+ }\n+ ],\n+ \"default\": \"agent\"\n+ },\n \"status\": {\n \"$ref\": \"#/definitions/CommandExecutionStatus\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json", - "status": "modified" - }, - { - "additions": 17, - "deletions": 0, - "patch_excerpt": "@@ -291,6 +291,15 @@\n }\n ]\n },\n+ \"CommandExecutionSource\": {\n+ \"enum\": [\n+ \"agent\",\n+ \"userShell\",\n+ \"unifiedExecStartup\",\n+ \"unifiedExecInteraction\"\n+ ],\n+ \"type\": \"string\"\n+ },\n \"CommandExecutionStatus\": {\n \"enum\": [\n \"inProgress\",\n@@ -1007,6 +1016,14 @@\n \"null\"\n ]\n },\n+ \"source\": {\n+ \"allOf\": [\n+ {\n+ \"$ref\": \"#/definitions/CommandExecutionSource\"\n+ }\n+ ],\n+ \"default\": \"agent\"\n+ },\n \"status\": {\n \"$ref\": \"#/definitions/CommandExecutionStatus\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json", - "status": "modified" - }, - { - "additions": 17, - "deletions": 0, - "patch_excerpt": "@@ -291,6 +291,15 @@\n }\n ]\n },\n+ \"CommandExecutionSource\": {\n+ \"enum\": [\n+ \"agent\",\n+ \"userShell\",\n+ \"unifiedExecStartup\",\n+ \"unifiedExecInteraction\"\n+ ],\n+ \"type\": \"string\"\n+ },\n \"CommandExecutionStatus\": {\n \"enum\": [\n \"inProgress\",\n@@ -1007,6 +1016,14 @@\n \"null\"\n ]\n },\n+ \"source\": {\n+ \"allOf\": [\n+ {\n+ \"$ref\": \"#/definitions/CommandExecutionSource\"\n+ }\n+ ],\n+ \"default\": \"agent\"\n+ },\n \"status\": {\n \"$ref\": \"#/definitions/CommandExecutionStatus\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json", - "status": "modified" - }, - { - "additions": 17, - "deletions": 0, - "patch_excerpt": "@@ -291,6 +291,15 @@\n }\n ]\n },\n+ \"CommandExecutionSource\": {\n+ \"enum\": [\n+ \"agent\",\n+ \"userShell\",\n+ \"unifiedExecStartup\",\n+ \"unifiedExecInteraction\"\n+ ],\n+ \"type\": \"string\"\n+ },\n \"CommandExecutionStatus\": {\n \"enum\": [\n \"inProgress\",\n@@ -756,6 +765,14 @@\n \"null\"\n ]\n },\n+ \"source\": {\n+ \"allOf\": [\n+ {\n+ \"$ref\": \"#/definitions/CommandExecutionSource\"\n+ }\n+ ],\n+ \"default\": \"agent\"\n+ },\n \"status\": {\n \"$ref\": \"#/definitions/CommandExecutionStatus\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json", - "status": "modified" - }, - { - "additions": 17, - "deletions": 0, - "patch_excerpt": "@@ -291,6 +291,15 @@\n }\n ]\n },\n+ \"CommandExecutionSource\": {\n+ \"enum\": [\n+ \"agent\",\n+ \"userShell\",\n+ \"unifiedExecStartup\",\n+ \"unifiedExecInteraction\"\n+ ],\n+ \"type\": \"string\"\n+ },\n \"CommandExecutionStatus\": {\n \"enum\": [\n \"inProgress\",\n@@ -756,6 +765,14 @@\n \"null\"\n ]\n },\n+ \"source\": {\n+ \"allOf\": [\n+ {\n+ \"$ref\": \"#/definitions/CommandExecutionSource\"\n+ }\n+ ],\n+ \"default\": \"agent\"\n+ },\n \"status\": {\n \"$ref\": \"#/definitions/CommandExecutionStatus\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json", - "status": "modified" - }, - { - "additions": 17, - "deletions": 0, - "patch_excerpt": "@@ -291,6 +291,15 @@\n }\n ]\n },\n+ \"CommandExecutionSource\": {\n+ \"enum\": [\n+ \"agent\",\n+ \"userShell\",\n+ \"unifiedExecStartup\",\n+ \"unifiedExecInteraction\"\n+ ],\n+ \"type\": \"string\"\n+ },\n \"CommandExecutionStatus\": {\n \"enum\": [\n \"inProgress\",\n@@ -756,6 +765,14 @@\n \"null\"\n ]\n },\n+ \"source\": {\n+ \"allOf\": [\n+ {\n+ \"$ref\": \"#/definitions/CommandExecutionSource\"\n+ }\n+ ],\n+ \"default\": \"agent\"\n+ },\n \"status\": {\n \"$ref\": \"#/definitions/CommandExecutionStatus\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json", - "status": "modified" - }, - { - "additions": 2, - "deletions": 1, - "patch_excerpt": "@@ -49,6 +49,7 @@ import type { ThreadReadParams } from \"./v2/ThreadReadParams\";\n import type { ThreadResumeParams } from \"./v2/ThreadResumeParams\";\n import type { ThreadRollbackParams } from \"./v2/ThreadRollbackParams\";\n import type { ThreadSetNameParams } from \"./v2/ThreadSetNameParams\";\n+import type { ThreadShellCommandParams } from \"./v2/ThreadShellCommandParams\";\n import type { ThreadStartParams } from \"./v2/ThreadStartParams\";\n import type { ThreadUnarchiveParams } from \"./v2/ThreadUnarchiveParams\";\n import type { ThreadUnsubscribeParams } from \"./v2/ThreadUnsubscribeParams\";\n@@ -60,4 +61,4 @@ import type { WindowsSandboxSetupStartParams } from \"./v2/WindowsSandboxSetupSta\n /**\n * Request from the client to the server.\n */\n-export type ClientRequest ={ \"method\": \"initialize\", id: RequestId, params: InitializeParams, } | { \"method\": \"thread/start\", id: RequestId, params: ThreadSta...", - "path": "codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts", - "status": "modified" - }, - { - "additions": 5, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,5 @@\n+// GENERATED CODE! DO NOT MODIFY BY HAND!\n+\n+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n+\n+export type CommandExecutionSource = \"agent\" | \"userShell\" | \"unifiedExecStartup\" | \"unifiedExecInteraction\";", - "path": "codex-rs/app-server-protocol/schema/typescript/v2/CommandExecutionSource.ts", - "status": "added" - }, - { - "additions": 2, - "deletions": 1, - "patch_excerpt": "@@ -8,6 +8,7 @@ import type { CollabAgentState } from \"./CollabAgentState\";\n import type { CollabAgentTool } from \"./CollabAgentTool\";\n import type { CollabAgentToolCallStatus } from \"./CollabAgentToolCallStatus\";\n import type { CommandAction } from \"./CommandAction\";\n+import type { CommandExecutionSource } from \"./CommandExecutionSource\";\n import type { CommandExecutionStatus } from \"./CommandExecutionStatus\";\n import type { DynamicToolCallOutputContentItem } from \"./DynamicToolCallOutputContentItem\";\n import type { DynamicToolCallStatus } from \"./DynamicToolCallStatus\";\n@@ -32,7 +33,7 @@ cwd: string,\n /**\n * Identifier for the underlying PTY process (when available).\n */\n-processId: string | null, status: CommandExecutionStatus, \n+processId: string | null, source: CommandExecutionSource, status: CommandExecutionStatus, \n /**\n * A best-effort parsing of the command to understand the ...", - "path": "codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts", - "status": "modified" - }, - { - "additions": 12, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,12 @@\n+// GENERATED CODE! DO NOT MODIFY BY HAND!\n+\n+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n+\n+export type ThreadShellCommandParams = { threadId: string, \n+/**\n+ * Shell command string evaluated by the thread's configured shell.\n+ * Unlike `command/exec`, this intentionally preserves shell syntax\n+ * such as pipes, redirects, and quoting. This runs unsandboxed with full\n+ * access rather than inheriting the thread sandbox policy.\n+ */\n+command: string, };", - "path": "codex-rs/app-server-protocol/schema/typescript/v2/ThreadShellCommandParams.ts", - "status": "added" - }, - { - "additions": 5, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,5 @@\n+// GENERATED CODE! DO NOT MODIFY BY HAND!\n+\n+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n+\n+export type ThreadShellCommandResponse = Record;", - "path": "codex-rs/app-server-protocol/schema/typescript/v2/ThreadShellCommandResponse.ts", - "status": "added" - }, - { - "additions": 3, - "deletions": 0, - "patch_excerpt": "@@ -55,6 +55,7 @@ export type { CommandExecutionOutputDeltaNotification } from \"./CommandExecution\n export type { CommandExecutionRequestApprovalParams } from \"./CommandExecutionRequestApprovalParams\";\n export type { CommandExecutionRequestApprovalResponse } from \"./CommandExecutionRequestApprovalResponse\";\n export type { CommandExecutionRequestApprovalSkillMetadata } from \"./CommandExecutionRequestApprovalSkillMetadata\";\n+export type { CommandExecutionSource } from \"./CommandExecutionSource\";\n export type { CommandExecutionStatus } from \"./CommandExecutionStatus\";\n export type { Config } from \"./Config\";\n export type { ConfigBatchWriteParams } from \"./ConfigBatchWriteParams\";\n@@ -283,6 +284,8 @@ export type { ThreadRollbackParams } from \"./ThreadRollbackParams\";\n export type { ThreadRollbackResponse } from \"./ThreadRollbackResponse\";\n export type { ThreadSetNameParams } from \"./ThreadSe...", - "path": "codex-rs/app-server-protocol/schema/typescript/v2/index.ts", - "status": "modified" - }, - { - "additions": 4, - "deletions": 0, - "patch_excerpt": "@@ -267,6 +267,10 @@ client_request_definitions! {\n params: v2::ThreadCompactStartParams,\n response: v2::ThreadCompactStartResponse,\n },\n+ ThreadShellCommand => \"thread/shellCommand\" {\n+ params: v2::ThreadShellCommandParams,\n+ response: v2::ThreadShellCommandResponse,\n+ },\n #[experimental(\"thread/backgroundTerminals/clean\")]\n ThreadBackgroundTerminalsClean => \"thread/backgroundTerminals/clean\" {\n params: v2::ThreadBackgroundTerminalsCleanParams,", - "path": "codex-rs/app-server-protocol/src/protocol/common.rs", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -341,6 +341,7 @@ impl ThreadHistoryBuilder {\n command,\n cwd: payload.cwd.clone(),\n process_id: payload.process_id.clone(),\n+ source: payload.source.into(),\n status: CommandExecutionStatus::InProgress,\n command_actions,\n aggregated_output: None,\n@@ -371,6 +372,7 @@ impl ThreadHistoryBuilder {\n command,\n cwd: payload.cwd.clone(),\n process_id: payload.process_id.clone(),\n+ source: payload.source.into(),\n status,\n command_actions,\n aggregated_output,\n@@ -1144,6 +1146,7 @@ impl From<&PendingTurn> for Turn {\n #[cfg(test)]\n mod tests {\n use super::*;\n+ use crate::protocol::v2::CommandExecutionSource;\n use codex_protocol::ThreadId;\n use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem as CoreDynamicT...", - "path": "codex-rs/app-server-protocol/src/protocol/thread_history.rs", - "status": "modified" - }, - { - "additions": 93, - "deletions": 0, - "patch_excerpt": "@@ -52,6 +52,7 @@ use codex_protocol::protocol::AgentStatus as CoreAgentStatus;\n use codex_protocol::protocol::AskForApproval as CoreAskForApproval;\n use codex_protocol::protocol::CodexErrorInfo as CoreCodexErrorInfo;\n use codex_protocol::protocol::CreditsSnapshot as CoreCreditsSnapshot;\n+use codex_protocol::protocol::ExecCommandSource as CoreExecCommandSource;\n use codex_protocol::protocol::ExecCommandStatus as CoreExecCommandStatus;\n use codex_protocol::protocol::GranularApprovalConfig as CoreGranularApprovalConfig;\n use codex_protocol::protocol::GuardianRiskLevel as CoreGuardianRiskLevel;\n@@ -92,6 +93,7 @@ use schemars::JsonSchema;\n use serde::Deserialize;\n use serde::Serialize;\n use serde_json::Value as JsonValue;\n+use serde_with::serde_as;\n use thiserror::Error;\n use ts_rs::TS;\n \n@@ -2874,6 +2876,23 @@ pub struct ThreadCompactStartParams {\n #[ts(export_to = \"v2/\")]\n pub struct Threa...", - "path": "codex-rs/app-server-protocol/src/protocol/v2.rs", - "status": "modified" - }, - { - "additions": 26, - "deletions": 0, - "patch_excerpt": "@@ -139,6 +139,7 @@ Example with notification opt-out:\n - `thread/name/set` \u2014 set or update a thread\u2019s user-facing name for either a loaded thread or a persisted rollout; returns `{}` on success and emits `thread/name/updated` to initialized, opted-in clients. Thread names are not required to be unique; name lookups resolve to the most recently updated thread.\n - `thread/unarchive` \u2014 move an archived rollout file back into the sessions directory; returns the restored `thread` on success and emits `thread/unarchived`.\n - `thread/compact/start` \u2014 trigger conversation history compaction for a thread; returns `{}` immediately while progress streams through standard turn/item notifications.\n+- `thread/shellCommand` \u2014 run a user-initiated `!` shell command against a thread; this runs unsandboxed with full access rather than inheriting the thread sandbox policy. Returns `{}` immediately while p...", - "path": "codex-rs/app-server/README.md", - "status": "modified" - }, - { - "additions": 11, - "deletions": 2, - "patch_excerpt": "@@ -27,6 +27,7 @@ use codex_app_server_protocol::CommandExecutionOutputDeltaNotification;\n use codex_app_server_protocol::CommandExecutionRequestApprovalParams;\n use codex_app_server_protocol::CommandExecutionRequestApprovalResponse;\n use codex_app_server_protocol::CommandExecutionRequestApprovalSkillMetadata;\n+use codex_app_server_protocol::CommandExecutionSource;\n use codex_app_server_protocol::CommandExecutionStatus;\n use codex_app_server_protocol::ContextCompactedNotification;\n use codex_app_server_protocol::DeprecationNoticeNotification;\n@@ -1563,6 +1564,7 @@ pub(crate) async fn apply_bespoke_event_handling(\n command,\n cwd,\n process_id,\n+ source: exec_command_begin_event.source.into(),\n status: CommandExecutionStatus::InProgress,\n command_actions,\n aggregated_output: None,\n...", - "path": "codex-rs/app-server/src/bespoke_event_handling.rs", - "status": "modified" - }, - { - "additions": 58, - "deletions": 0, - "patch_excerpt": "@@ -146,6 +146,8 @@ use codex_app_server_protocol::ThreadResumeResponse;\n use codex_app_server_protocol::ThreadRollbackParams;\n use codex_app_server_protocol::ThreadSetNameParams;\n use codex_app_server_protocol::ThreadSetNameResponse;\n+use codex_app_server_protocol::ThreadShellCommandParams;\n+use codex_app_server_protocol::ThreadShellCommandResponse;\n use codex_app_server_protocol::ThreadSortKey;\n use codex_app_server_protocol::ThreadSourceKind;\n use codex_app_server_protocol::ThreadStartParams;\n@@ -693,6 +695,10 @@ impl CodexMessageProcessor {\n self.thread_read(to_connection_request_id(request_id), params)\n .await;\n }\n+ ClientRequest::ThreadShellCommand { request_id, params } => {\n+ self.thread_shell_command(to_connection_request_id(request_id), params)\n+ .await;\n+ }\n Clie...", - "path": "codex-rs/app-server/src/codex_message_processor.rs", - "status": "modified" - }, - { - "additions": 10, - "deletions": 0, - "patch_excerpt": "@@ -68,6 +68,7 @@ use codex_app_server_protocol::ThreadRealtimeStopParams;\n use codex_app_server_protocol::ThreadResumeParams;\n use codex_app_server_protocol::ThreadRollbackParams;\n use codex_app_server_protocol::ThreadSetNameParams;\n+use codex_app_server_protocol::ThreadShellCommandParams;\n use codex_app_server_protocol::ThreadStartParams;\n use codex_app_server_protocol::ThreadUnarchiveParams;\n use codex_app_server_protocol::ThreadUnsubscribeParams;\n@@ -399,6 +400,15 @@ impl McpProcess {\n self.send_request(\"thread/compact/start\", params).await\n }\n \n+ /// Send a `thread/shellCommand` JSON-RPC request.\n+ pub async fn send_thread_shell_command_request(\n+ &mut self,\n+ params: ThreadShellCommandParams,\n+ ) -> anyhow::Result {\n+ let params = Some(serde_json::to_value(params)?);\n+ self.send_request(\"thread/shellCommand\", params).await\n+ ...", - "path": "codex-rs/app-server/tests/common/mcp_process.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -38,6 +38,7 @@ mod thread_name_websocket;\n mod thread_read;\n mod thread_resume;\n mod thread_rollback;\n+mod thread_shell_command;\n mod thread_start;\n mod thread_status;\n mod thread_unarchive;", - "path": "codex-rs/app-server/tests/suite/v2/mod.rs", - "status": "modified" - }, - { - "additions": 439, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,439 @@\n+use anyhow::Result;\n+use app_test_support::McpProcess;\n+use app_test_support::create_final_assistant_message_sse_response;\n+use app_test_support::create_mock_responses_server_sequence;\n+use app_test_support::create_shell_command_sse_response;\n+use app_test_support::format_with_current_shell_display;\n+use app_test_support::to_response;\n+use codex_app_server_protocol::CommandExecutionApprovalDecision;\n+use codex_app_server_protocol::CommandExecutionOutputDeltaNotification;\n+use codex_app_server_protocol::CommandExecutionRequestApprovalResponse;\n+use codex_app_server_protocol::CommandExecutionSource;\n+use codex_app_server_protocol::CommandExecutionStatus;\n+use codex_app_server_protocol::ItemCompletedNotification;\n+use codex_app_server_protocol::ItemStartedNotification;\n+use codex_app_server_protocol::JSONRPCResponse;\n+use codex_app_server_protocol::RequestId;\n+use codex_a...", - "path": "codex-rs/app-server/tests/suite/v2/thread_shell_command.rs", - "status": "added" - }, - { - "additions": 3, - "deletions": 0, - "patch_excerpt": "@@ -331,6 +331,9 @@ async fn persist_user_shell_output(\n session\n .record_conversation_items(turn_context, std::slice::from_ref(&output_item))\n .await;\n+ // Standalone shell turns can run before any regular user turn, so\n+ // explicitly materialize rollout persistence after recording output.\n+ session.ensure_rollout_materialized().await;\n return;\n }", - "path": "codex-rs/core/src/tasks/user_shell.rs", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -2050,6 +2050,12 @@ impl App {\n app_server.thread_realtime_stop(thread_id).await?;\n Ok(true)\n }\n+ AppCommandView::RunUserShellCommand { command } => {\n+ app_server\n+ .thread_shell_command(thread_id, command.to_string())\n+ .await?;\n+ Ok(true)\n+ }\n AppCommandView::OverrideTurnContext { .. } => Ok(true),\n _ => Ok(false),\n }", - "path": "codex-rs/tui_app_server/src/app.rs", - "status": "modified" - }, - { - "additions": 1384, - "deletions": 51, - "patch_excerpt": "@@ -1,3 +1,16 @@\n+/*\n+This module holds the temporary adapter layer between the TUI and the app\n+server during the hybrid migration period.\n+\n+For now, the TUI still owns its existing direct-core behavior, but startup\n+allocates a local in-process app server and drains its event stream. Keeping\n+the app-server-specific wiring here keeps that transitional logic out of the\n+main `app.rs` orchestration path.\n+\n+As more TUI flows move onto the app-server surface directly, this adapter\n+should shrink and eventually disappear.\n+*/\n+\n use super::App;\n use crate::app_event::AppEvent;\n use crate::app_server_session::AppServerSession;\n@@ -12,8 +25,90 @@ use codex_app_server_protocol::JSONRPCNotification;\n use codex_app_server_protocol::RequestId;\n use codex_app_server_protocol::ServerNotification;\n use codex_app_server_protocol::ServerRequest;\n+#[cfg(test)]\n+use codex_app_server_protocol::Thread;\n...", - "path": "codex-rs/tui_app_server/src/app/app_server_adapter.rs", - "status": "modified" - }, - { - "additions": 8, - "deletions": 0, - "patch_excerpt": "@@ -35,6 +35,9 @@ pub(crate) enum AppCommandView<'a> {\n RealtimeConversationAudio(&'a ConversationAudioParams),\n RealtimeConversationText(&'a ConversationTextParams),\n RealtimeConversationClose,\n+ RunUserShellCommand {\n+ command: &'a str,\n+ },\n UserTurn {\n items: &'a [UserInput],\n cwd: &'a PathBuf,\n@@ -134,6 +137,10 @@ impl AppCommand {\n Self(Op::RealtimeConversationClose)\n }\n \n+ pub(crate) fn run_user_shell_command(command: String) -> Self {\n+ Self(Op::RunUserShellCommand { command })\n+ }\n+\n #[allow(clippy::too_many_arguments)]\n pub(crate) fn user_turn(\n items: Vec,\n@@ -291,6 +298,7 @@ impl AppCommand {\n AppCommandView::RealtimeConversationText(params)\n }\n Op::RealtimeConversationClose => AppCommandView::RealtimeConversationClose,\n+ Op::RunU...", - "path": "codex-rs/tui_app_server/src/app_command.rs", - "status": "modified" - }, - { - "additions": 22, - "deletions": 0, - "patch_excerpt": "@@ -42,6 +42,8 @@ use codex_app_server_protocol::ThreadRollbackParams;\n use codex_app_server_protocol::ThreadRollbackResponse;\n use codex_app_server_protocol::ThreadSetNameParams;\n use codex_app_server_protocol::ThreadSetNameResponse;\n+use codex_app_server_protocol::ThreadShellCommandParams;\n+use codex_app_server_protocol::ThreadShellCommandResponse;\n use codex_app_server_protocol::ThreadStartParams;\n use codex_app_server_protocol::ThreadStartResponse;\n use codex_app_server_protocol::ThreadUnsubscribeParams;\n@@ -492,6 +494,26 @@ impl AppServerSession {\n Ok(())\n }\n \n+ pub(crate) async fn thread_shell_command(\n+ &mut self,\n+ thread_id: ThreadId,\n+ command: String,\n+ ) -> Result<()> {\n+ let request_id = self.next_request_id();\n+ let _: ThreadShellCommandResponse = self\n+ .client\n+ .request_typed(ClientRequest::Thread...", - "path": "codex-rs/tui_app_server/src/app_server_session.rs", - "status": "modified" - }, - { - "additions": 9, - "deletions": 12, - "patch_excerpt": "@@ -5134,13 +5134,7 @@ impl ChatWidget {\n )));\n return;\n }\n- // TODO: Restore `!` support in app-server TUI once command execution can\n- // persist transcript-visible output into thread history with parity to the\n- // legacy TUI.\n- self.add_to_history(history_cell::new_error_event(\n- \"`!` shell commands are unavailable in app-server TUI because command output is not yet persisted in thread history.\".to_string(),\n- ));\n- self.request_redraw();\n+ self.submit_op(AppCommand::run_user_shell_command(cmd.to_string()));\n return;\n }\n \n@@ -5562,6 +5556,7 @@ impl ChatWidget {\n command,\n cwd,\n process_id,\n+ source,\n status,\n command_actions,\n ...", - "path": "codex-rs/tui_app_server/src/chatwidget.rs", - "status": "modified" - }, - { - "additions": 5, - "deletions": 16, - "patch_excerpt": "@@ -8840,7 +8840,7 @@ async fn user_shell_command_renders_output_not_exploring() {\n }\n \n #[tokio::test]\n-async fn bang_shell_command_is_disabled_in_app_server_tui() {\n+async fn bang_shell_command_submits_run_user_shell_command_in_app_server_tui() {\n let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None).await;\n let conversation_id = ThreadId::new();\n let rollout_file = NamedTempFile::new().unwrap();\n@@ -8873,22 +8873,11 @@ async fn bang_shell_command_is_disabled_in_app_server_tui() {\n .set_composer_text(\"!echo hi\".to_string(), Vec::new(), Vec::new());\n chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));\n \n- assert_matches!(op_rx.try_recv(), Err(TryRecvError::Empty));\n-\n- let mut rendered = None;\n- while let Ok(event) = rx.try_recv() {\n- if let AppEvent::InsertHistoryCell(cell) = event {\n- rendered = Some(l...", - "path": "codex-rs/tui_app_server/src/chatwidget/tests.rs", - "status": "modified" - } - ], - "linked_issues": [ - "#14988" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "This PR adds a new `thread/shellCommand` app server API so clients can implement `!` shell commands. These commands are executed within the sandbox, and the command text and output are visible to the model.\r\n\r\nThe internal implementation mirrors the current TUI `!` behavior.\r\n- persist shell command execution as `CommandExecution` thread items, including source and formatted output metadata\r\n- bridge live and replayed app-server command execution events back into the existing `tui_app_server` exec rendering path\r\n\r\nThis PR also wires `tui_app_server` to submit `!` commands through the new API.", - "labels": [], - "merged_at": "2026-03-19T05:42:41Z", - "number": 14988, - "state": "merged", - "title": "Add thread/shellCommand to app server API surface", - "url": "https://github.com/openai/codex/pull/14988" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15011.json b/artifacts/github/bundles/openai-codex-pr-15011.json deleted file mode 100644 index a208f85..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15011.json +++ /dev/null @@ -1,138 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-18T01:14:32Z", - "message": "Forward tool call task headers to MCP HTTP requests", - "sha": "bccce0f2d8e94a961dfb90c47ed7855010118136", - "url": "https://github.com/openai/codex/commit/bccce0f2d8e94a961dfb90c47ed7855010118136" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-18T16:12:10Z", - "message": "codex: fix CI failure on PR #15011", - "sha": "fb27c2058161762864921b5a6a482a766269c2c0", - "url": "https://github.com/openai/codex/commit/fb27c2058161762864921b5a6a482a766269c2c0" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-18T16:41:06Z", - "message": "codex: fix CI failure on PR #15011", - "sha": "de9864340312e1c60307a8c55a965676c6d3db56", - "url": "https://github.com/openai/codex/commit/de9864340312e1c60307a8c55a965676c6d3db56" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-18T16:41:54Z", - "message": "Merge branch 'main' into nicholasclark/tool-call-task-headers", - "sha": "2321532dec64b5bf499df8a23e236220c7f68c85", - "url": "https://github.com/openai/codex/commit/2321532dec64b5bf499df8a23e236220c7f68c85" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-18T17:44:04Z", - "message": "Merge branch 'main' into nicholasclark/tool-call-task-headers", - "sha": "52fdfbcfb889ece9662a9818218e6a30f72fc9f3", - "url": "https://github.com/openai/codex/commit/52fdfbcfb889ece9662a9818218e6a30f72fc9f3" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-18T19:11:32Z", - "message": "Merge branch 'main' into nicholasclark/tool-call-task-headers", - "sha": "1f3ec4172c78175d3ae3ea6b671a3c8e4d83d4ba", - "url": "https://github.com/openai/codex/commit/1f3ec4172c78175d3ae3ea6b671a3c8e4d83d4ba" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-18T23:42:10Z", - "message": "codex: scope MCP request headers to turn lifecycle", - "sha": "732d7ac81fd1ef112dd21714055cc6fc83feb316", - "url": "https://github.com/openai/codex/commit/732d7ac81fd1ef112dd21714055cc6fc83feb316" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-19T00:12:13Z", - "message": "codex: fix MCP header CI regressions", - "sha": "1a2536d12bcdb6eefefdd0897d3fe93b93bbbb45", - "url": "https://github.com/openai/codex/commit/1a2536d12bcdb6eefefdd0897d3fe93b93bbbb45" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "MCP", - "HTTP", - "RMCP", - "X_CODEX_TURN_METADATA_HEADER", - "CODEX_APPS_MCP_SERVER_NAME", - "MCP_SANDBOX_STATE_CAPABILITY", - "WWW_AUTHENTICATE", - "HEADER_LAST_EVENT_ID", - "HEADER_SESSION_ID", - "NON_JSON_RESPONSE_BODY_PREVIEW_BYTES" - ], - "files": [ - { - "additions": 41, - "deletions": 0, - "patch_excerpt": "@@ -125,6 +125,8 @@ use futures::future::BoxFuture;\n use futures::future::Shared;\n use futures::prelude::*;\n use futures::stream::FuturesOrdered;\n+use reqwest::header::HeaderMap;\n+use reqwest::header::HeaderValue;\n use rmcp::model::ListResourceTemplatesResult;\n use rmcp::model::ListResourcesResult;\n use rmcp::model::PaginatedRequestParams;\n@@ -3951,6 +3953,45 @@ impl Session {\n .await\n }\n \n+ pub(crate) async fn sync_mcp_request_headers_for_turn(&self, turn_context: &TurnContext) {\n+ let mut request_headers = HeaderMap::new();\n+ let session_id = self.conversation_id.to_string();\n+ if let Ok(value) = HeaderValue::from_str(&session_id) {\n+ request_headers.insert(\"session_id\", value.clone());\n+ request_headers.insert(\"x-client-request-id\", value);\n+ }\n+ if let Some(turn_metadata) = turn_context.turn_metadata_state.cu...", - "path": "codex-rs/core/src/codex.rs", - "status": "modified" - }, - { - "additions": 33, - "deletions": 2, - "patch_excerpt": "@@ -423,6 +423,7 @@ impl ManagedClient {\n #[derive(Clone)]\n struct AsyncManagedClient {\n client: Shared>>,\n+ request_headers: Arc>>,\n startup_snapshot: Option>,\n startup_complete: Arc,\n tool_plugin_provenance: Arc,\n@@ -448,17 +449,26 @@ impl AsyncManagedClient {\n codex_apps_tools_cache_context.as_ref(),\n )\n .map(|tools| filter_tools(tools, &tool_filter));\n+ let request_headers = Arc::new(StdMutex::new(None));\n let startup_tool_filter = tool_filter;\n let startup_complete = Arc::new(AtomicBool::new(false));\n let startup_complete_for_fut = Arc::clone(&startup_complete);\n+ let request_headers_for_client = Arc::clone(&request_headers);\n let fut = asy...", - "path": "codex-rs/core/src/mcp_connection_manager.rs", - "status": "modified" - }, - { - "additions": 5, - "deletions": 0, - "patch_excerpt": "@@ -4,6 +4,7 @@ use codex_protocol::protocol::McpAuthStatus;\n use rmcp::model::JsonObject;\n use std::collections::HashSet;\n use std::sync::Arc;\n+use std::sync::Mutex as StdMutex;\n use tempfile::tempdir;\n \n fn create_test_tool(server_name: &str, tool_name: &str) -> ToolInfo {\n@@ -413,6 +414,7 @@ async fn list_all_tools_uses_startup_snapshot_while_client_is_pending() {\n CODEX_APPS_MCP_SERVER_NAME.to_string(),\n AsyncManagedClient {\n client: pending_client,\n+ request_headers: Arc::new(StdMutex::new(None)),\n startup_snapshot: Some(startup_tools),\n startup_complete: Arc::new(std::sync::atomic::AtomicBool::new(false)),\n tool_plugin_provenance: Arc::new(ToolPluginProvenance::default()),\n@@ -438,6 +440,7 @@ async fn list_all_tools_blocks_while_client_is_pending_without_startup_snapshot(\n CODEX_APPS_MCP_SERVER_NAME....", - "path": "codex-rs/core/src/mcp_connection_manager_tests.rs", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -153,6 +153,8 @@ impl Session {\n ) {\n self.abort_all_tasks(TurnAbortReason::Replaced).await;\n self.clear_connector_selection().await;\n+ self.sync_mcp_request_headers_for_turn(turn_context.as_ref())\n+ .await;\n \n let task: Arc = Arc::new(task);\n let task_kind = task.kind();\n@@ -233,6 +235,7 @@ impl Session {\n // in-flight approval wait can surface as a model-visible rejection before TurnAborted.\n active_turn.clear_pending().await;\n }\n+ self.clear_mcp_request_headers().await;\n }\n \n pub async fn on_task_finished(\n@@ -262,6 +265,9 @@ impl Session {\n *active = None;\n }\n drop(active);\n+ if should_clear_active_turn {\n+ self.clear_mcp_request_headers().await;\n+ }\n if !pending_input.is_empty() {\n for pendin...", - "path": "codex-rs/core/src/tasks/mod.rs", - "status": "modified" - }, - { - "additions": 69, - "deletions": 10, - "patch_excerpt": "@@ -5,6 +5,7 @@ use std::io;\n use std::path::PathBuf;\n use std::process::Stdio;\n use std::sync::Arc;\n+use std::sync::Mutex as StdMutex;\n use std::time::Duration;\n \n use anyhow::Result;\n@@ -22,6 +23,7 @@ use reqwest::header::HeaderMap;\n use reqwest::header::WWW_AUTHENTICATE;\n use rmcp::model::CallToolRequestParams;\n use rmcp::model::CallToolResult;\n+use rmcp::model::ClientJsonRpcMessage;\n use rmcp::model::ClientNotification;\n use rmcp::model::ClientRequest;\n use rmcp::model::CreateElicitationRequestParams;\n@@ -83,14 +85,45 @@ const HEADER_LAST_EVENT_ID: &str = \"Last-Event-Id\";\n const HEADER_SESSION_ID: &str = \"Mcp-Session-Id\";\n const NON_JSON_RESPONSE_BODY_PREVIEW_BYTES: usize = 8_192;\n \n+fn message_uses_request_scoped_headers(message: &ClientJsonRpcMessage) -> bool {\n+ matches!(\n+ message,\n+ ClientJsonRpcMessage::Request(request)\n+ if request.request.method() ...", - "path": "codex-rs/rmcp-client/src/rmcp_client.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 0, - "patch_excerpt": "@@ -1,5 +1,7 @@\n use std::net::TcpListener;\n use std::path::PathBuf;\n+use std::sync::Arc;\n+use std::sync::Mutex as StdMutex;\n use std::time::Duration;\n use std::time::Instant;\n \n@@ -77,6 +79,7 @@ async fn create_client(base_url: &str) -> anyhow::Result {\n None,\n None,\n OAuthCredentialsStoreMode::File,\n+ Arc::new(StdMutex::new(None)),\n )\n .await?;", - "path": "codex-rs/rmcp-client/tests/streamable_http_recovery.rs", - "status": "modified" - } - ], - "linked_issues": [ - "#15011" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "## Summary\n- forward request-scoped task headers through MCP tool metadata lookups and tool calls\n- apply those headers to streamable HTTP initialize, tools/list, and tools/call requests\n- update affected rmcp/core tests for the new request_headers plumbing\n\n## Testing\n- cargo test -p codex-rmcp-client\n- cargo test -p codex-core (fails on pre-existing unrelated error in core/src/auth_env_telemetry.rs: missing websocket_connect_timeout_ms in ModelProviderInfo initializer)\n- just fix -p codex-rmcp-client\n- just fix -p codex-core (hits the same unrelated auth_env_telemetry.rs error)\n- just fmt", - "labels": [], - "merged_at": "2026-03-19T04:29:37Z", - "number": 15011, - "state": "merged", - "title": "Forward session and turn headers to MCP HTTP requests", - "url": "https://github.com/openai/codex/pull/15011" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15021.json b/artifacts/github/bundles/openai-codex-pr-15021.json deleted file mode 100644 index 3208ae8..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15021.json +++ /dev/null @@ -1,328 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "cconger", - "committed_at": "2026-03-17T17:04:41Z", - "message": "Setup a new v8-poc package", - "sha": "d317180dc2f417f1c9db80012aeadb39554ba516", - "url": "https://github.com/openai/codex/commit/d317180dc2f417f1c9db80012aeadb39554ba516" - }, - { - "author": "cconger", - "committed_at": "2026-03-17T19:52:12Z", - "message": "Merge the v8 building for musl", - "sha": "6c2e61f810939ae228caf7521015bfd3c72c4688", - "url": "https://github.com/openai/codex/commit/6c2e61f810939ae228caf7521015bfd3c72c4688" - }, - { - "author": "cconger", - "committed_at": "2026-03-18T05:06:12Z", - "message": "Update v8 version", - "sha": "ca8204a0d08db305547312905dc19d86b7a946b1", - "url": "https://github.com/openai/codex/commit/ca8204a0d08db305547312905dc19d86b7a946b1" - }, - { - "author": "cconger", - "committed_at": "2026-03-18T06:21:29Z", - "message": "Link in the missing llvm runtime archives", - "sha": "cef4b7715dd828920074e14d05eaee2099ff0ce9", - "url": "https://github.com/openai/codex/commit/cef4b7715dd828920074e14d05eaee2099ff0ce9" - }, - { - "author": "cconger", - "committed_at": "2026-03-18T06:55:53Z", - "message": "Version match", - "sha": "f49672657e2ece0a989337ee53f8016628a8b12e", - "url": "https://github.com/openai/codex/commit/f49672657e2ece0a989337ee53f8016628a8b12e" - }, - { - "author": "cconger", - "committed_at": "2026-03-18T23:13:43Z", - "message": "Do not test this package by default", - "sha": "d040eda677b4a253e2d78e21c751911048eb1ea4", - "url": "https://github.com/openai/codex/commit/d040eda677b4a253e2d78e21c751911048eb1ea4" - }, - { - "author": "cconger", - "committed_at": "2026-03-19T05:57:02Z", - "message": "Add canary workflow for now", - "sha": "dc98f8e77c501282670dc4555c09ba49ff762cf6", - "url": "https://github.com/openai/codex/commit/dc98f8e77c501282670dc4555c09ba49ff762cf6" - }, - { - "author": "cconger", - "committed_at": "2026-03-19T06:47:05Z", - "message": "I swear no v8", - "sha": "4809cd10763964578fdb3cbc45d9418aae8f9437", - "url": "https://github.com/openai/codex/commit/4809cd10763964578fdb3cbc45d9418aae8f9437" - }, - { - "author": "cconger", - "committed_at": "2026-03-19T06:52:23Z", - "message": "Split v8-poc consumer to follow-up branch", - "sha": "1ab65666bc7a5e18f3e45350da604937f4a97595", - "url": "https://github.com/openai/codex/commit/1ab65666bc7a5e18f3e45350da604937f4a97595" - }, - { - "author": "cconger", - "committed_at": "2026-03-19T06:57:56Z", - "message": "rename to v8-canary", - "sha": "2d05604751793517db54b8c3517ed873d2e451d4", - "url": "https://github.com/openai/codex/commit/2d05604751793517db54b8c3517ed873d2e451d4" - }, - { - "author": "cconger", - "committed_at": "2026-03-19T06:59:45Z", - "message": "Remove v8 from cargo.toml for now", - "sha": "93dbe4852cb49fec48bdc0e8e9b1f7bedb7ad56d", - "url": "https://github.com/openai/codex/commit/93dbe4852cb49fec48bdc0e8e9b1f7bedb7ad56d" - }, - { - "author": "cconger", - "committed_at": "2026-03-19T07:32:27Z", - "message": "Fix build", - "sha": "3b573d19c837d19d927d51753ec2de34bf03b7e9", - "url": "https://github.com/openai/codex/commit/3b573d19c837d19d927d51753ec2de34bf03b7e9" - }, - { - "author": "cconger", - "committed_at": "2026-03-19T17:18:51Z", - "message": "Hermetic python + cleanup", - "sha": "e35858585c85884c20c4ddac864a61dc6bbe5fdd", - "url": "https://github.com/openai/codex/commit/e35858585c85884c20c4ddac864a61dc6bbe5fdd" - }, - { - "author": "cconger", - "committed_at": "2026-03-19T17:28:59Z", - "message": "skylib", - "sha": "b9f276129e7cc51ad9d51a86c86eab2998be74cd", - "url": "https://github.com/openai/codex/commit/b9f276129e7cc51ad9d51a86c86eab2998be74cd" - }, - { - "author": "cconger", - "committed_at": "2026-03-19T18:34:30Z", - "message": "Build opt for release", - "sha": "abc472b79a81b88b90a58d9cbc28abbb9537c928", - "url": "https://github.com/openai/codex/commit/abc472b79a81b88b90a58d9cbc28abbb9537c928" - }, - { - "author": "cconger", - "committed_at": "2026-03-19T19:01:14Z", - "message": "Release fixes", - "sha": "d0289aa1a207fade6efff0b2374d08edf778d904", - "url": "https://github.com/openai/codex/commit/d0289aa1a207fade6efff0b2374d08edf778d904" - }, - { - "author": "cconger", - "committed_at": "2026-03-19T20:25:46Z", - "message": "Stronger lean in to opt", - "sha": "14f4bd9e9b023ec7dd44a10e1b70bdd07bdf8a50", - "url": "https://github.com/openai/codex/commit/14f4bd9e9b023ec7dd44a10e1b70bdd07bdf8a50" - }, - { - "author": "cconger", - "committed_at": "2026-03-19T22:55:40Z", - "message": "Address PR feedback", - "sha": "f92d13094c3819239c1c182419ebaca489464825", - "url": "https://github.com/openai/codex/commit/f92d13094c3819239c1c182419ebaca489464825" - }, - { - "author": "cconger", - "committed_at": "2026-03-19T23:02:42Z", - "message": "Drop patch to prebuilt v8", - "sha": "cef0a87eb78aac31eee5a1debfef087503e6f6db", - "url": "https://github.com/openai/codex/commit/cef0a87eb78aac31eee5a1debfef087503e6f6db" - } - ], - "default_branch": "main", - "docs_refs": [ - "third_party/v8/README.md" - ], - "examples_refs": [], - "extracted_flags": [ - "TODO", - "MODULE", - "ROOT", - "MUSL_RUNTIME_ARCHIVE_LABELS", - "LLVM_AR_LABEL", - "LLVM_RANLIB_LABEL", - "--platforms", - "--output", - "--platform", - "--target", - "--output-dir", - "--compilation-mode", - "--test_verbose_timeout_warnings", - "--build_metadata", - "REPO_URL=https://github.com/openai/codex.git", - "COMMIT_SHA=$(git", - "HEAD", - "ROLE=CI", - "VISIBILITY=PUBLIC", - "RUNNER_OS", - "PATH", - "--bazelrc", - "--remote_header", - "BUILDBUDDY_API_KEY", - "PIPESTATUS", - "--remote_cache", - "--remote_executor", - "GITHUB_OUTPUT", - "RELEASE_TAG_INPUT", - "V8_VERSION", - "PLATFORM", - "TARGET", - "--noexperimental_remote_repo_contents_cache", - "DEFAULT_BRANCH", - "GITHUB_REF_NAME", - "GH_TOKEN", - "RELEASE_TAG", - "--repo", - "GITHUB_REPOSITORY", - "BUILD", - "--git", - "UNICODE", - "--no-as-needed", - "--jinja_dir", - "--inspector_protocol_dir", - "INSPECTOR_PROTOCOL_DIR=$$(dirname", - "PYTHONPATH=$$INSPECTOR_PROTOCOL_DIR:external/rules_python++pip+v8_python_deps_311_jinja2/site-packages:external/rules_python++pip+v8_python_deps_311_markupsafe/site-packages:$${PYTHONPATH-}", - "INSPECTOR_PROTOCOL_DIR", - "--config", - "--config_value", - "--output_base", - "BSD", - "LICENSE", - "FP16", - "LLVM", - "--sysroot", - "WORKSPACE_ROOT", - "HAVE_EXECINFO_H", - "ABI", - "V8_LIBC_GLIBC", - "V8_WEAK", - "CSS", - "DCHECK", - "PKU", - "V8_COPTS", - "V8_STATIC_LIBRARY_FEATURES", - "EOF" - ], - "files": [ - { - "additions": 287, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,287 @@\n+#!/usr/bin/env python3\n+\n+from __future__ import annotations\n+\n+import argparse\n+import gzip\n+import re\n+import shutil\n+import subprocess\n+import sys\n+import tempfile\n+import tomllib\n+from pathlib import Path\n+\n+\n+ROOT = Path(__file__).resolve().parents[2]\n+MUSL_RUNTIME_ARCHIVE_LABELS = [\n+ \"@llvm//runtimes/libcxx:libcxx.static\",\n+ \"@llvm//runtimes/libcxx:libcxxabi.static\",\n+]\n+LLVM_AR_LABEL = \"@llvm//tools:llvm-ar\"\n+LLVM_RANLIB_LABEL = \"@llvm//tools:llvm-ranlib\"\n+\n+\n+def bazel_execroot() -> Path:\n+ result = subprocess.run(\n+ [\"bazel\", \"info\", \"execution_root\"],\n+ cwd=ROOT,\n+ check=True,\n+ capture_output=True,\n+ text=True,\n+ )\n+ return Path(result.stdout.strip())\n+\n+\n+def bazel_output_base() -> Path:\n+ result = subprocess.run(\n+ [\"bazel\", \"info\", \"output_base\"],\n+ cwd=ROOT,\n+ check=True,\n+ ...", - "path": ".github/scripts/rusty_v8_bazel.py", - "status": "added" - }, - { - "additions": 11, - "deletions": 1, - "patch_excerpt": "@@ -156,14 +156,20 @@ jobs:\n \n bazel_args=(\n test\n- //...\n --test_verbose_timeout_warnings\n --build_metadata=REPO_URL=https://github.com/openai/codex.git\n --build_metadata=COMMIT_SHA=$(git rev-parse HEAD)\n --build_metadata=ROLE=CI\n --build_metadata=VISIBILITY=PUBLIC\n )\n \n+ bazel_targets=(\n+ //...\n+ # Keep V8 out of the ordinary Bazel CI path. Only the dedicated\n+ # canary and release workflows should build `third_party/v8`.\n+ -//third_party/v8:all\n+ )\n+\n if [[ \"${RUNNER_OS:-}\" != \"Windows\" ]]; then\n # Bazel test sandboxes on macOS may resolve an older Homebrew `node`\n # before the `actions/setup-node` runtime on PATH.\n@@ -183,6 +189,8 @@ jobs:\n --bazelrc=.github/workflows/ci.baz...", - "path": ".github/workflows/bazel.yml", - "status": "modified" - }, - { - "additions": 188, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,188 @@\n+name: rusty-v8-release\n+\n+on:\n+ workflow_dispatch:\n+ inputs:\n+ release_tag:\n+ description: Optional release tag. Defaults to rusty-v8-v.\n+ required: false\n+ type: string\n+ publish:\n+ description: Publish the staged musl artifacts to a GitHub release.\n+ required: false\n+ default: true\n+ type: boolean\n+\n+concurrency:\n+ group: ${{ github.workflow }}::${{ inputs.release_tag || github.run_id }}\n+ cancel-in-progress: false\n+\n+jobs:\n+ metadata:\n+ runs-on: ubuntu-latest\n+ outputs:\n+ release_tag: ${{ steps.release_tag.outputs.release_tag }}\n+ v8_version: ${{ steps.v8_version.outputs.version }}\n+\n+ steps:\n+ - uses: actions/checkout@v6\n+\n+ - name: Set up Python\n+ uses: actions/setup-python@v6\n+ with:\n+ python-version: \"3.12\"\n+\n+ - name: R...", - "path": ".github/workflows/rusty-v8-release.yml", - "status": "added" - }, - { - "additions": 132, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,132 @@\n+name: v8-canary\n+\n+on:\n+ pull_request:\n+ paths:\n+ - \".github/scripts/rusty_v8_bazel.py\"\n+ - \".github/workflows/rusty-v8-release.yml\"\n+ - \".github/workflows/v8-canary.yml\"\n+ - \"MODULE.bazel\"\n+ - \"MODULE.bazel.lock\"\n+ - \"codex-rs/Cargo.toml\"\n+ - \"patches/BUILD.bazel\"\n+ - \"patches/v8_*.patch\"\n+ - \"third_party/v8/**\"\n+ push:\n+ branches:\n+ - main\n+ paths:\n+ - \".github/scripts/rusty_v8_bazel.py\"\n+ - \".github/workflows/rusty-v8-release.yml\"\n+ - \".github/workflows/v8-canary.yml\"\n+ - \"MODULE.bazel\"\n+ - \"MODULE.bazel.lock\"\n+ - \"codex-rs/Cargo.toml\"\n+ - \"patches/BUILD.bazel\"\n+ - \"patches/v8_*.patch\"\n+ - \"third_party/v8/**\"\n+ workflow_dispatch:\n+\n+concurrency:\n+ group: ${{ github.workflow }}::${{ github.event.pull_request.number > 0 && format('pr-{0}', github.event.pull_requ...", - "path": ".github/workflows/v8-canary.yml", - "status": "added" - }, - { - "additions": 5, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,5 @@\n+import %workspace%/.github/workflows/ci.bazelrc\n+\n+common --build_metadata=REPO_URL=https://github.com/openai/codex.git\n+common --build_metadata=ROLE=CI\n+common --build_metadata=VISIBILITY=PUBLIC", - "path": ".github/workflows/v8-ci.bazelrc", - "status": "added" - }, - { - "additions": 26, - "deletions": 0, - "patch_excerpt": "@@ -1,5 +1,6 @@\n module(name = \"codex\")\n \n+bazel_dep(name = \"bazel_skylib\", version = \"1.8.2\")\n bazel_dep(name = \"platforms\", version = \"1.0.0\")\n bazel_dep(name = \"llvm\", version = \"0.6.7\")\n \n@@ -132,6 +133,8 @@ crate.annotation(\n workspace_cargo_toml = \"rust/runfiles/Cargo.toml\",\n )\n \n+http_archive = use_repo_rule(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\")\n+\n llvm = use_extension(\"@llvm//extensions:llvm.bzl\", \"llvm\")\n use_repo(llvm, \"llvm-project\")\n \n@@ -174,6 +177,29 @@ crate.annotation(\n \n inject_repo(crate, \"alsa_lib\")\n \n+bazel_dep(name = \"v8\", version = \"14.6.202.9\")\n+archive_override(\n+ module_name = \"v8\",\n+ integrity = \"sha256-JphDwLAzsd9KvgRZ7eQvNtPU6qGd3XjFt/a/1QITAJU=\",\n+ patch_strip = 3,\n+ patches = [\n+ \"//patches:v8_module_deps.patch\",\n+ \"//patches:v8_bazel_rules.patch\",\n+ \"//patches:v8_source_portability.patch\",\n+ ...", - "path": "MODULE.bazel", - "status": "modified" - }, - { - "additions": 5, - "deletions": 0, - "patch_excerpt": "@@ -12,6 +12,7 @@\n \"https://bcr.bazel.build/modules/abseil-cpp/20240116.2/MODULE.bazel\": \"73939767a4686cd9a520d16af5ab440071ed75cec1a876bf2fcfaf1f71987a16\",\n \"https://bcr.bazel.build/modules/abseil-cpp/20250127.1/MODULE.bazel\": \"c4a89e7ceb9bf1e25cf84a9f830ff6b817b72874088bf5141b314726e46a57c1\",\n \"https://bcr.bazel.build/modules/abseil-cpp/20250512.1/MODULE.bazel\": \"d209fdb6f36ffaf61c509fcc81b19e81b411a999a934a032e10cd009a0226215\",\n+ \"https://bcr.bazel.build/modules/abseil-cpp/20250814.0/MODULE.bazel\": \"c43c16ca2c432566cdb78913964497259903ebe8fb7d9b57b38e9f1425b427b8\",\n \"https://bcr.bazel.build/modules/abseil-cpp/20250814.1/MODULE.bazel\": \"51f2312901470cdab0dbdf3b88c40cd21c62a7ed58a3de45b365ddc5b11bcab2\",\n \"https://bcr.bazel.build/modules/abseil-cpp/20250814.1/source.json\": \"cea3901d7e299da7320700abbaafe57a65d039f10d0d7ea601c4a66938ea4b0c\",\n \"https://bcr.bazel.b...", - "path": "MODULE.bazel.lock", - "status": "modified" - }, - { - "additions": 7, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,7 @@\n+exports_files([\n+ \"aws-lc-sys_memcmp_check.patch\",\n+ \"v8_bazel_rules.patch\",\n+ \"v8_module_deps.patch\",\n+ \"v8_source_portability.patch\",\n+ \"windows-link.patch\",\n+])", - "path": "patches/BUILD.bazel", - "status": "modified" - }, - { - "additions": 227, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,227 @@\n+# What: adapt upstream V8 Bazel rules to this workspace's hermetic toolchains\n+# and externally provided dependencies.\n+# Scope: Bazel BUILD/defs/BUILD.icu integration only, including dependency\n+# wiring, generated sources, and visibility; no standalone V8 source patching.\n+\n+diff --git a/orig/v8-14.6.202.11/bazel/defs.bzl b/mod/v8-14.6.202.11/bazel/defs.bzl\n+index 9648e4a..88efd41 100644\n+--- a/orig/v8-14.6.202.11/bazel/defs.bzl\n++++ b/mod/v8-14.6.202.11/bazel/defs.bzl\n+@@ -97,7 +97,7 @@ v8_config = rule(\n+ \n+ def _default_args():\n+ return struct(\n+- deps = [\":define_flags\", \"@libcxx//:libc++\"],\n++ deps = [\":define_flags\"],\n+ defines = select({\n+ \"@v8//bazel/config:is_windows\": [\n+ \"UNICODE\",\n+@@ -128,12 +128,6 @@ def _default_args():\n+ ],\n+ \"//conditions:default\": [],\n+ }) + select...", - "path": "patches/v8_bazel_rules.patch", - "status": "added" - }, - { - "additions": 256, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,256 @@\n+# What: replace upstream V8 module dependency bootstrapping with repository\n+# declarations and dependency setup that match this Bazel workspace.\n+# Scope: upstream MODULE.bazel only; affects external repo resolution and Bazel\n+# module wiring, not V8 source files.\n+\n+diff --git a/orig/v8-14.6.202.11/MODULE.bazel b/mod/v8-14.6.202.11/MODULE.bazel\n+--- a/orig/v8-14.6.202.11/MODULE.bazel\n++++ b/mod/v8-14.6.202.11/MODULE.bazel\n+@@ -8,7 +8,57 @@\n+ bazel_dep(name = \"rules_python\", version = \"1.0.0\")\n+ bazel_dep(name = \"platforms\", version = \"1.0.0\")\n+ bazel_dep(name = \"abseil-cpp\", version = \"20250814.0\")\n+-bazel_dep(name = \"highway\", version = \"1.2.0\")\n++bazel_dep(name = \"rules_license\", version = \"0.0.4\")\n++\n++git_repository = use_repo_rule(\"@bazel_tools//tools/build_defs/repo:git.bzl\", \"git_repository\")\n++http_archive = use_repo_rule(\"@bazel_tools//tools/build_defs/repo:...", - "path": "patches/v8_module_deps.patch", - "status": "added" - }, - { - "additions": 78, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,78 @@\n+# What: make upstream V8 sources build cleanly in this hermetic toolchain setup.\n+# Scope: minimal source-level portability fixes only, such as libexecinfo guards,\n+# weak glibc symbol handling, and warning annotations; no dependency\n+# include-path rewrites or intentional V8 feature changes.\n+\n+diff --git a/orig/v8-14.6.202.11/src/base/debug/stack_trace_posix.cc b/mod/v8-14.6.202.11/src/base/debug/stack_trace_posix.cc\n+index 6176ed4..a02043d 100644\n+--- a/orig/v8-14.6.202.11/src/base/debug/stack_trace_posix.cc\n++++ b/mod/v8-14.6.202.11/src/base/debug/stack_trace_posix.cc\n+@@ -64,6 +64,7 @@ namespace {\n+ volatile sig_atomic_t in_signal_handler = 0;\n+ bool dump_stack_in_signal_handler = true;\n+ \n++#if HAVE_EXECINFO_H\n+ // The prefix used for mangled symbols, per the Itanium C++ ABI:\n+ // http://www.codesourcery.com/cxx-abi/abi.html#mangling\n+ const char kMangledSymbolPre...", - "path": "patches/v8_source_portability.patch", - "status": "added" - }, - { - "additions": 241, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,241 @@\n+load(\"@bazel_skylib//rules:copy_file.bzl\", \"copy_file\")\n+load(\"@rules_cc//cc:cc_static_library.bzl\", \"cc_static_library\")\n+load(\"@rules_cc//cc:defs.bzl\", \"cc_library\")\n+\n+package(default_visibility = [\"//visibility:public\"])\n+\n+V8_COPTS = [\"-std=c++20\"]\n+\n+V8_STATIC_LIBRARY_FEATURES = [\n+ \"-symbol_check\",\n+ \"-validate-static-library\",\n+]\n+\n+genrule(\n+ name = \"binding_cc\",\n+ srcs = [\"@v8_crate_146_4_0//:binding_cc\"],\n+ outs = [\"binding.cc\"],\n+ cmd = \"\"\"\n+ sed \\\n+ -e '/#include \"v8\\\\/src\\\\/flags\\\\/flags.h\"/d' \\\n+ -e 's|\"v8/src/libplatform/default-platform.h\"|\"src/libplatform/default-platform.h\"|' \\\n+ -e 's| namespace i = v8::internal;| (void)usage;|' \\\n+ -e '/using HelpOptions = i::FlagList::HelpOptions;/d' \\\n+ -e '/HelpOptions help_options = HelpOptions(HelpOptions::kExit, usage);/d' \\\n+ -e 's| i::FlagL...", - "path": "third_party/v8/BUILD.bazel", - "status": "added" - }, - { - "additions": 45, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,45 @@\n+# `rusty_v8` Release Artifacts\n+\n+This directory contains the Bazel packaging used to build and stage\n+target-specific `rusty_v8` release artifacts for Bazel-managed consumers.\n+\n+Current pinned versions:\n+\n+- Rust crate: `v8 = =146.4.0`\n+- Embedded upstream V8 source: `14.6.202.9`\n+\n+The generated release pairs include:\n+\n+- `//third_party/v8:rusty_v8_release_pair_x86_64_apple_darwin`\n+- `//third_party/v8:rusty_v8_release_pair_aarch64_apple_darwin`\n+- `//third_party/v8:rusty_v8_release_pair_x86_64_unknown_linux_gnu`\n+- `//third_party/v8:rusty_v8_release_pair_aarch64_unknown_linux_gnu`\n+- `//third_party/v8:rusty_v8_release_pair_x86_64_unknown_linux_musl`\n+- `//third_party/v8:rusty_v8_release_pair_aarch64_unknown_linux_musl`\n+- `//third_party/v8:rusty_v8_release_pair_x86_64_pc_windows_msvc`\n+- `//third_party/v8:rusty_v8_release_pair_aarch64_pc_windows_msvc`\n+\n+Each relea...", - "path": "third_party/v8/README.md", - "status": "added" - }, - { - "additions": 41, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,41 @@\n+package(default_visibility = [\"//visibility:public\"])\n+\n+filegroup(\n+ name = \"binding_cc\",\n+ srcs = [\"src/binding.cc\"],\n+)\n+\n+filegroup(\n+ name = \"support_h\",\n+ srcs = [\"src/support.h\"],\n+)\n+\n+filegroup(\n+ name = \"src_binding_release_aarch64_apple_darwin\",\n+ srcs = [\"gen/src_binding_release_aarch64-apple-darwin.rs\"],\n+)\n+\n+filegroup(\n+ name = \"src_binding_release_x86_64_apple_darwin\",\n+ srcs = [\"gen/src_binding_release_x86_64-apple-darwin.rs\"],\n+)\n+\n+filegroup(\n+ name = \"src_binding_release_aarch64_unknown_linux_gnu\",\n+ srcs = [\"gen/src_binding_release_aarch64-unknown-linux-gnu.rs\"],\n+)\n+\n+filegroup(\n+ name = \"src_binding_release_x86_64_unknown_linux_gnu\",\n+ srcs = [\"gen/src_binding_release_x86_64-unknown-linux-gnu.rs\"],\n+)\n+\n+filegroup(\n+ name = \"src_binding_release_x86_64_pc_windows_msvc\",\n+ srcs = [\"gen/src_binding_release_x8...", - "path": "third_party/v8/v8_crate.BUILD.bazel", - "status": "added" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "Alternative approach, we use rusty_v8 for all platforms that its predefined, but lets build from source a musl v8 version with bazel for x86 and aarch64 only. We would need to release this on github and then use the release.\r\n", - "labels": [], - "merged_at": "2026-03-20T01:05:24Z", - "number": 15021, - "state": "merged", - "title": "V8 Bazel Build", - "url": "https://github.com/openai/codex/pull/15021" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15041.json b/artifacts/github/bundles/openai-codex-pr-15041.json deleted file mode 100644 index 0b56c09..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15041.json +++ /dev/null @@ -1,309 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "xl-openai", - "committed_at": "2026-03-18T06:51:24Z", - "message": "feat: support product-scoped plugins.", - "sha": "d3b349c82e98e6d23b9d67ab20bb94ee80f0597f", - "url": "https://github.com/openai/codex/commit/d3b349c82e98e6d23b9d67ab20bb94ee80f0597f" - }, - { - "author": "xl-openai", - "committed_at": "2026-03-19T04:06:54Z", - "message": "Address comments.", - "sha": "06e9a1ad83a7634f4ef96341095499d474390f9b", - "url": "https://github.com/openai/codex/commit/06e9a1ad83a7634f4ef96341095499d474390f9b" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "--session-source", - "VSC", - "CHANNEL_CAPACITY", - "DEFAULT_LISTEN_URL", - "SOURCE", - "DEFAULT_CLIENT_NAME", - "MCP", - "CODEX_HOME", - "RUST_LOG", - "CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR", - "CHATGPT", - "DEFAULT_TIMEOUT", - "SKILL", - "CODEX", - "SESSIONS_SUBDIR", - "ARCHIVED_SESSIONS_SUBDIR", - "INTERACTIVE_SESSION_SOURCES", - "TEST_PROVIDER", - "OPENAI_PROVIDER_ID", - "THREAD_CREATED_CHANNEL_CAPACITY", - "ATLAS", - "PAGE_SIZE" - ], - "files": [ - { - "additions": 13, - "deletions": 0, - "patch_excerpt": "@@ -1880,6 +1880,19 @@\n ],\n \"type\": \"string\"\n },\n+ {\n+ \"additionalProperties\": false,\n+ \"properties\": {\n+ \"custom\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"custom\"\n+ ],\n+ \"title\": \"CustomSessionSource\",\n+ \"type\": \"object\"\n+ },\n {\n \"additionalProperties\": false,\n \"properties\": {", - "path": "codex-rs/app-server-protocol/schema/json/ServerNotification.json", - "status": "modified" - }, - { - "additions": 13, - "deletions": 0, - "patch_excerpt": "@@ -10991,6 +10991,19 @@\n ],\n \"type\": \"string\"\n },\n+ {\n+ \"additionalProperties\": false,\n+ \"properties\": {\n+ \"custom\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"custom\"\n+ ],\n+ \"title\": \"CustomSessionSource\",\n+ \"type\": \"object\"\n+ },\n {\n \"additionalProperties\": false,\n \"properties\": {", - "path": "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json", - "status": "modified" - }, - { - "additions": 13, - "deletions": 0, - "patch_excerpt": "@@ -8751,6 +8751,19 @@\n ],\n \"type\": \"string\"\n },\n+ {\n+ \"additionalProperties\": false,\n+ \"properties\": {\n+ \"custom\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"custom\"\n+ ],\n+ \"title\": \"CustomSessionSource\",\n+ \"type\": \"object\"\n+ },\n {\n \"additionalProperties\": false,\n \"properties\": {", - "path": "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json", - "status": "modified" - }, - { - "additions": 13, - "deletions": 0, - "patch_excerpt": "@@ -826,6 +826,19 @@\n ],\n \"type\": \"string\"\n },\n+ {\n+ \"additionalProperties\": false,\n+ \"properties\": {\n+ \"custom\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"custom\"\n+ ],\n+ \"title\": \"CustomSessionSource\",\n+ \"type\": \"object\"\n+ },\n {\n \"additionalProperties\": false,\n \"properties\": {", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json", - "status": "modified" - }, - { - "additions": 13, - "deletions": 0, - "patch_excerpt": "@@ -584,6 +584,19 @@\n ],\n \"type\": \"string\"\n },\n+ {\n+ \"additionalProperties\": false,\n+ \"properties\": {\n+ \"custom\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"custom\"\n+ ],\n+ \"title\": \"CustomSessionSource\",\n+ \"type\": \"object\"\n+ },\n {\n \"additionalProperties\": false,\n \"properties\": {", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json", - "status": "modified" - }, - { - "additions": 13, - "deletions": 0, - "patch_excerpt": "@@ -584,6 +584,19 @@\n ],\n \"type\": \"string\"\n },\n+ {\n+ \"additionalProperties\": false,\n+ \"properties\": {\n+ \"custom\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"custom\"\n+ ],\n+ \"title\": \"CustomSessionSource\",\n+ \"type\": \"object\"\n+ },\n {\n \"additionalProperties\": false,\n \"properties\": {", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json", - "status": "modified" - }, - { - "additions": 13, - "deletions": 0, - "patch_excerpt": "@@ -584,6 +584,19 @@\n ],\n \"type\": \"string\"\n },\n+ {\n+ \"additionalProperties\": false,\n+ \"properties\": {\n+ \"custom\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"custom\"\n+ ],\n+ \"title\": \"CustomSessionSource\",\n+ \"type\": \"object\"\n+ },\n {\n \"additionalProperties\": false,\n \"properties\": {", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json", - "status": "modified" - }, - { - "additions": 13, - "deletions": 0, - "patch_excerpt": "@@ -826,6 +826,19 @@\n ],\n \"type\": \"string\"\n },\n+ {\n+ \"additionalProperties\": false,\n+ \"properties\": {\n+ \"custom\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"custom\"\n+ ],\n+ \"title\": \"CustomSessionSource\",\n+ \"type\": \"object\"\n+ },\n {\n \"additionalProperties\": false,\n \"properties\": {", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json", - "status": "modified" - }, - { - "additions": 13, - "deletions": 0, - "patch_excerpt": "@@ -584,6 +584,19 @@\n ],\n \"type\": \"string\"\n },\n+ {\n+ \"additionalProperties\": false,\n+ \"properties\": {\n+ \"custom\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"custom\"\n+ ],\n+ \"title\": \"CustomSessionSource\",\n+ \"type\": \"object\"\n+ },\n {\n \"additionalProperties\": false,\n \"properties\": {", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json", - "status": "modified" - }, - { - "additions": 13, - "deletions": 0, - "patch_excerpt": "@@ -826,6 +826,19 @@\n ],\n \"type\": \"string\"\n },\n+ {\n+ \"additionalProperties\": false,\n+ \"properties\": {\n+ \"custom\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"custom\"\n+ ],\n+ \"title\": \"CustomSessionSource\",\n+ \"type\": \"object\"\n+ },\n {\n \"additionalProperties\": false,\n \"properties\": {", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json", - "status": "modified" - }, - { - "additions": 13, - "deletions": 0, - "patch_excerpt": "@@ -584,6 +584,19 @@\n ],\n \"type\": \"string\"\n },\n+ {\n+ \"additionalProperties\": false,\n+ \"properties\": {\n+ \"custom\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"custom\"\n+ ],\n+ \"title\": \"CustomSessionSource\",\n+ \"type\": \"object\"\n+ },\n {\n \"additionalProperties\": false,\n \"properties\": {", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json", - "status": "modified" - }, - { - "additions": 13, - "deletions": 0, - "patch_excerpt": "@@ -584,6 +584,19 @@\n ],\n \"type\": \"string\"\n },\n+ {\n+ \"additionalProperties\": false,\n+ \"properties\": {\n+ \"custom\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"custom\"\n+ ],\n+ \"title\": \"CustomSessionSource\",\n+ \"type\": \"object\"\n+ },\n {\n \"additionalProperties\": false,\n \"properties\": {", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -3,4 +3,4 @@\n // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n import type { SubAgentSource } from \"./SubAgentSource\";\n \n-export type SessionSource = \"cli\" | \"vscode\" | \"exec\" | \"mcp\" | { \"subagent\": SubAgentSource } | \"unknown\";\n+export type SessionSource = \"cli\" | \"vscode\" | \"exec\" | \"mcp\" | { \"custom\": string } | { \"subagent\": SubAgentSource } | \"unknown\";", - "path": "codex-rs/app-server-protocol/schema/typescript/SessionSource.ts", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -3,4 +3,4 @@\n // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n import type { SubAgentSource } from \"../SubAgentSource\";\n \n-export type SessionSource = \"cli\" | \"vscode\" | \"exec\" | \"appServer\" | { \"subAgent\": SubAgentSource } | \"unknown\";\n+export type SessionSource = \"cli\" | \"vscode\" | \"exec\" | \"appServer\" | { \"custom\": string } | { \"subAgent\": SubAgentSource } | \"unknown\";", - "path": "codex-rs/app-server-protocol/schema/typescript/v2/SessionSource.ts", - "status": "modified" - }, - { - "additions": 3, - "deletions": 0, - "patch_excerpt": "@@ -1467,6 +1467,7 @@ pub enum SessionSource {\n VsCode,\n Exec,\n AppServer,\n+ Custom(String),\n SubAgent(CoreSubAgentSource),\n #[serde(other)]\n Unknown,\n@@ -1479,6 +1480,7 @@ impl From for SessionSource {\n CoreSessionSource::VSCode => SessionSource::VsCode,\n CoreSessionSource::Exec => SessionSource::Exec,\n CoreSessionSource::Mcp => SessionSource::AppServer,\n+ CoreSessionSource::Custom(source) => SessionSource::Custom(source),\n CoreSessionSource::SubAgent(sub) => SessionSource::SubAgent(sub),\n CoreSessionSource::Unknown => SessionSource::Unknown,\n }\n@@ -1492,6 +1494,7 @@ impl From for CoreSessionSource {\n SessionSource::VsCode => CoreSessionSource::VSCode,\n SessionSource::Exec => CoreSessionSource::Exec,\n SessionSourc...", - "path": "codex-rs/app-server-protocol/src/protocol/v2.rs", - "status": "modified" - }, - { - "additions": 12, - "deletions": 1, - "patch_excerpt": "@@ -5573,6 +5573,17 @@ impl CodexMessageProcessor {\n };\n let app_summaries =\n plugin_app_helpers::load_plugin_app_summaries(&config, &outcome.plugin.apps).await;\n+ let visible_skills = outcome\n+ .plugin\n+ .skills\n+ .iter()\n+ .filter(|skill| {\n+ skill.matches_product_restriction_for_product(\n+ self.thread_manager.session_source().restriction_product(),\n+ )\n+ })\n+ .cloned()\n+ .collect::>();\n let plugin = PluginDetail {\n marketplace_name: outcome.marketplace_name,\n marketplace_path: outcome.marketplace_path,\n@@ -5587,7 +5598,7 @@ impl CodexMessageProcessor {\n interface: outcome.plugin.interface.map(plugin_interface_to_info),\n },\n description: outcome.plug...", - "path": "codex-rs/app-server/src/codex_message_processor.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 1, - "patch_excerpt": "@@ -336,6 +336,7 @@ pub async fn run_main(\n loader_overrides,\n default_analytics_enabled,\n AppServerTransport::Stdio,\n+ SessionSource::VSCode,\n )\n .await\n }\n@@ -346,6 +347,7 @@ pub async fn run_main_with_transport(\n loader_overrides: LoaderOverrides,\n default_analytics_enabled: bool,\n transport: AppServerTransport,\n+ session_source: SessionSource,\n ) -> IoResult<()> {\n let (transport_event_tx, mut transport_event_rx) =\n mpsc::channel::(CHANNEL_CAPACITY);\n@@ -621,7 +623,7 @@ pub async fn run_main_with_transport(\n feedback: feedback.clone(),\n log_db,\n config_warnings,\n- session_source: SessionSource::VSCode,\n+ session_source,\n enable_codex_api_key_env: false,\n });\n let mut thread_created_rx = processor.thread_created_receive...", - "path": "codex-rs/app-server/src/lib.rs", - "status": "modified" - }, - { - "additions": 12, - "deletions": 0, - "patch_excerpt": "@@ -4,6 +4,7 @@ use codex_app_server::run_main_with_transport;\n use codex_arg0::Arg0DispatchPaths;\n use codex_arg0::arg0_dispatch_or_else;\n use codex_core::config_loader::LoaderOverrides;\n+use codex_protocol::protocol::SessionSource;\n use codex_utils_cli::CliConfigOverrides;\n use std::path::PathBuf;\n \n@@ -21,6 +22,15 @@ struct AppServerArgs {\n default_value = AppServerTransport::DEFAULT_LISTEN_URL\n )]\n listen: AppServerTransport,\n+\n+ /// Session source used to derive product restrictions and metadata.\n+ #[arg(\n+ long = \"session-source\",\n+ value_name = \"SOURCE\",\n+ default_value = \"vscode\",\n+ value_parser = SessionSource::from_startup_arg\n+ )]\n+ session_source: SessionSource,\n }\n \n fn main() -> anyhow::Result<()> {\n@@ -32,13 +42,15 @@ fn main() -> anyhow::Result<()> {\n ..Default::default()\n };\n let transpor...", - "path": "codex-rs/app-server/src/main.rs", - "status": "modified" - }, - { - "additions": 14, - "deletions": 1, - "patch_excerpt": "@@ -95,7 +95,11 @@ pub const DEFAULT_CLIENT_NAME: &str = \"codex-app-server-tests\";\n \n impl McpProcess {\n pub async fn new(codex_home: &Path) -> anyhow::Result {\n- Self::new_with_env(codex_home, &[]).await\n+ Self::new_with_env_and_args(codex_home, &[], &[]).await\n+ }\n+\n+ pub async fn new_with_args(codex_home: &Path, args: &[&str]) -> anyhow::Result {\n+ Self::new_with_env_and_args(codex_home, &[], args).await\n }\n \n /// Creates a new MCP process, allowing tests to override or remove\n@@ -106,6 +110,14 @@ impl McpProcess {\n pub async fn new_with_env(\n codex_home: &Path,\n env_overrides: &[(&str, Option<&str>)],\n+ ) -> anyhow::Result {\n+ Self::new_with_env_and_args(codex_home, env_overrides, &[]).await\n+ }\n+\n+ async fn new_with_env_and_args(\n+ codex_home: &Path,\n+ env_overrides: &[(&str, O...", - "path": "codex-rs/app-server/tests/common/mcp_process.rs", - "status": "modified" - }, - { - "additions": 50, - "deletions": 0, - "patch_excerpt": "@@ -146,6 +146,56 @@ async fn plugin_install_returns_invalid_request_for_not_available_plugin() -> Re\n Ok(())\n }\n \n+#[tokio::test]\n+async fn plugin_install_returns_invalid_request_for_disallowed_product_plugin() -> Result<()> {\n+ let codex_home = TempDir::new()?;\n+ let repo_root = TempDir::new()?;\n+ std::fs::create_dir_all(repo_root.path().join(\".agents/plugins\"))?;\n+ std::fs::write(\n+ repo_root.path().join(\".agents/plugins/marketplace.json\"),\n+ r#\"{\n+ \"name\": \"debug\",\n+ \"plugins\": [\n+ {\n+ \"name\": \"sample-plugin\",\n+ \"source\": {\n+ \"source\": \"local\",\n+ \"path\": \"./sample-plugin\"\n+ },\n+ \"policy\": {\n+ \"products\": [\"CHATGPT\"]\n+ }\n+ }\n+ ]\n+}\"#,\n+ )?;\n+ write_plugin_source(repo_root.path(), \"sample-plugin\", &[])?;\n+ let marketplace_path =\n+ AbsolutePathBuf::try_from(repo_root.path().join(\".agents...", - "path": "codex-rs/app-server/tests/suite/v2/plugin_install.rs", - "status": "modified" - }, - { - "additions": 27, - "deletions": 0, - "patch_excerpt": "@@ -25,6 +25,7 @@ async fn plugin_read_returns_plugin_details_with_bundle_contents() -> Result<()>\n std::fs::create_dir_all(repo_root.path().join(\".agents/plugins\"))?;\n std::fs::create_dir_all(plugin_root.join(\".codex-plugin\"))?;\n std::fs::create_dir_all(plugin_root.join(\"skills/thread-summarizer\"))?;\n+ std::fs::create_dir_all(plugin_root.join(\"skills/chatgpt-only\"))?;\n std::fs::write(\n repo_root.path().join(\".agents/plugins/marketplace.json\"),\n r#\"{\n@@ -79,6 +80,32 @@ description: Summarize email threads\n ---\n \n # Thread Summarizer\n+\"#,\n+ )?;\n+ std::fs::write(\n+ plugin_root.join(\"skills/chatgpt-only/SKILL.md\"),\n+ r#\"---\n+name: chatgpt-only\n+description: Visible only for ChatGPT\n+---\n+\n+# ChatGPT Only\n+\"#,\n+ )?;\n+ std::fs::create_dir_all(plugin_root.join(\"skills/thread-summarizer/agents\"))?;\n+ std::fs::write(\n+ plugin...", - "path": "codex-rs/app-server/tests/suite/v2/plugin_read.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -649,6 +649,7 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {\n codex_core::config_loader::LoaderOverrides::default(),\n app_server_cli.analytics_default_enabled,\n transport,\n+ codex_protocol::protocol::SessionSource::VSCode,\n )\n .await?;\n }", - "path": "codex-rs/cli/src/main.rs", - "status": "modified" - }, - { - "additions": 61, - "deletions": 5, - "patch_excerpt": "@@ -42,6 +42,7 @@ use crate::skills::loader::SkillRoot;\n use crate::skills::loader::load_skills_from_roots;\n use codex_app_server_protocol::ConfigValueWriteParams;\n use codex_app_server_protocol::MergeStrategy;\n+use codex_protocol::protocol::Product;\n use codex_protocol::protocol::SkillScope;\n use codex_utils_absolute_path::AbsolutePathBuf;\n use serde::Deserialize;\n@@ -461,16 +462,32 @@ pub struct PluginsManager {\n store: PluginStore,\n featured_plugin_ids_cache: RwLock>,\n cached_enabled_outcome: RwLock>,\n+ restriction_product: Option,\n analytics_events_client: RwLock>,\n }\n \n impl PluginsManager {\n pub fn new(codex_home: PathBuf) -> Self {\n+ Self::new_with_restriction_product(codex_home, Some(Product::Codex))\n+ }\n+\n+ pub fn new_with_restriction_product(\n+ ...", - "path": "codex-rs/core/src/plugins/manager.rs", - "status": "modified" - }, - { - "additions": 5, - "deletions": 1, - "patch_excerpt": "@@ -146,6 +146,7 @@ impl MarketplaceError {\n pub fn resolve_marketplace_plugin(\n marketplace_path: &AbsolutePathBuf,\n plugin_name: &str,\n+ restriction_product: Option,\n ) -> Result {\n let marketplace = load_raw_marketplace_manifest(marketplace_path)?;\n let marketplace_name = marketplace.name;\n@@ -168,7 +169,10 @@ pub fn resolve_marketplace_plugin(\n ..\n } = plugin;\n let install_policy = policy.installation;\n- if install_policy == MarketplacePluginInstallPolicy::NotAvailable {\n+ let product_allowed = policy.products.is_empty()\n+ || restriction_product\n+ .is_some_and(|product| product.matches_product_restriction(&policy.products));\n+ if install_policy == MarketplacePluginInstallPolicy::NotAvailable || !product_allowed {\n return Err(MarketplaceError::PluginNotAvailable ...", - "path": "codex-rs/core/src/plugins/marketplace.rs", - "status": "modified" - }, - { - "additions": 44, - "deletions": 0, - "patch_excerpt": "@@ -30,6 +30,7 @@ fn resolve_marketplace_plugin_finds_repo_marketplace_plugin() {\n let resolved = resolve_marketplace_plugin(\n &AbsolutePathBuf::try_from(repo_root.join(\".agents/plugins/marketplace.json\")).unwrap(),\n \"local-plugin\",\n+ Some(Product::Codex),\n )\n .unwrap();\n \n@@ -59,6 +60,7 @@ fn resolve_marketplace_plugin_reports_missing_plugin() {\n let err = resolve_marketplace_plugin(\n &AbsolutePathBuf::try_from(repo_root.join(\".agents/plugins/marketplace.json\")).unwrap(),\n \"missing\",\n+ Some(Product::Codex),\n )\n .unwrap_err();\n \n@@ -297,6 +299,7 @@ fn list_marketplaces_keeps_distinct_entries_for_same_name() {\n let resolved = resolve_marketplace_plugin(\n &AbsolutePathBuf::try_from(repo_marketplace).unwrap(),\n \"local-plugin\",\n+ Some(Product::Codex),\n )\n .unwrap();\n \n@@ -687,6 +690,7 @@ ...", - "path": "codex-rs/core/src/plugins/marketplace_tests.rs", - "status": "modified" - }, - { - "additions": 10, - "deletions": 2, - "patch_excerpt": "@@ -1,11 +1,19 @@\n //! Rollout module: persistence and discovery of session rollout files.\n \n+use std::sync::LazyLock;\n+\n use codex_protocol::protocol::SessionSource;\n \n pub const SESSIONS_SUBDIR: &str = \"sessions\";\n pub const ARCHIVED_SESSIONS_SUBDIR: &str = \"archived_sessions\";\n-pub const INTERACTIVE_SESSION_SOURCES: &[SessionSource] =\n- &[SessionSource::Cli, SessionSource::VSCode];\n+pub static INTERACTIVE_SESSION_SOURCES: LazyLock> = LazyLock::new(|| {\n+ vec![\n+ SessionSource::Cli,\n+ SessionSource::VSCode,\n+ SessionSource::Custom(\"atlas\".to_string()),\n+ SessionSource::Custom(\"chatgpt\".to_string()),\n+ ]\n+});\n \n pub(crate) mod error;\n pub mod list;", - "path": "codex-rs/core/src/rollout/mod.rs", - "status": "modified" - }, - { - "additions": 13, - "deletions": 13, - "patch_excerpt": "@@ -516,7 +516,7 @@ async fn test_list_conversations_latest_first() {\n 10,\n None,\n ThreadSortKey::CreatedAt,\n- INTERACTIVE_SESSION_SOURCES,\n+ INTERACTIVE_SESSION_SOURCES.as_slice(),\n Some(provider_filter.as_slice()),\n TEST_PROVIDER,\n )\n@@ -665,7 +665,7 @@ async fn test_pagination_cursor() {\n 2,\n None,\n ThreadSortKey::CreatedAt,\n- INTERACTIVE_SESSION_SOURCES,\n+ INTERACTIVE_SESSION_SOURCES.as_slice(),\n Some(provider_filter.as_slice()),\n TEST_PROVIDER,\n )\n@@ -733,7 +733,7 @@ async fn test_pagination_cursor() {\n 2,\n page1.next_cursor.as_ref(),\n ThreadSortKey::CreatedAt,\n- INTERACTIVE_SESSION_SOURCES,\n+ INTERACTIVE_SESSION_SOURCES.as_slice(),\n Some(provider_filter.as_slice()),\n TEST_PROVIDER,\n )\n@@ -801,7 +801,7 @@ async ...", - "path": "codex-rs/core/src/rollout/tests.rs", - "status": "modified" - }, - { - "additions": 33, - "deletions": 4, - "patch_excerpt": "@@ -6,6 +6,7 @@ use std::sync::Arc;\n use std::sync::RwLock;\n \n use codex_app_server_protocol::ConfigLayerSource;\n+use codex_protocol::protocol::Product;\n use codex_protocol::protocol::SkillScope;\n use codex_utils_absolute_path::AbsolutePathBuf;\n use toml::Value as TomlValue;\n@@ -30,6 +31,7 @@ use crate::skills::system::uninstall_system_skills;\n pub struct SkillsManager {\n codex_home: PathBuf,\n plugins_manager: Arc,\n+ restriction_product: Option,\n cache_by_cwd: RwLock>,\n cache_by_config: RwLock>,\n }\n@@ -39,10 +41,25 @@ impl SkillsManager {\n codex_home: PathBuf,\n plugins_manager: Arc,\n bundled_skills_enabled: bool,\n+ ) -> Self {\n+ Self::new_with_restriction_product(\n+ codex_home,\n+ plugins_mana...", - "path": "codex-rs/core/src/skills/manager.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -20,4 +20,5 @@ pub use model::SkillError;\n pub use model::SkillLoadOutcome;\n pub use model::SkillMetadata;\n pub use model::SkillPolicy;\n+pub use model::filter_skill_load_outcome_for_product;\n pub use render::render_skills_section;", - "path": "codex-rs/core/src/skills/mod.rs", - "status": "modified" - }, - { - "additions": 41, - "deletions": 0, - "patch_excerpt": "@@ -42,6 +42,21 @@ impl SkillMetadata {\n .and_then(|policy| policy.allow_implicit_invocation)\n .unwrap_or(true)\n }\n+\n+ pub fn matches_product_restriction_for_product(\n+ &self,\n+ restriction_product: Option,\n+ ) -> bool {\n+ match &self.policy {\n+ Some(policy) => {\n+ policy.products.is_empty()\n+ || restriction_product.is_some_and(|product| {\n+ product.matches_product_restriction(&policy.products)\n+ })\n+ }\n+ None => true,\n+ }\n+ }\n }\n \n #[derive(Debug, Clone, PartialEq, Eq, Default)]\n@@ -115,3 +130,29 @@ impl SkillLoadOutcome {\n .map(|skill| (skill, self.is_skill_enabled(skill)))\n }\n }\n+\n+pub fn filter_skill_load_outcome_for_product(\n+ mut outcome: SkillLoadOutcome,\n+ restriction_product: O...", - "path": "codex-rs/core/src/skills/model.rs", - "status": "modified" - }, - { - "additions": 14, - "deletions": 4, - "patch_excerpt": "@@ -169,18 +169,23 @@ impl ThreadManager {\n collaboration_modes_config: CollaborationModesConfig,\n ) -> Self {\n let codex_home = config.codex_home.clone();\n+ let restriction_product = session_source.restriction_product();\n let openai_models_provider = config\n .model_providers\n .get(OPENAI_PROVIDER_ID)\n .cloned()\n .unwrap_or_else(|| ModelProviderInfo::create_openai_provider(/*base_url*/ None));\n let (thread_created_tx, _) = broadcast::channel(THREAD_CREATED_CHANNEL_CAPACITY);\n- let plugins_manager = Arc::new(PluginsManager::new(codex_home.clone()));\n+ let plugins_manager = Arc::new(PluginsManager::new_with_restriction_product(\n+ codex_home.clone(),\n+ restriction_product,\n+ ));\n let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager))...", - "path": "codex-rs/core/src/thread_manager.rs", - "status": "modified" - }, - { - "additions": 146, - "deletions": 0, - "patch_excerpt": "@@ -2272,6 +2272,7 @@ pub enum SessionSource {\n VSCode,\n Exec,\n Mcp,\n+ Custom(String),\n SubAgent(SubAgentSource),\n #[serde(other)]\n Unknown,\n@@ -2302,13 +2303,31 @@ impl fmt::Display for SessionSource {\n SessionSource::VSCode => f.write_str(\"vscode\"),\n SessionSource::Exec => f.write_str(\"exec\"),\n SessionSource::Mcp => f.write_str(\"mcp\"),\n+ SessionSource::Custom(source) => f.write_str(source),\n SessionSource::SubAgent(sub_source) => write!(f, \"subagent_{sub_source}\"),\n SessionSource::Unknown => f.write_str(\"unknown\"),\n }\n }\n }\n \n impl SessionSource {\n+ pub fn from_startup_arg(value: &str) -> Result {\n+ let trimmed = value.trim();\n+ if trimmed.is_empty() {\n+ return Err(\"session source must not be empty\");\n+ }\n+\n+ let nor...", - "path": "codex-rs/protocol/src/protocol.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -735,7 +735,7 @@ async fn run_ratatui_app(\n /*page_size*/ 1,\n /*cursor*/ None,\n ThreadSortKey::UpdatedAt,\n- INTERACTIVE_SESSION_SOURCES,\n+ INTERACTIVE_SESSION_SOURCES.as_slice(),\n Some(provider_filter.as_slice()),\n &config.model_provider_id,\n /*search_term*/ None,\n@@ -835,7 +835,7 @@ async fn run_ratatui_app(\n /*page_size*/ 1,\n /*cursor*/ None,\n ThreadSortKey::UpdatedAt,\n- INTERACTIVE_SESSION_SOURCES,\n+ INTERACTIVE_SESSION_SOURCES.as_slice(),\n Some(provider_filter.as_slice()),\n &config.model_provider_id,\n filter_cwd,", - "path": "codex-rs/tui/src/lib.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -164,7 +164,7 @@ async fn run_session_picker(\n PAGE_SIZE,\n request.cursor.as_ref(),\n request.sort_key,\n- INTERACTIVE_SESSION_SOURCES,\n+ INTERACTIVE_SESSION_SOURCES.as_slice(),\n Some(provider_filter.as_slice()),\n request.default_provider.as_str(),\n /*search_term*/ None,", - "path": "codex-rs/tui/src/resume_picker.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -322,7 +322,7 @@ fn spawn_rollout_page_loader(\n PAGE_SIZE,\n cursor,\n request.sort_key,\n- INTERACTIVE_SESSION_SOURCES,\n+ INTERACTIVE_SESSION_SOURCES.as_slice(),\n default_provider.as_ref().map(std::slice::from_ref),\n default_provider.as_deref().unwrap_or_default(),\n /*search_term*/ None,", - "path": "codex-rs/tui_app_server/src/resume_picker.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": " 1. Added SessionSource::Custom(String) and --session-source.\r\n 2. Enforced plugin and skill products by session_source.\r\n 3. Applied the same filtering to curated background refresh.", - "labels": [], - "merged_at": "2026-03-19T07:46:16Z", - "number": 15041, - "state": "merged", - "title": "feat: support product-scoped plugins.", - "url": "https://github.com/openai/codex/pull/15041" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15042.json b/artifacts/github/bundles/openai-codex-pr-15042.json deleted file mode 100644 index 0f20801..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15042.json +++ /dev/null @@ -1,277 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "alexsong-oai", - "committed_at": "2026-03-18T08:32:27Z", - "message": "Support featured plugins", - "sha": "39be8e1ead7782405919b907f159d3c9ee0524d9", - "url": "https://github.com/openai/codex/commit/39be8e1ead7782405919b907f159d3c9ee0524d9" - }, - { - "author": "alexsong-oai", - "committed_at": "2026-03-18T18:57:45Z", - "message": "Merge branch 'main' into alexs/plugins-featured", - "sha": "76cb1c09e91dcd767f96c01ab76e2789f7f40890", - "url": "https://github.com/openai/codex/commit/76cb1c09e91dcd767f96c01ab76e2789f7f40890" - }, - { - "author": "alexsong-oai", - "committed_at": "2026-03-18T21:21:44Z", - "message": "add cache", - "sha": "a553a10574f6160d757b2f5054f890ffbd45ad0a", - "url": "https://github.com/openai/codex/commit/a553a10574f6160d757b2f5054f890ffbd45ad0a" - }, - { - "author": "alexsong-oai", - "committed_at": "2026-03-18T21:28:29Z", - "message": "fmt", - "sha": "63aa14e9162d17cb5a1752aad0f97c9b26447f46", - "url": "https://github.com/openai/codex/commit/63aa14e9162d17cb5a1752aad0f97c9b26447f46" - }, - { - "author": "alexsong-oai", - "committed_at": "2026-03-18T21:46:13Z", - "message": "minor", - "sha": "d0ba77e6d924ca23479bf225bc07570c01888566", - "url": "https://github.com/openai/codex/commit/d0ba77e6d924ca23479bf225bc07570c01888566" - }, - { - "author": "alexsong-oai", - "committed_at": "2026-03-18T21:58:05Z", - "message": "remove unrelated", - "sha": "9e938052528ff38f6f11eb09478833ea7c06c795", - "url": "https://github.com/openai/codex/commit/9e938052528ff38f6f11eb09478833ea7c06c795" - }, - { - "author": "alexsong-oai", - "committed_at": "2026-03-18T22:00:26Z", - "message": "remove py", - "sha": "9236f69c95d34febf25e93248e5ed51a3e5a588d", - "url": "https://github.com/openai/codex/commit/9236f69c95d34febf25e93248e5ed51a3e5a588d" - }, - { - "author": "alexsong-oai", - "committed_at": "2026-03-18T22:06:23Z", - "message": "revert unrelated", - "sha": "03d7d85aa7b3aeed97df6866f7fcf3eec0e966d8", - "url": "https://github.com/openai/codex/commit/03d7d85aa7b3aeed97df6866f7fcf3eec0e966d8" - }, - { - "author": "alexsong-oai", - "committed_at": "2026-03-18T22:24:10Z", - "message": "minor", - "sha": "37487f9f064339c31f4eca102d7e02f6f8088c33", - "url": "https://github.com/openai/codex/commit/37487f9f064339c31f4eca102d7e02f6f8088c33" - }, - { - "author": "alexsong-oai", - "committed_at": "2026-03-18T23:01:24Z", - "message": "merge startup", - "sha": "e605d012dfb17c554a9b21996d68ed14abde634f", - "url": "https://github.com/openai/codex/commit/e605d012dfb17c554a9b21996d68ed14abde634f" - }, - { - "author": "alexsong-oai", - "committed_at": "2026-03-18T23:05:54Z", - "message": "Merge branch 'main' into alexs/plugins-featured", - "sha": "9dd1676aa2fc8fcf51b954983f740e1c3fbe40cc", - "url": "https://github.com/openai/codex/commit/9dd1676aa2fc8fcf51b954983f740e1c3fbe40cc" - }, - { - "author": "alexsong-oai", - "committed_at": "2026-03-18T23:08:11Z", - "message": "minor", - "sha": "2e1c40c88af2b424e2e4a1497bddd7e4ed8ada3a", - "url": "https://github.com/openai/codex/commit/2e1c40c88af2b424e2e4a1497bddd7e4ed8ada3a" - }, - { - "author": "alexsong-oai", - "committed_at": "2026-03-18T23:24:08Z", - "message": "minor", - "sha": "5d854d48c72d442103dd0f7b632209ea2bce6ea7", - "url": "https://github.com/openai/codex/commit/5d854d48c72d442103dd0f7b632209ea2bce6ea7" - }, - { - "author": "alexsong-oai", - "committed_at": "2026-03-19T00:07:09Z", - "message": "Merge branch 'main' into alexs/plugins-featured", - "sha": "a93899c10e621f31ca3ad15fb35d8703563cf80a", - "url": "https://github.com/openai/codex/commit/a93899c10e621f31ca3ad15fb35d8703563cf80a" - }, - { - "author": "alexsong-oai", - "committed_at": "2026-03-18T23:49:00Z", - "message": "pass clone", - "sha": "0d58db1a14b26e3e1dbde63b7e923d4b3db95fe0", - "url": "https://github.com/openai/codex/commit/0d58db1a14b26e3e1dbde63b7e923d4b3db95fe0" - }, - { - "author": "alexsong-oai", - "committed_at": "2026-03-19T00:08:36Z", - "message": "fmt", - "sha": "5e275ecfbee8b7ea7ff580dd53c951e29a5b42ac", - "url": "https://github.com/openai/codex/commit/5e275ecfbee8b7ea7ff580dd53c951e29a5b42ac" - }, - { - "author": "alexsong-oai", - "committed_at": "2026-03-19T00:16:43Z", - "message": "fix", - "sha": "1e0a7f2f622baad80cf7d9be6b88cfc2ba0fa43a", - "url": "https://github.com/openai/codex/commit/1e0a7f2f622baad80cf7d9be6b88cfc2ba0fa43a" - } - ], - "default_branch": "main", - "docs_refs": [ - "codex-rs/app-server/README.md" - ], - "examples_refs": [], - "extracted_flags": [ - "MCP", - "OPENAI_CURATED_MARKETPLACE_NAME", - "TODO", - "DEFAULT_TIMEOUT", - "POST", - "GET", - "JSONRPCR", - "DEFAULT_PLUGIN_VERSION", - "DEFAULT_APP_CONFIG_FILE", - "CURATED_REPO_SYNC_STARTED", - "MAX_CAPABILITY_SUMMARY_DESCRIPTION_LEN", - "FEATURED_PLUGIN_IDS_CACHE_TTL", - "DEFAULT_REMOTE_MARKETPLACE_NAME", - "REMOTE_PLUGIN_FETCH_TIMEOUT", - "REMOTE_FEATURED_PLUGIN_FETCH_TIMEOUT", - "REMOTE_PLUGIN_MUTATION_TIMEOUT" - ], - "files": [ - { - "additions": 7, - "deletions": 0, - "patch_excerpt": "@@ -9340,6 +9340,13 @@\n \"PluginListResponse\": {\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"properties\": {\n+ \"featuredPluginIds\": {\n+ \"default\": [],\n+ \"items\": {\n+ \"type\": \"string\"\n+ },\n+ \"type\": \"array\"\n+ },\n \"marketplaces\": {\n \"items\": {\n \"$ref\": \"#/definitions/v2/PluginMarketplaceEntry\"", - "path": "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json", - "status": "modified" - }, - { - "additions": 7, - "deletions": 0, - "patch_excerpt": "@@ -6128,6 +6128,13 @@\n \"PluginListResponse\": {\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"properties\": {\n+ \"featuredPluginIds\": {\n+ \"default\": [],\n+ \"items\": {\n+ \"type\": \"string\"\n+ },\n+ \"type\": \"array\"\n+ },\n \"marketplaces\": {\n \"items\": {\n \"$ref\": \"#/definitions/PluginMarketplaceEntry\"", - "path": "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json", - "status": "modified" - }, - { - "additions": 7, - "deletions": 0, - "patch_excerpt": "@@ -239,6 +239,13 @@\n }\n },\n \"properties\": {\n+ \"featuredPluginIds\": {\n+ \"default\": [],\n+ \"items\": {\n+ \"type\": \"string\"\n+ },\n+ \"type\": \"array\"\n+ },\n \"marketplaces\": {\n \"items\": {\n \"$ref\": \"#/definitions/PluginMarketplaceEntry\"", - "path": "codex-rs/app-server-protocol/schema/json/v2/PluginListResponse.json", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -3,4 +3,4 @@\n // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n import type { PluginMarketplaceEntry } from \"./PluginMarketplaceEntry\";\n \n-export type PluginListResponse = { marketplaces: Array, remoteSyncError: string | null, };\n+export type PluginListResponse = { marketplaces: Array, remoteSyncError: string | null, featuredPluginIds: Array, };", - "path": "codex-rs/app-server-protocol/schema/typescript/v2/PluginListResponse.ts", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -3093,6 +3093,8 @@ pub struct PluginListParams {\n pub struct PluginListResponse {\n pub marketplaces: Vec,\n pub remote_sync_error: Option,\n+ #[serde(default)]\n+ pub featured_plugin_ids: Vec,\n }\n \n #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]", - "path": "codex-rs/app-server-protocol/src/protocol/v2.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -162,7 +162,7 @@ Example with notification opt-out:\n - `experimentalFeature/list` \u2014 list feature flags with stage metadata (`beta`, `underDevelopment`, `stable`, etc.), enabled/default-enabled state, and cursor pagination. For non-beta flags, `displayName`/`description`/`announcement` are `null`.\n - `collaborationMode/list` \u2014 list available collaboration mode presets (experimental, no pagination). This response omits built-in developer instructions; clients should either pass `settings.developer_instructions: null` when setting a mode to use Codex's built-in instructions, or provide their own instructions explicitly.\n - `skills/list` \u2014 list skills for one or more `cwd` values (optional `forceReload`).\n-- `plugin/list` \u2014 list discovered plugin marketplaces and plugin state, including effective marketplace install/auth policy metadata. `interface.category` uses the marketplace category ...", - "path": "codex-rs/app-server/README.md", - "status": "modified" - }, - { - "additions": 32, - "deletions": 3, - "patch_excerpt": "@@ -221,6 +221,7 @@ use codex_core::models_manager::collaboration_mode_presets::CollaborationModesCo\n use codex_core::parse_cursor;\n use codex_core::plugins::MarketplaceError;\n use codex_core::plugins::MarketplacePluginSource;\n+use codex_core::plugins::OPENAI_CURATED_MARKETPLACE_NAME;\n use codex_core::plugins::PluginInstallError as CorePluginInstallError;\n use codex_core::plugins::PluginInstallRequest;\n use codex_core::plugins::PluginReadRequest;\n@@ -424,7 +425,10 @@ impl CodexMessageProcessor {\n Ok(config) => self\n .thread_manager\n .plugins_manager()\n- .maybe_start_curated_repo_sync_for_config(&config),\n+ .maybe_start_curated_repo_sync_for_config(\n+ &config,\n+ self.thread_manager.auth_manager(),\n+ ),\n Err(err) => warn!(\"failed to load latest config f...", - "path": "codex-rs/app-server/src/codex_message_processor.rs", - "status": "modified" - }, - { - "additions": 6, - "deletions": 4, - "patch_excerpt": "@@ -228,10 +228,7 @@ impl MessageProcessor {\n thread_manager\n .plugins_manager()\n .set_analytics_events_client(analytics_events_client.clone());\n- // TODO(xl): Move into PluginManager once this no longer depends on config feature gating.\n- thread_manager\n- .plugins_manager()\n- .maybe_start_curated_repo_sync_for_config(&config);\n+\n let cloud_requirements = Arc::new(RwLock::new(cloud_requirements));\n let codex_message_processor = CodexMessageProcessor::new(CodexMessageProcessorArgs {\n auth_manager: auth_manager.clone(),\n@@ -244,6 +241,11 @@ impl MessageProcessor {\n feedback,\n log_db,\n });\n+ // Keep plugin startup warmups aligned at app-server startup.\n+ // TODO(xl): Move into PluginManager once this no longer depends on config feature gating.\n+ ...", - "path": "codex-rs/app-server/src/message_processor.rs", - "status": "modified" - }, - { - "additions": 6, - "deletions": 5, - "patch_excerpt": "@@ -261,21 +261,22 @@ async fn plugin_install_tracks_analytics_event() -> Result<()> {\n let response: PluginInstallResponse = to_response(response)?;\n assert_eq!(response.apps_needing_auth, Vec::::new());\n \n- let payloads = timeout(DEFAULT_TIMEOUT, async {\n+ let payload = timeout(DEFAULT_TIMEOUT, async {\n loop {\n let Some(requests) = analytics_server.received_requests().await else {\n tokio::time::sleep(Duration::from_millis(25)).await;\n continue;\n };\n- if !requests.is_empty() {\n- break requests;\n+ if let Some(request) = requests.iter().find(|request| {\n+ request.method == \"POST\" && request.url.path() == \"/codex/analytics-events/events\"\n+ }) {\n+ break request.body.clone();\n }\n tokio::time::sleep(Dura...", - "path": "codex-rs/app-server/tests/suite/v2/plugin_install.rs", - "status": "modified" - }, - { - "additions": 126, - "deletions": 0, - "patch_excerpt": "@@ -1,6 +1,7 @@\n use std::time::Duration;\n \n use anyhow::Result;\n+use anyhow::bail;\n use app_test_support::ChatGptAuthFixture;\n use app_test_support::McpProcess;\n use app_test_support::to_response;\n@@ -674,6 +675,16 @@ async fn plugin_list_force_remote_sync_reconciles_curated_plugin_state() -> Resu\n ))\n .mount(&server)\n .await;\n+ Mock::given(method(\"GET\"))\n+ .and(path(\"/backend-api/plugins/featured\"))\n+ .and(header(\"authorization\", \"Bearer chatgpt-token\"))\n+ .and(header(\"chatgpt-account-id\", \"account-123\"))\n+ .respond_with(\n+ ResponseTemplate::new(200)\n+ .set_body_string(r#\"[\"linear@openai-curated\",\"calendar@openai-curated\"]\"#),\n+ )\n+ .mount(&server)\n+ .await;\n \n let mut mcp = McpProcess::new(codex_home.path()).await?;\n timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;\n@@ -692,...", - "path": "codex-rs/app-server/tests/suite/v2/plugin_list.rs", - "status": "modified" - }, - { - "additions": 6, - "deletions": 5, - "patch_excerpt": "@@ -183,21 +183,22 @@ async fn plugin_uninstall_tracks_analytics_event() -> Result<()> {\n let response: PluginUninstallResponse = to_response(response)?;\n assert_eq!(response, PluginUninstallResponse {});\n \n- let payloads = timeout(DEFAULT_TIMEOUT, async {\n+ let payload = timeout(DEFAULT_TIMEOUT, async {\n loop {\n let Some(requests) = analytics_server.received_requests().await else {\n tokio::time::sleep(Duration::from_millis(25)).await;\n continue;\n };\n- if !requests.is_empty() {\n- break requests;\n+ if let Some(request) = requests.iter().find(|request| {\n+ request.method == \"POST\" && request.url.path() == \"/codex/analytics-events/events\"\n+ }) {\n+ break request.body.clone();\n }\n tokio::time::sleep(Duration::from_m...", - "path": "codex-rs/app-server/tests/suite/v2/plugin_uninstall.rs", - "status": "modified" - }, - { - "additions": 135, - "deletions": 1, - "patch_excerpt": "@@ -15,6 +15,7 @@ use super::read_curated_plugins_sha;\n use super::remote::RemotePluginFetchError;\n use super::remote::RemotePluginMutationError;\n use super::remote::enable_remote_plugin;\n+use super::remote::fetch_remote_featured_plugin_ids;\n use super::remote::fetch_remote_plugin_status;\n use super::remote::uninstall_remote_plugin;\n use super::store::DEFAULT_PLUGIN_VERSION;\n@@ -24,6 +25,7 @@ use super::store::PluginInstallResult as StorePluginInstallResult;\n use super::store::PluginStore;\n use super::store::PluginStoreError;\n use super::sync_openai_plugins_repo;\n+use crate::AuthManager;\n use crate::analytics_client::AnalyticsEventsClient;\n use crate::auth::CodexAuth;\n use crate::config::Config;\n@@ -55,6 +57,8 @@ use std::sync::Arc;\n use std::sync::RwLock;\n use std::sync::atomic::AtomicBool;\n use std::sync::atomic::Ordering;\n+use std::time::Duration;\n+use std::time::Instant;\n use toml_ed...", - "path": "codex-rs/core/src/plugins/manager.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -46,6 +46,8 @@ pub use marketplace::MarketplacePluginAuthPolicy;\n pub use marketplace::MarketplacePluginInstallPolicy;\n pub use marketplace::MarketplacePluginPolicy;\n pub use marketplace::MarketplacePluginSource;\n+pub use remote::RemotePluginFetchError;\n+pub use remote::fetch_remote_featured_plugin_ids;\n pub(crate) use render::render_explicit_plugin_instructions;\n pub(crate) use render::render_plugins_section;\n pub use store::PluginId;", - "path": "codex-rs/core/src/plugins/mod.rs", - "status": "modified" - }, - { - "additions": 42, - "deletions": 1, - "patch_excerpt": "@@ -7,6 +7,7 @@ use url::Url;\n \n const DEFAULT_REMOTE_MARKETPLACE_NAME: &str = \"openai-curated\";\n const REMOTE_PLUGIN_FETCH_TIMEOUT: Duration = Duration::from_secs(30);\n+const REMOTE_FEATURED_PLUGIN_FETCH_TIMEOUT: Duration = Duration::from_secs(10);\n const REMOTE_PLUGIN_MUTATION_TIMEOUT: Duration = Duration::from_secs(30);\n \n #[derive(Debug, Clone, PartialEq, Eq, Deserialize)]\n@@ -80,7 +81,7 @@ pub enum RemotePluginMutationError {\n }\n \n #[derive(Debug, thiserror::Error)]\n-pub(crate) enum RemotePluginFetchError {\n+pub enum RemotePluginFetchError {\n #[error(\"chatgpt authentication required to sync remote plugins\")]\n AuthRequired,\n \n@@ -158,6 +159,46 @@ pub(crate) async fn fetch_remote_plugin_status(\n })\n }\n \n+pub async fn fetch_remote_featured_plugin_ids(\n+ config: &Config,\n+ auth: Option<&CodexAuth>,\n+) -> Result, RemotePluginFetchError> {\n+ let base_url =...", - "path": "codex-rs/core/src/plugins/remote.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 0, - "patch_excerpt": "@@ -270,6 +270,10 @@ impl ThreadManager {\n self.state.session_source.clone()\n }\n \n+ pub fn auth_manager(&self) -> Arc {\n+ self.state.auth_manager.clone()\n+ }\n+\n pub fn skills_manager(&self) -> Arc {\n self.state.skills_manager.clone()\n }", - "path": "codex-rs/core/src/thread_manager.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -2018,7 +2018,7 @@ impl App {\n // TODO(xl): Move into PluginManager once this no longer depends on config feature gating.\n thread_manager\n .plugins_manager()\n- .maybe_start_curated_repo_sync_for_config(&config);\n+ .maybe_start_curated_repo_sync_for_config(&config, auth_manager.clone());\n let mut model = thread_manager\n .get_models_manager()\n .get_default_model(&config.model, RefreshStrategy::Offline)", - "path": "codex-rs/tui/src/app.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "", - "labels": [], - "merged_at": "2026-03-19T00:45:31Z", - "number": 15042, - "state": "merged", - "title": "Support featured plugins", - "url": "https://github.com/openai/codex/pull/15042" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15056.json b/artifacts/github/bundles/openai-codex-pr-15056.json deleted file mode 100644 index c66e750..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15056.json +++ /dev/null @@ -1,186 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "jif-oai", - "committed_at": "2026-03-18T13:31:44Z", - "message": "feat: add graph representation of agent network", - "sha": "e24a0f8c4f6c0f66992b2c354c57036e55b30619", - "url": "https://github.com/openai/codex/commit/e24a0f8c4f6c0f66992b2c354c57036e55b30619" - }, - { - "author": "jif-oai", - "committed_at": "2026-03-18T13:36:42Z", - "message": "fix bazel", - "sha": "90c350a49a50d28a32681e64b0dcf46caaea4e3e", - "url": "https://github.com/openai/codex/commit/90c350a49a50d28a32681e64b0dcf46caaea4e3e" - }, - { - "author": "jif-oai", - "committed_at": "2026-03-18T14:49:46Z", - "message": "nit", - "sha": "c3b9f1b72c18058ed9f3d9220f68f476bedd537f", - "url": "https://github.com/openai/codex/commit/c3b9f1b72c18058ed9f3d9220f68f476bedd537f" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "AGENT_NAMES", - "FORKED_SPAWN_AGENT_OUTPUT_MESSAGE", - "TODO", - "SUBAGENT_NOTIFICATION_OPEN_TAG", - "ARCHIVED_SESSIONS_SUBDIR", - "DEFAULT_AGENT_MAX_DEPTH", - "CREATE", - "TABLE", - "TEXT", - "NOT", - "NULL", - "PRIMARY", - "KEY", - "INDEX", - "ORDER", - "ASC", - "INSERT", - "INTO", - "VALUES", - "CONFLICT", - "UPDATE", - "SET", - "WHERE", - "SELECT", - "FROM", - "AND", - "WITH", - "RECURSIVE", - "UNION", - "ALL", - "JOIN", - "NOTHING" - ], - "files": [ - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -1295,6 +1295,7 @@\n \"strum_0.26.3\": \"{\\\"dependencies\\\":[{\\\"features\\\":[\\\"macros\\\"],\\\"name\\\":\\\"phf\\\",\\\"optional\\\":true,\\\"req\\\":\\\"^0.10\\\"},{\\\"name\\\":\\\"strum_macros\\\",\\\"optional\\\":true,\\\"req\\\":\\\"^0.26.3\\\"},{\\\"kind\\\":\\\"dev\\\",\\\"name\\\":\\\"strum_macros\\\",\\\"req\\\":\\\"^0.26\\\"}],\\\"features\\\":{\\\"default\\\":[\\\"std\\\"],\\\"derive\\\":[\\\"strum_macros\\\"],\\\"std\\\":[]}}\",\n \"strum_0.27.2\": \"{\\\"dependencies\\\":[{\\\"features\\\":[\\\"macros\\\"],\\\"name\\\":\\\"phf\\\",\\\"optional\\\":true,\\\"req\\\":\\\"^0.12\\\"},{\\\"name\\\":\\\"strum_macros\\\",\\\"optional\\\":true,\\\"req\\\":\\\"^0.27\\\"}],\\\"features\\\":{\\\"default\\\":[\\\"std\\\"],\\\"derive\\\":[\\\"strum_macros\\\"],\\\"std\\\":[]}}\",\n \"strum_macros_0.26.4\": \"{\\\"dependencies\\\":[{\\\"name\\\":\\\"heck\\\",\\\"req\\\":\\\"^0.5.0\\\"},{\\\"name\\\":\\\"proc-macro2\\\",\\\"req\\\":\\\"^1.0\\\"},{\\\"name\\\":\\\"quote\\\",\\\"req\\\":\\\"^1.0\\\"},{\\\"name\\\":\\\"rustversion\\\",\\\"req\\\":\\\"^1.0\\\"},{\\\"kind\\\":\\\"dev\\\",\\\"name\\\":\\\"strum\\\",\\\"req\\\":\\\"^0.26\\\"},{\\...", - "path": "MODULE.bazel.lock", - "status": "modified" - }, - { - "additions": 16, - "deletions": 0, - "patch_excerpt": "@@ -2477,6 +2477,7 @@ dependencies = [\n \"serde\",\n \"serde_json\",\n \"sqlx\",\n+ \"strum 0.27.2\",\n \"tokio\",\n \"tracing\",\n \"tracing-subscriber\",\n@@ -9367,6 +9368,9 @@ name = \"strum\"\n version = \"0.27.2\"\n source = \"registry+https://github.com/rust-lang/crates.io-index\"\n checksum = \"af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf\"\n+dependencies = [\n+ \"strum_macros 0.27.2\",\n+]\n \n [[package]]\n name = \"strum_macros\"\n@@ -9381,6 +9385,18 @@ dependencies = [\n \"syn 2.0.114\",\n ]\n \n+[[package]]\n+name = \"strum_macros\"\n+version = \"0.27.2\"\n+source = \"registry+https://github.com/rust-lang/crates.io-index\"\n+checksum = \"7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7\"\n+dependencies = [\n+ \"heck\",\n+ \"proc-macro2\",\n+ \"quote\",\n+ \"syn 2.0.114\",\n+]\n+\n [[package]]\n name = \"strum_macros\"\n version = \"0.28.0\"", - "path": "codex-rs/Cargo.lock", - "status": "modified" - }, - { - "additions": 259, - "deletions": 31, - "patch_excerpt": "@@ -6,6 +6,8 @@ use crate::agent::status::is_final;\n use crate::codex_thread::ThreadConfigSnapshot;\n use crate::error::CodexErr;\n use crate::error::Result as CodexResult;\n+use crate::features::Feature;\n+use crate::find_archived_thread_path_by_id_str;\n use crate::find_thread_path_by_id_str;\n use crate::rollout::RolloutRecorder;\n use crate::session_prefix::format_subagent_context_line;\n@@ -23,9 +25,13 @@ use codex_protocol::protocol::SessionSource;\n use codex_protocol::protocol::SubAgentSource;\n use codex_protocol::protocol::TokenUsage;\n use codex_protocol::user_input::UserInput;\n+use codex_state::DirectionalThreadSpawnEdgeStatus;\n+use std::collections::HashMap;\n+use std::collections::VecDeque;\n use std::sync::Arc;\n use std::sync::Weak;\n use tokio::sync::watch;\n+use tracing::warn;\n \n const AGENT_NAMES: &str = include_str!(\"agent_names.txt\");\n const FORKED_SPAWN_AGENT_OUTPUT_MESSAGE: &str =...", - "path": "codex-rs/core/src/agent/control.rs", - "status": "modified" - }, - { - "additions": 782, - "deletions": 12, - "patch_excerpt": "@@ -10,6 +10,7 @@ use crate::config_loader::LoaderOverrides;\n use crate::contextual_user_message::SUBAGENT_NOTIFICATION_OPEN_TAG;\n use crate::features::Feature;\n use assert_matches::assert_matches;\n+use chrono::Utc;\n use codex_protocol::config_types::ModeKind;\n use codex_protocol::models::ContentItem;\n use codex_protocol::models::ResponseItem;\n@@ -143,6 +144,42 @@ async fn wait_for_subagent_notification(parent_thread: &Arc) -> boo\n timeout(Duration::from_secs(2), wait).await.is_ok()\n }\n \n+async fn persist_thread_for_tree_resume(thread: &Arc, message: &str) {\n+ thread\n+ .inject_user_message_without_turn(message.to_string())\n+ .await;\n+ thread.codex.session.ensure_rollout_materialized().await;\n+ thread.codex.session.flush_rollout().await;\n+}\n+\n+async fn wait_for_live_thread_spawn_children(\n+ control: &AgentControl,\n+ parent_thread_...", - "path": "codex-rs/core/src/agent/control_tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -378,7 +378,7 @@ mod agent {\n // Fire and forget close of the agent.\n if !matches!(final_status, AgentStatus::Shutdown | AgentStatus::NotFound) {\n tokio::spawn(async move {\n- if let Err(err) = agent_control.shutdown_agent(thread_id).await {\n+ if let Err(err) = agent_control.shutdown_live_agent(thread_id).await {\n warn!(\n \"failed to auto-close global memory consolidation agent {thread_id}: {err}\"\n );", - "path": "codex-rs/core/src/memories/phase2.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 4, - "patch_excerpt": "@@ -673,7 +673,7 @@ async fn run_agent_job_loop(\n let _ = session\n .services\n .agent_control\n- .shutdown_agent(thread_id)\n+ .shutdown_live_agent(thread_id)\n .await;\n continue;\n }\n@@ -833,7 +833,7 @@ async fn recover_running_items(\n let _ = session\n .services\n .agent_control\n- .shutdown_agent(thread_id)\n+ .shutdown_live_agent(thread_id)\n .await;\n }\n continue;\n@@ -955,7 +955,7 @@ async fn reap_stale_active_items(\n let _ = session\n .services\n .agent_control\n- .shutdown_agent(thread_id)\n+ .shutdown_live_agent(thread_id)\n ...", - "path": "codex-rs/core/src/tools/handlers/agent_jobs.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -72,7 +72,7 @@ impl ToolHandler for Handler {\n session\n .services\n .agent_control\n- .shutdown_agent(agent_id)\n+ .close_agent(agent_id)\n .await\n .map_err(|err| collab_agent_error(agent_id, err))\n .map(|_| ())", - "path": "codex-rs/core/src/tools/handlers/multi_agents/close_agent.rs", - "status": "modified" - }, - { - "additions": 199, - "deletions": 2, - "patch_excerpt": "@@ -6,6 +6,7 @@ use crate::built_in_model_providers;\n use crate::codex::make_session_and_context;\n use crate::config::DEFAULT_AGENT_MAX_DEPTH;\n use crate::config::types::ShellEnvironmentPolicy;\n+use crate::features::Feature;\n use crate::function_tool::FunctionCallError;\n use crate::protocol::AskForApproval;\n use crate::protocol::FileSystemSandboxPolicy;\n@@ -672,7 +673,7 @@ async fn resume_agent_restores_closed_agent_and_accepts_send_input() {\n let agent_id = thread.thread_id;\n let _ = manager\n .agent_control()\n- .shutdown_agent(agent_id)\n+ .shutdown_live_agent(agent_id)\n .await\n .expect(\"shutdown agent\");\n assert_eq!(\n@@ -720,7 +721,7 @@ async fn resume_agent_restores_closed_agent_and_accepts_send_input() {\n \n let _ = manager\n .agent_control()\n- .shutdown_agent(agent_id)\n+ .shutdown_live_agent(agent_id)\n ...", - "path": "codex-rs/core/src/tools/handlers/multi_agents_tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1526,7 +1526,7 @@ fn create_close_agent_tool() -> ToolSpec {\n \n ToolSpec::Function(ResponsesApiTool {\n name: \"close_agent\".to_string(),\n- description: \"Close an agent when it is no longer needed and return its previous status before shutdown was requested. Don't keep agents open for too long if they are not needed anymore.\".to_string(),\n+ description: \"Close an agent and any open descendants when they are no longer needed, and return the target agent's previous status before shutdown was requested. Don't keep agents open for too long if they are not needed anymore.\".to_string(),\n strict: false,\n defer_loading: None,\n parameters: JsonSchema::Object {", - "path": "codex-rs/core/src/tools/spec.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -15,6 +15,7 @@ owo-colors = { workspace = true }\n serde = { workspace = true, features = [\"derive\"] }\n serde_json = { workspace = true }\n sqlx = { workspace = true }\n+strum = { workspace = true, features = [\"derive\"] }\n tokio = { workspace = true, features = [\"fs\", \"io-util\", \"macros\", \"rt-multi-thread\", \"sync\", \"time\"] }\n tracing = { workspace = true }\n tracing-subscriber = { workspace = true }", - "path": "codex-rs/state/Cargo.toml", - "status": "modified" - }, - { - "additions": 8, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,8 @@\n+CREATE TABLE thread_spawn_edges (\n+ parent_thread_id TEXT NOT NULL,\n+ child_thread_id TEXT NOT NULL PRIMARY KEY,\n+ status TEXT NOT NULL\n+);\n+\n+CREATE INDEX idx_thread_spawn_edges_parent_status\n+ ON thread_spawn_edges(parent_thread_id, status);", - "path": "codex-rs/state/migrations/0021_thread_spawn_edges.sql", - "status": "added" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -35,6 +35,7 @@ pub use model::Anchor;\n pub use model::BackfillState;\n pub use model::BackfillStats;\n pub use model::BackfillStatus;\n+pub use model::DirectionalThreadSpawnEdgeStatus;\n pub use model::ExtractionOutcome;\n pub use model::SortKey;\n pub use model::Stage1JobClaim;", - "path": "codex-rs/state/src/lib.rs", - "status": "modified" - }, - { - "additions": 11, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,11 @@\n+use strum::AsRefStr;\n+use strum::Display;\n+use strum::EnumString;\n+\n+/// Status attached to a directional thread-spawn edge.\n+#[derive(Debug, Clone, Copy, PartialEq, Eq, AsRefStr, Display, EnumString)]\n+#[strum(serialize_all = \"snake_case\")]\n+pub enum DirectionalThreadSpawnEdgeStatus {\n+ Open,\n+ Closed,\n+}", - "path": "codex-rs/state/src/model/graph.rs", - "status": "added" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -1,5 +1,6 @@\n mod agent_job;\n mod backfill_state;\n+mod graph;\n mod log;\n mod memories;\n mod thread_metadata;\n@@ -13,6 +14,7 @@ pub use agent_job::AgentJobProgress;\n pub use agent_job::AgentJobStatus;\n pub use backfill_state::BackfillState;\n pub use backfill_state::BackfillStatus;\n+pub use graph::DirectionalThreadSpawnEdgeStatus;\n pub use log::LogEntry;\n pub use log::LogQuery;\n pub use log::LogRow;", - "path": "codex-rs/state/src/model/mod.rs", - "status": "modified" - }, - { - "additions": 274, - "deletions": 0, - "patch_excerpt": "@@ -1,4 +1,5 @@\n use super::*;\n+use codex_protocol::protocol::SessionSource;\n \n impl StateRuntime {\n pub async fn get_thread(&self, id: ThreadId) -> anyhow::Result> {\n@@ -78,6 +79,172 @@ ORDER BY position ASC\n Ok(Some(tools))\n }\n \n+ /// Persist or replace the directional parent-child edge for a spawned thread.\n+ pub async fn upsert_thread_spawn_edge(\n+ &self,\n+ parent_thread_id: ThreadId,\n+ child_thread_id: ThreadId,\n+ status: crate::DirectionalThreadSpawnEdgeStatus,\n+ ) -> anyhow::Result<()> {\n+ sqlx::query(\n+ r#\"\n+INSERT INTO thread_spawn_edges (\n+ parent_thread_id,\n+ child_thread_id,\n+ status\n+) VALUES (?, ?, ?)\n+ON CONFLICT(child_thread_id) DO UPDATE SET\n+ parent_thread_id = excluded.parent_thread_id,\n+ status = excluded.status\n+ \"#,\n+ )\n+ .bind...", - "path": "codex-rs/state/src/runtime/threads.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "Add a representation of the agent graph. This is now used for:\r\n* Cascade close agents (when I close a parent, it close the kids)\r\n* Cascade resume (oposite)\r\n\r\nLater, this will also be used for post-compaction stuffing of the context\r\n\r\nDirect fix for: https://github.com/openai/codex/issues/14458 ", - "labels": [], - "merged_at": "2026-03-19T10:21:26Z", - "number": 15056, - "state": "merged", - "title": "feat: add graph representation of agent network", - "url": "https://github.com/openai/codex/pull/15056" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15072.json b/artifacts/github/bundles/openai-codex-pr-15072.json deleted file mode 100644 index 0268048..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15072.json +++ /dev/null @@ -1,164 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "pakrym-oai", - "committed_at": "2026-03-18T17:34:42Z", - "message": "Handle view_image output", - "sha": "b48f93df65e26b5bd58d4e57ea97ea8501db5a5c", - "url": "https://github.com/openai/codex/commit/b48f93df65e26b5bd58d4e57ea97ea8501db5a5c" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-18T17:43:21Z", - "message": "Restore view image detail handling", - "sha": "c0c2b7191768c98bbe0d94c379ed257ba83048ab", - "url": "https://github.com/openai/codex/commit/c0c2b7191768c98bbe0d94c379ed257ba83048ab" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-18T17:50:47Z", - "message": "Replace debug panic with assertion in code mode test", - "sha": "b15720312a7755ac23ee5e5a45bc1ab538bf4b66", - "url": "https://github.com/openai/codex/commit/b15720312a7755ac23ee5e5a45bc1ab538bf4b66" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-18T18:08:49Z", - "message": "codex: fix CI failure on PR #15072", - "sha": "1733c874d8dd4ea8329da6957b9fbf280f482ae2", - "url": "https://github.com/openai/codex/commit/1733c874d8dd4ea8329da6957b9fbf280f482ae2" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-18T18:30:02Z", - "message": "protocol: remove unused mime_guess dependency", - "sha": "ce202b85553436bfa88d56b2e24e1ff6b31345f4", - "url": "https://github.com/openai/codex/commit/ce202b85553436bfa88d56b2e24e1ff6b31345f4" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "JSON", - "HTTPS", - "URL", - "ALL_TOOLS", - "AAA", - "STANDARD", - "BASE64_STANDARD", - "MIME" - ], - "files": [ - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -2325,7 +2325,6 @@ dependencies = [\n \"icu_decimal\",\n \"icu_locale_core\",\n \"icu_provider\",\n- \"mime_guess\",\n \"pretty_assertions\",\n \"schemars 0.8.22\",\n \"serde\",\n@@ -2756,6 +2755,7 @@ dependencies = [\n \"base64 0.22.1\",\n \"codex-utils-cache\",\n \"image\",\n+ \"mime_guess\",\n \"thiserror 2.0.18\",\n \"tokio\",\n ]", - "path": "codex-rs/Cargo.lock", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -11,7 +11,7 @@\n - Global helpers:\n - `exit()`: Immediately ends the current script successfully (like an early return from the top level).\n - `text(value: string | number | boolean | undefined | null)`: Appends a text item and returns it. Non-string values are stringified with `JSON.stringify(...)` when possible.\n-- `image(imageUrl: string)`: Appends an image item and returns it. `image_url` can be an HTTPS URL or a base64-encoded `data:` URL.\n+- `image(imageUrlOrItem: string | { image_url: string; detail?: \"auto\" | \"low\" | \"high\" | \"original\" | null })`: Appends an image item and returns it. `image_url` can be an HTTPS URL or a base64-encoded `data:` URL.\n - `store(key: string, value: any)`: stores a serializable value under a string key for later `exec` calls in the same session.\n - `load(key: string)`: returns the stored value for a string key, or `undefined` if it is missing.\n - `...", - "path": "codex-rs/core/src/tools/code_mode/description.md", - "status": "modified" - }, - { - "additions": 41, - "deletions": 10, - "patch_excerpt": "@@ -223,14 +223,48 @@ function codeModeWorkerMain() {\n return String(value);\n }\n \n- function normalizeOutputImageUrl(value) {\n- if (typeof value !== 'string' || !value) {\n- throw new TypeError('image expects a non-empty image URL string');\n+ function normalizeOutputImage(value) {\n+ let imageUrl;\n+ let detail;\n+ if (typeof value === 'string') {\n+ imageUrl = value;\n+ } else if (\n+ value &&\n+ typeof value === 'object' &&\n+ !Array.isArray(value)\n+ ) {\n+ if (typeof value.image_url === 'string') {\n+ imageUrl = value.image_url;\n+ }\n+ if (typeof value.detail === 'string') {\n+ detail = value.detail;\n+ } else if (\n+ Object.prototype.hasOwnProperty.call(value, 'detail') &&\n+ value.detail !== null &&\n+ typeof value.detail !== 'undefined'\n+ ) {\n+ throw new TypeError('image detail must...", - "path": "codex-rs/core/src/tools/code_mode/runner.cjs", - "status": "modified" - }, - { - "additions": 84, - "deletions": 21, - "patch_excerpt": "@@ -1,20 +1,22 @@\n use async_trait::async_trait;\n use codex_environment::ExecutorFileSystem;\n-use codex_protocol::models::ContentItem;\n+use codex_protocol::models::FunctionCallOutputBody;\n use codex_protocol::models::FunctionCallOutputContentItem;\n+use codex_protocol::models::FunctionCallOutputPayload;\n use codex_protocol::models::ImageDetail;\n-use codex_protocol::models::local_image_content_items_with_label_number;\n+use codex_protocol::models::ResponseInputItem;\n use codex_protocol::openai_models::InputModality;\n use codex_utils_absolute_path::AbsolutePathBuf;\n use codex_utils_image::PromptImageMode;\n+use codex_utils_image::load_for_prompt_bytes;\n use serde::Deserialize;\n \n use crate::function_tool::FunctionCallError;\n use crate::original_image_detail::can_request_original_image_detail;\n use crate::protocol::EventMsg;\n use crate::protocol::ViewImageToolCallEvent;\n-use crate::tools::cont...", - "path": "codex-rs/core/src/tools/handlers/view_image.rs", - "status": "modified" - }, - { - "additions": 15, - "deletions": 1, - "patch_excerpt": "@@ -980,7 +980,21 @@ fn create_view_image_tool(can_request_original_image_detail: bool) -> ToolSpec {\n required: Some(vec![\"path\".to_string()]),\n additional_properties: Some(false.into()),\n },\n- output_schema: None,\n+ output_schema: Some(serde_json::json!({\n+ \"type\": \"object\",\n+ \"properties\": {\n+ \"image_url\": {\n+ \"type\": \"string\",\n+ \"description\": \"Data URL for the loaded image.\"\n+ },\n+ \"detail\": {\n+ \"type\": [\"string\", \"null\"],\n+ \"description\": \"Image detail hint returned by view_image. Returns `original` when original resolution is preserved, otherwise `null`.\"\n+ }\n+ },\n+ \"required\": [\"image_url\", \"detail\"],\n+ \"additionalProperties\": false\n+ })),\n...", - "path": "codex-rs/core/src/tools/spec.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -2627,7 +2627,7 @@ fn code_mode_augments_builtin_tool_descriptions_with_typed_sample() {\n \n assert_eq!(\n description,\n- \"View a local image from the filesystem (only use if given a full filepath by the user, and the image isn't already attached to the thread context within tags).\\n\\nexec tool declaration:\\n```ts\\ndeclare const tools: { view_image(args: { path: string; }): Promise; };\\n```\"\n+ \"View a local image from the filesystem (only use if given a full filepath by the user, and the image isn't already attached to the thread context within tags).\\n\\nexec tool declaration:\\n```ts\\ndeclare const tools: { view_image(args: { path: string; }): Promise<{ detail: string | null; image_url: string; }>; };\\n```\"\n );\n }", - "path": "codex-rs/core/src/tools/spec_tests.rs", - "status": "modified" - }, - { - "additions": 87, - "deletions": 1, - "patch_excerpt": "@@ -1,6 +1,8 @@\n #![allow(clippy::expect_used, clippy::unwrap_used)]\n \n use anyhow::Result;\n+use base64::Engine;\n+use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;\n use codex_core::config::types::McpServerConfig;\n use codex_core::config::types::McpServerTransportConfig;\n use codex_core::features::Feature;\n@@ -1683,6 +1685,90 @@ image(\"data:image/png;base64,AAA\");\n Ok(())\n }\n \n+#[tokio::test(flavor = \"multi_thread\", worker_threads = 2)]\n+async fn code_mode_can_use_view_image_result_with_image_helper() -> Result<()> {\n+ skip_if_no_network!(Ok(()));\n+\n+ let server = responses::start_mock_server().await;\n+ let mut builder = test_codex()\n+ .with_model(\"gpt-5.3-codex\")\n+ .with_config(move |config| {\n+ let _ = config.features.enable(Feature::CodeMode);\n+ let _ = config.features.enable(Feature::ImageDetailOriginal);\n+ });\n+ ...", - "path": "codex-rs/core/tests/suite/code_mode.rs", - "status": "modified" - }, - { - "additions": 8, - "deletions": 9, - "patch_excerpt": "@@ -1087,7 +1087,7 @@ async fn view_image_tool_errors_when_path_is_directory() -> anyhow::Result<()> {\n }\n \n #[tokio::test(flavor = \"multi_thread\", worker_threads = 2)]\n-async fn view_image_tool_placeholder_for_non_image_files() -> anyhow::Result<()> {\n+async fn view_image_tool_errors_for_non_image_files() -> anyhow::Result<()> {\n skip_if_no_network!(Ok(()));\n \n let server = start_mock_server().await;\n@@ -1150,20 +1150,19 @@ async fn view_image_tool_placeholder_for_non_image_files() -> anyhow::Result<()>\n request.inputs_of_type(\"input_image\").is_empty(),\n \"non-image file should not produce an input_image message\"\n );\n- let (placeholder, success) = request\n+ let (error_text, success) = request\n .function_call_output_content_and_success(call_id)\n .expect(\"function_call_output should be present\");\n assert_eq!(success, None);\n- let pla...", - "path": "codex-rs/core/tests/suite/view_image.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 1, - "patch_excerpt": "@@ -19,7 +19,6 @@ codex-utils-image = { workspace = true }\n icu_decimal = { workspace = true }\n icu_locale_core = { workspace = true }\n icu_provider = { workspace = true, features = [\"sync\"] }\n-mime_guess = { workspace = true }\n schemars = { workspace = true }\n serde = { workspace = true, features = [\"derive\"] }\n serde_json = { workspace = true }", - "path": "codex-rs/protocol/Cargo.toml", - "status": "modified" - }, - { - "additions": 15, - "deletions": 23, - "patch_excerpt": "@@ -935,7 +935,7 @@ fn invalid_image_error_placeholder(\n fn unsupported_image_error_placeholder(path: &std::path::Path, mime: &str) -> ContentItem {\n ContentItem::InputText {\n text: format!(\n- \"Codex cannot attach image at `{}`: unsupported image format `{}`.\",\n+ \"Codex cannot attach image at `{}`: unsupported image `{}`.\",\n path.display(),\n mime\n ),\n@@ -966,28 +966,20 @@ pub fn local_image_content_items_with_label_number(\n }\n items\n }\n- Err(err) => {\n- if matches!(&err, ImageProcessingError::Read { .. }) {\n+ Err(err) => match &err {\n+ ImageProcessingError::Read { .. } | ImageProcessingError::Encode { .. } => {\n vec![local_image_error_placeholder(path, &err)]\n- } else if err.is_invalid_image() {\n+ }\n+ ImageP...", - "path": "codex-rs/protocol/src/models.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -11,6 +11,7 @@ workspace = true\n base64 = { workspace = true }\n image = { workspace = true, features = [\"jpeg\", \"png\", \"gif\", \"webp\"] }\n codex-utils-cache = { workspace = true }\n+mime_guess = { workspace = true }\n thiserror = { workspace = true }\n tokio = { workspace = true, features = [\"fs\", \"rt\", \"rt-multi-thread\", \"macros\"] }", - "path": "codex-rs/utils/image/Cargo.toml", - "status": "modified" - }, - { - "additions": 17, - "deletions": 0, - "patch_excerpt": "@@ -23,9 +23,26 @@ pub enum ImageProcessingError {\n #[source]\n source: image::ImageError,\n },\n+ #[error(\"unsupported image `{mime}`\")]\n+ UnsupportedImageFormat { mime: String },\n }\n \n impl ImageProcessingError {\n+ pub fn decode_error(path: &std::path::Path, source: image::ImageError) -> Self {\n+ if matches!(source, ImageError::Decoding(_)) {\n+ return ImageProcessingError::Decode {\n+ path: path.to_path_buf(),\n+ source,\n+ };\n+ }\n+\n+ let mime = mime_guess::from_path(path)\n+ .first()\n+ .map(|mime_guess| mime_guess.essence_str().to_owned())\n+ .unwrap_or_else(|| \"unknown\".to_string());\n+ ImageProcessingError::UnsupportedImageFormat { mime }\n+ }\n+\n pub fn is_invalid_image(&self) -> bool {\n matches!(\n self,", - "path": "codex-rs/utils/image/src/error.rs", - "status": "modified" - }, - { - "additions": 7, - "deletions": 10, - "patch_excerpt": "@@ -74,12 +74,8 @@ pub fn load_for_prompt_bytes(\n _ => None,\n };\n \n- let dynamic = image::load_from_memory(&file_bytes).map_err(|source| {\n- ImageProcessingError::Decode {\n- path: path_buf.clone(),\n- source,\n- }\n- })?;\n+ let dynamic = image::load_from_memory(&file_bytes)\n+ .map_err(|source| ImageProcessingError::decode_error(&path_buf, source))?;\n \n let (width, height) = dynamic.dimensions();\n \n@@ -294,10 +290,11 @@ mod tests {\n PromptImageMode::ResizeToFit,\n )\n .expect_err(\"invalid image should fail\");\n- match err {\n- ImageProcessingError::Decode { .. } => {}\n- _ => panic!(\"unexpected error variant\"),\n- }\n+ assert!(matches!(\n+ err,\n+ ImageProcessingError::Decode { .. }\n+ ...", - "path": "codex-rs/utils/image/src/lib.rs", - "status": "modified" - } - ], - "linked_issues": [ - "#15072" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "Cleanup image semantics in code mode.\r\n\r\n`view_image` now returns `{image_url:string, details?: string}` \r\n\r\n`image()` now allows both string parameter and `{image_url:string, details?: string}` ", - "labels": [], - "merged_at": "2026-03-18T20:58:21Z", - "number": 15072, - "state": "merged", - "title": "Return image URL from view_image tool", - "url": "https://github.com/openai/codex/pull/15072" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15075.json b/artifacts/github/bundles/openai-codex-pr-15075.json deleted file mode 100644 index e9ebe16..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15075.json +++ /dev/null @@ -1,127 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "pakrym-oai", - "committed_at": "2026-03-18T18:18:39Z", - "message": "Handle code-mode tool errors", - "sha": "b2f2dc2e1b20db8cd41136ae21c8af690db2c918", - "url": "https://github.com/openai/codex/commit/b2f2dc2e1b20db8cd41136ae21c8af690db2c918" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-18T18:29:55Z", - "message": "Update tool call error handling", - "sha": "e62bd04746b4f633d8cc373d25c035275ec1fb64", - "url": "https://github.com/openai/codex/commit/e62bd04746b4f633d8cc373d25c035275ec1fb64" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-18T19:03:37Z", - "message": "Merge remote-tracking branch 'origin/main' into pakrym/add-tests-for-respondtomodel-errors", - "sha": "a07b1c75e77da40960603115b5d83e88deb8eba8", - "url": "https://github.com/openai/codex/commit/a07b1c75e77da40960603115b5d83e88deb8eba8" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-18T19:30:39Z", - "message": "Fix post-merge code mode worker and custom output shape", - "sha": "68f228c770d8d11979bd1095e4fbb8433ee33fec", - "url": "https://github.com/openai/codex/commit/68f228c770d8d11979bd1095e4fbb8433ee33fec" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "PUBLIC_TOOL_NAME" - ], - "files": [ - { - "additions": 3, - "deletions": 2, - "patch_excerpt": "@@ -4557,15 +4557,16 @@ async fn fatal_tool_error_stops_turn_and_reports_error() {\n .expect(\"tool call present\");\n let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new()));\n let err = router\n- .dispatch_tool_call(\n+ .dispatch_tool_call_with_code_mode_result(\n Arc::clone(&session),\n Arc::clone(&turn_context),\n tracker,\n call,\n ToolCallSource::Direct,\n )\n .await\n- .expect_err(\"expected fatal error\");\n+ .err()\n+ .expect(\"expected fatal error\");\n \n match err {\n FunctionCallError::Fatal(message) => {", - "path": "codex-rs/core/src/codex_tests.rs", - "status": "modified" - }, - { - "additions": 9, - "deletions": 10, - "patch_excerpt": "@@ -14,6 +14,7 @@ use serde_json::Value as JsonValue;\n use crate::client_common::tools::ToolSpec;\n use crate::codex::Session;\n use crate::codex::TurnContext;\n+use crate::function_tool::FunctionCallError;\n use crate::tools::ToolRouter;\n use crate::tools::code_mode_description::augment_tool_spec_for_code_mode;\n use crate::tools::code_mode_description::code_mode_tool_reference;\n@@ -303,9 +304,11 @@ async fn call_nested_tool(\n tool_name: String,\n input: Option,\n cancellation_token: tokio_util::sync::CancellationToken,\n-) -> JsonValue {\n+) -> Result {\n if tool_name == PUBLIC_TOOL_NAME {\n- return JsonValue::String(format!(\"{PUBLIC_TOOL_NAME} cannot invoke itself\"));\n+ return Err(FunctionCallError::RespondToModel(format!(\n+ \"{PUBLIC_TOOL_NAME} cannot invoke itself\"\n+ )));\n }\n \n let payload =\n@@ -316...", - "path": "codex-rs/core/src/tools/code_mode/mod.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -70,6 +70,8 @@ pub(super) enum HostToNodeMessage {\n request_id: String,\n id: String,\n code_mode_result: JsonValue,\n+ #[serde(default)]\n+ error_text: Option,\n },\n }", - "path": "codex-rs/core/src/tools/code_mode/protocol.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 0, - "patch_excerpt": "@@ -595,6 +595,10 @@ function createProtocol() {\n return;\n }\n pending.delete(message.request_id + ':' + message.id);\n+ if (typeof message.error_text === 'string') {\n+ entry.reject(new Error(message.error_text));\n+ return;\n+ }\n entry.resolve(message.code_mode_result ?? '');\n return;\n }", - "path": "codex-rs/core/src/tools/code_mode/runner.cjs", - "status": "modified" - }, - { - "additions": 15, - "deletions": 8, - "patch_excerpt": "@@ -14,6 +14,7 @@ use super::process::write_message;\n use super::protocol::HostToNodeMessage;\n use super::protocol::NodeToHostMessage;\n use crate::tools::parallel::ToolCallRuntime;\n+\n pub(crate) struct CodeModeWorker {\n shutdown_tx: Option>,\n }\n@@ -53,17 +54,23 @@ impl CodeModeProcess {\n let tool_runtime = tool_runtime.clone();\n let stdin = stdin.clone();\n tokio::spawn(async move {\n+ let result = call_nested_tool(\n+ exec,\n+ tool_runtime,\n+ tool_call.name,\n+ tool_call.input,\n+ CancellationToken::new(),\n+ )\n+ .await;\n+ let (code_mode_res...", - "path": "codex-rs/core/src/tools/code_mode/worker.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 3, - "patch_excerpt": "@@ -1607,16 +1607,17 @@ impl JsReplManager {\n let tracker = Arc::clone(&exec.tracker);\n \n match router\n- .dispatch_tool_call(\n- session.clone(),\n+ .dispatch_tool_call_with_code_mode_result(\n+ session,\n turn,\n tracker,\n call,\n crate::tools::router::ToolCallSource::JsRepl,\n )\n .await\n {\n- Ok(response) => {\n+ Ok(result) => {\n+ let response = result.into_response();\n let summary = Self::summarize_tool_call_response(&response);\n match serde_json::to_value(response) {\n Ok(value) => {", - "path": "codex-rs/core/src/tools/js_repl/mod.rs", - "status": "modified" - }, - { - "additions": 42, - "deletions": 11, - "patch_excerpt": "@@ -16,6 +16,7 @@ use crate::error::CodexErr;\n use crate::function_tool::FunctionCallError;\n use crate::tools::context::AbortedToolOutput;\n use crate::tools::context::SharedTurnDiffTracker;\n+use crate::tools::context::ToolPayload;\n use crate::tools::registry::AnyToolResult;\n use crate::tools::router::ToolCall;\n use crate::tools::router::ToolCallSource;\n@@ -57,9 +58,17 @@ impl ToolCallRuntime {\n call: ToolCall,\n cancellation_token: CancellationToken,\n ) -> impl std::future::Future> {\n+ let error_call = call.clone();\n let future =\n self.handle_tool_call_with_source(call, ToolCallSource::Direct, cancellation_token);\n- async move { future.await.map(AnyToolResult::into_response) }.in_current_span()\n+ async move {\n+ match future.await {\n+ Ok(response) => Ok(respon...", - "path": "codex-rs/core/src/tools/parallel.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 75, - "patch_excerpt": "@@ -5,11 +5,9 @@ use crate::function_tool::FunctionCallError;\n use crate::mcp_connection_manager::ToolInfo;\n use crate::sandboxing::SandboxPermissions;\n use crate::tools::code_mode::is_code_mode_nested_tool;\n-use crate::tools::context::FunctionToolOutput;\n use crate::tools::context::SharedTurnDiffTracker;\n use crate::tools::context::ToolInvocation;\n use crate::tools::context::ToolPayload;\n-use crate::tools::context::ToolSearchOutput;\n use crate::tools::discoverable::DiscoverableTool;\n use crate::tools::registry::AnyToolResult;\n use crate::tools::registry::ConfiguredToolSpec;\n@@ -18,7 +16,6 @@ use crate::tools::spec::ToolsConfig;\n use crate::tools::spec::build_specs_with_discoverable_tools;\n use codex_protocol::dynamic_tools::DynamicToolSpec;\n use codex_protocol::models::LocalShellAction;\n-use codex_protocol::models::ResponseInputItem;\n use codex_protocol::models::ResponseItem;\n use codex...", - "path": "codex-rs/core/src/tools/router.rs", - "status": "modified" - }, - { - "additions": 32, - "deletions": 29, - "patch_excerpt": "@@ -1,9 +1,9 @@\n use std::sync::Arc;\n \n use crate::codex::make_session_and_context;\n+use crate::function_tool::FunctionCallError;\n use crate::tools::context::ToolPayload;\n use crate::turn_diff_tracker::TurnDiffTracker;\n-use codex_protocol::models::ResponseInputItem;\n use codex_protocol::models::ResponseItem;\n \n use super::ToolCall;\n@@ -50,20 +50,21 @@ async fn js_repl_tools_only_blocks_direct_tool_calls() -> anyhow::Result<()> {\n },\n };\n let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new()));\n- let response = router\n- .dispatch_tool_call(session, turn, tracker, call, ToolCallSource::Direct)\n- .await?;\n-\n- match response {\n- ResponseInputItem::FunctionCallOutput { output, .. } => {\n- let content = output.text_content().unwrap_or_default();\n- assert!(\n- content.contains(\"direct tool calls are d...", - "path": "codex-rs/core/src/tools/router_tests.rs", - "status": "modified" - }, - { - "additions": 40, - "deletions": 0, - "patch_excerpt": "@@ -537,6 +537,46 @@ Error:\\ boom\\n\n Ok(())\n }\n \n+#[cfg_attr(windows, ignore = \"no exec_command on Windows\")]\n+#[tokio::test(flavor = \"multi_thread\", worker_threads = 2)]\n+async fn code_mode_exec_surfaces_handler_errors_as_exceptions() -> Result<()> {\n+ skip_if_no_network!(Ok(()));\n+\n+ let server = responses::start_mock_server().await;\n+ let (_test, second_mock) = run_code_mode_turn(\n+ &server,\n+ \"surface nested tool handler failures as script exceptions\",\n+ r#\"\n+try {\n+ await tools.exec_command({});\n+ text(\"no-exception\");\n+} catch (error) {\n+ text(`caught:${error?.message ?? String(error)}`);\n+}\n+\"#,\n+ false,\n+ )\n+ .await?;\n+\n+ let request = second_mock.single_request();\n+ let (output, success) = custom_tool_output_body_and_success(&request, \"call-1\");\n+ assert_ne!(\n+ success,\n+ Some(false),\n+ \"script shoul...", - "path": "codex-rs/core/tests/suite/code_mode.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "Clean up error flow to push the FunctionCallError all the way up to dispatcher and allow code mode to surface as exception.\r\n", - "labels": [], - "merged_at": "2026-03-18T20:57:56Z", - "number": 15075, - "state": "merged", - "title": "Propagate tool errors to code mode", - "url": "https://github.com/openai/codex/pull/15075" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15076.json b/artifacts/github/bundles/openai-codex-pr-15076.json deleted file mode 100644 index 9e74da6..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15076.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "etraut-openai", - "committed_at": "2026-03-18T18:36:07Z", - "message": "Warn about deprecated custom prompts at startup", - "sha": "3c62fb077e09247c0251e4efb52ee11b9b3f2130", - "url": "https://github.com/openai/codex/commit/3c62fb077e09247c0251e4efb52ee11b9b3f2130" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "CODEX_HOME", - "TUI" - ], - "files": [ - { - "additions": 83, - "deletions": 0, - "patch_excerpt": "@@ -285,6 +285,32 @@ fn emit_missing_system_bwrap_warning(app_event_tx: &AppEventSender) {\n )));\n }\n \n+async fn emit_custom_prompt_deprecation_notice(app_event_tx: &AppEventSender, codex_home: &Path) {\n+ let prompts_dir = codex_home.join(\"prompts\");\n+ let prompt_count = codex_core::custom_prompts::discover_prompts_in(&prompts_dir)\n+ .await\n+ .len();\n+ if prompt_count == 0 {\n+ return;\n+ }\n+\n+ let prompt_label = if prompt_count == 1 {\n+ \"prompt\"\n+ } else {\n+ \"prompts\"\n+ };\n+ let details = format!(\n+ \"Detected {prompt_count} custom {prompt_label} in `$CODEX_HOME/prompts`. Use the `$skill-creator` skill to convert each custom prompt into a skill.\"\n+ );\n+\n+ app_event_tx.send(AppEvent::InsertHistoryCell(Box::new(\n+ history_cell::new_deprecation_notice(\n+ \"Custom prompts are deprecated and will soon be...", - "path": "codex-rs/tui/src/app.rs", - "status": "modified" - }, - { - "additions": 7, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,7 @@\n+---\n+source: tui/src/app.rs\n+expression: rendered\n+---\n+\u26a0 Custom prompts are deprecated and will soon be removed.\n+Detected 1 custom prompt in `$CODEX_HOME/prompts`. Use the `$skill-creator` skill to convert each custom prompt into\n+a skill.", - "path": "codex-rs/tui/src/snapshots/codex_tui__app__tests__startup_custom_prompt_deprecation_notice.snap", - "status": "added" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "## Summary\r\n- detect custom prompts in `$CODEX_HOME/prompts` during TUI startup\r\n- show a deprecation notice only when prompts are present, with guidance to use `$skill-creator`\r\n- add TUI tests and snapshot coverage for present, missing, and empty prompts directories\r\n\r\n## Testing\r\n- Manually tested", - "labels": [], - "merged_at": "2026-03-18T21:21:31Z", - "number": 15076, - "state": "merged", - "title": "Add a startup deprecation warning for custom prompts", - "url": "https://github.com/openai/codex/pull/15076" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15077.json b/artifacts/github/bundles/openai-codex-pr-15077.json deleted file mode 100644 index c4273c2..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15077.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "aibrahim-oai", - "committed_at": "2026-03-18T18:37:58Z", - "message": "Add final message prefix to realtime handoff output", - "sha": "26e02272bc36cf53a970701d4a4578e393d94184", - "url": "https://github.com/openai/codex/commit/26e02272bc36cf53a970701d4a4578e393d94184" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-18T19:14:43Z", - "message": "Merge branch 'main' into dev/realtime-final-message-prefix", - "sha": "b1ca236389562d9f84695e2428857802178f5122", - "url": "https://github.com/openai/codex/commit/b1ca236389562d9f84695e2428857802178f5122" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "REALTIME_AUDIO_SAMPLE_RATE", - "AGENT_FINAL_MESSAGE_PREFIX" - ], - "files": [ - { - "additions": 5, - "deletions": 2, - "patch_excerpt": "@@ -1173,7 +1173,10 @@ mod tests {\n let fourth_json: Value = serde_json::from_str(&fourth).expect(\"json\");\n assert_eq!(fourth_json[\"type\"], \"conversation.handoff.append\");\n assert_eq!(fourth_json[\"handoff_id\"], \"handoff_1\");\n- assert_eq!(fourth_json[\"output_text\"], \"hello from codex\");\n+ assert_eq!(\n+ fourth_json[\"output_text\"],\n+ \"\\\"Agent Final Message\\\":\\n\\nhello from codex\"\n+ );\n \n ws.send(Message::Text(\n json!({\n@@ -1504,7 +1507,7 @@ mod tests {\n );\n assert_eq!(\n third_json[\"item\"][\"output\"],\n- Value::String(\"delegated result\".to_string())\n+ Value::String(\"\\\"Agent Final Message\\\":\\n\\ndelegated result\".to_string())\n );\n });", - "path": "codex-rs/codex-api/src/endpoint/realtime_websocket/methods.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -12,6 +12,7 @@ use crate::endpoint::realtime_websocket::protocol::RealtimeSessionMode;\n use crate::endpoint::realtime_websocket::protocol::SessionUpdateSession;\n \n pub(super) const REALTIME_AUDIO_SAMPLE_RATE: u32 = 24_000;\n+const AGENT_FINAL_MESSAGE_PREFIX: &str = \"\\\"Agent Final Message\\\":\\n\\n\";\n \n pub(super) fn normalized_session_mode(\n event_parser: RealtimeEventParser,\n@@ -38,6 +39,7 @@ pub(super) fn conversation_handoff_append_message(\n handoff_id: String,\n output_text: String,\n ) -> RealtimeOutboundMessage {\n+ let output_text = format!(\"{AGENT_FINAL_MESSAGE_PREFIX}{output_text}\");\n match event_parser {\n RealtimeEventParser::V1 => v1_conversation_handoff_append_message(handoff_id, output_text),\n RealtimeEventParser::RealtimeV2 => {", - "path": "codex-rs/codex-api/src/endpoint/realtime_websocket/methods_common.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 4, - "patch_excerpt": "@@ -1144,7 +1144,7 @@ async fn conversation_mirrors_assistant_message_text_to_realtime_handoff() -> Re\n );\n assert_eq!(\n realtime_connections[0][1].body_json()[\"output_text\"].as_str(),\n- Some(\"assistant says hi\")\n+ Some(\"\\\"Agent Final Message\\\":\\n\\nassistant says hi\")\n );\n \n realtime_server.shutdown().await;\n@@ -1249,7 +1249,7 @@ async fn conversation_handoff_persists_across_item_done_until_turn_complete() ->\n );\n assert_eq!(\n first_append.body_json()[\"output_text\"].as_str(),\n- Some(\"assistant message 1\")\n+ Some(\"\\\"Agent Final Message\\\":\\n\\nassistant message 1\")\n );\n \n let _ = wait_for_event_match(&test.codex, |msg| match msg {\n@@ -1273,7 +1273,7 @@ async fn conversation_handoff_persists_across_item_done_until_turn_complete() ->\n );\n assert_eq!(\n second_append.body_json()[\"output_text\"].as_str(),\n...", - "path": "codex-rs/core/tests/suite/realtime_conversation.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "- prefix realtime handoff output with the agent final message label for both realtime v1 and v2\n- update realtime websocket and core expectations to match", - "labels": [], - "merged_at": "2026-03-18T22:19:50Z", - "number": 15077, - "state": "merged", - "title": "Add final message prefix to realtime handoff output", - "url": "https://github.com/openai/codex/pull/15077" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15088.json b/artifacts/github/bundles/openai-codex-pr-15088.json deleted file mode 100644 index 1bb13eb..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15088.json +++ /dev/null @@ -1,213 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "shaqayeq-oai", - "committed_at": "2026-03-18T03:56:37Z", - "message": "python-sdk: add thread.run convenience methods", - "sha": "35361493510c75d82bfb2ee6921d923a30527091", - "url": "https://github.com/openai/codex/commit/35361493510c75d82bfb2ee6921d923a30527091" - }, - { - "author": "shaqayeq-oai", - "committed_at": "2026-03-18T20:22:38Z", - "message": "python-sdk: update quickstart examples for thread.run", - "sha": "33289a1dd3e6bfbc5c15ab1bdf64b8c6db024253", - "url": "https://github.com/openai/codex/commit/33289a1dd3e6bfbc5c15ab1bdf64b8c6db024253" - }, - { - "author": "shaqayeq-oai", - "committed_at": "2026-03-18T20:28:48Z", - "message": "Merge branch 'main' into dev/shaqayeq/python-sdk-thread-run", - "sha": "8c521cb74df0838781f4c9a17141d939a7643b6d", - "url": "https://github.com/openai/codex/commit/8c521cb74df0838781f4c9a17141d939a7643b6d" - }, - { - "author": "shaqayeq-oai", - "committed_at": "2026-03-18T20:50:40Z", - "message": "python-sdk: fix thread.run final response semantics", - "sha": "5f141adc0dcec3af0e33e9c65f5d0305139ecc99", - "url": "https://github.com/openai/codex/commit/5f141adc0dcec3af0e33e9c65f5d0305139ecc99" - }, - { - "author": "shaqayeq-oai", - "committed_at": "2026-03-18T20:57:14Z", - "message": "Merge branch 'main' into dev/shaqayeq/python-sdk-thread-run", - "sha": "c547a4334d71b41934a3b9d2b8475a38dced0e7b", - "url": "https://github.com/openai/codex/commit/c547a4334d71b41934a3b9d2b8475a38dced0e7b" - }, - { - "author": "shaqayeq-oai", - "committed_at": "2026-03-18T22:19:32Z", - "message": "python-sdk: use typed thread items for run results", - "sha": "62ff8ba5d40d89d838e6f991d37ac5f0b2e74db7", - "url": "https://github.com/openai/codex/commit/62ff8ba5d40d89d838e6f991d37ac5f0b2e74db7" - }, - { - "author": "shaqayeq-oai", - "committed_at": "2026-03-18T22:22:03Z", - "message": "Merge branch 'main' into dev/shaqayeq/python-sdk-thread-run", - "sha": "5bec0bb5f9e0dcb6953e8188317e89ec13a991fc", - "url": "https://github.com/openai/codex/commit/5bec0bb5f9e0dcb6953e8188317e89ec13a991fc" - }, - { - "author": "shaqayeq-oai", - "committed_at": "2026-03-18T22:46:36Z", - "message": "Merge remote-tracking branch 'origin/main' into dev/shaqayeq/python-sdk-thread-run", - "sha": "93f5ebe275a20be612169d6680547f7e395b26d2", - "url": "https://github.com/openai/codex/commit/93f5ebe275a20be612169d6680547f7e395b26d2" - }, - { - "author": "shaqayeq-oai", - "committed_at": "2026-03-18T23:00:21Z", - "message": "python-sdk: prefer final-answer phase in run results", - "sha": "4c52a5dd47de08bc731d79ae82b1c9146a5bc7fc", - "url": "https://github.com/openai/codex/commit/4c52a5dd47de08bc731d79ae82b1c9146a5bc7fc" - }, - { - "author": "shaqayeq-oai", - "committed_at": "2026-03-18T23:08:54Z", - "message": "python-sdk: split convenience helpers out of api", - "sha": "49ff2823022f7ed7c99f0f16db25446089993611", - "url": "https://github.com/openai/codex/commit/49ff2823022f7ed7c99f0f16db25446089993611" - }, - { - "author": "shaqayeq-oai", - "committed_at": "2026-03-18T23:11:50Z", - "message": "python-sdk: inline turn handles into api facade", - "sha": "a32b804c29b864c635e76356f4bd041a58acd587", - "url": "https://github.com/openai/codex/commit/a32b804c29b864c635e76356f4bd041a58acd587" - }, - { - "author": "shaqayeq-oai", - "committed_at": "2026-03-19T00:54:58Z", - "message": "python-sdk: make run final_response optional", - "sha": "7f5e539e1a2cb14d3f343a18fd8bbd603c1a12dd", - "url": "https://github.com/openai/codex/commit/7f5e539e1a2cb14d3f343a18fd8bbd603c1a12dd" - } - ], - "default_branch": "main", - "docs_refs": [ - "sdk/python/README.md" - ], - "examples_refs": [ - "sdk/python/examples/01_quickstart_constructor/async.py", - "sdk/python/examples/01_quickstart_constructor/sync.py" - ], - "extracted_flags": [ - "SDK", - "THREAD_ID", - "BEGIN", - "GENERATED", - "ANN001", - "ANN202", - "ARG005", - "E731", - "ARG001", - "ROOT" - ], - "files": [ - { - "additions": 9, - "deletions": 4, - "patch_excerpt": "@@ -19,15 +19,18 @@ installs the pinned runtime package automatically.\n ## Quickstart\n \n ```python\n-from codex_app_server import Codex, TextInput\n+from codex_app_server import Codex\n \n with Codex() as codex:\n thread = codex.thread_start(model=\"gpt-5\")\n- completed_turn = thread.turn(TextInput(\"Say hello in one sentence.\")).run()\n- print(completed_turn.status)\n- print(completed_turn.id)\n+ result = thread.run(\"Say hello in one sentence.\")\n+ print(result.final_response)\n+ print(len(result.items))\n ```\n \n+`result.final_response` is `None` when the turn completes without a final-answer\n+or phase-less assistant message item.\n+\n ## Docs map\n \n - Golden path tutorial: `docs/getting-started.md`\n@@ -95,4 +98,6 @@ This supports the CI release flow:\n \n - `Codex()` is eager and performs startup + `initialize` in the constructor.\n - Use context managers (`with Codex() as codex:`) ...", - "path": "sdk/python/README.md", - "status": "modified" - }, - { - "additions": 22, - "deletions": 5, - "patch_excerpt": "@@ -2,14 +2,15 @@\n \n Public surface of `codex_app_server` for app-server v2.\n \n-This SDK surface is experimental. The current implementation intentionally allows only one active `TurnHandle.stream()` or `TurnHandle.run()` consumer per client instance at a time.\n+This SDK surface is experimental. The current implementation intentionally allows only one active turn consumer (`Thread.run()`, `TurnHandle.stream()`, or `TurnHandle.run()`) per client instance at a time.\n \n ## Package Entry\n \n ```python\n from codex_app_server import (\n Codex,\n AsyncCodex,\n+ RunResult,\n Thread,\n AsyncThread,\n TurnHandle,\n@@ -24,7 +25,7 @@ from codex_app_server import (\n MentionInput,\n TurnStatus,\n )\n-from codex_app_server.generated.v2_all import ThreadItem\n+from codex_app_server.generated.v2_all import ThreadItem, ThreadTokenUsage\n ```\n \n - Version: `codex_app_server.__version__`\n@...", - "path": "sdk/python/docs/api-reference.md", - "status": "modified" - }, - { - "additions": 19, - "deletions": 19, - "patch_excerpt": "@@ -22,41 +22,42 @@ Requirements:\n ## 2) Run your first turn (sync)\n \n ```python\n-from codex_app_server import Codex, TextInput\n+from codex_app_server import Codex\n \n with Codex() as codex:\n server = codex.metadata.serverInfo\n print(\"Server:\", None if server is None else server.name, None if server is None else server.version)\n \n thread = codex.thread_start(model=\"gpt-5.4\", config={\"model_reasoning_effort\": \"high\"})\n- completed_turn = thread.turn(TextInput(\"Say hello in one sentence.\")).run()\n+ result = thread.run(\"Say hello in one sentence.\")\n \n print(\"Thread:\", thread.id)\n- print(\"Turn:\", completed_turn.id)\n- print(\"Status:\", completed_turn.status)\n- print(\"Items:\", len(completed_turn.items or []))\n+ print(\"Text:\", result.final_response)\n+ print(\"Items:\", len(result.items))\n ```\n \n What happened:\n \n - `Codex()` started and initialized `codex app-ser...", - "path": "sdk/python/docs/getting-started.md", - "status": "modified" - }, - { - "additions": 4, - "deletions": 10, - "patch_excerpt": "@@ -6,9 +6,7 @@\n sys.path.insert(0, str(_EXAMPLES_ROOT))\n \n from _bootstrap import (\n- assistant_text_from_turn,\n ensure_local_sdk_src,\n- find_turn_by_id,\n runtime_config,\n server_label,\n )\n@@ -17,21 +15,17 @@\n \n import asyncio\n \n-from codex_app_server import AsyncCodex, TextInput\n+from codex_app_server import AsyncCodex\n \n \n async def main() -> None:\n async with AsyncCodex(config=runtime_config()) as codex:\n print(\"Server:\", server_label(codex.metadata))\n \n thread = await codex.thread_start(model=\"gpt-5.4\", config={\"model_reasoning_effort\": \"high\"})\n- turn = await thread.turn(TextInput(\"Say hello in one sentence.\"))\n- result = await turn.run()\n- persisted = await thread.read(include_turns=True)\n- persisted_turn = find_turn_by_id(persisted.thread.turns, result.id)\n-\n- print(\"Status:\", result.status)\n- pri...", - "path": "sdk/python/examples/01_quickstart_constructor/async.py", - "status": "modified" - }, - { - "additions": 4, - "deletions": 8, - "patch_excerpt": "@@ -6,23 +6,19 @@\n sys.path.insert(0, str(_EXAMPLES_ROOT))\n \n from _bootstrap import (\n- assistant_text_from_turn,\n ensure_local_sdk_src,\n- find_turn_by_id,\n runtime_config,\n server_label,\n )\n \n ensure_local_sdk_src()\n \n-from codex_app_server import Codex, TextInput\n+from codex_app_server import Codex\n \n with Codex(config=runtime_config()) as codex:\n print(\"Server:\", server_label(codex.metadata))\n \n thread = codex.thread_start(model=\"gpt-5.4\", config={\"model_reasoning_effort\": \"high\"})\n- result = thread.turn(TextInput(\"Say hello in one sentence.\")).run()\n- persisted = thread.read(include_turns=True)\n- persisted_turn = find_turn_by_id(persisted.thread.turns, result.id)\n- print(\"Status:\", result.status)\n- print(\"Text:\", assistant_text_from_turn(persisted_turn))\n+ result = thread.run(\"Say hello in one sentence.\")\n+ print(\"Items:\", len(resul...", - "path": "sdk/python/examples/01_quickstart_constructor/sync.py", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -47,6 +47,7 @@\n InputItem,\n LocalImageInput,\n MentionInput,\n+ RunResult,\n SkillInput,\n TextInput,\n Thread,\n@@ -68,6 +69,7 @@\n \"TurnHandle\",\n \"AsyncTurnHandle\",\n \"InitializeResponse\",\n+ \"RunResult\",\n \"Input\",\n \"InputItem\",\n \"TextInput\",", - "path": "sdk/python/src/codex_app_server/__init__.py", - "status": "modified" - }, - { - "additions": 63, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,63 @@\n+from __future__ import annotations\n+\n+from dataclasses import dataclass\n+\n+from .models import JsonObject\n+\n+\n+@dataclass(slots=True)\n+class TextInput:\n+ text: str\n+\n+\n+@dataclass(slots=True)\n+class ImageInput:\n+ url: str\n+\n+\n+@dataclass(slots=True)\n+class LocalImageInput:\n+ path: str\n+\n+\n+@dataclass(slots=True)\n+class SkillInput:\n+ name: str\n+ path: str\n+\n+\n+@dataclass(slots=True)\n+class MentionInput:\n+ name: str\n+ path: str\n+\n+\n+InputItem = TextInput | ImageInput | LocalImageInput | SkillInput | MentionInput\n+Input = list[InputItem] | InputItem\n+RunInput = Input | str\n+\n+\n+def _to_wire_item(item: InputItem) -> JsonObject:\n+ if isinstance(item, TextInput):\n+ return {\"type\": \"text\", \"text\": item.text}\n+ if isinstance(item, ImageInput):\n+ return {\"type\": \"image\", \"url\": item.url}\n+ if isinstance(item, LocalImageInput):\n+ ...", - "path": "sdk/python/src/codex_app_server/_inputs.py", - "status": "added" - }, - { - "additions": 112, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,112 @@\n+from __future__ import annotations\n+\n+from dataclasses import dataclass\n+from typing import AsyncIterator, Iterator\n+\n+from .generated.v2_all import (\n+ AgentMessageThreadItem,\n+ ItemCompletedNotification,\n+ MessagePhase,\n+ ThreadItem,\n+ ThreadTokenUsage,\n+ ThreadTokenUsageUpdatedNotification,\n+ Turn as AppServerTurn,\n+ TurnCompletedNotification,\n+ TurnStatus,\n+)\n+from .models import Notification\n+\n+\n+@dataclass(slots=True)\n+class RunResult:\n+ final_response: str | None\n+ items: list[ThreadItem]\n+ usage: ThreadTokenUsage | None\n+\n+\n+def _agent_message_item_from_thread_item(\n+ item: ThreadItem,\n+) -> AgentMessageThreadItem | None:\n+ thread_item = item.root if hasattr(item, \"root\") else item\n+ if isinstance(thread_item, AgentMessageThreadItem):\n+ return thread_item\n+ return None\n+\n+\n+def _final_assistant_response_fro...", - "path": "sdk/python/src/codex_app_server/_run.py", - "status": "added" - }, - { - "additions": 86, - "deletions": 52, - "patch_excerpt": "@@ -7,6 +7,7 @@\n from .async_client import AsyncAppServerClient\n from .client import AppServerClient, AppServerConfig\n from .generated.v2_all import (\n+ ApprovalsReviewer,\n AskForApproval,\n ModelListResponse,\n Personality,\n@@ -18,7 +19,6 @@\n ThreadArchiveResponse,\n ThreadCompactStartResponse,\n ThreadForkParams,\n- ThreadItem,\n ThreadListParams,\n ThreadListResponse,\n ThreadReadResponse,\n@@ -34,57 +34,23 @@\n TurnSteerResponse,\n )\n from .models import InitializeResponse, JsonObject, Notification, ServerInfo\n-\n-\n-@dataclass(slots=True)\n-class TextInput:\n- text: str\n-\n-\n-@dataclass(slots=True)\n-class ImageInput:\n- url: str\n-\n-\n-@dataclass(slots=True)\n-class LocalImageInput:\n- path: str\n-\n-\n-@dataclass(slots=True)\n-class SkillInput:\n- name: str\n- path: str\n-\n-\n-@dataclass(slots=True)\n-class MentionInput:\n- name: str\n- path: str\n...", - "path": "sdk/python/src/codex_app_server/api.py", - "status": "modified" - }, - { - "additions": 344, - "deletions": 4, - "patch_excerpt": "@@ -3,21 +3,28 @@\n import asyncio\n from collections import deque\n from pathlib import Path\n+from types import SimpleNamespace\n \n import pytest\n \n import codex_app_server.api as public_api_module\n from codex_app_server.client import AppServerClient\n from codex_app_server.generated.v2_all import (\n AgentMessageDeltaNotification,\n+ ItemCompletedNotification,\n+ MessagePhase,\n+ ThreadTokenUsageUpdatedNotification,\n TurnCompletedNotification,\n TurnStatus,\n )\n from codex_app_server.models import InitializeResponse, Notification\n from codex_app_server.api import (\n AsyncCodex,\n+ AsyncThread,\n AsyncTurnHandle,\n Codex,\n+ RunResult,\n+ Thread,\n TurnHandle,\n )\n \n@@ -48,16 +55,78 @@ def _completed_notification(\n thread_id: str = \"thread-1\",\n turn_id: str = \"turn-1\",\n status: str = \"completed\",\n+ error_message: str | None = None,\n ) -> Notifi...", - "path": "sdk/python/tests/test_public_api_runtime_behavior.py", - "status": "modified" - }, - { - "additions": 29, - "deletions": 1, - "patch_excerpt": "@@ -4,7 +4,7 @@\n import inspect\n from typing import Any\n \n-from codex_app_server import AppServerConfig\n+from codex_app_server import AppServerConfig, RunResult\n from codex_app_server.models import InitializeResponse\n from codex_app_server.api import AsyncCodex, AsyncThread, Codex, Thread\n \n@@ -31,6 +31,10 @@ def test_root_exports_app_server_config() -> None:\n assert AppServerConfig.__name__ == \"AppServerConfig\"\n \n \n+def test_root_exports_run_result() -> None:\n+ assert RunResult.__name__ == \"RunResult\"\n+\n+\n def test_package_includes_py_typed_marker() -> None:\n marker = resources.files(\"codex_app_server\").joinpath(\"py.typed\")\n assert marker.is_file()\n@@ -101,6 +105,18 @@ def test_generated_public_signatures_are_snake_case_and_typed() -> None:\n \"service_tier\",\n \"summary\",\n ],\n+ Thread.run: [\n+ \"approval_policy\",\n+ ...", - "path": "sdk/python/tests/test_public_api_signatures.py", - "status": "modified" - }, - { - "additions": 66, - "deletions": 0, - "patch_excerpt": "@@ -265,6 +265,36 @@ def test_real_thread_and_turn_start_smoke(runtime_env: PreparedRuntimeEnv) -> No\n assert isinstance(data[\"persisted_items_count\"], int)\n \n \n+def test_real_thread_run_convenience_smoke(runtime_env: PreparedRuntimeEnv) -> None:\n+ data = _run_json_python(\n+ runtime_env,\n+ textwrap.dedent(\n+ \"\"\"\n+ import json\n+ from codex_app_server import Codex\n+\n+ with Codex() as codex:\n+ thread = codex.thread_start(\n+ model=\"gpt-5.4\",\n+ config={\"model_reasoning_effort\": \"high\"},\n+ )\n+ result = thread.run(\"say ok\")\n+ print(json.dumps({\n+ \"thread_id\": thread.id,\n+ \"final_response\": result.final_response,\n+ \"items_count\": len(result.items),\n+ \"has_usage...", - "path": "sdk/python/tests/test_real_app_server_integration.py", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "## TL;DR\nAdd `thread.run(...)` / `async thread.run(...)` convenience methods to the Python SDK for the common case.\n\n- add `RunInput = Input | str` and `RunResult` with `final_response`, collected `items`, and optional `usage`\n- keep `thread.turn(...)` strict and lower-level for streaming, steering, interrupting, and raw generated `Turn` access\n- update Python SDK docs, quickstart examples, and tests for the sync and async convenience flows\n\n## Validation\n- `python3 -m pytest sdk/python/tests/test_public_api_signatures.py sdk/python/tests/test_public_api_runtime_behavior.py`\n- `python3 -m pytest sdk/python/tests/test_real_app_server_integration.py -k 'thread_run_convenience or async_thread_run_convenience'` (skipped in this environment)\n", - "labels": [ - "oai" - ], - "merged_at": "2026-03-19T00:57:48Z", - "number": 15088, - "state": "merged", - "title": "Add Python SDK thread.run convenience methods", - "url": "https://github.com/openai/codex/pull/15088" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15089.json b/artifacts/github/bundles/openai-codex-pr-15089.json deleted file mode 100644 index b04cb32..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15089.json +++ /dev/null @@ -1,305 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "starr-openai", - "committed_at": "2026-03-16T23:10:50Z", - "message": "Add codex-exec-server crate", - "sha": "144c3593dbd3bc2ccdc6906f2afbc4cfc2f069b2", - "url": "https://github.com/openai/codex/commit/144c3593dbd3bc2ccdc6906f2afbc4cfc2f069b2" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-17T00:37:34Z", - "message": "docs(exec-server): add protocol README", - "sha": "949932ca110e5c4211fc9e2f73f24d79d38ebb19", - "url": "https://github.com/openai/codex/commit/949932ca110e5c4211fc9e2f73f24d79d38ebb19" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-18T19:29:54Z", - "message": "Add Bazel package for exec-server", - "sha": "40cc199757b1665fef748ed762e9ee9d142d2cbc", - "url": "https://github.com/openai/codex/commit/40cc199757b1665fef748ed762e9ee9d142d2cbc" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-18T19:48:00Z", - "message": "Trim exec-server PR to stub server slice", - "sha": "2958067cf9579c022eb14fca67001867147fe2ad", - "url": "https://github.com/openai/codex/commit/2958067cf9579c022eb14fca67001867147fe2ad" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-18T20:01:16Z", - "message": "Keep first exec-server PR initialize-only", - "sha": "76071974bb36af24188b1bff81585f57dc51df33", - "url": "https://github.com/openai/codex/commit/76071974bb36af24188b1bff81585f57dc51df33" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-18T20:59:41Z", - "message": "Add generic RPC server glue to exec-server stub", - "sha": "16ff474725b011aea5c9e78f56319e0c055b3490", - "url": "https://github.com/openai/codex/commit/16ff474725b011aea5c9e78f56319e0c055b3490" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-18T21:30:57Z", - "message": "Add generic exec-server RPC foundation", - "sha": "0a846a2625a56d2a913e516c476abd5a1d71c0e1", - "url": "https://github.com/openai/codex/commit/0a846a2625a56d2a913e516c476abd5a1d71c0e1" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-18T21:32:35Z", - "message": "Wire notification sender into exec-server RPC foundation", - "sha": "c5dbe421bb606d8bfa154c6c5cdd1eb89ab6a8a2", - "url": "https://github.com/openai/codex/commit/c5dbe421bb606d8bfa154c6c5cdd1eb89ab6a8a2" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-18T21:34:21Z", - "message": "Remove outer handler mutex from exec-server RPC base", - "sha": "66f49ea604315574d0b8a92d75dc196d5470df0a", - "url": "https://github.com/openai/codex/commit/66f49ea604315574d0b8a92d75dc196d5470df0a" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-18T21:43:39Z", - "message": "Test generic exec-server RPC response matching", - "sha": "43b112c263c5d99e77d5dc9231ae2a6312b1c8ab", - "url": "https://github.com/openai/codex/commit/43b112c263c5d99e77d5dc9231ae2a6312b1c8ab" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-18T23:30:10Z", - "message": "exec-server: simplify stub json-rpc transport shape", - "sha": "2661dc53309b75e3d817362599af730e93c555c2", - "url": "https://github.com/openai/codex/commit/2661dc53309b75e3d817362599af730e93c555c2" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-18T23:41:24Z", - "message": "exec-server: address transport review feedback", - "sha": "3b4a5229113ccd0ea14ed46a9a8e204457aa2baf", - "url": "https://github.com/openai/codex/commit/3b4a5229113ccd0ea14ed46a9a8e204457aa2baf" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-18T23:53:24Z", - "message": "exec-server: keep malformed-json connections alive", - "sha": "2ed509ddd33c3001e5ef50d84766b97ab5bf0d83", - "url": "https://github.com/openai/codex/commit/2ed509ddd33c3001e5ef50d84766b97ab5bf0d83" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-19T00:04:44Z", - "message": "exec-server: trim unused stub dependencies", - "sha": "866743de96e8051adcd7db1c7df4875e1d984213", - "url": "https://github.com/openai/codex/commit/866743de96e8051adcd7db1c7df4875e1d984213" - } - ], - "default_branch": "main", - "docs_refs": [ - "codex-rs/exec-server/README.md" - ], - "examples_refs": [], - "extracted_flags": [ - "JSON", - "CLI", - "README", - "RPC", - "API", - "PORT", - "PATH", - "PTY", - "DEFAULT_OUTPUT_BYTES_CAP", - "IFS", - "ZWN", - "URL", - "DEFAULT_LISTEN_URL", - "INITIALIZE_METHOD", - "INITIALIZED_METHOD", - "CONNECT_TIMEOUT", - "INITIALIZE_TIMEOUT", - "JSONRPCM", - "CHANNEL_CAPACITY", - "--listen", - "JSONRPCE", - "JSONRPCN", - "JSONRPCR" - ], - "files": [ - { - "additions": 20, - "deletions": 0, - "patch_excerpt": "@@ -2003,6 +2003,26 @@ dependencies = [\n \"wiremock\",\n ]\n \n+[[package]]\n+name = \"codex-exec-server\"\n+version = \"0.0.0\"\n+dependencies = [\n+ \"anyhow\",\n+ \"base64 0.22.1\",\n+ \"clap\",\n+ \"codex-app-server-protocol\",\n+ \"codex-utils-cargo-bin\",\n+ \"codex-utils-pty\",\n+ \"futures\",\n+ \"pretty_assertions\",\n+ \"serde\",\n+ \"serde_json\",\n+ \"thiserror 2.0.18\",\n+ \"tokio\",\n+ \"tokio-tungstenite\",\n+ \"tracing\",\n+]\n+\n [[package]]\n name = \"codex-execpolicy\"\n version = \"0.0.0\"", - "path": "codex-rs/Cargo.lock", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -26,6 +26,7 @@ members = [\n \"hooks\",\n \"secrets\",\n \"exec\",\n+ \"exec-server\",\n \"execpolicy\",\n \"execpolicy-legacy\",\n \"keyring-store\",", - "path": "codex-rs/Cargo.toml", - "status": "modified" - }, - { - "additions": 7, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,7 @@\n+load(\"//:defs.bzl\", \"codex_rust_crate\")\n+\n+codex_rust_crate(\n+ name = \"exec-server\",\n+ crate_name = \"codex_exec_server\",\n+ test_tags = [\"no-sandbox\"],\n+)", - "path": "codex-rs/exec-server/BUILD.bazel", - "status": "added" - }, - { - "additions": 40, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,40 @@\n+[package]\n+name = \"codex-exec-server\"\n+version.workspace = true\n+edition.workspace = true\n+license.workspace = true\n+\n+[lib]\n+doctest = false\n+\n+[[bin]]\n+name = \"codex-exec-server\"\n+path = \"src/bin/codex-exec-server.rs\"\n+\n+[lints]\n+workspace = true\n+\n+[dependencies]\n+clap = { workspace = true, features = [\"derive\"] }\n+codex-app-server-protocol = { workspace = true }\n+futures = { workspace = true }\n+serde = { workspace = true, features = [\"derive\"] }\n+serde_json = { workspace = true }\n+thiserror = { workspace = true }\n+tokio = { workspace = true, features = [\n+ \"io-std\",\n+ \"io-util\",\n+ \"macros\",\n+ \"net\",\n+ \"process\",\n+ \"rt-multi-thread\",\n+ \"sync\",\n+ \"time\",\n+] }\n+tokio-tungstenite = { workspace = true }\n+tracing = { workspace = true }\n+\n+[dev-dependencies]\n+anyhow = { workspace = true }\n+codex-utils-cargo-bin = { workspace = true }\n+pretty_asserti...", - "path": "codex-rs/exec-server/Cargo.toml", - "status": "added" - }, - { - "additions": 282, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,282 @@\n+# codex-exec-server\n+\n+`codex-exec-server` is a small standalone JSON-RPC server for spawning\n+and controlling subprocesses through `codex-utils-pty`.\n+\n+This PR intentionally lands only the standalone binary, client, wire protocol,\n+and docs. Exec and filesystem methods are stubbed server-side here and are\n+implemented in follow-up PRs.\n+\n+It currently provides:\n+\n+- a standalone binary: `codex-exec-server`\n+- a Rust client: `ExecServerClient`\n+- a small protocol module with shared request/response types\n+\n+This crate is intentionally narrow. It is not wired into the main Codex CLI or\n+unified-exec in this PR; it is only the standalone transport layer.\n+\n+## Transport\n+\n+The server speaks the shared `codex-app-server-protocol` message envelope on\n+the wire.\n+\n+The standalone binary supports:\n+\n+- `ws://IP:PORT` (default)\n+- `stdio://`\n+\n+Wire framing:\n+\n+- websocket: ...", - "path": "codex-rs/exec-server/README.md", - "status": "added" - }, - { - "additions": 20, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,20 @@\n+use clap::Parser;\n+use codex_exec_server::ExecServerTransport;\n+\n+#[derive(Debug, Parser)]\n+struct ExecServerArgs {\n+ /// Transport endpoint URL. Supported values: `ws://IP:PORT` (default),\n+ /// `stdio://`.\n+ #[arg(\n+ long = \"listen\",\n+ value_name = \"URL\",\n+ default_value = ExecServerTransport::DEFAULT_LISTEN_URL\n+ )]\n+ listen: ExecServerTransport,\n+}\n+\n+#[tokio::main]\n+async fn main() -> Result<(), Box> {\n+ let args = ExecServerArgs::parse();\n+ codex_exec_server::run_main_with_transport(args.listen).await\n+}", - "path": "codex-rs/exec-server/src/bin/codex-exec-server.rs", - "status": "added" - }, - { - "additions": 267, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,267 @@\n+use std::sync::Arc;\n+use std::time::Duration;\n+\n+use tokio::io::AsyncRead;\n+use tokio::io::AsyncWrite;\n+use tokio::time::timeout;\n+use tokio_tungstenite::connect_async;\n+use tracing::warn;\n+\n+use crate::client_api::ExecServerClientConnectOptions;\n+use crate::client_api::RemoteExecServerConnectArgs;\n+use crate::connection::JsonRpcConnection;\n+use crate::protocol::INITIALIZE_METHOD;\n+use crate::protocol::INITIALIZED_METHOD;\n+use crate::protocol::InitializeParams;\n+use crate::protocol::InitializeResponse;\n+use crate::rpc::RpcCallError;\n+use crate::rpc::RpcClient;\n+use crate::rpc::RpcClientEvent;\n+\n+mod local_backend;\n+use local_backend::LocalBackend;\n+\n+const CONNECT_TIMEOUT: Duration = Duration::from_secs(10);\n+const INITIALIZE_TIMEOUT: Duration = Duration::from_secs(10);\n+\n+impl Default for ExecServerClientConnectOptions {\n+ fn default() -> Self {\n+ Self {\n+ ...", - "path": "codex-rs/exec-server/src/client.rs", - "status": "added" - }, - { - "additions": 38, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,38 @@\n+use std::sync::Arc;\n+\n+use crate::protocol::InitializeResponse;\n+use crate::server::ExecServerHandler;\n+\n+use super::ExecServerError;\n+\n+#[derive(Clone)]\n+pub(super) struct LocalBackend {\n+ handler: Arc,\n+}\n+\n+impl LocalBackend {\n+ pub(super) fn new(handler: ExecServerHandler) -> Self {\n+ Self {\n+ handler: Arc::new(handler),\n+ }\n+ }\n+\n+ pub(super) async fn shutdown(&self) {\n+ self.handler.shutdown().await;\n+ }\n+\n+ pub(super) async fn initialize(&self) -> Result {\n+ self.handler\n+ .initialize()\n+ .map_err(|error| ExecServerError::Server {\n+ code: error.code,\n+ message: error.message,\n+ })\n+ }\n+\n+ pub(super) async fn initialized(&self) -> Result<(), ExecServerError> {\n+ self.handler\n+ ...", - "path": "codex-rs/exec-server/src/client/local_backend.rs", - "status": "added" - }, - { - "additions": 17, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,17 @@\n+use std::time::Duration;\n+\n+/// Connection options for any exec-server client transport.\n+#[derive(Debug, Clone, PartialEq, Eq)]\n+pub struct ExecServerClientConnectOptions {\n+ pub client_name: String,\n+ pub initialize_timeout: Duration,\n+}\n+\n+/// WebSocket connection arguments for a remote exec-server.\n+#[derive(Debug, Clone, PartialEq, Eq)]\n+pub struct RemoteExecServerConnectArgs {\n+ pub websocket_url: String,\n+ pub client_name: String,\n+ pub connect_timeout: Duration,\n+ pub initialize_timeout: Duration,\n+}", - "path": "codex-rs/exec-server/src/client_api.rs", - "status": "added" - }, - { - "additions": 275, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,275 @@\n+use codex_app_server_protocol::JSONRPCMessage;\n+use futures::SinkExt;\n+use futures::StreamExt;\n+use tokio::io::AsyncBufReadExt;\n+use tokio::io::AsyncRead;\n+use tokio::io::AsyncWrite;\n+use tokio::io::AsyncWriteExt;\n+use tokio::io::BufReader;\n+use tokio::io::BufWriter;\n+use tokio::sync::mpsc;\n+use tokio_tungstenite::WebSocketStream;\n+use tokio_tungstenite::tungstenite::Message;\n+\n+pub(crate) const CHANNEL_CAPACITY: usize = 128;\n+\n+#[derive(Debug)]\n+pub(crate) enum JsonRpcConnectionEvent {\n+ Message(JSONRPCMessage),\n+ MalformedMessage { reason: String },\n+ Disconnected { reason: Option },\n+}\n+\n+pub(crate) struct JsonRpcConnection {\n+ outgoing_tx: mpsc::Sender,\n+ incoming_rx: mpsc::Receiver,\n+ task_handles: Vec>,\n+}\n+\n+impl JsonRpcConnection {\n+ pub(crate) fn from_stdio(r...", - "path": "codex-rs/exec-server/src/connection.rs", - "status": "added" - }, - { - "additions": 21, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,21 @@\n+mod client;\n+mod client_api;\n+mod connection;\n+mod local;\n+mod protocol;\n+mod rpc;\n+mod server;\n+\n+pub use client::ExecServerClient;\n+pub use client::ExecServerError;\n+pub use client_api::ExecServerClientConnectOptions;\n+pub use client_api::RemoteExecServerConnectArgs;\n+pub use local::ExecServerLaunchCommand;\n+pub use local::SpawnedExecServer;\n+pub use local::spawn_local_exec_server;\n+pub use protocol::InitializeParams;\n+pub use protocol::InitializeResponse;\n+pub use server::ExecServerTransport;\n+pub use server::ExecServerTransportParseError;\n+pub use server::run_main;\n+pub use server::run_main_with_transport;", - "path": "codex-rs/exec-server/src/lib.rs", - "status": "added" - }, - { - "additions": 71, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,71 @@\n+use std::path::PathBuf;\n+use std::process::Stdio;\n+use std::sync::Mutex as StdMutex;\n+\n+use tokio::process::Child;\n+use tokio::process::Command;\n+\n+use crate::client::ExecServerClient;\n+use crate::client::ExecServerError;\n+use crate::client_api::ExecServerClientConnectOptions;\n+\n+#[derive(Debug, Clone, PartialEq, Eq)]\n+pub struct ExecServerLaunchCommand {\n+ pub program: PathBuf,\n+ pub args: Vec,\n+}\n+\n+pub struct SpawnedExecServer {\n+ client: ExecServerClient,\n+ child: StdMutex>,\n+}\n+\n+impl SpawnedExecServer {\n+ pub fn client(&self) -> &ExecServerClient {\n+ &self.client\n+ }\n+}\n+\n+impl Drop for SpawnedExecServer {\n+ fn drop(&mut self) {\n+ if let Ok(mut child_guard) = self.child.lock()\n+ && let Some(child) = child_guard.as_mut()\n+ {\n+ let _ = child.start_kill();\n+ }\n+ }\n+}\n+\n+pu...", - "path": "codex-rs/exec-server/src/local.rs", - "status": "added" - }, - { - "additions": 15, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,15 @@\n+use serde::Deserialize;\n+use serde::Serialize;\n+\n+pub const INITIALIZE_METHOD: &str = \"initialize\";\n+pub const INITIALIZED_METHOD: &str = \"initialized\";\n+\n+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]\n+#[serde(rename_all = \"camelCase\")]\n+pub struct InitializeParams {\n+ pub client_name: String,\n+}\n+\n+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]\n+#[serde(rename_all = \"camelCase\")]\n+pub struct InitializeResponse {}", - "path": "codex-rs/exec-server/src/protocol.rs", - "status": "added" - }, - { - "additions": 347, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,347 @@\n+use std::collections::HashMap;\n+use std::sync::Arc;\n+use std::sync::atomic::AtomicI64;\n+use std::sync::atomic::Ordering;\n+\n+use codex_app_server_protocol::JSONRPCError;\n+use codex_app_server_protocol::JSONRPCErrorError;\n+use codex_app_server_protocol::JSONRPCMessage;\n+use codex_app_server_protocol::JSONRPCNotification;\n+use codex_app_server_protocol::JSONRPCRequest;\n+use codex_app_server_protocol::JSONRPCResponse;\n+use codex_app_server_protocol::RequestId;\n+use serde::Serialize;\n+use serde::de::DeserializeOwned;\n+use serde_json::Value;\n+use tokio::sync::Mutex;\n+use tokio::sync::mpsc;\n+use tokio::sync::oneshot;\n+use tokio::task::JoinHandle;\n+use tracing::warn;\n+\n+use crate::connection::JsonRpcConnection;\n+use crate::connection::JsonRpcConnectionEvent;\n+\n+type PendingRequest = oneshot::Sender>;\n+\n+#[derive(Debug)]\n+pub(crate) enum RpcClie...", - "path": "codex-rs/exec-server/src/rpc.rs", - "status": "added" - }, - { - "additions": 18, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,18 @@\n+mod handler;\n+mod jsonrpc;\n+mod processor;\n+mod transport;\n+\n+pub(crate) use handler::ExecServerHandler;\n+pub use transport::ExecServerTransport;\n+pub use transport::ExecServerTransportParseError;\n+\n+pub async fn run_main() -> Result<(), Box> {\n+ run_main_with_transport(ExecServerTransport::Stdio).await\n+}\n+\n+pub async fn run_main_with_transport(\n+ transport: ExecServerTransport,\n+) -> Result<(), Box> {\n+ transport::run_transport(transport).await\n+}", - "path": "codex-rs/exec-server/src/server.rs", - "status": "added" - }, - { - "additions": 40, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,40 @@\n+use std::sync::atomic::AtomicBool;\n+use std::sync::atomic::Ordering;\n+\n+use codex_app_server_protocol::JSONRPCErrorError;\n+\n+use crate::protocol::InitializeResponse;\n+use crate::server::jsonrpc::invalid_request;\n+\n+pub(crate) struct ExecServerHandler {\n+ initialize_requested: AtomicBool,\n+ initialized: AtomicBool,\n+}\n+\n+impl ExecServerHandler {\n+ pub(crate) fn new() -> Self {\n+ Self {\n+ initialize_requested: AtomicBool::new(false),\n+ initialized: AtomicBool::new(false),\n+ }\n+ }\n+\n+ pub(crate) async fn shutdown(&self) {}\n+\n+ pub(crate) fn initialize(&self) -> Result {\n+ if self.initialize_requested.swap(true, Ordering::SeqCst) {\n+ return Err(invalid_request(\n+ \"initialize may only be sent once per connection\".to_string(),\n+ ));\n+ ...", - "path": "codex-rs/exec-server/src/server/handler.rs", - "status": "added" - }, - { - "additions": 53, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,53 @@\n+use codex_app_server_protocol::JSONRPCError;\n+use codex_app_server_protocol::JSONRPCErrorError;\n+use codex_app_server_protocol::JSONRPCMessage;\n+use codex_app_server_protocol::JSONRPCResponse;\n+use codex_app_server_protocol::RequestId;\n+use serde_json::Value;\n+\n+pub(crate) fn invalid_request(message: String) -> JSONRPCErrorError {\n+ JSONRPCErrorError {\n+ code: -32600,\n+ data: None,\n+ message,\n+ }\n+}\n+\n+pub(crate) fn invalid_params(message: String) -> JSONRPCErrorError {\n+ JSONRPCErrorError {\n+ code: -32602,\n+ data: None,\n+ message,\n+ }\n+}\n+\n+pub(crate) fn method_not_found(message: String) -> JSONRPCErrorError {\n+ JSONRPCErrorError {\n+ code: -32601,\n+ data: None,\n+ message,\n+ }\n+}\n+\n+pub(crate) fn response_message(\n+ request_id: RequestId,\n+ result: Result,\n+)...", - "path": "codex-rs/exec-server/src/server/jsonrpc.rs", - "status": "added" - }, - { - "additions": 121, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,121 @@\n+use codex_app_server_protocol::JSONRPCMessage;\n+use codex_app_server_protocol::JSONRPCNotification;\n+use codex_app_server_protocol::JSONRPCRequest;\n+use tracing::debug;\n+\n+use crate::connection::JsonRpcConnection;\n+use crate::connection::JsonRpcConnectionEvent;\n+use crate::protocol::INITIALIZE_METHOD;\n+use crate::protocol::INITIALIZED_METHOD;\n+use crate::protocol::InitializeParams;\n+use crate::server::ExecServerHandler;\n+use crate::server::jsonrpc::invalid_params;\n+use crate::server::jsonrpc::invalid_request_message;\n+use crate::server::jsonrpc::method_not_found;\n+use crate::server::jsonrpc::response_message;\n+use tracing::warn;\n+\n+pub(crate) async fn run_connection(connection: JsonRpcConnection) {\n+ let (json_outgoing_tx, mut incoming_rx, _connection_tasks) = connection.into_parts();\n+ let handler = ExecServerHandler::new();\n+\n+ while let Some(event) = incomi...", - "path": "codex-rs/exec-server/src/server/processor.rs", - "status": "added" - }, - { - "additions": 118, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,118 @@\n+use std::net::SocketAddr;\n+use std::str::FromStr;\n+\n+use tokio::net::TcpListener;\n+use tokio_tungstenite::accept_async;\n+use tracing::warn;\n+\n+use crate::connection::JsonRpcConnection;\n+use crate::server::processor::run_connection;\n+\n+#[derive(Clone, Copy, Debug, Eq, PartialEq)]\n+pub enum ExecServerTransport {\n+ Stdio,\n+ WebSocket { bind_address: SocketAddr },\n+}\n+\n+#[derive(Debug, Clone, Eq, PartialEq)]\n+pub enum ExecServerTransportParseError {\n+ UnsupportedListenUrl(String),\n+ InvalidWebSocketListenUrl(String),\n+}\n+\n+impl std::fmt::Display for ExecServerTransportParseError {\n+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n+ match self {\n+ ExecServerTransportParseError::UnsupportedListenUrl(listen_url) => write!(\n+ f,\n+ \"unsupported --listen URL `{listen_url}`; expected `stdio://` or...", - "path": "codex-rs/exec-server/src/server/transport.rs", - "status": "added" - }, - { - "additions": 54, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,54 @@\n+use pretty_assertions::assert_eq;\n+\n+use super::ExecServerTransport;\n+\n+#[test]\n+fn exec_server_transport_parses_default_websocket_listen_url() {\n+ let transport = ExecServerTransport::from_listen_url(ExecServerTransport::DEFAULT_LISTEN_URL)\n+ .expect(\"default listen URL should parse\");\n+ assert_eq!(\n+ transport,\n+ ExecServerTransport::WebSocket {\n+ bind_address: \"127.0.0.1:0\".parse().expect(\"valid socket address\"),\n+ }\n+ );\n+}\n+\n+#[test]\n+fn exec_server_transport_parses_stdio_listen_url() {\n+ let transport =\n+ ExecServerTransport::from_listen_url(\"stdio://\").expect(\"stdio listen URL should parse\");\n+ assert_eq!(transport, ExecServerTransport::Stdio);\n+}\n+\n+#[test]\n+fn exec_server_transport_parses_websocket_listen_url() {\n+ let transport = ExecServerTransport::from_listen_url(\"ws://127.0.0.1:1234\")\n+ ...", - "path": "codex-rs/exec-server/src/server/transport_tests.rs", - "status": "added" - }, - { - "additions": 129, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,129 @@\n+#![cfg(unix)]\n+\n+use std::process::Stdio;\n+use std::time::Duration;\n+\n+use codex_app_server_protocol::JSONRPCMessage;\n+use codex_app_server_protocol::JSONRPCNotification;\n+use codex_app_server_protocol::JSONRPCRequest;\n+use codex_app_server_protocol::JSONRPCResponse;\n+use codex_app_server_protocol::RequestId;\n+use codex_exec_server::InitializeParams;\n+use codex_exec_server::InitializeResponse;\n+use codex_utils_cargo_bin::cargo_bin;\n+use pretty_assertions::assert_eq;\n+use tokio::io::AsyncBufReadExt;\n+use tokio::io::AsyncWriteExt;\n+use tokio::io::BufReader;\n+use tokio::process::Command;\n+use tokio::time::timeout;\n+\n+#[tokio::test(flavor = \"multi_thread\", worker_threads = 2)]\n+async fn exec_server_accepts_initialize_over_stdio() -> anyhow::Result<()> {\n+ let binary = cargo_bin(\"codex-exec-server\")?;\n+ let mut child = Command::new(binary);\n+ child.args([\"--listen\"...", - "path": "codex-rs/exec-server/tests/stdio_smoke.rs", - "status": "added" - }, - { - "additions": 229, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,229 @@\n+#![cfg(unix)]\n+\n+use std::process::Stdio;\n+use std::time::Duration;\n+\n+use codex_app_server_protocol::JSONRPCError;\n+use codex_app_server_protocol::JSONRPCMessage;\n+use codex_app_server_protocol::JSONRPCNotification;\n+use codex_app_server_protocol::JSONRPCRequest;\n+use codex_app_server_protocol::JSONRPCResponse;\n+use codex_app_server_protocol::RequestId;\n+use codex_exec_server::InitializeParams;\n+use codex_exec_server::InitializeResponse;\n+use codex_utils_cargo_bin::cargo_bin;\n+use pretty_assertions::assert_eq;\n+use tokio::process::Command;\n+use tokio_tungstenite::connect_async;\n+use tokio_tungstenite::tungstenite::Message;\n+\n+#[tokio::test(flavor = \"multi_thread\", worker_threads = 2)]\n+async fn exec_server_accepts_initialize_over_websocket() -> anyhow::Result<()> {\n+ let binary = cargo_bin(\"codex-exec-server\")?;\n+ let websocket_url = reserve_websocket_url()?;\n+ ...", - "path": "codex-rs/exec-server/tests/websocket_smoke.rs", - "status": "added" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "Stacked PR 1/3.\n\nThis is the initialize-only exec-server stub slice: binary/client scaffolding and protocol docs, without exec/filesystem implementation.", - "labels": [], - "merged_at": "2026-03-19T00:30:05Z", - "number": 15089, - "state": "merged", - "title": "Add exec-server stub server and protocol docs", - "url": "https://github.com/openai/codex/pull/15089" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15090.json b/artifacts/github/bundles/openai-codex-pr-15090.json deleted file mode 100644 index 49e45f7..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15090.json +++ /dev/null @@ -1,190 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "starr-openai", - "committed_at": "2026-03-19T17:56:47Z", - "message": "Add exec-server process and filesystem RPCs (#15090)", - "sha": "dd1416a5ccce5bf9f05193c41045f61d6f771d9f", - "url": "https://github.com/openai/codex/commit/dd1416a5ccce5bf9f05193c41045f61d6f771d9f" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-19T17:58:47Z", - "message": "exec-server: report false after retained exit", - "sha": "ab89fdf2a7f4c08865a297734185f2f059cff216", - "url": "https://github.com/openai/codex/commit/ab89fdf2a7f4c08865a297734185f2f059cff216" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "RPC", - "USE_SCCACHE", - "CARGO_INCREMENTAL", - "SCCACHE_CACHE_SIZE", - "LTO", - "JSONRPCN", - "EXEC_EXITED_METHOD", - "EXEC_METHOD", - "EXEC_OUTPUT_DELTA_METHOD", - "EXEC_READ_METHOD", - "EXEC_TERMINATE_METHOD", - "EXEC_WRITE_METHOD", - "FS_COPY_METHOD", - "FS_CREATE_DIRECTORY_METHOD", - "FS_GET_METADATA_METHOD", - "FS_READ_DIRECTORY_METHOD", - "FS_READ_FILE_METHOD", - "FS_REMOVE_METHOD", - "FS_WRITE_FILE_METHOD", - "INITIALIZE_METHOD", - "INITIALIZED_METHOD", - "DEFAULT_LISTEN_URL", - "STANDARD", - "BASE64_STANDARD", - "JSONRPCE", - "JSONRPCR", - "JSONRPCM", - "JSON", - "RETAINED_OUTPUT_BYTES_PER_PROCESS", - "EXITED_PROCESS_RETENTION", - "MAX", - "PATH", - "CHANNEL_CAPACITY" - ], - "files": [ - { - "additions": 8, - "deletions": 4, - "patch_excerpt": "@@ -141,8 +141,10 @@ jobs:\n run:\n working-directory: codex-rs\n env:\n- # Speed up repeated builds across CI runs by caching compiled objects (non-Windows).\n- USE_SCCACHE: ${{ startsWith(matrix.runner, 'windows') && 'false' || 'true' }}\n+ # Speed up repeated builds across CI runs by caching compiled objects, except on\n+ # arm64 macOS runners cross-targeting x86_64 where ring/cc-rs can produce\n+ # mixed-architecture archives under sccache.\n+ USE_SCCACHE: ${{ (startsWith(matrix.runner, 'windows') || (matrix.runner == 'macos-15-xlarge' && matrix.target == 'x86_64-apple-darwin')) && 'false' || 'true' }}\n CARGO_INCREMENTAL: \"0\"\n SCCACHE_CACHE_SIZE: 10G\n # In rust-ci, representative release-profile checks use thin LTO for faster feedback.\n@@ -506,8 +508,10 @@ jobs:\n run:\n working-directory: codex-rs\n env:\n- ...", - "path": ".github/workflows/rust-ci.yml", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -1998,10 +1998,12 @@ version = \"0.0.0\"\n dependencies = [\n \"anyhow\",\n \"async-trait\",\n+ \"base64 0.22.1\",\n \"clap\",\n \"codex-app-server-protocol\",\n \"codex-utils-absolute-path\",\n \"codex-utils-cargo-bin\",\n+ \"codex-utils-pty\",\n \"futures\",\n \"pretty_assertions\",\n \"serde\",", - "path": "codex-rs/Cargo.lock", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -16,9 +16,11 @@ workspace = true\n \n [dependencies]\n async-trait = { workspace = true }\n+base64 = { workspace = true }\n clap = { workspace = true, features = [\"derive\"] }\n codex-app-server-protocol = { workspace = true }\n codex-utils-absolute-path = { workspace = true }\n+codex-utils-pty = { workspace = true }\n futures = { workspace = true }\n serde = { workspace = true, features = [\"derive\"] }\n serde_json = { workspace = true }", - "path": "codex-rs/exec-server/Cargo.toml", - "status": "modified" - }, - { - "additions": 328, - "deletions": 23, - "patch_excerpt": "@@ -1,20 +1,65 @@\n use std::sync::Arc;\n use std::time::Duration;\n \n+use codex_app_server_protocol::FsCopyParams;\n+use codex_app_server_protocol::FsCopyResponse;\n+use codex_app_server_protocol::FsCreateDirectoryParams;\n+use codex_app_server_protocol::FsCreateDirectoryResponse;\n+use codex_app_server_protocol::FsGetMetadataParams;\n+use codex_app_server_protocol::FsGetMetadataResponse;\n+use codex_app_server_protocol::FsReadDirectoryParams;\n+use codex_app_server_protocol::FsReadDirectoryResponse;\n+use codex_app_server_protocol::FsReadFileParams;\n+use codex_app_server_protocol::FsReadFileResponse;\n+use codex_app_server_protocol::FsRemoveParams;\n+use codex_app_server_protocol::FsRemoveResponse;\n+use codex_app_server_protocol::FsWriteFileParams;\n+use codex_app_server_protocol::FsWriteFileResponse;\n+use codex_app_server_protocol::JSONRPCNotification;\n+use serde_json::Value;\n+use tokio::sync::broa...", - "path": "codex-rs/exec-server/src/client.rs", - "status": "modified" - }, - { - "additions": 162, - "deletions": 0, - "patch_excerpt": "@@ -1,7 +1,29 @@\n use std::sync::Arc;\n \n+use crate::protocol::ExecParams;\n+use crate::protocol::ExecResponse;\n use crate::protocol::InitializeResponse;\n+use crate::protocol::ReadParams;\n+use crate::protocol::ReadResponse;\n+use crate::protocol::TerminateParams;\n+use crate::protocol::TerminateResponse;\n+use crate::protocol::WriteParams;\n+use crate::protocol::WriteResponse;\n use crate::server::ExecServerHandler;\n+use codex_app_server_protocol::FsCopyParams;\n+use codex_app_server_protocol::FsCopyResponse;\n+use codex_app_server_protocol::FsCreateDirectoryParams;\n+use codex_app_server_protocol::FsCreateDirectoryResponse;\n+use codex_app_server_protocol::FsGetMetadataParams;\n+use codex_app_server_protocol::FsGetMetadataResponse;\n+use codex_app_server_protocol::FsReadDirectoryParams;\n+use codex_app_server_protocol::FsReadDirectoryResponse;\n+use codex_app_server_protocol::FsReadFileParams;\n+use co...", - "path": "codex-rs/exec-server/src/client/local_backend.rs", - "status": "modified" - }, - { - "additions": 10, - "deletions": 0, - "patch_excerpt": "@@ -1,5 +1,8 @@\n use std::time::Duration;\n \n+use crate::protocol::ExecExitedNotification;\n+use crate::protocol::ExecOutputDeltaNotification;\n+\n /// Connection options for any exec-server client transport.\n #[derive(Debug, Clone, PartialEq, Eq)]\n pub struct ExecServerClientConnectOptions {\n@@ -15,3 +18,10 @@ pub struct RemoteExecServerConnectArgs {\n pub connect_timeout: Duration,\n pub initialize_timeout: Duration,\n }\n+\n+/// Connection-level server events.\n+#[derive(Debug, Clone, PartialEq, Eq)]\n+pub enum ExecServerEvent {\n+ OutputDelta(ExecOutputDeltaNotification),\n+ Exited(ExecExitedNotification),\n+}", - "path": "codex-rs/exec-server/src/client_api.rs", - "status": "modified" - }, - { - "additions": 27, - "deletions": 0, - "patch_excerpt": "@@ -10,7 +10,23 @@ mod server;\n pub use client::ExecServerClient;\n pub use client::ExecServerError;\n pub use client_api::ExecServerClientConnectOptions;\n+pub use client_api::ExecServerEvent;\n pub use client_api::RemoteExecServerConnectArgs;\n+pub use codex_app_server_protocol::FsCopyParams;\n+pub use codex_app_server_protocol::FsCopyResponse;\n+pub use codex_app_server_protocol::FsCreateDirectoryParams;\n+pub use codex_app_server_protocol::FsCreateDirectoryResponse;\n+pub use codex_app_server_protocol::FsGetMetadataParams;\n+pub use codex_app_server_protocol::FsGetMetadataResponse;\n+pub use codex_app_server_protocol::FsReadDirectoryEntry;\n+pub use codex_app_server_protocol::FsReadDirectoryParams;\n+pub use codex_app_server_protocol::FsReadDirectoryResponse;\n+pub use codex_app_server_protocol::FsReadFileParams;\n+pub use codex_app_server_protocol::FsReadFileResponse;\n+pub use codex_app_server_pro...", - "path": "codex-rs/exec-server/src/lib.rs", - "status": "modified" - }, - { - "additions": 151, - "deletions": 0, - "patch_excerpt": "@@ -1,8 +1,41 @@\n+use std::collections::HashMap;\n+use std::path::PathBuf;\n+\n+use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;\n use serde::Deserialize;\n use serde::Serialize;\n \n pub const INITIALIZE_METHOD: &str = \"initialize\";\n pub const INITIALIZED_METHOD: &str = \"initialized\";\n+pub const EXEC_METHOD: &str = \"process/start\";\n+pub const EXEC_READ_METHOD: &str = \"process/read\";\n+pub const EXEC_WRITE_METHOD: &str = \"process/write\";\n+pub const EXEC_TERMINATE_METHOD: &str = \"process/terminate\";\n+pub const EXEC_OUTPUT_DELTA_METHOD: &str = \"process/output\";\n+pub const EXEC_EXITED_METHOD: &str = \"process/exited\";\n+pub const FS_READ_FILE_METHOD: &str = \"fs/readFile\";\n+pub const FS_WRITE_FILE_METHOD: &str = \"fs/writeFile\";\n+pub const FS_CREATE_DIRECTORY_METHOD: &str = \"fs/createDirectory\";\n+pub const FS_GET_METADATA_METHOD: &str = \"fs/getMetadata\";\n+pub const FS_READ_DIRECTORY_ME...", - "path": "codex-rs/exec-server/src/protocol.rs", - "status": "modified" - }, - { - "additions": 227, - "deletions": 8, - "patch_excerpt": "@@ -1,4 +1,6 @@\n use std::collections::HashMap;\n+use std::future::Future;\n+use std::pin::Pin;\n use std::sync::Arc;\n use std::sync::atomic::AtomicI64;\n use std::sync::atomic::Ordering;\n@@ -23,13 +25,151 @@ use crate::connection::JsonRpcConnection;\n use crate::connection::JsonRpcConnectionEvent;\n \n type PendingRequest = oneshot::Sender>;\n+type BoxFuture = Pin + Send + 'static>>;\n+type RequestRoute =\n+ Box, JSONRPCRequest) -> BoxFuture + Send + Sync>;\n+type NotificationRoute =\n+ Box, JSONRPCNotification) -> BoxFuture> + Send + Sync>;\n \n #[derive(Debug)]\n pub(crate) enum RpcClientEvent {\n Notification(JSONRPCNotification),\n Disconnected { reason: Option },\n }\n \n+#[derive(Debug, Clone, PartialEq)]\n+pub(crate) enum RpcServerOutbound...", - "path": "codex-rs/exec-server/src/rpc.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 1, - "patch_excerpt": "@@ -1,6 +1,7 @@\n+mod filesystem;\n mod handler;\n-mod jsonrpc;\n mod processor;\n+mod registry;\n mod transport;\n \n pub(crate) use handler::ExecServerHandler;", - "path": "codex-rs/exec-server/src/server.rs", - "status": "modified" - }, - { - "additions": 170, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,170 @@\n+use std::io;\n+use std::sync::Arc;\n+\n+use base64::Engine as _;\n+use base64::engine::general_purpose::STANDARD;\n+use codex_app_server_protocol::FsCopyParams;\n+use codex_app_server_protocol::FsCopyResponse;\n+use codex_app_server_protocol::FsCreateDirectoryParams;\n+use codex_app_server_protocol::FsCreateDirectoryResponse;\n+use codex_app_server_protocol::FsGetMetadataParams;\n+use codex_app_server_protocol::FsGetMetadataResponse;\n+use codex_app_server_protocol::FsReadDirectoryEntry;\n+use codex_app_server_protocol::FsReadDirectoryParams;\n+use codex_app_server_protocol::FsReadDirectoryResponse;\n+use codex_app_server_protocol::FsReadFileParams;\n+use codex_app_server_protocol::FsReadFileResponse;\n+use codex_app_server_protocol::FsRemoveParams;\n+use codex_app_server_protocol::FsRemoveResponse;\n+use codex_app_server_protocol::FsWriteFileParams;\n+use codex_app_server_protocol::FsWr...", - "path": "codex-rs/exec-server/src/server/filesystem.rs", - "status": "added" - }, - { - "additions": 480, - "deletions": 3, - "patch_excerpt": "@@ -1,25 +1,112 @@\n+use std::collections::HashMap;\n+use std::collections::VecDeque;\n+use std::sync::Arc;\n use std::sync::atomic::AtomicBool;\n use std::sync::atomic::Ordering;\n+use std::time::Duration;\n \n+use codex_app_server_protocol::FsCopyParams;\n+use codex_app_server_protocol::FsCopyResponse;\n+use codex_app_server_protocol::FsCreateDirectoryParams;\n+use codex_app_server_protocol::FsCreateDirectoryResponse;\n+use codex_app_server_protocol::FsGetMetadataParams;\n+use codex_app_server_protocol::FsGetMetadataResponse;\n+use codex_app_server_protocol::FsReadDirectoryParams;\n+use codex_app_server_protocol::FsReadDirectoryResponse;\n+use codex_app_server_protocol::FsReadFileParams;\n+use codex_app_server_protocol::FsReadFileResponse;\n+use codex_app_server_protocol::FsRemoveParams;\n+use codex_app_server_protocol::FsRemoveResponse;\n+use codex_app_server_protocol::FsWriteFileParams;\n+use codex_app_s...", - "path": "codex-rs/exec-server/src/server/handler.rs", - "status": "modified" - }, - { - "additions": 102, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,102 @@\n+use std::collections::HashMap;\n+use std::sync::Arc;\n+use std::time::Duration;\n+\n+use pretty_assertions::assert_eq;\n+use tokio::sync::mpsc;\n+\n+use super::ExecServerHandler;\n+use crate::protocol::ExecParams;\n+use crate::protocol::InitializeResponse;\n+use crate::protocol::TerminateParams;\n+use crate::protocol::TerminateResponse;\n+use crate::rpc::RpcNotificationSender;\n+\n+fn exec_params(process_id: &str) -> ExecParams {\n+ let mut env = HashMap::new();\n+ if let Some(path) = std::env::var_os(\"PATH\") {\n+ env.insert(\"PATH\".to_string(), path.to_string_lossy().into_owned());\n+ }\n+ ExecParams {\n+ process_id: process_id.to_string(),\n+ argv: vec![\n+ \"bash\".to_string(),\n+ \"-lc\".to_string(),\n+ \"sleep 0.1\".to_string(),\n+ ],\n+ cwd: std::env::current_dir().expect(\"cwd\"),\n+ env,\n+ tty: false,\n+ ...", - "path": "codex-rs/exec-server/src/server/handler/tests.rs", - "status": "added" - }, - { - "additions": 92, - "deletions": 90, - "patch_excerpt": "@@ -1,53 +1,109 @@\n-use codex_app_server_protocol::JSONRPCMessage;\n-use codex_app_server_protocol::JSONRPCNotification;\n-use codex_app_server_protocol::JSONRPCRequest;\n+use std::sync::Arc;\n+\n+use tokio::sync::mpsc;\n use tracing::debug;\n+use tracing::warn;\n \n+use crate::connection::CHANNEL_CAPACITY;\n use crate::connection::JsonRpcConnection;\n use crate::connection::JsonRpcConnectionEvent;\n-use crate::protocol::INITIALIZE_METHOD;\n-use crate::protocol::INITIALIZED_METHOD;\n-use crate::protocol::InitializeParams;\n+use crate::rpc::RpcNotificationSender;\n+use crate::rpc::RpcServerOutboundMessage;\n+use crate::rpc::encode_server_message;\n+use crate::rpc::invalid_request;\n+use crate::rpc::method_not_found;\n use crate::server::ExecServerHandler;\n-use crate::server::jsonrpc::invalid_params;\n-use crate::server::jsonrpc::invalid_request_message;\n-use crate::server::jsonrpc::method_not_found;\n-use crat...", - "path": "codex-rs/exec-server/src/server/processor.rs", - "status": "modified" - }, - { - "additions": 110, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,110 @@\n+use std::sync::Arc;\n+\n+use crate::protocol::EXEC_METHOD;\n+use crate::protocol::EXEC_READ_METHOD;\n+use crate::protocol::EXEC_TERMINATE_METHOD;\n+use crate::protocol::EXEC_WRITE_METHOD;\n+use crate::protocol::ExecParams;\n+use crate::protocol::FS_COPY_METHOD;\n+use crate::protocol::FS_CREATE_DIRECTORY_METHOD;\n+use crate::protocol::FS_GET_METADATA_METHOD;\n+use crate::protocol::FS_READ_DIRECTORY_METHOD;\n+use crate::protocol::FS_READ_FILE_METHOD;\n+use crate::protocol::FS_REMOVE_METHOD;\n+use crate::protocol::FS_WRITE_FILE_METHOD;\n+use crate::protocol::INITIALIZE_METHOD;\n+use crate::protocol::INITIALIZED_METHOD;\n+use crate::protocol::InitializeParams;\n+use crate::protocol::ReadParams;\n+use crate::protocol::TerminateParams;\n+use crate::protocol::WriteParams;\n+use crate::rpc::RpcRouter;\n+use crate::server::ExecServerHandler;\n+use codex_app_server_protocol::FsCopyParams;\n+use codex_...", - "path": "codex-rs/exec-server/src/server/registry.rs", - "status": "added" - }, - { - "additions": 14, - "deletions": 8, - "patch_excerpt": "@@ -2,15 +2,15 @@\n \n mod common;\n \n-use codex_app_server_protocol::JSONRPCError;\n use codex_app_server_protocol::JSONRPCMessage;\n use codex_app_server_protocol::JSONRPCResponse;\n+use codex_exec_server::ExecResponse;\n use codex_exec_server::InitializeParams;\n use common::exec_server::exec_server;\n use pretty_assertions::assert_eq;\n \n #[tokio::test(flavor = \"multi_thread\", worker_threads = 2)]\n-async fn exec_server_stubs_process_start_over_websocket() -> anyhow::Result<()> {\n+async fn exec_server_starts_process_over_websocket() -> anyhow::Result<()> {\n let mut server = exec_server().await?;\n let initialize_id = server\n .send_request(\n@@ -29,6 +29,10 @@ async fn exec_server_stubs_process_start_over_websocket() -> anyhow::Result<()>\n })\n .await?;\n \n+ server\n+ .send_notification(\"initialized\", serde_json::json!({}))\n+ .await?;\n+\n let proces...", - "path": "codex-rs/exec-server/tests/process.rs", - "status": "modified" - } - ], - "linked_issues": [ - "#15091", - "#15090" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "Rebased onto current `main` after #15091 landed separately.\n\nThis PR now carries the surviving exec-server process RPC and filesystem RPC implementation on top of the current exec-server stub / websocket-only / environment-abstraction shape on `main`.\n\nIt also includes the `rust-ci` sccache guard for `macos-15-xlarge` + `x86_64-apple-darwin` so this branch does not reproduce the same mixed-architecture `ring` failure that hit the earlier stack.", - "labels": [], - "merged_at": "2026-03-19T19:00:37Z", - "number": 15090, - "state": "merged", - "title": "Add exec-server process and filesystem RPCs", - "url": "https://github.com/openai/codex/pull/15090" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15092.json b/artifacts/github/bundles/openai-codex-pr-15092.json deleted file mode 100644 index 17b3e18..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15092.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "bolinfest", - "committed_at": "2026-03-18T20:47:26Z", - "message": "fix: try to fix \"Stage npm package\" step in ci.yml", - "sha": "ee651d1af43aa2962bd144b4f3b4a37952e95cac", - "url": "https://github.com/openai/codex/commit/ee651d1af43aa2962bd144b4f3b4a37952e95cac" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "L33", - "CODEX_VERSION=0.74.0", - "CODEX_VERSION=0.115.0", - "OUTPUT_DIR=\"${RUNNER_TEMP}\"", - "--release-version", - "CODEX_VERSION" - ], - "files": [ - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -37,7 +37,7 @@ jobs:\n run: |\n set -euo pipefail\n # Use a rust-release version that includes all native binaries.\n- CODEX_VERSION=0.74.0\n+ CODEX_VERSION=0.115.0\n OUTPUT_DIR=\"${RUNNER_TEMP}\"\n python3 ./scripts/stage_npm_packages.py \\\n --release-version \"$CODEX_VERSION\" \\", - "path": ".github/workflows/ci.yml", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "Fix the CI job by updating it to use artifacts from a more recent release (`0.115.0`) instead of the existing one (`0.74.0`).\r\n\r\nThis step in our CI job on PRs started failing today:\r\n\r\nhttps://github.com/openai/codex/blob/334164a6f714c171bb9f6440c7d3cd04ec04d295/.github/workflows/ci.yml#L33-L47\r\n\r\nI believe it's because this test verifies that the \"package npm\" script works, but we want it to be fast and not wait for binaries to be built, so it uses a GitHub workflow that's already done. Because it was using a GitHub workflow associated with `0.74.0`, it seems likely that workflow's history has been reaped, so we need to use a newer one.", - "labels": [], - "merged_at": "2026-03-18T20:52:33Z", - "number": 15092, - "state": "merged", - "title": "fix: try to fix \"Stage npm package\" step in ci.yml", - "url": "https://github.com/openai/codex/pull/15092" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15100.json b/artifacts/github/bundles/openai-codex-pr-15100.json deleted file mode 100644 index ba7c557..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15100.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "pakrym-oai", - "committed_at": "2026-03-18T21:51:32Z", - "message": "Implement apply_patch code mode", - "sha": "dd3790452f7adf7a23f0cf35112fa77c7f992084", - "url": "https://github.com/openai/codex/commit/dd3790452f7adf7a23f0cf35112fa77c7f992084" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-18T21:55:28Z", - "message": "test(core): assert apply_patch code mode result in integration", - "sha": "fa4e5951a55070d545d5bfcde0a398fed9298f74", - "url": "https://github.com/openai/codex/commit/fa4e5951a55070d545d5bfcde0a398fed9298f74" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [], - "files": [ - { - "additions": 35, - "deletions": 0, - "patch_excerpt": "@@ -199,6 +199,41 @@ impl ToolOutput for FunctionToolOutput {\n }\n }\n \n+pub struct ApplyPatchToolOutput {\n+ pub text: String,\n+}\n+\n+impl ApplyPatchToolOutput {\n+ pub fn from_text(text: String) -> Self {\n+ Self { text }\n+ }\n+}\n+\n+impl ToolOutput for ApplyPatchToolOutput {\n+ fn log_preview(&self) -> String {\n+ telemetry_preview(&self.text)\n+ }\n+\n+ fn success_for_logging(&self) -> bool {\n+ true\n+ }\n+\n+ fn to_response_item(&self, call_id: &str, payload: &ToolPayload) -> ResponseInputItem {\n+ function_tool_response(\n+ call_id,\n+ payload,\n+ vec![FunctionCallOutputContentItem::InputText {\n+ text: self.text.clone(),\n+ }],\n+ Some(true),\n+ )\n+ }\n+\n+ fn code_mode_result(&self, _payload: &ToolPayload) -> JsonValue {\n+ JsonValue::Object(serde_json::Map::new(...", - "path": "codex-rs/core/src/tools/context.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 3, - "patch_excerpt": "@@ -13,6 +13,7 @@ use crate::codex::TurnContext;\n use crate::function_tool::FunctionCallError;\n use crate::sandboxing::effective_file_system_sandbox_policy;\n use crate::sandboxing::merge_permission_profiles;\n+use crate::tools::context::ApplyPatchToolOutput;\n use crate::tools::context::FunctionToolOutput;\n use crate::tools::context::SharedTurnDiffTracker;\n use crate::tools::context::ToolInvocation;\n@@ -125,7 +126,7 @@ async fn effective_patch_permissions(\n \n #[async_trait]\n impl ToolHandler for ApplyPatchHandler {\n- type Output = FunctionToolOutput;\n+ type Output = ApplyPatchToolOutput;\n \n fn kind(&self) -> ToolKind {\n ToolKind::Function\n@@ -179,7 +180,7 @@ impl ToolHandler for ApplyPatchHandler {\n {\n InternalApplyPatchInvocation::Output(item) => {\n let content = item?;\n- Ok(FunctionToolOu...", - "path": "codex-rs/core/src/tools/handlers/apply_patch.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -1753,6 +1753,7 @@ async fn code_mode_can_apply_patch_via_nested_tool() -> Result<()> {\n ),\n text_item(&items, 0),\n );\n+ assert_eq!(text_item(&items, 1), \"{}\");\n \n let file_path = test.cwd_path().join(file_name);\n assert_eq!(fs::read_to_string(&file_path)?, \"hello from code_mode\\n\");", - "path": "codex-rs/core/tests/suite/code_mode.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "It's empty !", - "labels": [], - "merged_at": "2026-03-18T23:11:11Z", - "number": 15100, - "state": "merged", - "title": "Add apply_patch code mode result", - "url": "https://github.com/openai/codex/pull/15100" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15102.json b/artifacts/github/bundles/openai-codex-pr-15102.json deleted file mode 100644 index 51f9496..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15102.json +++ /dev/null @@ -1,346 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "xl-openai", - "committed_at": "2026-03-18T21:54:10Z", - "message": "Revert \"fix: harden plugin feature gating (#15020)\"", - "sha": "c5281d1f5a729fa3648c09813441bd0a704f9416", - "url": "https://github.com/openai/codex/commit/c5281d1f5a729fa3648c09813441bd0a704f9416" - } - ], - "default_branch": "main", - "docs_refs": [ - "codex-rs/app-server/README.md" - ], - "examples_refs": [], - "extracted_flags": [ - "VSC", - "API", - "INTERACTIVE_SESSION_SOURCES", - "CHANNEL_CAPACITY", - "DEFAULT_LISTEN_URL", - "SOURCE", - "--session-source", - "TODO", - "DEFAULT_CLIENT_NAME", - "MCP", - "CODEX_HOME", - "RUST_LOG", - "CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR", - "CHATGPT", - "ATLAS", - "DEFAULT_TIMEOUT", - "JSONRPCR", - "SKILL", - "AVAILABLE", - "ON_INSTALL", - "CODEX", - "--remote", - "CURATED_REPO_SYNC_STARTED", - "OPENAI_CURATED_MARKETPLACE_NAME", - "TEST_CURATED_PLUGIN_SHA", - "CONFIG_TOML_FILE" - ], - "files": [ - { - "additions": 0, - "deletions": 4, - "patch_excerpt": "@@ -1033,10 +1033,6 @@ mod tests {\n for (session_source, expected_source) in [\n (SessionSource::Exec, ApiSessionSource::Exec),\n (SessionSource::Cli, ApiSessionSource::Cli),\n- (\n- SessionSource::Custom(\"atlas\".to_string()),\n- ApiSessionSource::Custom(\"atlas\".to_string()),\n- ),\n ] {\n let client = start_test_client(session_source).await;\n let parsed: ThreadStartResponse = client", - "path": "codex-rs/app-server-client/src/lib.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 1, - "patch_excerpt": "@@ -2894,7 +2894,6 @@\n \"vscode\",\n \"exec\",\n \"appServer\",\n- \"custom\",\n \"subAgent\",\n \"subAgentReview\",\n \"subAgentCompact\",", - "path": "codex-rs/app-server-protocol/schema/json/ClientRequest.json", - "status": "modified" - }, - { - "additions": 0, - "deletions": 13, - "patch_excerpt": "@@ -1880,19 +1880,6 @@\n ],\n \"type\": \"string\"\n },\n- {\n- \"additionalProperties\": false,\n- \"properties\": {\n- \"custom\": {\n- \"type\": \"string\"\n- }\n- },\n- \"required\": [\n- \"custom\"\n- ],\n- \"title\": \"CustomSessionSource\",\n- \"type\": \"object\"\n- },\n {\n \"additionalProperties\": false,\n \"properties\": {", - "path": "codex-rs/app-server-protocol/schema/json/ServerNotification.json", - "status": "modified" - }, - { - "additions": 0, - "deletions": 14, - "patch_excerpt": "@@ -10984,19 +10984,6 @@\n ],\n \"type\": \"string\"\n },\n- {\n- \"additionalProperties\": false,\n- \"properties\": {\n- \"custom\": {\n- \"type\": \"string\"\n- }\n- },\n- \"required\": [\n- \"custom\"\n- ],\n- \"title\": \"CustomSessionSource\",\n- \"type\": \"object\"\n- },\n {\n \"additionalProperties\": false,\n \"properties\": {\n@@ -13110,7 +13097,6 @@\n \"vscode\",\n \"exec\",\n \"appServer\",\n- \"custom\",\n \"subAgent\",\n \"subAgentReview\",\n \"subAgentCompact\",", - "path": "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json", - "status": "modified" - }, - { - "additions": 0, - "deletions": 14, - "patch_excerpt": "@@ -8744,19 +8744,6 @@\n ],\n \"type\": \"string\"\n },\n- {\n- \"additionalProperties\": false,\n- \"properties\": {\n- \"custom\": {\n- \"type\": \"string\"\n- }\n- },\n- \"required\": [\n- \"custom\"\n- ],\n- \"title\": \"CustomSessionSource\",\n- \"type\": \"object\"\n- },\n {\n \"additionalProperties\": false,\n \"properties\": {\n@@ -10870,7 +10857,6 @@\n \"vscode\",\n \"exec\",\n \"appServer\",\n- \"custom\",\n \"subAgent\",\n \"subAgentReview\",\n \"subAgentCompact\",", - "path": "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json", - "status": "modified" - }, - { - "additions": 0, - "deletions": 13, - "patch_excerpt": "@@ -826,19 +826,6 @@\n ],\n \"type\": \"string\"\n },\n- {\n- \"additionalProperties\": false,\n- \"properties\": {\n- \"custom\": {\n- \"type\": \"string\"\n- }\n- },\n- \"required\": [\n- \"custom\"\n- ],\n- \"title\": \"CustomSessionSource\",\n- \"type\": \"object\"\n- },\n {\n \"additionalProperties\": false,\n \"properties\": {", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json", - "status": "modified" - }, - { - "additions": 0, - "deletions": 1, - "patch_excerpt": "@@ -14,7 +14,6 @@\n \"vscode\",\n \"exec\",\n \"appServer\",\n- \"custom\",\n \"subAgent\",\n \"subAgentReview\",\n \"subAgentCompact\",", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadListParams.json", - "status": "modified" - }, - { - "additions": 0, - "deletions": 13, - "patch_excerpt": "@@ -584,19 +584,6 @@\n ],\n \"type\": \"string\"\n },\n- {\n- \"additionalProperties\": false,\n- \"properties\": {\n- \"custom\": {\n- \"type\": \"string\"\n- }\n- },\n- \"required\": [\n- \"custom\"\n- ],\n- \"title\": \"CustomSessionSource\",\n- \"type\": \"object\"\n- },\n {\n \"additionalProperties\": false,\n \"properties\": {", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json", - "status": "modified" - }, - { - "additions": 0, - "deletions": 13, - "patch_excerpt": "@@ -584,19 +584,6 @@\n ],\n \"type\": \"string\"\n },\n- {\n- \"additionalProperties\": false,\n- \"properties\": {\n- \"custom\": {\n- \"type\": \"string\"\n- }\n- },\n- \"required\": [\n- \"custom\"\n- ],\n- \"title\": \"CustomSessionSource\",\n- \"type\": \"object\"\n- },\n {\n \"additionalProperties\": false,\n \"properties\": {", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json", - "status": "modified" - }, - { - "additions": 0, - "deletions": 13, - "patch_excerpt": "@@ -584,19 +584,6 @@\n ],\n \"type\": \"string\"\n },\n- {\n- \"additionalProperties\": false,\n- \"properties\": {\n- \"custom\": {\n- \"type\": \"string\"\n- }\n- },\n- \"required\": [\n- \"custom\"\n- ],\n- \"title\": \"CustomSessionSource\",\n- \"type\": \"object\"\n- },\n {\n \"additionalProperties\": false,\n \"properties\": {", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json", - "status": "modified" - }, - { - "additions": 0, - "deletions": 13, - "patch_excerpt": "@@ -826,19 +826,6 @@\n ],\n \"type\": \"string\"\n },\n- {\n- \"additionalProperties\": false,\n- \"properties\": {\n- \"custom\": {\n- \"type\": \"string\"\n- }\n- },\n- \"required\": [\n- \"custom\"\n- ],\n- \"title\": \"CustomSessionSource\",\n- \"type\": \"object\"\n- },\n {\n \"additionalProperties\": false,\n \"properties\": {", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json", - "status": "modified" - }, - { - "additions": 0, - "deletions": 13, - "patch_excerpt": "@@ -584,19 +584,6 @@\n ],\n \"type\": \"string\"\n },\n- {\n- \"additionalProperties\": false,\n- \"properties\": {\n- \"custom\": {\n- \"type\": \"string\"\n- }\n- },\n- \"required\": [\n- \"custom\"\n- ],\n- \"title\": \"CustomSessionSource\",\n- \"type\": \"object\"\n- },\n {\n \"additionalProperties\": false,\n \"properties\": {", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json", - "status": "modified" - }, - { - "additions": 0, - "deletions": 13, - "patch_excerpt": "@@ -826,19 +826,6 @@\n ],\n \"type\": \"string\"\n },\n- {\n- \"additionalProperties\": false,\n- \"properties\": {\n- \"custom\": {\n- \"type\": \"string\"\n- }\n- },\n- \"required\": [\n- \"custom\"\n- ],\n- \"title\": \"CustomSessionSource\",\n- \"type\": \"object\"\n- },\n {\n \"additionalProperties\": false,\n \"properties\": {", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json", - "status": "modified" - }, - { - "additions": 0, - "deletions": 13, - "patch_excerpt": "@@ -584,19 +584,6 @@\n ],\n \"type\": \"string\"\n },\n- {\n- \"additionalProperties\": false,\n- \"properties\": {\n- \"custom\": {\n- \"type\": \"string\"\n- }\n- },\n- \"required\": [\n- \"custom\"\n- ],\n- \"title\": \"CustomSessionSource\",\n- \"type\": \"object\"\n- },\n {\n \"additionalProperties\": false,\n \"properties\": {", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json", - "status": "modified" - }, - { - "additions": 0, - "deletions": 13, - "patch_excerpt": "@@ -584,19 +584,6 @@\n ],\n \"type\": \"string\"\n },\n- {\n- \"additionalProperties\": false,\n- \"properties\": {\n- \"custom\": {\n- \"type\": \"string\"\n- }\n- },\n- \"required\": [\n- \"custom\"\n- ],\n- \"title\": \"CustomSessionSource\",\n- \"type\": \"object\"\n- },\n {\n \"additionalProperties\": false,\n \"properties\": {", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -3,4 +3,4 @@\n // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n import type { SubAgentSource } from \"./SubAgentSource\";\n \n-export type SessionSource = \"cli\" | \"vscode\" | \"exec\" | \"mcp\" | { \"custom\": string } | { \"subagent\": SubAgentSource } | \"unknown\";\n+export type SessionSource = \"cli\" | \"vscode\" | \"exec\" | \"mcp\" | { \"subagent\": SubAgentSource } | \"unknown\";", - "path": "codex-rs/app-server-protocol/schema/typescript/SessionSource.ts", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -3,4 +3,4 @@\n // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n import type { SubAgentSource } from \"../SubAgentSource\";\n \n-export type SessionSource = \"cli\" | \"vscode\" | \"exec\" | \"appServer\" | { \"custom\": string } | { \"subAgent\": SubAgentSource } | \"unknown\";\n+export type SessionSource = \"cli\" | \"vscode\" | \"exec\" | \"appServer\" | { \"subAgent\": SubAgentSource } | \"unknown\";", - "path": "codex-rs/app-server-protocol/schema/typescript/v2/SessionSource.ts", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -2,4 +2,4 @@\n \n // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n \n-export type ThreadSourceKind = \"cli\" | \"vscode\" | \"exec\" | \"appServer\" | \"custom\" | \"subAgent\" | \"subAgentReview\" | \"subAgentCompact\" | \"subAgentThreadSpawn\" | \"subAgentOther\" | \"unknown\";\n+export type ThreadSourceKind = \"cli\" | \"vscode\" | \"exec\" | \"appServer\" | \"subAgent\" | \"subAgentReview\" | \"subAgentCompact\" | \"subAgentThreadSpawn\" | \"subAgentOther\" | \"unknown\";", - "path": "codex-rs/app-server-protocol/schema/typescript/v2/ThreadSourceKind.ts", - "status": "modified" - }, - { - "additions": 0, - "deletions": 4, - "patch_excerpt": "@@ -1467,7 +1467,6 @@ pub enum SessionSource {\n VsCode,\n Exec,\n AppServer,\n- Custom(String),\n SubAgent(CoreSubAgentSource),\n #[serde(other)]\n Unknown,\n@@ -1480,7 +1479,6 @@ impl From for SessionSource {\n CoreSessionSource::VSCode => SessionSource::VsCode,\n CoreSessionSource::Exec => SessionSource::Exec,\n CoreSessionSource::Mcp => SessionSource::AppServer,\n- CoreSessionSource::Custom(source) => SessionSource::Custom(source),\n CoreSessionSource::SubAgent(sub) => SessionSource::SubAgent(sub),\n CoreSessionSource::Unknown => SessionSource::Unknown,\n }\n@@ -1494,7 +1492,6 @@ impl From for CoreSessionSource {\n SessionSource::VsCode => CoreSessionSource::VSCode,\n SessionSource::Exec => CoreSessionSource::Exec,\n SessionSourc...", - "path": "codex-rs/app-server-protocol/src/protocol/v2.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -256,7 +256,7 @@ Experimental API: `thread/start`, `thread/resume`, and `thread/fork` accept `per\n - `limit` \u2014 server defaults to a reasonable page size if unset.\n - `sortKey` \u2014 `created_at` (default) or `updated_at`.\n - `modelProviders` \u2014 restrict results to specific providers; unset, null, or an empty array will include all providers.\n-- `sourceKinds` \u2014 restrict results to specific sources; omit or pass `[]` for interactive sessions only (`cli`, `vscode`, and custom product sources).\n+- `sourceKinds` \u2014 restrict results to specific sources; omit or pass `[]` for interactive sessions only (`cli`, `vscode`).\n - `archived` \u2014 when `true`, list archived threads only. When `false` or `null`, list non-archived threads (default).\n - `cwd` \u2014 restrict results to threads whose session cwd exactly matches this path.\n - `searchTerm` \u2014 restrict results to threads whose extracted title contains thi...", - "path": "codex-rs/app-server/README.md", - "status": "modified" - }, - { - "additions": 13, - "deletions": 35, - "patch_excerpt": "@@ -423,10 +423,7 @@ impl CodexMessageProcessor {\n Ok(config) => self\n .thread_manager\n .plugins_manager()\n- .maybe_start_curated_repo_sync_for_config(\n- &config,\n- &self.thread_manager.session_source(),\n- ),\n+ .maybe_start_curated_repo_sync_for_config(&config),\n Err(err) => warn!(\"failed to load latest config for curated plugin sync: {err:?}\"),\n }\n }\n@@ -5305,7 +5302,6 @@ impl CodexMessageProcessor {\n force_reload,\n per_cwd_extra_user_roots,\n } = params;\n- let session_source = self.thread_manager.session_source();\n let cwds = if cwds.is_empty() {\n vec![self.config.cwd.clone()]\n } else {\n@@ -5350,12 +5346,9 @@ impl CodexMessageProcessor {\n let extra_roots = extr...", - "path": "codex-rs/app-server/src/codex_message_processor.rs", - "status": "modified" - }, - { - "additions": 7, - "deletions": 34, - "patch_excerpt": "@@ -1,32 +1,24 @@\n use codex_app_server_protocol::ThreadSourceKind;\n+use codex_core::INTERACTIVE_SESSION_SOURCES;\n use codex_protocol::protocol::SessionSource as CoreSessionSource;\n use codex_protocol::protocol::SubAgentSource as CoreSubAgentSource;\n \n-fn interactive_source_kinds() -> Vec {\n- vec![\n- ThreadSourceKind::Cli,\n- ThreadSourceKind::VsCode,\n- ThreadSourceKind::Custom,\n- ]\n-}\n-\n pub(crate) fn compute_source_filters(\n source_kinds: Option>,\n ) -> (Vec, Option>) {\n let Some(source_kinds) = source_kinds else {\n- return (Vec::new(), Some(interactive_source_kinds()));\n+ return (INTERACTIVE_SESSION_SOURCES.to_vec(), None);\n };\n \n if source_kinds.is_empty() {\n- return (Vec::new(), Some(interactive_source_kinds()));\n+ return (INTERACTIVE...", - "path": "codex-rs/app-server/src/filters.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 4, - "patch_excerpt": "@@ -808,10 +808,6 @@ mod tests {\n for (requested_source, expected_source) in [\n (SessionSource::Cli, ApiSessionSource::Cli),\n (SessionSource::Exec, ApiSessionSource::Exec),\n- (\n- SessionSource::Custom(\"atlas\".to_string()),\n- ApiSessionSource::Custom(\"atlas\".to_string()),\n- ),\n ] {\n let client = start_test_client(requested_source).await;\n let response = client", - "path": "codex-rs/app-server/src/in_process.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 3, - "patch_excerpt": "@@ -336,7 +336,6 @@ pub async fn run_main(\n loader_overrides,\n default_analytics_enabled,\n AppServerTransport::Stdio,\n- SessionSource::VSCode,\n )\n .await\n }\n@@ -347,7 +346,6 @@ pub async fn run_main_with_transport(\n loader_overrides: LoaderOverrides,\n default_analytics_enabled: bool,\n transport: AppServerTransport,\n- session_source: SessionSource,\n ) -> IoResult<()> {\n let (transport_event_tx, mut transport_event_rx) =\n mpsc::channel::(CHANNEL_CAPACITY);\n@@ -623,7 +621,7 @@ pub async fn run_main_with_transport(\n feedback: feedback.clone(),\n log_db,\n config_warnings,\n- session_source,\n+ session_source: SessionSource::VSCode,\n enable_codex_api_key_env: false,\n });\n let mut thread_created_rx = processor.thread_created_receive...", - "path": "codex-rs/app-server/src/lib.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 15, - "patch_excerpt": "@@ -4,7 +4,6 @@ use codex_app_server::run_main_with_transport;\n use codex_arg0::Arg0DispatchPaths;\n use codex_arg0::arg0_dispatch_or_else;\n use codex_core::config_loader::LoaderOverrides;\n-use codex_protocol::protocol::SessionSource;\n use codex_utils_cli::CliConfigOverrides;\n use std::path::PathBuf;\n \n@@ -22,17 +21,6 @@ struct AppServerArgs {\n default_value = AppServerTransport::DEFAULT_LISTEN_URL\n )]\n listen: AppServerTransport,\n-\n- /// Session source stamped into new threads started by this app-server.\n- ///\n- /// Known values such as `vscode`, `cli`, `exec`, and `mcp` map to built-in\n- /// sources. Any other non-empty value is recorded as a custom source.\n- #[arg(\n- long = \"session-source\",\n- value_name = \"SOURCE\",\n- default_value = \"vscode\"\n- )]\n- session_source: String,\n }\n \n fn main() -> anyhow::Result<()> {\n@@ -44,16 +32,13...", - "path": "codex-rs/app-server/src/main.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -231,7 +231,7 @@ impl MessageProcessor {\n // TODO(xl): Move into PluginManager once this no longer depends on config feature gating.\n thread_manager\n .plugins_manager()\n- .maybe_start_curated_repo_sync_for_config(&config, &thread_manager.session_source());\n+ .maybe_start_curated_repo_sync_for_config(&config);\n let cloud_requirements = Arc::new(RwLock::new(cloud_requirements));\n let codex_message_processor = CodexMessageProcessor::new(CodexMessageProcessorArgs {\n auth_manager: auth_manager.clone(),", - "path": "codex-rs/app-server/src/message_processor.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 14, - "patch_excerpt": "@@ -95,11 +95,7 @@ pub const DEFAULT_CLIENT_NAME: &str = \"codex-app-server-tests\";\n \n impl McpProcess {\n pub async fn new(codex_home: &Path) -> anyhow::Result {\n- Self::new_with_env_and_args(codex_home, &[], &[]).await\n- }\n-\n- pub async fn new_with_args(codex_home: &Path, args: &[&str]) -> anyhow::Result {\n- Self::new_with_env_and_args(codex_home, &[], args).await\n+ Self::new_with_env(codex_home, &[]).await\n }\n \n /// Creates a new MCP process, allowing tests to override or remove\n@@ -110,14 +106,6 @@ impl McpProcess {\n pub async fn new_with_env(\n codex_home: &Path,\n env_overrides: &[(&str, Option<&str>)],\n- ) -> anyhow::Result {\n- Self::new_with_env_and_args(codex_home, env_overrides, &[]).await\n- }\n-\n- pub async fn new_with_env_and_args(\n- codex_home: &Path,\n- env_overrides: &[(&st...", - "path": "codex-rs/app-server/tests/common/mcp_process.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 127, - "patch_excerpt": "@@ -25,8 +25,6 @@ use codex_app_server_protocol::PluginAuthPolicy;\n use codex_app_server_protocol::PluginInstallParams;\n use codex_app_server_protocol::PluginInstallResponse;\n use codex_app_server_protocol::RequestId;\n-use codex_app_server_protocol::SkillsListParams;\n-use codex_app_server_protocol::SkillsListResponse;\n use codex_core::auth::AuthCredentialsStoreMode;\n use codex_utils_absolute_path::AbsolutePathBuf;\n use pretty_assertions::assert_eq;\n@@ -478,92 +476,6 @@ async fn plugin_install_filters_disallowed_apps_needing_auth() -> Result<()> {\n Ok(())\n }\n \n-#[tokio::test]\n-async fn plugin_install_filters_product_restricted_plugin_skills() -> Result<()> {\n- let codex_home = TempDir::new()?;\n- let repo_root = TempDir::new()?;\n- write_plugins_enabled_config(codex_home.path())?;\n- write_plugin_marketplace(\n- repo_root.path(),\n- \"debug\",\n- \"sample-plugi...", - "path": "codex-rs/app-server/tests/suite/v2/plugin_install.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 173, - "patch_excerpt": "@@ -377,179 +377,6 @@ enabled = false\n Ok(())\n }\n \n-#[tokio::test]\n-async fn plugin_list_filters_plugins_for_custom_session_source_products() -> Result<()> {\n- let codex_home = TempDir::new()?;\n- let repo_root = TempDir::new()?;\n- std::fs::create_dir_all(repo_root.path().join(\".git\"))?;\n- std::fs::create_dir_all(repo_root.path().join(\".agents/plugins\"))?;\n- std::fs::write(\n- repo_root.path().join(\".agents/plugins/marketplace.json\"),\n- r#\"{\n- \"name\": \"codex-curated\",\n- \"plugins\": [\n- {\n- \"name\": \"all-products\",\n- \"source\": {\n- \"source\": \"local\",\n- \"path\": \"./all-products\"\n- }\n- },\n- {\n- \"name\": \"chatgpt-only\",\n- \"source\": {\n- \"source\": \"local\",\n- \"path\": \"./chatgpt-only\"\n- },\n- \"policy\": {\n- \"installation\": \"AVAILABLE\",\n- \"authentication\": \"ON_INSTALL\",\n- \"produc...", - "path": "codex-rs/app-server/tests/suite/v2/plugin_list.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 24, - "patch_excerpt": "@@ -331,17 +331,6 @@ struct AppServerCommand {\n )]\n listen: codex_app_server::AppServerTransport,\n \n- /// Session source stamped into new threads started by this app-server.\n- ///\n- /// Known values such as `vscode`, `cli`, `exec`, and `mcp` map to built-in\n- /// sources. Any other non-empty value is recorded as a custom source.\n- #[arg(\n- long = \"session-source\",\n- value_name = \"SOURCE\",\n- default_value = \"vscode\"\n- )]\n- session_source: String,\n-\n /// Controls whether analytics are enabled by default.\n ///\n /// Analytics are disabled by default for app-server. Users have to explicitly opt in\n@@ -654,17 +643,12 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {\n None => {\n reject_remote_mode_for_subcommand(root_remote.as_deref(), \"app-server\")?;\n let transpor...", - "path": "codex-rs/cli/src/main.rs", - "status": "modified" - }, - { - "additions": 6, - "deletions": 14, - "patch_excerpt": "@@ -2385,12 +2385,11 @@ impl Session {\n &per_turn_config,\n )\n .await;\n- let skills_outcome = Arc::new(crate::skills::filter_skill_load_outcome_for_session_source(\n+ let skills_outcome = Arc::new(\n self.services\n .skills_manager\n .skills_for_config(&per_turn_config),\n- &session_configuration.session_source,\n- ));\n+ );\n let mut turn_context: TurnContext = Self::make_turn_context(\n Some(Arc::clone(&self.services.auth_manager)),\n &self.services.session_telemetry,\n@@ -4774,24 +4773,17 @@ mod handlers {\n cwds: Vec,\n force_reload: bool,\n ) {\n- let (cwds, session_source) = if cwds.is_empty() {\n+ let cwds = if cwds.is_empty() {\n let state = sess.state.lock().await;\n- (\n- ...", - "path": "codex-rs/core/src/codex.rs", - "status": "modified" - }, - { - "additions": 5, - "deletions": 35, - "patch_excerpt": "@@ -44,7 +44,6 @@ use crate::skills::loader::SkillRoot;\n use crate::skills::loader::load_skills_from_roots;\n use codex_app_server_protocol::ConfigValueWriteParams;\n use codex_app_server_protocol::MergeStrategy;\n-use codex_protocol::protocol::SessionSource;\n use codex_protocol::protocol::SkillScope;\n use codex_utils_absolute_path::AbsolutePathBuf;\n use serde::Deserialize;\n@@ -939,11 +938,7 @@ impl PluginsManager {\n })\n }\n \n- pub fn maybe_start_curated_repo_sync_for_config(\n- self: &Arc,\n- config: &Config,\n- session_source: &SessionSource,\n- ) {\n+ pub fn maybe_start_curated_repo_sync_for_config(self: &Arc, config: &Config) {\n if plugins_feature_enabled_from_stack(&config.config_layer_stack) {\n let mut configured_curated_plugin_ids =\n configured_plugins_from_stack(&config.config_layer_stack)\n@@ -966,15...", - "path": "codex-rs/core/src/plugins/manager.rs", - "status": "modified" - }, - { - "additions": 6, - "deletions": 80, - "patch_excerpt": "@@ -9,12 +9,10 @@ use crate::config_loader::ConfigRequirements;\n use crate::config_loader::ConfigRequirementsToml;\n use crate::plugins::MarketplacePluginInstallPolicy;\n use crate::plugins::test_support::TEST_CURATED_PLUGIN_SHA;\n-use crate::plugins::test_support::write_curated_plugin;\n use crate::plugins::test_support::write_curated_plugin_sha_with as write_curated_plugin_sha;\n use crate::plugins::test_support::write_file;\n use crate::plugins::test_support::write_openai_curated_marketplace;\n use codex_app_server_protocol::ConfigLayerSource;\n-use codex_protocol::protocol::SessionSource;\n use pretty_assertions::assert_eq;\n use std::fs;\n use tempfile::TempDir;\n@@ -1034,12 +1032,6 @@ async fn list_marketplaces_includes_curated_repo_marketplace() {\n r#\"{\"name\":\"linear\"}\"#,\n )\n .unwrap();\n- write_file(\n- &tmp.path().join(CONFIG_TOML_FILE),\n- r#\"[features]\n-plugi...", - "path": "codex-rs/core/src/plugins/manager_tests.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -54,6 +54,8 @@ pub enum MarketplacePluginSource {\n pub struct MarketplacePluginPolicy {\n pub installation: MarketplacePluginInstallPolicy,\n pub authentication: MarketplacePluginAuthPolicy,\n+ // TODO: Surface or enforce product gating at the Codex/plugin consumer boundary instead of\n+ // only carrying it through core marketplace metadata.\n pub products: Vec,\n }", - "path": "codex-rs/core/src/plugins/marketplace.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 2, - "patch_excerpt": "@@ -20,6 +20,4 @@ pub use model::SkillError;\n pub use model::SkillLoadOutcome;\n pub use model::SkillMetadata;\n pub use model::SkillPolicy;\n-pub use model::filter_skill_load_outcome_for_session_source;\n-pub use model::filter_skills_for_session_source;\n pub use render::render_skills_section;", - "path": "codex-rs/core/src/skills/mod.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 44, - "patch_excerpt": "@@ -5,7 +5,6 @@ use std::sync::Arc;\n \n use codex_protocol::models::PermissionProfile;\n use codex_protocol::protocol::Product;\n-use codex_protocol::protocol::SessionSource;\n use codex_protocol::protocol::SkillScope;\n use serde::Deserialize;\n \n@@ -43,18 +42,13 @@ impl SkillMetadata {\n .and_then(|policy| policy.allow_implicit_invocation)\n .unwrap_or(true)\n }\n-\n- pub fn matches_product_restriction(&self, session_source: &SessionSource) -> bool {\n- match &self.policy {\n- Some(policy) => session_source.matches_product_restriction(&policy.products),\n- None => true,\n- }\n- }\n }\n \n #[derive(Debug, Clone, PartialEq, Eq, Default)]\n pub struct SkillPolicy {\n pub allow_implicit_invocation: Option,\n+ // TODO: Enforce product gating in Codex skill selection/injection instead of only parsing and\n+ // storing this metada...", - "path": "codex-rs/core/src/skills/model.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -276,7 +276,7 @@ impl SessionTelemetry {\n account_email,\n originator: sanitize_metric_tag_value(originator.as_str()),\n service_name: None,\n- session_source: sanitize_metric_tag_value(session_source.to_string().as_str()),\n+ session_source: session_source.to_string(),\n model: model.to_owned(),\n slug: slug.to_owned(),\n log_user_prompts,", - "path": "codex-rs/otel/src/events/session_telemetry.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 136, - "patch_excerpt": "@@ -2272,7 +2272,6 @@ pub enum SessionSource {\n VSCode,\n Exec,\n Mcp,\n- Custom(String),\n SubAgent(SubAgentSource),\n #[serde(other)]\n Unknown,\n@@ -2303,31 +2302,13 @@ impl fmt::Display for SessionSource {\n SessionSource::VSCode => f.write_str(\"vscode\"),\n SessionSource::Exec => f.write_str(\"exec\"),\n SessionSource::Mcp => f.write_str(\"mcp\"),\n- SessionSource::Custom(source) => f.write_str(source),\n SessionSource::SubAgent(sub_source) => write!(f, \"subagent_{sub_source}\"),\n SessionSource::Unknown => f.write_str(\"unknown\"),\n }\n }\n }\n \n impl SessionSource {\n- pub fn from_startup_arg(value: &str) -> Result {\n- let trimmed = value.trim();\n- if trimmed.is_empty() {\n- return Err(\"session source must not be empty\");\n- }\n-\n- let nor...", - "path": "codex-rs/protocol/src/protocol.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -2018,7 +2018,7 @@ impl App {\n // TODO(xl): Move into PluginManager once this no longer depends on config feature gating.\n thread_manager\n .plugins_manager()\n- .maybe_start_curated_repo_sync_for_config(&config, &SessionSource::Cli);\n+ .maybe_start_curated_repo_sync_for_config(&config);\n let mut model = thread_manager\n .get_models_manager()\n .get_default_model(&config.model, RefreshStrategy::Offline)", - "path": "codex-rs/tui/src/app.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 11, - "patch_excerpt": "@@ -3131,7 +3131,6 @@ class ThreadSourceKind(Enum):\n vscode = \"vscode\"\n exec = \"exec\"\n app_server = \"appServer\"\n- custom = \"custom\"\n sub_agent = \"subAgent\"\n sub_agent_review = \"subAgentReview\"\n sub_agent_compact = \"subAgentCompact\"\n@@ -5811,19 +5810,11 @@ class SubAgentSessionSource(BaseModel):\n sub_agent: Annotated[SubAgentSource, Field(alias=\"subAgent\")]\n \n \n-class CustomSessionSource(BaseModel):\n- model_config = ConfigDict(\n- extra=\"forbid\",\n- populate_by_name=True,\n- )\n- custom: str\n-\n-\n-class SessionSource(RootModel[SessionSourceValue | CustomSessionSource | SubAgentSessionSource]):\n+class SessionSource(RootModel[SessionSourceValue | SubAgentSessionSource]):\n model_config = ConfigDict(\n populate_by_name=True,\n )\n- root: SessionSourceValue | CustomSessionSource | SubAgentSessionSource\n+ root: SessionSourceVal...", - "path": "sdk/python/src/codex_app_server/generated/v2_all.py", - "status": "modified" - } - ], - "linked_issues": [ - "openai/codex#15020", - "#15020" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "Reverts openai/codex#15020\r\n\r\nI messed up the commit in my PR and accidentally merged changes that were still under review.", - "labels": [], - "merged_at": "2026-03-18T22:19:29Z", - "number": 15102, - "state": "merged", - "title": "Revert \"fix: harden plugin feature gating\"", - "url": "https://github.com/openai/codex/pull/15102" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15103.json b/artifacts/github/bundles/openai-codex-pr-15103.json deleted file mode 100644 index 8404f7a..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15103.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "pakrym-oai", - "committed_at": "2026-03-18T21:56:28Z", - "message": "Implement code-mode plan result", - "sha": "e1b3f11bf13af18c7bc06ead8902fb6436dfdbdd", - "url": "https://github.com/openai/codex/commit/e1b3f11bf13af18c7bc06ead8902fb6436dfdbdd" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "PLAN_UPDATED_MESSAGE", - "PLAN_TOOL", - "JSON" - ], - "files": [ - { - "additions": 35, - "deletions": 5, - "patch_excerpt": "@@ -3,21 +3,52 @@ use crate::client_common::tools::ToolSpec;\n use crate::codex::Session;\n use crate::codex::TurnContext;\n use crate::function_tool::FunctionCallError;\n-use crate::tools::context::FunctionToolOutput;\n use crate::tools::context::ToolInvocation;\n+use crate::tools::context::ToolOutput;\n use crate::tools::context::ToolPayload;\n use crate::tools::registry::ToolHandler;\n use crate::tools::registry::ToolKind;\n use crate::tools::spec::JsonSchema;\n use async_trait::async_trait;\n use codex_protocol::config_types::ModeKind;\n+use codex_protocol::models::FunctionCallOutputPayload;\n+use codex_protocol::models::ResponseInputItem;\n use codex_protocol::plan_tool::UpdatePlanArgs;\n use codex_protocol::protocol::EventMsg;\n+use serde_json::Value as JsonValue;\n use std::collections::BTreeMap;\n use std::sync::LazyLock;\n \n pub struct PlanHandler;\n \n+pub struct PlanToolOutput;\n+\n+const PLAN_UPDATE...", - "path": "codex-rs/core/src/tools/handlers/plan.rs", - "status": "modified" - }, - { - "additions": 32, - "deletions": 0, - "patch_excerpt": "@@ -361,6 +361,38 @@ text(output.output);\n Ok(())\n }\n \n+#[tokio::test(flavor = \"multi_thread\", worker_threads = 2)]\n+async fn code_mode_update_plan_nested_tool_result_is_empty_object() -> Result<()> {\n+ skip_if_no_network!(Ok(()));\n+\n+ let server = responses::start_mock_server().await;\n+ let (_test, second_mock) = run_code_mode_turn(\n+ &server,\n+ \"use exec to run update_plan\",\n+ r#\"\n+const result = await tools.update_plan({\n+ plan: [{ step: \"Run update_plan from code mode\", status: \"in_progress\" }],\n+});\n+text(JSON.stringify(result));\n+\"#,\n+ false,\n+ )\n+ .await?;\n+\n+ let req = second_mock.single_request();\n+ let (output, success) = custom_tool_output_body_and_success(&req, \"call-1\");\n+ assert_ne!(\n+ success,\n+ Some(false),\n+ \"exec update_plan call failed unexpectedly: {output}\"\n+ );\n+\n+ let parsed: Value ...", - "path": "codex-rs/core/tests/suite/code_mode.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "It's empty!", - "labels": [], - "merged_at": "2026-03-18T23:10:51Z", - "number": 15103, - "state": "merged", - "title": "Add update_plan code mode result", - "url": "https://github.com/openai/codex/pull/15103" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15104.json b/artifacts/github/bundles/openai-codex-pr-15104.json deleted file mode 100644 index 50a46bb..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15104.json +++ /dev/null @@ -1,161 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "xl-openai", - "committed_at": "2026-03-18T04:19:13Z", - "message": "fix: read plugin feature flag from requirement-resolved config.", - "sha": "62b5fbceec22f9530a389ba2eb2ede9902535ae7", - "url": "https://github.com/openai/codex/commit/62b5fbceec22f9530a389ba2eb2ede9902535ae7" - }, - { - "author": "xl-openai", - "committed_at": "2026-03-18T07:31:00Z", - "message": "guard plugin/*** with the feature flag.", - "sha": "fdd9ab96a5b82385042dc98a3419d3ab4a0331c8", - "url": "https://github.com/openai/codex/commit/fdd9ab96a5b82385042dc98a3419d3ab4a0331c8" - }, - { - "author": "xl-openai", - "committed_at": "2026-03-18T07:34:59Z", - "message": "Fail open to marketplace.json fail.", - "sha": "2f834baff6670f65bc8fd8d1f37d01f89478d698", - "url": "https://github.com/openai/codex/commit/2f834baff6670f65bc8fd8d1f37d01f89478d698" - }, - { - "author": "xl-openai", - "committed_at": "2026-03-18T08:36:24Z", - "message": "Cleanup configured_plugin_states", - "sha": "801f8345b86a0ddf10afb9b3b8c3c2dd3752a660", - "url": "https://github.com/openai/codex/commit/801f8345b86a0ddf10afb9b3b8c3c2dd3752a660" - }, - { - "author": "xl-openai", - "committed_at": "2026-03-18T22:13:04Z", - "message": "fix tests.", - "sha": "fdb355c92326f9954a4db16f2bdfc88b59f87414", - "url": "https://github.com/openai/codex/commit/fdb355c92326f9954a4db16f2bdfc88b59f87414" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "DEFAULT_TIMEOUT", - "TEST_CURATED_PLUGIN_SHA", - "JSONRPCR", - "CONFIG_TOML_FILE", - "SKILL", - "MARKETPLACE_RELATIVE_PATH", - "API" - ], - "files": [ - { - "additions": 9, - "deletions": 1, - "patch_excerpt": "@@ -4842,6 +4842,7 @@ impl CodexMessageProcessor {\n MarketplaceError::InvalidMarketplaceFile { .. }\n | MarketplaceError::PluginNotFound { .. }\n | MarketplaceError::PluginNotAvailable { .. }\n+ | MarketplaceError::PluginsDisabled\n | MarketplaceError::InvalidPlugin(_) => {\n self.send_invalid_request_error(request_id, err.to_string())\n .await;\n@@ -5363,14 +5364,21 @@ impl CodexMessageProcessor {\n .extend(valid_extra_roots);\n }\n \n+ let config = match self.load_latest_config(/*fallback_cwd*/ None).await {\n+ Ok(config) => config,\n+ Err(error) => {\n+ self.outgoing.send_error(request_id, error).await;\n+ return;\n+ }\n+ };\n let skills_manager = self.thread_manager.skills_manager();\n let mut ...", - "path": "codex-rs/app-server/src/codex_message_processor.rs", - "status": "modified" - }, - { - "additions": 27, - "deletions": 5, - "patch_excerpt": "@@ -28,12 +28,22 @@ use wiremock::matchers::path;\n const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);\n const TEST_CURATED_PLUGIN_SHA: &str = \"0123456789abcdef0123456789abcdef01234567\";\n \n+fn write_plugins_enabled_config(codex_home: &std::path::Path) -> std::io::Result<()> {\n+ std::fs::write(\n+ codex_home.join(\"config.toml\"),\n+ r#\"[features]\n+plugins = true\n+\"#,\n+ )\n+}\n+\n #[tokio::test]\n-async fn plugin_list_returns_invalid_request_for_invalid_marketplace_file() -> Result<()> {\n+async fn plugin_list_skips_invalid_marketplace_file() -> Result<()> {\n let codex_home = TempDir::new()?;\n let repo_root = TempDir::new()?;\n std::fs::create_dir_all(repo_root.path().join(\".git\"))?;\n std::fs::create_dir_all(repo_root.path().join(\".agents/plugins\"))?;\n+ write_plugins_enabled_config(codex_home.path())?;\n std::fs::write(\n repo_root.path().join(...", - "path": "codex-rs/app-server/tests/suite/v2/plugin_list.rs", - "status": "modified" - }, - { - "additions": 13, - "deletions": 0, - "patch_excerpt": "@@ -232,6 +232,7 @@ async fn plugin_read_accepts_legacy_string_default_prompt() -> Result<()> {\n }\n }\"##,\n )?;\n+ write_plugins_enabled_config(&codex_home)?;\n \n let mut mcp = McpProcess::new(codex_home.path()).await?;\n timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;\n@@ -285,6 +286,7 @@ async fn plugin_read_returns_invalid_request_when_plugin_is_missing() -> Result<\n ]\n }\"#,\n )?;\n+ write_plugins_enabled_config(&codex_home)?;\n \n let mut mcp = McpProcess::new(codex_home.path()).await?;\n timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;\n@@ -336,6 +338,7 @@ async fn plugin_read_returns_invalid_request_when_plugin_manifest_is_missing() -\n ]\n }\"#,\n )?;\n+ write_plugins_enabled_config(&codex_home)?;\n \n let mut mcp = McpProcess::new(codex_home.path()).await?;\n timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;\n@@ -382,3 +385,13 @@ fn write_i...", - "path": "codex-rs/app-server/tests/suite/v2/plugin_read.rs", - "status": "modified" - }, - { - "additions": 16, - "deletions": 5, - "patch_excerpt": "@@ -148,7 +148,9 @@ impl ConfigLayerStack {\n })\n }\n \n- /// Returns the user config layer, if any.\n+ /// Returns the raw user config layer, if any.\n+ ///\n+ /// This does not merge other config layers or apply any requirements.\n pub fn get_user_layer(&self) -> Option<&ConfigLayerEntry> {\n self.user_layer_index\n .and_then(|index| self.layers.get(index))\n@@ -209,6 +211,10 @@ impl ConfigLayerStack {\n }\n }\n \n+ /// Returns the merged config-layer view.\n+ ///\n+ /// This only merges ordinary config layers and does not apply requirements\n+ /// such as cloud requirements.\n pub fn effective_config(&self) -> TomlValue {\n let mut merged = TomlValue::Table(toml::map::Map::new());\n for layer in self.get_layers(\n@@ -220,6 +226,9 @@ impl ConfigLayerStack {\n merged\n }\n \n+ /// Returns field origins for...", - "path": "codex-rs/config/src/state.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 1, - "patch_excerpt": "@@ -4781,9 +4781,12 @@ mod handlers {\n };\n \n let skills_manager = &sess.services.skills_manager;\n+ let config = sess.get_config().await;\n let mut skills = Vec::new();\n for cwd in cwds {\n- let outcome = skills_manager.skills_for_cwd(&cwd, force_reload).await;\n+ let outcome = skills_manager\n+ .skills_for_cwd(&cwd, config.as_ref(), force_reload)\n+ .await;\n let errors = super::errors_to_info(&outcome.errors);\n let skills_metadata = super::skills_to_info(&outcome.skills, &outcome.disabled_paths);\n skills.push(SkillsListEntry {", - "path": "codex-rs/core/src/codex.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -2187,7 +2187,7 @@ async fn new_default_turn_uses_config_aware_skills_for_role_overrides() {\n let parent_outcome = session\n .services\n .skills_manager\n- .skills_for_cwd(&parent_config.cwd, true)\n+ .skills_for_cwd(&parent_config.cwd, &parent_config, true)\n .await;\n let parent_skill = parent_outcome\n .skills", - "path": "codex-rs/core/src/codex_tests.rs", - "status": "modified" - }, - { - "additions": 44, - "deletions": 68, - "patch_excerpt": "@@ -29,16 +29,12 @@ use crate::auth::CodexAuth;\n use crate::config::Config;\n use crate::config::ConfigService;\n use crate::config::ConfigServiceError;\n-use crate::config::ConfigToml;\n use crate::config::edit::ConfigEdit;\n use crate::config::edit::ConfigEditsBuilder;\n-use crate::config::profile::ConfigProfile;\n use crate::config::types::McpServerConfig;\n use crate::config::types::PluginConfig;\n use crate::config_loader::ConfigLayerStack;\n use crate::features::Feature;\n-use crate::features::FeatureOverrides;\n-use crate::features::Features;\n use crate::skills::SkillMetadata;\n use crate::skills::loader::SkillRoot;\n use crate::skills::loader::load_skills_from_roots;\n@@ -421,7 +417,7 @@ impl From for PluginRemoteSyncError {\n pub struct PluginsManager {\n codex_home: PathBuf,\n store: PluginStore,\n- cache_by_cwd: RwLock>,\n+ ...", - "path": "codex-rs/core/src/plugins/manager.rs", - "status": "modified" - }, - { - "additions": 131, - "deletions": 18, - "patch_excerpt": "@@ -59,18 +59,8 @@ fn plugin_config_toml(enabled: bool, plugins_feature_enabled: bool) -> String {\n \n fn load_plugins_from_config(config_toml: &str, codex_home: &Path) -> PluginLoadOutcome {\n write_file(&codex_home.join(CONFIG_TOML_FILE), config_toml);\n- let stack = ConfigLayerStack::new(\n- vec![ConfigLayerEntry::new(\n- ConfigLayerSource::User {\n- file: AbsolutePathBuf::try_from(codex_home.join(CONFIG_TOML_FILE)).unwrap(),\n- },\n- toml::from_str(config_toml).expect(\"plugin test config should parse\"),\n- )],\n- ConfigRequirements::default(),\n- ConfigRequirementsToml::default(),\n- )\n- .expect(\"config layer stack should build\");\n- PluginsManager::new(codex_home.to_path_buf()).plugins_for_layer_stack(codex_home, &stack, false)\n+ let config = load_config_blocking(codex_home, codex_home);\n+ PluginsMa...", - "path": "codex-rs/core/src/plugins/manager_tests.rs", - "status": "modified" - }, - { - "additions": 14, - "deletions": 1, - "patch_excerpt": "@@ -14,6 +14,7 @@ use std::io;\n use std::path::Component;\n use std::path::Path;\n use std::path::PathBuf;\n+use tracing::warn;\n \n const MARKETPLACE_RELATIVE_PATH: &str = \".agents/plugins/marketplace.json\";\n \n@@ -127,6 +128,9 @@ pub enum MarketplaceError {\n marketplace_name: String,\n },\n \n+ #[error(\"plugins feature is disabled\")]\n+ PluginsDisabled,\n+\n #[error(\"{0}\")]\n InvalidPlugin(String),\n }\n@@ -238,7 +242,16 @@ fn list_marketplaces_with_home(\n let mut marketplaces = Vec::new();\n \n for marketplace_path in discover_marketplace_paths_from_roots(additional_roots, home_dir) {\n- marketplaces.push(load_marketplace(&marketplace_path)?);\n+ match load_marketplace(&marketplace_path) {\n+ Ok(marketplace) => marketplaces.push(marketplace),\n+ Err(err) => {\n+ warn!(\n+ path = %marketplace_path.display(...", - "path": "codex-rs/core/src/plugins/marketplace.rs", - "status": "modified" - }, - { - "additions": 56, - "deletions": 0, - "patch_excerpt": "@@ -403,6 +403,62 @@ fn list_marketplaces_reads_marketplace_display_name() {\n );\n }\n \n+#[test]\n+fn list_marketplaces_skips_marketplaces_that_fail_to_load() {\n+ let tmp = tempdir().unwrap();\n+ let valid_repo_root = tmp.path().join(\"valid-repo\");\n+ let invalid_repo_root = tmp.path().join(\"invalid-repo\");\n+\n+ fs::create_dir_all(valid_repo_root.join(\".git\")).unwrap();\n+ fs::create_dir_all(valid_repo_root.join(\".agents/plugins\")).unwrap();\n+ fs::create_dir_all(invalid_repo_root.join(\".git\")).unwrap();\n+ fs::create_dir_all(invalid_repo_root.join(\".agents/plugins\")).unwrap();\n+ fs::write(\n+ valid_repo_root.join(\".agents/plugins/marketplace.json\"),\n+ r#\"{\n+ \"name\": \"valid-marketplace\",\n+ \"plugins\": [\n+ {\n+ \"name\": \"valid-plugin\",\n+ \"source\": {\n+ \"source\": \"local\",\n+ \"path\": \"./plugin\"\n+ }\n+ }\n+ ]\n+}\"#,\n+ )\n+ .u...", - "path": "codex-rs/core/src/plugins/marketplace_tests.rs", - "status": "modified" - }, - { - "additions": 11, - "deletions": 5, - "patch_excerpt": "@@ -92,18 +92,24 @@ impl SkillsManager {\n roots\n }\n \n- pub async fn skills_for_cwd(&self, cwd: &Path, force_reload: bool) -> SkillLoadOutcome {\n+ pub async fn skills_for_cwd(\n+ &self,\n+ cwd: &Path,\n+ config: &Config,\n+ force_reload: bool,\n+ ) -> SkillLoadOutcome {\n if !force_reload && let Some(outcome) = self.cached_outcome_for_cwd(cwd) {\n return outcome;\n }\n \n- self.skills_for_cwd_with_extra_user_roots(cwd, force_reload, &[])\n+ self.skills_for_cwd_with_extra_user_roots(cwd, config, force_reload, &[])\n .await\n }\n \n pub async fn skills_for_cwd_with_extra_user_roots(\n &self,\n cwd: &Path,\n+ config: &Config,\n force_reload: bool,\n extra_user_roots: &[PathBuf],\n ) -> SkillLoadOutcome {\n@@ -147,9 +153,9 @@ impl SkillsManager {\n }\n ...", - "path": "codex-rs/core/src/skills/manager.rs", - "status": "modified" - }, - { - "additions": 10, - "deletions": 2, - "patch_excerpt": "@@ -93,6 +93,7 @@ async fn skills_for_cwd_reuses_cached_entry_even_when_entry_has_extra_roots() {\n let outcome_with_extra = skills_manager\n .skills_for_cwd_with_extra_user_roots(\n cwd.path(),\n+ &config,\n true,\n std::slice::from_ref(&extra_root_path),\n )\n@@ -112,7 +113,9 @@ async fn skills_for_cwd_reuses_cached_entry_even_when_entry_has_extra_roots() {\n \n // The cwd-only API returns the current cached entry for this cwd, even when that entry\n // was produced with extra roots.\n- let outcome_without_extra = skills_manager.skills_for_cwd(cwd.path(), false).await;\n+ let outcome_without_extra = skills_manager\n+ .skills_for_cwd(cwd.path(), &config, false)\n+ .await;\n assert_eq!(outcome_without_extra.skills, outcome_with_extra.skills);\n assert_eq!(outcome_without_extra.errors, outcome_with_extra...", - "path": "codex-rs/core/src/skills/manager_tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -489,7 +489,7 @@ impl CoreShellActionProvider {\n .session\n .services\n .skills_manager\n- .skills_for_cwd(&self.turn.cwd, force_reload)\n+ .skills_for_cwd(&self.turn.cwd, self.turn.config.as_ref(), force_reload)\n .await;\n \n let program_path = program.as_path();", - "path": "codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "Resubmit https://github.com/openai/codex/pull/15020 with correct content.\r\n\r\n1. Use requirement-resolved config.features as the plugin gate.\r\n2. Guard plugin/list, plugin/read, and related flows behind that gate.\r\n3. Skip bad marketplace.json files instead of failing the whole list.\r\n4. Simplify plugin state and caching.", - "labels": [], - "merged_at": "2026-03-19T00:03:37Z", - "number": 15104, - "state": "merged", - "title": "fix: harden plugin feature gating", - "url": "https://github.com/openai/codex/pull/15104" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15111.json b/artifacts/github/bundles/openai-codex-pr-15111.json deleted file mode 100644 index 119c4a0..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15111.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "aibrahim-oai", - "committed_at": "2026-03-18T22:33:38Z", - "message": "don't add tranacript", - "sha": "e8232d32911ee5d246ecc482010c748021bc065e", - "url": "https://github.com/openai/codex/commit/e8232d32911ee5d246ecc482010c748021bc065e" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [], - "files": [ - { - "additions": 3, - "deletions": 1, - "patch_excerpt": "@@ -408,7 +408,9 @@ impl RealtimeWebsocketEvents {\n append_transcript_delta(&mut active_transcript.entries, \"assistant\", delta);\n }\n RealtimeEvent::HandoffRequested(handoff) => {\n- handoff.active_transcript = std::mem::take(&mut active_transcript.entries);\n+ if self.event_parser == RealtimeEventParser::V1 {\n+ handoff.active_transcript = std::mem::take(&mut active_transcript.entries);\n+ }\n }\n RealtimeEvent::SessionUpdated { .. }\n | RealtimeEvent::AudioOut(_)", - "path": "codex-rs/codex-api/src/endpoint/realtime_websocket/methods.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "# External (non-OpenAI) Pull Request Requirements\r\n\r\nBefore opening this Pull Request, please read the dedicated \"Contributing\" markdown file or your PR may be closed:\r\nhttps://github.com/openai/codex/blob/main/docs/contributing.md\r\n\r\nIf your PR conforms to our contribution guidelines, replace this text with a detailed and high quality description of your changes.\r\n\r\nInclude a link to a bug report or enhancement request.\r\n", - "labels": [], - "merged_at": "2026-03-18T22:54:13Z", - "number": 15111, - "state": "merged", - "title": "don't add transcript for v2 realtime", - "url": "https://github.com/openai/codex/pull/15111" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15118.json b/artifacts/github/bundles/openai-codex-pr-15118.json deleted file mode 100644 index 240b14a..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15118.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "eternal-openai", - "committed_at": "2026-03-18T23:30:35Z", - "message": "turn_id extension for stop/userpromptsubmit", - "sha": "9647be07b7f8f4435569f0f3ba0ef428647b31c3", - "url": "https://github.com/openai/codex/commit/9647be07b7f8f4435569f0f3ba0ef428647b31c3" - }, - { - "author": "eternal-openai", - "committed_at": "2026-03-19T00:31:52Z", - "message": "codex: fix CI failure on PR #15118", - "sha": "524f3aa437ea9e6f7a70077e18fa6c323ac42604", - "url": "https://github.com/openai/codex/commit/524f3aa437ea9e6f7a70077e18fa6c323ac42604" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "STOP_OUTPUT_FIXTURE", - "GENERATED_DIR", - "SESSION_START_OUTPUT_FIXTURE", - "STOP_INPUT_FIXTURE", - "USER_PROMPT_SUBMIT_INPUT_FIXTURE", - "USER_PROMPT_SUBMIT_OUTPUT_FIXTURE" - ], - "files": [ - { - "additions": 107, - "deletions": 0, - "patch_excerpt": "@@ -88,15 +88,20 @@ fn write_user_prompt_submit_hook(\n additional_context: &str,\n ) -> Result<()> {\n let script_path = home.join(\"user_prompt_submit_hook.py\");\n+ let log_path = home.join(\"user_prompt_submit_hook_log.jsonl\");\n+ let log_path = log_path.display();\n let blocked_prompt_json =\n serde_json::to_string(blocked_prompt).context(\"serialize blocked prompt for test\")?;\n let additional_context_json = serde_json::to_string(additional_context)\n .context(\"serialize user prompt submit additional context for test\")?;\n let script = format!(\n r#\"import json\n+from pathlib import Path\n import sys\n \n payload = json.load(sys.stdin)\n+with Path(r\"{log_path}\").open(\"a\", encoding=\"utf-8\") as handle:\n+ handle.write(json.dumps(payload) + \"\\n\")\n \n if payload.get(\"prompt\") == {blocked_prompt_json}:\n print(json.dumps({{\n@@ -202,6 +207,15 @@ fn read_...", - "path": "codex-rs/core/tests/suite/hooks.rs", - "status": "modified" - }, - { - "additions": 6, - "deletions": 1, - "patch_excerpt": "@@ -41,6 +41,10 @@\n },\n \"transcript_path\": {\n \"$ref\": \"#/definitions/NullableString\"\n+ },\n+ \"turn_id\": {\n+ \"description\": \"Codex extension: expose the active turn id to internal turn-scoped hooks.\",\n+ \"type\": \"string\"\n }\n },\n \"required\": [\n@@ -51,7 +55,8 @@\n \"permission_mode\",\n \"session_id\",\n \"stop_hook_active\",\n- \"transcript_path\"\n+ \"transcript_path\",\n+ \"turn_id\"\n ],\n \"title\": \"stop.command.input\",\n \"type\": \"object\"", - "path": "codex-rs/hooks/schema/generated/stop.command.input.schema.json", - "status": "modified" - }, - { - "additions": 6, - "deletions": 1, - "patch_excerpt": "@@ -38,6 +38,10 @@\n },\n \"transcript_path\": {\n \"$ref\": \"#/definitions/NullableString\"\n+ },\n+ \"turn_id\": {\n+ \"description\": \"Codex extension: expose the active turn id to internal turn-scoped hooks.\",\n+ \"type\": \"string\"\n }\n },\n \"required\": [\n@@ -47,7 +51,8 @@\n \"permission_mode\",\n \"prompt\",\n \"session_id\",\n- \"transcript_path\"\n+ \"transcript_path\",\n+ \"turn_id\"\n ],\n \"title\": \"user-prompt-submit.command.input\",\n \"type\": \"object\"", - "path": "codex-rs/hooks/schema/generated/user-prompt-submit.command.input.schema.json", - "status": "modified" - }, - { - "additions": 12, - "deletions": 9, - "patch_excerpt": "@@ -14,6 +14,7 @@ use crate::engine::ConfiguredHandler;\n use crate::engine::command_runner::CommandRunResult;\n use crate::engine::dispatcher;\n use crate::engine::output_parser;\n+use crate::schema::NullableString;\n use crate::schema::StopCommandInput;\n \n #[derive(Debug, Clone)]\n@@ -75,15 +76,17 @@ pub(crate) async fn run(\n };\n }\n \n- let input_json = match serde_json::to_string(&StopCommandInput::new(\n- request.session_id.to_string(),\n- request.transcript_path.clone(),\n- request.cwd.display().to_string(),\n- request.model.clone(),\n- request.permission_mode.clone(),\n- request.stop_hook_active,\n- request.last_assistant_message.clone(),\n- )) {\n+ let input_json = match serde_json::to_string(&StopCommandInput {\n+ session_id: request.session_id.to_string(),\n+ turn_id: request.turn_id.clone(),\n+ transcript_p...", - "path": "codex-rs/hooks/src/events/stop.rs", - "status": "modified" - }, - { - "additions": 11, - "deletions": 8, - "patch_excerpt": "@@ -14,6 +14,7 @@ use crate::engine::ConfiguredHandler;\n use crate::engine::command_runner::CommandRunResult;\n use crate::engine::dispatcher;\n use crate::engine::output_parser;\n+use crate::schema::NullableString;\n use crate::schema::UserPromptSubmitCommandInput;\n \n #[derive(Debug, Clone)]\n@@ -75,14 +76,16 @@ pub(crate) async fn run(\n };\n }\n \n- let input_json = match serde_json::to_string(&UserPromptSubmitCommandInput::new(\n- request.session_id.to_string(),\n- request.transcript_path.clone(),\n- request.cwd.display().to_string(),\n- request.model.clone(),\n- request.permission_mode.clone(),\n- request.prompt.clone(),\n- )) {\n+ let input_json = match serde_json::to_string(&UserPromptSubmitCommandInput {\n+ session_id: request.session_id.to_string(),\n+ turn_id: request.turn_id.clone(),\n+ transcript_path: NullableSt...", - "path": "codex-rs/hooks/src/events/user_prompt_submit.rs", - "status": "modified" - }, - { - "additions": 35, - "deletions": 46, - "patch_excerpt": "@@ -25,11 +25,11 @@ const STOP_OUTPUT_FIXTURE: &str = \"stop.command.output.schema.json\";\n pub(crate) struct NullableString(Option);\n \n impl NullableString {\n- fn from_path(path: Option) -> Self {\n+ pub(crate) fn from_path(path: Option) -> Self {\n Self(path.map(|path| path.display().to_string()))\n }\n \n- fn from_string(value: Option) -> Self {\n+ pub(crate) fn from_string(value: Option) -> Self {\n Self(value)\n }\n }\n@@ -178,6 +178,8 @@ impl SessionStartCommandInput {\n #[schemars(rename = \"user-prompt-submit.command.input\")]\n pub(crate) struct UserPromptSubmitCommandInput {\n pub session_id: String,\n+ /// Codex extension: expose the active turn id to internal turn-scoped hooks.\n+ pub turn_id: String,\n pub transcript_path: NullableString,\n pub cwd: String,\n #[schemars(schema_with = \"user_prompt_s...", - "path": "codex-rs/hooks/src/schema.rs", - "status": "modified" - } - ], - "linked_issues": [ - "#15118" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "## Description\r\n\r\nAdding an extension to the spec that exposes the turn_id to hook scripts. This is a codex-specific mechanic that allows connecting the hook runs with particular turns\r\n\r\n## Testing\r\n\r\nhooks config / sample hooks to use. Extract this, rename codex -> .codex, and place this into a repo or your home folder. It includes: config.toml that enables hooks, hooks.json, and sample python hooks:\r\n\r\n[codex.zip](https://github.com/user-attachments/files/26102671/codex.zip)\r\n\r\nexample run (note the turn_ids change between turns):\r\n\r\n```\r\n\u203a hello\r\n\r\n\r\n\u2022 Running SessionStart hook: lighting the observatory\r\n\r\nSessionStart hook (completed)\r\n warning: Hi, I'm a session start hook for wizard-tower (startup).\r\n hook context: Startup context: A wimboltine stonpet is an exotic cuisine from hyperspace\r\n\r\n\u2022 Running UserPromptSubmit hook: lighting the observatory lanterns\r\n\r\nUserPromptSubmit hook (completed)\r\n warning: wizard-tower UserPromptSubmit demo inspected: hello for turn: 019d036d-c7fa-72d2-b6fd-\r\n78878bfe34e4\r\n hook context: Wizard Tower UserPromptSubmit demo fired. For this reply only, include the exact\r\nphrase 'observatory lanterns lit' near the end.\r\n\r\n\u2022 Aloha! Grateful to be here and ready to build with you. Show me what you want to tackle in wizard-\r\n tower, and we\u2019ll surf the next wave together. observatory lanterns lit\r\n\r\n\u2022 Running Stop hook: back to shore\r\n\r\nStop hook (completed)\r\n warning: Wizard Tower Stop hook reviewed the completed reply (170 chars) for turn: 019d036d-c7fa-\r\n72d2-b6fd-78878bfe34e4\r\n\r\n\r\n\u203a what's a stonpet?\r\n\r\n\r\n\u2022 Running UserPromptSubmit hook: lighting the observatory lanterns\r\n\r\nUserPromptSubmit hook (completed)\r\n warning: wizard-tower UserPromptSubmit demo inspected: what's a stonpet? for turn: 019d036e-3164-\r\n72c3-a170-98925564c4fc\r\n hook context: Wizard Tower UserPromptSubmit demo fired. For this reply only, include the exact\r\nphrase 'observatory lanterns lit' near the end.\r\n\r\n\u2022 A stonpet isn\u2019t a standard real-world word, brah. In our shared context here, a wimboltine stonpet\r\n is an exotic cuisine from hyperspace, so \u201cstonpet\u201d sounds like the dish or food itself.\r\n\r\n If you want, we can totally invent the lore for it next. observatory lanterns lit\r\n\r\n\u2022 Running Stop hook: back to shore\r\n\r\nStop hook (completed)\r\n warning: Wizard Tower Stop hook reviewed the completed reply (271 chars) for turn: 019d036e-3164-\r\n72c3-a170-98925564c4fc\r\n```", - "labels": [], - "merged_at": "2026-03-19T04:48:31Z", - "number": 15118, - "state": "merged", - "title": "[hooks] turn_id extension for Stop & UserPromptSubmit", - "url": "https://github.com/openai/codex/pull/15118" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15119.json b/artifacts/github/bundles/openai-codex-pr-15119.json deleted file mode 100644 index 11d9989..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15119.json +++ /dev/null @@ -1,285 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "starr-openai", - "committed_at": "2026-03-16T23:10:50Z", - "message": "Add codex-exec-server crate", - "sha": "144c3593dbd3bc2ccdc6906f2afbc4cfc2f069b2", - "url": "https://github.com/openai/codex/commit/144c3593dbd3bc2ccdc6906f2afbc4cfc2f069b2" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-17T00:37:34Z", - "message": "docs(exec-server): add protocol README", - "sha": "949932ca110e5c4211fc9e2f73f24d79d38ebb19", - "url": "https://github.com/openai/codex/commit/949932ca110e5c4211fc9e2f73f24d79d38ebb19" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-18T19:29:54Z", - "message": "Add Bazel package for exec-server", - "sha": "40cc199757b1665fef748ed762e9ee9d142d2cbc", - "url": "https://github.com/openai/codex/commit/40cc199757b1665fef748ed762e9ee9d142d2cbc" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-18T19:48:00Z", - "message": "Trim exec-server PR to stub server slice", - "sha": "2958067cf9579c022eb14fca67001867147fe2ad", - "url": "https://github.com/openai/codex/commit/2958067cf9579c022eb14fca67001867147fe2ad" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-18T20:01:16Z", - "message": "Keep first exec-server PR initialize-only", - "sha": "76071974bb36af24188b1bff81585f57dc51df33", - "url": "https://github.com/openai/codex/commit/76071974bb36af24188b1bff81585f57dc51df33" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-18T20:59:41Z", - "message": "Add generic RPC server glue to exec-server stub", - "sha": "16ff474725b011aea5c9e78f56319e0c055b3490", - "url": "https://github.com/openai/codex/commit/16ff474725b011aea5c9e78f56319e0c055b3490" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-18T21:30:57Z", - "message": "Add generic exec-server RPC foundation", - "sha": "0a846a2625a56d2a913e516c476abd5a1d71c0e1", - "url": "https://github.com/openai/codex/commit/0a846a2625a56d2a913e516c476abd5a1d71c0e1" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-18T21:32:35Z", - "message": "Wire notification sender into exec-server RPC foundation", - "sha": "c5dbe421bb606d8bfa154c6c5cdd1eb89ab6a8a2", - "url": "https://github.com/openai/codex/commit/c5dbe421bb606d8bfa154c6c5cdd1eb89ab6a8a2" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-18T21:34:21Z", - "message": "Remove outer handler mutex from exec-server RPC base", - "sha": "66f49ea604315574d0b8a92d75dc196d5470df0a", - "url": "https://github.com/openai/codex/commit/66f49ea604315574d0b8a92d75dc196d5470df0a" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-18T21:43:39Z", - "message": "Test generic exec-server RPC response matching", - "sha": "43b112c263c5d99e77d5dc9231ae2a6312b1c8ab", - "url": "https://github.com/openai/codex/commit/43b112c263c5d99e77d5dc9231ae2a6312b1c8ab" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-18T23:30:10Z", - "message": "exec-server: simplify stub json-rpc transport shape", - "sha": "2661dc53309b75e3d817362599af730e93c555c2", - "url": "https://github.com/openai/codex/commit/2661dc53309b75e3d817362599af730e93c555c2" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-18T23:41:24Z", - "message": "exec-server: address transport review feedback", - "sha": "3b4a5229113ccd0ea14ed46a9a8e204457aa2baf", - "url": "https://github.com/openai/codex/commit/3b4a5229113ccd0ea14ed46a9a8e204457aa2baf" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-18T23:53:24Z", - "message": "exec-server: keep malformed-json connections alive", - "sha": "2ed509ddd33c3001e5ef50d84766b97ab5bf0d83", - "url": "https://github.com/openai/codex/commit/2ed509ddd33c3001e5ef50d84766b97ab5bf0d83" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-19T00:04:44Z", - "message": "exec-server: trim unused stub dependencies", - "sha": "866743de96e8051adcd7db1c7df4875e1d984213", - "url": "https://github.com/openai/codex/commit/866743de96e8051adcd7db1c7df4875e1d984213" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-19T00:32:23Z", - "message": "Remove stdio transport add harness", - "sha": "a2feb4bc64a8c06029ef383b2db7e43d9e41039e", - "url": "https://github.com/openai/codex/commit/a2feb4bc64a8c06029ef383b2db7e43d9e41039e" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-19T00:33:08Z", - "message": "Merge branch 'main' into starr/exec-server-stub-fresh", - "sha": "a979f03531ef8dca143709ac502410db0a927e8d", - "url": "https://github.com/openai/codex/commit/a979f03531ef8dca143709ac502410db0a927e8d" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-19T00:33:16Z", - "message": "Merge branch 'starr/exec-server-stub-fresh' into pakrym/remove-exec-server-stdio-transport", - "sha": "18e7c53082aa40108eadd89173a8b70c9658e4dd", - "url": "https://github.com/openai/codex/commit/18e7c53082aa40108eadd89173a8b70c9658e4dd" - } - ], - "default_branch": "main", - "docs_refs": [ - "codex-rs/exec-server/README.md" - ], - "examples_refs": [], - "extracted_flags": [ - "JSON", - "CLI", - "README", - "RPC", - "API", - "PORT", - "DEFAULT_LISTEN_URL", - "URL", - "JSONRPCM", - "CHANNEL_CAPACITY", - "--listen", - "JSONRPCN", - "JSONRPCR", - "CONNECT_TIMEOUT", - "CONNECT_RETRY_INTERVAL", - "EVENT_TIMEOUT", - "JSONRPCE" - ], - "files": [ - { - "additions": 0, - "deletions": 2, - "patch_excerpt": "@@ -2008,11 +2008,9 @@ name = \"codex-exec-server\"\n version = \"0.0.0\"\n dependencies = [\n \"anyhow\",\n- \"base64 0.22.1\",\n \"clap\",\n \"codex-app-server-protocol\",\n \"codex-utils-cargo-bin\",\n- \"codex-utils-pty\",\n \"futures\",\n \"pretty_assertions\",\n \"serde\",", - "path": "codex-rs/Cargo.lock", - "status": "modified" - }, - { - "additions": 8, - "deletions": 10, - "patch_excerpt": "@@ -24,12 +24,10 @@ the wire.\n The standalone binary supports:\n \n - `ws://IP:PORT` (default)\n-- `stdio://`\n \n Wire framing:\n \n - websocket: one JSON-RPC message per websocket text frame\n-- stdio: one newline-delimited JSON-RPC message per line on stdin/stdout\n \n ## Lifecycle\n \n@@ -43,8 +41,8 @@ Each connection follows this sequence:\n If the server receives any notification other than `initialized`, it replies\n with an error using request id `-1`.\n \n-If the stdio connection closes, the server terminates any remaining managed\n-processes before exiting.\n+If the websocket connection closes, the server terminates any remaining managed\n+processes for that client connection.\n \n ## API\n \n@@ -239,13 +237,13 @@ Typical error cases:\n The crate exports:\n \n - `ExecServerClient`\n-- `ExecServerLaunchCommand`\n-- `ExecServerProcess`\n - `ExecServerError`\n-- protocol structs such as `ExecParams`, `ExecResp...", - "path": "codex-rs/exec-server/README.md", - "status": "modified" - }, - { - "additions": 4, - "deletions": 6, - "patch_excerpt": "@@ -1,20 +1,18 @@\n use clap::Parser;\n-use codex_exec_server::ExecServerTransport;\n \n #[derive(Debug, Parser)]\n struct ExecServerArgs {\n- /// Transport endpoint URL. Supported values: `ws://IP:PORT` (default),\n- /// `stdio://`.\n+ /// Transport endpoint URL. Supported values: `ws://IP:PORT` (default).\n #[arg(\n long = \"listen\",\n value_name = \"URL\",\n- default_value = ExecServerTransport::DEFAULT_LISTEN_URL\n+ default_value = codex_exec_server::DEFAULT_LISTEN_URL\n )]\n- listen: ExecServerTransport,\n+ listen: String,\n }\n \n #[tokio::main]\n async fn main() -> Result<(), Box> {\n let args = ExecServerArgs::parse();\n- codex_exec_server::run_main_with_transport(args.listen).await\n+ codex_exec_server::run_main_with_listen_url(&args.listen).await\n }", - "path": "codex-rs/exec-server/src/bin/codex-exec-server.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 18, - "patch_excerpt": "@@ -1,8 +1,6 @@\n use std::sync::Arc;\n use std::time::Duration;\n \n-use tokio::io::AsyncRead;\n-use tokio::io::AsyncWrite;\n use tokio::time::timeout;\n use tokio_tungstenite::connect_async;\n use tracing::warn;\n@@ -136,22 +134,6 @@ impl ExecServerClient {\n Ok(client)\n }\n \n- pub async fn connect_stdio(\n- stdin: W,\n- stdout: R,\n- options: ExecServerClientConnectOptions,\n- ) -> Result\n- where\n- R: AsyncRead + Unpin + Send + 'static,\n- W: AsyncWrite + Unpin + Send + 'static,\n- {\n- Self::connect(\n- JsonRpcConnection::from_stdio(stdout, stdin, \"exec-server stdio\".to_string()),\n- options,\n- )\n- .await\n- }\n-\n pub async fn connect_websocket(\n args: RemoteExecServerConnectArgs,\n ) -> Result {", - "path": "codex-rs/exec-server/src/client.rs", - "status": "modified" - }, - { - "additions": 11, - "deletions": 4, - "patch_excerpt": "@@ -1,16 +1,21 @@\n use codex_app_server_protocol::JSONRPCMessage;\n use futures::SinkExt;\n use futures::StreamExt;\n-use tokio::io::AsyncBufReadExt;\n use tokio::io::AsyncRead;\n use tokio::io::AsyncWrite;\n-use tokio::io::AsyncWriteExt;\n-use tokio::io::BufReader;\n-use tokio::io::BufWriter;\n use tokio::sync::mpsc;\n use tokio_tungstenite::WebSocketStream;\n use tokio_tungstenite::tungstenite::Message;\n \n+#[cfg(test)]\n+use tokio::io::AsyncBufReadExt;\n+#[cfg(test)]\n+use tokio::io::AsyncWriteExt;\n+#[cfg(test)]\n+use tokio::io::BufReader;\n+#[cfg(test)]\n+use tokio::io::BufWriter;\n+\n pub(crate) const CHANNEL_CAPACITY: usize = 128;\n \n #[derive(Debug)]\n@@ -27,6 +32,7 @@ pub(crate) struct JsonRpcConnection {\n }\n \n impl JsonRpcConnection {\n+ #[cfg(test)]\n pub(crate) fn from_stdio(reader: R, writer: W, connection_label: String) -> Self\n where\n R: AsyncRead + Unpin + Send + 'static,...", - "path": "codex-rs/exec-server/src/connection.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 7, - "patch_excerpt": "@@ -1,7 +1,6 @@\n mod client;\n mod client_api;\n mod connection;\n-mod local;\n mod protocol;\n mod rpc;\n mod server;\n@@ -10,12 +9,9 @@ pub use client::ExecServerClient;\n pub use client::ExecServerError;\n pub use client_api::ExecServerClientConnectOptions;\n pub use client_api::RemoteExecServerConnectArgs;\n-pub use local::ExecServerLaunchCommand;\n-pub use local::SpawnedExecServer;\n-pub use local::spawn_local_exec_server;\n pub use protocol::InitializeParams;\n pub use protocol::InitializeResponse;\n-pub use server::ExecServerTransport;\n-pub use server::ExecServerTransportParseError;\n+pub use server::DEFAULT_LISTEN_URL;\n+pub use server::ExecServerListenUrlParseError;\n pub use server::run_main;\n-pub use server::run_main_with_transport;\n+pub use server::run_main_with_listen_url;", - "path": "codex-rs/exec-server/src/lib.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 71, - "patch_excerpt": "@@ -1,71 +0,0 @@\n-use std::path::PathBuf;\n-use std::process::Stdio;\n-use std::sync::Mutex as StdMutex;\n-\n-use tokio::process::Child;\n-use tokio::process::Command;\n-\n-use crate::client::ExecServerClient;\n-use crate::client::ExecServerError;\n-use crate::client_api::ExecServerClientConnectOptions;\n-\n-#[derive(Debug, Clone, PartialEq, Eq)]\n-pub struct ExecServerLaunchCommand {\n- pub program: PathBuf,\n- pub args: Vec,\n-}\n-\n-pub struct SpawnedExecServer {\n- client: ExecServerClient,\n- child: StdMutex>,\n-}\n-\n-impl SpawnedExecServer {\n- pub fn client(&self) -> &ExecServerClient {\n- &self.client\n- }\n-}\n-\n-impl Drop for SpawnedExecServer {\n- fn drop(&mut self) {\n- if let Ok(mut child_guard) = self.child.lock()\n- && let Some(child) = child_guard.as_mut()\n- {\n- let _ = child.start_kill();\n- }\n- }\n-}\n-\n-pu...", - "path": "codex-rs/exec-server/src/local.rs", - "status": "removed" - }, - { - "additions": 6, - "deletions": 6, - "patch_excerpt": "@@ -4,15 +4,15 @@ mod processor;\n mod transport;\n \n pub(crate) use handler::ExecServerHandler;\n-pub use transport::ExecServerTransport;\n-pub use transport::ExecServerTransportParseError;\n+pub use transport::DEFAULT_LISTEN_URL;\n+pub use transport::ExecServerListenUrlParseError;\n \n pub async fn run_main() -> Result<(), Box> {\n- run_main_with_transport(ExecServerTransport::Stdio).await\n+ run_main_with_listen_url(DEFAULT_LISTEN_URL).await\n }\n \n-pub async fn run_main_with_transport(\n- transport: ExecServerTransport,\n+pub async fn run_main_with_listen_url(\n+ listen_url: &str,\n ) -> Result<(), Box> {\n- transport::run_transport(transport).await\n+ transport::run_transport(listen_url).await\n }", - "path": "codex-rs/exec-server/src/server.rs", - "status": "modified" - }, - { - "additions": 20, - "deletions": 52, - "patch_excerpt": "@@ -1,5 +1,4 @@\n use std::net::SocketAddr;\n-use std::str::FromStr;\n \n use tokio::net::TcpListener;\n use tokio_tungstenite::accept_async;\n@@ -8,81 +7,50 @@ use tracing::warn;\n use crate::connection::JsonRpcConnection;\n use crate::server::processor::run_connection;\n \n-#[derive(Clone, Copy, Debug, Eq, PartialEq)]\n-pub enum ExecServerTransport {\n- Stdio,\n- WebSocket { bind_address: SocketAddr },\n-}\n+pub const DEFAULT_LISTEN_URL: &str = \"ws://127.0.0.1:0\";\n \n #[derive(Debug, Clone, Eq, PartialEq)]\n-pub enum ExecServerTransportParseError {\n+pub enum ExecServerListenUrlParseError {\n UnsupportedListenUrl(String),\n InvalidWebSocketListenUrl(String),\n }\n \n-impl std::fmt::Display for ExecServerTransportParseError {\n+impl std::fmt::Display for ExecServerListenUrlParseError {\n fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n match self {\n- Exe...", - "path": "codex-rs/exec-server/src/server/transport.rs", - "status": "modified" - }, - { - "additions": 18, - "deletions": 28, - "patch_excerpt": "@@ -1,41 +1,31 @@\n use pretty_assertions::assert_eq;\n \n-use super::ExecServerTransport;\n+use super::DEFAULT_LISTEN_URL;\n+use super::parse_listen_url;\n \n #[test]\n-fn exec_server_transport_parses_default_websocket_listen_url() {\n- let transport = ExecServerTransport::from_listen_url(ExecServerTransport::DEFAULT_LISTEN_URL)\n- .expect(\"default listen URL should parse\");\n+fn parse_listen_url_accepts_default_websocket_url() {\n+ let bind_address =\n+ parse_listen_url(DEFAULT_LISTEN_URL).expect(\"default listen URL should parse\");\n assert_eq!(\n- transport,\n- ExecServerTransport::WebSocket {\n- bind_address: \"127.0.0.1:0\".parse().expect(\"valid socket address\"),\n- }\n+ bind_address,\n+ \"127.0.0.1:0\".parse().expect(\"valid socket address\")\n );\n }\n \n #[test]\n-fn exec_server_transport_parses_stdio_listen_url() {\n- let transport =\n...", - "path": "codex-rs/exec-server/src/server/transport_tests.rs", - "status": "modified" - }, - { - "additions": 188, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,188 @@\n+#![allow(dead_code)]\n+\n+use std::process::Stdio;\n+use std::time::Duration;\n+\n+use anyhow::anyhow;\n+use codex_app_server_protocol::JSONRPCMessage;\n+use codex_app_server_protocol::JSONRPCNotification;\n+use codex_app_server_protocol::JSONRPCRequest;\n+use codex_app_server_protocol::RequestId;\n+use codex_utils_cargo_bin::cargo_bin;\n+use futures::SinkExt;\n+use futures::StreamExt;\n+use tokio::process::Child;\n+use tokio::process::Command;\n+use tokio::time::Instant;\n+use tokio::time::sleep;\n+use tokio::time::timeout;\n+use tokio_tungstenite::connect_async;\n+use tokio_tungstenite::tungstenite::Message;\n+\n+const CONNECT_TIMEOUT: Duration = Duration::from_secs(5);\n+const CONNECT_RETRY_INTERVAL: Duration = Duration::from_millis(25);\n+const EVENT_TIMEOUT: Duration = Duration::from_secs(5);\n+\n+pub(crate) struct ExecServerHarness {\n+ child: Child,\n+ websocket: tokio_tungstenite::...", - "path": "codex-rs/exec-server/tests/common/exec_server.rs", - "status": "added" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1 @@\n+pub(crate) mod exec_server;", - "path": "codex-rs/exec-server/tests/common/mod.rs", - "status": "added" - }, - { - "additions": 34, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,34 @@\n+#![cfg(unix)]\n+\n+mod common;\n+\n+use codex_app_server_protocol::JSONRPCMessage;\n+use codex_app_server_protocol::JSONRPCResponse;\n+use codex_exec_server::InitializeParams;\n+use codex_exec_server::InitializeResponse;\n+use common::exec_server::exec_server;\n+use pretty_assertions::assert_eq;\n+\n+#[tokio::test(flavor = \"multi_thread\", worker_threads = 2)]\n+async fn exec_server_accepts_initialize() -> anyhow::Result<()> {\n+ let mut server = exec_server().await?;\n+ let initialize_id = server\n+ .send_request(\n+ \"initialize\",\n+ serde_json::to_value(InitializeParams {\n+ client_name: \"exec-server-test\".to_string(),\n+ })?,\n+ )\n+ .await?;\n+\n+ let response = server.next_event().await?;\n+ let JSONRPCMessage::Response(JSONRPCResponse { id, result }) = response else {\n+ panic!(\"expected initialize resp...", - "path": "codex-rs/exec-server/tests/initialize.rs", - "status": "added" - }, - { - "additions": 65, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,65 @@\n+#![cfg(unix)]\n+\n+mod common;\n+\n+use codex_app_server_protocol::JSONRPCError;\n+use codex_app_server_protocol::JSONRPCMessage;\n+use codex_app_server_protocol::JSONRPCResponse;\n+use codex_exec_server::InitializeParams;\n+use common::exec_server::exec_server;\n+use pretty_assertions::assert_eq;\n+\n+#[tokio::test(flavor = \"multi_thread\", worker_threads = 2)]\n+async fn exec_server_stubs_process_start_over_websocket() -> anyhow::Result<()> {\n+ let mut server = exec_server().await?;\n+ let initialize_id = server\n+ .send_request(\n+ \"initialize\",\n+ serde_json::to_value(InitializeParams {\n+ client_name: \"exec-server-test\".to_string(),\n+ })?,\n+ )\n+ .await?;\n+ let _ = server\n+ .wait_for_event(|event| {\n+ matches!(\n+ event,\n+ JSONRPCMessage::Response(JSONRPCRespon...", - "path": "codex-rs/exec-server/tests/process.rs", - "status": "added" - }, - { - "additions": 0, - "deletions": 129, - "patch_excerpt": "@@ -1,129 +0,0 @@\n-#![cfg(unix)]\n-\n-use std::process::Stdio;\n-use std::time::Duration;\n-\n-use codex_app_server_protocol::JSONRPCMessage;\n-use codex_app_server_protocol::JSONRPCNotification;\n-use codex_app_server_protocol::JSONRPCRequest;\n-use codex_app_server_protocol::JSONRPCResponse;\n-use codex_app_server_protocol::RequestId;\n-use codex_exec_server::InitializeParams;\n-use codex_exec_server::InitializeResponse;\n-use codex_utils_cargo_bin::cargo_bin;\n-use pretty_assertions::assert_eq;\n-use tokio::io::AsyncBufReadExt;\n-use tokio::io::AsyncWriteExt;\n-use tokio::io::BufReader;\n-use tokio::process::Command;\n-use tokio::time::timeout;\n-\n-#[tokio::test(flavor = \"multi_thread\", worker_threads = 2)]\n-async fn exec_server_accepts_initialize_over_stdio() -> anyhow::Result<()> {\n- let binary = cargo_bin(\"codex-exec-server\")?;\n- let mut child = Command::new(binary);\n- child.args([\"--listen\"...", - "path": "codex-rs/exec-server/tests/stdio_smoke.rs", - "status": "removed" - }, - { - "additions": 60, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,60 @@\n+#![cfg(unix)]\n+\n+mod common;\n+\n+use codex_app_server_protocol::JSONRPCError;\n+use codex_app_server_protocol::JSONRPCMessage;\n+use codex_app_server_protocol::JSONRPCResponse;\n+use codex_exec_server::InitializeParams;\n+use codex_exec_server::InitializeResponse;\n+use common::exec_server::exec_server;\n+use pretty_assertions::assert_eq;\n+\n+#[tokio::test(flavor = \"multi_thread\", worker_threads = 2)]\n+async fn exec_server_reports_malformed_websocket_json_and_keeps_running() -> anyhow::Result<()> {\n+ let mut server = exec_server().await?;\n+ server.send_raw_text(\"not-json\").await?;\n+\n+ let response = server\n+ .wait_for_event(|event| matches!(event, JSONRPCMessage::Error(_)))\n+ .await?;\n+ let JSONRPCMessage::Error(JSONRPCError { id, error }) = response else {\n+ panic!(\"expected malformed-message error response\");\n+ };\n+ assert_eq!(id, codex_...", - "path": "codex-rs/exec-server/tests/websocket.rs", - "status": "added" - }, - { - "additions": 0, - "deletions": 229, - "patch_excerpt": "@@ -1,229 +0,0 @@\n-#![cfg(unix)]\n-\n-use std::process::Stdio;\n-use std::time::Duration;\n-\n-use codex_app_server_protocol::JSONRPCError;\n-use codex_app_server_protocol::JSONRPCMessage;\n-use codex_app_server_protocol::JSONRPCNotification;\n-use codex_app_server_protocol::JSONRPCRequest;\n-use codex_app_server_protocol::JSONRPCResponse;\n-use codex_app_server_protocol::RequestId;\n-use codex_exec_server::InitializeParams;\n-use codex_exec_server::InitializeResponse;\n-use codex_utils_cargo_bin::cargo_bin;\n-use pretty_assertions::assert_eq;\n-use tokio::process::Command;\n-use tokio_tungstenite::connect_async;\n-use tokio_tungstenite::tungstenite::Message;\n-\n-#[tokio::test(flavor = \"multi_thread\", worker_threads = 2)]\n-async fn exec_server_accepts_initialize_over_websocket() -> anyhow::Result<()> {\n- let binary = cargo_bin(\"codex-exec-server\")?;\n- let websocket_url = reserve_websocket_url()?;\n- ...", - "path": "codex-rs/exec-server/tests/websocket_smoke.rs", - "status": "removed" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "Summary\n- delete the deprecated stdio transport plumbing from the exec server stack\n- add a basic `exec_server()` harness plus test utilities to start a server, send requests, and await events\n- refresh exec-server dependencies, configs, and documentation to reflect the new flow\n\nTesting\n- Not run (not requested)", - "labels": [], - "merged_at": "2026-03-19T01:00:36Z", - "number": 15119, - "state": "merged", - "title": "Remove stdio transport from exec server", - "url": "https://github.com/openai/codex/pull/15119" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15125.json b/artifacts/github/bundles/openai-codex-pr-15125.json deleted file mode 100644 index f15004c..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15125.json +++ /dev/null @@ -1,179 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "pakrym-oai", - "committed_at": "2026-03-19T00:59:10Z", - "message": "Move environment into exec server", - "sha": "b3807c51ef81557391a199a9b0bb0686bdb949e4", - "url": "https://github.com/openai/codex/commit/b3807c51ef81557391a199a9b0bb0686bdb949e4" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-19T01:03:14Z", - "message": "Move environment module into exec", - "sha": "46eb5f49fdb7975fd49186220dbe8f7365d1adbc", - "url": "https://github.com/openai/codex/commit/46eb5f49fdb7975fd49186220dbe8f7365d1adbc" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-19T01:05:11Z", - "message": "Merge origin/main", - "sha": "7dd923d986f731a9280910955e687f4b64ffafb3", - "url": "https://github.com/openai/codex/commit/7dd923d986f731a9280910955e687f4b64ffafb3" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-19T01:10:31Z", - "message": "Align exec server build deps", - "sha": "86b0538d5a99c60df6d066179ff954a8ad992acc", - "url": "https://github.com/openai/codex/commit/86b0538d5a99c60df6d066179ff954a8ad992acc" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-19T01:16:52Z", - "message": "codex: fix CI failure on PR #15125", - "sha": "8902a180f66d60caac58da2e7a60ce609f6c1f06", - "url": "https://github.com/openai/codex/commit/8902a180f66d60caac58da2e7a60ce609f6c1f06" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "JSONRPCE", - "DEFAULT_LISTEN_URL" - ], - "files": [ - { - "additions": 5, - "deletions": 13, - "patch_excerpt": "@@ -1427,7 +1427,7 @@ dependencies = [\n \"codex-chatgpt\",\n \"codex-cloud-requirements\",\n \"codex-core\",\n- \"codex-environment\",\n+ \"codex-exec-server\",\n \"codex-feedback\",\n \"codex-file-search\",\n \"codex-login\",\n@@ -1843,7 +1843,7 @@ dependencies = [\n \"codex-client\",\n \"codex-config\",\n \"codex-connectors\",\n- \"codex-environment\",\n+ \"codex-exec-server\",\n \"codex-execpolicy\",\n \"codex-file-search\",\n \"codex-git\",\n@@ -1947,17 +1947,6 @@ dependencies = [\n \"serde_json\",\n ]\n \n-[[package]]\n-name = \"codex-environment\"\n-version = \"0.0.0\"\n-dependencies = [\n- \"async-trait\",\n- \"codex-utils-absolute-path\",\n- \"pretty_assertions\",\n- \"tempfile\",\n- \"tokio\",\n-]\n-\n [[package]]\n name = \"codex-exec\"\n version = \"0.0.0\"\n@@ -2008,13 +1997,16 @@ name = \"codex-exec-server\"\n version = \"0.0.0\"\n dependencies = [\n \"anyhow\",\n+ \"async-trait\",\n \"clap\",\n \"codex-app-server-protocol\",\n+ \"codex-utils-absolute-path\",\n \"co...", - "path": "codex-rs/Cargo.lock", - "status": "modified" - }, - { - "additions": 1, - "deletions": 2, - "patch_excerpt": "@@ -22,7 +22,6 @@ members = [\n \"shell-escalation\",\n \"skills\",\n \"core\",\n- \"environment\",\n \"hooks\",\n \"secrets\",\n \"exec\",\n@@ -105,8 +104,8 @@ codex-cloud-requirements = { path = \"cloud-requirements\" }\n codex-connectors = { path = \"connectors\" }\n codex-config = { path = \"config\" }\n codex-core = { path = \"core\" }\n-codex-environment = { path = \"environment\" }\n codex-exec = { path = \"exec\" }\n+codex-exec-server = { path = \"exec-server\" }\n codex-execpolicy = { path = \"execpolicy\" }\n codex-experimental-api-macros = { path = \"codex-experimental-api-macros\" }\n codex-feedback = { path = \"feedback\" }", - "path": "codex-rs/Cargo.toml", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -32,7 +32,7 @@ axum = { workspace = true, default-features = false, features = [\n codex-arg0 = { workspace = true }\n codex-cloud-requirements = { workspace = true }\n codex-core = { workspace = true }\n-codex-environment = { workspace = true }\n+codex-exec-server = { workspace = true }\n codex-otel = { workspace = true }\n codex-shell-command = { workspace = true }\n codex-utils-cli = { workspace = true }", - "path": "codex-rs/app-server/Cargo.toml", - "status": "modified" - }, - { - "additions": 5, - "deletions": 5, - "patch_excerpt": "@@ -18,11 +18,11 @@ use codex_app_server_protocol::FsRemoveResponse;\n use codex_app_server_protocol::FsWriteFileParams;\n use codex_app_server_protocol::FsWriteFileResponse;\n use codex_app_server_protocol::JSONRPCErrorError;\n-use codex_environment::CopyOptions;\n-use codex_environment::CreateDirectoryOptions;\n-use codex_environment::Environment;\n-use codex_environment::ExecutorFileSystem;\n-use codex_environment::RemoveOptions;\n+use codex_exec_server::CopyOptions;\n+use codex_exec_server::CreateDirectoryOptions;\n+use codex_exec_server::Environment;\n+use codex_exec_server::ExecutorFileSystem;\n+use codex_exec_server::RemoveOptions;\n use std::io;\n use std::sync::Arc;", - "path": "codex-rs/app-server/src/fs_api.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -34,7 +34,7 @@ codex-async-utils = { workspace = true }\n codex-client = { workspace = true }\n codex-connectors = { workspace = true }\n codex-config = { workspace = true }\n-codex-environment = { workspace = true }\n+codex-exec-server = { workspace = true }\n codex-shell-command = { workspace = true }\n codex-skills = { workspace = true }\n codex-execpolicy = { workspace = true }", - "path": "codex-rs/core/Cargo.toml", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -59,7 +59,7 @@ use chrono::Local;\n use chrono::Utc;\n use codex_app_server_protocol::McpServerElicitationRequest;\n use codex_app_server_protocol::McpServerElicitationRequestParams;\n-use codex_environment::Environment;\n+use codex_exec_server::Environment;\n use codex_hooks::HookEvent;\n use codex_hooks::HookEventAfterAgent;\n use codex_hooks::HookPayload;", - "path": "codex-rs/core/src/codex.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -2465,7 +2465,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {\n true,\n ));\n let network_approval = Arc::new(NetworkApprovalService::default());\n- let environment = Arc::new(codex_environment::Environment);\n+ let environment = Arc::new(codex_exec_server::Environment);\n \n let file_watcher = Arc::new(FileWatcher::noop());\n let services = SessionServices {\n@@ -3259,7 +3259,7 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(\n true,\n ));\n let network_approval = Arc::new(NetworkApprovalService::default());\n- let environment = Arc::new(codex_environment::Environment);\n+ let environment = Arc::new(codex_exec_server::Environment);\n \n let file_watcher = Arc::new(FileWatcher::noop());\n let services = SessionServices {", - "path": "codex-rs/core/src/codex_tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -20,7 +20,7 @@ use crate::tools::network_approval::NetworkApprovalService;\n use crate::tools::runtimes::ExecveSessionApproval;\n use crate::tools::sandboxing::ApprovalStore;\n use crate::unified_exec::UnifiedExecProcessManager;\n-use codex_environment::Environment;\n+use codex_exec_server::Environment;\n use codex_hooks::Hooks;\n use codex_otel::SessionTelemetry;\n use codex_utils_absolute_path::AbsolutePathBuf;", - "path": "codex-rs/core/src/state/service.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,5 +1,5 @@\n use async_trait::async_trait;\n-use codex_environment::ExecutorFileSystem;\n+use codex_exec_server::ExecutorFileSystem;\n use codex_protocol::models::FunctionCallOutputBody;\n use codex_protocol::models::FunctionCallOutputContentItem;\n use codex_protocol::models::FunctionCallOutputPayload;", - "path": "codex-rs/core/src/tools/handlers/view_image.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 6, - "patch_excerpt": "@@ -1,6 +0,0 @@\n-load(\"//:defs.bzl\", \"codex_rust_crate\")\n-\n-codex_rust_crate(\n- name = \"environment\",\n- crate_name = \"codex_environment\",\n-)", - "path": "codex-rs/environment/BUILD.bazel", - "status": "removed" - }, - { - "additions": 0, - "deletions": 21, - "patch_excerpt": "@@ -1,21 +0,0 @@\n-[package]\n-name = \"codex-environment\"\n-version.workspace = true\n-edition.workspace = true\n-license.workspace = true\n-\n-[lib]\n-name = \"codex_environment\"\n-path = \"src/lib.rs\"\n-\n-[lints]\n-workspace = true\n-\n-[dependencies]\n-async-trait = { workspace = true }\n-codex-utils-absolute-path = { workspace = true }\n-tokio = { workspace = true, features = [\"fs\", \"io-util\", \"rt\"] }\n-\n-[dev-dependencies]\n-pretty_assertions = { workspace = true }\n-tempfile = { workspace = true }", - "path": "codex-rs/environment/Cargo.toml", - "status": "removed" - }, - { - "additions": 0, - "deletions": 18, - "patch_excerpt": "@@ -1,18 +0,0 @@\n-pub mod fs;\n-\n-pub use fs::CopyOptions;\n-pub use fs::CreateDirectoryOptions;\n-pub use fs::ExecutorFileSystem;\n-pub use fs::FileMetadata;\n-pub use fs::FileSystemResult;\n-pub use fs::ReadDirectoryEntry;\n-pub use fs::RemoveOptions;\n-\n-#[derive(Clone, Debug, Default)]\n-pub struct Environment;\n-\n-impl Environment {\n- pub fn get_filesystem(&self) -> impl ExecutorFileSystem + use<> {\n- fs::LocalFileSystem\n- }\n-}", - "path": "codex-rs/environment/src/lib.rs", - "status": "removed" - }, - { - "additions": 4, - "deletions": 0, - "patch_excerpt": "@@ -15,13 +15,16 @@ path = \"src/bin/codex-exec-server.rs\"\n workspace = true\n \n [dependencies]\n+async-trait = { workspace = true }\n clap = { workspace = true, features = [\"derive\"] }\n codex-app-server-protocol = { workspace = true }\n+codex-utils-absolute-path = { workspace = true }\n futures = { workspace = true }\n serde = { workspace = true, features = [\"derive\"] }\n serde_json = { workspace = true }\n thiserror = { workspace = true }\n tokio = { workspace = true, features = [\n+ \"fs\",\n \"io-std\",\n \"io-util\",\n \"macros\",\n@@ -38,3 +41,4 @@ tracing = { workspace = true }\n anyhow = { workspace = true }\n codex-utils-cargo-bin = { workspace = true }\n pretty_assertions = { workspace = true }\n+tempfile = { workspace = true }", - "path": "codex-rs/exec-server/Cargo.toml", - "status": "modified" - }, - { - "additions": 11, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,11 @@\n+use crate::fs;\n+use crate::fs::ExecutorFileSystem;\n+\n+#[derive(Clone, Debug, Default)]\n+pub struct Environment;\n+\n+impl Environment {\n+ pub fn get_filesystem(&self) -> impl ExecutorFileSystem + use<> {\n+ fs::LocalFileSystem\n+ }\n+}", - "path": "codex-rs/exec-server/src/environment.rs", - "status": "added" - }, - { - "additions": 0, - "deletions": 0, - "patch_excerpt": null, - "path": "codex-rs/exec-server/src/fs.rs", - "status": "renamed" - }, - { - "additions": 10, - "deletions": 0, - "patch_excerpt": "@@ -1,6 +1,8 @@\n mod client;\n mod client_api;\n mod connection;\n+mod environment;\n+mod fs;\n mod protocol;\n mod rpc;\n mod server;\n@@ -9,6 +11,14 @@ pub use client::ExecServerClient;\n pub use client::ExecServerError;\n pub use client_api::ExecServerClientConnectOptions;\n pub use client_api::RemoteExecServerConnectArgs;\n+pub use environment::Environment;\n+pub use fs::CopyOptions;\n+pub use fs::CreateDirectoryOptions;\n+pub use fs::ExecutorFileSystem;\n+pub use fs::FileMetadata;\n+pub use fs::FileSystemResult;\n+pub use fs::ReadDirectoryEntry;\n+pub use fs::RemoveOptions;\n pub use protocol::InitializeParams;\n pub use protocol::InitializeResponse;\n pub use server::DEFAULT_LISTEN_URL;", - "path": "codex-rs/exec-server/src/lib.rs", - "status": "modified" - } - ], - "linked_issues": [ - "#15125" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "The idea is that codex-exec exposes an Environment struct with services on it. Each of those is a trait. \r\n\r\nDepending on construction parameters passed to Environment they are either backed by local or remote server but core doesn't see these differences.", - "labels": [], - "merged_at": "2026-03-19T15:31:15Z", - "number": 15125, - "state": "merged", - "title": "Move environment abstraction into exec server", - "url": "https://github.com/openai/codex/pull/15125" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15150.json b/artifacts/github/bundles/openai-codex-pr-15150.json deleted file mode 100644 index af092ec..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15150.json +++ /dev/null @@ -1,468 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "aibrahim-oai", - "committed_at": "2026-03-19T18:43:42Z", - "message": "Move terminal module to its own crate", - "sha": "df7a5872916053eab788ed158eb738d4472153bf", - "url": "https://github.com/openai/codex/commit/df7a5872916053eab788ed158eb738d4472153bf" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-19T20:13:26Z", - "message": "Rename terminal crate to terminal-detection", - "sha": "3661c085ded4aa30083d374fc786681eeea044b0", - "url": "https://github.com/openai/codex/commit/3661c085ded4aa30083d374fc786681eeea044b0" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-19T20:39:49Z", - "message": "codex: fix CI failure on PR #15216", - "sha": "9e6c4040126f6c977c099716ff1135b9f0638747", - "url": "https://github.com/openai/codex/commit/9e6c4040126f6c977c099716ff1135b9f0638747" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-19T20:26:08Z", - "message": "Move auth code into login crate", - "sha": "4b9dde5ebbb4e4f41769ab77f2797c09efb81a35", - "url": "https://github.com/openai/codex/commit/4b9dde5ebbb4e4f41769ab77f2797c09efb81a35" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-19T20:46:37Z", - "message": "codex: clean up auth import style on PR #15150", - "sha": "a7173968c60fb1086bdc1a7d7e52682734c1fcfd", - "url": "https://github.com/openai/codex/commit/a7173968c60fb1086bdc1a7d7e52682734c1fcfd" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-19T20:52:08Z", - "message": "codex: fix CI failure on PR #15150", - "sha": "905255deff1d503ce1e72f48292c2881ba5b1234", - "url": "https://github.com/openai/codex/commit/905255deff1d503ce1e72f48292c2881ba5b1234" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-19T20:58:16Z", - "message": "codex: avoid default_client imports in login on PR #15150", - "sha": "59c056e43d84d950abcdbb3d02b1082bf3ab8844", - "url": "https://github.com/openai/codex/commit/59c056e43d84d950abcdbb3d02b1082bf3ab8844" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-19T21:05:58Z", - "message": "codex: restore login auth API surface on PR #15150", - "sha": "499c3190e0a6cccbf1c3cab824b31f040beb6a77", - "url": "https://github.com/openai/codex/commit/499c3190e0a6cccbf1c3cab824b31f040beb6a77" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-19T21:08:24Z", - "message": "Merge branch 'main' into auth-crate-split", - "sha": "cfbec84964d4b2492daba415ab0da1457de73c2c", - "url": "https://github.com/openai/codex/commit/cfbec84964d4b2492daba415ab0da1457de73c2c" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-19T21:48:19Z", - "message": "fix", - "sha": "e2cd38a787890ebad801f28742ed9ce4845705e8", - "url": "https://github.com/openai/codex/commit/e2cd38a787890ebad801f28742ed9ce4845705e8" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-19T21:53:41Z", - "message": "fix", - "sha": "5c3bdad1a8b6ce471b82cf72dc6ccb2d8a2ed70f", - "url": "https://github.com/openai/codex/commit/5c3bdad1a8b6ce471b82cf72dc6ccb2d8a2ed70f" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-19T21:54:26Z", - "message": "fix", - "sha": "589fe5ffd9da8ff2b50fb5ffbddfe40a6237264d", - "url": "https://github.com/openai/codex/commit/589fe5ffd9da8ff2b50fb5ffbddfe40a6237264d" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-19T21:56:52Z", - "message": "fix", - "sha": "e73bd633525e723eed5529316ac5cd49742b7db1", - "url": "https://github.com/openai/codex/commit/e73bd633525e723eed5529316ac5cd49742b7db1" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-19T21:58:47Z", - "message": "fix", - "sha": "c13f83d8dee18a273693ca4df0b7872b25903a34", - "url": "https://github.com/openai/codex/commit/c13f83d8dee18a273693ca4df0b7872b25903a34" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-19T22:04:45Z", - "message": "fix", - "sha": "019ab69ee533ec45252e885dccfc22d2094b4dc6", - "url": "https://github.com/openai/codex/commit/019ab69ee533ec45252e885dccfc22d2094b4dc6" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-19T22:06:27Z", - "message": "fix", - "sha": "b6c3ecfd8b2e9a059dd3f44020ca6083d7b55e61", - "url": "https://github.com/openai/codex/commit/b6c3ecfd8b2e9a059dd3f44020ca6083d7b55e61" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-19T22:14:24Z", - "message": "fix", - "sha": "37e4e55475c5cc13f2bbd759b6f3ec18afa2af3f", - "url": "https://github.com/openai/codex/commit/37e4e55475c5cc13f2bbd759b6f3ec18afa2af3f" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-19T22:18:21Z", - "message": "fix", - "sha": "7c374c18e0105a71920886c336e60f38126f4243", - "url": "https://github.com/openai/codex/commit/7c374c18e0105a71920886c336e60f38126f4243" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-19T22:19:41Z", - "message": "fix", - "sha": "7c9e4b492fd8dd1bdebd10dbfd819dc98c3fa56e", - "url": "https://github.com/openai/codex/commit/7c9e4b492fd8dd1bdebd10dbfd819dc98c3fa56e" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-19T22:27:47Z", - "message": "fix", - "sha": "fa2b69dc7800fdee33cdda9443fad8e2896cb64a", - "url": "https://github.com/openai/codex/commit/fa2b69dc7800fdee33cdda9443fad8e2896cb64a" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-20T00:10:01Z", - "message": "fix", - "sha": "e57552f3e9bd00ea87d9ecabed7188733f15c4da", - "url": "https://github.com/openai/codex/commit/e57552f3e9bd00ea87d9ecabed7188733f15c4da" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-20T00:10:18Z", - "message": "fix", - "sha": "d3c83a408ae2de157034c673086df969904a25ae", - "url": "https://github.com/openai/codex/commit/d3c83a408ae2de157034c673086df969904a25ae" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-20T00:17:02Z", - "message": "fix", - "sha": "178a0dc31caac49158d5dca91cb3b28a93c8e4b0", - "url": "https://github.com/openai/codex/commit/178a0dc31caac49158d5dca91cb3b28a93c8e4b0" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-20T01:01:35Z", - "message": "unify", - "sha": "112d231f8535d5d41f66e85963003b8968e0726e", - "url": "https://github.com/openai/codex/commit/112d231f8535d5d41f66e85963003b8968e0726e" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-20T01:03:53Z", - "message": "unify", - "sha": "0461e2a98b2a389abd3127c440f15d0d1ac2e2b3", - "url": "https://github.com/openai/codex/commit/0461e2a98b2a389abd3127c440f15d0d1ac2e2b3" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "API", - "CLIENT_ID", - "HTTP", - "LMSTUDIO_OSS_PROVIDER_ID", - "OLLAMA_OSS_PROVIDER_ID", - "CODEX_SANDBOX_ENV_VAR", - "DEFAULT_ORIGINATOR", - "CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR", - "CODEX_INTERNAL_ORIGINATOR_OVERRIDE", - "RESIDENCY_HEADER_NAME", - "CODEX_SANDBOX", - "URL", - "CODEX_API_KEY_ENV_VAR", - "OPENAI_API_KEY_ENV_VAR", - "REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR", - "URL_SAFE_NO_PAD", - "INTERACTIVE_SESSION_SOURCES" - ], - "files": [ - { - "additions": 14, - "deletions": 6, - "patch_excerpt": "@@ -1841,15 +1841,14 @@ dependencies = [\n \"codex-arg0\",\n \"codex-artifacts\",\n \"codex-async-utils\",\n- \"codex-client\",\n \"codex-config\",\n \"codex-connectors\",\n \"codex-exec-server\",\n \"codex-execpolicy\",\n \"codex-file-search\",\n \"codex-git\",\n \"codex-hooks\",\n- \"codex-keyring-store\",\n+ \"codex-login\",\n \"codex-network-proxy\",\n \"codex-otel\",\n \"codex-protocol\",\n@@ -1886,7 +1885,6 @@ dependencies = [\n \"image\",\n \"indexmap 2.13.0\",\n \"insta\",\n- \"keyring\",\n \"landlock\",\n \"libc\",\n \"maplit\",\n@@ -1895,7 +1893,6 @@ dependencies = [\n \"openssl-sys\",\n \"opentelemetry\",\n \"opentelemetry_sdk\",\n- \"os_info\",\n \"predicates\",\n \"pretty_assertions\",\n \"rand 0.9.2\",\n@@ -1909,7 +1906,6 @@ dependencies = [\n \"serde_yaml\",\n \"serial_test\",\n \"sha1\",\n- \"sha2\",\n \"shlex\",\n \"similar\",\n \"tempfile\",\n@@ -2172,19 +2168,30 @@ name = \"codex-login\"\n version = \"0.0.0\"\n dependencies = [\n \"anyhow\",\n+ \"async-trait\",\n...", - "path": "codex-rs/Cargo.lock", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -191,7 +191,6 @@ use codex_core::ThreadSortKey as CoreThreadSortKey;\n use codex_core::auth::AuthMode as CoreAuthMode;\n use codex_core::auth::CLIENT_ID;\n use codex_core::auth::login_with_api_key;\n-use codex_core::auth::login_with_chatgpt_auth_tokens;\n use codex_core::config::Config;\n use codex_core::config::ConfigOverrides;\n use codex_core::config::NetworkProxyAuditMetadata;\n@@ -242,6 +241,7 @@ use codex_core::windows_sandbox::WindowsSandboxSetupRequest;\n use codex_feedback::CodexFeedback;\n use codex_login::ServerOptions as LoginServerOptions;\n use codex_login::ShutdownHandle;\n+use codex_login::auth::login_with_chatgpt_auth_tokens;\n use codex_login::run_login_server;\n use codex_protocol::ThreadId;\n use codex_protocol::config_types::CollaborationMode;\n@@ -1411,7 +1411,7 @@ impl CodexMessageProcessor {\n let account = match self.auth_manager.auth_cached() {\n Some(auth)...", - "path": "codex-rs/app-server/src/codex_message_processor.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 4, - "patch_excerpt": "@@ -50,10 +50,6 @@ use codex_arg0::Arg0DispatchPaths;\n use codex_core::AnalyticsEventsClient;\n use codex_core::AuthManager;\n use codex_core::ThreadManager;\n-use codex_core::auth::ExternalAuthRefreshContext;\n-use codex_core::auth::ExternalAuthRefreshReason;\n-use codex_core::auth::ExternalAuthRefresher;\n-use codex_core::auth::ExternalAuthTokens;\n use codex_core::config::Config;\n use codex_core::config_loader::CloudRequirementsLoader;\n use codex_core::config_loader::LoaderOverrides;\n@@ -64,6 +60,10 @@ use codex_core::default_client::set_default_client_residency_requirement;\n use codex_core::default_client::set_default_originator;\n use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;\n use codex_feedback::CodexFeedback;\n+use codex_login::auth::ExternalAuthRefreshContext;\n+use codex_login::auth::ExternalAuthRefreshReason;\n+use codex_login::auth::ExternalAuthRef...", - "path": "codex-rs/app-server/src/message_processor.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -328,7 +328,7 @@ pub async fn run_login_status(cli_config_overrides: CliConfigOverrides) -> ! {\n std::process::exit(1);\n }\n },\n- AuthMode::Chatgpt => {\n+ AuthMode::Chatgpt | AuthMode::ChatgptAuthTokens => {\n eprintln!(\"Logged in using ChatGPT\");\n std::process::exit(0);\n }", - "path": "codex-rs/cli/src/login.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 11, - "patch_excerpt": "@@ -31,17 +31,16 @@ codex-api = { workspace = true }\n codex-app-server-protocol = { workspace = true }\n codex-apply-patch = { workspace = true }\n codex-async-utils = { workspace = true }\n-codex-client = { workspace = true }\n codex-connectors = { workspace = true }\n codex-config = { workspace = true }\n codex-exec-server = { workspace = true }\n+codex-login = { workspace = true }\n codex-shell-command = { workspace = true }\n codex-skills = { workspace = true }\n codex-execpolicy = { workspace = true }\n codex-file-search = { workspace = true }\n codex-git = { workspace = true }\n codex-hooks = { workspace = true }\n-codex-keyring-store = { workspace = true }\n codex-network-proxy = { workspace = true }\n codex-otel = { workspace = true }\n codex-artifacts = { workspace = true }\n@@ -70,11 +69,9 @@ http = { workspace = true }\n iana-time-zone = { workspace = true }\n image = { workspace = true, features...", - "path": "codex-rs/core/Cargo.toml", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1530,7 +1530,7 @@ impl AuthRequestTelemetryContext {\n Self {\n auth_mode: auth_mode.map(|mode| match mode {\n AuthMode::ApiKey => \"ApiKey\",\n- AuthMode::Chatgpt => \"Chatgpt\",\n+ AuthMode::Chatgpt | AuthMode::ChatgptAuthTokens => \"Chatgpt\",\n }),\n auth_header_attached: api_auth.auth_header_attached(),\n auth_header_name: api_auth.auth_header_name(),", - "path": "codex-rs/core/src/client.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,2 @@\n+// Re-exported as `crate::default_client` from `lib.rs`.\n+pub use codex_login::default_client::*;", - "path": "codex-rs/core/src/default_client_forwarding.rs", - "status": "added" - }, - { - "additions": 2, - "deletions": 24, - "patch_excerpt": "@@ -9,6 +9,8 @@ use chrono::Datelike;\n use chrono::Local;\n use chrono::Utc;\n use codex_async_utils::CancelErr;\n+pub use codex_login::auth::RefreshTokenFailedError;\n+pub use codex_login::auth::RefreshTokenFailedReason;\n use codex_protocol::ThreadId;\n use codex_protocol::protocol::CodexErrorInfo;\n use codex_protocol::protocol::ErrorEvent;\n@@ -261,30 +263,6 @@ impl std::fmt::Display for ResponseStreamFailed {\n }\n }\n \n-#[derive(Debug, Clone, PartialEq, Eq, Error)]\n-#[error(\"{message}\")]\n-pub struct RefreshTokenFailedError {\n- pub reason: RefreshTokenFailedReason,\n- pub message: String,\n-}\n-\n-impl RefreshTokenFailedError {\n- pub fn new(reason: RefreshTokenFailedReason, message: impl Into) -> Self {\n- Self {\n- reason,\n- message: message.into(),\n- }\n- }\n-}\n-\n-#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n-pub enum RefreshTokenFailedReas...", - "path": "codex-rs/core/src/error.rs", - "status": "modified" - }, - { - "additions": 11, - "deletions": 3, - "patch_excerpt": "@@ -10,7 +10,7 @@ pub mod api_bridge;\n mod apply_patch;\n mod apps;\n mod arc_monitor;\n-pub mod auth;\n+pub use codex_login as auth;\n mod auth_env_telemetry;\n mod client;\n mod client_common;\n@@ -76,7 +76,7 @@ mod shell_detect;\n mod stream_events_utils;\n pub mod test_support;\n mod text_encoding;\n-pub mod token_data;\n+pub use codex_login::token_data;\n mod truncate;\n mod unified_exec;\n pub mod windows_sandbox;\n@@ -110,7 +110,15 @@ pub type CodexConversation = CodexThread;\n pub use analytics_client::AnalyticsEventsClient;\n pub use auth::AuthManager;\n pub use auth::CodexAuth;\n-pub mod default_client;\n+mod default_client_forwarding;\n+\n+/// Default Codex HTTP client headers and reqwest construction.\n+///\n+/// Implemented in [`codex_login::default_client`]; this module re-exports that API for crates\n+/// that import `codex_core::default_client`.\n+pub mod default_client {\n+ pub use super::default...", - "path": "codex-rs/core/src/lib.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 16, - "patch_excerpt": "@@ -4,7 +4,6 @@ use std::time::Duration;\n \n use codex_protocol::ThreadId;\n use rand::Rng;\n-use tracing::debug;\n use tracing::error;\n \n use crate::auth_env_telemetry::AuthEnvTelemetry;\n@@ -217,21 +216,6 @@ pub(crate) fn error_or_panic(message: impl std::string::ToString) {\n }\n }\n \n-pub(crate) fn try_parse_error_message(text: &str) -> String {\n- debug!(\"Parsing server error response: {}\", text);\n- let json = serde_json::from_str::(text).unwrap_or_default();\n- if let Some(error) = json.get(\"error\")\n- && let Some(message) = error.get(\"message\")\n- && let Some(message_str) = message.as_str()\n- {\n- return message_str.to_string();\n- }\n- if text.is_empty() {\n- return \"Unknown error\".to_string();\n- }\n- text.to_string()\n-}\n-\n pub fn resolve_path(base: &Path, path: &PathBuf) -> PathBuf {\n if path.is_absolute() {\n ...", - "path": "codex-rs/core/src/util.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 24, - "patch_excerpt": "@@ -12,30 +12,6 @@ use tracing_subscriber::layer::SubscriberExt;\n use tracing_subscriber::registry::LookupSpan;\n use tracing_subscriber::util::SubscriberInitExt;\n \n-#[test]\n-fn test_try_parse_error_message() {\n- let text = r#\"{\n- \"error\": {\n- \"message\": \"Your refresh token has already been used to generate a new access token. Please try signing in again.\",\n- \"type\": \"invalid_request_error\",\n- \"param\": null,\n- \"code\": \"refresh_token_reused\"\n- }\n-}\"#;\n- let message = try_parse_error_message(text);\n- assert_eq!(\n- message,\n- \"Your refresh token has already been used to generate a new access token. Please try signing in again.\"\n- );\n-}\n-\n-#[test]\n-fn test_try_parse_error_message_no_error() {\n- let text = r#\"{\"message\": \"test\"}\"#;\n- let message = try_parse_error_message(text);\n- assert_eq!(message, r#\"{\"message\": \"test\"}\"#);\n-}\n-\n #[test]\n fn f...", - "path": "codex-rs/core/src/util_tests.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 2, - "patch_excerpt": "@@ -789,8 +789,10 @@ fn minimal_jwt() -> String {\n }\n \n fn build_tokens(access_token: &str, refresh_token: &str) -> TokenData {\n- let mut id_token = IdTokenInfo::default();\n- id_token.raw_jwt = minimal_jwt();\n+ let id_token = IdTokenInfo {\n+ raw_jwt: minimal_jwt(),\n+ ..Default::default()\n+ };\n TokenData {\n id_token,\n access_token: access_token.to_string(),", - "path": "codex-rs/core/tests/suite/auth_refresh.rs", - "status": "modified" - }, - { - "additions": 7, - "deletions": 1, - "patch_excerpt": "@@ -45,6 +45,7 @@ use codex_cloud_requirements::cloud_requirements_loader;\n use codex_core::AuthManager;\n use codex_core::LMSTUDIO_OSS_PROVIDER_ID;\n use codex_core::OLLAMA_OSS_PROVIDER_ID;\n+use codex_core::auth::AuthConfig;\n use codex_core::auth::enforce_login_restrictions;\n use codex_core::check_execpolicy_for_warnings;\n use codex_core::config::Config;\n@@ -381,7 +382,12 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result\n \n set_default_client_residency_requirement(config.enforce_residency.value());\n \n- if let Err(err) = enforce_login_restrictions(&config) {\n+ if let Err(err) = enforce_login_restrictions(&AuthConfig {\n+ codex_home: config.codex_home.clone(),\n+ auth_credentials_store_mode: config.cli_auth_credentials_store_mode,\n+ forced_login_method: config.forced_login_method,\n+ forced_chatgpt_workspace_id: config.for...", - "path": "codex-rs/exec/src/lib.rs", - "status": "modified" - }, - { - "additions": 13, - "deletions": 2, - "patch_excerpt": "@@ -8,16 +8,24 @@ license.workspace = true\n workspace = true\n \n [dependencies]\n+async-trait = { workspace = true }\n base64 = { workspace = true }\n chrono = { workspace = true, features = [\"serde\"] }\n-codex-client = { workspace = true }\n-codex-core = { workspace = true }\n codex-app-server-protocol = { workspace = true }\n+codex-client = { workspace = true }\n+codex-config = { workspace = true }\n+codex-keyring-store = { workspace = true }\n+codex-protocol = { workspace = true }\n+codex-terminal-detection = { workspace = true }\n+once_cell = { workspace = true }\n+os_info = { workspace = true }\n rand = { workspace = true }\n reqwest = { workspace = true, features = [\"json\", \"blocking\"] }\n+schemars = { workspace = true }\n serde = { workspace = true, features = [\"derive\"] }\n serde_json = { workspace = true }\n sha2 = { workspace = true }\n+thiserror = { workspace = true }\n tiny_http = { workspace = tr...", - "path": "codex-rs/login/Cargo.toml", - "status": "modified" - }, - { - "additions": 9, - "deletions": 13, - "patch_excerpt": "@@ -1,8 +1,6 @@\n use super::*;\n use crate::auth::storage::FileAuthStorage;\n use crate::auth::storage::get_auth_file;\n-use crate::config::Config;\n-use crate::config::ConfigBuilder;\n use crate::token_data::IdTokenInfo;\n use crate::token_data::KnownPlan as InternalKnownPlan;\n use crate::token_data::PlanType as InternalPlanType;\n@@ -103,7 +101,7 @@ async fn pro_account_with_no_api_key_uses_chatgpt_auth() {\n .unwrap()\n .unwrap();\n assert_eq!(None, auth.api_key());\n- assert_eq!(AuthMode::Chatgpt, auth.auth_mode());\n+ assert_eq!(crate::AuthMode::Chatgpt, auth.auth_mode());\n assert_eq!(auth.get_chatgpt_user_id().as_deref(), Some(\"user-12345\"));\n \n let auth_dot_json = auth\n@@ -149,7 +147,7 @@ async fn loads_api_key_from_auth_json() {\n let auth = super::load_auth(dir.path(), false, AuthCredentialsStoreMode::File)\n .unwrap()\n .unwrap();\n- ass...", - "path": "codex-rs/login/src/auth/auth_tests.rs", - "status": "renamed" - }, - { - "additions": 9, - "deletions": 3, - "patch_excerpt": "@@ -1,5 +1,9 @@\n-use crate::config_loader::ResidencyRequirement;\n-use crate::spawn::CODEX_SANDBOX_ENV_VAR;\n+//! Default Codex HTTP client: shared `User-Agent`, `originator`, optional residency header, and\n+//! reqwest/`CodexHttpClient` construction.\n+//!\n+//! Use [`crate::default_client`] or [`codex_login::default_client`] from other crates in this\n+//! workspace.\n+\n use codex_client::BuildCustomCaTransportError;\n use codex_client::CodexHttpClient;\n pub use codex_client::CodexRequestBuilder;\n@@ -31,6 +35,8 @@ pub const DEFAULT_ORIGINATOR: &str = \"codex_cli_rs\";\n pub const CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR: &str = \"CODEX_INTERNAL_ORIGINATOR_OVERRIDE\";\n pub const RESIDENCY_HEADER_NAME: &str = \"x-openai-internal-codex-residency\";\n \n+pub use codex_config::ResidencyRequirement;\n+\n #[derive(Debug, Clone)]\n pub struct Originator {\n pub value: String,\n@@ -232,7 +238,7 @@ pub fn defa...", - "path": "codex-rs/login/src/auth/default_client.rs", - "status": "renamed" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -1,3 +1,4 @@\n+use super::sanitize_user_agent;\n use super::*;\n use core_test_support::skip_if_no_network;\n use pretty_assertions::assert_eq;", - "path": "codex-rs/login/src/auth/default_client_tests.rs", - "status": "renamed" - }, - { - "additions": 25, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,25 @@\n+use thiserror::Error;\n+\n+#[derive(Debug, Clone, PartialEq, Eq, Error)]\n+#[error(\"{message}\")]\n+pub struct RefreshTokenFailedError {\n+ pub reason: RefreshTokenFailedReason,\n+ pub message: String,\n+}\n+\n+impl RefreshTokenFailedError {\n+ pub fn new(reason: RefreshTokenFailedReason, message: impl Into) -> Self {\n+ Self {\n+ reason,\n+ message: message.into(),\n+ }\n+ }\n+}\n+\n+#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n+pub enum RefreshTokenFailedReason {\n+ Expired,\n+ Exhausted,\n+ Revoked,\n+ Other,\n+}", - "path": "codex-rs/login/src/auth/error.rs", - "status": "added" - }, - { - "additions": 37, - "deletions": 63, - "patch_excerpt": "@@ -1,5 +1,3 @@\n-mod storage;\n-\n use async_trait::async_trait;\n use chrono::Utc;\n use reqwest::StatusCode;\n@@ -16,46 +14,25 @@ use std::sync::Mutex;\n use std::sync::RwLock;\n \n use codex_app_server_protocol::AuthMode as ApiAuthMode;\n-use codex_otel::TelemetryAuthMode;\n use codex_protocol::config_types::ForcedLoginMethod;\n \n+use crate::auth::error::RefreshTokenFailedError;\n+use crate::auth::error::RefreshTokenFailedReason;\n pub use crate::auth::storage::AuthCredentialsStoreMode;\n pub use crate::auth::storage::AuthDotJson;\n use crate::auth::storage::AuthStorageBackend;\n use crate::auth::storage::create_auth_storage;\n-use crate::config::Config;\n-use crate::error::RefreshTokenFailedError;\n-use crate::error::RefreshTokenFailedReason;\n+use crate::auth::util::try_parse_error_message;\n+use crate::default_client::create_client;\n use crate::token_data::KnownPlan as InternalKnownPlan;\n use crate::to...", - "path": "codex-rs/login/src/auth/manager.rs", - "status": "renamed" - }, - { - "additions": 10, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,10 @@\n+pub mod default_client;\n+pub mod error;\n+mod storage;\n+mod util;\n+\n+mod manager;\n+\n+pub use error::RefreshTokenFailedError;\n+pub use error::RefreshTokenFailedReason;\n+pub use manager::*;", - "path": "codex-rs/login/src/auth/mod.rs", - "status": "added" - }, - { - "additions": 0, - "deletions": 0, - "patch_excerpt": null, - "path": "codex-rs/login/src/auth/storage.rs", - "status": "renamed" - }, - { - "additions": 0, - "deletions": 0, - "patch_excerpt": null, - "path": "codex-rs/login/src/auth/storage_tests.rs", - "status": "renamed" - }, - { - "additions": 45, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,45 @@\n+use tracing::debug;\n+\n+pub(crate) fn try_parse_error_message(text: &str) -> String {\n+ debug!(\"Parsing server error response: {}\", text);\n+ let json = serde_json::from_str::(text).unwrap_or_default();\n+ if let Some(error) = json.get(\"error\")\n+ && let Some(message) = error.get(\"message\")\n+ && let Some(message_str) = message.as_str()\n+ {\n+ return message_str.to_string();\n+ }\n+ if text.is_empty() {\n+ return \"Unknown error\".to_string();\n+ }\n+ text.to_string()\n+}\n+\n+#[cfg(test)]\n+mod tests {\n+ use super::try_parse_error_message;\n+\n+ #[test]\n+ fn try_parse_error_message_extracts_openai_error_message() {\n+ let text = r#\"{\n+ \"error\": {\n+ \"message\": \"Your refresh token has already been used to generate a new access token. Please try signing in again.\",\n+ \"type\": \"invalid_request_error\",...", - "path": "codex-rs/login/src/auth/util.rs", - "status": "added" - }, - { - "additions": 22, - "deletions": 11, - "patch_excerpt": "@@ -1,3 +1,6 @@\n+pub mod auth;\n+pub mod token_data;\n+\n mod device_code_auth;\n mod pkce;\n mod server;\n@@ -12,15 +15,23 @@ pub use server::ServerOptions;\n pub use server::ShutdownHandle;\n pub use server::run_login_server;\n \n-// Re-export commonly used auth types and helpers from codex-core for compatibility\n+pub use auth::AuthConfig;\n+pub use auth::AuthCredentialsStoreMode;\n+pub use auth::AuthDotJson;\n+pub use auth::AuthManager;\n+pub use auth::CLIENT_ID;\n+pub use auth::CODEX_API_KEY_ENV_VAR;\n+pub use auth::CodexAuth;\n+pub use auth::OPENAI_API_KEY_ENV_VAR;\n+pub use auth::REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR;\n+pub use auth::RefreshTokenError;\n+pub use auth::UnauthorizedRecovery;\n+pub use auth::default_client;\n+pub use auth::enforce_login_restrictions;\n+pub use auth::load_auth_dot_json;\n+pub use auth::login_with_api_key;\n+pub use auth::logout;\n+pub use auth::read_openai_api_key_from_env;\n+pub u...", - "path": "codex-rs/login/src/lib.rs", - "status": "modified" - }, - { - "additions": 7, - "deletions": 10, - "patch_excerpt": "@@ -23,18 +23,18 @@ use std::sync::Arc;\n use std::thread;\n use std::time::Duration;\n \n+use crate::auth::AuthCredentialsStoreMode;\n+use crate::auth::AuthDotJson;\n+use crate::auth::save_auth;\n+use crate::default_client::originator;\n use crate::pkce::PkceCodes;\n use crate::pkce::generate_pkce;\n+use crate::token_data::TokenData;\n+use crate::token_data::parse_chatgpt_jwt_claims;\n use base64::Engine;\n use chrono::Utc;\n use codex_app_server_protocol::AuthMode;\n use codex_client::build_reqwest_client_with_custom_ca;\n-use codex_core::auth::AuthCredentialsStoreMode;\n-use codex_core::auth::AuthDotJson;\n-use codex_core::auth::save_auth;\n-use codex_core::default_client::originator;\n-use codex_core::token_data::TokenData;\n-use codex_core::token_data::parse_chatgpt_jwt_claims;\n use rand::RngCore;\n use serde_json::Value as JsonValue;\n use tiny_http::Header;\n@@ -484,10 +484,7 @@ fn build_authorize_url(\n ...", - "path": "codex-rs/login/src/server.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 4, - "patch_excerpt": "@@ -27,7 +27,7 @@ pub struct IdTokenInfo {\n /// The ChatGPT subscription plan type\n /// (e.g., \"free\", \"plus\", \"pro\", \"business\", \"enterprise\", \"edu\").\n /// (Note: values may vary by backend.)\n- pub(crate) chatgpt_plan_type: Option,\n+ pub chatgpt_plan_type: Option,\n /// ChatGPT user identifier associated with the token, if present.\n pub chatgpt_user_id: Option,\n /// Organization/workspace identifier associated with the token, if present.\n@@ -55,13 +55,13 @@ impl IdTokenInfo {\n \n #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]\n #[serde(untagged)]\n-pub(crate) enum PlanType {\n+pub enum PlanType {\n Known(KnownPlan),\n Unknown(String),\n }\n \n impl PlanType {\n- pub(crate) fn from_raw_value(raw: &str) -> Self {\n+ pub fn from_raw_value(raw: &str) -> Self {\n match raw.to_ascii_lowercase().as_str() {\n ...", - "path": "codex-rs/login/src/token_data.rs", - "status": "renamed" - }, - { - "additions": 0, - "deletions": 0, - "patch_excerpt": null, - "path": "codex-rs/login/src/token_data_tests.rs", - "status": "renamed" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -3,9 +3,9 @@\n use anyhow::Context;\n use base64::Engine;\n use base64::engine::general_purpose::URL_SAFE_NO_PAD;\n-use codex_core::auth::AuthCredentialsStoreMode;\n-use codex_core::auth::load_auth_dot_json;\n use codex_login::ServerOptions;\n+use codex_login::auth::AuthCredentialsStoreMode;\n+use codex_login::auth::load_auth_dot_json;\n use codex_login::run_device_code_login;\n use serde_json::json;\n use std::sync::Arc;", - "path": "codex-rs/login/tests/suite/device_code_login.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -7,8 +7,8 @@ use std::time::Duration;\n \n use anyhow::Result;\n use base64::Engine;\n-use codex_core::auth::AuthCredentialsStoreMode;\n use codex_login::ServerOptions;\n+use codex_login::auth::AuthCredentialsStoreMode;\n use codex_login::run_login_server;\n use core_test_support::skip_if_no_network;\n use tempfile::tempdir;", - "path": "codex-rs/login/tests/suite/login_server_e2e.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -24,6 +24,7 @@ chrono = { workspace = true }\n codex-utils-absolute-path = { workspace = true }\n codex-utils-string = { workspace = true }\n codex-api = { workspace = true }\n+codex-app-server-protocol = { workspace = true }\n codex-protocol = { workspace = true }\n eventsource-stream = { workspace = true }\n gethostname = { workspace = true }", - "path": "codex-rs/otel/Cargo.toml", - "status": "modified" - }, - { - "additions": 11, - "deletions": 1, - "patch_excerpt": "@@ -36,13 +36,23 @@ pub enum ToolDecisionSource {\n User,\n }\n \n-/// Maps to core AuthMode to avoid a circular dependency on codex-core.\n+/// Maps to API/auth `AuthMode` to avoid a circular dependency on codex-core.\n #[derive(Debug, Clone, Copy, PartialEq, Eq, Display)]\n pub enum TelemetryAuthMode {\n ApiKey,\n Chatgpt,\n }\n \n+impl From for TelemetryAuthMode {\n+ fn from(mode: codex_app_server_protocol::AuthMode) -> Self {\n+ match mode {\n+ codex_app_server_protocol::AuthMode::ApiKey => Self::ApiKey,\n+ codex_app_server_protocol::AuthMode::Chatgpt\n+ | codex_app_server_protocol::AuthMode::ChatgptAuthTokens => Self::Chatgpt,\n+ }\n+ }\n+}\n+\n /// Start a metrics timer using the globally installed metrics client.\n pub fn start_global_timer(name: &str, tags: &[(&str, &str)]) -> MetricsResult {\n ...", - "path": "codex-rs/otel/src/lib.rs", - "status": "modified" - }, - { - "additions": 7, - "deletions": 1, - "patch_excerpt": "@@ -13,6 +13,7 @@ use codex_core::CodexAuth;\n use codex_core::INTERACTIVE_SESSION_SOURCES;\n use codex_core::RolloutRecorder;\n use codex_core::ThreadSortKey;\n+use codex_core::auth::AuthConfig;\n use codex_core::auth::AuthMode;\n use codex_core::auth::enforce_login_restrictions;\n use codex_core::check_execpolicy_for_warnings;\n@@ -454,7 +455,12 @@ pub async fn run_main(\n }\n \n #[allow(clippy::print_stderr)]\n- if let Err(err) = enforce_login_restrictions(&config) {\n+ if let Err(err) = enforce_login_restrictions(&AuthConfig {\n+ codex_home: config.codex_home.clone(),\n+ auth_credentials_store_mode: config.cli_auth_credentials_store_mode,\n+ forced_login_method: config.forced_login_method,\n+ forced_chatgpt_workspace_id: config.forced_chatgpt_workspace_id.clone(),\n+ }) {\n eprintln!(\"{err}\");\n std::process::exit(1);\n }", - "path": "codex-rs/tui/src/lib.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -92,7 +92,7 @@ pub(crate) fn compose_account_display(\n \n match auth.auth_mode() {\n CoreAuthMode::ApiKey => Some(StatusAccountDisplay::ApiKey),\n- CoreAuthMode::Chatgpt => {\n+ CoreAuthMode::Chatgpt | CoreAuthMode::ChatgptAuthTokens => {\n let email = auth.get_account_email();\n let plan = plan\n .map(|plan_type| title_case(format!(\"{plan_type:?}\").as_str()))", - "path": "codex-rs/tui/src/status/helpers.rs", - "status": "modified" - }, - { - "additions": 7, - "deletions": 1, - "patch_excerpt": "@@ -21,6 +21,7 @@ use codex_app_server_protocol::ThreadListParams;\n use codex_app_server_protocol::ThreadSortKey as AppServerThreadSortKey;\n use codex_app_server_protocol::ThreadSourceKind;\n use codex_cloud_requirements::cloud_requirements_loader_for_storage;\n+use codex_core::auth::AuthConfig;\n use codex_core::auth::enforce_login_restrictions;\n use codex_core::check_execpolicy_for_warnings;\n use codex_core::config::Config;\n@@ -777,7 +778,12 @@ pub async fn run_main(\n \n if matches!(app_server_target, AppServerTarget::Embedded) {\n #[allow(clippy::print_stderr)]\n- if let Err(err) = enforce_login_restrictions(&config) {\n+ if let Err(err) = enforce_login_restrictions(&AuthConfig {\n+ codex_home: config.codex_home.clone(),\n+ auth_credentials_store_mode: config.cli_auth_credentials_store_mode,\n+ forced_login_method: config.forced_login_m...", - "path": "codex-rs/tui_app_server/src/lib.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -70,9 +70,9 @@ mod tests {\n use chrono::Utc;\n use codex_app_server_protocol::AuthMode;\n use codex_core::auth::AuthDotJson;\n- use codex_core::auth::login_with_chatgpt_auth_tokens;\n use codex_core::auth::save_auth;\n use codex_core::token_data::TokenData;\n+ use codex_login::auth::login_with_chatgpt_auth_tokens;\n use pretty_assertions::assert_eq;\n use serde::Serialize;\n use serde_json::json;", - "path": "codex-rs/tui_app_server/src/local_chatgpt_auth.rs", - "status": "modified" - } - ], - "linked_issues": [ - "#15216", - "#15150" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "- Move the auth implementation and token data into codex-login.\n- Keep codex-core re-exporting that surface from codex-login for existing callers.", - "labels": [], - "merged_at": "2026-03-20T01:58:18Z", - "number": 15150, - "state": "merged", - "title": "Move auth code into login crate", - "url": "https://github.com/openai/codex/pull/15150" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15154.json b/artifacts/github/bundles/openai-codex-pr-15154.json deleted file mode 100644 index e083660..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15154.json +++ /dev/null @@ -1,142 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "won-openai", - "committed_at": "2026-03-19T07:24:22Z", - "message": "adding full imagepath to tui", - "sha": "51153bef7ffce98b014a916904f2ed8795376f29", - "url": "https://github.com/openai/codex/commit/51153bef7ffce98b014a916904f2ed8795376f29" - }, - { - "author": "won-openai", - "committed_at": "2026-03-19T19:45:41Z", - "message": "added snapshots", - "sha": "8ca6ef3d5ebb717d632375e99b3f6965489a8480", - "url": "https://github.com/openai/codex/commit/8ca6ef3d5ebb717d632375e99b3f6965489a8480" - }, - { - "author": "won-openai", - "committed_at": "2026-03-19T20:04:24Z", - "message": "flaky code", - "sha": "f27a4e92151050f703a0e375d50af7b1b5b08eeb", - "url": "https://github.com/openai/codex/commit/f27a4e92151050f703a0e375d50af7b1b5b08eeb" - }, - { - "author": "won-openai", - "committed_at": "2026-03-19T20:53:01Z", - "message": "Merge branch 'main' into tui_image_link", - "sha": "809df3f7655b04598551ebd48cfac24082a9ccca", - "url": "https://github.com/openai/codex/commit/809df3f7655b04598551ebd48cfac24082a9ccca" - }, - { - "author": "won-openai", - "committed_at": "2026-03-19T20:57:25Z", - "message": "flaky syntax", - "sha": "436f73bf550d07d8ca9f7d314c6b27212573ce98", - "url": "https://github.com/openai/codex/commit/436f73bf550d07d8ca9f7d314c6b27212573ce98" - }, - { - "author": "won-openai", - "committed_at": "2026-03-19T20:59:01Z", - "message": "flaky syntax", - "sha": "49a76f44cc103e3b1eb707fe8f8d078ace4dc6d0", - "url": "https://github.com/openai/codex/commit/49a76f44cc103e3b1eb707fe8f8d078ace4dc6d0" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "TUI", - "VSC" - ], - "files": [ - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -36,7 +36,7 @@ pub(crate) struct ExecServerFileSystem {\n impl Default for ExecServerFileSystem {\n fn default() -> Self {\n Self {\n- file_system: Arc::new(Environment.get_filesystem()),\n+ file_system: Arc::new(Environment::default().get_filesystem()),\n }\n }\n }", - "path": "codex-rs/exec-server/src/server/filesystem.rs", - "status": "modified" - }, - { - "additions": 7, - "deletions": 5, - "patch_excerpt": "@@ -37,6 +37,8 @@ use std::sync::atomic::Ordering;\n use std::time::Duration;\n use std::time::Instant;\n \n+use url::Url;\n+\n use self::realtime::PendingSteerCompareKey;\n use crate::app_event::RealtimeAudioDeviceKind;\n #[cfg(not(target_os = \"linux\"))]\n@@ -2761,15 +2763,15 @@ impl ChatWidget {\n \n fn on_image_generation_end(&mut self, event: ImageGenerationEndEvent) {\n self.flush_answer_stream_with_separator();\n- let saved_to = event.saved_path.as_deref().and_then(|saved_path| {\n- std::path::Path::new(saved_path)\n- .parent()\n- .map(|parent| parent.display().to_string())\n+ let saved_path = event.saved_path.map(|saved_path| {\n+ Url::from_file_path(Path::new(&saved_path))\n+ .map(|url| url.to_string())\n+ .unwrap_or(saved_path)\n });\n self.add_to_history(history_cell::new_image...", - "path": "codex-rs/tui/src/chatwidget.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -5,4 +5,4 @@ expression: combined\n ---\n \u2022 Generated Image:\n \u2514 A tiny blue square\n- \u2514 Saved to: /tmp\n+ \u2514 Saved to: file:///tmp/ig-1.png", - "path": "codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__image_generation_call_history_snapshot.snap", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -6509,7 +6509,7 @@ async fn image_generation_call_adds_history_cell() {\n status: \"completed\".into(),\n revised_prompt: Some(\"A tiny blue square\".into()),\n result: \"Zm9v\".into(),\n- saved_path: Some(\"/tmp/ig-1.png\".into()),\n+ saved_path: Some(\"file:///tmp/ig-1.png\".into()),\n }),\n });", - "path": "codex-rs/tui/src/chatwidget/tests.rs", - "status": "modified" - }, - { - "additions": 22, - "deletions": 3, - "patch_excerpt": "@@ -2310,16 +2310,16 @@ pub(crate) fn new_view_image_tool_call(path: PathBuf, cwd: &Path) -> PlainHistor\n pub(crate) fn new_image_generation_call(\n call_id: String,\n revised_prompt: Option,\n- saved_to: Option,\n+ saved_path: Option,\n ) -> PlainHistoryCell {\n let detail = revised_prompt.unwrap_or_else(|| call_id.clone());\n \n let mut lines: Vec> = vec![\n vec![\"\u2022 \".dim(), \"Generated Image:\".bold()].into(),\n vec![\" \u2514 \".dim(), detail.dim()].into(),\n ];\n- if let Some(saved_to) = saved_to {\n- lines.push(vec![\" \u2514 \".dim(), format!(\"Saved to: {saved_to}\").dim()].into());\n+ if let Some(saved_path) = saved_path {\n+ lines.push(vec![\" \u2514 \".dim(), \"Saved to: \".dim(), saved_path.into()].into());\n }\n \n PlainHistoryCell { lines }\n@@ -2624,6 +2624,25 @@ mod tests {\n .expect(\"resource link co...", - "path": "codex-rs/tui/src/history_cell.rs", - "status": "modified" - }, - { - "additions": 7, - "deletions": 5, - "patch_excerpt": "@@ -37,6 +37,8 @@ use std::sync::atomic::Ordering;\n use std::time::Duration;\n use std::time::Instant;\n \n+use url::Url;\n+\n use self::realtime::PendingSteerCompareKey;\n use crate::app_command::AppCommand;\n use crate::app_event::RealtimeAudioDeviceKind;\n@@ -3133,15 +3135,15 @@ impl ChatWidget {\n \n fn on_image_generation_end(&mut self, event: ImageGenerationEndEvent) {\n self.flush_answer_stream_with_separator();\n- let saved_to = event.saved_path.as_deref().and_then(|saved_path| {\n- std::path::Path::new(saved_path)\n- .parent()\n- .map(|parent| parent.display().to_string())\n+ let saved_path = event.saved_path.map(|saved_path| {\n+ Url::from_file_path(Path::new(&saved_path))\n+ .map(|url| url.to_string())\n+ .unwrap_or(saved_path)\n });\n self.add_to_history(history_cell::new_im...", - "path": "codex-rs/tui_app_server/src/chatwidget.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -5,4 +5,4 @@ expression: combined\n ---\n \u2022 Generated Image:\n \u2514 A tiny blue square\n- \u2514 Saved to: /tmp\n+ \u2514 Saved to: file:///tmp/ig-1.png", - "path": "codex-rs/tui_app_server/src/chatwidget/snapshots/codex_tui__chatwidget__tests__image_generation_call_history_snapshot.snap", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -4,4 +4,4 @@ expression: combined\n ---\n \u2022 Generated Image:\n \u2514 A tiny blue square\n- \u2514 Saved to: /tmp\n+ \u2514 Saved to: file:///tmp/ig-1.png", - "path": "codex-rs/tui_app_server/src/chatwidget/snapshots/codex_tui_app_server__chatwidget__tests__image_generation_call_history_snapshot.snap", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -7097,7 +7097,7 @@ async fn image_generation_call_adds_history_cell() {\n status: \"completed\".into(),\n revised_prompt: Some(\"A tiny blue square\".into()),\n result: \"Zm9v\".into(),\n- saved_path: Some(\"/tmp/ig-1.png\".into()),\n+ saved_path: Some(\"file:///tmp/ig-1.png\".into()),\n }),\n });", - "path": "codex-rs/tui_app_server/src/chatwidget/tests.rs", - "status": "modified" - }, - { - "additions": 22, - "deletions": 3, - "patch_excerpt": "@@ -2538,16 +2538,16 @@ pub(crate) fn new_view_image_tool_call(path: PathBuf, cwd: &Path) -> PlainHistor\n pub(crate) fn new_image_generation_call(\n call_id: String,\n revised_prompt: Option,\n- saved_to: Option,\n+ saved_path: Option,\n ) -> PlainHistoryCell {\n let detail = revised_prompt.unwrap_or_else(|| call_id.clone());\n \n let mut lines: Vec> = vec![\n vec![\"\u2022 \".dim(), \"Generated Image:\".bold()].into(),\n vec![\" \u2514 \".dim(), detail.dim()].into(),\n ];\n- if let Some(saved_to) = saved_to {\n- lines.push(vec![\" \u2514 \".dim(), format!(\"Saved to: {saved_to}\").dim()].into());\n+ if let Some(saved_path) = saved_path {\n+ lines.push(vec![\" \u2514 \".dim(), \"Saved to: \".dim(), saved_path.into()].into());\n }\n \n PlainHistoryCell { lines }\n@@ -2853,6 +2853,25 @@ mod tests {\n .expect(\"resource link co...", - "path": "codex-rs/tui_app_server/src/history_cell.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "adding full path to TUI so image is open-able in the TUI after being generated. LImited to VSCode Terminal for now. ", - "labels": [], - "merged_at": "2026-03-19T21:29:23Z", - "number": 15154, - "state": "merged", - "title": "adding full imagepath to tui", - "url": "https://github.com/openai/codex/pull/15154" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15163.json b/artifacts/github/bundles/openai-codex-pr-15163.json deleted file mode 100644 index 7b4a809..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15163.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "jif-oai", - "committed_at": "2026-03-19T10:27:23Z", - "message": "fix: case where agent is already closed", - "sha": "17fb3262d4c25605c97b28298ef7dcacaca7d400", - "url": "https://github.com/openai/codex/commit/17fb3262d4c25605c97b28298ef7dcacaca7d400" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [], - "files": [ - { - "additions": 9, - "deletions": 3, - "patch_excerpt": "@@ -444,11 +444,17 @@ impl AgentControl {\n /// persisted spawn-edge state.\n pub(crate) async fn shutdown_live_agent(&self, agent_id: ThreadId) -> CodexResult {\n let state = self.upgrade()?;\n- if let Ok(thread) = state.get_thread(agent_id).await {\n+ let result = if let Ok(thread) = state.get_thread(agent_id).await {\n thread.codex.session.ensure_rollout_materialized().await;\n thread.codex.session.flush_rollout().await;\n- }\n- let result = state.send_op(agent_id, Op::Shutdown {}).await;\n+ if matches!(thread.agent_status().await, AgentStatus::Shutdown) {\n+ Ok(String::new())\n+ } else {\n+ state.send_op(agent_id, Op::Shutdown {}).await\n+ }\n+ } else {\n+ state.send_op(agent_id, Op::Shutdown {}).await\n+ };\n let _ = state.remove_t...", - "path": "codex-rs/core/src/agent/control.rs", - "status": "modified" - }, - { - "additions": 7, - "deletions": 11, - "patch_excerpt": "@@ -68,17 +68,13 @@ impl ToolHandler for Handler {\n return Err(collab_agent_error(agent_id, err));\n }\n };\n- let result = if !matches!(status, AgentStatus::Shutdown) {\n- session\n- .services\n- .agent_control\n- .close_agent(agent_id)\n- .await\n- .map_err(|err| collab_agent_error(agent_id, err))\n- .map(|_| ())\n- } else {\n- Ok(())\n- };\n+ let result = session\n+ .services\n+ .agent_control\n+ .close_agent(agent_id)\n+ .await\n+ .map_err(|err| collab_agent_error(agent_id, err))\n+ .map(|_| ());\n session\n .send_event(\n &turn,", - "path": "codex-rs/core/src/tools/handlers/multi_agents/close_agent.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "", - "labels": [], - "merged_at": "2026-03-19T12:12:51Z", - "number": 15163, - "state": "merged", - "title": "fix: case where agent is already closed", - "url": "https://github.com/openai/codex/pull/15163" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15175.json b/artifacts/github/bundles/openai-codex-pr-15175.json deleted file mode 100644 index 38b6c8d..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15175.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "jif-oai", - "committed_at": "2026-03-19T14:26:29Z", - "message": "chore: morpheus does not generate memories", - "sha": "db01ae2c9e606f9ef446515c2161a054a82e3685", - "url": "https://github.com/openai/codex/commit/db01ae2c9e606f9ef446515c2161a054a82e3685" - }, - { - "author": "jif-oai", - "committed_at": "2026-03-19T15:03:05Z", - "message": "nit fix", - "sha": "9c1838ef3a5606e38796619a6bcb2c8a1dfc66ff", - "url": "https://github.com/openai/codex/commit/9c1838ef3a5606e38796619a6bcb2c8a1dfc66ff" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [], - "files": [ - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -267,6 +267,8 @@ mod agent {\n let mut agent_config = config.as_ref().clone();\n \n agent_config.cwd = root;\n+ // Consolidation threads must never feed back into phase-1 memory generation.\n+ agent_config.memories.generate_memories = false;\n // Approval policy\n agent_config.permissions.approval_policy = Constrained::allow_only(AskForApproval::Never);\n // Consolidation runs as an internal sub-agent and must not recursively delegate.", - "path": "codex-rs/core/src/memories/phase2.rs", - "status": "modified" - }, - { - "additions": 31, - "deletions": 1, - "patch_excerpt": "@@ -437,6 +437,7 @@ mod phase2 {\n use codex_state::ThreadMetadataBuilder;\n use std::path::PathBuf;\n use std::sync::Arc;\n+ use std::time::Duration;\n use tempfile::TempDir;\n \n fn stage1_output_with_source_updated_at(source_updated_at: i64) -> Stage1Output {\n@@ -663,9 +664,10 @@ mod phase2 {\n pretty_assertions::assert_eq!(user_input_ops, 1);\n let thread_ids = harness.manager.list_thread_ids().await;\n pretty_assertions::assert_eq!(thread_ids.len(), 1);\n+ let thread_id = thread_ids[0];\n let subagent = harness\n .manager\n- .get_thread(thread_ids[0])\n+ .get_thread(thread_id)\n .await\n .expect(\"get consolidation thread\");\n let config_snapshot = subagent.config_snapshot().await;\n@@ -682,6 +684,34 @@ mod phase2 {\n }\n other => panic!(\"unexpected sandb...", - "path": "codex-rs/core/src/memories/tests.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "For obvious reasons", - "labels": [], - "merged_at": "2026-03-19T15:48:28Z", - "number": 15175, - "state": "merged", - "title": "chore: morpheus does not generate memories", - "url": "https://github.com/openai/codex/pull/15175" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15180.json b/artifacts/github/bundles/openai-codex-pr-15180.json deleted file mode 100644 index 0157f5a..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15180.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "jif-oai", - "committed_at": "2026-03-19T15:21:22Z", - "message": "chore: add metrics for profile", - "sha": "959dc2a4d88ebe8caad796b05e5a76096ba20f2e", - "url": "https://github.com/openai/codex/commit/959dc2a4d88ebe8caad796b05e5a76096ba20f2e" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "API_CALL_COUNT_METRIC", - "API_CALL_DURATION_METRIC", - "PROFILE_USAGE_METRIC", - "RESPONSES_API_ENGINE_IAPI_TBT_DURATION_METRIC", - "RESPONSES_API_ENGINE_IAPI_TTFT_DURATION_METRIC", - "RESPONSES_API_ENGINE_SERVICE_TBT_DURATION_METRIC", - "TURN_TTFM_DURATION_METRIC", - "TURN_NETWORK_PROXY_METRIC", - "TURN_TOOL_CALL_METRIC", - "TURN_TOKEN_USAGE_METRIC", - "STARTUP_PREWARM_DURATION_METRIC" - ], - "files": [ - { - "additions": 4, - "deletions": 0, - "patch_excerpt": "@@ -9,6 +9,7 @@ use crate::metrics::MetricsError;\n use crate::metrics::Result as MetricsResult;\n use crate::metrics::names::API_CALL_COUNT_METRIC;\n use crate::metrics::names::API_CALL_DURATION_METRIC;\n+use crate::metrics::names::PROFILE_USAGE_METRIC;\n use crate::metrics::names::RESPONSES_API_ENGINE_IAPI_TBT_DURATION_METRIC;\n use crate::metrics::names::RESPONSES_API_ENGINE_IAPI_TTFT_DURATION_METRIC;\n use crate::metrics::names::RESPONSES_API_ENGINE_SERVICE_TBT_DURATION_METRIC;\n@@ -321,6 +322,9 @@ impl SessionTelemetry {\n mcp_servers: Vec<&str>,\n active_profile: Option,\n ) {\n+ if active_profile.is_some() {\n+ self.counter(PROFILE_USAGE_METRIC, /*inc*/ 1, &[]);\n+ }\n log_and_trace_event!(\n self,\n common: {", - "path": "codex-rs/otel/src/events/session_telemetry.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -25,6 +25,7 @@ pub const TURN_TTFM_DURATION_METRIC: &str = \"codex.turn.ttfm.duration_ms\";\n pub const TURN_NETWORK_PROXY_METRIC: &str = \"codex.turn.network_proxy\";\n pub const TURN_TOOL_CALL_METRIC: &str = \"codex.turn.tool.call\";\n pub const TURN_TOKEN_USAGE_METRIC: &str = \"codex.turn.token_usage\";\n+pub const PROFILE_USAGE_METRIC: &str = \"codex.profile.usage\";\n /// Total runtime of a startup prewarm attempt until it completes, tagged by final status.\n pub const STARTUP_PREWARM_DURATION_METRIC: &str = \"codex.startup_prewarm.duration_ms\";\n /// Age of the startup prewarm attempt when the first real turn resolves it, tagged by outcome.", - "path": "codex-rs/otel/src/metrics/names.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "", - "labels": [], - "merged_at": "2026-03-19T15:48:02Z", - "number": 15180, - "state": "merged", - "title": "chore: add metrics for profile", - "url": "https://github.com/openai/codex/pull/15180" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15185.json b/artifacts/github/bundles/openai-codex-pr-15185.json deleted file mode 100644 index 8752791..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15185.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-19T04:40:32Z", - "message": "Revert \"Forward session and turn headers to MCP HTTP requests (#15011)\"", - "sha": "bab2a28f26223da6a3a6f61bb9656d2f8d6cc4ee", - "url": "https://github.com/openai/codex/commit/bab2a28f26223da6a3a6f61bb9656d2f8d6cc4ee" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-19T16:26:43Z", - "message": "Merge branch 'main' into revert-15011-nicholasclark/tool-call-task-headers", - "sha": "7df3c80f25f8fe27ad26e4788fa6336043079686", - "url": "https://github.com/openai/codex/commit/7df3c80f25f8fe27ad26e4788fa6336043079686" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "MCP", - "HTTP", - "X_CODEX_TURN_METADATA_HEADER", - "CODEX_APPS_MCP_SERVER_NAME", - "MCP_SANDBOX_STATE_CAPABILITY", - "WWW_AUTHENTICATE", - "HEADER_LAST_EVENT_ID", - "HEADER_SESSION_ID", - "NON_JSON_RESPONSE_BODY_PREVIEW_BYTES" - ], - "files": [ - { - "additions": 0, - "deletions": 41, - "patch_excerpt": "@@ -125,8 +125,6 @@ use futures::future::BoxFuture;\n use futures::future::Shared;\n use futures::prelude::*;\n use futures::stream::FuturesOrdered;\n-use reqwest::header::HeaderMap;\n-use reqwest::header::HeaderValue;\n use rmcp::model::ListResourceTemplatesResult;\n use rmcp::model::ListResourcesResult;\n use rmcp::model::PaginatedRequestParams;\n@@ -3952,45 +3950,6 @@ impl Session {\n .await\n }\n \n- pub(crate) async fn sync_mcp_request_headers_for_turn(&self, turn_context: &TurnContext) {\n- let mut request_headers = HeaderMap::new();\n- let session_id = self.conversation_id.to_string();\n- if let Ok(value) = HeaderValue::from_str(&session_id) {\n- request_headers.insert(\"session_id\", value.clone());\n- request_headers.insert(\"x-client-request-id\", value);\n- }\n- if let Some(turn_metadata) = turn_context.turn_metadata_state.cu...", - "path": "codex-rs/core/src/codex.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 33, - "patch_excerpt": "@@ -423,7 +423,6 @@ impl ManagedClient {\n #[derive(Clone)]\n struct AsyncManagedClient {\n client: Shared>>,\n- request_headers: Arc>>,\n startup_snapshot: Option>,\n startup_complete: Arc,\n tool_plugin_provenance: Arc,\n@@ -449,26 +448,17 @@ impl AsyncManagedClient {\n codex_apps_tools_cache_context.as_ref(),\n )\n .map(|tools| filter_tools(tools, &tool_filter));\n- let request_headers = Arc::new(StdMutex::new(None));\n let startup_tool_filter = tool_filter;\n let startup_complete = Arc::new(AtomicBool::new(false));\n let startup_complete_for_fut = Arc::clone(&startup_complete);\n- let request_headers_for_client = Arc::clone(&request_headers);\n let fut = asy...", - "path": "codex-rs/core/src/mcp_connection_manager.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 5, - "patch_excerpt": "@@ -4,7 +4,6 @@ use codex_protocol::protocol::McpAuthStatus;\n use rmcp::model::JsonObject;\n use std::collections::HashSet;\n use std::sync::Arc;\n-use std::sync::Mutex as StdMutex;\n use tempfile::tempdir;\n \n fn create_test_tool(server_name: &str, tool_name: &str) -> ToolInfo {\n@@ -414,7 +413,6 @@ async fn list_all_tools_uses_startup_snapshot_while_client_is_pending() {\n CODEX_APPS_MCP_SERVER_NAME.to_string(),\n AsyncManagedClient {\n client: pending_client,\n- request_headers: Arc::new(StdMutex::new(None)),\n startup_snapshot: Some(startup_tools),\n startup_complete: Arc::new(std::sync::atomic::AtomicBool::new(false)),\n tool_plugin_provenance: Arc::new(ToolPluginProvenance::default()),\n@@ -440,7 +438,6 @@ async fn list_all_tools_blocks_while_client_is_pending_without_startup_snapshot(\n CODEX_APPS_MCP_SERVER_NAME....", - "path": "codex-rs/core/src/mcp_connection_manager_tests.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 6, - "patch_excerpt": "@@ -153,8 +153,6 @@ impl Session {\n ) {\n self.abort_all_tasks(TurnAbortReason::Replaced).await;\n self.clear_connector_selection().await;\n- self.sync_mcp_request_headers_for_turn(turn_context.as_ref())\n- .await;\n \n let task: Arc = Arc::new(task);\n let task_kind = task.kind();\n@@ -235,7 +233,6 @@ impl Session {\n // in-flight approval wait can surface as a model-visible rejection before TurnAborted.\n active_turn.clear_pending().await;\n }\n- self.clear_mcp_request_headers().await;\n }\n \n pub async fn on_task_finished(\n@@ -265,9 +262,6 @@ impl Session {\n *active = None;\n }\n drop(active);\n- if should_clear_active_turn {\n- self.clear_mcp_request_headers().await;\n- }\n if !pending_input.is_empty() {\n for pendin...", - "path": "codex-rs/core/src/tasks/mod.rs", - "status": "modified" - }, - { - "additions": 10, - "deletions": 69, - "patch_excerpt": "@@ -5,7 +5,6 @@ use std::io;\n use std::path::PathBuf;\n use std::process::Stdio;\n use std::sync::Arc;\n-use std::sync::Mutex as StdMutex;\n use std::time::Duration;\n \n use anyhow::Result;\n@@ -23,7 +22,6 @@ use reqwest::header::HeaderMap;\n use reqwest::header::WWW_AUTHENTICATE;\n use rmcp::model::CallToolRequestParams;\n use rmcp::model::CallToolResult;\n-use rmcp::model::ClientJsonRpcMessage;\n use rmcp::model::ClientNotification;\n use rmcp::model::ClientRequest;\n use rmcp::model::CreateElicitationRequestParams;\n@@ -85,45 +83,14 @@ const HEADER_LAST_EVENT_ID: &str = \"Last-Event-Id\";\n const HEADER_SESSION_ID: &str = \"Mcp-Session-Id\";\n const NON_JSON_RESPONSE_BODY_PREVIEW_BYTES: usize = 8_192;\n \n-fn message_uses_request_scoped_headers(message: &ClientJsonRpcMessage) -> bool {\n- matches!(\n- message,\n- ClientJsonRpcMessage::Request(request)\n- if request.request.method() ...", - "path": "codex-rs/rmcp-client/src/rmcp_client.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 3, - "patch_excerpt": "@@ -1,7 +1,5 @@\n use std::net::TcpListener;\n use std::path::PathBuf;\n-use std::sync::Arc;\n-use std::sync::Mutex as StdMutex;\n use std::time::Duration;\n use std::time::Instant;\n \n@@ -79,7 +77,6 @@ async fn create_client(base_url: &str) -> anyhow::Result {\n None,\n None,\n OAuthCredentialsStoreMode::File,\n- Arc::new(StdMutex::new(None)),\n )\n .await?;", - "path": "codex-rs/rmcp-client/tests/streamable_http_recovery.rs", - "status": "modified" - } - ], - "linked_issues": [ - "openai/codex#15011", - "#15011" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "Reverts openai/codex#15011\r\n\r\nCodex merged by mistake before feedback applied", - "labels": [], - "merged_at": "2026-03-19T17:38:54Z", - "number": 15185, - "state": "merged", - "title": "Revert \"Forward session and turn headers to MCP HTTP requests\"", - "url": "https://github.com/openai/codex/pull/15185" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15190.json b/artifacts/github/bundles/openai-codex-pr-15190.json deleted file mode 100644 index 96c5aaa..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15190.json +++ /dev/null @@ -1,191 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-19T04:40:32Z", - "message": "Revert \"Forward session and turn headers to MCP HTTP requests (#15011)\"", - "sha": "bab2a28f26223da6a3a6f61bb9656d2f8d6cc4ee", - "url": "https://github.com/openai/codex/commit/bab2a28f26223da6a3a6f61bb9656d2f8d6cc4ee" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-19T16:26:43Z", - "message": "Merge branch 'main' into revert-15011-nicholasclark/tool-call-task-headers", - "sha": "7df3c80f25f8fe27ad26e4788fa6336043079686", - "url": "https://github.com/openai/codex/commit/7df3c80f25f8fe27ad26e4788fa6336043079686" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-19T16:29:03Z", - "message": "Plumb MCP turn metadata through _meta", - "sha": "9124fe9cbc405bde42d244d27ddf31fdbc6be106", - "url": "https://github.com/openai/codex/commit/9124fe9cbc405bde42d244d27ddf31fdbc6be106" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-19T17:31:51Z", - "message": "Merge RMCP tool call _meta entries", - "sha": "edc8e17c5c30439f70575a2aca479996762f5b84", - "url": "https://github.com/openai/codex/commit/edc8e17c5c30439f70575a2aca479996762f5b84" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-19T17:46:08Z", - "message": "Merge branch 'main' into nicholasclark/tool-call-task-headers-_meta", - "sha": "fb816b13221f1bb7ef8dbbef8d8b54c32a12f09a", - "url": "https://github.com/openai/codex/commit/fb816b13221f1bb7ef8dbbef8d8b54c32a12f09a" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-19T18:18:05Z", - "message": "Include session id in MCP turn metadata", - "sha": "e84f54294aa2f05fffd616e28293070a58383e02", - "url": "https://github.com/openai/codex/commit/e84f54294aa2f05fffd616e28293070a58383e02" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-19T18:35:21Z", - "message": "Skip non-JSON apps requests in search tool test", - "sha": "98f97df37fdbc59afb69e9acad434475017951a8", - "url": "https://github.com/openai/codex/commit/98f97df37fdbc59afb69e9acad434475017951a8" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-19T18:50:06Z", - "message": "Simplify RMCP tool call _meta plumbing", - "sha": "88653c9245444c06747da275d812ec04998a82e9", - "url": "https://github.com/openai/codex/commit/88653c9245444c06747da275d812ec04998a82e9" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-19T18:58:51Z", - "message": "Restore RMCP meta local variable name", - "sha": "db3ddeae35b13a91d19788da3a23ad5ea0984782", - "url": "https://github.com/openai/codex/commit/db3ddeae35b13a91d19788da3a23ad5ea0984782" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-19T19:38:16Z", - "message": "Restore explicit RMCP tools/call request path", - "sha": "cbd07008379354b09034fd9ee1eec591f83f0a05", - "url": "https://github.com/openai/codex/commit/cbd07008379354b09034fd9ee1eec591f83f0a05" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-19T19:45:36Z", - "message": "Merge branch 'main' into nicholasclark/tool-call-task-headers-_meta", - "sha": "b679c7596bd330ab5d66658d6f0d03c47a1670d8", - "url": "https://github.com/openai/codex/commit/b679c7596bd330ab5d66658d6f0d03c47a1670d8" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-19T20:32:18Z", - "message": "Merge branch 'main' into nicholasclark/tool-call-task-headers-_meta", - "sha": "6bf3961d66fff5ae75283374b1d8fea83c045162", - "url": "https://github.com/openai/codex/commit/6bf3961d66fff5ae75283374b1d8fea83c045162" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-19T21:15:56Z", - "message": "Merge branch 'main' into nicholasclark/tool-call-task-headers-_meta", - "sha": "656d2f57246876df9a64283e1d071478fa7e6b05", - "url": "https://github.com/openai/codex/commit/656d2f57246876df9a64283e1d071478fa7e6b05" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-19T21:33:47Z", - "message": "Merge branch 'main' into nicholasclark/tool-call-task-headers-_meta", - "sha": "5305a38085d1f9bff1266b69f77a4c02cc7a6c3a", - "url": "https://github.com/openai/codex/commit/5305a38085d1f9bff1266b69f77a4c02cc7a6c3a" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "MCP", - "HTTP", - "RMCP", - "MCP_TOOL_CODEX_APPS_META_KEY", - "CODEX_APPS_MCP_SERVER_NAME", - "X_CODEX_TURN_METADATA_HEADER", - "CALENDAR_CREATE_EVENT_RESOURCE_URI" - ], - "files": [ - { - "additions": 4, - "deletions": 0, - "patch_excerpt": "@@ -1286,6 +1286,7 @@ impl Session {\n \n #[allow(clippy::too_many_arguments)]\n fn make_turn_context(\n+ conversation_id: ThreadId,\n auth_manager: Option>,\n session_telemetry: &SessionTelemetry,\n provider: ModelProviderInfo,\n@@ -1336,6 +1337,7 @@ impl Session {\n \n let cwd = session_configuration.cwd.clone();\n let turn_metadata_state = Arc::new(TurnMetadataState::new(\n+ conversation_id.to_string(),\n sub_id.clone(),\n cwd.clone(),\n session_configuration.sandbox_policy.get(),\n@@ -2394,6 +2396,7 @@ impl Session {\n .skills_for_config(&per_turn_config),\n );\n let mut turn_context: TurnContext = Self::make_turn_context(\n+ self.conversation_id,\n Some(Arc::clone(&self.services.auth_manager)),\n &self.services.session_te...", - "path": "codex-rs/core/src/codex.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -2517,6 +2517,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {\n \n let skills_outcome = Arc::new(services.skills_manager.skills_for_config(&per_turn_config));\n let turn_context = Session::make_turn_context(\n+ conversation_id,\n Some(Arc::clone(&auth_manager)),\n &session_telemetry,\n session_configuration.provider.clone(),\n@@ -3315,6 +3316,7 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(\n \n let skills_outcome = Arc::new(services.skills_manager.skills_for_config(&per_turn_config));\n let turn_context = Arc::new(Session::make_turn_context(\n+ conversation_id,\n Some(Arc::clone(&auth_manager)),\n &session_telemetry,\n session_configuration.provider.clone(),", - "path": "codex-rs/core/src/codex_tests.rs", - "status": "modified" - }, - { - "additions": 20, - "deletions": 7, - "patch_excerpt": "@@ -119,7 +119,8 @@ pub(crate) async fn handle_mcp_tool_call(\n );\n return CallToolResult::from_result(result);\n }\n- let request_meta = build_mcp_tool_call_request_meta(&server, metadata.as_ref());\n+ let request_meta =\n+ build_mcp_tool_call_request_meta(turn_context.as_ref(), &server, metadata.as_ref());\n \n let tool_call_begin_event = EventMsg::McpToolCallBegin(McpToolCallBeginEvent {\n call_id: call_id.clone(),\n@@ -390,18 +391,30 @@ pub(crate) struct McpToolApprovalMetadata {\n const MCP_TOOL_CODEX_APPS_META_KEY: &str = \"_codex_apps\";\n \n fn build_mcp_tool_call_request_meta(\n+ turn_context: &TurnContext,\n server: &str,\n metadata: Option<&McpToolApprovalMetadata>,\n ) -> Option {\n- if server != CODEX_APPS_MCP_SERVER_NAME {\n- return None;\n+ let mut request_meta = serde_json::Map::new();\n+\n+ if let Some(t...", - "path": "codex-rs/core/src/mcp_tool_call.rs", - "status": "modified" - }, - { - "additions": 39, - "deletions": 3, - "patch_excerpt": "@@ -439,8 +439,39 @@ fn sanitize_mcp_tool_result_for_model_preserves_image_when_supported() {\n assert_eq!(got, original);\n }\n \n-#[test]\n-fn codex_apps_tool_call_request_meta_includes_codex_apps_meta() {\n+#[tokio::test]\n+async fn mcp_tool_call_request_meta_includes_turn_metadata_for_custom_server() {\n+ let (_, turn_context) = make_session_and_context().await;\n+ let expected_turn_metadata = serde_json::from_str::(\n+ &turn_context\n+ .turn_metadata_state\n+ .current_header_value()\n+ .expect(\"turn metadata header\"),\n+ )\n+ .expect(\"turn metadata json\");\n+\n+ let meta =\n+ build_mcp_tool_call_request_meta(&turn_context, \"custom_server\", /*metadata*/ None)\n+ .expect(\"custom servers should receive turn metadata\");\n+\n+ assert_eq!(\n+ meta,\n+ serde_json::json!({\n+ crate::X_CODEX_T...", - "path": "codex-rs/core/src/mcp_tool_call_tests.rs", - "status": "modified" - }, - { - "additions": 13, - "deletions": 0, - "patch_excerpt": "@@ -53,6 +53,8 @@ impl From for TurnMetadataWorkspace {\n \n #[derive(Clone, Debug, Serialize, Default)]\n pub(crate) struct TurnMetadataBag {\n+ #[serde(default, skip_serializing_if = \"Option::is_none\")]\n+ session_id: Option,\n #[serde(default, skip_serializing_if = \"Option::is_none\")]\n turn_id: Option,\n #[serde(default, skip_serializing_if = \"BTreeMap::is_empty\")]\n@@ -68,6 +70,7 @@ impl TurnMetadataBag {\n }\n \n fn build_turn_metadata_bag(\n+ session_id: Option,\n turn_id: Option,\n sandbox: Option,\n repo_root: Option,\n@@ -81,6 +84,7 @@ fn build_turn_metadata_bag(\n }\n \n TurnMetadataBag {\n+ session_id,\n turn_id,\n workspaces,\n sandbox,\n@@ -104,6 +108,7 @@ pub async fn build_turn_metadata_header(cwd: &Path, sandbox: Option<&str>) -> Op\n }\n \n build_tur...", - "path": "codex-rs/core/src/turn_metadata.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 0, - "patch_excerpt": "@@ -67,6 +67,7 @@ fn turn_metadata_state_uses_platform_sandbox_tag() {\n let sandbox_policy = SandboxPolicy::new_read_only_policy();\n \n let state = TurnMetadataState::new(\n+ \"session-a\".to_string(),\n \"turn-a\".to_string(),\n cwd,\n &sandbox_policy,\n@@ -76,7 +77,9 @@ fn turn_metadata_state_uses_platform_sandbox_tag() {\n let header = state.current_header_value().expect(\"header\");\n let json: Value = serde_json::from_str(&header).expect(\"json\");\n let sandbox_name = json.get(\"sandbox\").and_then(Value::as_str);\n+ let session_id = json.get(\"session_id\").and_then(Value::as_str);\n \n let expected_sandbox = sandbox_tag(&sandbox_policy, WindowsSandboxLevel::Disabled);\n assert_eq!(sandbox_name, Some(expected_sandbox));\n+ assert_eq!(session_id, Some(\"session-a\"));\n }", - "path": "codex-rs/core/src/turn_metadata_tests.rs", - "status": "modified" - }, - { - "additions": 33, - "deletions": 0, - "patch_excerpt": "@@ -424,6 +424,39 @@ async fn tool_search_returns_deferred_tools_without_follow_up_tool_injection() -\n let requests = mock.requests();\n assert_eq!(requests.len(), 3);\n \n+ let apps_tool_call = server\n+ .received_requests()\n+ .await\n+ .unwrap_or_default()\n+ .into_iter()\n+ .find_map(|request| {\n+ let body: Value = serde_json::from_slice(&request.body).ok()?;\n+ (request.url.path() == \"/api/codex/apps\"\n+ && body.get(\"method\").and_then(Value::as_str) == Some(\"tools/call\"))\n+ .then_some(body)\n+ })\n+ .expect(\"apps tools/call request should be recorded\");\n+\n+ assert_eq!(\n+ apps_tool_call.pointer(\"/params/_meta/_codex_apps\"),\n+ Some(&json!({\n+ \"resource_uri\": CALENDAR_CREATE_EVENT_RESOURCE_URI,\n+ \"contains_mcp_source\": true,\n+ \"connector_id\":...", - "path": "codex-rs/core/tests/suite/search_tool.rs", - "status": "modified" - }, - { - "additions": 25, - "deletions": 2, - "patch_excerpt": "@@ -723,15 +723,38 @@ impl RmcpClient {\n None => None,\n };\n let rmcp_params = CallToolRequestParams {\n- meta,\n+ meta: None,\n name: name.into(),\n arguments,\n task: None,\n };\n let result = self\n .run_service_operation(\"tools/call\", timeout, move |service| {\n let rmcp_params = rmcp_params.clone();\n- async move { service.call_tool(rmcp_params).await }.boxed()\n+ let meta = meta.clone();\n+ async move {\n+ let result = service\n+ .peer()\n+ .send_request_with_option(\n+ ClientRequest::CallToolRequest(rmcp::model::CallToolRequest {\n+ method: Default::default(),\n+ params: rmcp_...", - "path": "codex-rs/rmcp-client/src/rmcp_client.rs", - "status": "modified" - } - ], - "linked_issues": [ - "#15011" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "## Summary\r\n\r\nSome background. We're looking to instrument GA turns end to end. Right now a big gap is grouping mcp tool calls with their codex sessions. We send session id and turn id headers to the responses call but not the mcp/wham calls. \r\n\r\nIdeally we could pass the args as headers like with responses, but given the setup of the rmcp client, we can't send as headers without either changing the rmcp package upstream to allow per request headers or introducing a mutex which break concurrency. An earlier attempt made the assumption that we had 1 client per thread, which allowed us to set headers at the start of a turn. @pakrym mentioned that this assumption might break in the near future.\r\n\r\nSo the solution now is to package the turn metadata/session id into the _meta field in the post body and pull out in codex-backend.\r\n\r\n- send turn metadata to MCP servers via `tools/call` `_meta` instead of assuming per-thread request headers on shared clients\r\n- preserve the existing `_codex_apps` metadata while adding `x-codex-turn-metadata` for all MCP tool calls\r\n- extend tests to cover both custom MCP servers and the codex apps search flow\r\n", - "labels": [], - "merged_at": "2026-03-19T22:05:14Z", - "number": 15190, - "state": "merged", - "title": "Plumb MCP turn metadata through _meta", - "url": "https://github.com/openai/codex/pull/15190" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15195.json b/artifacts/github/bundles/openai-codex-pr-15195.json deleted file mode 100644 index c3af073..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15195.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "mzeng-openai", - "committed_at": "2026-03-19T17:39:56Z", - "message": "update", - "sha": "1a910fd874e5d88d2ecd372e47b4f81835b15850", - "url": "https://github.com/openai/codex/commit/1a910fd874e5d88d2ecd372e47b4f81835b15850" - }, - { - "author": "mzeng-openai", - "committed_at": "2026-03-19T20:50:32Z", - "message": "update", - "sha": "3067fcdabc109264135c5b0b721c568862731d58", - "url": "https://github.com/openai/codex/commit/3067fcdabc109264135c5b0b721c568862731d58" - }, - { - "author": "mzeng-openai", - "committed_at": "2026-03-19T20:56:27Z", - "message": "Merge branch 'main' of github.com:openai/codex into dev/mzeng/plugin_install_mcp_auth", - "sha": "663387989c49f696d3727431f8388866a6e692a5", - "url": "https://github.com/openai/codex/commit/663387989c49f696d3727431f8388866a6e692a5" - }, - { - "author": "mzeng-openai", - "committed_at": "2026-03-19T22:43:44Z", - "message": "update", - "sha": "824d265d6d8821d5bd7a8a54646b7428003f8cc9", - "url": "https://github.com/openai/codex/commit/824d265d6d8821d5bd7a8a54646b7428003f8cc9" - }, - { - "author": "mzeng-openai", - "committed_at": "2026-03-19T22:43:53Z", - "message": "Merge branch 'main' of github.com:openai/codex into dev/mzeng/plugin_install_mcp_auth", - "sha": "49edd6bfc227df69272e5669c1ae7ac3fe66818e", - "url": "https://github.com/openai/codex/commit/49edd6bfc227df69272e5669c1ae7ac3fe66818e" - }, - { - "author": "mzeng-openai", - "committed_at": "2026-03-19T23:48:21Z", - "message": "update", - "sha": "5504f1300fc9b5f6cc7eba2d08154f78811a47ec", - "url": "https://github.com/openai/codex/commit/5504f1300fc9b5f6cc7eba2d08154f78811a47ec" - } - ], - "default_branch": "main", - "docs_refs": [ - "codex-rs/app-server/README.md" - ], - "examples_refs": [], - "extracted_flags": [ - "MCP", - "JSONRPCE", - "INTERNAL_ERROR_CODE", - "DEFAULT_TIMEOUT", - "JSONRPCR", - "HTTP" - ], - "files": [ - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -168,7 +168,7 @@ Example with notification opt-out:\n - `skills/changed` \u2014 notification emitted when watched local skill files change.\n - `app/list` \u2014 list available apps.\n - `skills/config/write` \u2014 write user-level skill config by path.\n-- `plugin/install` \u2014 install a plugin from a discovered marketplace entry, rejecting marketplace entries marked unavailable for install, and return the effective plugin auth policy plus any apps that still need auth (**under development; do not call from production clients yet**).\n+- `plugin/install` \u2014 install a plugin from a discovered marketplace entry, rejecting marketplace entries marked unavailable for install, install MCPs if any, and return the effective plugin auth policy plus any apps that still need auth (**under development; do not call from production clients yet**).\n - `plugin/uninstall` \u2014 uninstall a plugin by id by removing its cached f...", - "path": "codex-rs/app-server/README.md", - "status": "modified" - }, - { - "additions": 37, - "deletions": 15, - "patch_excerpt": "@@ -229,6 +229,7 @@ use codex_core::plugins::PluginInstallRequest;\n use codex_core::plugins::PluginReadRequest;\n use codex_core::plugins::PluginUninstallError as CorePluginUninstallError;\n use codex_core::plugins::load_plugin_apps;\n+use codex_core::plugins::load_plugin_mcp_servers;\n use codex_core::read_head_for_summary;\n use codex_core::read_session_meta_line;\n use codex_core::rollout_date_parts;\n@@ -311,6 +312,7 @@ use codex_app_server_protocol::ServerRequest;\n \n mod apps_list_helpers;\n mod plugin_app_helpers;\n+mod plugin_mcp_oauth;\n \n use crate::filters::compute_source_filters;\n use crate::filters::source_kind_matches;\n@@ -4587,36 +4589,42 @@ impl CodexMessageProcessor {\n }\n };\n \n- let configured_servers = self\n- .thread_manager\n- .mcp_manager()\n- .configured_servers(&config);\n+ if let Err(error) = self.queue_mcp_serve...", - "path": "codex-rs/app-server/src/codex_message_processor.rs", - "status": "modified" - }, - { - "additions": 95, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,95 @@\n+use std::collections::HashMap;\n+use std::sync::Arc;\n+\n+use codex_app_server_protocol::McpServerOauthLoginCompletedNotification;\n+use codex_app_server_protocol::ServerNotification;\n+use codex_core::config::Config;\n+use codex_core::config::types::McpServerConfig;\n+use codex_core::mcp::auth::McpOAuthLoginSupport;\n+use codex_core::mcp::auth::oauth_login_support;\n+use codex_core::mcp::auth::resolve_oauth_scopes;\n+use codex_core::mcp::auth::should_retry_without_scopes;\n+use codex_rmcp_client::perform_oauth_login;\n+use tracing::warn;\n+\n+use super::CodexMessageProcessor;\n+\n+impl CodexMessageProcessor {\n+ pub(super) async fn start_plugin_mcp_oauth_logins(\n+ &self,\n+ config: &Config,\n+ plugin_mcp_servers: HashMap,\n+ ) {\n+ for (name, server) in plugin_mcp_servers {\n+ let oauth_config = match oauth_login_support(...", - "path": "codex-rs/app-server/src/codex_message_processor/plugin_mcp_oauth.rs", - "status": "added" - }, - { - "additions": 73, - "deletions": 0, - "patch_excerpt": "@@ -529,6 +529,79 @@ async fn plugin_install_filters_disallowed_apps_needing_auth() -> Result<()> {\n Ok(())\n }\n \n+#[tokio::test]\n+async fn plugin_install_makes_bundled_mcp_servers_available_to_followup_requests() -> Result<()> {\n+ let codex_home = TempDir::new()?;\n+ std::fs::write(\n+ codex_home.path().join(\"config.toml\"),\n+ \"[features]\\nplugins = true\\n\",\n+ )?;\n+ let repo_root = TempDir::new()?;\n+ write_plugin_marketplace(\n+ repo_root.path(),\n+ \"debug\",\n+ \"sample-plugin\",\n+ \"./sample-plugin\",\n+ None,\n+ None,\n+ )?;\n+ write_plugin_source(repo_root.path(), \"sample-plugin\", &[])?;\n+ std::fs::write(\n+ repo_root.path().join(\"sample-plugin/.mcp.json\"),\n+ r#\"{\n+ \"mcpServers\": {\n+ \"sample-mcp\": {\n+ \"command\": \"echo\"\n+ }\n+ }\n+}\"#,\n+ )?;\n+ let marketplace_path =\n+ AbsolutePath...", - "path": "codex-rs/app-server/tests/suite/v2/plugin_install.rs", - "status": "modified" - }, - { - "additions": 16, - "deletions": 0, - "patch_excerpt": "@@ -1660,6 +1660,22 @@ pub fn plugin_telemetry_metadata_from_root(\n }\n }\n \n+pub fn load_plugin_mcp_servers(plugin_root: &Path) -> HashMap {\n+ let Some(manifest) = load_plugin_manifest(plugin_root) else {\n+ return HashMap::new();\n+ };\n+\n+ let mut mcp_servers = HashMap::new();\n+ for mcp_config_path in plugin_mcp_config_paths(plugin_root, &manifest.paths) {\n+ let plugin_mcp = load_mcp_servers_from_file(plugin_root, &mcp_config_path);\n+ for (name, config) in plugin_mcp.mcp_servers {\n+ mcp_servers.entry(name).or_insert(config);\n+ }\n+ }\n+\n+ mcp_servers\n+}\n+\n pub fn installed_plugin_telemetry_metadata(\n codex_home: &Path,\n plugin_id: &PluginId,", - "path": "codex-rs/core/src/plugins/manager.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -36,6 +36,7 @@ pub use manager::PluginsManager;\n pub use manager::RemotePluginSyncResult;\n pub use manager::installed_plugin_telemetry_metadata;\n pub use manager::load_plugin_apps;\n+pub use manager::load_plugin_mcp_servers;\n pub(crate) use manager::plugin_namespace_for_skill_path;\n pub use manager::plugin_telemetry_metadata_from_root;\n pub use manifest::PluginManifestInterface;", - "path": "codex-rs/core/src/plugins/mod.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints." - ], - "primary_pr": { - "body": "- [x] Auth MCPs when installing plugins.", - "labels": [], - "merged_at": "2026-03-20T02:36:59Z", - "number": 15195, - "state": "merged", - "title": "[plugins] Install MCPs when calling plugin/install", - "url": "https://github.com/openai/codex/pull/15195" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15196.json b/artifacts/github/bundles/openai-codex-pr-15196.json deleted file mode 100644 index fdb0862..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15196.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "pakrym-oai", - "committed_at": "2026-03-19T17:44:00Z", - "message": "Add experimental exec server config", - "sha": "1f0ed50a125c758bb8d4d2f71e425e9eef3519f4", - "url": "https://github.com/openai/codex/commit/1f0ed50a125c758bb8d4d2f71e425e9eef3519f4" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "URL", - "TOML", - "API" - ], - "files": [ - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -34,7 +34,7 @@ pub(crate) struct FsApi {\n impl Default for FsApi {\n fn default() -> Self {\n Self {\n- file_system: Arc::new(Environment.get_filesystem()),\n+ file_system: Arc::new(Environment::default().get_filesystem()),\n }\n }\n }", - "path": "codex-rs/app-server/src/fs_api.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 0, - "patch_excerpt": "@@ -1895,6 +1895,10 @@\n \"experimental_compact_prompt_file\": {\n \"$ref\": \"#/definitions/AbsolutePathBuf\"\n },\n+ \"experimental_exec_server_url\": {\n+ \"description\": \"Experimental / do not use. Overrides the URL used when connecting to a remote exec server.\",\n+ \"type\": \"string\"\n+ },\n \"experimental_realtime_start_instructions\": {\n \"description\": \"Experimental / do not use. Replaces the built-in realtime start instructions inserted into developer messages when realtime becomes active.\",\n \"type\": \"string\"", - "path": "codex-rs/core/config.schema.json", - "status": "modified" - }, - { - "additions": 3, - "deletions": 1, - "patch_excerpt": "@@ -1827,7 +1827,9 @@ impl Session {\n code_mode_service: crate::tools::code_mode::CodeModeService::new(\n config.js_repl_node_path.clone(),\n ),\n- environment: Arc::new(Environment),\n+ environment: Arc::new(\n+ Environment::create(config.experimental_exec_server_url.clone()).await?,\n+ ),\n };\n let js_repl = Arc::new(JsReplHandle::with_node_path(\n config.js_repl_node_path.clone(),", - "path": "codex-rs/core/src/codex.rs", - "status": "modified" - }, - { - "additions": 10, - "deletions": 2, - "patch_excerpt": "@@ -2450,7 +2450,11 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {\n true,\n ));\n let network_approval = Arc::new(NetworkApprovalService::default());\n- let environment = Arc::new(codex_exec_server::Environment);\n+ let environment = Arc::new(\n+ codex_exec_server::Environment::create(None)\n+ .await\n+ .expect(\"create environment\"),\n+ );\n \n let file_watcher = Arc::new(FileWatcher::noop());\n let services = SessionServices {\n@@ -3244,7 +3248,11 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(\n true,\n ));\n let network_approval = Arc::new(NetworkApprovalService::default());\n- let environment = Arc::new(codex_exec_server::Environment);\n+ let environment = Arc::new(\n+ codex_exec_server::Environment::create(None)\n+ .await\n+ .expect(\"...", - "path": "codex-rs/core/src/codex_tests.rs", - "status": "modified" - }, - { - "additions": 32, - "deletions": 0, - "patch_excerpt": "@@ -4310,6 +4310,7 @@ fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {\n model_verbosity: None,\n personality: Some(Personality::Pragmatic),\n chatgpt_base_url: \"https://chatgpt.com/backend-api/\".to_string(),\n+ experimental_exec_server_url: None,\n realtime_audio: RealtimeAudioConfig::default(),\n experimental_realtime_start_instructions: None,\n experimental_realtime_ws_base_url: None,\n@@ -4451,6 +4452,7 @@ fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {\n model_verbosity: None,\n personality: Some(Personality::Pragmatic),\n chatgpt_base_url: \"https://chatgpt.com/backend-api/\".to_string(),\n+ experimental_exec_server_url: None,\n realtime_audio: RealtimeAudioConfig::default(),\n experimental_realtime_start_instructions: None,\n...", - "path": "codex-rs/core/src/config/config_tests.rs", - "status": "modified" - }, - { - "additions": 9, - "deletions": 0, - "patch_excerpt": "@@ -493,6 +493,10 @@ pub struct Config {\n /// Base URL for requests to ChatGPT (as opposed to the OpenAI API).\n pub chatgpt_base_url: String,\n \n+ /// Experimental / do not use. Overrides the URL used when connecting to\n+ /// a remote exec server.\n+ pub experimental_exec_server_url: Option,\n+\n /// Machine-local realtime audio device preferences used by realtime voice.\n pub realtime_audio: RealtimeAudioConfig,\n \n@@ -1393,6 +1397,10 @@ pub struct ConfigToml {\n /// Base URL override for the built-in `openai` model provider.\n pub openai_base_url: Option,\n \n+ /// Experimental / do not use. Overrides the URL used when connecting to\n+ /// a remote exec server.\n+ pub experimental_exec_server_url: Option,\n+\n /// Machine-local realtime audio device preferences used by realtime voice.\n #[serde(default)]\n pub audio: Option<...", - "path": "codex-rs/core/src/config/mod.rs", - "status": "modified" - }, - { - "additions": 67, - "deletions": 2, - "patch_excerpt": "@@ -1,11 +1,76 @@\n+use crate::ExecServerClient;\n+use crate::ExecServerError;\n+use crate::RemoteExecServerConnectArgs;\n use crate::fs;\n use crate::fs::ExecutorFileSystem;\n \n-#[derive(Clone, Debug, Default)]\n-pub struct Environment;\n+#[derive(Clone, Default)]\n+pub struct Environment {\n+ experimental_exec_server_url: Option,\n+ remote_exec_server_client: Option,\n+}\n+\n+impl std::fmt::Debug for Environment {\n+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n+ f.debug_struct(\"Environment\")\n+ .field(\n+ \"experimental_exec_server_url\",\n+ &self.experimental_exec_server_url,\n+ )\n+ .field(\n+ \"has_remote_exec_server_client\",\n+ &self.remote_exec_server_client.is_some(),\n+ )\n+ .finish()\n+ }\n+}\n \n impl Environment {\n+ pub a...", - "path": "codex-rs/exec-server/src/environment.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "Add a config and attempt to start the server.", - "labels": [], - "merged_at": "2026-03-19T18:25:12Z", - "number": 15196, - "state": "merged", - "title": "Add experimental exec server URL handling", - "url": "https://github.com/openai/codex/pull/15196" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15198.json b/artifacts/github/bundles/openai-codex-pr-15198.json deleted file mode 100644 index 7f8d5e6..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15198.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "bolinfest", - "committed_at": "2026-03-19T17:57:34Z", - "message": "Publish runnable DotSlash package for argument-comment lint", - "sha": "5afbcf6136ed7bddd7bf074567491d660c212896", - "url": "https://github.com/openai/codex/commit/5afbcf6136ed7bddd7bf074567491d660c212896" - } - ], - "default_branch": "main", - "docs_refs": [ - "tools/argument-comment-lint/README.md" - ], - "examples_refs": [], - "extracted_flags": [ - "--lib-path", - "RUNNER_TEMP", - "--locked", - "--root", - "INSTALL_ROOT=$install_root\"", - "GITHUB_ENV", - "--release", - "--target", - "INSTALL_ROOT", - "GITHUB_WORKSPACE", - "GITHUB_TOKEN", - "--all", - "--lib", - "DYLINT_RUSTFLAGS", - "CARGO_INCREMENTAL" - ], - "files": [ - { - "additions": 24, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,24 @@\n+{\n+ \"outputs\": {\n+ \"argument-comment-lint\": {\n+ \"platforms\": {\n+ \"macos-aarch64\": {\n+ \"regex\": \"^argument-comment-lint-aarch64-apple-darwin\\\\.tar\\\\.gz$\",\n+ \"path\": \"argument-comment-lint/bin/argument-comment-lint\"\n+ },\n+ \"linux-x86_64\": {\n+ \"regex\": \"^argument-comment-lint-x86_64-unknown-linux-gnu\\\\.tar\\\\.gz$\",\n+ \"path\": \"argument-comment-lint/bin/argument-comment-lint\"\n+ },\n+ \"linux-aarch64\": {\n+ \"regex\": \"^argument-comment-lint-aarch64-unknown-linux-gnu\\\\.tar\\\\.gz$\",\n+ \"path\": \"argument-comment-lint/bin/argument-comment-lint\"\n+ },\n+ \"windows-x86_64\": {\n+ \"regex\": \"^argument-comment-lint-x86_64-pc-windows-msvc\\\\.zip$\",\n+ \"path\": \"argument-comment-lint/bin/argument-comment-lint.exe\"\n+ }\n+ }\n+ }\n+ }\n+}", - "path": ".github/dotslash-argument-comment-lint-config.json", - "status": "added" - }, - { - "additions": 103, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,103 @@\n+name: rust-release-argument-comment-lint\n+\n+on:\n+ workflow_call:\n+ inputs:\n+ publish:\n+ required: true\n+ type: boolean\n+\n+jobs:\n+ skip:\n+ if: ${{ !inputs.publish }}\n+ runs-on: ubuntu-latest\n+ steps:\n+ - run: echo \"Skipping argument-comment-lint release assets for prerelease tag\"\n+\n+ build:\n+ if: ${{ inputs.publish }}\n+ name: Build - ${{ matrix.runner }} - ${{ matrix.target }}\n+ runs-on: ${{ matrix.runs_on || matrix.runner }}\n+ timeout-minutes: 60\n+\n+ strategy:\n+ fail-fast: false\n+ matrix:\n+ include:\n+ - runner: macos-15-xlarge\n+ target: aarch64-apple-darwin\n+ archive_name: argument-comment-lint-aarch64-apple-darwin.tar.gz\n+ lib_name: libargument_comment_lint@nightly-2025-09-18-aarch64-apple-darwin.dylib\n+ runner_binary: argument-comment-lint\n+ ...", - "path": ".github/workflows/rust-release-argument-comment-lint.yml", - "status": "added" - }, - { - "additions": 15, - "deletions": 0, - "patch_excerpt": "@@ -380,11 +380,19 @@ jobs:\n publish: true\n secrets: inherit\n \n+ argument-comment-lint-release-assets:\n+ name: argument-comment-lint release assets\n+ needs: tag-check\n+ uses: ./.github/workflows/rust-release-argument-comment-lint.yml\n+ with:\n+ publish: true\n+\n release:\n needs:\n - build\n - build-windows\n - shell-tool-mcp\n+ - argument-comment-lint-release-assets\n name: release\n runs-on: ubuntu-latest\n permissions:\n@@ -521,6 +529,13 @@ jobs:\n tag: ${{ github.ref_name }}\n config: .github/dotslash-config.json\n \n+ - uses: facebook/dotslash-publish-release@v2\n+ env:\n+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n+ with:\n+ tag: ${{ github.ref_name }}\n+ config: .github/dotslash-argument-comment-lint-config.json\n+\n - name: Trigger developers.openai.com deploy\n ...", - "path": ".github/workflows/rust-release.yml", - "status": "modified" - }, - { - "additions": 5, - "deletions": 0, - "patch_excerpt": "@@ -68,6 +68,11 @@ cd tools/argument-comment-lint\n cargo test\n ```\n \n+GitHub releases also publish a DotSlash file named\n+`argument-comment-lint` for macOS arm64, Linux arm64, Linux x64, and Windows\n+x64. The published package contains a small runner executable, a bundled\n+`cargo-dylint`, and the prebuilt lint library.\n+\n Run the lint against `codex-rs` from the repo root:\n \n ```bash", - "path": "tools/argument-comment-lint/README.md", - "status": "modified" - }, - { - "additions": 164, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,164 @@\n+use std::env;\n+use std::ffi::OsString;\n+use std::fs;\n+use std::path::Path;\n+use std::path::PathBuf;\n+use std::process::Command;\n+use std::process::ExitCode;\n+\n+fn main() -> ExitCode {\n+ match run() {\n+ Ok(code) => code,\n+ Err(err) => {\n+ eprintln!(\"{err}\");\n+ ExitCode::from(1)\n+ }\n+ }\n+}\n+\n+fn run() -> Result {\n+ let exe_path =\n+ env::current_exe().map_err(|err| format!(\"failed to locate current executable: {err}\"))?;\n+ let bin_dir = exe_path.parent().ok_or_else(|| {\n+ format!(\n+ \"failed to locate parent directory for executable {}\",\n+ exe_path.display()\n+ )\n+ })?;\n+ let package_root = bin_dir.parent().ok_or_else(|| {\n+ format!(\n+ \"failed to locate package root for executable {}\",\n+ exe_path.display()\n+ )\n+ })?...", - "path": "tools/argument-comment-lint/src/bin/argument-comment-lint.rs", - "status": "added" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "## Why\r\n\r\nTo date, the argument-comment linter introduced in https://github.com/openai/codex/pull/14651 had to be built from source to run, which can be a bit slow (both for local dev and when it is run in CI). Because of the potential slowness, I did not wire it up to run as part of `just clippy` or anything like that. As a result, I have seen a number of occasions where folks put up PRs that violate the lint, see it fail in CI, and then have to put up their PR again.\r\n\r\nThe goal of this PR is to pre-build a runnable version of the linter and then make it available via a DotSlash file. Once it is available, I will update `just clippy` and other touchpoints to make it a natural part of the dev cycle so lint violations should get flagged _before_ putting up a PR for review.\r\n\r\nTo get things started, we will build the DotSlash file as part of an alpha release. Though I don't expect the linter to change often, so I'll probably change this to only build as part of mainline releases once we have a working DotSlash file. (Ultimately, we should probably move the linter into its own repo so it can have its own release cycle.)\r\n\r\n## What Changed\r\n- add a reusable `rust-release-argument-comment-lint.yml` workflow that builds host-specific archives for macOS arm64, Linux arm64/x64, and Windows x64\r\n- wire `rust-release.yml` to publish the `argument-comment-lint` DotSlash manifest on all releases for now, including alpha tags\r\n- package a runnable layout instead of a bare library\r\n\r\nThe Unix archive layout is:\r\n\r\n```text\r\nargument-comment-lint/\r\n bin/\r\n argument-comment-lint\r\n cargo-dylint\r\n lib/\r\n libargument_comment_lint@nightly-2025-09-18-.dylib|so\r\n```\r\n\r\nOn Windows the same layout is published as a `.zip`, with `.exe` and `.dll` filenames instead.\r\n\r\nDotSlash resolves the package entrypoint to `argument-comment-lint/bin/argument-comment-lint`. That runner finds the sibling bundled `cargo-dylint` binary plus the single packaged Dylint library under `lib/`, then invokes `cargo-dylint dylint --lib-path ` with the repo's default lint settings.", - "labels": [], - "merged_at": "2026-03-19T18:59:03Z", - "number": 15198, - "state": "merged", - "title": "Publish runnable DotSlash package for argument-comment lint", - "url": "https://github.com/openai/codex/pull/15198" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15199.json b/artifacts/github/bundles/openai-codex-pr-15199.json deleted file mode 100644 index 75a9863..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15199.json +++ /dev/null @@ -1,291 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "bolinfest", - "committed_at": "2026-03-20T02:11:09Z", - "message": "Use released DotSlash package for argument-comment lint", - "sha": "a1b8effb15edfc337ba58215adc4d70f60c548ac", - "url": "https://github.com/openai/codex/commit/a1b8effb15edfc337ba58215adc4d70f60c548ac" - } - ], - "default_branch": "main", - "docs_refs": [ - "tools/argument-comment-lint/README.md" - ], - "examples_refs": [], - "extracted_flags": [ - "RUSTUP_TOOLCHAIN", - "RUSTUP_HOME", - "README", - "DEBIAN_FRONTEND=noninteractive", - "--no-install-recommends", - "--locked", - "TUI", - "NONE", - "STATUS_DETAILS_DEFAULT_MAX_LINES", - "INVALID_HANDLE_VALUE", - "EXPLICIT_ACCESS_W", - "FILE_WRITE_DATA", - "FILE_APPEND_DATA", - "FILE_WRITE_EA", - "FILE_WRITE_ATTRIBUTES", - "HANDLE", - "GIT_ALLOW_PROTOCOLS", - "--fix", - "--tests", - "--allow-dirty", - "SQL", - "--component", - "--lib-path", - "DYLINT_RUSTFLAGS", - "CARGO_INCREMENTAL=0", - "PATH", - "--workspace", - "--no-deps", - "CARGO_INCREMENTAL=1", - "--all-targets", - "BASH_SOURCE", - "--manifest-path", - "--package", - "--lib", - "EOF", - "IFS", - "PATH=\"$rustup_bin_dir\"", - "IFS=:;", - "RUSTUP_HOME=\"$rustup_home\"", - "BASH_REMATCH", - "TMPDIR", - "XXXXXX", - "DYLINT_RUSTFLAGS=\"-D", - "CARGO_INCREMENTAL", - "--all", - "DYLINT_RUSTFLAGS=\"${DYLINT_RUSTFLAGS:+${DYLINT_RUSTFLAGS}", - "--path", - "UNIX_EPOCH", - "UTF" - ], - "files": [ - { - "additions": 55, - "deletions": 14, - "patch_excerpt": "@@ -91,17 +91,13 @@ jobs:\n - name: cargo shear\n run: cargo shear\n \n- argument_comment_lint:\n- name: Argument comment lint\n+ argument_comment_lint_package:\n+ name: Argument comment lint package\n runs-on: ubuntu-24.04\n needs: changed\n- if: ${{ needs.changed.outputs.argument_comment_lint == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push' }}\n+ if: ${{ needs.changed.outputs.argument_comment_lint_package == 'true' || github.event_name == 'push' }}\n steps:\n - uses: actions/checkout@v6\n- - name: Install Linux sandbox build dependencies\n- run: |\n- sudo DEBIAN_FRONTEND=noninteractive apt-get update\n- sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev\n - uses: dtolnay/rust-toolchain@1.93.0\n with:\n toolchain: nightly-...", - "path": ".github/workflows/rust-ci.yml", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -48,6 +48,8 @@ Run `just fmt` (in `codex-rs` directory) automatically after you have finished m\n \n Before finalizing a large change to `codex-rs`, run `just fix -p ` (in `codex-rs` directory) to fix any linter issues in the code. Prefer scoping with `-p` to avoid slow workspace\u2011wide Clippy builds; only run `just fix` without `-p` if you changed shared crates. Do not re-run tests after running `fix` or `fmt`.\n \n+Also run `just argument-comment-lint` to ensure the codebase is clean of comment lint errors.\n+\n ## TUI style conventions\n \n See `codex-rs/tui/styles.md`.", - "path": "AGENTS.md", - "status": "modified" - }, - { - "additions": 5, - "deletions": 5, - "patch_excerpt": "@@ -170,7 +170,7 @@ async fn run_command_under_sandbox(\n command_vec,\n &cwd_clone,\n env_map,\n- None,\n+ /*timeout_ms*/ None,\n config.permissions.windows_sandbox_private_desktop,\n )\n } else {\n@@ -181,7 +181,7 @@ async fn run_command_under_sandbox(\n command_vec,\n &cwd_clone,\n env_map,\n- None,\n+ /*timeout_ms*/ None,\n config.permissions.windows_sandbox_private_desktop,\n )\n }\n@@ -251,15 +251,15 @@ async fn run_command_under_sandbox(\n &config.permissions.file_system_sandbox_policy,\n config.permissions.network_sand...", - "path": "codex-rs/cli/src/debug_sandbox.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -387,7 +387,7 @@ fn record_windows_sandbox_spawn_failure(\n if let Some(metrics) = codex_otel::metrics::global() {\n let _ = metrics.counter(\n \"codex.windows_sandbox.createprocessasuserw_failed\",\n- 1,\n+ /*inc*/ 1,\n &[\n (\"error_code\", error_code.as_str()),\n (\"path_kind\", path_kind),", - "path": "codex-rs/core/src/exec.rs", - "status": "modified" - }, - { - "additions": 7, - "deletions": 3, - "patch_excerpt": "@@ -185,8 +185,8 @@ pub fn run_elevated_setup(\n command_cwd,\n env_map,\n codex_home,\n- None,\n- None,\n+ /*read_roots_override*/ None,\n+ /*write_roots_override*/ None,\n )\n }\n \n@@ -421,7 +421,11 @@ fn emit_windows_sandbox_setup_failure_metrics(\n if let Some(message) = message_tag.as_deref() {\n failure_tags.push((\"message\", message));\n }\n- let _ = metrics.counter(elevated_setup_failure_metric_name(_err), 1, &failure_tags);\n+ let _ = metrics.counter(\n+ elevated_setup_failure_metric_name(_err),\n+ /*inc*/ 1,\n+ &failure_tags,\n+ );\n }\n } else {\n let _ = metrics.counter(", - "path": "codex-rs/core/src/windows_sandbox.rs", - "status": "modified" - }, - { - "additions": 11, - "deletions": 9, - "patch_excerpt": "@@ -2901,7 +2901,7 @@ impl App {\n Ok(()) => {\n session_telemetry.counter(\n \"codex.windows_sandbox.elevated_setup_success\",\n- 1,\n+ /*inc*/ 1,\n &[],\n );\n AppEvent::EnableWindowsSandboxForAgentMode {\n@@ -2931,7 +2931,7 @@ impl App {\n codex_core::windows_sandbox::elevated_setup_failure_metric_name(\n &err,\n ),\n- 1,\n+ /*inc*/ 1,\n &tags,\n );\n tracing::error...", - "path": "codex-rs/tui/src/app.rs", - "status": "modified" - }, - { - "additions": 41, - "deletions": 12, - "patch_excerpt": "@@ -4536,7 +4536,7 @@ impl ChatWidget {\n \n self.session_telemetry.counter(\n \"codex.windows_sandbox.setup_elevated_sandbox_command\",\n- 1,\n+ /*inc*/ 1,\n &[],\n );\n self.app_event_tx\n@@ -7525,8 +7525,11 @@ impl ChatWidget {\n return;\n }\n \n- self.session_telemetry\n- .counter(\"codex.windows_sandbox.elevated_prompt_shown\", 1, &[]);\n+ self.session_telemetry.counter(\n+ \"codex.windows_sandbox.elevated_prompt_shown\",\n+ /*inc*/ 1,\n+ &[],\n+ );\n \n let mut header = ColumnRenderable::new();\n header.push(*Box::new(\n@@ -7545,7 +7548,11 @@ impl ChatWidget {\n name: \"Set up default sandbox (requires Administrator permissions)\".to_string(),\n ...", - "path": "codex-rs/tui/src/chatwidget.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 2, - "patch_excerpt": "@@ -979,8 +979,10 @@ async fn enter_with_only_remote_images_does_not_submit_when_input_disabled() {\n \n let remote_url = \"https://example.com/remote-only.png\".to_string();\n chat.set_remote_image_urls(vec![remote_url.clone()]);\n- chat.bottom_pane\n- .set_composer_input_enabled(false, Some(\"Input disabled for test.\".to_string()));\n+ chat.bottom_pane.set_composer_input_enabled(\n+ /*enabled*/ false,\n+ Some(\"Input disabled for test.\".to_string()),\n+ );\n \n chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));", - "path": "codex-rs/tui/src/chatwidget/tests.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -1265,7 +1265,7 @@ mod tests {\n let temp_dir = TempDir::new()?;\n let mut config = build_config(&temp_dir).await?;\n config.active_project = ProjectConfig { trust_level: None };\n- config.set_windows_sandbox_enabled(false);\n+ config.set_windows_sandbox_enabled(/*value*/ false);\n \n let should_show = should_show_trust_screen(&config);\n assert!(\n@@ -1281,7 +1281,7 @@ mod tests {\n let temp_dir = TempDir::new()?;\n let mut config = build_config(&temp_dir).await?;\n config.active_project = ProjectConfig { trust_level: None };\n- config.set_windows_sandbox_enabled(true);\n+ config.set_windows_sandbox_enabled(/*value*/ true);\n \n let should_show = should_show_trust_screen(&config);\n if cfg!(target_os = \"windows\") {", - "path": "codex-rs/tui/src/lib.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -354,7 +354,7 @@ mod tests {\n StatusDetailsCapitalization::CapitalizeFirst,\n STATUS_DETAILS_DEFAULT_MAX_LINES,\n );\n- w.set_interrupt_hint_visible(false);\n+ w.set_interrupt_hint_visible(/*visible*/ false);\n \n // Freeze time-dependent rendering (elapsed + spinner) to keep the snapshot stable.\n w.is_paused = true;", - "path": "codex-rs/tui/src/status_indicator_widget.rs", - "status": "modified" - }, - { - "additions": 37, - "deletions": 35, - "patch_excerpt": "@@ -1363,18 +1363,18 @@ impl App {\n let windows_sandbox_level = WindowsSandboxLevel::from_config(&self.config);\n self.app_event_tx.send(AppEvent::CodexOp(\n AppCommand::override_turn_context(\n- None,\n- None,\n- None,\n- None,\n+ /*cwd*/ None,\n+ /*approval_policy*/ None,\n+ /*approvals_reviewer*/ None,\n+ /*sandbox_policy*/ None,\n #[cfg(target_os = \"windows\")]\n Some(windows_sandbox_level),\n- None,\n- None,\n- None,\n- None,\n- None,\n- None,\n+ /*model*/ None,\n+ ...", - "path": "codex-rs/tui_app_server/src/app.rs", - "status": "modified" - }, - { - "additions": 41, - "deletions": 12, - "patch_excerpt": "@@ -4699,7 +4699,7 @@ impl ChatWidget {\n \n self.session_telemetry.counter(\n \"codex.windows_sandbox.setup_elevated_sandbox_command\",\n- 1,\n+ /*inc*/ 1,\n &[],\n );\n self.app_event_tx\n@@ -8707,8 +8707,11 @@ impl ChatWidget {\n return;\n }\n \n- self.session_telemetry\n- .counter(\"codex.windows_sandbox.elevated_prompt_shown\", 1, &[]);\n+ self.session_telemetry.counter(\n+ \"codex.windows_sandbox.elevated_prompt_shown\",\n+ /*inc*/ 1,\n+ &[],\n+ );\n \n let mut header = ColumnRenderable::new();\n header.push(*Box::new(\n@@ -8727,7 +8730,11 @@ impl ChatWidget {\n name: \"Set up default sandbox (requires Administrator permissions)\".to_string(),\n ...", - "path": "codex-rs/tui_app_server/src/chatwidget.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 2, - "patch_excerpt": "@@ -1003,8 +1003,10 @@ async fn enter_with_only_remote_images_does_not_submit_when_input_disabled() {\n \n let remote_url = \"https://example.com/remote-only.png\".to_string();\n chat.set_remote_image_urls(vec![remote_url.clone()]);\n- chat.bottom_pane\n- .set_composer_input_enabled(false, Some(\"Input disabled for test.\".to_string()));\n+ chat.bottom_pane.set_composer_input_enabled(\n+ /*enabled*/ false,\n+ Some(\"Input disabled for test.\".to_string()),\n+ );\n \n chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));", - "path": "codex-rs/tui_app_server/src/chatwidget/tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -352,7 +352,7 @@ mod tests {\n StatusDetailsCapitalization::CapitalizeFirst,\n STATUS_DETAILS_DEFAULT_MAX_LINES,\n );\n- w.set_interrupt_hint_visible(false);\n+ w.set_interrupt_hint_visible(/*visible*/ false);\n \n // Freeze time-dependent rendering (elapsed + spinner) to keep the snapshot stable.\n w.is_paused = true;", - "path": "codex-rs/tui_app_server/src/status_indicator_widget.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -172,7 +172,7 @@ impl PsuedoCon {\n si.StartupInfo.hStdOutput = INVALID_HANDLE_VALUE;\n si.StartupInfo.hStdError = INVALID_HANDLE_VALUE;\n \n- let mut attrs = ProcThreadAttributeList::with_capacity(1)?;\n+ let mut attrs = ProcThreadAttributeList::with_capacity(/*num_attributes*/ 1)?;\n attrs.set_pty(self.con)?;\n si.lpAttributeList = attrs.as_mut_ptr();", - "path": "codex-rs/utils/pty/src/win/psuedocon.rs", - "status": "modified" - }, - { - "additions": 6, - "deletions": 1, - "patch_excerpt": "@@ -275,7 +275,12 @@ unsafe fn ensure_allow_mask_aces_with_inheritance_impl(\n let (p_dacl, p_sd) = fetch_dacl_handle(path)?;\n let mut entries: Vec = Vec::new();\n for sid in sids {\n- if dacl_mask_allows(p_dacl, &[*sid], allow_mask, true) {\n+ if dacl_mask_allows(\n+ p_dacl,\n+ &[*sid],\n+ allow_mask,\n+ /*require_all_bits*/ true,\n+ ) {\n continue;\n }\n entries.push(EXPLICIT_ACCESS_W {", - "path": "codex-rs/windows-sandbox-rs/src/acl.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -81,7 +81,7 @@ unsafe fn path_has_world_write_allow(path: &Path) -> Result {\n let mut world = world_sid()?;\n let psid_world = world.as_mut_ptr() as *mut c_void;\n let write_mask = FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES;\n- path_mask_allows(path, &[psid_world], write_mask, false)\n+ path_mask_allows(path, &[psid_world], write_mask, /*require_all_bits*/ false)\n }\n \n pub fn audit_everyone_writable(", - "path": "codex-rs/windows-sandbox-rs/src/audit.rs", - "status": "modified" - }, - { - "additions": 5, - "deletions": 3, - "patch_excerpt": "@@ -76,7 +76,9 @@ pub fn create_conpty(cols: i16, rows: i16) -> Result {\n hpc: hpc as HANDLE,\n input_write: input_write as HANDLE,\n output_read: output_read as HANDLE,\n- _desktop: LaunchDesktop::prepare(false, None)?,\n+ _desktop: LaunchDesktop::prepare(\n+ /*use_private_desktop*/ false, /*logs_base_dir*/ None,\n+ )?,\n })\n }\n \n@@ -108,8 +110,8 @@ pub fn spawn_conpty_process_as_user(\n let desktop = LaunchDesktop::prepare(use_private_desktop, logs_base_dir)?;\n si.StartupInfo.lpDesktop = desktop.startup_info_desktop();\n \n- let conpty = create_conpty(80, 24)?;\n- let mut attrs = ProcThreadAttributeList::new(1)?;\n+ let conpty = create_conpty(/*cols*/ 80, /*rows*/ 24)?;\n+ let mut attrs = ProcThreadAttributeList::new(/*attr_count*/ 1)?;\n attrs.set_pseudoconsole(conpty.hpc)?;\n si.lpAttributeList =...", - "path": "codex-rs/windows-sandbox-rs/src/conpty/mod.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -289,7 +289,7 @@ fn spawn_ipc_process(\n &req.env,\n stdin_mode,\n StderrMode::Separate,\n- false,\n+ /*use_private_desktop*/ false,\n )?;\n (\n pipe_handles.process,", - "path": "codex-rs/windows-sandbox-rs/src/elevated/command_runner_win.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -159,7 +159,7 @@ pub fn apply_no_network_to_env(env_map: &mut HashMap) -> Result<\n .entry(\"GIT_ALLOW_PROTOCOLS\".into())\n .or_insert_with(|| \"\".into());\n \n- let base = ensure_denybin(&[\"ssh\", \"scp\"], None)?;\n+ let base = ensure_denybin(&[\"ssh\", \"scp\"], /*denybin_dir*/ None)?;\n for tool in [\"curl\", \"wget\"] {\n for ext in [\".bat\", \".cmd\"] {\n let p = base.join(format!(\"{}{}\", tool, ext));", - "path": "codex-rs/windows-sandbox-rs/src/env.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -229,7 +229,7 @@ pub fn spawn_process_with_pipes(\n argv,\n cwd,\n env_map,\n- None,\n+ /*logs_base_dir*/ None,\n stdio,\n use_private_desktop,\n )", - "path": "codex-rs/windows-sandbox-rs/src/process.rs", - "status": "modified" - }, - { - "additions": 20, - "deletions": 19, - "patch_excerpt": "@@ -153,7 +153,7 @@ fn apply_read_acls(\n let builtin_has = read_mask_allows_or_log(\n root,\n subjects.rx_psids,\n- None,\n+ /*label*/ None,\n access_mask,\n access_label,\n refresh_errors,\n@@ -215,7 +215,7 @@ fn read_mask_allows_or_log(\n refresh_errors: &mut Vec,\n log: &mut File,\n ) -> Result {\n- match path_mask_allows(root, psids, read_mask, true) {\n+ match path_mask_allows(root, psids, read_mask, /*require_all_bits*/ true) {\n Ok(has) => Ok(has),\n Err(e) => {\n let label_suffix = label\n@@ -653,25 +653,26 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<(\n (\"sandbox_group\", sandbox_group_psid),\n (cap_label, cap_psid_for_root),\n ] {\n- let has = match path_mask_allows(root, &[ps...", - "path": "codex-rs/windows-sandbox-rs/src/setup_main_win.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -91,8 +91,8 @@ pub fn run_setup_refresh(\n command_cwd,\n env_map,\n codex_home,\n- None,\n- None,\n+ /*read_roots_override*/ None,\n+ /*write_roots_override*/ None,\n )\n }", - "path": "codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs", - "status": "modified" - }, - { - "additions": 5, - "deletions": 1, - "patch_excerpt": "@@ -30,7 +30,7 @@ fmt:\n fix *args:\n cargo clippy --fix --tests --allow-dirty \"$@\"\n \n-clippy:\n+clippy *args:\n cargo clippy --tests \"$@\"\n \n install:\n@@ -89,6 +89,10 @@ write-hooks-schema:\n # Run the argument-comment Dylint checks across codex-rs.\n [no-cd]\n argument-comment-lint *args:\n+ ./tools/argument-comment-lint/run-prebuilt-linter.sh \"$@\"\n+\n+[no-cd]\n+argument-comment-lint-from-source *args:\n ./tools/argument-comment-lint/run.sh \"$@\"\n \n # Tail logs from the state SQLite database", - "path": "justfile", - "status": "modified" - }, - { - "additions": 54, - "deletions": 4, - "patch_excerpt": "@@ -73,21 +73,71 @@ GitHub releases also publish a DotSlash file named\n x64. The published package contains a small runner executable, a bundled\n `cargo-dylint`, and the prebuilt lint library.\n \n-Run the lint against `codex-rs` from the repo root:\n+The package is not a full Rust toolchain. Running the prebuilt path still\n+requires the pinned nightly toolchain to be installed via `rustup`:\n+\n+```bash\n+rustup toolchain install nightly-2025-09-18 \\\n+ --component llvm-tools-preview \\\n+ --component rustc-dev \\\n+ --component rust-src\n+```\n+\n+The checked-in DotSlash file lives at `tools/argument-comment-lint/argument-comment-lint`.\n+`run-prebuilt-linter.sh` resolves that file via `dotslash` and is the path used by\n+`just clippy`, `just argument-comment-lint`, and the Rust CI job. The\n+source-build path remains available in `run.sh` for people\n+iterating on the lint crate itself.\n+\n+The Unix ...", - "path": "tools/argument-comment-lint/README.md", - "status": "modified" - }, - { - "additions": 79, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,79 @@\n+#!/usr/bin/env dotslash\n+\n+{\n+ \"name\": \"argument-comment-lint\",\n+ \"platforms\": {\n+ \"macos-aarch64\": {\n+ \"size\": 3402747,\n+ \"hash\": \"blake3\",\n+ \"digest\": \"a11669d2f184a2c6f226cedce1bf10d1ec478d53413c42fe80d17dd873fdb2d7\",\n+ \"format\": \"tar.gz\",\n+ \"path\": \"argument-comment-lint/bin/argument-comment-lint\",\n+ \"providers\": [\n+ {\n+ \"url\": \"https://github.com/openai/codex/releases/download/rust-v0.117.0-alpha.2/argument-comment-lint-aarch64-apple-darwin.tar.gz\"\n+ },\n+ {\n+ \"type\": \"github-release\",\n+ \"repo\": \"https://github.com/openai/codex\",\n+ \"tag\": \"rust-v0.117.0-alpha.2\",\n+ \"name\": \"argument-comment-lint-aarch64-apple-darwin.tar.gz\"\n+ }\n+ ]\n+ },\n+ \"linux-x86_64\": {\n+ \"size\": 3869711,\n+ \"hash\": \"blake3\",\n+ \"digest\": \"1015f4ba07d57edc5ec79c8f670...", - "path": "tools/argument-comment-lint/argument-comment-lint", - "status": "added" - }, - { - "additions": 164, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,164 @@\n+#!/usr/bin/env bash\n+\n+set -euo pipefail\n+\n+repo_root=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/../..\" && pwd)\"\n+manifest_path=\"$repo_root/codex-rs/Cargo.toml\"\n+dotslash_manifest=\"$repo_root/tools/argument-comment-lint/argument-comment-lint\"\n+\n+has_manifest_path=false\n+has_package_selection=false\n+has_library_selection=false\n+has_no_deps=false\n+expect_value=\"\"\n+\n+for arg in \"$@\"; do\n+ if [[ -n \"$expect_value\" ]]; then\n+ case \"$expect_value\" in\n+ manifest_path)\n+ has_manifest_path=true\n+ ;;\n+ package_selection)\n+ has_package_selection=true\n+ ;;\n+ library_selection)\n+ has_library_selection=true\n+ ;;\n+ esac\n+ expect_value=\"\"\n+ continue\n+ fi\n+\n+ case \"$arg\" in\n+ --)\n+ break\n+ ;;\n+ --m...", - "path": "tools/argument-comment-lint/run-prebuilt-linter.sh", - "status": "added" - }, - { - "additions": 48, - "deletions": 16, - "patch_excerpt": "@@ -5,6 +5,7 @@ set -euo pipefail\n repo_root=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/../..\" && pwd)\"\n lint_path=\"$repo_root/tools/argument-comment-lint\"\n manifest_path=\"$repo_root/codex-rs/Cargo.toml\"\n+toolchain_channel=\"nightly-2025-09-18\"\n strict_lint=\"uncommented-anonymous-literal-argument\"\n noise_lint=\"unknown_lints\"\n \n@@ -14,6 +15,42 @@ has_no_deps=false\n has_library_selection=false\n expect_value=\"\"\n \n+ensure_local_prerequisites() {\n+ if ! command -v cargo-dylint >/dev/null 2>&1 || ! command -v dylint-link >/dev/null 2>&1; then\n+ cat >&2 <&2 < ExitCode {\n match run() {\n@@ -33,7 +35,7 @@ fn run() -> Result {\n })?;\n let cargo_dylint = bin_dir.join(cargo_dylint_binary_name());\n let library_dir = package_root.join(\"lib\");\n- let library_path = find_bundled_library(&library_dir)?;\n+ let library_path = prepare_library_path_for_dylint(&find_bundled_library(&library_dir)?)?;\n \n ensure_exists(&cargo_dylint, \"bundled cargo-dylint executable\")?;\n ensure_exists(\n@@ -49,7 +51,7 @@ fn run() -> Result {\n command.arg(\"--all\");\n }\n command.args(&args);\n- set_default_env(&mut command);\n+ set_default_env(&mut command)?;\n \n let status = command\n .status()\n@@ -80...", - "path": "tools/argument-comment-lint/src/bin/argument-comment-lint.rs", - "status": "modified" - } - ], - "linked_issues": [ - "#15198" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "## Why\r\nThe argument-comment lint now has a packaged DotSlash artifact from [#15198](https://github.com/openai/codex/pull/15198), so the normal repo lint path should use that released payload instead of rebuilding the lint from source every time.\r\n\r\nThat keeps `just clippy` and CI aligned with the shipped artifact while preserving a separate source-build path for people actively hacking on the lint crate.\r\n\r\nThe current alpha package also exposed two integration wrinkles that the repo-side prebuilt wrapper needs to smooth over:\r\n- the bundled Dylint library filename includes the host triple, for example `@nightly-2025-09-18-aarch64-apple-darwin`, and Dylint derives `RUSTUP_TOOLCHAIN` from that filename\r\n- on Windows, Dylint's driver path also expects `RUSTUP_HOME` to be present in the environment\r\n\r\nWithout those adjustments, the prebuilt CI jobs fail during `cargo metadata` or driver setup. This change makes the checked-in prebuilt wrapper normalize the packaged library name to the plain `nightly-2025-09-18` channel before invoking `cargo-dylint`, and it teaches both the wrapper and the packaged runner source to infer `RUSTUP_HOME` from `rustup show home` when the environment does not already provide it.\r\n\r\nAfter the prebuilt Windows lint job started running successfully, it also surfaced a handful of existing anonymous literal callsites in `windows-sandbox-rs`. This PR now annotates those callsites so the new cross-platform lint job is green on the current tree.\r\n\r\n## What Changed\r\n- checked in the current `tools/argument-comment-lint/argument-comment-lint` DotSlash manifest\r\n- kept `tools/argument-comment-lint/run.sh` as the source-build wrapper for lint development\r\n- added `tools/argument-comment-lint/run-prebuilt-linter.sh` as the normal enforcement path, using the checked-in DotSlash package and bundled `cargo-dylint`\r\n- updated `just clippy` and `just argument-comment-lint` to use the prebuilt wrapper\r\n- split `.github/workflows/rust-ci.yml` so source-package checks live in a dedicated `argument_comment_lint_package` job, while the released lint runs in an `argument_comment_lint_prebuilt` matrix on Linux, macOS, and Windows\r\n- kept the pinned `nightly-2025-09-18` toolchain install in the prebuilt CI matrix, since the prebuilt package still relies on rustup-provided toolchain components\r\n- updated `tools/argument-comment-lint/run-prebuilt-linter.sh` to normalize host-qualified nightly library filenames, keep the `rustup` shim directory ahead of direct toolchain `cargo` binaries, and export `RUSTUP_HOME` when needed for Windows Dylint driver setup\r\n- updated `tools/argument-comment-lint/src/bin/argument-comment-lint.rs` so future published DotSlash artifacts apply the same nightly-filename normalization and `RUSTUP_HOME` inference internally\r\n- fixed the remaining Windows lint violations in `codex-rs/windows-sandbox-rs` by adding the required `/*param*/` comments at the reported callsites\r\n- documented the checked-in DotSlash file, wrapper split, archive layout, nightly prerequisite, and Windows `RUSTUP_HOME` requirement in `tools/argument-comment-lint/README.md`\r\n\r\n", - "labels": [], - "merged_at": "2026-03-20T03:19:22Z", - "number": 15199, - "state": "merged", - "title": "Use released DotSlash package for argument-comment lint", - "url": "https://github.com/openai/codex/pull/15199" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15201.json b/artifacts/github/bundles/openai-codex-pr-15201.json deleted file mode 100644 index a9a4890..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15201.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "gabec-openai", - "committed_at": "2026-03-19T18:04:05Z", - "message": "Log automated approval reviewers distinctly", - "sha": "a3dcc1d69412c7f72ef738a8ef86a84bf7915c69", - "url": "https://github.com/openai/codex/commit/a3dcc1d69412c7f72ef738a8ef86a84bf7915c69" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "--nocapture" - ], - "files": [ - { - "additions": 13, - "deletions": 2, - "patch_excerpt": "@@ -112,6 +112,7 @@ impl ToolOrchestrator {\n let otel_tn = &tool_ctx.tool_name;\n let otel_ci = &tool_ctx.call_id;\n let otel_user = ToolDecisionSource::User;\n+ let otel_automated_reviewer = ToolDecisionSource::AutomatedReviewer;\n let otel_cfg = ToolDecisionSource::Config;\n \n // 1) Approval\n@@ -136,8 +137,13 @@ impl ToolOrchestrator {\n network_approval_context: None,\n };\n let decision = tool.start_approval_async(req, approval_ctx).await;\n+ let otel_source = if routes_approval_to_guardian(turn_ctx) {\n+ otel_automated_reviewer.clone()\n+ } else {\n+ otel_user.clone()\n+ };\n \n- otel.tool_decision(otel_tn, otel_ci, &decision, otel_user.clone());\n+ otel.tool_decision(otel_tn, otel_ci, &dec...", - "path": "codex-rs/core/src/tools/orchestrator.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -31,6 +31,7 @@ pub use codex_utils_string::sanitize_metric_tag_value;\n #[derive(Debug, Clone, Serialize, Display)]\n #[serde(rename_all = \"snake_case\")]\n pub enum ToolDecisionSource {\n+ AutomatedReviewer,\n Config,\n User,\n }", - "path": "codex-rs/otel/src/lib.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "## Summary\n\n- log guardian-reviewed tool approvals as `source=automated_reviewer` in `codex.tool_decision`\n- keep direct user approvals as `source=user` and config-driven approvals as `source=config`\n\n## Testing\n\n- `/Users/gabec/.codex/skills/codex-oss-fastdev/scripts/codex-rs-fmt-quiet.sh`\n- `/Users/gabec/.codex/skills/codex-oss-fastdev/scripts/codex-rs-test-quiet.sh -p codex-otel` (fails in sandboxed loopback bind tests under `otel/tests/suite/otlp_http_loopback.rs`)\n- `cargo test -p codex-core guardian -- --nocapture` (original-tree run reached Guardian tests and only hit sandbox-related listener/proxy failures)\n", - "labels": [], - "merged_at": "2026-03-19T19:10:42Z", - "number": 15201, - "state": "merged", - "title": "Log automated reviewer approval sources distinctly", - "url": "https://github.com/openai/codex/pull/15201" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15203.json b/artifacts/github/bundles/openai-codex-pr-15203.json deleted file mode 100644 index 740bc84..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15203.json +++ /dev/null @@ -1,260 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "cconger", - "committed_at": "2026-03-19T17:57:10Z", - "message": "Add v8-poc consumer of our new built v8", - "sha": "733e86887874527c37c2464fb0ecef7b0635609e", - "url": "https://github.com/openai/codex/commit/733e86887874527c37c2464fb0ecef7b0635609e" - }, - { - "author": "cconger", - "committed_at": "2026-03-20T01:46:39Z", - "message": "Build v8-poc as part of CI", - "sha": "20ce3015e6504f7284306cc6419fc6027e4f3cfd", - "url": "https://github.com/openai/codex/commit/20ce3015e6504f7284306cc6419fc6027e4f3cfd" - }, - { - "author": "cconger", - "committed_at": "2026-03-20T03:10:07Z", - "message": "Working on all platforms", - "sha": "9ae2f06755be6657101c8882dd2573677bc7b28b", - "url": "https://github.com/openai/codex/commit/9ae2f06755be6657101c8882dd2573677bc7b28b" - }, - { - "author": "cconger", - "committed_at": "2026-03-20T03:13:54Z", - "message": "Ignore codex-v8-poc", - "sha": "1fb916af19f7d5bff96a034dfeedc6960bb61cfa", - "url": "https://github.com/openai/codex/commit/1fb916af19f7d5bff96a034dfeedc6960bb61cfa" - }, - { - "author": "cconger", - "committed_at": "2026-03-20T03:24:53Z", - "message": "Prepull our artifacts for musl", - "sha": "f8a239e0c070b9f351d2595550f3408fdda21ddf", - "url": "https://github.com/openai/codex/commit/f8a239e0c070b9f351d2595550f3408fdda21ddf" - }, - { - "author": "cconger", - "committed_at": "2026-03-20T03:30:05Z", - "message": "Path", - "sha": "b6b58e185058acdd7f66a0cf2ad3b97433abb2f7", - "url": "https://github.com/openai/codex/commit/b6b58e185058acdd7f66a0cf2ad3b97433abb2f7" - }, - { - "author": "cconger", - "committed_at": "2026-03-20T03:40:31Z", - "message": "Fix bazel build for v8-poc", - "sha": "7b806611e2bde64a7a0ef2b2fbc85f5c203ea12a", - "url": "https://github.com/openai/codex/commit/7b806611e2bde64a7a0ef2b2fbc85f5c203ea12a" - }, - { - "author": "cconger", - "committed_at": "2026-03-20T03:52:36Z", - "message": "Repatch rusty_v8", - "sha": "393b5603d192146cb5c27ba9e84e07cea9eac044", - "url": "https://github.com/openai/codex/commit/393b5603d192146cb5c27ba9e84e07cea9eac044" - }, - { - "author": "cconger", - "committed_at": "2026-03-20T07:45:05Z", - "message": "Always link v8", - "sha": "6c0deb2808c38167474b25637f1d7b0fbd61866e", - "url": "https://github.com/openai/codex/commit/6c0deb2808c38167474b25637f1d7b0fbd61866e" - }, - { - "author": "cconger", - "committed_at": "2026-03-20T08:17:33Z", - "message": "Build v8 for our bazel platforms", - "sha": "ffbe9423df3b633637be0b61c4408da2f6352979", - "url": "https://github.com/openai/codex/commit/ffbe9423df3b633637be0b61c4408da2f6352979" - }, - { - "author": "cconger", - "committed_at": "2026-03-20T17:20:20Z", - "message": "Merge branch 'main' into v8-bazel-use", - "sha": "118540bf65977714fdcb1826f20d97f885bdad54", - "url": "https://github.com/openai/codex/commit/118540bf65977714fdcb1826f20d97f885bdad54" - }, - { - "author": "cconger", - "committed_at": "2026-03-20T17:38:07Z", - "message": "Remove local=1", - "sha": "e43c0f6f2b5e5ca1fc444db8ba30aac3db2e36a6", - "url": "https://github.com/openai/codex/commit/e43c0f6f2b5e5ca1fc444db8ba30aac3db2e36a6" - }, - { - "author": "cconger", - "committed_at": "2026-03-20T18:19:53Z", - "message": "darwin exception", - "sha": "9edd9a394964c559751caff0c1a61a1735621778", - "url": "https://github.com/openai/codex/commit/9edd9a394964c559751caff0c1a61a1735621778" - }, - { - "author": "cconger", - "committed_at": "2026-03-20T18:34:30Z", - "message": "Merge branch 'main' into v8-bazel-use", - "sha": "0285917c3bb3decc635ef5473c174a43027dc155", - "url": "https://github.com/openai/codex/commit/0285917c3bb3decc635ef5473c174a43027dc155" - } - ], - "default_branch": "main", - "docs_refs": [ - "third_party/v8/README.md" - ], - "examples_refs": [], - "extracted_flags": [ - "CFLAGS=${cflags}\"", - "GITHUB_ENV", - "CXXFLAGS=${cxxflags}\"", - "TARGET", - "GITHUB_WORKSPACE", - "RUNNER_TEMP", - "RUSTY_V8_ARCHIVE=${archive}\"", - "RUSTY_V8_SRC_BINDING_PATH=${binding_path}\"", - "BUILD", - "RUSTY_V8_ARCHIVE", - "RUSTY_V8_SRC_BINDING_PATH", - "INIT", - "UTF", - "OUT_DIR", - "CARGO_FEATURE_USE_CUSTOM_LIBCXX", - "GN_ARGS", - "L2462", - "CXXSTDLIB", - "--config", - "--config_value", - "--output_base", - "V8_COPTS", - "V8_STATIC_LIBRARY_FEATURES", - "EOF", - "GNU", - "MODULE", - "RUSTY_V8_MIRROR" - ], - "files": [ - { - "additions": 18, - "deletions": 0, - "patch_excerpt": "@@ -447,6 +447,24 @@ jobs:\n echo \"CFLAGS=${cflags}\" >> \"$GITHUB_ENV\"\n echo \"CXXFLAGS=${cxxflags}\" >> \"$GITHUB_ENV\"\n \n+ - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl' }}\n+ name: Configure musl rusty_v8 artifact overrides\n+ env:\n+ TARGET: ${{ matrix.target }}\n+ shell: bash\n+ run: |\n+ set -euo pipefail\n+ version=\"$(python3 \"${GITHUB_WORKSPACE}/.github/scripts/rusty_v8_bazel.py\" resolved-v8-crate-version)\"\n+ release_tag=\"rusty-v8-v${version}\"\n+ base_url=\"https://github.com/openai/codex/releases/download/${release_tag}\"\n+ archive=\"https://github.com/openai/codex/releases/download/rusty-v8-v${version}/librusty_v8_release_${TARGET}.a.gz\"\n+ binding_dir=\"${RUNNER_TEMP}/rusty_v8\"\n+ binding_path=\"${binding_dir}/src_bind...", - "path": ".github/workflows/rust-ci.yml", - "status": "modified" - }, - { - "additions": 18, - "deletions": 0, - "patch_excerpt": "@@ -210,6 +210,24 @@ jobs:\n echo \"CFLAGS=${cflags}\" >> \"$GITHUB_ENV\"\n echo \"CXXFLAGS=${cxxflags}\" >> \"$GITHUB_ENV\"\n \n+ - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl' }}\n+ name: Configure musl rusty_v8 artifact overrides\n+ env:\n+ TARGET: ${{ matrix.target }}\n+ shell: bash\n+ run: |\n+ set -euo pipefail\n+ version=\"$(python3 \"${GITHUB_WORKSPACE}/.github/scripts/rusty_v8_bazel.py\" resolved-v8-crate-version)\"\n+ release_tag=\"rusty-v8-v${version}\"\n+ base_url=\"https://github.com/openai/codex/releases/download/${release_tag}\"\n+ archive=\"https://github.com/openai/codex/releases/download/rusty-v8-v${version}/librusty_v8_release_${TARGET}.a.gz\"\n+ binding_dir=\"${RUNNER_TEMP}/rusty_v8\"\n+ binding_path=\"${binding_dir}/src_bind...", - "path": ".github/workflows/rust-release.yml", - "status": "modified" - }, - { - "additions": 107, - "deletions": 0, - "patch_excerpt": "@@ -144,6 +144,33 @@ crate.annotation(\n )\n \n http_archive = use_repo_rule(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\")\n+http_file = use_repo_rule(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_file\")\n+new_local_repository = use_repo_rule(\"@bazel_tools//tools/build_defs/repo:local.bzl\", \"new_local_repository\")\n+\n+new_local_repository(\n+ name = \"v8_targets\",\n+ build_file = \"//third_party/v8:BUILD.bazel\",\n+ path = \"third_party/v8\",\n+)\n+\n+crate.annotation(\n+ build_script_data = [\n+ \"@v8_targets//:rusty_v8_archive_for_target\",\n+ \"@v8_targets//:rusty_v8_binding_for_target\",\n+ ],\n+ build_script_env = {\n+ \"RUSTY_V8_ARCHIVE\": \"$(execpath @v8_targets//:rusty_v8_archive_for_target)\",\n+ \"RUSTY_V8_SRC_BINDING_PATH\": \"$(execpath @v8_targets//:rusty_v8_binding_for_target)\",\n+ },\n+ crate = \"v8\",\n+ gen_build_script = \"on\",\n+...", - "path": "MODULE.bazel", - "status": "modified" - }, - { - "additions": 19, - "deletions": 0, - "patch_excerpt": "@@ -680,6 +680,7 @@\n \"cached_0.56.0\": \"{\\\"dependencies\\\":[{\\\"default_features\\\":false,\\\"name\\\":\\\"ahash\\\",\\\"optional\\\":true,\\\"req\\\":\\\"^0.8\\\"},{\\\"features\\\":[\\\"attributes\\\"],\\\"kind\\\":\\\"dev\\\",\\\"name\\\":\\\"async-std\\\",\\\"req\\\":\\\"^1.6\\\"},{\\\"name\\\":\\\"async-trait\\\",\\\"optional\\\":true,\\\"req\\\":\\\"^0.1\\\"},{\\\"name\\\":\\\"cached_proc_macro\\\",\\\"optional\\\":true,\\\"req\\\":\\\"^0.25.0\\\"},{\\\"name\\\":\\\"cached_proc_macro_types\\\",\\\"optional\\\":true,\\\"req\\\":\\\"^0.1.1\\\"},{\\\"kind\\\":\\\"dev\\\",\\\"name\\\":\\\"copy_dir\\\",\\\"req\\\":\\\"^0.1.3\\\"},{\\\"name\\\":\\\"directories\\\",\\\"optional\\\":true,\\\"req\\\":\\\"^6.0\\\"},{\\\"default_features\\\":false,\\\"name\\\":\\\"futures\\\",\\\"optional\\\":true,\\\"req\\\":\\\"^0.3\\\"},{\\\"kind\\\":\\\"dev\\\",\\\"name\\\":\\\"googletest\\\",\\\"req\\\":\\\"^0.11.0\\\"},{\\\"default_features\\\":false,\\\"features\\\":[\\\"inline-more\\\"],\\\"name\\\":\\\"hashbrown\\\",\\\"req\\\":\\\"^0.15\\\"},{\\\"name\\\":\\\"once_cell\\\",\\\"req\\\":\\\"^1\\\"},{\\\"name\\\":\\\"r2d2\\\",\\\"optional\\\":true,\\\"req\\\"...", - "path": "MODULE.bazel.lock", - "status": "modified" - }, - { - "additions": 231, - "deletions": 7, - "patch_excerpt": "@@ -949,6 +949,8 @@ dependencies = [\n \"cexpr\",\n \"clang-sys\",\n \"itertools 0.13.0\",\n+ \"log\",\n+ \"prettyplease\",\n \"proc-macro2\",\n \"quote\",\n \"regex\",\n@@ -1152,6 +1154,16 @@ version = \"0.1.1\"\n source = \"registry+https://github.com/rust-lang/crates.io-index\"\n checksum = \"ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0\"\n \n+[[package]]\n+name = \"calendrical_calculations\"\n+version = \"0.2.3\"\n+source = \"registry+https://github.com/rust-lang/crates.io-index\"\n+checksum = \"3a0b39595c6ee54a8d0900204ba4c401d0ab4eb45adaf07178e8d017541529e7\"\n+dependencies = [\n+ \"core_maths\",\n+ \"displaydoc\",\n+]\n+\n [[package]]\n name = \"cassowary\"\n version = \"0.3.0\"\n@@ -1584,7 +1596,7 @@ dependencies = [\n \"thiserror 2.0.18\",\n \"tokio\",\n \"url\",\n- \"which\",\n+ \"which 8.0.0\",\n \"wiremock\",\n \"zip\",\n ]\n@@ -1930,7 +1942,7 @@ dependencies = [\n \"url\",\n \"uuid\",\n \"walkdir\",\n- \"which\",\n+ \"which 8.0.0\",\n \"wildm...", - "path": "codex-rs/Cargo.lock", - "status": "modified" - }, - { - "additions": 5, - "deletions": 1, - "patch_excerpt": "@@ -45,6 +45,7 @@ members = [\n \"otel\",\n \"tui\",\n \"tui_app_server\",\n+ \"v8-poc\",\n \"utils/absolute-path\",\n \"utils/cargo-bin\",\n \"utils/git\",\n@@ -137,6 +138,7 @@ codex-test-macros = { path = \"test-macros\" }\n codex-terminal-detection = { path = \"terminal-detection\" }\n codex-tui = { path = \"tui\" }\n codex-tui-app-server = { path = \"tui_app_server\" }\n+codex-v8-poc = { path = \"v8-poc\" }\n codex-utils-absolute-path = { path = \"utils/absolute-path\" }\n codex-utils-approval-presets = { path = \"utils/approval-presets\" }\n codex-utils-cache = { path = \"utils/cache\" }\n@@ -245,6 +247,7 @@ regex-lite = \"0.1.8\"\n reqwest = \"0.12\"\n rmcp = { version = \"0.15.0\", default-features = false }\n runfiles = { git = \"https://github.com/dzbarsky/rules_rust\", rev = \"b56cbaa8465e74127f1ea216f813cd377295ad81\" }\n+v8 = \"=146.4.0\"\n rustls = { version = \"0.23\", default-features = false, features = [\n ...", - "path": "codex-rs/Cargo.toml", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,2 @@\n+/target/\n+/target-rs/", - "path": "codex-rs/v8-poc/.gitignore", - "status": "added" - }, - { - "additions": 12, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,12 @@\n+load(\"//:defs.bzl\", \"codex_rust_crate\")\n+\n+codex_rust_crate(\n+ name = \"v8-poc\",\n+ crate_name = \"codex_v8_poc\",\n+ deps_extra = [\"@crates//:v8\"],\n+)\n+\n+alias(\n+ name = \"v8-poc-rusty-v8\",\n+ actual = \":v8-poc\",\n+)", - "path": "codex-rs/v8-poc/BUILD.bazel", - "status": "added" - }, - { - "additions": 18, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,18 @@\n+[package]\n+name = \"codex-v8-poc\"\n+version.workspace = true\n+edition.workspace = true\n+license.workspace = true\n+\n+[lib]\n+name = \"codex_v8_poc\"\n+path = \"src/lib.rs\"\n+\n+[lints]\n+workspace = true\n+\n+[dependencies]\n+v8 = { workspace = true }\n+\n+[dev-dependencies]\n+pretty_assertions = { workspace = true }", - "path": "codex-rs/v8-poc/Cargo.toml", - "status": "added" - }, - { - "additions": 65, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,65 @@\n+//! Bazel-wired proof-of-concept crate reserved for future V8 experiments.\n+\n+/// Returns the Bazel label for this proof-of-concept crate.\n+#[must_use]\n+pub fn bazel_target() -> &'static str {\n+ \"//codex-rs/v8-poc:v8-poc\"\n+}\n+\n+/// Returns the embedded V8 version.\n+#[must_use]\n+pub fn embedded_v8_version() -> &'static str {\n+ v8::V8::get_version()\n+}\n+\n+#[cfg(test)]\n+mod tests {\n+ use pretty_assertions::assert_eq;\n+ use std::sync::Once;\n+\n+ use super::bazel_target;\n+\n+ fn initialize_v8() {\n+ static INIT: Once = Once::new();\n+\n+ INIT.call_once(|| {\n+ v8::V8::initialize_platform(v8::new_default_platform(0, false).make_shared());\n+ v8::V8::initialize();\n+ });\n+ }\n+\n+ fn evaluate_expression(expression: &str) -> String {\n+ initialize_v8();\n+\n+ let isolate = &mut v8::Isolate::new(Default::default...", - "path": "codex-rs/v8-poc/src/lib.rs", - "status": "added" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -1,5 +1,6 @@\n exports_files([\n \"aws-lc-sys_memcmp_check.patch\",\n+ \"rusty_v8_prebuilt_out_dir.patch\",\n \"v8_bazel_rules.patch\",\n \"v8_module_deps.patch\",\n \"v8_source_portability.patch\",", - "path": "patches/BUILD.bazel", - "status": "modified" - }, - { - "additions": 52, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,52 @@\n+--- a/build.rs\n++++ b/build.rs\n+@@ -577,7 +577,23 @@\n+ path\n+ }\n+ \n++fn out_dir_abs() -> PathBuf {\n++ let cwd = env::current_dir().unwrap();\n++\n++ // target/debug/build/rusty_v8-d9e5a424d4f96994/out/\n++ let out_dir = env::var_os(\"OUT_DIR\").expect(\n++ \"The 'OUT_DIR' environment is not set (it should be something like \\\n++ 'target/debug/rusty_v8-{hash}').\",\n++ );\n++\n++ cwd.join(out_dir)\n++}\n++\n+ fn static_lib_dir() -> PathBuf {\n++ if env::var_os(\"RUSTY_V8_ARCHIVE\").is_some() {\n++ return out_dir_abs().join(\"gn_out\").join(\"obj\");\n++ }\n++\n+ build_dir().join(\"gn_out\").join(\"obj\")\n+ }\n+ \n+@@ -794,22 +810,23 @@\n+ }\n+ \n+ fn print_link_flags() {\n++ let target = env::var(\"TARGET\").unwrap();\n+ println!(\"cargo:rustc-link-lib=static=rusty_v8\");\n+ let should_dyn_link_libcxx = env::var(\"CARGO_FEATURE_USE_CUSTOM_LIBCXX\")\n+ .is_err()\n++ || (target.contain...", - "path": "patches/rusty_v8_prebuilt_out_dir.patch", - "status": "added" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -121,7 +121,7 @@ index 85f31b7..7314584 100644\n ],\n outs = [\n \"include/inspector/Debugger.h\",\n-@@ -4426,15 +4426,19 @@ genrule(\n+@@ -4426,15 +4426,18 @@ genrule(\n \"src/inspector/protocol/Schema.cpp\",\n \"src/inspector/protocol/Schema.h\",\n ],\n@@ -134,7 +134,7 @@ index 85f31b7..7314584 100644\n --config $(location :src/inspector/inspector_protocol_config.json) \\\n --config_value protocol.path=$(location :include/js_protocol.pdl) \\\n --output_base $(@D)/src/inspector\",\n- local = 1,\n+- local = 1,\n message = \"Generating inspector files\",\n tools = [\n - \":code_generator\",", - "path": "patches/v8_bazel_rules.patch", - "status": "modified" - }, - { - "additions": 167, - "deletions": 77, - "patch_excerpt": "@@ -4,6 +4,158 @@ load(\"@rules_cc//cc:defs.bzl\", \"cc_library\")\n \n package(default_visibility = [\"//visibility:public\"])\n \n+config_setting(\n+ name = \"platform_aarch64_unknown_linux_musl\",\n+ constraint_values = [\n+ \"@platforms//cpu:aarch64\",\n+ \"@platforms//os:linux\",\n+ \"@llvm//constraints/libc:musl\",\n+ ],\n+)\n+\n+config_setting(\n+ name = \"platform_x86_64_unknown_linux_musl\",\n+ constraint_values = [\n+ \"@platforms//cpu:x86_64\",\n+ \"@platforms//os:linux\",\n+ \"@llvm//constraints/libc:musl\",\n+ ],\n+)\n+\n+alias(\n+ name = \"v8_146_4_0_x86_64_apple_darwin\",\n+ actual = \"@rusty_v8_146_4_0_x86_64_apple_darwin_archive//file\",\n+)\n+\n+alias(\n+ name = \"v8_146_4_0_aarch64_apple_darwin\",\n+ actual = \"@rusty_v8_146_4_0_aarch64_apple_darwin_archive//file\",\n+)\n+\n+alias(\n+ name = \"v8_146_4_0_x86_64_unknown_linux_gnu\",\n+ actual = \"@rusty_v8_14...", - "path": "third_party/v8/BUILD.bazel", - "status": "modified" - }, - { - "additions": 32, - "deletions": 30, - "patch_excerpt": "@@ -1,45 +1,47 @@\n-# `rusty_v8` Release Artifacts\n+# `rusty_v8` Consumer Artifacts\n \n-This directory contains the Bazel packaging used to build and stage\n-target-specific `rusty_v8` release artifacts for Bazel-managed consumers.\n+This directory wires the `v8` crate to exact-version Bazel inputs.\n+Bazel consumer builds use:\n+\n+- upstream `denoland/rusty_v8` release archives on Windows\n+- source-built V8 archives on Darwin, GNU Linux, and musl Linux\n+- `openai/codex` release assets for published musl release pairs\n+\n+Cargo builds still use prebuilt `rusty_v8` archives by default. Only Bazel\n+overrides `RUSTY_V8_ARCHIVE`/`RUSTY_V8_SRC_BINDING_PATH` in `MODULE.bazel` to\n+select source-built local archives for its consumer builds.\n \n Current pinned versions:\n \n - Rust crate: `v8 = =146.4.0`\n-- Embedded upstream V8 source: `14.6.202.9`\n+- Embedded upstream V8 source for musl release builds: `1...", - "path": "third_party/v8/README.md", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Discovered via hourly merged-PR sync: https://github.com/openai/codex/pull/15203" - ], - "primary_pr": { - "body": "This adds a dummy v8-poc project that in Cargo links against our prebuilt binaries and the ones provided by rusty_v8 for non musl platforms. This demonstrates that we can successfully link and use v8 on all platforms that we want to target.\r\n\r\nIn bazel things are slightly more complicated. Since the libraries as published have libc++ linked in already we end up with a lot of double linked symbols if we try to use them in bazel land. Instead we fall back to building rusty_v8 and v8 from source (cached of course) on the platforms we ship to.\r\n\r\nThere is likely some compatibility drift in the windows bazel builder that we'll need to reconcile before we can re-enable them. I'm happy to be on the hook to unwind that.", - "labels": [], - "merged_at": "2026-03-20T19:08:25Z", - "number": 15203, - "state": "merged", - "title": "Add v8-poc consumer of our new built v8", - "url": "https://github.com/openai/codex/pull/15203" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15206.json b/artifacts/github/bundles/openai-codex-pr-15206.json deleted file mode 100644 index a40bc0d..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15206.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "owenlin0", - "committed_at": "2026-03-19T18:38:47Z", - "message": "feat(tracing): tag app-server turn spans with turn_id", - "sha": "7f9f93e057d7c73a84ea071aabcc88ddfe743150", - "url": "https://github.com/openai/codex/commit/7f9f93e057d7c73a84ea071aabcc88ddfe743150" - }, - { - "author": "owenlin0", - "committed_at": "2026-03-19T18:46:29Z", - "message": "also record turn id for turn/steer and turn/interrupt", - "sha": "8e042a093cceac78a48d1d8cabd6f6247e51532f", - "url": "https://github.com/openai/codex/commit/8e042a093cceac78a48d1d8cabd6f6247e51532f" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "API" - ], - "files": [ - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -107,6 +107,7 @@ fn app_server_request_span_template(\n app_server.api_version = \"v2\",\n app_server.client_name = field::Empty,\n app_server.client_version = field::Empty,\n+ turn.id = field::Empty,\n )\n }", - "path": "codex-rs/app-server/src/app_server_tracing.rs", - "status": "modified" - }, - { - "additions": 10, - "deletions": 1, - "patch_excerpt": "@@ -6024,6 +6024,9 @@ impl CodexMessageProcessor {\n \n match turn_id {\n Ok(turn_id) => {\n+ self.outgoing\n+ .record_request_turn_id(&request_id, &turn_id)\n+ .await;\n let turn = Turn {\n id: turn_id.clone(),\n items: vec![],\n@@ -6076,6 +6079,9 @@ impl CodexMessageProcessor {\n .await;\n return;\n }\n+ self.outgoing\n+ .record_request_turn_id(&request_id, ¶ms.expected_turn_id)\n+ .await;\n if let Err(error) = Self::validate_v2_input_limit(¶ms.input) {\n self.outgoing.send_error(request_id, error).await;\n return;\n@@ -6556,7 +6562,10 @@ impl CodexMessageProcessor {\n request_id: ConnectionRequestId,\n params: TurnInterruptParams,\n ) {\n- let TurnInterrup...", - "path": "codex-rs/app-server/src/codex_message_processor.rs", - "status": "modified" - }, - { - "additions": 5, - "deletions": 1, - "patch_excerpt": "@@ -580,7 +580,7 @@ async fn turn_start_jsonrpc_span_parents_core_turn_spans() -> Result<()> {\n parent_span_id: remote_parent_span_id,\n context: remote_trace,\n } = RemoteTrace::new(\"00000000000000000000000000000077\", \"0000000000000088\");\n- let _: TurnStartResponse = harness\n+ let turn_start_response: TurnStartResponse = harness\n .request(\n ClientRequest::TurnStart {\n request_id: RequestId::Integer(3),\n@@ -628,6 +628,10 @@ async fn turn_start_jsonrpc_span_parents_core_turn_spans() -> Result<()> {\n assert_eq!(server_request_span.parent_span_id, remote_parent_span_id);\n assert!(server_request_span.parent_span_is_remote);\n assert_eq!(server_request_span.span_context.trace_id(), remote_trace_id);\n+ assert_eq!(\n+ span_attr(server_request_span, \"turn.id\"),\n+ Some(turn_start_response.turn.id.as_str())\n+ ...", - "path": "codex-rs/app-server/src/message_processor/tracing_tests.rs", - "status": "modified" - }, - { - "additions": 15, - "deletions": 0, - "patch_excerpt": "@@ -75,6 +75,10 @@ impl RequestContext {\n pub(crate) fn span(&self) -> Span {\n self.span.clone()\n }\n+\n+ fn record_turn_id(&self, turn_id: &str) {\n+ self.span.record(\"turn.id\", turn_id);\n+ }\n }\n \n #[derive(Debug, Clone)]\n@@ -217,6 +221,17 @@ impl OutgoingMessageSender {\n .and_then(RequestContext::request_trace)\n }\n \n+ pub(crate) async fn record_request_turn_id(\n+ &self,\n+ request_id: &ConnectionRequestId,\n+ turn_id: &str,\n+ ) {\n+ let request_contexts = self.request_contexts.lock().await;\n+ if let Some(request_context) = request_contexts.get(request_id) {\n+ request_context.record_turn_id(turn_id);\n+ }\n+ }\n+\n async fn take_request_context(\n &self,\n request_id: &ConnectionRequestId,", - "path": "codex-rs/app-server/src/outgoing_message.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "So we can find and filter spans by `turn.id`.\r\n\r\nWe do this for the `turn/start`, `turn/steer`, and `turn/interrupt` APIs.\r\n", - "labels": [], - "merged_at": "2026-03-19T20:07:19Z", - "number": 15206, - "state": "merged", - "title": "feat(tracing): tag app-server turn spans with turn_id", - "url": "https://github.com/openai/codex/pull/15206" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15207.json b/artifacts/github/bundles/openai-codex-pr-15207.json deleted file mode 100644 index f40063f..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15207.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "iceweasel-oai", - "committed_at": "2026-03-19T18:39:48Z", - "message": "add specific tool guidance for Windows destructive commands", - "sha": "4151776de0adf6ddc5940125b0d42cf5bddef08b", - "url": "https://github.com/openai/codex/commit/4151776de0adf6ddc5940125b0d42cf5bddef08b" - }, - { - "author": "iceweasel-oai", - "committed_at": "2026-03-19T19:09:35Z", - "message": "fix test compile error", - "sha": "5c8adebb02266094d5017fd30f3b576645e28e49", - "url": "https://github.com/openai/codex/commit/5c8adebb02266094d5017fd30f3b576645e28e49" - }, - { - "author": "iceweasel-oai", - "committed_at": "2026-03-19T20:00:31Z", - "message": "fix environment compile error", - "sha": "58ab27ff592e1a7723d07c56862f15ad9584a7b5", - "url": "https://github.com/openai/codex/commit/58ab27ff592e1a7723d07c56862f15ad9584a7b5" - }, - { - "author": "iceweasel-oai", - "committed_at": "2026-03-19T20:44:00Z", - "message": "fix failing test", - "sha": "503fc5670f9cfc2d58954a821f658b260c982df6", - "url": "https://github.com/openai/codex/commit/503fc5670f9cfc2d58954a821f658b260c982df6" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "PTY", - "TODO", - "FOO='bar';", - "FOO" - ], - "files": [ - { - "additions": 35, - "deletions": 11, - "patch_excerpt": "@@ -587,6 +587,12 @@ fn create_request_permissions_schema() -> JsonSchema {\n }\n }\n \n+fn windows_destructive_filesystem_guidance() -> &'static str {\n+ r#\"Windows safety rules:\n+- Do not compose destructive filesystem commands across shells. Do not enumerate paths in PowerShell and then pass them to `cmd /c`, batch builtins, or another shell for deletion or moving. Use one shell end-to-end, prefer native PowerShell cmdlets such as `Remove-Item` / `Move-Item` with `-LiteralPath`, and avoid string-built shell commands for file operations.\n+- Before any recursive delete or move on Windows, verify the resolved absolute target paths stay within the intended workspace or explicitly named target directory. Never issue a recursive delete or move against a computed path if the final target has not been checked.\"#\n+}\n+\n fn create_approval_parameters(\n exec_permission_approvals_enabled: bo...", - "path": "codex-rs/core/src/tools/spec.rs", - "status": "modified" - }, - { - "additions": 37, - "deletions": 5, - "patch_excerpt": "@@ -50,6 +50,10 @@ fn discoverable_connector(id: &str, name: &str, description: &str) -> Discoverab\n }))\n }\n \n+fn windows_shell_safety_description() -> String {\n+ format!(\"\\n\\n{}\", super::windows_destructive_filesystem_guidance())\n+}\n+\n fn search_capable_model_info() -> ModelInfo {\n let config = test_config();\n let mut model_info =\n@@ -2363,7 +2367,7 @@ fn test_shell_tool() {\n assert_eq!(name, \"shell\");\n \n let expected = if cfg!(windows) {\n- r#\"Runs a Powershell command (Windows) and returns its output. Arguments to `shell` will be passed to CreateProcessW(). Most commands should be prefixed with [\"powershell.exe\", \"-Command\"].\n+ r#\"Runs a Powershell command (Windows) and returns its output. Arguments to `shell` will be passed to CreateProcessW(). Most commands should be prefixed with [\"powershell.exe\", \"-Command\"].\n \n Examples of valid command st...", - "path": "codex-rs/core/src/tools/spec_tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -36,7 +36,7 @@ pub(crate) struct ExecServerFileSystem {\n impl Default for ExecServerFileSystem {\n fn default() -> Self {\n Self {\n- file_system: Arc::new(Environment.get_filesystem()),\n+ file_system: Arc::new(Environment::default().get_filesystem()),\n }\n }\n }", - "path": "codex-rs/exec-server/src/server/filesystem.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "updated Windows shell/unified_exec tool descriptions:\r\n\r\n`exec_command`\r\n```text\r\nRuns a command in a PTY, returning output or a session ID for ongoing interaction.\r\n\r\nWindows safety rules:\r\n- Do not compose destructive filesystem commands across shells. Do not enumerate paths in PowerShell and then pass them to `cmd /c`, batch builtins, or another shell for deletion or moving. Use one shell end-to-end, prefer native PowerShell cmdlets such as `Remove-Item` / `Move-Item` with `-LiteralPath`, and avoid string-built shell commands for file operations.\r\n- Before any recursive delete or move on Windows, verify the resolved absolute target paths stay within the intended workspace or explicitly named target directory. Never issue a recursive delete or move against a computed path if the final target has not been checked.\r\n```\r\n\r\n`shell`\r\n```text\r\nRuns a Powershell command (Windows) and returns its output. Arguments to `shell` will be passed to CreateProcessW(). Most commands should be prefixed with [\"powershell.exe\", \"-Command\"].\r\n\r\nExamples of valid command strings:\r\n\r\n- ls -a (show hidden): [\"powershell.exe\", \"-Command\", \"Get-ChildItem -Force\"]\r\n- recursive find by name: [\"powershell.exe\", \"-Command\", \"Get-ChildItem -Recurse -Filter *.py\"]\r\n- recursive grep: [\"powershell.exe\", \"-Command\", \"Get-ChildItem -Path C:\\\\myrepo -Recurse | Select-String -Pattern 'TODO' -CaseSensitive\"]\r\n- ps aux | grep python: [\"powershell.exe\", \"-Command\", \"Get-Process | Where-Object { $_.ProcessName -like '*python*' }\"]\r\n- setting an env var: [\"powershell.exe\", \"-Command\", \"$env:FOO='bar'; echo $env:FOO\"]\r\n- running an inline Python script: [\"powershell.exe\", \"-Command\", \"@'\\nprint('Hello, world!')\\n'@ | python -\"]\r\n\r\nWindows safety rules:\r\n- Do not compose destructive filesystem commands across shells. Do not enumerate paths in PowerShell and then pass them to `cmd /c`, batch builtins, or another shell for deletion or moving. Use one shell end-to-end, prefer native PowerShell cmdlets such as `Remove-Item` / `Move-Item` with `-LiteralPath`, and avoid string-built shell commands for file operations.\r\n- Before any recursive delete or move on Windows, verify the resolved absolute target paths stay within the intended workspace or explicitly named target directory. Never issue a recursive delete or move against a computed path if the final target has not been checked.\r\n```\r\n\r\n`shell_command`\r\n```text\r\nRuns a Powershell command (Windows) and returns its output.\r\n\r\nExamples of valid command strings:\r\n\r\n- ls -a (show hidden): \"Get-ChildItem -Force\"\r\n- recursive find by name: \"Get-ChildItem -Recurse -Filter *.py\"\r\n- recursive grep: \"Get-ChildItem -Path C:\\\\myrepo -Recurse | Select-String -Pattern 'TODO' -CaseSensitive\"\r\n- ps aux | grep python: \"Get-Process | Where-Object { $_.ProcessName -like '*python*' }\"\r\n- setting an env var: \"$env:FOO='bar'; echo $env:FOO\"\r\n- running an inline Python script: \"@'\\nprint('Hello, world!')\\n'@ | python -\"\r\n\r\nWindows safety rules:\r\n- Do not compose destructive filesystem commands across shells. Do not enumerate paths in PowerShell and then pass them to `cmd /c`, batch builtins, or another shell for deletion or moving. Use one shell end-to-end, prefer native PowerShell cmdlets such as `Remove-Item` / `Move-Item` with `-LiteralPath`, and avoid string-built shell commands for file operations.\r\n- Before any recursive delete or move on Windows, verify the resolved absolute target paths stay within the intended workspace or explicitly named target directory. Never issue a recursive delete or move against a computed path if the final target has not been checked.\r\n```\r\n", - "labels": [], - "merged_at": "2026-03-19T21:09:35Z", - "number": 15207, - "state": "merged", - "title": "add specific tool guidance for Windows destructive commands", - "url": "https://github.com/openai/codex/pull/15207" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15215.json b/artifacts/github/bundles/openai-codex-pr-15215.json deleted file mode 100644 index 120c9b3..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15215.json +++ /dev/null @@ -1,186 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "canvrno-oai", - "committed_at": "2026-03-18T23:56:59Z", - "message": "Initial plugins TUI - list and read only. tui + tui_app_server", - "sha": "ff5efc86c9e2f54c718fc914a8eeef5ae6f88e4d", - "url": "https://github.com/openai/codex/commit/ff5efc86c9e2f54c718fc914a8eeef5ae6f88e4d" - }, - { - "author": "canvrno-oai", - "committed_at": "2026-03-19T20:26:43Z", - "message": "Merge branch 'main' into canvrno/plugins_tui_menu", - "sha": "93f19ae59376f47637a375d6da772707315c319f", - "url": "https://github.com/openai/codex/commit/93f19ae59376f47637a375d6da772707315c319f" - }, - { - "author": "canvrno-oai", - "committed_at": "2026-03-19T21:53:45Z", - "message": "Merge branch 'main' into canvrno/plugins_tui_menu", - "sha": "17f6be542ce1b22c643aed705d59848049349297", - "url": "https://github.com/openai/codex/commit/17f6be542ce1b22c643aed705d59848049349297" - }, - { - "author": "canvrno-oai", - "committed_at": "2026-03-20T00:47:06Z", - "message": "Combine marketplace_path and plugin_name into PluginReadParams", - "sha": "a1011659d71c82d7dc6586ad43d816627c3147c9", - "url": "https://github.com/openai/codex/commit/a1011659d71c82d7dc6586ad43d816627c3147c9" - }, - { - "author": "canvrno-oai", - "committed_at": "2026-03-20T03:30:31Z", - "message": "Merge branch 'main' into canvrno/plugins_tui_menu", - "sha": "170481c1648c7c07f5e51c82c193b5bcea6b5ec0", - "url": "https://github.com/openai/codex/commit/170481c1648c7c07f5e51c82c193b5bcea6b5ec0" - }, - { - "author": "canvrno-oai", - "committed_at": "2026-03-20T03:59:00Z", - "message": "Merge fixes", - "sha": "cfa0a57805a8535742ad745751167157318f851f", - "url": "https://github.com/openai/codex/commit/cfa0a57805a8535742ad745751167157318f851f" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "TUI", - "MCP", - "--enable", - "CODEX_CLI_VERSION", - "DEFAULT_IN_PROCESS_CHANNEL_CAPACITY", - "HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG", - "CARGO_PKG_VERSION", - "TODO", - "OPENAI_CURATED_MARKETPLACE_NAME", - "PLUGINS_SELECTION_VIEW_ID", - "SUPPORTED_MARKETPLACE_NAME", - "RPC" - ], - "files": [ - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -2566,6 +2566,7 @@ dependencies = [\n \"chrono\",\n \"clap\",\n \"codex-ansi-escape\",\n+ \"codex-app-server-client\",\n \"codex-app-server-protocol\",\n \"codex-arg0\",\n \"codex-backend-client\",", - "path": "codex-rs/Cargo.lock", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -29,6 +29,7 @@ base64 = { workspace = true }\n chrono = { workspace = true, features = [\"serde\"] }\n clap = { workspace = true, features = [\"derive\"] }\n codex-ansi-escape = { workspace = true }\n+codex-app-server-client = { workspace = true }\n codex-app-server-protocol = { workspace = true }\n codex-arg0 = { workspace = true }\n codex-backend-client = { workspace = true }", - "path": "codex-rs/tui/Cargo.toml", - "status": "modified" - }, - { - "additions": 211, - "deletions": 4, - "patch_excerpt": "@@ -39,7 +39,18 @@ use crate::tui::TuiEvent;\n use crate::update_action::UpdateAction;\n use crate::version::CODEX_CLI_VERSION;\n use codex_ansi_escape::ansi_escape_line;\n+use codex_app_server_client::DEFAULT_IN_PROCESS_CHANNEL_CAPACITY;\n+use codex_app_server_client::InProcessAppServerClient;\n+use codex_app_server_client::InProcessClientStartArgs;\n+use codex_app_server_protocol::ClientRequest;\n use codex_app_server_protocol::ConfigLayerSource;\n+use codex_app_server_protocol::ConfigWarningNotification;\n+use codex_app_server_protocol::PluginListParams;\n+use codex_app_server_protocol::PluginListResponse;\n+use codex_app_server_protocol::PluginReadParams;\n+use codex_app_server_protocol::PluginReadResponse;\n+use codex_app_server_protocol::RequestId;\n+use codex_arg0::Arg0DispatchPaths;\n use codex_core::AuthManager;\n use codex_core::CodexAuth;\n use codex_core::ThreadManager;\n@@ -50,7 +61,9 @@ use c...", - "path": "codex-rs/tui/src/app.rs", - "status": "modified" - }, - { - "additions": 31, - "deletions": 0, - "patch_excerpt": "@@ -10,6 +10,9 @@\n \n use std::path::PathBuf;\n \n+use codex_app_server_protocol::PluginListResponse;\n+use codex_app_server_protocol::PluginReadParams;\n+use codex_app_server_protocol::PluginReadResponse;\n use codex_chatgpt::connectors::AppInfo;\n use codex_file_search::FileMatch;\n use codex_protocol::ThreadId;\n@@ -162,6 +165,34 @@ pub(crate) enum AppEvent {\n force_refetch: bool,\n },\n \n+ /// Fetch plugin marketplace state for the provided working directory.\n+ FetchPluginsList {\n+ cwd: PathBuf,\n+ },\n+\n+ /// Result of fetching plugin marketplace state.\n+ PluginsLoaded {\n+ cwd: PathBuf,\n+ result: Result,\n+ },\n+\n+ /// Replace the plugins popup with a plugin-detail loading state.\n+ OpenPluginDetailLoading {\n+ plugin_display_name: String,\n+ },\n+\n+ /// Fetch detail for a specific plugin from a marketpla...", - "path": "codex-rs/tui/src/app_event.rs", - "status": "modified" - }, - { - "additions": 19, - "deletions": 0, - "patch_excerpt": "@@ -289,6 +289,8 @@ mod skills;\n use self::skills::collect_tool_mentions;\n use self::skills::find_app_mentions;\n use self::skills::find_skill_mentions_with_tool_mentions;\n+mod plugins;\n+use self::plugins::PluginsCacheState;\n mod realtime;\n use self::realtime::RealtimeConversationUiState;\n use self::realtime::RenderedUserMessageEvent;\n@@ -520,6 +522,12 @@ enum ConnectorsCacheState {\n Failed(String),\n }\n \n+#[derive(Debug, Clone, Default)]\n+struct PluginListFetchState {\n+ cache_cwd: Option,\n+ in_flight_cwd: Option,\n+}\n+\n #[derive(Debug)]\n enum RateLimitErrorKind {\n ServerOverloaded,\n@@ -712,6 +720,8 @@ pub(crate) struct ChatWidget {\n connectors_partial_snapshot: Option,\n connectors_prefetch_in_flight: bool,\n connectors_force_refetch_pending: bool,\n+ plugins_cache: PluginsCacheState,\n+ plugins_fetch_state: PluginListFetchS...", - "path": "codex-rs/tui/src/chatwidget.rs", - "status": "modified" - }, - { - "additions": 550, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,550 @@\n+use std::path::PathBuf;\n+\n+use super::ChatWidget;\n+use crate::app_event::AppEvent;\n+use crate::bottom_pane::ColumnWidthMode;\n+use crate::bottom_pane::SelectionItem;\n+use crate::bottom_pane::SelectionViewParams;\n+use crate::history_cell;\n+use crate::render::renderable::ColumnRenderable;\n+use codex_app_server_protocol::PluginDetail;\n+use codex_app_server_protocol::PluginInstallPolicy;\n+use codex_app_server_protocol::PluginListResponse;\n+use codex_app_server_protocol::PluginMarketplaceEntry;\n+use codex_app_server_protocol::PluginReadResponse;\n+use codex_app_server_protocol::PluginSummary;\n+use codex_core::plugins::OPENAI_CURATED_MARKETPLACE_NAME;\n+use codex_features::Feature;\n+use ratatui::style::Stylize;\n+use ratatui::text::Line;\n+\n+const PLUGINS_SELECTION_VIEW_ID: &str = \"plugins-selection\";\n+const SUPPORTED_MARKETPLACE_NAME: &str = OPENAI_CURATED_MARKETPLACE_NAME;\n+\n+#...", - "path": "codex-rs/tui/src/chatwidget/plugins.rs", - "status": "added" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -1895,6 +1895,8 @@ async fn make_chatwidget_manual(\n connectors_partial_snapshot: None,\n connectors_prefetch_in_flight: false,\n connectors_force_refetch_pending: false,\n+ plugins_cache: PluginsCacheState::default(),\n+ plugins_fetch_state: PluginListFetchState::default(),\n interrupts: InterruptManager::new(),\n reasoning_buffer: String::new(),\n full_reasoning_buffer: String::new(),", - "path": "codex-rs/tui/src/chatwidget/tests.rs", - "status": "modified" - }, - { - "additions": 8, - "deletions": 1, - "patch_excerpt": "@@ -266,7 +266,7 @@ pub use public_widgets::composer_input::ComposerInput;\n pub async fn run_main(\n mut cli: Cli,\n arg0_paths: Arg0DispatchPaths,\n- _loader_overrides: LoaderOverrides,\n+ loader_overrides: LoaderOverrides,\n ) -> std::io::Result {\n let (sandbox_mode, approval_policy) = if cli.full_auto {\n (\n@@ -569,9 +569,11 @@ pub async fn run_main(\n \n run_ratatui_app(\n cli,\n+ arg0_paths,\n config,\n overrides,\n cli_kv_overrides,\n+ loader_overrides,\n cloud_requirements,\n feedback,\n )\n@@ -582,9 +584,11 @@ pub async fn run_main(\n #[allow(clippy::too_many_arguments)]\n async fn run_ratatui_app(\n cli: Cli,\n+ arg0_paths: Arg0DispatchPaths,\n initial_config: Config,\n overrides: ConfigOverrides,\n cli_kv_overrides: Vec<(String, toml::Value)>,\n+ loader_overrides: LoaderOver...", - "path": "codex-rs/tui/src/lib.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 0, - "patch_excerpt": "@@ -43,6 +43,7 @@ pub enum SlashCommand {\n Theme,\n Mcp,\n Apps,\n+ Plugins,\n Logout,\n Quit,\n Exit,\n@@ -110,6 +111,7 @@ impl SlashCommand {\n SlashCommand::Experimental => \"toggle experimental features\",\n SlashCommand::Mcp => \"list configured MCP tools\",\n SlashCommand::Apps => \"manage apps\",\n+ SlashCommand::Plugins => \"browse plugins\",\n SlashCommand::Logout => \"log out of Codex\",\n SlashCommand::Rollout => \"print the rollout file path\",\n SlashCommand::TestApproval => \"test approval request\",\n@@ -168,6 +170,7 @@ impl SlashCommand {\n | SlashCommand::Stop\n | SlashCommand::Mcp\n | SlashCommand::Apps\n+ | SlashCommand::Plugins\n | SlashCommand::Feedback\n | SlashCommand::Quit\n | SlashCommand::Exit => true,", - "path": "codex-rs/tui/src/slash_command.rs", - "status": "modified" - }, - { - "additions": 78, - "deletions": 0, - "patch_excerpt": "@@ -53,6 +53,10 @@ use codex_app_server_protocol::ConfigLayerSource;\n use codex_app_server_protocol::ListMcpServerStatusParams;\n use codex_app_server_protocol::ListMcpServerStatusResponse;\n use codex_app_server_protocol::McpServerStatus;\n+use codex_app_server_protocol::PluginListParams;\n+use codex_app_server_protocol::PluginListResponse;\n+use codex_app_server_protocol::PluginReadParams;\n+use codex_app_server_protocol::PluginReadResponse;\n use codex_app_server_protocol::RequestId;\n use codex_app_server_protocol::ServerNotification;\n use codex_app_server_protocol::ServerRequest;\n@@ -1826,6 +1830,33 @@ impl App {\n });\n }\n \n+ fn fetch_plugins_list(&mut self, app_server: &AppServerSession, cwd: PathBuf) {\n+ let request_handle = app_server.request_handle();\n+ let app_event_tx = self.app_event_tx.clone();\n+ tokio::spawn(async move {\n+ let result = ...", - "path": "codex-rs/tui_app_server/src/app.rs", - "status": "modified" - }, - { - "additions": 31, - "deletions": 0, - "patch_excerpt": "@@ -11,6 +11,9 @@\n use std::path::PathBuf;\n \n use codex_app_server_protocol::McpServerStatus;\n+use codex_app_server_protocol::PluginListResponse;\n+use codex_app_server_protocol::PluginReadParams;\n+use codex_app_server_protocol::PluginReadResponse;\n use codex_chatgpt::connectors::AppInfo;\n use codex_file_search::FileMatch;\n use codex_protocol::ThreadId;\n@@ -164,6 +167,34 @@ pub(crate) enum AppEvent {\n force_refetch: bool,\n },\n \n+ /// Fetch plugin marketplace state for the provided working directory.\n+ FetchPluginsList {\n+ cwd: PathBuf,\n+ },\n+\n+ /// Result of fetching plugin marketplace state.\n+ PluginsLoaded {\n+ cwd: PathBuf,\n+ result: Result,\n+ },\n+\n+ /// Replace the plugins popup with a plugin-detail loading state.\n+ OpenPluginDetailLoading {\n+ plugin_display_name: String,\n+ },\n+\n+ /// Fetc...", - "path": "codex-rs/tui_app_server/src/app_event.rs", - "status": "modified" - }, - { - "additions": 15, - "deletions": 0, - "patch_excerpt": "@@ -329,6 +329,8 @@ mod skills;\n use self::skills::collect_tool_mentions;\n use self::skills::find_app_mentions;\n use self::skills::find_skill_mentions_with_tool_mentions;\n+mod plugins;\n+use self::plugins::PluginsCacheState;\n mod realtime;\n use self::realtime::RealtimeConversationUiState;\n use self::realtime::RenderedUserMessageEvent;\n@@ -549,6 +551,12 @@ enum ConnectorsCacheState {\n Failed(String),\n }\n \n+#[derive(Debug, Clone, Default)]\n+struct PluginListFetchState {\n+ cache_cwd: Option,\n+ in_flight_cwd: Option,\n+}\n+\n #[derive(Debug)]\n enum RateLimitErrorKind {\n ServerOverloaded,\n@@ -753,6 +761,8 @@ pub(crate) struct ChatWidget {\n connectors_partial_snapshot: Option,\n connectors_prefetch_in_flight: bool,\n connectors_force_refetch_pending: bool,\n+ plugins_cache: PluginsCacheState,\n+ plugins_fetch_state: PluginListFetchS...", - "path": "codex-rs/tui_app_server/src/chatwidget.rs", - "status": "modified" - }, - { - "additions": 550, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,550 @@\n+use std::path::PathBuf;\n+\n+use super::ChatWidget;\n+use crate::app_event::AppEvent;\n+use crate::bottom_pane::ColumnWidthMode;\n+use crate::bottom_pane::SelectionItem;\n+use crate::bottom_pane::SelectionViewParams;\n+use crate::history_cell;\n+use crate::render::renderable::ColumnRenderable;\n+use codex_app_server_protocol::PluginDetail;\n+use codex_app_server_protocol::PluginInstallPolicy;\n+use codex_app_server_protocol::PluginListResponse;\n+use codex_app_server_protocol::PluginMarketplaceEntry;\n+use codex_app_server_protocol::PluginReadResponse;\n+use codex_app_server_protocol::PluginSummary;\n+use codex_core::plugins::OPENAI_CURATED_MARKETPLACE_NAME;\n+use codex_features::Feature;\n+use ratatui::style::Stylize;\n+use ratatui::text::Line;\n+\n+const PLUGINS_SELECTION_VIEW_ID: &str = \"plugins-selection\";\n+const SUPPORTED_MARKETPLACE_NAME: &str = OPENAI_CURATED_MARKETPLACE_NAME;\n+\n+#...", - "path": "codex-rs/tui_app_server/src/chatwidget/plugins.rs", - "status": "added" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -1916,6 +1916,8 @@ async fn make_chatwidget_manual(\n connectors_partial_snapshot: None,\n connectors_prefetch_in_flight: false,\n connectors_force_refetch_pending: false,\n+ plugins_cache: PluginsCacheState::default(),\n+ plugins_fetch_state: PluginListFetchState::default(),\n interrupts: InterruptManager::new(),\n reasoning_buffer: String::new(),\n full_reasoning_buffer: String::new(),", - "path": "codex-rs/tui_app_server/src/chatwidget/tests.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 0, - "patch_excerpt": "@@ -42,6 +42,7 @@ pub enum SlashCommand {\n Theme,\n Mcp,\n Apps,\n+ Plugins,\n Logout,\n Quit,\n Exit,\n@@ -108,6 +109,7 @@ impl SlashCommand {\n SlashCommand::Experimental => \"toggle experimental features\",\n SlashCommand::Mcp => \"list configured MCP tools\",\n SlashCommand::Apps => \"manage apps\",\n+ SlashCommand::Plugins => \"browse plugins\",\n SlashCommand::Logout => \"log out of Codex\",\n SlashCommand::Rollout => \"print the rollout file path\",\n SlashCommand::TestApproval => \"test approval request\",\n@@ -166,6 +168,7 @@ impl SlashCommand {\n | SlashCommand::Stop\n | SlashCommand::Mcp\n | SlashCommand::Apps\n+ | SlashCommand::Plugins\n | SlashCommand::Feedback\n | SlashCommand::Quit\n | SlashCommand::Exit => true,", - "path": "codex-rs/tui_app_server/src/slash_command.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints." - ], - "primary_pr": { - "body": "### Preliminary /plugins TUI menu\r\n - Adds a preliminary /plugins menu flow in both tui and tui_app_server.\r\n - Fetches plugin list data asynchronously and shows loading/error/cached states.\r\n - Limits this first pass to the curated ChatGPT marketplace.\r\n - Shows available plugins with installed/status metadata.\r\n - Supports in-menu search over plugin display name, plugin id, plugin name, and marketplace label.\r\n - Opens a plugin detail view on selection, including summaries for Skills, Apps, and MCP Servers, with back navigation.\r\n\r\n### Testing\r\n - Launch codex-cli with plugins enabled (`--enable plugins`).\r\n - Run /plugins and verify:\r\n - loading state appears first\r\n - plugin list is shown\r\n - search filters results\r\n - selecting a plugin opens detail view, with a list of skills/connectors/MCP servers for the plugin\r\n - back action returns to the list.\r\n - Verify disabled behavior by running /plugins without plugins enabled (shows \u201cPlugins are disabled\u201d message).\r\n - Launch with `--enable tui_app_server` (and plugins enabled) and repeat the same /plugins flow; behavior should match.", - "labels": [], - "merged_at": "2026-03-20T04:28:33Z", - "number": 15215, - "state": "merged", - "title": "Initial plugins TUI menu - list and read only. tui + tui_app_server", - "url": "https://github.com/openai/codex/pull/15215" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15216.json b/artifacts/github/bundles/openai-codex-pr-15216.json deleted file mode 100644 index d608d2b..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15216.json +++ /dev/null @@ -1,245 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "aibrahim-oai", - "committed_at": "2026-03-19T18:43:42Z", - "message": "Move terminal module to its own crate", - "sha": "df7a5872916053eab788ed158eb738d4472153bf", - "url": "https://github.com/openai/codex/commit/df7a5872916053eab788ed158eb738d4472153bf" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-19T20:13:26Z", - "message": "Rename terminal crate to terminal-detection", - "sha": "3661c085ded4aa30083d374fc786681eeea044b0", - "url": "https://github.com/openai/codex/commit/3661c085ded4aa30083d374fc786681eeea044b0" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-03-19T20:39:49Z", - "message": "codex: fix CI failure on PR #15216", - "sha": "9e6c4040126f6c977c099716ff1135b9f0638747", - "url": "https://github.com/openai/codex/commit/9e6c4040126f6c977c099716ff1135b9f0638747" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "API", - "CLI", - "USER_AGENT_SUFFIX", - "DEFAULT_PROJECT_DOC_FILENAME", - "FEATURES" - ], - "files": [ - { - "additions": 13, - "deletions": 0, - "patch_excerpt": "@@ -1664,6 +1664,7 @@ dependencies = [\n \"codex-rmcp-client\",\n \"codex-state\",\n \"codex-stdio-to-uds\",\n+ \"codex-terminal-detection\",\n \"codex-tui\",\n \"codex-tui-app-server\",\n \"codex-utils-cargo-bin\",\n@@ -1858,6 +1859,7 @@ dependencies = [\n \"codex-shell-escalation\",\n \"codex-skills\",\n \"codex-state\",\n+ \"codex-terminal-detection\",\n \"codex-test-macros\",\n \"codex-utils-absolute-path\",\n \"codex-utils-cache\",\n@@ -2507,6 +2509,14 @@ dependencies = [\n \"uds_windows\",\n ]\n \n+[[package]]\n+name = \"codex-terminal-detection\"\n+version = \"0.0.0\"\n+dependencies = [\n+ \"pretty_assertions\",\n+ \"tracing\",\n+]\n+\n [[package]]\n name = \"codex-test-macros\"\n version = \"0.0.0\"\n@@ -2542,6 +2552,7 @@ dependencies = [\n \"codex-protocol\",\n \"codex-shell-command\",\n \"codex-state\",\n+ \"codex-terminal-detection\",\n \"codex-tui-app-server\",\n \"codex-utils-absolute-path\",\n \"codex-utils-approval-presets\",\n@@ -2633,6 +2644,7 @...", - "path": "codex-rs/Cargo.lock", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -66,6 +66,7 @@ members = [\n \"codex-client\",\n \"codex-api\",\n \"state\",\n+ \"terminal-detection\",\n \"codex-experimental-api-macros\",\n \"test-macros\",\n \"package-manager\",\n@@ -131,6 +132,7 @@ codex-skills = { path = \"skills\" }\n codex-state = { path = \"state\" }\n codex-stdio-to-uds = { path = \"stdio-to-uds\" }\n codex-test-macros = { path = \"test-macros\" }\n+codex-terminal-detection = { path = \"terminal-detection\" }\n codex-tui = { path = \"tui\" }\n codex-tui-app-server = { path = \"tui_app_server\" }\n codex-utils-absolute-path = { path = \"utils/absolute-path\" }", - "path": "codex-rs/Cargo.toml", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -37,6 +37,7 @@ codex-responses-api-proxy = { workspace = true }\n codex-rmcp-client = { workspace = true }\n codex-state = { workspace = true }\n codex-stdio-to-uds = { workspace = true }\n+codex-terminal-detection = { workspace = true }\n codex-tui = { workspace = true }\n codex-tui-app-server = { workspace = true }\n libc = { workspace = true }", - "path": "codex-rs/cli/Cargo.toml", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -50,7 +50,7 @@ use codex_core::config::edit::ConfigEditsBuilder;\n use codex_core::config::find_codex_home;\n use codex_core::features::Stage;\n use codex_core::features::is_known_feature_key;\n-use codex_core::terminal::TerminalName;\n+use codex_terminal_detection::TerminalName;\n \n /// Codex CLI\n ///\n@@ -1049,7 +1049,7 @@ async fn run_interactive_tui(\n interactive.prompt = Some(prompt.replace(\"\\r\\n\", \"\\n\").replace('\\r', \"\\n\"));\n }\n \n- let terminal_info = codex_core::terminal::terminal_info();\n+ let terminal_info = codex_terminal_detection::terminal_info();\n if terminal_info.name == TerminalName::Dumb {\n if !(std::io::stdin().is_terminal() && std::io::stderr().is_terminal()) {\n return Ok(AppExitInfo::fatal(", - "path": "codex-rs/cli/src/main.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -48,6 +48,7 @@ codex-artifacts = { workspace = true }\n codex-protocol = { workspace = true }\n codex-rmcp-client = { workspace = true }\n codex-state = { workspace = true }\n+codex-terminal-detection = { workspace = true }\n codex-utils-absolute-path = { workspace = true }\n codex-utils-cache = { workspace = true }\n codex-utils-image = { workspace = true }", - "path": "codex-rs/core/Cargo.toml", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -49,7 +49,6 @@ use crate::stream_events_utils::handle_output_item_done;\n use crate::stream_events_utils::last_assistant_message_from_item;\n use crate::stream_events_utils::raw_assistant_output_text_from_item;\n use crate::stream_events_utils::record_completed_response_item;\n-use crate::terminal;\n use crate::truncate::TruncationPolicy;\n use crate::turn_metadata::TurnMetadataState;\n use crate::util::error_or_panic;\n@@ -117,6 +116,7 @@ use codex_protocol::request_user_input::RequestUserInputArgs;\n use codex_protocol::request_user_input::RequestUserInputResponse;\n use codex_rmcp_client::ElicitationResponse;\n use codex_rmcp_client::OAuthCredentialsStoreMode;\n+use codex_terminal_detection::user_agent;\n use codex_utils_stream_parser::AssistantTextChunk;\n use codex_utils_stream_parser::AssistantTextStreamParser;\n use codex_utils_stream_parser::ProposedPlanSegment;\n@@ -1581,7 +1581,7 @@ impl Se...", - "path": "codex-rs/core/src/codex.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 1, - "patch_excerpt": "@@ -4,6 +4,7 @@ use codex_client::BuildCustomCaTransportError;\n use codex_client::CodexHttpClient;\n pub use codex_client::CodexRequestBuilder;\n use codex_client::build_reqwest_client_with_custom_ca;\n+use codex_terminal_detection::user_agent;\n use reqwest::header::HeaderMap;\n use reqwest::header::HeaderValue;\n use std::sync::LazyLock;\n@@ -130,7 +131,7 @@ pub fn get_codex_user_agent() -> String {\n os_info.os_type(),\n os_info.version(),\n os_info.architecture().unwrap_or(\"unknown\"),\n- crate::terminal::user_agent()\n+ user_agent()\n );\n let suffix = USER_AGENT_SUFFIX\n .lock()", - "path": "codex-rs/core/src/default_client.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 1, - "patch_excerpt": "@@ -120,7 +120,6 @@ pub mod shell_snapshot;\n pub mod skills;\n pub mod spawn;\n pub mod state_db;\n-pub mod terminal;\n mod tools;\n pub mod turn_diff_tracker;\n mod turn_metadata;", - "path": "codex-rs/core/src/lib.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -36,7 +36,7 @@ pub(crate) struct ExecServerFileSystem {\n impl Default for ExecServerFileSystem {\n fn default() -> Self {\n Self {\n- file_system: Arc::new(Environment.get_filesystem()),\n+ file_system: Arc::new(Environment::default().get_filesystem()),\n }\n }\n }", - "path": "codex-rs/exec-server/src/server/filesystem.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -11,6 +11,7 @@ path = \"lib.rs\"\n anyhow = { workspace = true }\n codex-core = { workspace = true }\n codex-mcp-server = { workspace = true }\n+codex-terminal-detection = { workspace = true }\n codex-utils-cargo-bin = { workspace = true }\n rmcp = { workspace = true }\n os_info = { workspace = true }", - "path": "codex-rs/mcp-server/tests/common/Cargo.toml", - "status": "modified" - }, - { - "additions": 2, - "deletions": 1, - "patch_excerpt": "@@ -11,6 +11,7 @@ use tokio::process::ChildStdout;\n \n use anyhow::Context;\n use codex_mcp_server::CodexToolCallParam;\n+use codex_terminal_detection::user_agent;\n \n use pretty_assertions::assert_eq;\n use rmcp::model::CallToolRequestParams;\n@@ -156,7 +157,7 @@ impl McpProcess {\n os_info.os_type(),\n os_info.version(),\n os_info.architecture().unwrap_or(\"unknown\"),\n- codex_core::terminal::user_agent()\n+ user_agent()\n );\n let JsonRpcMessage::Response(JsonRpcResponse {\n jsonrpc,", - "path": "codex-rs/mcp-server/tests/common/mcp_process.rs", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,6 @@\n+load(\"//:defs.bzl\", \"codex_rust_crate\")\n+\n+codex_rust_crate(\n+ name = \"terminal-detection\",\n+ crate_name = \"codex_terminal_detection\",\n+)", - "path": "codex-rs/terminal-detection/BUILD.bazel", - "status": "added" - }, - { - "additions": 18, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,18 @@\n+[package]\n+name = \"codex-terminal-detection\"\n+version.workspace = true\n+edition.workspace = true\n+license.workspace = true\n+\n+[lib]\n+name = \"codex_terminal_detection\"\n+path = \"src/lib.rs\"\n+\n+[lints]\n+workspace = true\n+\n+[dependencies]\n+tracing = { workspace = true }\n+\n+[dev-dependencies]\n+pretty_assertions = { workspace = true }", - "path": "codex-rs/terminal-detection/Cargo.toml", - "status": "added" - }, - { - "additions": 0, - "deletions": 0, - "patch_excerpt": null, - "path": "codex-rs/terminal-detection/src/lib.rs", - "status": "renamed" - }, - { - "additions": 0, - "deletions": 0, - "patch_excerpt": null, - "path": "codex-rs/terminal-detection/src/terminal_tests.rs", - "status": "renamed" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -43,6 +43,7 @@ codex-otel = { workspace = true }\n codex-protocol = { workspace = true }\n codex-shell-command = { workspace = true }\n codex-state = { workspace = true }\n+codex-terminal-detection = { workspace = true }\n codex-tui-app-server = { workspace = true }\n codex-utils-approval-presets = { workspace = true }\n codex-utils-absolute-path = { workspace = true }", - "path": "codex-rs/tui/Cargo.toml", - "status": "modified" - }, - { - "additions": 2, - "deletions": 1, - "patch_excerpt": "@@ -80,6 +80,7 @@ use codex_protocol::protocol::SessionConfiguredEvent;\n use codex_protocol::protocol::SessionSource;\n use codex_protocol::protocol::SkillErrorInfo;\n use codex_protocol::protocol::TokenUsage;\n+use codex_terminal_detection::user_agent;\n use codex_utils_absolute_path::AbsolutePathBuf;\n use color_eyre::eyre::Result;\n use color_eyre::eyre::WrapErr;\n@@ -2079,7 +2080,7 @@ impl App {\n auth_mode,\n codex_core::default_client::originator().value,\n config.otel.log_user_prompt,\n- codex_core::terminal::user_agent(),\n+ user_agent(),\n SessionSource::Cli,\n );\n if config", - "path": "codex-rs/tui/src/app.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -76,8 +76,6 @@ use codex_core::models_manager::manager::ModelsManager;\n use codex_core::plugins::PluginsManager;\n use codex_core::project_doc::DEFAULT_PROJECT_DOC_FILENAME;\n use codex_core::skills::model::SkillMetadata;\n-use codex_core::terminal::TerminalName;\n-use codex_core::terminal::terminal_info;\n #[cfg(target_os = \"windows\")]\n use codex_core::windows_sandbox::WindowsSandboxLevelExt;\n use codex_otel::RuntimeMetricsSummary;\n@@ -155,6 +153,8 @@ use codex_protocol::request_permissions::RequestPermissionsEvent;\n use codex_protocol::request_user_input::RequestUserInputEvent;\n use codex_protocol::user_input::TextElement;\n use codex_protocol::user_input::UserInput;\n+use codex_terminal_detection::TerminalName;\n+use codex_terminal_detection::terminal_info;\n use codex_utils_sleep_inhibitor::SleepInhibitor;\n use crossterm::event::KeyCode;\n use crossterm::event::KeyEvent;", - "path": "codex-rs/tui/src/chatwidget.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -38,7 +38,6 @@ use codex_core::features::Feature;\n use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;\n use codex_core::models_manager::manager::ModelsManager;\n use codex_core::skills::model::SkillMetadata;\n-use codex_core::terminal::TerminalName;\n use codex_otel::RuntimeMetricsSummary;\n use codex_otel::SessionTelemetry;\n use codex_protocol::ThreadId;\n@@ -121,6 +120,7 @@ use codex_protocol::request_user_input::RequestUserInputQuestion;\n use codex_protocol::request_user_input::RequestUserInputQuestionOption;\n use codex_protocol::user_input::TextElement;\n use codex_protocol::user_input::UserInput;\n+use codex_terminal_detection::TerminalName;\n use codex_utils_absolute_path::AbsolutePathBuf;\n use codex_utils_approval_presets::builtin_approval_presets;\n use crossterm::event::KeyCode;", - "path": "codex-rs/tui/src/chatwidget/tests.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -93,9 +93,9 @@ use crate::terminal_palette::indexed_color;\n use crate::terminal_palette::rgb_color;\n use crate::terminal_palette::stdout_color_level;\n use codex_core::git_info::get_git_repo_root;\n-use codex_core::terminal::TerminalName;\n-use codex_core::terminal::terminal_info;\n use codex_protocol::protocol::FileChange;\n+use codex_terminal_detection::TerminalName;\n+use codex_terminal_detection::terminal_info;\n \n /// Classifies a diff line for gutter sign rendering and style selection.\n ///", - "path": "codex-rs/tui/src/diff_render.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 2, - "patch_excerpt": "@@ -33,7 +33,6 @@ use codex_core::format_exec_policy_error_with_source;\n use codex_core::path_utils;\n use codex_core::read_session_meta_line;\n use codex_core::state_db::get_state_db;\n-use codex_core::terminal::Multiplexer;\n use codex_core::windows_sandbox::WindowsSandboxLevelExt;\n use codex_protocol::ThreadId;\n use codex_protocol::config_types::AltScreenMode;\n@@ -43,6 +42,8 @@ use codex_protocol::protocol::AskForApproval;\n use codex_protocol::protocol::RolloutItem;\n use codex_protocol::protocol::RolloutLine;\n use codex_state::log_db;\n+use codex_terminal_detection::Multiplexer;\n+use codex_terminal_detection::terminal_info;\n use codex_utils_absolute_path::AbsolutePathBuf;\n use codex_utils_oss::ensure_oss_provider_ready;\n use codex_utils_oss::get_default_model_for_oss_provider;\n@@ -1138,7 +1139,7 @@ fn determine_alt_screen_mode(no_alt_screen: bool, tui_alternate_screen: AltScree\n ...", - "path": "codex-rs/tui/src/lib.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -48,6 +48,7 @@ codex-otel = { workspace = true }\n codex-protocol = { workspace = true }\n codex-shell-command = { workspace = true }\n codex-state = { workspace = true }\n+codex-terminal-detection = { workspace = true }\n codex-utils-approval-presets = { workspace = true }\n codex-utils-absolute-path = { workspace = true }\n codex-utils-cli = { workspace = true }", - "path": "codex-rs/tui_app_server/Cargo.toml", - "status": "modified" - }, - { - "additions": 2, - "deletions": 1, - "patch_excerpt": "@@ -96,6 +96,7 @@ use codex_protocol::protocol::SandboxPolicy;\n use codex_protocol::protocol::SessionSource;\n use codex_protocol::protocol::SkillErrorInfo;\n use codex_protocol::protocol::TokenUsage;\n+use codex_terminal_detection::user_agent;\n use codex_utils_absolute_path::AbsolutePathBuf;\n use color_eyre::eyre::Result;\n use color_eyre::eyre::WrapErr;\n@@ -2965,7 +2966,7 @@ impl App {\n auth_mode,\n codex_core::default_client::originator().value,\n config.otel.log_user_prompt,\n- codex_core::terminal::user_agent(),\n+ user_agent(),\n SessionSource::Cli,\n );\n if config", - "path": "codex-rs/tui_app_server/src/app.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -94,8 +94,6 @@ use codex_core::git_info::local_git_branches;\n use codex_core::plugins::PluginsManager;\n use codex_core::project_doc::DEFAULT_PROJECT_DOC_FILENAME;\n use codex_core::skills::model::SkillMetadata;\n-use codex_core::terminal::TerminalName;\n-use codex_core::terminal::terminal_info;\n #[cfg(target_os = \"windows\")]\n use codex_core::windows_sandbox::WindowsSandboxLevelExt;\n use codex_otel::RuntimeMetricsSummary;\n@@ -199,6 +197,8 @@ use codex_protocol::request_user_input::RequestUserInputEvent;\n use codex_protocol::request_user_input::RequestUserInputQuestionOption;\n use codex_protocol::user_input::TextElement;\n use codex_protocol::user_input::UserInput;\n+use codex_terminal_detection::TerminalName;\n+use codex_terminal_detection::terminal_info;\n use codex_utils_sleep_inhibitor::SleepInhibitor;\n use crossterm::event::KeyCode;\n use crossterm::event::KeyEvent;", - "path": "codex-rs/tui_app_server/src/chatwidget.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -61,7 +61,6 @@ use codex_core::features::FEATURES;\n use codex_core::features::Feature;\n use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;\n use codex_core::skills::model::SkillMetadata;\n-use codex_core::terminal::TerminalName;\n use codex_otel::RuntimeMetricsSummary;\n use codex_otel::SessionTelemetry;\n use codex_protocol::ThreadId;\n@@ -144,6 +143,7 @@ use codex_protocol::request_user_input::RequestUserInputQuestion;\n use codex_protocol::request_user_input::RequestUserInputQuestionOption;\n use codex_protocol::user_input::TextElement;\n use codex_protocol::user_input::UserInput;\n+use codex_terminal_detection::TerminalName;\n use codex_utils_absolute_path::AbsolutePathBuf;\n use codex_utils_approval_presets::builtin_approval_presets;\n use crossterm::event::KeyCode;", - "path": "codex-rs/tui_app_server/src/chatwidget/tests.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -93,9 +93,9 @@ use crate::terminal_palette::indexed_color;\n use crate::terminal_palette::rgb_color;\n use crate::terminal_palette::stdout_color_level;\n use codex_core::git_info::get_git_repo_root;\n-use codex_core::terminal::TerminalName;\n-use codex_core::terminal::terminal_info;\n use codex_protocol::protocol::FileChange;\n+use codex_terminal_detection::TerminalName;\n+use codex_terminal_detection::terminal_info;\n \n /// Classifies a diff line for gutter sign rendering and style selection.\n ///", - "path": "codex-rs/tui_app_server/src/diff_render.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 2, - "patch_excerpt": "@@ -38,7 +38,6 @@ use codex_core::format_exec_policy_error_with_source;\n use codex_core::path_utils;\n use codex_core::read_session_meta_line;\n use codex_core::state_db::get_state_db;\n-use codex_core::terminal::Multiplexer;\n use codex_core::windows_sandbox::WindowsSandboxLevelExt;\n use codex_protocol::ThreadId;\n use codex_protocol::config_types::AltScreenMode;\n@@ -49,6 +48,8 @@ use codex_protocol::protocol::RolloutItem;\n use codex_protocol::protocol::RolloutLine;\n use codex_protocol::protocol::TurnContextItem;\n use codex_state::log_db;\n+use codex_terminal_detection::Multiplexer;\n+use codex_terminal_detection::terminal_info;\n use codex_utils_absolute_path::AbsolutePathBuf;\n use codex_utils_oss::ensure_oss_provider_ready;\n use codex_utils_oss::get_default_model_for_oss_provider;\n@@ -1485,7 +1486,7 @@ fn determine_alt_screen_mode(no_alt_screen: bool, tui_alternate_screen: AltScree\n ...", - "path": "codex-rs/tui_app_server/src/lib.rs", - "status": "modified" - } - ], - "linked_issues": [ - "#15216" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "- Move core/src/terminal.rs and its tests into a standalone terminal-detection workspace crate.\n- Update direct consumers to depend on codex-terminal-detection and import terminal APIs directly.", - "labels": [], - "merged_at": "2026-03-19T21:08:05Z", - "number": 15216, - "state": "merged", - "title": "Move terminal module to terminal-detection crate", - "url": "https://github.com/openai/codex/pull/15216" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15217.json b/artifacts/github/bundles/openai-codex-pr-15217.json deleted file mode 100644 index c69833c..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15217.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "xl-openai", - "committed_at": "2026-03-19T20:08:06Z", - "message": "feat: expose needs_auth for plugin/read.", - "sha": "d9f6b7ff140538f20ffc5f87211e3a099326366d", - "url": "https://github.com/openai/codex/commit/d9f6b7ff140538f20ffc5f87211e3a099326366d" - } - ], - "default_branch": "main", - "docs_refs": [ - "codex-rs/app-server/README.md" - ], - "examples_refs": [], - "extracted_flags": [ - "EXPERIMENTAL", - "MCP", - "AUTHORIZATION", - "JSONRPCR", - "DEFAULT_TIMEOUT", - "UNAUTHORIZED", - "BAD_REQUEST" - ], - "files": [ - { - "additions": 5, - "deletions": 1, - "patch_excerpt": "@@ -5232,11 +5232,15 @@\n },\n \"name\": {\n \"type\": \"string\"\n+ },\n+ \"needsAuth\": {\n+ \"type\": \"boolean\"\n }\n },\n \"required\": [\n \"id\",\n- \"name\"\n+ \"name\",\n+ \"needsAuth\"\n ],\n \"type\": \"object\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json", - "status": "modified" - }, - { - "additions": 5, - "deletions": 1, - "patch_excerpt": "@@ -492,11 +492,15 @@\n },\n \"name\": {\n \"type\": \"string\"\n+ },\n+ \"needsAuth\": {\n+ \"type\": \"boolean\"\n }\n },\n \"required\": [\n \"id\",\n- \"name\"\n+ \"name\",\n+ \"needsAuth\"\n ],\n \"type\": \"object\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json", - "status": "modified" - }, - { - "additions": 5, - "deletions": 1, - "patch_excerpt": "@@ -21,11 +21,15 @@\n },\n \"name\": {\n \"type\": \"string\"\n+ },\n+ \"needsAuth\": {\n+ \"type\": \"boolean\"\n }\n },\n \"required\": [\n \"id\",\n- \"name\"\n+ \"name\",\n+ \"needsAuth\"\n ],\n \"type\": \"object\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/PluginInstallResponse.json", - "status": "modified" - }, - { - "additions": 5, - "deletions": 1, - "patch_excerpt": "@@ -25,11 +25,15 @@\n },\n \"name\": {\n \"type\": \"string\"\n+ },\n+ \"needsAuth\": {\n+ \"type\": \"boolean\"\n }\n },\n \"required\": [\n \"id\",\n- \"name\"\n+ \"name\",\n+ \"needsAuth\"\n ],\n \"type\": \"object\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/PluginReadResponse.json", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -5,4 +5,4 @@\n /**\n * EXPERIMENTAL - app metadata summary for plugin responses.\n */\n-export type AppSummary = { id: string, name: string, description: string | null, installUrl: string | null, };\n+export type AppSummary = { id: string, name: string, description: string | null, installUrl: string | null, needsAuth: boolean, };", - "path": "codex-rs/app-server-protocol/schema/typescript/v2/AppSummary.ts", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -2035,6 +2035,7 @@ pub struct AppSummary {\n pub name: String,\n pub description: Option,\n pub install_url: Option,\n+ pub needs_auth: bool,\n }\n \n impl From for AppSummary {\n@@ -2044,6 +2045,7 @@ impl From for AppSummary {\n name: value.name,\n description: value.description,\n install_url: value.install_url,\n+ needs_auth: false,\n }\n }\n }", - "path": "codex-rs/app-server-protocol/src/protocol/v2.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -164,7 +164,7 @@ Example with notification opt-out:\n - `collaborationMode/list` \u2014 list available collaboration mode presets (experimental, no pagination). This response omits built-in developer instructions; clients should either pass `settings.developer_instructions: null` when setting a mode to use Codex's built-in instructions, or provide their own instructions explicitly.\n - `skills/list` \u2014 list skills for one or more `cwd` values (optional `forceReload`).\n - `plugin/list` \u2014 list discovered plugin marketplaces and plugin state, including effective marketplace install/auth policy metadata and best-effort `featuredPluginIds` for the official curated marketplace. `interface.category` uses the marketplace category when present; otherwise it falls back to the plugin manifest category. Pass `forceRemoteSync: true` to refresh curated plugin state before listing (**under development; do n...", - "path": "codex-rs/app-server/README.md", - "status": "modified" - }, - { - "additions": 47, - "deletions": 3, - "patch_excerpt": "@@ -26,9 +26,47 @@ pub(super) async fn load_plugin_app_summaries(\n }\n };\n \n- connectors::connectors_for_plugin_apps(connectors, plugin_apps)\n+ let plugin_connectors = connectors::connectors_for_plugin_apps(connectors, plugin_apps);\n+\n+ let accessible_connectors =\n+ match connectors::list_accessible_connectors_from_mcp_tools_with_options_and_status(\n+ config, /*force_refetch*/ false,\n+ )\n+ .await\n+ {\n+ Ok(status) if status.codex_apps_ready => status.connectors,\n+ Ok(_) => {\n+ return plugin_connectors\n+ .into_iter()\n+ .map(AppSummary::from)\n+ .collect();\n+ }\n+ Err(err) => {\n+ warn!(\"failed to load app auth state for plugin/read: {err:#}\");\n+ return plugin_connectors\n+ ...", - "path": "codex-rs/app-server/src/codex_message_processor/plugin_app_helpers.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -435,6 +435,7 @@ async fn plugin_install_returns_apps_needing_auth() -> Result<()> {\n name: \"Alpha\".to_string(),\n description: Some(\"Alpha connector\".to_string()),\n install_url: Some(\"https://chatgpt.com/apps/alpha/alpha\".to_string()),\n+ needs_auth: true,\n }],\n }\n );\n@@ -518,6 +519,7 @@ async fn plugin_install_filters_disallowed_apps_needing_auth() -> Result<()> {\n name: \"Alpha\".to_string(),\n description: Some(\"Alpha connector\".to_string()),\n install_url: Some(\"https://chatgpt.com/apps/alpha/alpha\".to_string()),\n+ needs_auth: true,\n }],\n }\n );", - "path": "codex-rs/app-server/tests/suite/v2/plugin_install.rs", - "status": "modified" - }, - { - "additions": 319, - "deletions": 0, - "patch_excerpt": "@@ -1,17 +1,46 @@\n+use std::borrow::Cow;\n+use std::sync::Arc;\n+use std::sync::Mutex as StdMutex;\n use std::time::Duration;\n \n use anyhow::Result;\n+use app_test_support::ChatGptAuthFixture;\n use app_test_support::McpProcess;\n use app_test_support::to_response;\n+use app_test_support::write_chatgpt_auth;\n+use axum::Json;\n+use axum::Router;\n+use axum::extract::State;\n+use axum::http::HeaderMap;\n+use axum::http::StatusCode;\n+use axum::http::Uri;\n+use axum::http::header::AUTHORIZATION;\n+use axum::routing::get;\n+use codex_app_server_protocol::AppInfo;\n use codex_app_server_protocol::JSONRPCResponse;\n use codex_app_server_protocol::PluginAuthPolicy;\n use codex_app_server_protocol::PluginInstallPolicy;\n use codex_app_server_protocol::PluginReadParams;\n use codex_app_server_protocol::PluginReadResponse;\n use codex_app_server_protocol::RequestId;\n+use codex_core::auth::AuthCredentialsStoreMode;\n us...", - "path": "codex-rs/app-server/tests/suite/v2/plugin_read.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -36,7 +36,7 @@ pub(crate) struct ExecServerFileSystem {\n impl Default for ExecServerFileSystem {\n fn default() -> Self {\n Self {\n- file_system: Arc::new(Environment.get_filesystem()),\n+ file_system: Arc::new(Environment::default().get_filesystem()),\n }\n }\n }", - "path": "codex-rs/exec-server/src/server/filesystem.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "So UI can render it properly.\r\n", - "labels": [], - "merged_at": "2026-03-19T22:02:45Z", - "number": 15217, - "state": "merged", - "title": "feat: expose needs_auth for plugin/read.", - "url": "https://github.com/openai/codex/pull/15217" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15220.json b/artifacts/github/bundles/openai-codex-pr-15220.json deleted file mode 100644 index 356877d..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15220.json +++ /dev/null @@ -1,181 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "owenlin0", - "committed_at": "2026-03-19T20:16:19Z", - "message": "feat(app-server): add mcpServer/status/updated notification", - "sha": "9c0fdb49b71fccffc5fcadf247369067e441b902", - "url": "https://github.com/openai/codex/commit/9c0fdb49b71fccffc5fcadf247369067e441b902" - }, - { - "author": "owenlin0", - "committed_at": "2026-03-19T20:33:51Z", - "message": "mcpServer/startupStatus/updated", - "sha": "ba1d7436f867c1febcbb2a51bcfc6ddeb24c8ba0", - "url": "https://github.com/openai/codex/commit/ba1d7436f867c1febcbb2a51bcfc6ddeb24c8ba0" - }, - { - "author": "owenlin0", - "committed_at": "2026-03-19T20:48:30Z", - "message": "fix random bug", - "sha": "c58e46d2fa269d38367c1281998aac4e79494157", - "url": "https://github.com/openai/codex/commit/c58e46d2fa269d38367c1281998aac4e79494157" - }, - { - "author": "owenlin0", - "committed_at": "2026-03-19T21:37:29Z", - "message": "fix", - "sha": "660b395c5176bbf807748e253f2da2d9a40ed0bb", - "url": "https://github.com/openai/codex/commit/660b395c5176bbf807748e253f2da2d9a40ed0bb" - } - ], - "default_branch": "main", - "docs_refs": [ - "codex-rs/app-server/README.md" - ], - "examples_refs": [], - "extracted_flags": [ - "API", - "GENERATED", - "CODE", - "NOT", - "MODIFY", - "HAND", - "MCP", - "JSON", - "JSONRPCE", - "JSONRPCM", - "JSONRPCR", - "DEFAULT_READ_TIMEOUT" - ], - "files": [ - { - "additions": 50, - "deletions": 0, - "patch_excerpt": "@@ -1435,6 +1435,36 @@\n ],\n \"type\": \"object\"\n },\n+ \"McpServerStartupState\": {\n+ \"enum\": [\n+ \"starting\",\n+ \"ready\",\n+ \"failed\",\n+ \"cancelled\"\n+ ],\n+ \"type\": \"string\"\n+ },\n+ \"McpServerStatusUpdatedNotification\": {\n+ \"properties\": {\n+ \"error\": {\n+ \"type\": [\n+ \"string\",\n+ \"null\"\n+ ]\n+ },\n+ \"name\": {\n+ \"type\": \"string\"\n+ },\n+ \"status\": {\n+ \"$ref\": \"#/definitions/McpServerStartupState\"\n+ }\n+ },\n+ \"required\": [\n+ \"name\",\n+ \"status\"\n+ ],\n+ \"type\": \"object\"\n+ },\n \"McpToolCallError\": {\n \"properties\": {\n \"message\": {\n@@ -4214,6 +4244,26 @@\n \"title\": \"McpServer/oauthLogin/completedNotification\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"met...", - "path": "codex-rs/app-server-protocol/schema/json/ServerNotification.json", - "status": "modified" - }, - { - "additions": 52, - "deletions": 0, - "patch_excerpt": "@@ -3973,6 +3973,26 @@\n \"title\": \"McpServer/oauthLogin/completedNotification\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"method\": {\n+ \"enum\": [\n+ \"mcpServer/startupStatus/updated\"\n+ ],\n+ \"title\": \"McpServer/startupStatus/updatedNotificationMethod\",\n+ \"type\": \"string\"\n+ },\n+ \"params\": {\n+ \"$ref\": \"#/definitions/v2/McpServerStatusUpdatedNotification\"\n+ }\n+ },\n+ \"required\": [\n+ \"method\",\n+ \"params\"\n+ ],\n+ \"title\": \"McpServer/startupStatus/updatedNotification\",\n+ \"type\": \"object\"\n+ },\n {\n \"properties\": {\n \"method\": {\n@@ -8566,6 +8586,15 @@\n \"title\": \"McpServerRefreshResponse\",\n \"type\": \"object\"\n },...", - "path": "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json", - "status": "modified" - }, - { - "additions": 52, - "deletions": 0, - "patch_excerpt": "@@ -5354,6 +5354,15 @@\n \"title\": \"McpServerRefreshResponse\",\n \"type\": \"object\"\n },\n+ \"McpServerStartupState\": {\n+ \"enum\": [\n+ \"starting\",\n+ \"ready\",\n+ \"failed\",\n+ \"cancelled\"\n+ ],\n+ \"type\": \"string\"\n+ },\n \"McpServerStatus\": {\n \"properties\": {\n \"authStatus\": {\n@@ -5390,6 +5399,29 @@\n ],\n \"type\": \"object\"\n },\n+ \"McpServerStatusUpdatedNotification\": {\n+ \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n+ \"properties\": {\n+ \"error\": {\n+ \"type\": [\n+ \"string\",\n+ \"null\"\n+ ]\n+ },\n+ \"name\": {\n+ \"type\": \"string\"\n+ },\n+ \"status\": {\n+ \"$ref\": \"#/definitions/McpServerStartupState\"\n+ }\n+ },\n+ \"required\": [\n+ \"name\",\n+ \"status\"\n+ ],\n+ \"title\": \"McpServerStat...", - "path": "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json", - "status": "modified" - }, - { - "additions": 34, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,34 @@\n+{\n+ \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n+ \"definitions\": {\n+ \"McpServerStartupState\": {\n+ \"enum\": [\n+ \"starting\",\n+ \"ready\",\n+ \"failed\",\n+ \"cancelled\"\n+ ],\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"properties\": {\n+ \"error\": {\n+ \"type\": [\n+ \"string\",\n+ \"null\"\n+ ]\n+ },\n+ \"name\": {\n+ \"type\": \"string\"\n+ },\n+ \"status\": {\n+ \"$ref\": \"#/definitions/McpServerStartupState\"\n+ }\n+ },\n+ \"required\": [\n+ \"name\",\n+ \"status\"\n+ ],\n+ \"title\": \"McpServerStatusUpdatedNotification\",\n+ \"type\": \"object\"\n+}\n\\ No newline at end of file", - "path": "codex-rs/app-server-protocol/schema/json/v2/McpServerStatusUpdatedNotification.json", - "status": "added" - }, - { - "additions": 2, - "deletions": 1, - "patch_excerpt": "@@ -22,6 +22,7 @@ import type { ItemGuardianApprovalReviewCompletedNotification } from \"./v2/ItemG\n import type { ItemGuardianApprovalReviewStartedNotification } from \"./v2/ItemGuardianApprovalReviewStartedNotification\";\n import type { ItemStartedNotification } from \"./v2/ItemStartedNotification\";\n import type { McpServerOauthLoginCompletedNotification } from \"./v2/McpServerOauthLoginCompletedNotification\";\n+import type { McpServerStatusUpdatedNotification } from \"./v2/McpServerStatusUpdatedNotification\";\n import type { McpToolCallProgressNotification } from \"./v2/McpToolCallProgressNotification\";\n import type { ModelReroutedNotification } from \"./v2/ModelReroutedNotification\";\n import type { PlanDeltaNotification } from \"./v2/PlanDeltaNotification\";\n@@ -54,4 +55,4 @@ import type { WindowsWorldWritableWarningNotification } from \"./v2/WindowsWorldW\n /**\n * Notification sent from the serv...", - "path": "codex-rs/app-server-protocol/schema/typescript/ServerNotification.ts", - "status": "modified" - }, - { - "additions": 5, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,5 @@\n+// GENERATED CODE! DO NOT MODIFY BY HAND!\n+\n+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n+\n+export type McpServerStartupState = \"starting\" | \"ready\" | \"failed\" | \"cancelled\";", - "path": "codex-rs/app-server-protocol/schema/typescript/v2/McpServerStartupState.ts", - "status": "added" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,6 @@\n+// GENERATED CODE! DO NOT MODIFY BY HAND!\n+\n+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n+import type { McpServerStartupState } from \"./McpServerStartupState\";\n+\n+export type McpServerStatusUpdatedNotification = { name: string, status: McpServerStartupState, error: string | null, };", - "path": "codex-rs/app-server-protocol/schema/typescript/v2/McpServerStatusUpdatedNotification.ts", - "status": "added" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -171,7 +171,9 @@ export type { McpServerOauthLoginCompletedNotification } from \"./McpServerOauthL\n export type { McpServerOauthLoginParams } from \"./McpServerOauthLoginParams\";\n export type { McpServerOauthLoginResponse } from \"./McpServerOauthLoginResponse\";\n export type { McpServerRefreshResponse } from \"./McpServerRefreshResponse\";\n+export type { McpServerStartupState } from \"./McpServerStartupState\";\n export type { McpServerStatus } from \"./McpServerStatus\";\n+export type { McpServerStatusUpdatedNotification } from \"./McpServerStatusUpdatedNotification\";\n export type { McpToolCallError } from \"./McpToolCallError\";\n export type { McpToolCallProgressNotification } from \"./McpToolCallProgressNotification\";\n export type { McpToolCallResult } from \"./McpToolCallResult\";", - "path": "codex-rs/app-server-protocol/schema/typescript/v2/index.ts", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -905,6 +905,7 @@ server_notification_definitions! {\n ServerRequestResolved => \"serverRequest/resolved\" (v2::ServerRequestResolvedNotification),\n McpToolCallProgress => \"item/mcpToolCall/progress\" (v2::McpToolCallProgressNotification),\n McpServerOauthLoginCompleted => \"mcpServer/oauthLogin/completed\" (v2::McpServerOauthLoginCompletedNotification),\n+ McpServerStatusUpdated => \"mcpServer/startupStatus/updated\" (v2::McpServerStatusUpdatedNotification),\n AccountUpdated => \"account/updated\" (v2::AccountUpdatedNotification),\n AccountRateLimitsUpdated => \"account/rateLimits/updated\" (v2::AccountRateLimitsUpdatedNotification),\n AppListUpdated => \"app/list/updated\" (v2::AppListUpdatedNotification),", - "path": "codex-rs/app-server-protocol/src/protocol/common.rs", - "status": "modified" - }, - { - "additions": 19, - "deletions": 0, - "patch_excerpt": "@@ -5000,6 +5000,25 @@ pub struct McpServerOauthLoginCompletedNotification {\n pub error: Option,\n }\n \n+#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]\n+#[serde(rename_all = \"camelCase\")]\n+#[ts(export_to = \"v2/\")]\n+pub enum McpServerStartupState {\n+ Starting,\n+ Ready,\n+ Failed,\n+ Cancelled,\n+}\n+\n+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]\n+#[serde(rename_all = \"camelCase\")]\n+#[ts(export_to = \"v2/\")]\n+pub struct McpServerStatusUpdatedNotification {\n+ pub name: String,\n+ pub status: McpServerStartupState,\n+ pub error: Option,\n+}\n+\n #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]\n #[serde(rename_all = \"camelCase\")]\n #[ts(export_to = \"v2/\")]", - "path": "codex-rs/app-server-protocol/src/protocol/v2.rs", - "status": "modified" - }, - { - "additions": 5, - "deletions": 0, - "patch_excerpt": "@@ -836,6 +836,10 @@ Because audio is intentionally separate from `ThreadItem`, clients can opt out o\n \n - `windowsSandbox/setupCompleted` \u2014 `{ mode, success, error }` after a `windowsSandbox/setupStart` request finishes.\n \n+### MCP server startup events\n+\n+- `mcpServer/startupStatus/updated` \u2014 `{ name, status, error }` when app-server observes an MCP server startup transition. `status` is one of `starting`, `ready`, `failed`, or `cancelled`. `error` is `null` except for `failed`.\n+\n ### Turn events\n \n The app-server streams JSON-RPC notifications while a turn is running. Each turn emits `turn/started` when it begins running and ends with `turn/completed` (final `turn` status). Token usage events stream separately via `thread/tokenUsage/updated`. Clients subscribe to the events they care about, rendering each item incrementally as updates arrive. The per-item lifecycle is always: `item/s...", - "path": "codex-rs/app-server/README.md", - "status": "modified" - }, - { - "additions": 30, - "deletions": 0, - "patch_excerpt": "@@ -57,6 +57,8 @@ use codex_app_server_protocol::JSONRPCErrorError;\n use codex_app_server_protocol::McpServerElicitationAction;\n use codex_app_server_protocol::McpServerElicitationRequestParams;\n use codex_app_server_protocol::McpServerElicitationRequestResponse;\n+use codex_app_server_protocol::McpServerStartupState;\n+use codex_app_server_protocol::McpServerStatusUpdatedNotification;\n use codex_app_server_protocol::McpToolCallError;\n use codex_app_server_protocol::McpToolCallResult;\n use codex_app_server_protocol::McpToolCallStatus;\n@@ -310,6 +312,34 @@ pub(crate) async fn apply_bespoke_event_handling(\n .await;\n }\n }\n+ EventMsg::McpStartupUpdate(update) => {\n+ if let ApiVersion::V2 = api_version {\n+ let (status, error) = match update.status {\n+ codex_protocol::protocol::McpStartupStatus::Starting ...", - "path": "codex-rs/app-server/src/bespoke_event_handling.rs", - "status": "modified" - }, - { - "additions": 129, - "deletions": 0, - "patch_excerpt": "@@ -7,7 +7,10 @@ use app_test_support::write_chatgpt_auth;\n use codex_app_server_protocol::JSONRPCError;\n use codex_app_server_protocol::JSONRPCMessage;\n use codex_app_server_protocol::JSONRPCResponse;\n+use codex_app_server_protocol::McpServerStartupState;\n+use codex_app_server_protocol::McpServerStatusUpdatedNotification;\n use codex_app_server_protocol::RequestId;\n+use codex_app_server_protocol::ServerNotification;\n use codex_app_server_protocol::ThreadStartParams;\n use codex_app_server_protocol::ThreadStartResponse;\n use codex_app_server_protocol::ThreadStartedNotification;\n@@ -328,6 +331,103 @@ async fn thread_start_fails_when_required_mcp_server_fails_to_initialize() -> Re\n Ok(())\n }\n \n+#[tokio::test]\n+async fn thread_start_emits_mcp_server_status_updated_notifications() -> Result<()> {\n+ let server = create_mock_responses_server_repeating_assistant(\"Done\").await;\n+\n+ let c...", - "path": "codex-rs/app-server/tests/suite/v2/thread_start.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -36,7 +36,7 @@ pub(crate) struct ExecServerFileSystem {\n impl Default for ExecServerFileSystem {\n fn default() -> Self {\n Self {\n- file_system: Arc::new(Environment.get_filesystem()),\n+ file_system: Arc::new(Environment::default().get_filesystem()),\n }\n }\n }", - "path": "codex-rs/exec-server/src/server/filesystem.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -492,6 +492,7 @@ fn server_notification_thread_target(\n Some(notification.thread_id.as_str())\n }\n ServerNotification::SkillsChanged(_)\n+ | ServerNotification::McpServerStatusUpdated(_)\n | ServerNotification::McpServerOauthLoginCompleted(_)\n | ServerNotification::AccountUpdated(_)\n | ServerNotification::AccountRateLimitsUpdated(_)", - "path": "codex-rs/tui_app_server/src/app/app_server_adapter.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -6045,6 +6045,7 @@ impl ChatWidget {\n | ServerNotification::RawResponseItemCompleted(_)\n | ServerNotification::CommandExecOutputDelta(_)\n | ServerNotification::McpToolCallProgress(_)\n+ | ServerNotification::McpServerStatusUpdated(_)\n | ServerNotification::McpServerOauthLoginCompleted(_)\n | ServerNotification::AppListUpdated(_)\n | ServerNotification::ContextCompacted(_)", - "path": "codex-rs/tui_app_server/src/chatwidget.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints." - ], - "primary_pr": { - "body": "Exposes the legacy `codex/event/mcp_startup_update` event as an API v2 notification.\r\n\r\nThe legacy event has this shape:\r\n```\r\n#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]\r\npub struct McpStartupUpdateEvent {\r\n /// Server name being started.\r\n pub server: String,\r\n /// Current startup status.\r\n pub status: McpStartupStatus,\r\n}\r\n\r\n#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]\r\n#[serde(rename_all = \"snake_case\", tag = \"state\")]\r\n#[ts(rename_all = \"snake_case\", tag = \"state\")]\r\npub enum McpStartupStatus {\r\n Starting,\r\n Ready,\r\n Failed { error: String },\r\n Cancelled,\r\n}\r\n```", - "labels": [], - "merged_at": "2026-03-19T22:09:59Z", - "number": 15220, - "state": "merged", - "title": "feat(app-server): add mcpServer/startupStatus/updated notification", - "url": "https://github.com/openai/codex/pull/15220" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15222.json b/artifacts/github/bundles/openai-codex-pr-15222.json deleted file mode 100644 index 631c4b7..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15222.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "won-openai", - "committed_at": "2026-03-19T19:03:54Z", - "message": "changed save directory to codex_home", - "sha": "5d11650edcafe0db97b685a6428777bc7f7a0b72", - "url": "https://github.com/openai/codex/commit/5d11650edcafe0db97b685a6428777bc7f7a0b72" - }, - { - "author": "won-openai", - "committed_at": "2026-03-19T20:33:55Z", - "message": "flaky syntax", - "sha": "c5bfcdcac131c70f0ec849eb94e2f29bd5d3fe8e", - "url": "https://github.com/openai/codex/commit/c5bfcdcac131c70f0ec849eb94e2f29bd5d3fe8e" - }, - { - "author": "won-openai", - "committed_at": "2026-03-19T21:10:05Z", - "message": "Collapse generated image artifact helpers", - "sha": "6cd6f496d0ecde564e0ca313ea06d787eb39cdb3", - "url": "https://github.com/openai/codex/commit/6cd6f496d0ecde564e0ca313ea06d787eb39cdb3" - }, - { - "author": "won-openai", - "committed_at": "2026-03-19T21:39:35Z", - "message": "Avoid expect in image artifact path handling", - "sha": "f8b89207c2e373bd25310bb80d784bb471b4b068", - "url": "https://github.com/openai/codex/commit/f8b89207c2e373bd25310bb80d784bb471b4b068" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "GENERATED_IMAGE_ARTIFACTS_DIR", - "BASE64_STANDARD" - ], - "files": [ - { - "additions": 20, - "deletions": 4, - "patch_excerpt": "@@ -3709,7 +3709,11 @@ async fn handle_output_item_done_records_image_save_history_message() {\n let session = Arc::new(session);\n let turn_context = Arc::new(turn_context);\n let call_id = \"ig_history_records_message\";\n- let expected_saved_path = std::env::temp_dir().join(format!(\"{call_id}.png\"));\n+ let expected_saved_path = crate::stream_events_utils::image_generation_artifact_path(\n+ turn_context.config.codex_home.as_path(),\n+ &session.conversation_id.to_string(),\n+ call_id,\n+ );\n let _ = std::fs::remove_file(&expected_saved_path);\n let item = ResponseItem::ImageGenerationCall {\n id: call_id.to_string(),\n@@ -3729,10 +3733,18 @@ async fn handle_output_item_done_records_image_save_history_message() {\n .expect(\"image generation item should succeed\");\n \n let history = session.clone_history().await;\n+ let image_output_...", - "path": "codex-rs/core/src/codex_tests.rs", - "status": "modified" - }, - { - "additions": 66, - "deletions": 18, - "patch_excerpt": "@@ -1,3 +1,4 @@\n+use std::path::Path;\n use std::path::PathBuf;\n use std::pin::Pin;\n use std::sync::Arc;\n@@ -30,6 +31,36 @@ use futures::Future;\n use tracing::debug;\n use tracing::instrument;\n \n+const GENERATED_IMAGE_ARTIFACTS_DIR: &str = \"generated_images\";\n+\n+pub(crate) fn image_generation_artifact_path(\n+ codex_home: &Path,\n+ session_id: &str,\n+ call_id: &str,\n+) -> PathBuf {\n+ let sanitize = |value: &str| {\n+ let mut sanitized: String = value\n+ .chars()\n+ .map(|ch| {\n+ if ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' {\n+ ch\n+ } else {\n+ '_'\n+ }\n+ })\n+ .collect();\n+ if sanitized.is_empty() {\n+ sanitized = \"generated_image\".to_string();\n+ }\n+ sanitized\n+ };\n+\n+ codex_home\n+ .join(GENERATED_IM...", - "path": "codex-rs/core/src/stream_events_utils.rs", - "status": "modified" - }, - { - "additions": 42, - "deletions": 19, - "patch_excerpt": "@@ -1,4 +1,5 @@\n use super::handle_non_tool_response_item;\n+use super::image_generation_artifact_path;\n use super::last_assistant_message_from_item;\n use super::save_image_generation_result;\n use crate::codex::make_session_and_context;\n@@ -80,13 +81,16 @@ fn last_assistant_message_from_item_returns_none_for_plan_only_hidden_message()\n }\n \n #[tokio::test]\n-async fn save_image_generation_result_saves_base64_to_png_in_temp_dir() {\n- let expected_path = std::env::temp_dir().join(\"ig_save_base64.png\");\n+async fn save_image_generation_result_saves_base64_to_png_in_codex_home() {\n+ let codex_home = tempfile::tempdir().expect(\"create codex home\");\n+ let expected_path =\n+ image_generation_artifact_path(codex_home.path(), \"session-1\", \"ig_save_base64\");\n let _ = std::fs::remove_file(&expected_path);\n \n- let saved_path = save_image_generation_result(\"ig_save_base64\", \"Zm9v\")\n...", - "path": "codex-rs/core/src/stream_events_utils_tests.rs", - "status": "modified" - }, - { - "additions": 48, - "deletions": 4, - "patch_excerpt": "@@ -35,6 +35,32 @@ use core_test_support::test_codex::test_codex;\n use core_test_support::wait_for_event;\n use core_test_support::wait_for_event_match;\n use pretty_assertions::assert_eq;\n+use std::path::Path;\n+use std::path::PathBuf;\n+\n+fn image_generation_artifact_path(codex_home: &Path, session_id: &str, call_id: &str) -> PathBuf {\n+ fn sanitize(value: &str) -> String {\n+ let mut sanitized: String = value\n+ .chars()\n+ .map(|ch| {\n+ if ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' {\n+ ch\n+ } else {\n+ '_'\n+ }\n+ })\n+ .collect();\n+ if sanitized.is_empty() {\n+ sanitized = \"generated_image\".to_string();\n+ }\n+ sanitized\n+ }\n+\n+ codex_home\n+ .join(\"generated_images\")\n+ .join(sanitize(session_id))\n+ ...", - "path": "codex-rs/core/tests/suite/items.rs", - "status": "modified" - }, - { - "additions": 44, - "deletions": 9, - "patch_excerpt": "@@ -32,8 +32,34 @@ use core_test_support::skip_if_no_network;\n use core_test_support::test_codex::test_codex;\n use core_test_support::wait_for_event;\n use pretty_assertions::assert_eq;\n+use std::path::Path;\n+use std::path::PathBuf;\n use wiremock::MockServer;\n \n+fn image_generation_artifact_path(codex_home: &Path, session_id: &str, call_id: &str) -> PathBuf {\n+ fn sanitize(value: &str) -> String {\n+ let mut sanitized: String = value\n+ .chars()\n+ .map(|ch| {\n+ if ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' {\n+ ch\n+ } else {\n+ '_'\n+ }\n+ })\n+ .collect();\n+ if sanitized.is_empty() {\n+ sanitized = \"generated_image\".to_string();\n+ }\n+ sanitized\n+ }\n+\n+ codex_home\n+ .join(\"generated_images\")\n+ .join(s...", - "path": "codex-rs/core/tests/suite/model_switching.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -36,7 +36,7 @@ pub(crate) struct ExecServerFileSystem {\n impl Default for ExecServerFileSystem {\n fn default() -> Self {\n Self {\n- file_system: Arc::new(Environment.get_filesystem()),\n+ file_system: Arc::new(Environment::default().get_filesystem()),\n }\n }\n }", - "path": "codex-rs/exec-server/src/server/filesystem.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints." - ], - "primary_pr": { - "body": "saving image gen default save directory to codex_home/imagegen/thread_id/\r\n", - "labels": [], - "merged_at": "2026-03-19T22:16:26Z", - "number": 15222, - "state": "merged", - "title": "changed save directory to codex_home", - "url": "https://github.com/openai/codex/pull/15222" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15223.json b/artifacts/github/bundles/openai-codex-pr-15223.json deleted file mode 100644 index 15c0740..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15223.json +++ /dev/null @@ -1,285 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "won-openai", - "committed_at": "2026-03-19T20:14:47Z", - "message": "Restore image generation items in resumed thread history", - "sha": "f065885e2c82c85a3f62148571ca52a0a26e3f8f", - "url": "https://github.com/openai/codex/commit/f065885e2c82c85a3f62148571ca52a0a26e3f8f" - }, - { - "author": "won-openai", - "committed_at": "2026-03-19T21:15:54Z", - "message": "Persist image generation end events for history replay", - "sha": "005e6fc0cfe750bcf8c6d98130fcb5e854aefd4f", - "url": "https://github.com/openai/codex/commit/005e6fc0cfe750bcf8c6d98130fcb5e854aefd4f" - }, - { - "author": "won-openai", - "committed_at": "2026-03-19T22:17:29Z", - "message": "Restore saved image paths in resumed history", - "sha": "e870aae4dbb47ee7e65daa46031bebc272d9b865", - "url": "https://github.com/openai/codex/commit/e870aae4dbb47ee7e65daa46031bebc272d9b865" - }, - { - "author": "won-openai", - "committed_at": "2026-03-20T02:20:50Z", - "message": "Add copy guidance for generated images", - "sha": "85464fb6329466858d26c7646e5362121baa6287", - "url": "https://github.com/openai/codex/commit/85464fb6329466858d26c7646e5362121baa6287" - }, - { - "author": "won-openai", - "committed_at": "2026-03-20T03:01:30Z", - "message": "Fix tui app server image generation adapter", - "sha": "f1a6367e2d0514887f96b001b6a236c178654bbd", - "url": "https://github.com/openai/codex/commit/f1a6367e2d0514887f96b001b6a236c178654bbd" - }, - { - "author": "won-openai", - "committed_at": "2026-03-20T03:23:20Z", - "message": "Add tests for resumed image history preservation", - "sha": "ace552c658ff7c118523c4a37c72a4d088868968", - "url": "https://github.com/openai/codex/commit/ace552c658ff7c118523c4a37c72a4d088868968" - }, - { - "author": "won-openai", - "committed_at": "2026-03-20T03:29:04Z", - "message": "Remove temporary image history proof tests", - "sha": "b1ed4a7cb50fedd801033ba1532586548249606b", - "url": "https://github.com/openai/codex/commit/b1ed4a7cb50fedd801033ba1532586548249606b" - }, - { - "author": "won-openai", - "committed_at": "2026-03-20T03:41:33Z", - "message": "Avoid duplicating saved image blobs in limited rollouts", - "sha": "050a4783b9da451f169a9fafb76e05efa90daa34", - "url": "https://github.com/openai/codex/commit/050a4783b9da451f169a9fafb76e05efa90daa34" - }, - { - "author": "won-openai", - "committed_at": "2026-03-20T04:00:46Z", - "message": "Remove rollout image persistence test", - "sha": "66aa6d19363847463f3283d9462a03583b1ad2f1", - "url": "https://github.com/openai/codex/commit/66aa6d19363847463f3283d9462a03583b1ad2f1" - }, - { - "author": "won-openai", - "committed_at": "2026-03-20T04:15:56Z", - "message": "Revert rollout image blob clearing", - "sha": "6e7d37205eca1e7f2f9915b9306163d4bd7dbbdd", - "url": "https://github.com/openai/codex/commit/6e7d37205eca1e7f2f9915b9306163d4bd7dbbdd" - }, - { - "author": "won-openai", - "committed_at": "2026-03-20T04:38:25Z", - "message": "Trim restore image history branch scope", - "sha": "0552a98268e50d0397f6fcde32020648a530ebb1", - "url": "https://github.com/openai/codex/commit/0552a98268e50d0397f6fcde32020648a530ebb1" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [], - "files": [ - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -2817,6 +2817,12 @@\n \"null\"\n ]\n },\n+ \"savedPath\": {\n+ \"type\": [\n+ \"string\",\n+ \"null\"\n+ ]\n+ },\n \"status\": {\n \"type\": \"string\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/ServerNotification.json", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -12540,6 +12540,12 @@\n \"null\"\n ]\n },\n+ \"savedPath\": {\n+ \"type\": [\n+ \"string\",\n+ \"null\"\n+ ]\n+ },\n \"status\": {\n \"type\": \"string\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -10300,6 +10300,12 @@\n \"null\"\n ]\n },\n+ \"savedPath\": {\n+ \"type\": [\n+ \"string\",\n+ \"null\"\n+ ]\n+ },\n \"status\": {\n \"type\": \"string\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -1026,6 +1026,12 @@\n \"null\"\n ]\n },\n+ \"savedPath\": {\n+ \"type\": [\n+ \"string\",\n+ \"null\"\n+ ]\n+ },\n \"status\": {\n \"type\": \"string\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -1026,6 +1026,12 @@\n \"null\"\n ]\n },\n+ \"savedPath\": {\n+ \"type\": [\n+ \"string\",\n+ \"null\"\n+ ]\n+ },\n \"status\": {\n \"type\": \"string\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -1140,6 +1140,12 @@\n \"null\"\n ]\n },\n+ \"savedPath\": {\n+ \"type\": [\n+ \"string\",\n+ \"null\"\n+ ]\n+ },\n \"status\": {\n \"type\": \"string\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -1633,6 +1633,12 @@\n \"null\"\n ]\n },\n+ \"savedPath\": {\n+ \"type\": [\n+ \"string\",\n+ \"null\"\n+ ]\n+ },\n \"status\": {\n \"type\": \"string\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -1391,6 +1391,12 @@\n \"null\"\n ]\n },\n+ \"savedPath\": {\n+ \"type\": [\n+ \"string\",\n+ \"null\"\n+ ]\n+ },\n \"status\": {\n \"type\": \"string\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -1391,6 +1391,12 @@\n \"null\"\n ]\n },\n+ \"savedPath\": {\n+ \"type\": [\n+ \"string\",\n+ \"null\"\n+ ]\n+ },\n \"status\": {\n \"type\": \"string\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -1391,6 +1391,12 @@\n \"null\"\n ]\n },\n+ \"savedPath\": {\n+ \"type\": [\n+ \"string\",\n+ \"null\"\n+ ]\n+ },\n \"status\": {\n \"type\": \"string\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -1633,6 +1633,12 @@\n \"null\"\n ]\n },\n+ \"savedPath\": {\n+ \"type\": [\n+ \"string\",\n+ \"null\"\n+ ]\n+ },\n \"status\": {\n \"type\": \"string\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -1391,6 +1391,12 @@\n \"null\"\n ]\n },\n+ \"savedPath\": {\n+ \"type\": [\n+ \"string\",\n+ \"null\"\n+ ]\n+ },\n \"status\": {\n \"type\": \"string\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -1633,6 +1633,12 @@\n \"null\"\n ]\n },\n+ \"savedPath\": {\n+ \"type\": [\n+ \"string\",\n+ \"null\"\n+ ]\n+ },\n \"status\": {\n \"type\": \"string\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -1391,6 +1391,12 @@\n \"null\"\n ]\n },\n+ \"savedPath\": {\n+ \"type\": [\n+ \"string\",\n+ \"null\"\n+ ]\n+ },\n \"status\": {\n \"type\": \"string\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -1391,6 +1391,12 @@\n \"null\"\n ]\n },\n+ \"savedPath\": {\n+ \"type\": [\n+ \"string\",\n+ \"null\"\n+ ]\n+ },\n \"status\": {\n \"type\": \"string\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -1140,6 +1140,12 @@\n \"null\"\n ]\n },\n+ \"savedPath\": {\n+ \"type\": [\n+ \"string\",\n+ \"null\"\n+ ]\n+ },\n \"status\": {\n \"type\": \"string\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -1140,6 +1140,12 @@\n \"null\"\n ]\n },\n+ \"savedPath\": {\n+ \"type\": [\n+ \"string\",\n+ \"null\"\n+ ]\n+ },\n \"status\": {\n \"type\": \"string\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -1140,6 +1140,12 @@\n \"null\"\n ]\n },\n+ \"savedPath\": {\n+ \"type\": [\n+ \"string\",\n+ \"null\"\n+ ]\n+ },\n \"status\": {\n \"type\": \"string\"\n },", - "path": "codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -97,4 +97,4 @@ reasoningEffort: ReasoningEffort | null,\n /**\n * Last known status of the target agents, when available.\n */\n-agentsStates: { [key in string]?: CollabAgentState }, } | { \"type\": \"webSearch\", id: string, query: string, action: WebSearchAction | null, } | { \"type\": \"imageView\", id: string, path: string, } | { \"type\": \"imageGeneration\", id: string, status: string, revisedPrompt: string | null, result: string, } | { \"type\": \"enteredReviewMode\", id: string, review: string, } | { \"type\": \"exitedReviewMode\", id: string, review: string, } | { \"type\": \"contextCompaction\", id: string, };\n+agentsStates: { [key in string]?: CollabAgentState }, } | { \"type\": \"webSearch\", id: string, query: string, action: WebSearchAction | null, } | { \"type\": \"imageView\", id: string, path: string, } | { \"type\": \"imageGeneration\", id: string, status: string, revisedPrompt: string | null, result: st...", - "path": "codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts", - "status": "modified" - }, - { - "additions": 57, - "deletions": 0, - "patch_excerpt": "@@ -569,6 +569,7 @@ impl ThreadHistoryBuilder {\n status: String::new(),\n revised_prompt: None,\n result: String::new(),\n+ saved_path: None,\n };\n self.upsert_item_in_current_turn(item);\n }\n@@ -579,6 +580,7 @@ impl ThreadHistoryBuilder {\n status: payload.status.clone(),\n revised_prompt: payload.revised_prompt.clone(),\n result: payload.result.clone(),\n+ saved_path: payload.saved_path.clone(),\n };\n self.upsert_item_in_current_turn(item);\n }\n@@ -1385,6 +1387,61 @@ mod tests {\n );\n }\n \n+ #[test]\n+ fn replays_image_generation_end_events_into_turn_history() {\n+ let items = vec![\n+ RolloutItem::EventMsg(EventMsg::TurnStarted(TurnStartedEvent {\n+ turn_id: \"turn-image\".into(),\n+ model_context_window: No...", - "path": "codex-rs/app-server-protocol/src/protocol/thread_history.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 0, - "patch_excerpt": "@@ -4256,6 +4256,9 @@ pub enum ThreadItem {\n status: String,\n revised_prompt: Option,\n result: String,\n+ #[serde(default, skip_serializing_if = \"Option::is_none\")]\n+ #[ts(optional)]\n+ saved_path: Option,\n },\n #[serde(rename_all = \"camelCase\")]\n #[ts(rename_all = \"camelCase\")]\n@@ -4432,6 +4435,7 @@ impl From for ThreadItem {\n status: image.status,\n revised_prompt: image.revised_prompt,\n result: image.result,\n+ saved_path: image.saved_path,\n },\n CoreTurnItem::ContextCompaction(compaction) => {\n ThreadItem::ContextCompaction { id: compaction.id }", - "path": "codex-rs/app-server-protocol/src/protocol/v2.rs", - "status": "modified" - }, - { - "additions": 6, - "deletions": 1, - "patch_excerpt": "@@ -3751,7 +3751,12 @@ async fn handle_output_item_done_records_image_save_history_message() {\n image_output_path.display(),\n ))\n .into();\n- assert_eq!(history.raw_items(), &[save_message, item]);\n+ let copy_message: ResponseItem = DeveloperInstructions::new(\n+ \"If you need to use a generated image at another path, copy it and leave the original in place unless the user explicitly asks you to delete it.\"\n+ .to_string(),\n+ )\n+ .into();\n+ assert_eq!(history.raw_items(), &[save_message, copy_message, item]);\n assert_eq!(\n std::fs::read(&expected_saved_path).expect(\"saved file\"),\n b\"foo\"", - "path": "codex-rs/core/src/codex_tests.rs", - "status": "modified" - }, - { - "additions": 26, - "deletions": 2, - "patch_excerpt": "@@ -105,7 +105,8 @@ fn event_msg_persistence_mode(ev: &EventMsg) -> Option {\n | EventMsg::UndoCompleted(_)\n | EventMsg::TurnAborted(_)\n | EventMsg::TurnStarted(_)\n- | EventMsg::TurnComplete(_) => Some(EventPersistenceMode::Limited),\n+ | EventMsg::TurnComplete(_)\n+ | EventMsg::ImageGenerationEnd(_) => Some(EventPersistenceMode::Limited),\n EventMsg::ItemCompleted(event) => {\n // Plan items are derived from streaming tags and are not part of the\n // raw ResponseItem history, so we persist their completion to replay\n@@ -123,7 +124,6 @@ fn event_msg_persistence_mode(ev: &EventMsg) -> Option {\n | EventMsg::PatchApplyEnd(_)\n | EventMsg::McpToolCallEnd(_)\n | EventMsg::ViewImageToolCall(_)\n- | EventMsg::ImageGenerationEnd(_)\n | EventMsg::C...", - "path": "codex-rs/core/src/rollout/policy.rs", - "status": "modified" - }, - { - "additions": 6, - "deletions": 4, - "patch_excerpt": "@@ -372,11 +372,13 @@ pub(crate) async fn handle_non_tool_response_item(\n image_output_path.display(),\n ))\n .into();\n- sess.record_conversation_items(\n- turn_context,\n- std::slice::from_ref(&message),\n+ let copy_message: ResponseItem = DeveloperInstructions::new(\n+ \"If you need to use a generated image at another path, copy it and leave the original in place unless the user explicitly asks you to delete it.\"\n+ .to_string(),\n )\n- .await;\n+ .into();\n+ sess.record_conversation_items(turn_context, &[message, copy_message])\n+ .await;\n ...", - "path": "codex-rs/core/src/stream_events_utils.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 1, - "patch_excerpt": "@@ -995,12 +995,13 @@ fn thread_item_to_core(item: &ThreadItem) -> Option {\n status,\n revised_prompt,\n result,\n+ saved_path,\n } => Some(TurnItem::ImageGeneration(ImageGenerationItem {\n id: id.clone(),\n status: status.clone(),\n revised_prompt: revised_prompt.clone(),\n result: result.clone(),\n- saved_path: None,\n+ saved_path: saved_path.clone(),\n })),\n ThreadItem::ContextCompaction { id } => {\n Some(TurnItem::ContextCompaction(ContextCompactionItem {\n@@ -1850,6 +1851,7 @@ mod tests {\n status: \"completed\".to_string(),\n revised_prompt: Some(\"diagram\".to_string()),\n result: \"image.png\".to_string(),\n+ saved_path: None,\n ...", - "path": "codex-rs/tui_app_server/src/app/app_server_adapter.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 1, - "patch_excerpt": "@@ -5725,13 +5725,14 @@ impl ChatWidget {\n status,\n revised_prompt,\n result,\n+ saved_path,\n } => {\n self.on_image_generation_end(ImageGenerationEndEvent {\n call_id: id,\n result,\n revised_prompt,\n status,\n- saved_path: None,\n+ saved_path,\n });\n }\n ThreadItem::EnteredReviewMode { review, .. } => {", - "path": "codex-rs/tui_app_server/src/chatwidget.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints." - ], - "primary_pr": { - "body": "Restore image generation items in resumed thread history\r\n", - "labels": [], - "merged_at": "2026-03-20T05:57:17Z", - "number": 15223, - "state": "merged", - "title": "Feat/restore image generation history", - "url": "https://github.com/openai/codex/pull/15223" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15232.json b/artifacts/github/bundles/openai-codex-pr-15232.json deleted file mode 100644 index 6033aed..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15232.json +++ /dev/null @@ -1,180 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "pakrym-oai", - "committed_at": "2026-03-19T21:52:37Z", - "message": "Extract local and remote fs APIs", - "sha": "4b42ea880de8db20694905bb2057b711f4eaf9b7", - "url": "https://github.com/openai/codex/commit/4b42ea880de8db20694905bb2057b711f4eaf9b7" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-19T22:41:06Z", - "message": "Add parameterized file system tests", - "sha": "ec76d77fe343f95433bcc9d2458fa1ea61266c19", - "url": "https://github.com/openai/codex/commit/ec76d77fe343f95433bcc9d2458fa1ea61266c19" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-19T23:01:09Z", - "message": "Merge remote-tracking branch 'origin/main' into pakrym/compare-execserverfilesystem-and", - "sha": "7dc075970b435ca4b3269db46e5e0aafd2d12fc9", - "url": "https://github.com/openai/codex/commit/7dc075970b435ca4b3269db46e5e0aafd2d12fc9" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-19T23:15:10Z", - "message": "Print exec server websocket URL", - "sha": "4a9479b983cd5de31690ab34621182e00de1b045", - "url": "https://github.com/openai/codex/commit/4a9479b983cd5de31690ab34621182e00de1b045" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-19T23:26:51Z", - "message": "exec-server: remove expect usage in filesystem tests", - "sha": "4b303df05795a90402c8b9ee08e719cf81b1f659", - "url": "https://github.com/openai/codex/commit/4b303df05795a90402c8b9ee08e719cf81b1f659" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "PRC", - "API", - "URL", - "UNIX_EPOCH", - "MAX_READ_FILE_BYTES", - "STANDARD", - "INVALID_REQUEST_ERROR_CODE", - "JSONRPCE", - "RETAINED_OUTPUT_BYTES_PER_PROCESS", - "EVENT_TIMEOUT", - "--listen", - "CONNECT_TIMEOUT" - ], - "files": [ - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -2011,6 +2011,7 @@ dependencies = [\n \"serde\",\n \"serde_json\",\n \"tempfile\",\n+ \"test-case\",\n \"thiserror 2.0.18\",\n \"tokio\",\n \"tokio-tungstenite\",", - "path": "codex-rs/Cargo.lock", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -34,7 +34,7 @@ pub(crate) struct FsApi {\n impl Default for FsApi {\n fn default() -> Self {\n Self {\n- file_system: Arc::new(Environment::default().get_filesystem()),\n+ file_system: Environment::default().get_filesystem(),\n }\n }\n }", - "path": "codex-rs/app-server/src/fs_api.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 1, - "patch_excerpt": "@@ -1,5 +1,4 @@\n use async_trait::async_trait;\n-use codex_exec_server::ExecutorFileSystem;\n use codex_protocol::models::FunctionCallOutputBody;\n use codex_protocol::models::FunctionCallOutputContentItem;\n use codex_protocol::models::FunctionCallOutputPayload;", - "path": "codex-rs/core/src/tools/handlers/view_image.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -44,3 +44,4 @@ anyhow = { workspace = true }\n codex-utils-cargo-bin = { workspace = true }\n pretty_assertions = { workspace = true }\n tempfile = { workspace = true }\n+test-case = \"3.3.1\"", - "path": "codex-rs/exec-server/Cargo.toml", - "status": "modified" - }, - { - "additions": 10, - "deletions": 4, - "patch_excerpt": "@@ -1,8 +1,10 @@\n use crate::ExecServerClient;\n use crate::ExecServerError;\n use crate::RemoteExecServerConnectArgs;\n-use crate::fs;\n-use crate::fs::ExecutorFileSystem;\n+use crate::file_system::ExecutorFileSystem;\n+use crate::local_file_system::LocalFileSystem;\n+use crate::remote_file_system::RemoteFileSystem;\n+use std::sync::Arc;\n \n #[derive(Clone, Default)]\n pub struct Environment {\n@@ -56,8 +58,12 @@ impl Environment {\n self.remote_exec_server_client.as_ref()\n }\n \n- pub fn get_filesystem(&self) -> impl ExecutorFileSystem + use<> {\n- fs::LocalFileSystem\n+ pub fn get_filesystem(&self) -> Arc {\n+ if let Some(client) = self.remote_exec_server_client.clone() {\n+ Arc::new(RemoteFileSystem::new(client))\n+ } else {\n+ Arc::new(LocalFileSystem)\n+ }\n }\n }", - "path": "codex-rs/exec-server/src/environment.rs", - "status": "modified" - }, - { - "additions": 65, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,65 @@\n+use async_trait::async_trait;\n+use codex_utils_absolute_path::AbsolutePathBuf;\n+use tokio::io;\n+\n+#[derive(Clone, Copy, Debug, Eq, PartialEq)]\n+pub struct CreateDirectoryOptions {\n+ pub recursive: bool,\n+}\n+\n+#[derive(Clone, Copy, Debug, Eq, PartialEq)]\n+pub struct RemoveOptions {\n+ pub recursive: bool,\n+ pub force: bool,\n+}\n+\n+#[derive(Clone, Copy, Debug, Eq, PartialEq)]\n+pub struct CopyOptions {\n+ pub recursive: bool,\n+}\n+\n+#[derive(Clone, Debug, Eq, PartialEq)]\n+pub struct FileMetadata {\n+ pub is_directory: bool,\n+ pub is_file: bool,\n+ pub created_at_ms: i64,\n+ pub modified_at_ms: i64,\n+}\n+\n+#[derive(Clone, Debug, Eq, PartialEq)]\n+pub struct ReadDirectoryEntry {\n+ pub file_name: String,\n+ pub is_directory: bool,\n+ pub is_file: bool,\n+}\n+\n+pub type FileSystemResult = io::Result;\n+\n+#[async_trait]\n+pub trait ExecutorFileSystem: Se...", - "path": "codex-rs/exec-server/src/file_system.rs", - "status": "added" - }, - { - "additions": 10, - "deletions": 8, - "patch_excerpt": "@@ -2,8 +2,10 @@ mod client;\n mod client_api;\n mod connection;\n mod environment;\n-mod fs;\n+mod file_system;\n+mod local_file_system;\n mod protocol;\n+mod remote_file_system;\n mod rpc;\n mod server;\n \n@@ -28,13 +30,13 @@ pub use codex_app_server_protocol::FsRemoveResponse;\n pub use codex_app_server_protocol::FsWriteFileParams;\n pub use codex_app_server_protocol::FsWriteFileResponse;\n pub use environment::Environment;\n-pub use fs::CopyOptions;\n-pub use fs::CreateDirectoryOptions;\n-pub use fs::ExecutorFileSystem;\n-pub use fs::FileMetadata;\n-pub use fs::FileSystemResult;\n-pub use fs::ReadDirectoryEntry;\n-pub use fs::RemoveOptions;\n+pub use file_system::CopyOptions;\n+pub use file_system::CreateDirectoryOptions;\n+pub use file_system::ExecutorFileSystem;\n+pub use file_system::FileMetadata;\n+pub use file_system::FileSystemResult;\n+pub use file_system::ReadDirectoryEntry;\n+pub use file_system::Remov...", - "path": "codex-rs/exec-server/src/lib.rs", - "status": "modified" - }, - { - "additions": 8, - "deletions": 62, - "patch_excerpt": "@@ -7,69 +7,15 @@ use std::time::SystemTime;\n use std::time::UNIX_EPOCH;\n use tokio::io;\n \n-const MAX_READ_FILE_BYTES: u64 = 512 * 1024 * 1024;\n-\n-#[derive(Clone, Copy, Debug, Eq, PartialEq)]\n-pub struct CreateDirectoryOptions {\n- pub recursive: bool,\n-}\n-\n-#[derive(Clone, Copy, Debug, Eq, PartialEq)]\n-pub struct RemoveOptions {\n- pub recursive: bool,\n- pub force: bool,\n-}\n-\n-#[derive(Clone, Copy, Debug, Eq, PartialEq)]\n-pub struct CopyOptions {\n- pub recursive: bool,\n-}\n-\n-#[derive(Clone, Debug, Eq, PartialEq)]\n-pub struct FileMetadata {\n- pub is_directory: bool,\n- pub is_file: bool,\n- pub created_at_ms: i64,\n- pub modified_at_ms: i64,\n-}\n+use crate::CopyOptions;\n+use crate::CreateDirectoryOptions;\n+use crate::ExecutorFileSystem;\n+use crate::FileMetadata;\n+use crate::FileSystemResult;\n+use crate::ReadDirectoryEntry;\n+use crate::RemoveOptions;\n \n-#[derive(Clone, D...", - "path": "codex-rs/exec-server/src/local_file_system.rs", - "status": "renamed" - }, - { - "additions": 154, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,154 @@\n+use async_trait::async_trait;\n+use base64::Engine as _;\n+use base64::engine::general_purpose::STANDARD;\n+use codex_app_server_protocol::FsCopyParams;\n+use codex_app_server_protocol::FsCreateDirectoryParams;\n+use codex_app_server_protocol::FsGetMetadataParams;\n+use codex_app_server_protocol::FsReadDirectoryParams;\n+use codex_app_server_protocol::FsReadFileParams;\n+use codex_app_server_protocol::FsRemoveParams;\n+use codex_app_server_protocol::FsWriteFileParams;\n+use codex_utils_absolute_path::AbsolutePathBuf;\n+use tokio::io;\n+\n+use crate::CopyOptions;\n+use crate::CreateDirectoryOptions;\n+use crate::ExecServerClient;\n+use crate::ExecServerError;\n+use crate::ExecutorFileSystem;\n+use crate::FileMetadata;\n+use crate::FileSystemResult;\n+use crate::ReadDirectoryEntry;\n+use crate::RemoveOptions;\n+\n+const INVALID_REQUEST_ERROR_CODE: i64 = -32600;\n+\n+#[derive(Clone)]\n+pub(crate) ...", - "path": "codex-rs/exec-server/src/remote_file_system.rs", - "status": "added" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,4 +1,4 @@\n-mod filesystem;\n+mod file_system_handler;\n mod handler;\n mod processor;\n mod registry;", - "path": "codex-rs/exec-server/src/server.rs", - "status": "modified" - }, - { - "additions": 5, - "deletions": 14, - "patch_excerpt": "@@ -1,5 +1,4 @@\n use std::io;\n-use std::sync::Arc;\n \n use base64::Engine as _;\n use base64::engine::general_purpose::STANDARD;\n@@ -22,26 +21,18 @@ use codex_app_server_protocol::JSONRPCErrorError;\n \n use crate::CopyOptions;\n use crate::CreateDirectoryOptions;\n-use crate::Environment;\n use crate::ExecutorFileSystem;\n use crate::RemoveOptions;\n+use crate::local_file_system::LocalFileSystem;\n use crate::rpc::internal_error;\n use crate::rpc::invalid_request;\n \n-#[derive(Clone)]\n-pub(crate) struct ExecServerFileSystem {\n- file_system: Arc,\n+#[derive(Clone, Default)]\n+pub(crate) struct FileSystemHandler {\n+ file_system: LocalFileSystem,\n }\n \n-impl Default for ExecServerFileSystem {\n- fn default() -> Self {\n- Self {\n- file_system: Arc::new(Environment::default().get_filesystem()),\n- }\n- }\n-}\n-\n-impl ExecServerFileSystem {\n+impl FileSy...", - "path": "codex-rs/exec-server/src/server/file_system_handler.rs", - "status": "renamed" - }, - { - "additions": 3, - "deletions": 3, - "patch_excerpt": "@@ -43,7 +43,7 @@ use crate::rpc::RpcNotificationSender;\n use crate::rpc::internal_error;\n use crate::rpc::invalid_params;\n use crate::rpc::invalid_request;\n-use crate::server::filesystem::ExecServerFileSystem;\n+use crate::server::file_system_handler::FileSystemHandler;\n \n const RETAINED_OUTPUT_BYTES_PER_PROCESS: usize = 1024 * 1024;\n #[cfg(test)]\n@@ -75,7 +75,7 @@ enum ProcessEntry {\n \n pub(crate) struct ExecServerHandler {\n notifications: RpcNotificationSender,\n- file_system: ExecServerFileSystem,\n+ file_system: FileSystemHandler,\n processes: Arc>>,\n initialize_requested: AtomicBool,\n initialized: AtomicBool,\n@@ -85,7 +85,7 @@ impl ExecServerHandler {\n pub(crate) fn new(notifications: RpcNotificationSender) -> Self {\n Self {\n notifications,\n- file_system: ExecServerFileSystem::default(),\n+ ...", - "path": "codex-rs/exec-server/src/server/handler.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -59,6 +59,7 @@ async fn run_websocket_listener(\n let listener = TcpListener::bind(bind_address).await?;\n let local_addr = listener.local_addr()?;\n tracing::info!(\"codex-exec-server listening on ws://{local_addr}\");\n+ println!(\"ws://{local_addr}\");\n \n loop {\n let (stream, peer_addr) = listener.accept().await?;", - "path": "codex-rs/exec-server/src/server/transport.rs", - "status": "modified" - }, - { - "additions": 39, - "deletions": 11, - "patch_excerpt": "@@ -11,6 +11,8 @@ use codex_app_server_protocol::RequestId;\n use codex_utils_cargo_bin::cargo_bin;\n use futures::SinkExt;\n use futures::StreamExt;\n+use tokio::io::AsyncBufReadExt;\n+use tokio::io::BufReader;\n use tokio::process::Child;\n use tokio::process::Command;\n use tokio::time::Instant;\n@@ -25,6 +27,7 @@ const EVENT_TIMEOUT: Duration = Duration::from_secs(5);\n \n pub(crate) struct ExecServerHarness {\n child: Child,\n+ websocket_url: String,\n websocket: tokio_tungstenite::WebSocketStream<\n tokio_tungstenite::MaybeTlsStream,\n >,\n@@ -39,23 +42,28 @@ impl Drop for ExecServerHarness {\n \n pub(crate) async fn exec_server() -> anyhow::Result {\n let binary = cargo_bin(\"codex-exec-server\")?;\n- let websocket_url = reserve_websocket_url()?;\n let mut child = Command::new(binary);\n- child.args([\"--listen\", &websocket_url]...", - "path": "codex-rs/exec-server/tests/common/exec_server.rs", - "status": "modified" - }, - { - "additions": 361, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,361 @@\n+#![cfg(unix)]\n+\n+mod common;\n+\n+use std::os::unix::fs::symlink;\n+use std::process::Command;\n+use std::sync::Arc;\n+\n+use anyhow::Context;\n+use anyhow::Result;\n+use codex_exec_server::CopyOptions;\n+use codex_exec_server::CreateDirectoryOptions;\n+use codex_exec_server::Environment;\n+use codex_exec_server::ExecutorFileSystem;\n+use codex_exec_server::ReadDirectoryEntry;\n+use codex_exec_server::RemoveOptions;\n+use codex_utils_absolute_path::AbsolutePathBuf;\n+use pretty_assertions::assert_eq;\n+use tempfile::TempDir;\n+use test_case::test_case;\n+\n+use common::exec_server::ExecServerHarness;\n+use common::exec_server::exec_server;\n+\n+struct FileSystemContext {\n+ file_system: Arc,\n+ _server: Option,\n+}\n+\n+async fn create_file_system_context(use_remote: bool) -> Result {\n+ if use_remote {\n+ let server = e...", - "path": "codex-rs/exec-server/tests/file_system.rs", - "status": "added" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "For each feature we have:\r\n1. Trait exposed on environment\r\n2. **Local Implementation** of the trait\r\n3. Remote implementation that uses the client to proxy via network\r\n4. Handler implementation that handles PRC requests and calls into **Local Implementation** ", - "labels": [], - "merged_at": "2026-03-20T00:08:04Z", - "number": 15232, - "state": "merged", - "title": "Refactor ExecServer filesystem split between local and remote", - "url": "https://github.com/openai/codex/pull/15232" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15233.json b/artifacts/github/bundles/openai-codex-pr-15233.json deleted file mode 100644 index 8b88d65..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15233.json +++ /dev/null @@ -1,175 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "starr-openai", - "committed_at": "2026-03-19T23:46:02Z", - "message": "Split exec process into local and remote implementations", - "sha": "1cb24257691875788c77da7afe84996a8ab65c71", - "url": "https://github.com/openai/codex/commit/1cb24257691875788c77da7afe84996a8ab65c71" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-20T00:00:17Z", - "message": "Initialize default local exec process", - "sha": "23b8169fed1b56552c924ad781bdc2cb33e5c481", - "url": "https://github.com/openai/codex/commit/23b8169fed1b56552c924ad781bdc2cb33e5c481" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-20T00:03:00Z", - "message": "Avoid expect in default environment setup", - "sha": "c23d879e26b5162107121070f403b1edb0173936", - "url": "https://github.com/openai/codex/commit/c23d879e26b5162107121070f403b1edb0173936" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-20T00:10:51Z", - "message": "Address exec process review comments", - "sha": "63333b07e51546ca2679d7f546ebe80f2449c891", - "url": "https://github.com/openai/codex/commit/63333b07e51546ca2679d7f546ebe80f2449c891" - }, - { - "author": "starr-openai", - "committed_at": "2026-03-20T00:12:20Z", - "message": "Fix environment test after accessor cleanup", - "sha": "25585b9aed8884ccab1b6f04c1289821761e9a79", - "url": "https://github.com/openai/codex/commit/25585b9aed8884ccab1b6f04c1289821761e9a79" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "RPC", - "CARGO_TARGET_DIR=~/.cache/cargo-target/codex", - "JSONRPCN", - "EXEC_EXITED_METHOD", - "EXEC_METHOD", - "EXEC_OUTPUT_DELTA_METHOD", - "CONNECT_TIMEOUT", - "INITIALIZE_TIMEOUT", - "INITIALIZE_METHOD", - "EXEC_READ_METHOD", - "EXEC_WRITE_METHOD", - "EXEC_TERMINATE_METHOD", - "FS_READ_FILE_METHOD", - "FS_WRITE_FILE_METHOD", - "FS_CREATE_DIRECTORY_METHOD", - "FS_GET_METADATA_METHOD", - "FS_READ_DIRECTORY_METHOD", - "FS_REMOVE_METHOD", - "FS_COPY_METHOD", - "INITIALIZED_METHOD", - "JSONRPCE", - "RETAINED_OUTPUT_BYTES_PER_PROCESS", - "EVENT_CHANNEL_CAPACITY", - "NOTIFICATION_CHANNEL_CAPACITY", - "EXITED_PROCESS_RETENTION", - "MAX" - ], - "files": [ - { - "additions": 51, - "deletions": 212, - "patch_excerpt": "@@ -18,16 +18,15 @@ use codex_app_server_protocol::FsWriteFileResponse;\n use codex_app_server_protocol::JSONRPCNotification;\n use serde_json::Value;\n use tokio::sync::broadcast;\n-use tokio::sync::mpsc;\n use tokio::time::timeout;\n use tokio_tungstenite::connect_async;\n use tracing::debug;\n use tracing::warn;\n \n use crate::client_api::ExecServerClientConnectOptions;\n-use crate::client_api::ExecServerEvent;\n use crate::client_api::RemoteExecServerConnectArgs;\n use crate::connection::JsonRpcConnection;\n+use crate::process::ExecServerEvent;\n use crate::protocol::EXEC_EXITED_METHOD;\n use crate::protocol::EXEC_METHOD;\n use crate::protocol::EXEC_OUTPUT_DELTA_METHOD;\n@@ -58,11 +57,6 @@ use crate::protocol::WriteResponse;\n use crate::rpc::RpcCallError;\n use crate::rpc::RpcClient;\n use crate::rpc::RpcClientEvent;\n-use crate::rpc::RpcNotificationSender;\n-use crate::rpc::RpcServerOutboundMessage;\n-\n-...", - "path": "codex-rs/exec-server/src/client.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 200, - "patch_excerpt": "@@ -1,200 +0,0 @@\n-use std::sync::Arc;\n-\n-use crate::protocol::ExecParams;\n-use crate::protocol::ExecResponse;\n-use crate::protocol::InitializeResponse;\n-use crate::protocol::ReadParams;\n-use crate::protocol::ReadResponse;\n-use crate::protocol::TerminateParams;\n-use crate::protocol::TerminateResponse;\n-use crate::protocol::WriteParams;\n-use crate::protocol::WriteResponse;\n-use crate::server::ExecServerHandler;\n-use codex_app_server_protocol::FsCopyParams;\n-use codex_app_server_protocol::FsCopyResponse;\n-use codex_app_server_protocol::FsCreateDirectoryParams;\n-use codex_app_server_protocol::FsCreateDirectoryResponse;\n-use codex_app_server_protocol::FsGetMetadataParams;\n-use codex_app_server_protocol::FsGetMetadataResponse;\n-use codex_app_server_protocol::FsReadDirectoryParams;\n-use codex_app_server_protocol::FsReadDirectoryResponse;\n-use codex_app_server_protocol::FsReadFileParams;\n-use c...", - "path": "codex-rs/exec-server/src/client/local_backend.rs", - "status": "removed" - }, - { - "additions": 0, - "deletions": 10, - "patch_excerpt": "@@ -1,8 +1,5 @@\n use std::time::Duration;\n \n-use crate::protocol::ExecExitedNotification;\n-use crate::protocol::ExecOutputDeltaNotification;\n-\n /// Connection options for any exec-server client transport.\n #[derive(Debug, Clone, PartialEq, Eq)]\n pub struct ExecServerClientConnectOptions {\n@@ -18,10 +15,3 @@ pub struct RemoteExecServerConnectArgs {\n pub connect_timeout: Duration,\n pub initialize_timeout: Duration,\n }\n-\n-/// Connection-level server events.\n-#[derive(Debug, Clone, PartialEq, Eq)]\n-pub enum ExecServerEvent {\n- OutputDelta(ExecOutputDeltaNotification),\n- Exited(ExecExitedNotification),\n-}", - "path": "codex-rs/exec-server/src/client_api.rs", - "status": "modified" - }, - { - "additions": 92, - "deletions": 22, - "patch_excerpt": "@@ -1,15 +1,42 @@\n+use std::sync::Arc;\n+\n use crate::ExecServerClient;\n use crate::ExecServerError;\n use crate::RemoteExecServerConnectArgs;\n use crate::file_system::ExecutorFileSystem;\n use crate::local_file_system::LocalFileSystem;\n+use crate::local_process::LocalProcess;\n+use crate::process::ExecProcess;\n use crate::remote_file_system::RemoteFileSystem;\n-use std::sync::Arc;\n+use crate::remote_process::RemoteProcess;\n \n-#[derive(Clone, Default)]\n+pub trait ExecutorEnvironment: Send + Sync {\n+ fn get_executor(&self) -> Arc;\n+}\n+\n+#[derive(Clone)]\n pub struct Environment {\n experimental_exec_server_url: Option,\n remote_exec_server_client: Option,\n+ executor: Arc,\n+}\n+\n+impl Default for Environment {\n+ fn default() -> Self {\n+ let local_process = LocalProcess::default();\n+ if let Err(err) = local_pr...", - "path": "codex-rs/exec-server/src/environment.rs", - "status": "modified" - }, - { - "additions": 6, - "deletions": 2, - "patch_excerpt": "@@ -4,23 +4,24 @@ mod connection;\n mod environment;\n mod file_system;\n mod local_file_system;\n+mod local_process;\n+mod process;\n mod protocol;\n mod remote_file_system;\n+mod remote_process;\n mod rpc;\n mod server;\n \n pub use client::ExecServerClient;\n pub use client::ExecServerError;\n pub use client_api::ExecServerClientConnectOptions;\n-pub use client_api::ExecServerEvent;\n pub use client_api::RemoteExecServerConnectArgs;\n pub use codex_app_server_protocol::FsCopyParams;\n pub use codex_app_server_protocol::FsCopyResponse;\n pub use codex_app_server_protocol::FsCreateDirectoryParams;\n pub use codex_app_server_protocol::FsCreateDirectoryResponse;\n pub use codex_app_server_protocol::FsGetMetadataParams;\n pub use codex_app_server_protocol::FsGetMetadataResponse;\n-pub use codex_app_server_protocol::FsReadDirectoryEntry;\n pub use codex_app_server_protocol::FsReadDirectoryParams;\n pub use codex_ap...", - "path": "codex-rs/exec-server/src/lib.rs", - "status": "modified" - }, - { - "additions": 515, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,515 @@\n+use std::collections::HashMap;\n+use std::collections::VecDeque;\n+use std::sync::Arc;\n+use std::sync::atomic::AtomicBool;\n+use std::sync::atomic::Ordering;\n+use std::time::Duration;\n+\n+use async_trait::async_trait;\n+use codex_app_server_protocol::JSONRPCErrorError;\n+use codex_utils_pty::ExecCommandSession;\n+use codex_utils_pty::TerminalSize;\n+use tokio::sync::Mutex;\n+use tokio::sync::Notify;\n+use tokio::sync::broadcast;\n+use tokio::sync::mpsc;\n+use tracing::warn;\n+\n+use crate::ExecProcess;\n+use crate::ExecServerError;\n+use crate::ExecServerEvent;\n+use crate::protocol::ExecExitedNotification;\n+use crate::protocol::ExecOutputDeltaNotification;\n+use crate::protocol::ExecOutputStream;\n+use crate::protocol::ExecParams;\n+use crate::protocol::ExecResponse;\n+use crate::protocol::InitializeResponse;\n+use crate::protocol::ProcessOutputChunk;\n+use crate::protocol::ReadParams;\n+use...", - "path": "codex-rs/exec-server/src/local_process.rs", - "status": "added" - }, - { - "additions": 35, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,35 @@\n+use async_trait::async_trait;\n+use tokio::sync::broadcast;\n+\n+use crate::ExecServerError;\n+use crate::protocol::ExecExitedNotification;\n+use crate::protocol::ExecOutputDeltaNotification;\n+use crate::protocol::ExecParams;\n+use crate::protocol::ExecResponse;\n+use crate::protocol::ReadParams;\n+use crate::protocol::ReadResponse;\n+use crate::protocol::TerminateResponse;\n+use crate::protocol::WriteResponse;\n+\n+#[derive(Debug, Clone, PartialEq, Eq)]\n+pub enum ExecServerEvent {\n+ OutputDelta(ExecOutputDeltaNotification),\n+ Exited(ExecExitedNotification),\n+}\n+\n+#[async_trait]\n+pub trait ExecProcess: Send + Sync {\n+ async fn start(&self, params: ExecParams) -> Result;\n+\n+ async fn read(&self, params: ReadParams) -> Result;\n+\n+ async fn write(\n+ &self,\n+ process_id: &str,\n+ chunk: Vec...", - "path": "codex-rs/exec-server/src/process.rs", - "status": "added" - }, - { - "additions": 51, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,51 @@\n+use async_trait::async_trait;\n+use tokio::sync::broadcast;\n+\n+use crate::ExecProcess;\n+use crate::ExecServerClient;\n+use crate::ExecServerError;\n+use crate::ExecServerEvent;\n+use crate::protocol::ExecParams;\n+use crate::protocol::ExecResponse;\n+use crate::protocol::ReadParams;\n+use crate::protocol::ReadResponse;\n+use crate::protocol::TerminateResponse;\n+use crate::protocol::WriteResponse;\n+\n+#[derive(Clone)]\n+pub(crate) struct RemoteProcess {\n+ client: ExecServerClient,\n+}\n+\n+impl RemoteProcess {\n+ pub(crate) fn new(client: ExecServerClient) -> Self {\n+ Self { client }\n+ }\n+}\n+\n+#[async_trait]\n+impl ExecProcess for RemoteProcess {\n+ async fn start(&self, params: ExecParams) -> Result {\n+ self.client.exec(params).await\n+ }\n+\n+ async fn read(&self, params: ReadParams) -> Result {...", - "path": "codex-rs/exec-server/src/remote_process.rs", - "status": "added" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -1,5 +1,6 @@\n mod file_system_handler;\n mod handler;\n+mod process_handler;\n mod processor;\n mod registry;\n mod transport;", - "path": "codex-rs/exec-server/src/server.rs", - "status": "modified" - }, - { - "additions": 17, - "deletions": 395, - "patch_excerpt": "@@ -1,10 +1,3 @@\n-use std::collections::HashMap;\n-use std::collections::VecDeque;\n-use std::sync::Arc;\n-use std::sync::atomic::AtomicBool;\n-use std::sync::atomic::Ordering;\n-use std::time::Duration;\n-\n use codex_app_server_protocol::FsCopyParams;\n use codex_app_server_protocol::FsCopyResponse;\n use codex_app_server_protocol::FsCreateDirectoryParams;\n@@ -20,498 +13,127 @@ use codex_app_server_protocol::FsRemoveResponse;\n use codex_app_server_protocol::FsWriteFileParams;\n use codex_app_server_protocol::FsWriteFileResponse;\n use codex_app_server_protocol::JSONRPCErrorError;\n-use codex_utils_pty::ExecCommandSession;\n-use codex_utils_pty::TerminalSize;\n-use tokio::sync::Mutex;\n-use tokio::sync::Notify;\n-use tracing::warn;\n \n-use crate::protocol::ExecExitedNotification;\n-use crate::protocol::ExecOutputDeltaNotification;\n-use crate::protocol::ExecOutputStream;\n use crate::protocol::ExecParams;\n...", - "path": "codex-rs/exec-server/src/server/handler.rs", - "status": "modified" - }, - { - "additions": 70, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,70 @@\n+use codex_app_server_protocol::JSONRPCErrorError;\n+\n+use crate::local_process::LocalProcess;\n+use crate::protocol::ExecParams;\n+use crate::protocol::ExecResponse;\n+use crate::protocol::InitializeResponse;\n+use crate::protocol::ReadParams;\n+use crate::protocol::ReadResponse;\n+use crate::protocol::TerminateParams;\n+use crate::protocol::TerminateResponse;\n+use crate::protocol::WriteParams;\n+use crate::protocol::WriteResponse;\n+use crate::rpc::RpcNotificationSender;\n+\n+#[derive(Clone)]\n+pub(crate) struct ProcessHandler {\n+ process: LocalProcess,\n+}\n+\n+impl ProcessHandler {\n+ pub(crate) fn new(notifications: RpcNotificationSender) -> Self {\n+ Self {\n+ process: LocalProcess::new(notifications),\n+ }\n+ }\n+\n+ pub(crate) async fn shutdown(&self) {\n+ self.process.shutdown().await;\n+ }\n+\n+ pub(crate) fn initialize(&self) -> Result,\n+ _server: Option,\n+}\n+\n+async fn create_process_context(use_remote: bool) -> Result {\n+ if use_remote {\n+ let server = exec_server().await?;\n+ let environment = Environment::create(Some(server.websocket_url().to_string())).await?;\n+ Ok(ProcessContext {\n+ process: environment.get_executor(),\n+ _server: Some(server),\n+ }...", - "path": "codex-rs/exec-server/tests/exec_process.rs", - "status": "added" - } - ], - "linked_issues": [ - "#15232" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "## Summary\n- match the exec-process structure to filesystem PR #15232\n- expose `ExecProcess` on `Environment`\n- make `LocalProcess` the real implementation and `RemoteProcess` a thin network proxy over `ExecServerClient`\n- make `ProcessHandler` a thin RPC adapter delegating to `LocalProcess`\n- add a shared local/remote process test\n\n## Validation\n- `just fmt`\n- `CARGO_TARGET_DIR=~/.cache/cargo-target/codex cargo test -p codex-exec-server`\n- `just fix -p codex-exec-server`\n", - "labels": [], - "merged_at": "2026-03-20T03:13:08Z", - "number": 15233, - "state": "merged", - "title": "Split exec process into local and remote implementations", - "url": "https://github.com/openai/codex/pull/15233" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15252.json b/artifacts/github/bundles/openai-codex-pr-15252.json deleted file mode 100644 index acf9a80..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15252.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "eternal-openai", - "committed_at": "2026-03-20T00:34:47Z", - "message": "disable hooks on windows for now", - "sha": "933ef3ccbdeb1877f2f9ba45d1379826f4c63088", - "url": "https://github.com/openai/codex/commit/933ef3ccbdeb1877f2f9ba45d1379826f4c63088" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [], - "files": [ - { - "additions": 11, - "deletions": 0, - "patch_excerpt": "@@ -74,6 +74,17 @@ impl ClaudeHooksEngine {\n };\n }\n \n+ if cfg!(windows) {\n+ return Self {\n+ handlers: Vec::new(),\n+ warnings: vec![\n+ \"Disabled `codex_hooks` for this session because `hooks.json` lifecycle hooks are not supported on Windows yet.\"\n+ .to_string(),\n+ ],\n+ shell,\n+ };\n+ }\n+\n let _ = schema_loader::generated_hook_schemas();\n let discovered = discovery::discover_handlers(config_layer_stack);\n Self {", - "path": "codex-rs/hooks/src/engine/mod.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "We'll verify a bit later that all of this works correctly and re-enable", - "labels": [], - "merged_at": "2026-03-20T04:31:57Z", - "number": 15252, - "state": "merged", - "title": "Disable hooks on windows for now", - "url": "https://github.com/openai/codex/pull/15252" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15253.json b/artifacts/github/bundles/openai-codex-pr-15253.json deleted file mode 100644 index 3025690..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15253.json +++ /dev/null @@ -1,1019 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "aibrahim-oai", - "committed_at": "2026-03-20T00:39:50Z", - "message": "Split features into codex-features crate", - "sha": "dc4350ff7d118af85cb2eff28447ce9c440f0610", - "url": "https://github.com/openai/codex/commit/dc4350ff7d118af85cb2eff28447ce9c440f0610" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "API", - "FEATURES", - "JSONRPCR", - "V2U", - "PERSONALITY_MIGRATION_FILENAME", - "CLI", - "SUBAGENT_NOTIFICATION_OPEN_TAG", - "CONFIG_TOML_FILE", - "GUARDIAN_REVIEWER_NAME", - "LEGACY_OLLAMA_CHAT_PROVIDER_ID", - "CODEX_APPS_MCP_SERVER_NAME", - "CONNECTORS_CACHE_TTL", - "CONNECTORS_READY_TIMEOUT_ON_EMPTY_TOOLS", - "GUARDIAN_REVIEW_TIMEOUT", - "SKILL_MCP_DEPENDENCY_PROMPT_ID", - "MCP_DEPENDENCY_OPTION_INSTALL", - "BASE_INSTRUCTIONS", - "TOOL_SUGGEST_DISCOVERABLE_PLUGIN_ALLOWLIST", - "PUBLIC_TOOL_NAME", - "ARTIFACTS_TOOL_NAME", - "ARTIFACT_TOOL_PRAGMA_PREFIX", - "JS_REPL_PRAGMA_PREFIX", - "DEFAULT_AGENT_MAX_DEPTH", - "TEST_ARG0_PATH_ENTRY", - "INSTA_WORKSPACE_ROOT", - "SSE", - "JSON", - "X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER", - "STANDARD", - "BASE64_STANDARD", - "SUMMARIZATION_PROMPT", - "SUMMARY_PREFIX", - "APPLY_PATCH_TOOL_INSTRUCTIONS", - "BUILD", - "ALIASES", - "USER_AGENT_SUFFIX", - "HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG", - "HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG", - "DEFAULT_PROJECT_DOC_FILENAME" - ], - "files": [ - { - "additions": 24, - "deletions": 0, - "patch_excerpt": "@@ -409,6 +409,7 @@ dependencies = [\n \"chrono\",\n \"codex-app-server-protocol\",\n \"codex-core\",\n+ \"codex-features\",\n \"codex-protocol\",\n \"codex-utils-cargo-bin\",\n \"core_test_support\",\n@@ -1428,6 +1429,7 @@ dependencies = [\n \"codex-cloud-requirements\",\n \"codex-core\",\n \"codex-exec-server\",\n+ \"codex-features\",\n \"codex-feedback\",\n \"codex-file-search\",\n \"codex-login\",\n@@ -1474,6 +1476,7 @@ dependencies = [\n \"codex-app-server-protocol\",\n \"codex-arg0\",\n \"codex-core\",\n+ \"codex-features\",\n \"codex-feedback\",\n \"codex-protocol\",\n \"futures\",\n@@ -1657,6 +1660,7 @@ dependencies = [\n \"codex-core\",\n \"codex-exec\",\n \"codex-execpolicy\",\n+ \"codex-features\",\n \"codex-login\",\n \"codex-mcp-server\",\n \"codex-protocol\",\n@@ -1845,6 +1849,7 @@ dependencies = [\n \"codex-connectors\",\n \"codex-exec-server\",\n \"codex-execpolicy\",\n+ \"codex-features\",\n \"codex-file-search\",\n \"codex-git\",\n \"codex-hooks\"...", - "path": "codex-rs/Cargo.lock", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -11,6 +11,7 @@ members = [\n \"apply-patch\",\n \"arg0\",\n \"feedback\",\n+ \"features\",\n \"codex-backend-openapi-models\",\n \"cloud-requirements\",\n \"cloud-tasks\",\n@@ -110,6 +111,7 @@ codex-exec-server = { path = \"exec-server\" }\n codex-execpolicy = { path = \"execpolicy\" }\n codex-experimental-api-macros = { path = \"codex-experimental-api-macros\" }\n codex-feedback = { path = \"feedback\" }\n+codex-features = { path = \"features\" }\n codex-file-search = { path = \"file-search\" }\n codex-git = { path = \"utils/git\" }\n codex-hooks = { path = \"hooks\" }", - "path": "codex-rs/Cargo.toml", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -16,6 +16,7 @@ codex-app-server = { workspace = true }\n codex-app-server-protocol = { workspace = true }\n codex-arg0 = { workspace = true }\n codex-core = { workspace = true }\n+codex-features = { workspace = true }\n codex-feedback = { workspace = true }\n codex-protocol = { workspace = true }\n futures = { workspace = true }", - "path": "codex-rs/app-server-client/Cargo.toml", - "status": "modified" - }, - { - "additions": 3, - "deletions": 2, - "patch_excerpt": "@@ -47,6 +47,7 @@ use codex_core::config::Config;\n use codex_core::config_loader::CloudRequirementsLoader;\n use codex_core::config_loader::LoaderOverrides;\n use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;\n+use codex_features::Feature;\n use codex_feedback::CodexFeedback;\n use codex_protocol::protocol::SessionSource;\n use serde::de::DeserializeOwned;\n@@ -215,7 +216,7 @@ impl InProcessClientStartArgs {\n default_mode_request_user_input: self\n .config\n .features\n- .enabled(codex_core::features::Feature::DefaultModeRequestUserInput),\n+ .enabled(Feature::DefaultModeRequestUserInput),\n },\n ));\n \n@@ -1484,7 +1485,7 @@ mod tests {\n CollaborationModesConfig {\n default_mode_request_user_input: config\n ....", - "path": "codex-rs/app-server-client/src/lib.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -33,6 +33,7 @@ codex-arg0 = { workspace = true }\n codex-cloud-requirements = { workspace = true }\n codex-core = { workspace = true }\n codex-exec-server = { workspace = true }\n+codex-features = { workspace = true }\n codex-otel = { workspace = true }\n codex-shell-command = { workspace = true }\n codex-utils-cli = { workspace = true }", - "path": "codex-rs/app-server/Cargo.toml", - "status": "modified" - }, - { - "additions": 3, - "deletions": 3, - "patch_excerpt": "@@ -206,9 +206,6 @@ use codex_core::error::Result as CodexResult;\n use codex_core::exec::ExecExpiration;\n use codex_core::exec::ExecParams;\n use codex_core::exec_env::create_env;\n-use codex_core::features::FEATURES;\n-use codex_core::features::Feature;\n-use codex_core::features::Stage;\n use codex_core::find_archived_thread_path_by_id_str;\n use codex_core::find_thread_name_by_id;\n use codex_core::find_thread_names_by_ids;\n@@ -238,6 +235,9 @@ use codex_core::state_db::reconcile_rollout;\n use codex_core::windows_sandbox::WindowsSandboxLevelExt;\n use codex_core::windows_sandbox::WindowsSandboxSetupMode as CoreWindowsSandboxSetupMode;\n use codex_core::windows_sandbox::WindowsSandboxSetupRequest;\n+use codex_features::FEATURES;\n+use codex_features::Feature;\n+use codex_features::Stage;\n use codex_feedback::CodexFeedback;\n use codex_login::ServerOptions as LoginServerOptions;\n use codex_login::Shu...", - "path": "codex-rs/app-server/src/codex_message_processor.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 1, - "patch_excerpt": "@@ -59,6 +59,7 @@ use codex_core::default_client::get_codex_user_agent;\n use codex_core::default_client::set_default_client_residency_requirement;\n use codex_core::default_client::set_default_originator;\n use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;\n+use codex_features::Feature;\n use codex_feedback::CodexFeedback;\n use codex_login::auth::ExternalAuthRefreshContext;\n use codex_login::auth::ExternalAuthRefreshReason;\n@@ -212,7 +213,7 @@ impl MessageProcessor {\n CollaborationModesConfig {\n default_mode_request_user_input: config\n .features\n- .enabled(codex_core::features::Feature::DefaultModeRequestUserInput),\n+ .enabled(Feature::DefaultModeRequestUserInput),\n },\n ));\n (auth_...", - "path": "codex-rs/app-server/src/message_processor.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -13,6 +13,7 @@ base64 = { workspace = true }\n chrono = { workspace = true }\n codex-app-server-protocol = { workspace = true }\n codex-core = { workspace = true }\n+codex-features = { workspace = true }\n codex-protocol = { workspace = true }\n codex-utils-cargo-bin = { workspace = true }\n serde = { workspace = true }", - "path": "codex-rs/app-server/tests/common/Cargo.toml", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -1,5 +1,5 @@\n-use codex_core::features::FEATURES;\n-use codex_core::features::Feature;\n+use codex_features::FEATURES;\n+use codex_features::Feature;\n use std::collections::BTreeMap;\n use std::path::Path;", - "path": "codex-rs/app-server/tests/common/config.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -10,8 +10,8 @@ use codex_app_server_protocol::ExperimentalFeatureStage;\n use codex_app_server_protocol::JSONRPCResponse;\n use codex_app_server_protocol::RequestId;\n use codex_core::config::ConfigBuilder;\n-use codex_core::features::FEATURES;\n-use codex_core::features::Stage;\n+use codex_features::FEATURES;\n+use codex_features::Stage;\n use pretty_assertions::assert_eq;\n use tempfile::TempDir;\n use tokio::time::timeout;", - "path": "codex-rs/app-server/tests/suite/v2/experimental_feature_list.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -18,8 +18,8 @@ use codex_app_server_protocol::TurnStartParams;\n use codex_app_server_protocol::TurnStartResponse;\n use codex_app_server_protocol::TurnStatus;\n use codex_app_server_protocol::UserInput as V2UserInput;\n-use codex_core::features::FEATURES;\n-use codex_core::features::Feature;\n+use codex_features::FEATURES;\n+use codex_features::Feature;\n use codex_protocol::config_types::CollaborationMode;\n use codex_protocol::config_types::ModeKind;\n use codex_protocol::config_types::Settings;", - "path": "codex-rs/app-server/tests/suite/v2/plan_item.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -23,8 +23,8 @@ use codex_app_server_protocol::ThreadRealtimeStopParams;\n use codex_app_server_protocol::ThreadRealtimeStopResponse;\n use codex_app_server_protocol::ThreadStartParams;\n use codex_app_server_protocol::ThreadStartResponse;\n-use codex_core::features::FEATURES;\n-use codex_core::features::Feature;\n+use codex_features::FEATURES;\n+use codex_features::Feature;\n use codex_protocol::protocol::RealtimeConversationVersion;\n use core_test_support::responses::start_websocket_server;\n use core_test_support::skip_if_no_network;", - "path": "codex-rs/app-server/tests/suite/v2/realtime_conversation.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -26,8 +26,8 @@ use codex_app_server_protocol::TurnCompletedNotification;\n use codex_app_server_protocol::TurnStartParams;\n use codex_app_server_protocol::TurnStartResponse;\n use codex_app_server_protocol::UserInput as V2UserInput;\n-use codex_core::features::FEATURES;\n-use codex_core::features::Feature;\n+use codex_features::FEATURES;\n+use codex_features::Feature;\n use pretty_assertions::assert_eq;\n use std::collections::BTreeMap;\n use std::path::Path;", - "path": "codex-rs/app-server/tests/suite/v2/thread_shell_command.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -44,9 +44,9 @@ use codex_app_server_protocol::TurnStartedNotification;\n use codex_app_server_protocol::TurnStatus;\n use codex_app_server_protocol::UserInput as V2UserInput;\n use codex_core::config::ConfigToml;\n-use codex_core::features::FEATURES;\n-use codex_core::features::Feature;\n use codex_core::personality_migration::PERSONALITY_MIGRATION_FILENAME;\n+use codex_features::FEATURES;\n+use codex_features::Feature;\n use codex_protocol::config_types::CollaborationMode;\n use codex_protocol::config_types::ModeKind;\n use codex_protocol::config_types::Personality;", - "path": "codex-rs/app-server/tests/suite/v2/turn_start.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -30,8 +30,8 @@ use codex_app_server_protocol::TurnStartParams;\n use codex_app_server_protocol::TurnStartResponse;\n use codex_app_server_protocol::TurnStatus;\n use codex_app_server_protocol::UserInput as V2UserInput;\n-use codex_core::features::FEATURES;\n-use codex_core::features::Feature;\n+use codex_features::FEATURES;\n+use codex_features::Feature;\n use core_test_support::responses;\n use core_test_support::skip_if_no_network;\n use pretty_assertions::assert_eq;", - "path": "codex-rs/app-server/tests/suite/v2/turn_start_zsh_fork.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -30,6 +30,7 @@ codex-config = { workspace = true }\n codex-core = { workspace = true }\n codex-exec = { workspace = true }\n codex-execpolicy = { workspace = true }\n+codex-features = { workspace = true }\n codex-login = { workspace = true }\n codex-mcp-server = { workspace = true }\n codex-protocol = { workspace = true }", - "path": "codex-rs/cli/Cargo.toml", - "status": "modified" - }, - { - "additions": 7, - "deletions": 10, - "patch_excerpt": "@@ -48,8 +48,9 @@ use codex_core::config::Config;\n use codex_core::config::ConfigOverrides;\n use codex_core::config::edit::ConfigEditsBuilder;\n use codex_core::config::find_codex_home;\n-use codex_core::features::Stage;\n-use codex_core::features::is_known_feature_key;\n+use codex_features::FEATURES;\n+use codex_features::Stage;\n+use codex_features::is_known_feature_key;\n use codex_terminal_detection::TerminalName;\n \n /// Codex CLI\n@@ -569,8 +570,7 @@ struct FeatureSetArgs {\n feature: String,\n }\n \n-fn stage_str(stage: codex_core::features::Stage) -> &'static str {\n- use codex_core::features::Stage;\n+fn stage_str(stage: Stage) -> &'static str {\n match stage {\n Stage::UnderDevelopment => \"under development\",\n Stage::Experimental { .. } => \"experimental\",\n@@ -886,10 +886,10 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {\n ...", - "path": "codex-rs/cli/src/main.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -34,6 +34,7 @@ codex-async-utils = { workspace = true }\n codex-connectors = { workspace = true }\n codex-config = { workspace = true }\n codex-exec-server = { workspace = true }\n+codex-features = { workspace = true }\n codex-login = { workspace = true }\n codex-shell-command = { workspace = true }\n codex-skills = { workspace = true }", - "path": "codex-rs/core/Cargo.toml", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -6,7 +6,6 @@ use crate::agent::status::is_final;\n use crate::codex_thread::ThreadConfigSnapshot;\n use crate::error::CodexErr;\n use crate::error::Result as CodexResult;\n-use crate::features::Feature;\n use crate::find_archived_thread_path_by_id_str;\n use crate::find_thread_path_by_id_str;\n use crate::rollout::RolloutRecorder;\n@@ -15,6 +14,7 @@ use crate::session_prefix::format_subagent_notification_message;\n use crate::shell_snapshot::ShellSnapshot;\n use crate::state_db;\n use crate::thread_manager::ThreadManagerState;\n+use codex_features::Feature;\n use codex_protocol::ThreadId;\n use codex_protocol::models::FunctionCallOutputPayload;\n use codex_protocol::models::ResponseItem;", - "path": "codex-rs/core/src/agent/control.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -8,9 +8,9 @@ use crate::config::Config;\n use crate::config::ConfigBuilder;\n use crate::config_loader::LoaderOverrides;\n use crate::contextual_user_message::SUBAGENT_NOTIFICATION_OPEN_TAG;\n-use crate::features::Feature;\n use assert_matches::assert_matches;\n use chrono::Utc;\n+use codex_features::Feature;\n use codex_protocol::config_types::ModeKind;\n use codex_protocol::models::ContentItem;\n use codex_protocol::models::ResponseItem;", - "path": "codex-rs/core/src/agent/control_tests.rs", - "status": "modified" - }, - { - "additions": 19, - "deletions": 6, - "patch_excerpt": "@@ -27,9 +27,6 @@ use crate::compact_remote::run_inline_remote_auto_compact_task;\n use crate::config::ManagedFeatures;\n use crate::connectors;\n use crate::exec_policy::ExecPolicyManager;\n-use crate::features::FEATURES;\n-use crate::features::Feature;\n-use crate::features::maybe_push_unstable_features_warning;\n #[cfg(test)]\n use crate::models_manager::collaboration_mode_presets::CollaborationModesConfig;\n use crate::models_manager::manager::ModelsManager;\n@@ -59,6 +56,9 @@ use chrono::Utc;\n use codex_app_server_protocol::McpServerElicitationRequest;\n use codex_app_server_protocol::McpServerElicitationRequestParams;\n use codex_exec_server::Environment;\n+use codex_features::FEATURES;\n+use codex_features::Feature;\n+use codex_features::unstable_features_warning_event;\n use codex_hooks::HookEvent;\n use codex_hooks::HookEventAfterAgent;\n use codex_hooks::HookPayload;\n@@ -140,6 +140,7 @@ use toki...", - "path": "codex-rs/core/src/codex.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 1, - "patch_excerpt": "@@ -14,6 +14,7 @@ use crate::models_manager::model_info;\n use crate::shell::default_user_shell;\n use crate::tools::format_exec_output_str;\n \n+use codex_features::Features;\n use codex_protocol::ThreadId;\n use codex_protocol::models::FunctionCallOutputBody;\n use codex_protocol::models::FunctionCallOutputPayload;\n@@ -3408,7 +3409,7 @@ async fn refresh_mcp_servers_is_deferred_until_next_turn() {\n #[tokio::test]\n async fn record_model_warning_appends_user_message() {\n let (mut session, turn_context) = make_session_and_context().await;\n- let features = crate::features::Features::with_defaults().into();\n+ let features = Features::with_defaults().into();\n session.features = features;\n \n session", - "path": "codex-rs/core/src/codex_tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -5,7 +5,6 @@ use crate::config_loader::ConfigRequirements;\n use crate::config_loader::ConfigRequirementsToml;\n use crate::exec::ExecParams;\n use crate::exec_policy::ExecPolicyManager;\n-use crate::features::Feature;\n use crate::guardian::GUARDIAN_REVIEWER_NAME;\n use crate::protocol::AskForApproval;\n use crate::sandboxing::SandboxPermissions;\n@@ -15,6 +14,7 @@ use codex_app_server_protocol::ConfigLayerSource;\n use codex_execpolicy::Decision;\n use codex_execpolicy::Evaluation;\n use codex_execpolicy::RuleMatch;\n+use codex_features::Feature;\n use codex_protocol::models::ContentItem;\n use codex_protocol::models::NetworkPermissions;\n use codex_protocol::models::PermissionProfile;", - "path": "codex-rs/core/src/codex_tests_guardian.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -4,11 +4,11 @@ use crate::codex::SteerInputError;\n use crate::config::ConstraintResult;\n use crate::error::CodexErr;\n use crate::error::Result as CodexResult;\n-use crate::features::Feature;\n use crate::file_watcher::WatchRegistration;\n use crate::protocol::Event;\n use crate::protocol::Op;\n use crate::protocol::Submission;\n+use codex_features::Feature;\n use codex_protocol::config_types::ApprovalsReviewer;\n use codex_protocol::config_types::Personality;\n use codex_protocol::config_types::ServiceTier;", - "path": "codex-rs/core/src/codex_thread.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 3, - "patch_excerpt": "@@ -13,9 +13,10 @@ use crate::config::types::NotificationMethod;\n use crate::config::types::Notifications;\n use crate::config::types::ToolSuggestDiscoverableType;\n use crate::config_loader::RequirementSource;\n-use crate::features::Feature;\n use assert_matches::assert_matches;\n use codex_config::CONFIG_TOML_FILE;\n+use codex_features::Feature;\n+use codex_features::FeaturesToml;\n use codex_protocol::permissions::FileSystemAccessMode;\n use codex_protocol::permissions::FileSystemPath;\n use codex_protocol::permissions::FileSystemSandboxEntry;\n@@ -1662,7 +1663,7 @@ fn feature_table_overrides_legacy_flags() -> std::io::Result<()> {\n let mut entries = BTreeMap::new();\n entries.insert(\"apply_patch_freeform\".to_string(), false);\n let cfg = ConfigToml {\n- features: Some(crate::features::FeaturesToml { entries }),\n+ features: Some(FeaturesToml { entries }),\n ..Defaul...", - "path": "codex-rs/core/src/config/config_tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,10 +1,10 @@\n use crate::config::types::McpServerConfig;\n use crate::config::types::Notice;\n-use crate::features::FEATURES;\n use crate::path_utils::resolve_symlink_write_paths;\n use crate::path_utils::write_atomically;\n use anyhow::Context;\n use codex_config::CONFIG_TOML_FILE;\n+use codex_features::FEATURES;\n use codex_protocol::config_types::Personality;\n use codex_protocol::config_types::ServiceTier;\n use codex_protocol::config_types::TrustLevel;", - "path": "codex-rs/core/src/config/edit.rs", - "status": "modified" - }, - { - "additions": 22, - "deletions": 6, - "patch_excerpt": "@@ -10,11 +10,12 @@ use codex_config::Sourced;\n \n use crate::config::ConfigToml;\n use crate::config::profile::ConfigProfile;\n-use crate::features::Feature;\n-use crate::features::FeatureOverrides;\n-use crate::features::Features;\n-use crate::features::canonical_feature_for_key;\n-use crate::features::feature_for_key;\n+use codex_features::Feature;\n+use codex_features::FeatureConfigSource;\n+use codex_features::FeatureOverrides;\n+use codex_features::Features;\n+use codex_features::canonical_feature_for_key;\n+use codex_features::feature_for_key;\n \n /// Wrapper around [`Features`] which enforces constraints defined in\n /// `FeatureRequirementsToml` and provides normalization to ensure constraints\n@@ -304,7 +305,22 @@ pub(crate) fn validate_feature_requirements_in_config_toml(\n profile: &ConfigProfile,\n feature_requirements: Option<&Sourced>,\n ) -> std:...", - "path": "codex-rs/core/src/config/managed_features.rs", - "status": "modified" - }, - { - "additions": 22, - "deletions": 5, - "patch_excerpt": "@@ -39,10 +39,6 @@ use crate::config_loader::McpServerRequirement;\n use crate::config_loader::ResidencyRequirement;\n use crate::config_loader::Sourced;\n use crate::config_loader::load_config_layers_state;\n-use crate::features::Feature;\n-use crate::features::FeatureOverrides;\n-use crate::features::Features;\n-use crate::features::FeaturesToml;\n use crate::git_info::resolve_root_git_project_for_trust;\n use crate::memories::memory_root;\n use crate::model_provider_info::LEGACY_OLLAMA_CHAT_PROVIDER_ID;\n@@ -65,6 +61,11 @@ use crate::windows_sandbox::resolve_windows_sandbox_mode;\n use crate::windows_sandbox::resolve_windows_sandbox_private_desktop;\n use codex_app_server_protocol::Tools;\n use codex_app_server_protocol::UserSavedConfig;\n+use codex_features::Feature;\n+use codex_features::FeatureConfigSource;\n+use codex_features::FeatureOverrides;\n+use codex_features::Features;\n+use codex_features::...", - "path": "codex-rs/core/src/config/mod.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 1, - "patch_excerpt": "@@ -8,6 +8,7 @@ use crate::config::types::ApprovalsReviewer;\n use crate::config::types::Personality;\n use crate::config::types::WindowsToml;\n use crate::protocol::AskForApproval;\n+use codex_features::FeaturesToml;\n use codex_protocol::config_types::ReasoningSummary;\n use codex_protocol::config_types::SandboxMode;\n use codex_protocol::config_types::ServiceTier;\n@@ -60,7 +61,7 @@ pub struct ConfigProfile {\n #[serde(default)]\n // Injects known feature keys into the schema and forbids unknown keys.\n #[schemars(schema_with = \"crate::config::schema::features_schema\")]\n- pub features: Option,\n+ pub features: Option,\n pub oss_provider: Option,\n }", - "path": "codex-rs/core/src/config/profile.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 2, - "patch_excerpt": "@@ -1,6 +1,7 @@\n use crate::config::ConfigToml;\n use crate::config::types::RawMcpServerConfig;\n-use crate::features::FEATURES;\n+use codex_features::FEATURES;\n+use codex_features::legacy_feature_keys;\n use schemars::r#gen::SchemaGenerator;\n use schemars::r#gen::SchemaSettings;\n use schemars::schema::InstanceType;\n@@ -25,7 +26,7 @@ pub(crate) fn features_schema(schema_gen: &mut SchemaGenerator) -> Schema {\n .properties\n .insert(feature.key.to_string(), schema_gen.subschema_for::());\n }\n- for legacy_key in crate::features::legacy_feature_keys() {\n+ for legacy_key in legacy_feature_keys() {\n validation\n .properties\n .insert(legacy_key.to_string(), schema_gen.subschema_for::());", - "path": "codex-rs/core/src/config/schema.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -33,7 +33,6 @@ use crate::config_loader::AppsRequirementsToml;\n use crate::default_client::create_client;\n use crate::default_client::is_first_party_chat_originator;\n use crate::default_client::originator;\n-use crate::features::Feature;\n use crate::mcp::CODEX_APPS_MCP_SERVER_NAME;\n use crate::mcp::McpManager;\n use crate::mcp::ToolPluginProvenance;\n@@ -47,6 +46,7 @@ use crate::plugins::list_tool_suggest_discoverable_plugins;\n use crate::token_data::TokenData;\n use crate::tools::discoverable::DiscoverablePluginInfo;\n use crate::tools::discoverable::DiscoverableTool;\n+use codex_features::Feature;\n \n pub use codex_connectors::CONNECTORS_CACHE_TTL;\n const CONNECTORS_READY_TIMEOUT_ON_EMPTY_TOOLS: Duration = Duration::from_secs(30);", - "path": "codex-rs/core/src/connectors.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -11,9 +11,9 @@ use crate::config_loader::CloudRequirementsLoader;\n use crate::config_loader::ConfigLayerStack;\n use crate::config_loader::ConfigRequirements;\n use crate::config_loader::ConfigRequirementsToml;\n-use crate::features::Feature;\n use crate::mcp::CODEX_APPS_MCP_SERVER_NAME;\n use crate::mcp_connection_manager::ToolInfo;\n+use codex_features::Feature;\n use codex_utils_absolute_path::AbsolutePathBuf;\n use pretty_assertions::assert_eq;\n use rmcp::model::JsonObject;", - "path": "codex-rs/core/src/connectors_tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,9 +1,9 @@\n use crate::codex::PreviousTurnSettings;\n use crate::codex::TurnContext;\n use crate::environment_context::EnvironmentContext;\n-use crate::features::Feature;\n use crate::shell::Shell;\n use codex_execpolicy::Policy;\n+use codex_features::Feature;\n use codex_protocol::config_types::Personality;\n use codex_protocol::models::ContentItem;\n use codex_protocol::models::DeveloperInstructions;", - "path": "codex-rs/core/src/context_manager/updates.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -30,10 +30,10 @@ use crate::config::ManagedFeatures;\n use crate::config::NetworkProxySpec;\n use crate::config::Permissions;\n use crate::config::types::McpServerConfig;\n-use crate::features::Feature;\n use crate::model_provider_info::ModelProviderInfo;\n use crate::protocol::SandboxPolicy;\n use crate::rollout::recorder::RolloutRecorder;\n+use codex_features::Feature;\n \n use super::GUARDIAN_REVIEW_TIMEOUT;\n use super::GUARDIAN_REVIEWER_NAME;", - "path": "codex-rs/core/src/guardian/review_session.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 1, - "patch_excerpt": "@@ -39,7 +39,6 @@ pub mod exec;\n pub mod exec_env;\n mod exec_policy;\n pub mod external_agent_config;\n-pub mod features;\n mod file_watcher;\n mod flags;\n pub mod git_info;", - "path": "codex-rs/core/src/lib.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,9 +1,9 @@\n use super::*;\n use crate::config::CONFIG_TOML_FILE;\n use crate::config::ConfigBuilder;\n-use crate::features::Feature;\n use crate::plugins::AppConnectorId;\n use crate::plugins::PluginCapabilitySummary;\n+use codex_features::Feature;\n use pretty_assertions::assert_eq;\n use std::fs;\n use std::path::Path;", - "path": "codex-rs/core/src/mcp/mod_tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -24,9 +24,9 @@ use crate::config::types::McpServerConfig;\n use crate::config::types::McpServerTransportConfig;\n use crate::default_client::is_first_party_originator;\n use crate::default_client::originator;\n-use crate::features::Feature;\n use crate::skills::SkillMetadata;\n use crate::skills::model::SkillToolDependency;\n+use codex_features::Feature;\n \n const SKILL_MCP_DEPENDENCY_PROMPT_ID: &str = \"skill_mcp_dependency_install\";\n const MCP_DEPENDENCY_OPTION_INSTALL: &str = \"Install\";", - "path": "codex-rs/core/src/mcp/skill_dependencies.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -19,7 +19,6 @@ use crate::config::edit::ConfigEdit;\n use crate::config::edit::ConfigEditsBuilder;\n use crate::config::types::AppToolApproval;\n use crate::connectors;\n-use crate::features::Feature;\n use crate::guardian::GuardianApprovalRequest;\n use crate::guardian::GuardianMcpAnnotations;\n use crate::guardian::guardian_approval_request_to_json;\n@@ -33,6 +32,7 @@ use crate::protocol::McpInvocation;\n use crate::protocol::McpToolCallBeginEvent;\n use crate::protocol::McpToolCallEndEvent;\n use crate::state_db;\n+use codex_features::Feature;\n use codex_protocol::mcp::CallToolResult;\n use codex_protocol::openai_models::InputModality;\n use codex_protocol::protocol::AskForApproval;", - "path": "codex-rs/core/src/mcp_tool_call.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -2,7 +2,6 @@ use crate::agent::AgentStatus;\n use crate::agent::status::is_final as is_final_agent_status;\n use crate::codex::Session;\n use crate::config::Config;\n-use crate::features::Feature;\n use crate::memories::memory_root;\n use crate::memories::metrics;\n use crate::memories::phase_two;\n@@ -11,6 +10,7 @@ use crate::memories::storage::rebuild_raw_memories_file_from_memories;\n use crate::memories::storage::rollout_summary_file_stem;\n use crate::memories::storage::sync_rollout_summaries_from_memories;\n use codex_config::Constrained;\n+use codex_features::Feature;\n use codex_protocol::ThreadId;\n use codex_protocol::protocol::AskForApproval;\n use codex_protocol::protocol::SandboxPolicy;", - "path": "codex-rs/core/src/memories/phase2.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,8 +1,8 @@\n use crate::codex::Session;\n use crate::config::Config;\n-use crate::features::Feature;\n use crate::memories::phase1;\n use crate::memories::phase2;\n+use codex_features::Feature;\n use codex_protocol::protocol::SessionSource;\n use std::sync::Arc;\n use tracing::warn;", - "path": "codex-rs/core/src/memories/start.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -10,8 +10,8 @@ use codex_protocol::openai_models::WebSearchToolType;\n use codex_protocol::openai_models::default_input_modalities;\n \n use crate::config::Config;\n-use crate::features::Feature;\n use crate::truncate::approx_bytes_for_tokens;\n+use codex_features::Feature;\n use tracing::warn;\n \n pub const BASE_INSTRUCTIONS: &str = include_str!(\"../../prompt.md\");", - "path": "codex-rs/core/src/models_manager/model_info.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -1,5 +1,5 @@\n-use crate::features::Feature;\n-use crate::features::Features;\n+use codex_features::Feature;\n+use codex_features::Features;\n use codex_protocol::models::ImageDetail;\n use codex_protocol::openai_models::ModelInfo;", - "path": "codex-rs/core/src/original_image_detail.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,8 +1,8 @@\n use super::*;\n \n use crate::config::test_config;\n-use crate::features::Features;\n use crate::models_manager::manager::ModelsManager;\n+use codex_features::Features;\n use pretty_assertions::assert_eq;\n \n #[test]", - "path": "codex-rs/core/src/original_image_detail_tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -2,7 +2,7 @@ use crate::config::Config;\n use crate::config::types::OtelExporterKind as Kind;\n use crate::config::types::OtelHttpProtocol as Protocol;\n use crate::default_client::originator;\n-use crate::features::Feature;\n+use codex_features::Feature;\n use codex_otel::OtelProvider;\n use codex_otel::config::OtelExporter;\n use codex_otel::config::OtelHttpProtocol;", - "path": "codex-rs/core/src/otel_init.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -8,7 +8,7 @@ use super::PluginReadRequest;\n use super::PluginsManager;\n use crate::config::Config;\n use crate::config::types::ToolSuggestDiscoverableType;\n-use crate::features::Feature;\n+use codex_features::Feature;\n \n const TOOL_SUGGEST_DISCOVERABLE_PLUGIN_ALLOWLIST: &[&str] = &[\n \"github@openai-curated\",", - "path": "codex-rs/core/src/plugins/discoverable.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -36,12 +36,12 @@ use crate::config::edit::ConfigEditsBuilder;\n use crate::config::types::McpServerConfig;\n use crate::config::types::PluginConfig;\n use crate::config_loader::ConfigLayerStack;\n-use crate::features::Feature;\n use crate::skills::SkillMetadata;\n use crate::skills::loader::SkillRoot;\n use crate::skills::loader::load_skills_from_roots;\n use codex_app_server_protocol::ConfigValueWriteParams;\n use codex_app_server_protocol::MergeStrategy;\n+use codex_features::Feature;\n use codex_protocol::protocol::Product;\n use codex_protocol::protocol::SkillScope;\n use codex_utils_absolute_path::AbsolutePathBuf;", - "path": "codex-rs/core/src/plugins/manager.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -20,8 +20,8 @@ use crate::config_loader::ConfigLayerStackOrdering;\n use crate::config_loader::default_project_root_markers;\n use crate::config_loader::merge_toml_values;\n use crate::config_loader::project_root_markers_from_config;\n-use crate::features::Feature;\n use codex_app_server_protocol::ConfigLayerSource;\n+use codex_features::Feature;\n use dunce::canonicalize as normalize_path;\n use std::path::PathBuf;\n use tokio::io::AsyncReadExt;", - "path": "codex-rs/core/src/project_doc.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,6 +1,6 @@\n use super::*;\n use crate::config::ConfigBuilder;\n-use crate::features::Feature;\n+use codex_features::Feature;\n use std::fs;\n use std::path::PathBuf;\n use tempfile::TempDir;", - "path": "codex-rs/core/src/project_doc_tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,7 +1,7 @@\n use super::*;\n use crate::config::ConfigBuilder;\n-use crate::features::Feature;\n use chrono::TimeZone;\n+use codex_features::Feature;\n use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;\n use codex_protocol::protocol::AgentMessageEvent;\n use codex_protocol::protocol::AskForApproval;", - "path": "codex-rs/core/src/rollout/recorder_tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -47,7 +47,7 @@ use codex_protocol::models::ResponseItem;\n use codex_protocol::protocol::RolloutItem;\n use codex_protocol::user_input::UserInput;\n \n-use crate::features::Feature;\n+use codex_features::Feature;\n pub(crate) use compact::CompactTask;\n pub(crate) use ghost_snapshot::GhostSnapshotTask;\n pub(crate) use regular::RegularTask;", - "path": "codex-rs/core/src/tasks/mod.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -20,10 +20,10 @@ use crate::codex::Session;\n use crate::codex::TurnContext;\n use crate::codex_delegate::run_codex_thread_one_shot;\n use crate::config::Constrained;\n-use crate::features::Feature;\n use crate::review_format::format_review_findings_block;\n use crate::review_format::render_review_output_text;\n use crate::state::TaskKind;\n+use codex_features::Feature;\n use codex_protocol::user_input::UserInput;\n \n use super::SessionTask;", - "path": "codex-rs/core/src/tasks/review.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -8,11 +8,11 @@ use tracing::warn;\n \n use crate::codex::Session;\n use crate::codex::TurnContext;\n-use crate::features::Feature;\n use crate::tools::ToolRouter;\n use crate::tools::context::SharedTurnDiffTracker;\n use crate::tools::js_repl::resolve_compatible_node;\n use crate::tools::parallel::ToolCallRuntime;\n+use codex_features::Feature;\n \n use super::ExecContext;\n use super::PUBLIC_TOOL_NAME;", - "path": "codex-rs/core/src/tools/code_mode/service.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -13,7 +13,6 @@ use crate::codex::Session;\n use crate::codex::TurnContext;\n use crate::exec::ExecToolCallOutput;\n use crate::exec::StreamOutput;\n-use crate::features::Feature;\n use crate::function_tool::FunctionCallError;\n use crate::packages::versions;\n use crate::protocol::ExecCommandSource;\n@@ -26,6 +25,7 @@ use crate::tools::events::ToolEventFailure;\n use crate::tools::events::ToolEventStage;\n use crate::tools::registry::ToolHandler;\n use crate::tools::registry::ToolKind;\n+use codex_features::Feature;\n \n const ARTIFACTS_TOOL_NAME: &str = \"artifacts\";\n const ARTIFACT_TOOL_PRAGMA_PREFIX: &str = \"// codex-artifact-tool:\";", - "path": "codex-rs/core/src/tools/handlers/artifacts.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -6,7 +6,6 @@ use std::time::Instant;\n \n use crate::exec::ExecToolCallOutput;\n use crate::exec::StreamOutput;\n-use crate::features::Feature;\n use crate::function_tool::FunctionCallError;\n use crate::protocol::ExecCommandSource;\n use crate::tools::context::FunctionToolOutput;\n@@ -21,6 +20,7 @@ use crate::tools::js_repl::JS_REPL_PRAGMA_PREFIX;\n use crate::tools::js_repl::JsReplArgs;\n use crate::tools::registry::ToolHandler;\n use crate::tools::registry::ToolKind;\n+use codex_features::Feature;\n use codex_protocol::models::FunctionCallOutputContentItem;\n \n pub struct JsReplHandler;", - "path": "codex-rs/core/src/tools/handlers/js_repl.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -11,7 +11,6 @@ use crate::codex::Session;\n use crate::codex::TurnContext;\n use crate::config::Config;\n use crate::error::CodexErr;\n-use crate::features::Feature;\n use crate::function_tool::FunctionCallError;\n use crate::models_manager::manager::RefreshStrategy;\n use crate::tools::context::FunctionToolOutput;\n@@ -22,6 +21,7 @@ use crate::tools::handlers::parse_arguments;\n use crate::tools::registry::ToolHandler;\n use crate::tools::registry::ToolKind;\n use async_trait::async_trait;\n+use codex_features::Feature;\n use codex_protocol::ThreadId;\n use codex_protocol::models::BaseInstructions;\n use codex_protocol::models::ResponseInputItem;", - "path": "codex-rs/core/src/tools/handlers/multi_agents.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -6,7 +6,6 @@ use crate::built_in_model_providers;\n use crate::codex::make_session_and_context;\n use crate::config::DEFAULT_AGENT_MAX_DEPTH;\n use crate::config::types::ShellEnvironmentPolicy;\n-use crate::features::Feature;\n use crate::function_tool::FunctionCallError;\n use crate::protocol::AskForApproval;\n use crate::protocol::FileSystemSandboxPolicy;\n@@ -17,6 +16,7 @@ use crate::protocol::SessionSource;\n use crate::protocol::SubAgentSource;\n use crate::tools::context::ToolOutput;\n use crate::turn_diff_tracker::TurnDiffTracker;\n+use codex_features::Feature;\n use codex_protocol::ThreadId;\n use codex_protocol::models::ContentItem;\n use codex_protocol::models::FunctionCallOutputBody;", - "path": "codex-rs/core/src/tools/handlers/multi_agents_tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -8,7 +8,6 @@ use crate::codex::TurnContext;\n use crate::exec::ExecParams;\n use crate::exec_env::create_env;\n use crate::exec_policy::ExecApprovalRequest;\n-use crate::features::Feature;\n use crate::function_tool::FunctionCallError;\n use crate::is_safe_command::is_known_safe_command;\n use crate::protocol::ExecCommandSource;\n@@ -33,6 +32,7 @@ use crate::tools::runtimes::shell::ShellRuntime;\n use crate::tools::runtimes::shell::ShellRuntimeBackend;\n use crate::tools::sandboxing::ToolCtx;\n use crate::tools::spec::ShellCommandBackendConfig;\n+use codex_features::Feature;\n use codex_protocol::models::PermissionProfile;\n \n pub struct ShellHandler;", - "path": "codex-rs/core/src/tools/handlers/shell.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,4 +1,3 @@\n-use crate::features::Feature;\n use crate::function_tool::FunctionCallError;\n use crate::is_safe_command::is_known_safe_command;\n use crate::protocol::EventMsg;\n@@ -25,6 +24,7 @@ use crate::unified_exec::UnifiedExecContext;\n use crate::unified_exec::UnifiedExecProcessManager;\n use crate::unified_exec::WriteStdinRequest;\n use async_trait::async_trait;\n+use codex_features::Feature;\n use codex_protocol::models::PermissionProfile;\n use serde::Deserialize;\n use std::path::PathBuf;", - "path": "codex-rs/core/src/tools/handlers/unified_exec.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,11 +1,11 @@\n use super::*;\n use crate::codex::make_session_and_context;\n use crate::codex::make_session_and_context_with_dynamic_tools_and_rx;\n-use crate::features::Feature;\n use crate::protocol::AskForApproval;\n use crate::protocol::EventMsg;\n use crate::protocol::SandboxPolicy;\n use crate::turn_diff_tracker::TurnDiffTracker;\n+use codex_features::Feature;\n use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem;\n use codex_protocol::dynamic_tools::DynamicToolResponse;\n use codex_protocol::dynamic_tools::DynamicToolSpec;", - "path": "codex-rs/core/src/tools/js_repl/mod_tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -10,7 +10,6 @@ pub(crate) mod zsh_fork_backend;\n \n use crate::command_canonicalization::canonicalize_command_for_approval;\n use crate::exec::ExecToolCallOutput;\n-use crate::features::Feature;\n use crate::guardian::GuardianApprovalRequest;\n use crate::guardian::review_approval_request;\n use crate::guardian::routes_approval_to_guardian;\n@@ -34,6 +33,7 @@ use crate::tools::sandboxing::ToolError;\n use crate::tools::sandboxing::ToolRuntime;\n use crate::tools::sandboxing::sandbox_override_for_first_attempt;\n use crate::tools::sandboxing::with_cached_approval;\n+use codex_features::Feature;\n use codex_network_proxy::NetworkProxy;\n use codex_protocol::models::PermissionProfile;\n use codex_protocol::protocol::ReviewDecision;", - "path": "codex-rs/core/src/tools/runtimes/shell.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -5,7 +5,6 @@ use crate::exec::ExecExpiration;\n use crate::exec::ExecToolCallOutput;\n use crate::exec::SandboxType;\n use crate::exec::is_likely_sandbox_denied;\n-use crate::features::Feature;\n use crate::guardian::GuardianApprovalRequest;\n use crate::guardian::review_approval_request;\n use crate::guardian::routes_approval_to_guardian;\n@@ -24,6 +23,7 @@ use codex_execpolicy::Evaluation;\n use codex_execpolicy::MatchOptions;\n use codex_execpolicy::Policy;\n use codex_execpolicy::RuleMatch;\n+use codex_features::Feature;\n use codex_protocol::config_types::WindowsSandboxLevel;\n use codex_protocol::models::MacOsSeatbeltProfileExtensions;\n use codex_protocol::models::PermissionProfile;", - "path": "codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -8,7 +8,6 @@ use crate::command_canonicalization::canonicalize_command_for_approval;\n use crate::error::CodexErr;\n use crate::error::SandboxErr;\n use crate::exec::ExecExpiration;\n-use crate::features::Feature;\n use crate::guardian::GuardianApprovalRequest;\n use crate::guardian::review_approval_request;\n use crate::guardian::routes_approval_to_guardian;\n@@ -37,6 +36,7 @@ use crate::unified_exec::NoopSpawnLifecycle;\n use crate::unified_exec::UnifiedExecError;\n use crate::unified_exec::UnifiedExecProcess;\n use crate::unified_exec::UnifiedExecProcessManager;\n+use codex_features::Feature;\n use codex_network_proxy::NetworkProxy;\n use codex_protocol::models::PermissionProfile;\n use codex_protocol::protocol::ReviewDecision;", - "path": "codex-rs/core/src/tools/runtimes/unified_exec.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -3,8 +3,6 @@ use crate::client_common::tools::FreeformToolFormat;\n use crate::client_common::tools::ResponsesApiTool;\n use crate::client_common::tools::ToolSpec;\n use crate::config::AgentRoleConfig;\n-use crate::features::Feature;\n-use crate::features::Features;\n use crate::mcp::CODEX_APPS_MCP_SERVER_NAME;\n use crate::mcp_connection_manager::ToolInfo;\n use crate::models_manager::collaboration_mode_presets::CollaborationModesConfig;\n@@ -35,6 +33,8 @@ use crate::tools::handlers::request_permissions_tool_description;\n use crate::tools::handlers::request_user_input_tool_description;\n use crate::tools::registry::ToolRegistryBuilder;\n use crate::tools::registry::tool_handler_key;\n+use codex_features::Feature;\n+use codex_features::Features;\n use codex_protocol::config_types::WebSearchConfig;\n use codex_protocol::config_types::WebSearchMode;\n use codex_protocol::config_types::WindowsSandboxLev...", - "path": "codex-rs/core/src/tools/spec.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 3, - "patch_excerpt": "@@ -4,10 +4,10 @@ use crate::config::edit::ConfigEditsBuilder;\n use crate::config::profile::ConfigProfile;\n use crate::config::types::WindowsSandboxModeToml;\n use crate::default_client::originator;\n-use crate::features::Feature;\n-use crate::features::Features;\n-use crate::features::FeaturesToml;\n use crate::protocol::SandboxPolicy;\n+use codex_features::Feature;\n+use codex_features::Features;\n+use codex_features::FeaturesToml;\n use codex_otel::sanitize_metric_tag_value;\n use codex_protocol::config_types::WindowsSandboxLevel;\n use std::collections::BTreeMap;", - "path": "codex-rs/core/src/windows_sandbox.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -1,7 +1,7 @@\n use super::*;\n use crate::config::types::WindowsToml;\n-use crate::features::Features;\n-use crate::features::FeaturesToml;\n+use codex_features::Features;\n+use codex_features::FeaturesToml;\n use pretty_assertions::assert_eq;\n use std::collections::BTreeMap;", - "path": "codex-rs/core/src/windows_sandbox_tests.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -11,7 +11,9 @@ path = \"lib.rs\"\n anyhow = { workspace = true }\n assert_cmd = { workspace = true }\n base64 = { workspace = true }\n+codex-arg0 = { workspace = true }\n codex-core = { workspace = true }\n+codex-features = { workspace = true }\n codex-protocol = { workspace = true }\n codex-utils-absolute-path = { workspace = true }\n codex-utils-cargo-bin = { workspace = true }", - "path": "codex-rs/core/tests/common/Cargo.toml", - "status": "modified" - }, - { - "additions": 29, - "deletions": 4, - "patch_excerpt": "@@ -2,8 +2,10 @@\n \n use anyhow::Context as _;\n use anyhow::ensure;\n+use codex_arg0::Arg0PathEntryGuard;\n use codex_utils_cargo_bin::CargoBinError;\n use ctor::ctor;\n+use std::sync::OnceLock;\n use tempfile::TempDir;\n \n use codex_core::CodexThread;\n@@ -24,12 +26,19 @@ pub mod test_codex_exec;\n pub mod tracing;\n pub mod zsh_fork;\n \n+static TEST_ARG0_PATH_ENTRY: OnceLock> = OnceLock::new();\n+\n #[ctor]\n fn enable_deterministic_unified_exec_process_ids_for_tests() {\n codex_core::test_support::set_thread_manager_test_mode(/*enabled*/ true);\n codex_core::test_support::set_deterministic_process_ids(/*enabled*/ true);\n }\n \n+#[ctor]\n+fn configure_arg0_dispatch_for_test_binaries() {\n+ let _ = TEST_ARG0_PATH_ENTRY.get_or_init(codex_arg0::arg0_dispatch);\n+}\n+\n #[ctor]\n fn configure_insta_workspace_root_for_snapshot_tests() {\n if std::env::var_os(\"INSTA_WORKSPAC...", - "path": "codex-rs/core/tests/common/lib.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -11,10 +11,10 @@ use codex_core::ModelProviderInfo;\n use codex_core::ThreadManager;\n use codex_core::built_in_model_providers;\n use codex_core::config::Config;\n-use codex_core::features::Feature;\n use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;\n use codex_core::shell::Shell;\n use codex_core::shell::get_shell_by_model_provided_path;\n+use codex_features::Feature;\n use codex_protocol::config_types::ServiceTier;\n use codex_protocol::openai_models::ModelsResponse;\n use codex_protocol::protocol::AskForApproval;", - "path": "codex-rs/core/tests/common/test_codex.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -4,7 +4,7 @@ use std::path::PathBuf;\n use anyhow::Result;\n use codex_core::config::Config;\n use codex_core::config::Constrained;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::protocol::AskForApproval;\n use codex_protocol::protocol::SandboxPolicy;", - "path": "codex-rs/core/tests/common/zsh_fork.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,5 +1,5 @@\n use anyhow::Result;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use core_test_support::responses::ev_completed;\n use core_test_support::responses::ev_function_call;\n use core_test_support::responses::ev_response_created;", - "path": "codex-rs/core/tests/suite/agent_jobs.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,5 +1,5 @@\n use anyhow::Result;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::config_types::ServiceTier;\n use core_test_support::responses::WebSocketConnectionConfig;\n use core_test_support::responses::ev_assistant_message;", - "path": "codex-rs/core/tests/suite/agent_websocket.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -13,7 +13,7 @@ use std::fs;\n use std::sync::atomic::AtomicI32;\n use std::sync::atomic::Ordering;\n \n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::protocol::AskForApproval;\n use codex_protocol::protocol::EventMsg;\n use codex_protocol::protocol::Op;", - "path": "codex-rs/core/tests/suite/apply_patch_cli.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -9,8 +9,8 @@ use codex_core::config_loader::NetworkConstraints;\n use codex_core::config_loader::NetworkRequirementsToml;\n use codex_core::config_loader::RequirementSource;\n use codex_core::config_loader::Sourced;\n-use codex_core::features::Feature;\n use codex_core::sandboxing::SandboxPermissions;\n+use codex_features::Feature;\n use codex_protocol::approvals::NetworkApprovalProtocol;\n use codex_protocol::approvals::NetworkPolicyAmendment;\n use codex_protocol::approvals::NetworkPolicyRuleAction;", - "path": "codex-rs/core/tests/suite/approvals.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -10,8 +10,8 @@ use codex_core::auth::AuthCredentialsStoreMode;\n use codex_core::built_in_model_providers;\n use codex_core::default_client::originator;\n use codex_core::error::CodexErr;\n-use codex_core::features::Feature;\n use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;\n+use codex_features::Feature;\n use codex_otel::SessionTelemetry;\n use codex_otel::TelemetryAuthMode;\n use codex_protocol::ThreadId;", - "path": "codex-rs/core/tests/suite/client.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -9,7 +9,7 @@ use codex_core::Prompt;\n use codex_core::ResponseEvent;\n use codex_core::WireApi;\n use codex_core::X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_otel::SessionTelemetry;\n use codex_otel::TelemetryAuthMode;\n use codex_otel::current_span_w3c_trace_context;", - "path": "codex-rs/core/tests/suite/client_websockets.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -5,7 +5,7 @@ use base64::Engine;\n use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;\n use codex_core::config::types::McpServerConfig;\n use codex_core::config::types::McpServerTransportConfig;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem;\n use codex_protocol::dynamic_tools::DynamicToolResponse;\n use codex_protocol::dynamic_tools::DynamicToolSpec;", - "path": "codex-rs/core/tests/suite/code_mode.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 3, - "patch_excerpt": "@@ -5,6 +5,7 @@ use codex_core::built_in_model_providers;\n use codex_core::compact::SUMMARIZATION_PROMPT;\n use codex_core::compact::SUMMARY_PREFIX;\n use codex_core::config::Config;\n+use codex_features::Feature;\n use codex_protocol::items::TurnItem;\n use codex_protocol::openai_models::ModelInfo;\n use codex_protocol::openai_models::ModelsResponse;\n@@ -3115,9 +3116,7 @@ async fn snapshot_request_shape_pre_turn_compaction_strips_incoming_model_switch\n .with_config(move |config| {\n config.model_provider = model_provider;\n set_test_compact_prompt(config);\n- let _ = config\n- .features\n- .enable(codex_core::features::Feature::RemoteModels);\n+ let _ = config.features.enable(Feature::RemoteModels);\n config.model_auto_compact_token_limit = Some(200);\n })\n .build(&server)", - "path": "codex-rs/core/tests/suite/compact.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -6,7 +6,7 @@ use codex_core::config_loader::ConfigLayerEntry;\n use codex_core::config_loader::ConfigLayerStack;\n use codex_core::config_loader::ConfigRequirements;\n use codex_core::config_loader::ConfigRequirementsToml;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::protocol::DeprecationNoticeEvent;\n use codex_protocol::protocol::EventMsg;\n use core_test_support::responses::start_mock_server;", - "path": "codex-rs/core/tests/suite/deprecation_notice.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,7 +1,7 @@\n #![allow(clippy::unwrap_used, clippy::expect_used)]\n \n use anyhow::Result;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::config_types::CollaborationMode;\n use codex_protocol::config_types::ModeKind;\n use codex_protocol::config_types::Settings;", - "path": "codex-rs/core/tests/suite/exec_policy.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,4 +1,4 @@\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use core_test_support::responses::ev_completed;\n use core_test_support::responses::ev_response_created;\n use core_test_support::responses::mount_sse_once;", - "path": "codex-rs/core/tests/suite/hierarchical_agents.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -3,7 +3,7 @@ use std::path::Path;\n \n use anyhow::Context;\n use anyhow::Result;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::items::parse_hook_prompt_fragment;\n use codex_protocol::models::ContentItem;\n use codex_protocol::models::ResponseItem;", - "path": "codex-rs/core/tests/suite/hooks.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,7 +1,7 @@\n #![allow(clippy::expect_used, clippy::unwrap_used)]\n \n use anyhow::Result;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::protocol::EventMsg;\n use core_test_support::responses;\n use core_test_support::responses::ResponseMock;", - "path": "codex-rs/core/tests/suite/js_repl.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,7 +1,7 @@\n use anyhow::Result;\n use chrono::Duration as ChronoDuration;\n use chrono::Utc;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::ThreadId;\n use codex_protocol::protocol::EventMsg;\n use codex_protocol::protocol::Op;", - "path": "codex-rs/core/tests/suite/memories.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,8 +1,8 @@\n use anyhow::Result;\n use codex_core::CodexAuth;\n use codex_core::config::types::Personality;\n-use codex_core::features::Feature;\n use codex_core::models_manager::manager::RefreshStrategy;\n+use codex_features::Feature;\n use codex_protocol::config_types::ReasoningSummary;\n use codex_protocol::config_types::ServiceTier;\n use codex_protocol::openai_models::ConfigShellToolType;", - "path": "codex-rs/core/tests/suite/model_switching.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -5,7 +5,7 @@ use std::sync::Arc;\n \n use anyhow::Result;\n use codex_core::config::types::Personality;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::protocol::AskForApproval;\n use codex_protocol::protocol::EventMsg;\n use codex_protocol::protocol::Op;", - "path": "codex-rs/core/tests/suite/model_visible_layout.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,5 +1,5 @@\n use codex_core::config::Constrained;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::protocol::AskForApproval;\n use codex_protocol::protocol::EventMsg;\n use codex_protocol::protocol::Op;", - "path": "codex-rs/core/tests/suite/otel.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,7 +1,7 @@\n use codex_core::config::types::Personality;\n-use codex_core::features::Feature;\n use codex_core::models_manager::manager::ModelsManager;\n use codex_core::models_manager::manager::RefreshStrategy;\n+use codex_features::Feature;\n use codex_protocol::config_types::ReasoningSummary;\n use codex_protocol::openai_models::ConfigShellToolType;\n use codex_protocol::openai_models::ModelInfo;", - "path": "codex-rs/core/tests/suite/personality.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -7,7 +7,7 @@ use std::time::Instant;\n \n use anyhow::Result;\n use codex_core::CodexAuth;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::protocol::EventMsg;\n use codex_protocol::protocol::Op;\n use core_test_support::apps_test_server::AppsTestServer;", - "path": "codex-rs/core/tests/suite/plugins.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,9 +1,9 @@\n #![allow(clippy::unwrap_used)]\n \n use codex_apply_patch::APPLY_PATCH_TOOL_INSTRUCTIONS;\n-use codex_core::features::Feature;\n use codex_core::shell::Shell;\n use codex_core::shell::default_user_shell;\n+use codex_features::Feature;\n use codex_protocol::config_types::CollaborationMode;\n use codex_protocol::config_types::ModeKind;\n use codex_protocol::config_types::ReasoningSummary;", - "path": "codex-rs/core/tests/suite/prompt_caching.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,7 +1,7 @@\n #![cfg(not(target_os = \"windows\"))]\n \n use codex_core::CodexAuth;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::protocol::EventMsg;\n use codex_protocol::protocol::Op;\n use codex_protocol::user_input::UserInput;", - "path": "codex-rs/core/tests/suite/request_compression.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -2,8 +2,8 @@\n \n use anyhow::Result;\n use codex_core::config::Constrained;\n-use codex_core::features::Feature;\n use codex_core::sandboxing::SandboxPermissions;\n+use codex_features::Feature;\n use codex_protocol::models::FileSystemPermissions;\n use codex_protocol::models::PermissionProfile;\n use codex_protocol::protocol::AskForApproval;", - "path": "codex-rs/core/tests/suite/request_permissions.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -3,7 +3,7 @@\n \n use anyhow::Result;\n use codex_core::config::Constrained;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::models::FileSystemPermissions;\n use codex_protocol::protocol::AskForApproval;\n use codex_protocol::protocol::EventMsg;", - "path": "codex-rs/core/tests/suite/request_permissions_tool.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -2,7 +2,7 @@\n \n use std::collections::HashMap;\n \n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::config_types::CollaborationMode;\n use codex_protocol::config_types::ModeKind;\n use codex_protocol::config_types::Settings;", - "path": "codex-rs/core/tests/suite/request_user_input.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -4,7 +4,7 @@\n use anyhow::Result;\n use codex_core::CodexAuth;\n use codex_core::config::Config;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::openai_models::ModelsResponse;\n use codex_protocol::protocol::AskForApproval;\n use codex_protocol::protocol::EventMsg;", - "path": "codex-rs/core/tests/suite/search_tool.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,7 +1,7 @@\n use std::time::Duration;\n \n use anyhow::Result;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use core_test_support::assert_regex_match;\n use core_test_support::responses::ev_assistant_message;\n use core_test_support::responses::ev_completed;", - "path": "codex-rs/core/tests/suite/shell_command.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,5 +1,5 @@\n use anyhow::Result;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::protocol::AskForApproval;\n use codex_protocol::protocol::EventMsg;\n use codex_protocol::protocol::ExecCommandBeginEvent;", - "path": "codex-rs/core/tests/suite/shell_snapshot.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -3,9 +3,9 @@\n \n use anyhow::Result;\n use codex_core::CodexAuth;\n-use codex_core::features::Feature;\n use codex_core::models_manager::manager::ModelsManager;\n use codex_core::models_manager::manager::RefreshStrategy;\n+use codex_features::Feature;\n use codex_protocol::config_types::ReasoningSummary;\n use codex_protocol::openai_models::ConfigShellToolType;\n use codex_protocol::openai_models::ModelInfo;", - "path": "codex-rs/core/tests/suite/spawn_agent_description.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,7 +1,7 @@\n use anyhow::Result;\n use codex_core::config::types::McpServerConfig;\n use codex_core::config::types::McpServerTransportConfig;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::ThreadId;\n use codex_protocol::dynamic_tools::DynamicToolSpec;\n use codex_protocol::protocol::AskForApproval;", - "path": "codex-rs/core/tests/suite/sqlite_state.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,7 +1,7 @@\n use anyhow::Result;\n use codex_core::ThreadConfigSnapshot;\n use codex_core::config::AgentRoleConfig;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::ThreadId;\n use codex_protocol::openai_models::ReasoningEffort;\n use core_test_support::responses::ResponsesRequest;", - "path": "codex-rs/core/tests/suite/subagent_notifications.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -3,7 +3,7 @@\n use std::fs;\n \n use assert_matches::assert_matches;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::plan_tool::StepStatus;\n use codex_protocol::protocol::AskForApproval;\n use codex_protocol::protocol::EventMsg;", - "path": "codex-rs/core/tests/suite/tool_harness.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -7,8 +7,8 @@ use std::time::Instant;\n \n use anyhow::Context;\n use anyhow::Result;\n-use codex_core::features::Feature;\n use codex_core::sandboxing::SandboxPermissions;\n+use codex_features::Feature;\n use codex_protocol::protocol::AskForApproval;\n use codex_protocol::protocol::SandboxPolicy;\n use core_test_support::assert_regex_match;", - "path": "codex-rs/core/tests/suite/tools.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -9,7 +9,7 @@ use anyhow::Context;\n use anyhow::Result;\n use anyhow::bail;\n use codex_core::CodexThread;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::protocol::EventMsg;\n use codex_protocol::protocol::Op;\n use codex_protocol::protocol::UndoCompletedEvent;", - "path": "codex-rs/core/tests/suite/undo.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -4,7 +4,7 @@ use std::fs;\n \n use anyhow::Context;\n use anyhow::Result;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::protocol::AskForApproval;\n use codex_protocol::protocol::EventMsg;\n use codex_protocol::protocol::ExecCommandSource;", - "path": "codex-rs/core/tests/suite/unified_exec.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -3,7 +3,7 @@\n use codex_config::CONFIG_TOML_FILE;\n use codex_core::CodexAuth;\n use codex_core::NewThread;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::protocol::EventMsg;\n use codex_protocol::protocol::InitialHistory;\n use codex_protocol::protocol::WarningEvent;", - "path": "codex-rs/core/tests/suite/unstable_features_warning.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,5 +1,5 @@\n use anyhow::Context;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::permissions::NetworkSandboxPolicy;\n use codex_protocol::protocol::AskForApproval;\n use codex_protocol::protocol::EventMsg;", - "path": "codex-rs/core/tests/suite/user_shell_cmd.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -3,7 +3,7 @@\n use base64::Engine;\n use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;\n use codex_core::CodexAuth;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::config_types::ReasoningSummary;\n use codex_protocol::openai_models::ConfigShellToolType;\n use codex_protocol::openai_models::InputModality;", - "path": "codex-rs/core/tests/suite/view_image.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,6 +1,6 @@\n #![allow(clippy::unwrap_used)]\n \n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::config_types::WebSearchMode;\n use codex_protocol::protocol::SandboxPolicy;\n use core_test_support::responses;", - "path": "codex-rs/core/tests/suite/web_search.rs", - "status": "modified" - }, - { - "additions": 16, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,16 @@\n+load(\"//:defs.bzl\", \"codex_rust_crate\")\n+\n+codex_rust_crate(\n+ name = \"features\",\n+ crate_name = \"codex_features\",\n+ compile_data = glob(\n+ include = [\"**\"],\n+ exclude = [\n+ \"BUILD.bazel\",\n+ \"Cargo.toml\",\n+ ],\n+ allow_empty = True,\n+ ) + [\n+ \"//codex-rs:node-version.txt\",\n+ ],\n+)", - "path": "codex-rs/features/BUILD.bazel", - "status": "added" - }, - { - "additions": 25, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,25 @@\n+[package]\n+edition.workspace = true\n+license.workspace = true\n+name = \"codex-features\"\n+version.workspace = true\n+\n+[lib]\n+doctest = false\n+name = \"codex_features\"\n+path = \"src/lib.rs\"\n+\n+[lints]\n+workspace = true\n+\n+[dependencies]\n+codex-login = { workspace = true }\n+codex-otel = { workspace = true }\n+codex-protocol = { workspace = true }\n+schemars = { workspace = true }\n+serde = { workspace = true, features = [\"derive\"] }\n+toml = { workspace = true }\n+tracing = { workspace = true, features = [\"log\"] }\n+\n+[dev-dependencies]\n+pretty_assertions = { workspace = true }", - "path": "codex-rs/features/Cargo.toml", - "status": "added" - }, - { - "additions": 4, - "deletions": 4, - "patch_excerpt": "@@ -1,5 +1,5 @@\n-use super::Feature;\n-use super::Features;\n+use crate::Feature;\n+use crate::Features;\n use tracing::info;\n \n #[derive(Clone, Copy)]\n@@ -47,7 +47,7 @@ const ALIASES: &[Alias] = &[\n },\n ];\n \n-pub(crate) fn legacy_feature_keys() -> impl Iterator {\n+pub fn legacy_feature_keys() -> impl Iterator {\n ALIASES.iter().map(|alias| alias.legacy_key)\n }\n \n@@ -62,7 +62,7 @@ pub(crate) fn feature_for_key(key: &str) -> Option {\n }\n \n #[derive(Debug, Default)]\n-pub struct LegacyFeatureToggles {\n+pub(crate) struct LegacyFeatureToggles {\n pub include_apply_patch_tool: Option,\n pub experimental_use_freeform_apply_patch: Option,\n pub experimental_use_unified_exec_tool: Option,", - "path": "codex-rs/features/src/legacy.rs", - "status": "renamed" - }, - { - "additions": 54, - "deletions": 72, - "patch_excerpt": "@@ -1,30 +1,24 @@\n //! Centralized feature flags and metadata.\n //!\n-//! This module defines a small set of toggles that gate experimental and\n-//! optional behavior across the codebase. Instead of wiring individual\n-//! booleans through multiple types, call sites consult a single `Features`\n-//! container attached to `Config`.\n-\n-use crate::auth::AuthManager;\n-use crate::auth::CodexAuth;\n-use crate::config::Config;\n-use crate::config::ConfigToml;\n-use crate::config::profile::ConfigProfile;\n-use crate::protocol::Event;\n-use crate::protocol::EventMsg;\n-use crate::protocol::WarningEvent;\n-use codex_config::CONFIG_TOML_FILE;\n+//! This crate defines the feature registry plus the logic used to resolve an\n+//! effective feature set from config-like inputs.\n+\n+use codex_login::AuthManager;\n+use codex_login::CodexAuth;\n use codex_otel::SessionTelemetry;\n+use codex_protocol::protocol::Event;\n+use...", - "path": "codex-rs/features/src/lib.rs", - "status": "renamed" - }, - { - "additions": 80, - "deletions": 6, - "patch_excerpt": "@@ -1,10 +1,21 @@\n-use super::*;\n-\n+use crate::Feature;\n+use crate::FeatureConfigSource;\n+use crate::FeatureOverrides;\n+use crate::Features;\n+use crate::FeaturesToml;\n+use crate::Stage;\n+use crate::feature_for_key;\n+use crate::unstable_features_warning_event;\n+use codex_protocol::protocol::EventMsg;\n+use codex_protocol::protocol::WarningEvent;\n use pretty_assertions::assert_eq;\n+use std::collections::BTreeMap;\n+use toml::Table;\n+use toml::Value as TomlValue;\n \n #[test]\n fn under_development_features_are_disabled_by_default() {\n- for spec in FEATURES {\n+ for spec in crate::FEATURES {\n if matches!(spec.stage, Stage::UnderDevelopment) {\n assert_eq!(\n spec.default_enabled, false,\n@@ -17,7 +28,7 @@ fn under_development_features_are_disabled_by_default() {\n \n #[test]\n fn default_enabled_features_are_stable() {\n- for spec in FEATURES {\n+ for spec ...", - "path": "codex-rs/features/src/tests.rs", - "status": "renamed" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -19,6 +19,7 @@ workspace = true\n anyhow = { workspace = true }\n codex-arg0 = { workspace = true }\n codex-core = { workspace = true }\n+codex-features = { workspace = true }\n codex-protocol = { workspace = true }\n codex-shell-command = { workspace = true }\n codex-utils-cli = { workspace = true }", - "path": "codex-rs/mcp-server/Cargo.toml", - "status": "modified" - }, - { - "additions": 2, - "deletions": 1, - "patch_excerpt": "@@ -7,6 +7,7 @@ use codex_core::config::Config;\n use codex_core::default_client::USER_AGENT_SUFFIX;\n use codex_core::default_client::get_codex_user_agent;\n use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;\n+use codex_features::Feature;\n use codex_protocol::ThreadId;\n use codex_protocol::protocol::SessionSource;\n use codex_protocol::protocol::Submission;\n@@ -65,7 +66,7 @@ impl MessageProcessor {\n CollaborationModesConfig {\n default_mode_request_user_input: config\n .features\n- .enabled(codex_core::features::Feature::DefaultModeRequestUserInput),\n+ .enabled(Feature::DefaultModeRequestUserInput),\n },\n ));\n Self {", - "path": "codex-rs/mcp-server/src/message_processor.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -36,6 +36,7 @@ codex-chatgpt = { workspace = true }\n codex-client = { workspace = true }\n codex-cloud-requirements = { workspace = true }\n codex-core = { workspace = true }\n+codex-features = { workspace = true }\n codex-feedback = { workspace = true }\n codex-file-search = { workspace = true }\n codex-login = { workspace = true }", - "path": "codex-rs/tui/Cargo.toml", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -51,13 +51,13 @@ use codex_core::config::edit::ConfigEditsBuilder;\n use codex_core::config::types::ApprovalsReviewer;\n use codex_core::config::types::ModelAvailabilityNuxConfig;\n use codex_core::config_loader::ConfigLayerStackOrdering;\n-use codex_core::features::Feature;\n use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;\n use codex_core::models_manager::manager::RefreshStrategy;\n use codex_core::models_manager::model_presets::HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG;\n use codex_core::models_manager::model_presets::HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG;\n #[cfg(target_os = \"windows\")]\n use codex_core::windows_sandbox::WindowsSandboxLevelExt;\n+use codex_features::Feature;\n use codex_otel::SessionTelemetry;\n use codex_otel::TelemetryAuthMode;\n use codex_protocol::ThreadId;", - "path": "codex-rs/tui/src/app.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -24,7 +24,7 @@ use crate::bottom_pane::TerminalTitleItem;\n use crate::history_cell::HistoryCell;\n \n use codex_core::config::types::ApprovalsReviewer;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::config_types::CollaborationModeMask;\n use codex_protocol::config_types::Personality;\n use codex_protocol::config_types::ServiceTier;", - "path": "codex-rs/tui/src/app_event.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -3,7 +3,7 @@ use std::future::Future;\n use crate::Cli;\n use codex_core::config::Config;\n use codex_core::config::ConfigOverrides;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n \n pub(crate) fn app_server_tui_config_inputs(\n cli: &Cli,", - "path": "codex-rs/tui/src/app_server_tui_dispatch.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -16,7 +16,7 @@ use crate::key_hint::KeyBinding;\n use crate::render::highlight::highlight_bash_to_lines;\n use crate::render::renderable::ColumnRenderable;\n use crate::render::renderable::Renderable;\n-use codex_core::features::Features;\n+use codex_features::Features;\n use codex_protocol::ThreadId;\n use codex_protocol::mcp::RequestId;\n use codex_protocol::models::MacOsAutomationPermission;", - "path": "codex-rs/tui/src/bottom_pane/approval_overlay.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -19,7 +19,7 @@ use crate::render::renderable::ColumnRenderable;\n use crate::render::renderable::Renderable;\n use crate::style::user_message_style;\n \n-use codex_core::features::Feature;\n+use codex_features::Feature;\n \n use super::CancellationEvent;\n use super::bottom_pane_view::BottomPaneView;", - "path": "codex-rs/tui/src/bottom_pane/experimental_features_view.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -27,9 +27,9 @@ use crate::render::renderable::Renderable;\n use crate::render::renderable::RenderableItem;\n use crate::tui::FrameRequester;\n use bottom_pane_view::BottomPaneView;\n-use codex_core::features::Features;\n use codex_core::plugins::PluginCapabilitySummary;\n use codex_core::skills::model::SkillMetadata;\n+use codex_features::Features;\n use codex_file_search::FileMatch;\n use codex_protocol::request_user_input::RequestUserInputEvent;\n use codex_protocol::user_input::TextElement;", - "path": "codex-rs/tui/src/bottom_pane/mod.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -67,8 +67,6 @@ use codex_core::config::types::ApprovalsReviewer;\n use codex_core::config::types::Notifications;\n use codex_core::config::types::WindowsSandboxModeToml;\n use codex_core::config_loader::ConfigLayerStackOrdering;\n-use codex_core::features::FEATURES;\n-use codex_core::features::Feature;\n use codex_core::find_thread_name_by_id;\n use codex_core::git_info::current_branch_name;\n use codex_core::git_info::get_git_repo_root;\n@@ -80,6 +78,8 @@ use codex_core::project_doc::DEFAULT_PROJECT_DOC_FILENAME;\n use codex_core::skills::model::SkillMetadata;\n #[cfg(target_os = \"windows\")]\n use codex_core::windows_sandbox::WindowsSandboxLevelExt;\n+use codex_features::FEATURES;\n+use codex_features::Feature;\n use codex_otel::RuntimeMetricsSummary;\n use codex_otel::SessionTelemetry;\n use codex_protocol::ThreadId;", - "path": "codex-rs/tui/src/chatwidget.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -33,11 +33,11 @@ use codex_core::config_loader::ConfigLayerStack;\n use codex_core::config_loader::ConfigRequirements;\n use codex_core::config_loader::ConfigRequirementsToml;\n use codex_core::config_loader::RequirementSource;\n-use codex_core::features::FEATURES;\n-use codex_core::features::Feature;\n use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;\n use codex_core::models_manager::manager::ModelsManager;\n use codex_core::skills::model::SkillMetadata;\n+use codex_features::FEATURES;\n+use codex_features::Feature;\n use codex_otel::RuntimeMetricsSummary;\n use codex_otel::SessionTelemetry;\n use codex_protocol::ThreadId;", - "path": "codex-rs/tui/src/chatwidget/tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1246,7 +1246,7 @@ mod tests {\n use codex_core::config::ConfigBuilder;\n use codex_core::config::ConfigOverrides;\n use codex_core::config::ProjectConfig;\n- use codex_core::features::Feature;\n+ use codex_features::Feature;\n use codex_protocol::protocol::AskForApproval;\n use codex_protocol::protocol::RolloutItem;\n use codex_protocol::protocol::RolloutLine;", - "path": "codex-rs/tui/src/lib.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,4 +1,4 @@\n-use codex_core::features::FEATURES;\n+use codex_features::FEATURES;\n use codex_protocol::account::PlanType;\n use lazy_static::lazy_static;\n use rand::Rng;", - "path": "codex-rs/tui/src/tooltips.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -41,6 +41,7 @@ codex-chatgpt = { workspace = true }\n codex-client = { workspace = true }\n codex-cloud-requirements = { workspace = true }\n codex-core = { workspace = true }\n+codex-features = { workspace = true }\n codex-feedback = { workspace = true }\n codex-file-search = { workspace = true }\n codex-login = { workspace = true }", - "path": "codex-rs/tui_app_server/Cargo.toml", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -68,13 +68,13 @@ use codex_core::config::edit::ConfigEditsBuilder;\n use codex_core::config::types::ApprovalsReviewer;\n use codex_core::config::types::ModelAvailabilityNuxConfig;\n use codex_core::config_loader::ConfigLayerStackOrdering;\n-use codex_core::features::Feature;\n use codex_core::message_history;\n use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;\n use codex_core::models_manager::model_presets::HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG;\n use codex_core::models_manager::model_presets::HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG;\n #[cfg(target_os = \"windows\")]\n use codex_core::windows_sandbox::WindowsSandboxLevelExt;\n+use codex_features::Feature;\n use codex_otel::SessionTelemetry;\n use codex_protocol::ThreadId;\n use codex_protocol::approvals::ExecApprovalRequestEvent;", - "path": "codex-rs/tui_app_server/src/app.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -25,7 +25,7 @@ use crate::bottom_pane::StatusLineItem;\n use crate::history_cell::HistoryCell;\n \n use codex_core::config::types::ApprovalsReviewer;\n-use codex_core::features::Feature;\n+use codex_features::Feature;\n use codex_protocol::config_types::CollaborationModeMask;\n use codex_protocol::config_types::Personality;\n use codex_protocol::config_types::ServiceTier;", - "path": "codex-rs/tui_app_server/src/app_event.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -16,7 +16,7 @@ use crate::key_hint::KeyBinding;\n use crate::render::highlight::highlight_bash_to_lines;\n use crate::render::renderable::ColumnRenderable;\n use crate::render::renderable::Renderable;\n-use codex_core::features::Features;\n+use codex_features::Features;\n use codex_protocol::ThreadId;\n use codex_protocol::mcp::RequestId;\n use codex_protocol::models::MacOsAutomationPermission;", - "path": "codex-rs/tui_app_server/src/bottom_pane/approval_overlay.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -19,7 +19,7 @@ use crate::render::renderable::ColumnRenderable;\n use crate::render::renderable::Renderable;\n use crate::style::user_message_style;\n \n-use codex_core::features::Feature;\n+use codex_features::Feature;\n \n use super::CancellationEvent;\n use super::bottom_pane_view::BottomPaneView;", - "path": "codex-rs/tui_app_server/src/bottom_pane/experimental_features_view.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -27,9 +27,9 @@ use crate::render::renderable::Renderable;\n use crate::render::renderable::RenderableItem;\n use crate::tui::FrameRequester;\n use bottom_pane_view::BottomPaneView;\n-use codex_core::features::Features;\n use codex_core::plugins::PluginCapabilitySummary;\n use codex_core::skills::model::SkillMetadata;\n+use codex_features::Features;\n use codex_file_search::FileMatch;\n use codex_protocol::request_user_input::RequestUserInputEvent;\n use codex_protocol::user_input::TextElement;", - "path": "codex-rs/tui_app_server/src/bottom_pane/mod.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -87,8 +87,6 @@ use codex_core::config::types::ApprovalsReviewer;\n use codex_core::config::types::Notifications;\n use codex_core::config::types::WindowsSandboxModeToml;\n use codex_core::config_loader::ConfigLayerStackOrdering;\n-use codex_core::features::FEATURES;\n-use codex_core::features::Feature;\n use codex_core::find_thread_name_by_id;\n use codex_core::git_info::current_branch_name;\n use codex_core::git_info::get_git_repo_root;\n@@ -98,6 +96,8 @@ use codex_core::project_doc::DEFAULT_PROJECT_DOC_FILENAME;\n use codex_core::skills::model::SkillMetadata;\n #[cfg(target_os = \"windows\")]\n use codex_core::windows_sandbox::WindowsSandboxLevelExt;\n+use codex_features::FEATURES;\n+use codex_features::Feature;\n use codex_otel::RuntimeMetricsSummary;\n use codex_otel::SessionTelemetry;\n use codex_protocol::ThreadId;", - "path": "codex-rs/tui_app_server/src/chatwidget.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -57,10 +57,10 @@ use codex_core::config_loader::ConfigLayerStack;\n use codex_core::config_loader::ConfigRequirements;\n use codex_core::config_loader::ConfigRequirementsToml;\n use codex_core::config_loader::RequirementSource;\n-use codex_core::features::FEATURES;\n-use codex_core::features::Feature;\n use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;\n use codex_core::skills::model::SkillMetadata;\n+use codex_features::FEATURES;\n+use codex_features::Feature;\n use codex_otel::RuntimeMetricsSummary;\n use codex_otel::SessionTelemetry;\n use codex_protocol::ThreadId;", - "path": "codex-rs/tui_app_server/src/chatwidget/tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1594,7 +1594,7 @@ mod tests {\n use codex_core::config::ConfigBuilder;\n use codex_core::config::ConfigOverrides;\n use codex_core::config::ProjectConfig;\n- use codex_core::features::Feature;\n+ use codex_features::Feature;\n use codex_protocol::protocol::AskForApproval;\n use codex_protocol::protocol::RolloutItem;\n use codex_protocol::protocol::RolloutLine;", - "path": "codex-rs/tui_app_server/src/lib.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,4 +1,4 @@\n-use codex_core::features::FEATURES;\n+use codex_features::FEATURES;\n use codex_protocol::account::PlanType;\n use lazy_static::lazy_static;\n use rand::Rng;", - "path": "codex-rs/tui_app_server/src/tooltips.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "- Split the feature system into a new `codex-features` crate.\n- Cut `codex-core` and workspace consumers over to the new config and warning APIs.", - "labels": [], - "merged_at": "2026-03-20T03:12:07Z", - "number": 15253, - "state": "merged", - "title": "Split features into codex-features crate", - "url": "https://github.com/openai/codex/pull/15253" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15254.json b/artifacts/github/bundles/openai-codex-pr-15254.json deleted file mode 100644 index a3940f0..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15254.json +++ /dev/null @@ -1,161 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "bolinfest", - "committed_at": "2026-03-20T01:22:32Z", - "message": "core: add an exec capture policy for full output", - "sha": "90cb67e93ecb87a903014e8769bfeccc6bcd0f01", - "url": "https://github.com/openai/codex/commit/90cb67e93ecb87a903014e8769bfeccc6bcd0f01" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "EOF", - "IO_DRAIN_TIMEOUT_MS", - "EXEC_OUTPUT_MAX_BYTES", - "TODO", - "EXIT_CODE_SIGNAL_BASE", - "TIMEOUT_CODE", - "AGGREGATE_BUFFER_INITIAL_CAPACITY", - "READ_CHUNK_SIZE", - "USER_SHELL_TIMEOUT_MS", - "--codex-run-as-apply-patch", - "DNS", - "NETWORK_TIMEOUT_MS" - ], - "files": [ - { - "additions": 7, - "deletions": 0, - "patch_excerpt": "@@ -204,6 +204,7 @@ use codex_core::config_loader::CloudRequirementsLoader;\n use codex_core::default_client::set_default_client_residency_requirement;\n use codex_core::error::CodexErr;\n use codex_core::error::Result as CodexResult;\n+use codex_core::exec::ExecCapturePolicy;\n use codex_core::exec::ExecExpiration;\n use codex_core::exec::ExecParams;\n use codex_core::exec_env::create_env;\n@@ -1672,11 +1673,17 @@ impl CodexMessageProcessor {\n None => ExecExpiration::DefaultTimeout,\n }\n };\n+ let capture_policy = if disable_output_cap {\n+ ExecCapturePolicy::FullBuffer\n+ } else {\n+ ExecCapturePolicy::ShellTool\n+ };\n let sandbox_cwd = self.config.cwd.clone();\n let exec_params = ExecParams {\n command,\n cwd: cwd.clone(),\n expiration,\n+ capture_policy,\n ...", - "path": "codex-rs/app-server/src/codex_message_processor.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -733,6 +733,7 @@ mod tests {\n env: HashMap::new(),\n network: None,\n expiration: ExecExpiration::DefaultTimeout,\n+ capture_policy: codex_core::exec::ExecCapturePolicy::ShellTool,\n sandbox: SandboxType::WindowsRestrictedToken,\n windows_sandbox_level: WindowsSandboxLevel::Disabled,\n windows_sandbox_private_desktop: false,\n@@ -845,6 +846,7 @@ mod tests {\n env: HashMap::new(),\n network: None,\n expiration: ExecExpiration::Cancellation(CancellationToken::new()),\n+ capture_policy: codex_core::exec::ExecCapturePolicy::ShellTool,\n sandbox: SandboxType::None,\n windows_sandbox_level: WindowsSandboxLevel::Disabled,\n windows_sandbox_private_desktop: false,", - "path": "codex-rs/app-server/src/command_exec.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 0, - "patch_excerpt": "@@ -7,6 +7,7 @@ use crate::config_loader::ConfigLayerStackOrdering;\n use crate::config_loader::NetworkConstraints;\n use crate::config_loader::RequirementSource;\n use crate::config_loader::Sourced;\n+use crate::exec::ExecCapturePolicy;\n use crate::exec::ExecToolCallOutput;\n use crate::function_tool::FunctionCallError;\n use crate::mcp_connection_manager::ToolInfo;\n@@ -4788,6 +4789,7 @@ async fn rejects_escalated_permissions_when_policy_not_on_request() {\n },\n cwd: turn_context.cwd.clone(),\n expiration: timeout_ms.into(),\n+ capture_policy: ExecCapturePolicy::ShellTool,\n env: HashMap::new(),\n network: None,\n sandbox_permissions,\n@@ -4805,6 +4807,7 @@ async fn rejects_escalated_permissions_when_policy_not_on_request() {\n command: params.command.clone(),\n cwd: params.cwd.clone(),\n expiration: timeout_ms.into(),\n+ ...", - "path": "codex-rs/core/src/codex_tests.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -3,6 +3,7 @@ use crate::compact::InitialContextInjection;\n use crate::config_loader::ConfigLayerEntry;\n use crate::config_loader::ConfigRequirements;\n use crate::config_loader::ConfigRequirementsToml;\n+use crate::exec::ExecCapturePolicy;\n use crate::exec::ExecParams;\n use crate::exec_policy::ExecPolicyManager;\n use crate::features::Feature;\n@@ -124,6 +125,7 @@ async fn guardian_allows_shell_additional_permissions_requests_past_policy_valid\n },\n cwd: turn_context.cwd.clone(),\n expiration: expiration_ms.into(),\n+ capture_policy: ExecCapturePolicy::ShellTool,\n env: HashMap::new(),\n network: None,\n sandbox_permissions: SandboxPermissions::WithAdditionalPermissions,", - "path": "codex-rs/core/src/codex_tests_guardian.rs", - "status": "modified" - }, - { - "additions": 98, - "deletions": 29, - "patch_excerpt": "@@ -78,6 +78,7 @@ pub struct ExecParams {\n pub command: Vec,\n pub cwd: PathBuf,\n pub expiration: ExecExpiration,\n+ pub capture_policy: ExecCapturePolicy,\n pub env: HashMap,\n pub network: Option,\n pub sandbox_permissions: SandboxPermissions,\n@@ -87,6 +88,16 @@ pub struct ExecParams {\n pub arg0: Option,\n }\n \n+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]\n+pub enum ExecCapturePolicy {\n+ /// Shell-like execs keep the historical output cap and timeout behavior.\n+ #[default]\n+ ShellTool,\n+ /// Trusted internal helpers can buffer the full child output in memory\n+ /// without the shell-oriented output cap or exec-expiration behavior.\n+ FullBuffer,\n+}\n+\n fn select_process_exec_tool_sandbox_type(\n file_system_sandbox_policy: &FileSystemSandboxPolicy,\n network_sandbox_policy: NetworkSandbo...", - "path": "codex-rs/core/src/exec.rs", - "status": "modified" - }, - { - "additions": 197, - "deletions": 6, - "patch_excerpt": "@@ -1,6 +1,7 @@\n use super::*;\n use codex_protocol::config_types::WindowsSandboxLevel;\n use pretty_assertions::assert_eq;\n+use std::collections::HashMap;\n use std::time::Duration;\n use tokio::io::AsyncWriteExt;\n \n@@ -91,14 +92,16 @@ fn sandbox_detection_ignores_network_policy_text_with_zero_exit_code() {\n }\n \n #[tokio::test]\n-async fn read_capped_limits_retained_bytes() {\n+async fn read_output_limits_retained_bytes_for_shell_capture() {\n let (mut writer, reader) = tokio::io::duplex(1024);\n let bytes = vec![b'a'; EXEC_OUTPUT_MAX_BYTES.saturating_add(128 * 1024)];\n tokio::spawn(async move {\n writer.write_all(&bytes).await.expect(\"write\");\n });\n \n- let out = read_capped(reader, None, false).await.expect(\"read\");\n+ let out = read_output(reader, None, false, Some(EXEC_OUTPUT_MAX_BYTES))\n+ .await\n+ .expect(\"read\");\n assert_eq!(out.text.len(), EXE...", - "path": "codex-rs/core/src/exec_tests.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 0, - "patch_excerpt": "@@ -8,6 +8,7 @@ ready\u2011to\u2011spawn environment.\n \n pub(crate) mod macos_permissions;\n \n+use crate::exec::ExecCapturePolicy;\n use crate::exec::ExecExpiration;\n use crate::exec::ExecToolCallOutput;\n use crate::exec::SandboxType;\n@@ -55,6 +56,7 @@ pub struct CommandSpec {\n pub cwd: PathBuf,\n pub env: HashMap,\n pub expiration: ExecExpiration,\n+ pub capture_policy: ExecCapturePolicy,\n pub sandbox_permissions: SandboxPermissions,\n pub additional_permissions: Option,\n pub justification: Option,\n@@ -67,6 +69,7 @@ pub struct ExecRequest {\n pub env: HashMap,\n pub network: Option,\n pub expiration: ExecExpiration,\n+ pub capture_policy: ExecCapturePolicy,\n pub sandbox: SandboxType,\n pub windows_sandbox_level: WindowsSandboxLevel,\n pub windows_sandbox_private_desktop: bool,\n@@ -70...", - "path": "codex-rs/core/src/sandboxing/mod.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 0, - "patch_excerpt": "@@ -158,6 +158,7 @@ fn transform_preserves_unrestricted_file_system_policy_for_restricted_network()\n cwd: cwd.clone(),\n env: HashMap::new(),\n expiration: crate::exec::ExecExpiration::DefaultTimeout,\n+ capture_policy: crate::exec::ExecCapturePolicy::ShellTool,\n sandbox_permissions: super::SandboxPermissions::UseDefault,\n additional_permissions: None,\n justification: None,\n@@ -518,6 +519,7 @@ fn transform_additional_permissions_enable_network_for_external_sandbox() {\n cwd: cwd.clone(),\n env: HashMap::new(),\n expiration: crate::exec::ExecExpiration::DefaultTimeout,\n+ capture_policy: crate::exec::ExecCapturePolicy::ShellTool,\n sandbox_permissions: super::SandboxPermissions::WithAdditionalPermissions,\n ...", - "path": "codex-rs/core/src/sandboxing/mod_tests.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -10,6 +10,7 @@ use tracing::error;\n use uuid::Uuid;\n \n use crate::codex::TurnContext;\n+use crate::exec::ExecCapturePolicy;\n use crate::exec::ExecToolCallOutput;\n use crate::exec::SandboxType;\n use crate::exec::StdoutStream;\n@@ -165,6 +166,7 @@ pub(crate) async fn execute_user_shell_command(\n // TODO(zhao-oai): Now that we have ExecExpiration::Cancellation, we\n // should use that instead of an \"arbitrarily large\" timeout here.\n expiration: USER_SHELL_TIMEOUT_MS.into(),\n+ capture_policy: ExecCapturePolicy::ShellTool,\n sandbox: SandboxType::None,\n windows_sandbox_level: turn_context.windows_sandbox_level,\n windows_sandbox_private_desktop: turn_context", - "path": "codex-rs/core/src/tasks/user_shell.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 0, - "patch_excerpt": "@@ -5,6 +5,7 @@ use codex_protocol::models::ShellToolCallParams;\n use std::sync::Arc;\n \n use crate::codex::TurnContext;\n+use crate::exec::ExecCapturePolicy;\n use crate::exec::ExecParams;\n use crate::exec_env::create_env;\n use crate::exec_policy::ExecApprovalRequest;\n@@ -70,6 +71,7 @@ impl ShellHandler {\n command: params.command.clone(),\n cwd: turn_context.resolve_path(params.workdir.clone()),\n expiration: params.timeout_ms.into(),\n+ capture_policy: ExecCapturePolicy::ShellTool,\n env: create_env(&turn_context.shell_environment_policy, Some(thread_id)),\n network: turn_context.network.clone(),\n sandbox_permissions: params.sandbox_permissions.unwrap_or_default(),\n@@ -124,6 +126,7 @@ impl ShellCommandHandler {\n command,\n cwd: turn_context.resolve_path(params.workdir.clone()),\n ...", - "path": "codex-rs/core/src/tools/handlers/shell.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -34,6 +34,7 @@ use uuid::Uuid;\n use crate::client_common::tools::ToolSpec;\n use crate::codex::Session;\n use crate::codex::TurnContext;\n+use crate::exec::ExecCapturePolicy;\n use crate::exec::ExecExpiration;\n use crate::exec_env::create_env;\n use crate::function_tool::FunctionCallError;\n@@ -1037,6 +1038,7 @@ impl JsReplManager {\n cwd: turn.cwd.clone(),\n env,\n expiration: ExecExpiration::DefaultTimeout,\n+ capture_policy: ExecCapturePolicy::ShellTool,\n sandbox_permissions: SandboxPermissions::UseDefault,\n additional_permissions: None,\n justification: None,", - "path": "codex-rs/core/src/tools/js_repl/mod.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -4,6 +4,7 @@\n //! decision to avoid re-prompting, builds the self-invocation command for\n //! `codex --codex-run-as-apply-patch`, and runs under the current\n //! `SandboxAttempt` with a minimal environment.\n+use crate::exec::ExecCapturePolicy;\n use crate::exec::ExecToolCallOutput;\n use crate::guardian::GuardianApprovalRequest;\n use crate::guardian::review_approval_request;\n@@ -93,6 +94,7 @@ impl ApplyPatchRuntime {\n ],\n cwd: req.action.cwd.clone(),\n expiration: req.timeout_ms.into(),\n+ capture_policy: ExecCapturePolicy::ShellTool,\n // Run apply_patch with a minimal environment for determinism and to avoid leaks.\n env: HashMap::new(),\n sandbox_permissions: req.sandbox_permissions,", - "path": "codex-rs/core/src/tools/runtimes/apply_patch.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -4,6 +4,7 @@ Module: runtimes\n Concrete ToolRuntime implementations for specific tools. Each runtime stays\n small and focused and reuses the orchestrator for approvals + sandbox + retry.\n */\n+use crate::exec::ExecCapturePolicy;\n use crate::exec::ExecExpiration;\n use crate::path_utils;\n use crate::sandboxing::CommandSpec;\n@@ -47,6 +48,7 @@ pub(crate) fn build_command_spec(\n cwd: cwd.to_path_buf(),\n env: env.clone(),\n expiration,\n+ capture_policy: ExecCapturePolicy::ShellTool,\n sandbox_permissions,\n additional_permissions,\n justification,", - "path": "codex-rs/core/src/tools/runtimes/mod.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 0, - "patch_excerpt": "@@ -1,6 +1,7 @@\n use super::ShellRequest;\n use crate::error::CodexErr;\n use crate::error::SandboxErr;\n+use crate::exec::ExecCapturePolicy;\n use crate::exec::ExecExpiration;\n use crate::exec::ExecToolCallOutput;\n use crate::exec::SandboxType;\n@@ -124,6 +125,7 @@ pub(super) async fn try_run_zsh_fork(\n env: sandbox_env,\n network: sandbox_network,\n expiration: _sandbox_expiration,\n+ capture_policy: _capture_policy,\n sandbox,\n windows_sandbox_level,\n windows_sandbox_private_desktop: _windows_sandbox_private_desktop,\n@@ -903,6 +905,7 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {\n env: exec_env,\n network: self.network.clone(),\n expiration: ExecExpiration::Cancellation(cancel_rx),\n+ capture_policy: ExecCapturePolicy::ShellTool,\n sandbox: self.sandbox...", - "path": "codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -3,6 +3,7 @@\n use std::collections::HashMap;\n use std::string::ToString;\n \n+use codex_core::exec::ExecCapturePolicy;\n use codex_core::exec::ExecParams;\n use codex_core::exec::ExecToolCallOutput;\n use codex_core::exec::SandboxType;\n@@ -37,6 +38,7 @@ async fn run_test_cmd(tmp: TempDir, cmd: Vec<&str>) -> Result),\n ExpectedTurnMismatch { expected: String, actual: String },\n+ ActiveTurnNotSteerable { turn_kind: NonSteerableTurnKind },\n EmptyInput,\n }\n \n+impl SteerInputError {\n+ fn to_error_event(&self) -> ErrorEvent {\n+ match self {\n+ Self::NoActiveTurn(_) => ErrorEvent {\n+ message: \"no active turn to steer\".to_string(),\n+ codex_error_info: Some(CodexErrorInfo::BadRequest),\n+ },\n+ Self::ExpectedTurnMismatch { expected, actual } => ErrorEvent {\n+ message: format!(\"expected active turn id `{expected}` but found `{actual}`\"),\n+ codex_error_info: Some(CodexErrorInfo::BadRequest),\n+ },\n+ Self::ActiveTurnNotSteerable { turn_kind } => {\n+ le...", - "path": "codex-rs/core/src/codex.rs", - "status": "modified" - }, - { - "additions": 38, - "deletions": 0, - "patch_excerpt": "@@ -24,6 +24,7 @@ use codex_protocol::permissions::FileSystemPath;\n use codex_protocol::permissions::FileSystemSandboxEntry;\n use codex_protocol::permissions::FileSystemSandboxPolicy;\n use codex_protocol::permissions::FileSystemSpecialPath;\n+use codex_protocol::protocol::NonSteerableTurnKind;\n use codex_protocol::protocol::ReadOnlyAccess;\n use codex_protocol::protocol::SandboxPolicy;\n use codex_protocol::request_permissions::PermissionGrantScope;\n@@ -4507,6 +4508,43 @@ async fn steer_input_enforces_expected_turn_id() {\n }\n }\n \n+#[tokio::test]\n+async fn steer_input_rejects_non_regular_turns() {\n+ for (task_kind, turn_kind) in [\n+ (TaskKind::Review, NonSteerableTurnKind::Review),\n+ (TaskKind::Compact, NonSteerableTurnKind::Compact),\n+ ] {\n+ let (sess, _tc, _rx) = make_session_and_context_with_rx().await;\n+ let input = vec![UserInput::Text {\n+ ...", - "path": "codex-rs/core/src/codex_tests.rs", - "status": "modified" - }, - { - "additions": 26, - "deletions": 1, - "patch_excerpt": "@@ -1538,6 +1538,15 @@ pub enum AgentStatus {\n NotFound,\n }\n \n+/// Turn kinds that reject same-turn steering.\n+#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, JsonSchema, TS)]\n+#[serde(rename_all = \"snake_case\")]\n+#[ts(rename_all = \"snake_case\")]\n+pub enum NonSteerableTurnKind {\n+ Review,\n+ Compact,\n+}\n+\n /// Codex errors that we expose to clients.\n #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, TS)]\n #[serde(rename_all = \"snake_case\")]\n@@ -1565,6 +1574,11 @@ pub enum CodexErrorInfo {\n ResponseTooManyFailedAttempts {\n http_status_code: Option,\n },\n+ /// Returned when `turn/start` or `turn/steer` is submitted while the current active turn\n+ /// cannot accept same-turn steering, for example `/review` or manual `/compact`.\n+ ActiveTurnNotSteerable {\n+ turn_kind: NonSteerableTurnKind,\n+ },\n T...", - "path": "codex-rs/protocol/src/protocol.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 4, - "patch_excerpt": "@@ -5008,7 +5008,7 @@ mod tests {\n app.chat_widget\n .apply_external_edit(\"queued follow-up\".to_string());\n app.chat_widget\n- .handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));\n+ .handle_key_event(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE));\n let input_state = app\n .chat_widget\n .capture_thread_input_state()\n@@ -5090,7 +5090,7 @@ mod tests {\n app.chat_widget\n .apply_external_edit(\"queued follow-up\".to_string());\n app.chat_widget\n- .handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));\n+ .handle_key_event(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE));\n let input_state = app\n .chat_widget\n .capture_thread_input_state()\n@@ -5171,7 +5171,7 @@ mod tests {\n app.chat_widget\n ...", - "path": "codex-rs/tui/src/app.rs", - "status": "modified" - }, - { - "additions": 19, - "deletions": 4, - "patch_excerpt": "@@ -825,8 +825,10 @@ impl BottomPane {\n &mut self,\n queued: Vec,\n pending_steers: Vec,\n+ rejected_steers: Vec,\n ) {\n self.pending_input_preview.pending_steers = pending_steers;\n+ self.pending_input_preview.rejected_steers = rejected_steers;\n self.pending_input_preview.queued_messages = queued;\n self.request_redraw();\n }\n@@ -1153,7 +1155,8 @@ impl BottomPane {\n }\n let has_pending_thread_approvals = !self.pending_thread_approvals.is_empty();\n let has_pending_input = !self.pending_input_preview.queued_messages.is_empty()\n- || !self.pending_input_preview.pending_steers.is_empty();\n+ || !self.pending_input_preview.pending_steers.is_empty()\n+ || !self.pending_input_preview.rejected_steers.is_empty();\n let has_...", - "path": "codex-rs/tui/src/bottom_pane/mod.rs", - "status": "modified" - }, - { - "additions": 40, - "deletions": 9, - "patch_excerpt": "@@ -10,17 +10,19 @@ use crate::render::renderable::Renderable;\n use crate::wrapping::RtOptions;\n use crate::wrapping::adaptive_wrap_lines;\n \n-/// Widget that displays pending steers plus user messages queued while a turn is in progress.\n+/// Widget that displays pending steers plus follow-up messages held while a turn is in progress.\n ///\n-/// The widget renders pending steers first, then queued user messages, as two\n-/// labeled sections. Pending steers explain that they will be submitted after\n-/// the next tool/result boundary unless the user presses Esc to interrupt and\n-/// send them immediately. The edit hint at the bottom only appears when there\n-/// are actual queued user messages to pop back into the composer. Because some\n-/// terminals intercept certain modifier-key combinations, the displayed\n-/// binding is configurable via [`set_edit_binding`](Self::set_edit_binding).\n+/// ...", - "path": "codex-rs/tui/src/bottom_pane/pending_input_preview.rs", - "status": "modified" - }, - { - "additions": 12, - "deletions": 5, - "patch_excerpt": "@@ -3,13 +3,16 @@ source: tui/src/bottom_pane/pending_input_preview.rs\n expression: \"format!(\\\"{buf:?}\\\")\"\n ---\n Buffer {\n- area: Rect { x: 0, y: 0, width: 52, height: 8 },\n+ area: Rect { x: 0, y: 0, width: 52, height: 11 },\n content: [\n \"\u2022 Messages to be submitted after next tool call \",\n \" (press esc to interrupt and send immediately) \",\n \" \u21b3 Please continue. \",\n \" \u21b3 Check the last command output. \",\n \" \",\n+ \"\u2022 Messages to be submitted at end of turn \",\n+ \" \u21b3 Rejected steer that will be retried. \",\n+ \" \",\n \"\u2022 Queued follow-up messages \",\n \" \u21b3 Queued follow-up question \",\n ...", - "path": "codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__pending_input_preview__tests__render_pending_steers_above_queued_messages.snap", - "status": "modified" - }, - { - "additions": 113, - "deletions": 31, - "patch_excerpt": "@@ -765,6 +765,8 @@ pub(crate) struct ChatWidget {\n suppress_session_configured_redraw: bool,\n // User messages queued while a turn is in progress\n queued_user_messages: VecDeque,\n+ // User messages that tried to steer a non-regular turn and must be retried first.\n+ rejected_steers_queue: VecDeque,\n // Steers already submitted to core but not yet committed into history.\n //\n // The bottom pane shows these above queued drafts until core records the\n@@ -925,9 +927,11 @@ impl ThreadComposerState {\n pub(crate) struct ThreadInputState {\n composer: Option,\n pending_steers: VecDeque,\n+ rejected_steers_queue: VecDeque,\n queued_user_messages: VecDeque,\n current_collaboration_mode: CollaborationMode,\n active_collaboration_mask: Option,\n+ ...", - "path": "codex-rs/tui/src/chatwidget.rs", - "status": "modified" - }, - { - "additions": 21, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,21 @@\n+---\n+source: tui/src/chatwidget/tests.rs\n+expression: term.backend().vt100().screen().contents()\n+---\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\u2022 Working (0s \u2022 esc to interrupt)\n+\n+\u2022 Messages to be submitted at end of turn\n+ \u21b3 Steer submitted while /compact was running.\n+\n+\u203a Ask Codex to do anything\n+\n+ ? for shortcuts 100% context left", - "path": "codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__compact_queues_user_messages_snapshot.snap", - "status": "added" - }, - { - "additions": 3, - "deletions": 3, - "patch_excerpt": "@@ -11,11 +11,11 @@ expression: term.backend().vt100().screen().contents()\n \n \n \n+\n \u2022 Working (0s \u2022 esc to interrupt)\n \n-\u2022 Queued follow-up messages\n- \u21b3 Queued while /review is running.\n- \u2325 + \u2191 edit last queued message\n+\u2022 Messages to be submitted at end of turn\n+ \u21b3 Steer submitted while /review was running.\n \n \u203a Ask Codex to do anything", - "path": "codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__review_queues_user_messages_snapshot.snap", - "status": "modified" - }, - { - "additions": 334, - "deletions": 9, - "patch_excerpt": "@@ -89,6 +89,7 @@ use codex_protocol::protocol::ItemCompletedEvent;\n use codex_protocol::protocol::McpStartupCompleteEvent;\n use codex_protocol::protocol::McpStartupStatus;\n use codex_protocol::protocol::McpStartupUpdateEvent;\n+use codex_protocol::protocol::NonSteerableTurnKind;\n use codex_protocol::protocol::Op;\n use codex_protocol::protocol::PatchApplyBeginEvent;\n use codex_protocol::protocol::PatchApplyEndEvent;\n@@ -1536,6 +1537,131 @@ async fn entered_review_mode_defaults_to_current_changes_banner() {\n assert!(chat.is_review_mode);\n }\n \n+#[tokio::test]\n+async fn steer_rejection_queues_review_follow_up_before_existing_queued_messages() {\n+ let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None).await;\n+ chat.thread_id = Some(ThreadId::new());\n+ chat.handle_codex_event(Event {\n+ id: \"turn-start\".into(),\n+ msg: EventMsg::TurnStarted(TurnStartedEvent {...", - "path": "codex-rs/tui/src/chatwidget/tests.rs", - "status": "modified" - }, - { - "additions": 61, - "deletions": 7, - "patch_excerpt": "@@ -48,7 +48,9 @@ use crate::update_action::UpdateAction;\n use crate::version::CODEX_CLI_VERSION;\n use codex_ansi_escape::ansi_escape_line;\n use codex_app_server_client::AppServerRequestHandle;\n+use codex_app_server_client::TypedRequestError;\n use codex_app_server_protocol::ClientRequest;\n+use codex_app_server_protocol::CodexErrorInfo as AppServerCodexErrorInfo;\n use codex_app_server_protocol::ConfigLayerSource;\n use codex_app_server_protocol::ListMcpServerStatusParams;\n use codex_app_server_protocol::ListMcpServerStatusResponse;\n@@ -63,6 +65,7 @@ use codex_app_server_protocol::ServerRequest;\n use codex_app_server_protocol::SkillsListResponse;\n use codex_app_server_protocol::ThreadRollbackResponse;\n use codex_app_server_protocol::Turn;\n+use codex_app_server_protocol::TurnError as AppServerTurnError;\n use codex_app_server_protocol::TurnStatus;\n use codex_core::config::Config;\n use codex_c...", - "path": "codex-rs/tui_app_server/src/app.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -1,6 +1,7 @@\n use codex_app_server_client::AppServerClient;\n use codex_app_server_client::AppServerEvent;\n use codex_app_server_client::AppServerRequestHandle;\n+use codex_app_server_client::TypedRequestError;\n use codex_app_server_protocol::Account;\n use codex_app_server_protocol::AuthMode;\n use codex_app_server_protocol::ClientRequest;\n@@ -429,7 +430,7 @@ impl AppServerSession {\n thread_id: ThreadId,\n turn_id: String,\n items: Vec,\n- ) -> Result {\n+ ) -> std::result::Result {\n let request_id = self.next_request_id();\n self.client\n .request_typed(ClientRequest::TurnSteer {\n@@ -441,7 +442,6 @@ impl AppServerSession {\n },\n })\n .await\n- .wrap_err(\"turn/steer failed in app-server TUI\")\n ...", - "path": "codex-rs/tui_app_server/src/app_server_session.rs", - "status": "modified" - }, - { - "additions": 19, - "deletions": 4, - "patch_excerpt": "@@ -821,8 +821,10 @@ impl BottomPane {\n &mut self,\n queued: Vec,\n pending_steers: Vec,\n+ rejected_steers: Vec,\n ) {\n self.pending_input_preview.pending_steers = pending_steers;\n+ self.pending_input_preview.rejected_steers = rejected_steers;\n self.pending_input_preview.queued_messages = queued;\n self.request_redraw();\n }\n@@ -1143,7 +1145,8 @@ impl BottomPane {\n }\n let has_pending_thread_approvals = !self.pending_thread_approvals.is_empty();\n let has_pending_input = !self.pending_input_preview.queued_messages.is_empty()\n- || !self.pending_input_preview.pending_steers.is_empty();\n+ || !self.pending_input_preview.pending_steers.is_empty()\n+ || !self.pending_input_preview.rejected_steers.is_empty();\n let has_...", - "path": "codex-rs/tui_app_server/src/bottom_pane/mod.rs", - "status": "modified" - }, - { - "additions": 40, - "deletions": 9, - "patch_excerpt": "@@ -10,17 +10,19 @@ use crate::render::renderable::Renderable;\n use crate::wrapping::RtOptions;\n use crate::wrapping::adaptive_wrap_lines;\n \n-/// Widget that displays pending steers plus user messages queued while a turn is in progress.\n+/// Widget that displays pending steers plus follow-up messages held while a turn is in progress.\n ///\n-/// The widget renders pending steers first, then queued user messages, as two\n-/// labeled sections. Pending steers explain that they will be submitted after\n-/// the next tool/result boundary unless the user presses Esc to interrupt and\n-/// send them immediately. The edit hint at the bottom only appears when there\n-/// are actual queued user messages to pop back into the composer. Because some\n-/// terminals intercept certain modifier-key combinations, the displayed\n-/// binding is configurable via [`set_edit_binding`](Self::set_edit_binding).\n+/// ...", - "path": "codex-rs/tui_app_server/src/bottom_pane/pending_input_preview.rs", - "status": "modified" - }, - { - "additions": 12, - "deletions": 5, - "patch_excerpt": "@@ -3,13 +3,16 @@ source: tui_app_server/src/bottom_pane/pending_input_preview.rs\n expression: \"format!(\\\"{buf:?}\\\")\"\n ---\n Buffer {\n- area: Rect { x: 0, y: 0, width: 52, height: 8 },\n+ area: Rect { x: 0, y: 0, width: 52, height: 11 },\n content: [\n \"\u2022 Messages to be submitted after next tool call \",\n \" (press esc to interrupt and send immediately) \",\n \" \u21b3 Please continue. \",\n \" \u21b3 Check the last command output. \",\n \" \",\n+ \"\u2022 Messages to be submitted at end of turn \",\n+ \" \u21b3 Rejected steer that will be retried. \",\n+ \" \",\n \"\u2022 Queued follow-up messages \",\n \" \u21b3 Queued follow-up question ...", - "path": "codex-rs/tui_app_server/src/bottom_pane/snapshots/codex_tui_app_server__bottom_pane__pending_input_preview__tests__render_pending_steers_above_queued_messages.snap", - "status": "modified" - }, - { - "additions": 124, - "deletions": 32, - "patch_excerpt": "@@ -807,6 +807,8 @@ pub(crate) struct ChatWidget {\n suppress_initial_user_message_submit: bool,\n // User messages queued while a turn is in progress\n queued_user_messages: VecDeque,\n+ // User messages that tried to steer a non-regular turn and must be retried first.\n+ rejected_steers_queue: VecDeque,\n // Steers already submitted to core but not yet committed into history.\n //\n // The bottom pane shows these above queued drafts until core records the\n@@ -958,9 +960,11 @@ impl ThreadComposerState {\n pub(crate) struct ThreadInputState {\n composer: Option,\n pending_steers: VecDeque,\n+ rejected_steers_queue: VecDeque,\n queued_user_messages: VecDeque,\n current_collaboration_mode: CollaborationMode,\n active_collaboration_mask: Option,\n...", - "path": "codex-rs/tui_app_server/src/chatwidget.rs", - "status": "modified" - }, - { - "additions": 21, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,21 @@\n+---\n+source: tui_app_server/src/chatwidget/tests.rs\n+expression: term.backend().vt100().screen().contents()\n+---\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\u2022 Working (0s \u2022 esc to interrupt)\n+\n+\u2022 Messages to be submitted at end of turn\n+ \u21b3 Steer submitted while /compact was running.\n+\n+\u203a Ask Codex to do anything\n+\n+ ? for shortcuts 100% context left", - "path": "codex-rs/tui_app_server/src/chatwidget/snapshots/codex_tui_app_server__chatwidget__tests__compact_queues_user_messages_snapshot.snap", - "status": "added" - }, - { - "additions": 3, - "deletions": 3, - "patch_excerpt": "@@ -11,11 +11,11 @@ expression: term.backend().vt100().screen().contents()\n \n \n \n+\n \u2022 Working (0s \u2022 esc to interrupt)\n \n-\u2022 Queued follow-up messages\n- \u21b3 Queued while /review is running.\n- \u2325 + \u2191 edit last queued message\n+\u2022 Messages to be submitted at end of turn\n+ \u21b3 Steer submitted while /review was running.\n \n \u203a Ask Codex to do anything", - "path": "codex-rs/tui_app_server/src/chatwidget/snapshots/codex_tui_app_server__chatwidget__tests__review_queues_user_messages_snapshot.snap", - "status": "modified" - }, - { - "additions": 344, - "deletions": 9, - "patch_excerpt": "@@ -112,6 +112,7 @@ use codex_protocol::protocol::ItemCompletedEvent;\n use codex_protocol::protocol::McpStartupCompleteEvent;\n use codex_protocol::protocol::McpStartupStatus;\n use codex_protocol::protocol::McpStartupUpdateEvent;\n+use codex_protocol::protocol::NonSteerableTurnKind;\n use codex_protocol::protocol::Op;\n use codex_protocol::protocol::PatchApplyBeginEvent;\n use codex_protocol::protocol::PatchApplyEndEvent;\n@@ -1560,6 +1561,131 @@ async fn entered_review_mode_defaults_to_current_changes_banner() {\n assert!(chat.is_review_mode);\n }\n \n+#[tokio::test]\n+async fn steer_rejection_queues_review_follow_up_before_existing_queued_messages() {\n+ let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None).await;\n+ chat.thread_id = Some(ThreadId::new());\n+ chat.handle_codex_event(Event {\n+ id: \"turn-start\".into(),\n+ msg: EventMsg::TurnStarted(TurnStartedEvent...", - "path": "codex-rs/tui_app_server/src/chatwidget/tests.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Discovered via hourly merged-PR sync: https://github.com/openai/codex/pull/15259" - ], - "primary_pr": { - "body": "## Summary\r\n- queue input after the user submits `/compact` until that manual compact turn ends\r\n- mirror the same behavior in the app-server TUI\r\n- add regressions for input queued before compact starts and while it is running", - "labels": [], - "merged_at": "2026-03-23T17:19:45Z", - "number": 15259, - "state": "merged", - "title": "tui: queue follow-ups during manual /compact", - "url": "https://github.com/openai/codex/pull/15259" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15262.json b/artifacts/github/bundles/openai-codex-pr-15262.json deleted file mode 100644 index dd60156..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15262.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "charley-oai", - "committed_at": "2026-03-20T02:00:04Z", - "message": "Add guardian follow-up reminder", - "sha": "0c5c63fd5301f54de042b4635cf5d12690ff90e6", - "url": "https://github.com/openai/codex/commit/0c5c63fd5301f54de042b4635cf5d12690ff90e6" - }, - { - "author": "charley-oai", - "committed_at": "2026-03-20T02:58:18Z", - "message": "Update guardian followup reminder wording", - "sha": "69569008a3ea3f03b981e2e6c92f9a1b85525a75", - "url": "https://github.com/openai/codex/commit/69569008a3ea3f03b981e2e6c92f9a1b85525a75" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "GUARDIAN_REVIEWER_NAME", - "GUARDIAN_INTERRUPT_DRAIN_TIMEOUT", - "GUARDIAN_FOLLOWUP_REVIEW_REMINDER", - "APPROVAL", - "REQUEST", - "END", - "JSON", - "TRANSCRIPT", - "START" - ], - "files": [ - { - "additions": 38, - "deletions": 1, - "patch_excerpt": "@@ -2,11 +2,15 @@ use std::collections::HashMap;\n use std::future::Future;\n use std::path::PathBuf;\n use std::sync::Arc;\n+use std::sync::atomic::AtomicBool;\n+use std::sync::atomic::Ordering;\n use std::time::Duration;\n \n use anyhow::anyhow;\n use codex_protocol::config_types::Personality;\n use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;\n+use codex_protocol::models::DeveloperInstructions;\n+use codex_protocol::models::ResponseItem;\n use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;\n use codex_protocol::protocol::AskForApproval;\n use codex_protocol::protocol::EventMsg;\n@@ -40,6 +44,12 @@ use super::GUARDIAN_REVIEWER_NAME;\n use super::prompt::guardian_policy_prompt;\n \n const GUARDIAN_INTERRUPT_DRAIN_TIMEOUT: Duration = Duration::from_secs(5);\n+const GUARDIAN_FOLLOWUP_REVIEW_REMINDER: &str = concat!(\n+ \"Use prior reviews as context, no...", - "path": "codex-rs/core/src/guardian/review_session.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 1, - "patch_excerpt": "@@ -49,7 +49,8 @@ Scenario: Guardian follow-up review request layout\n [15] >>> APPROVAL REQUEST END\\n\n [16] You may use read-only tool checks to gather any additional context you need to make a high-confidence determination.\\n\\nYour final message must be strict JSON with this exact schema:\\n{\\n \"risk_level\": \"low\" | \"medium\" | \"high\",\\n \"risk_score\": 0-100,\\n \"rationale\": string,\\n \"evidence\": [{\"message\": string, \"why\": string}]\\n}\\n\n 04:message/assistant:{\"risk_level\":\"low\",\"risk_score\":5,\"rationale\":\"first guardian rationale from the prior review\",\"evidence\":[]}\n-05:message/user[16]:\n+05:message/developer:Use prior reviews as context, not binding precedent. Follow the Workspace Policy. If the user explicitly approves a previously rejected action after being informed of the concrete risks, treat the action as authorized and assign low/medium risk.\n+06:message/user[16]:\n ...", - "path": "codex-rs/core/src/guardian/snapshots/codex_core__guardian__tests__guardian_followup_review_request_layout.snap", - "status": "modified" - }, - { - "additions": 10, - "deletions": 0, - "patch_excerpt": "@@ -677,6 +677,16 @@ async fn guardian_reuses_prompt_cache_key_and_appends_prior_reviews() -> anyhow:\n first_body[\"prompt_cache_key\"],\n second_body[\"prompt_cache_key\"]\n );\n+ assert!(\n+ second_body.to_string().contains(concat!(\n+ \"Use prior reviews as context, not binding precedent. \",\n+ \"Follow the Workspace Policy. \",\n+ \"If the user explicitly approves a previously rejected action after being \",\n+ \"informed of the concrete risks, treat the action as authorized and assign \",\n+ \"low/medium risk.\"\n+ )),\n+ \"follow-up guardian request should include the follow-up reminder\"\n+ );\n assert!(\n second_body.to_string().contains(first_rationale),\n \"guardian session should append earlier reviews into the follow-up request\"", - "path": "codex-rs/core/src/guardian/tests.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "## Summary\r\n- add a short guardian follow-up developer reminder before reused reviews\r\n- cache prior-review state on the guardian session instead of rescanning full history on each request\r\n- update guardian follow-up coverage and snapshot expectations", - "labels": [], - "merged_at": "2026-03-20T05:35:52Z", - "number": 15262, - "state": "merged", - "title": "Add guardian follow-up reminder", - "url": "https://github.com/openai/codex/pull/15262" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15263.json b/artifacts/github/bundles/openai-codex-pr-15263.json deleted file mode 100644 index 305b493..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15263.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "xl-openai", - "committed_at": "2026-03-20T01:34:12Z", - "message": "fix: Distinguish missing and empty plugin products", - "sha": "93e87d1e8182afdac575d679564be6d677d4a42f", - "url": "https://github.com/openai/codex/commit/93e87d1e8182afdac575d679564be6d677d4a42f" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "CONFIG_TOML_FILE", - "TODO" - ], - "files": [ - { - "additions": 11, - "deletions": 7, - "patch_excerpt": "@@ -500,11 +500,14 @@ impl PluginsManager {\n *stored_client = Some(analytics_events_client);\n }\n \n- fn restriction_product_matches(&self, products: &[Product]) -> bool {\n- products.is_empty()\n- || self\n+ fn restriction_product_matches(&self, products: Option<&[Product]>) -> bool {\n+ match products {\n+ None => true,\n+ Some([]) => false,\n+ Some(products) => self\n .restriction_product\n- .is_some_and(|product| product.matches_product_restriction(products))\n+ .is_some_and(|product| product.matches_product_restriction(products)),\n+ }\n }\n \n pub fn plugins_for_config(&self, config: &Config) -> PluginLoadOutcome {\n@@ -830,7 +833,8 @@ impl PluginsManager {\n .get(&plugin_key)\n .map(|plugin| plugin.enabled);\n let installe...", - "path": "codex-rs/core/src/plugins/manager.rs", - "status": "modified" - }, - { - "additions": 80, - "deletions": 6, - "patch_excerpt": "@@ -976,7 +976,7 @@ enabled = false\n policy: MarketplacePluginPolicy {\n installation: MarketplacePluginInstallPolicy::Available,\n authentication: MarketplacePluginAuthPolicy::OnInstall,\n- products: vec![],\n+ products: None,\n },\n interface: None,\n installed: true,\n@@ -992,7 +992,7 @@ enabled = false\n policy: MarketplacePluginPolicy {\n installation: MarketplacePluginInstallPolicy::Available,\n authentication: MarketplacePluginAuthPolicy::OnInstall,\n- products: vec![],\n+ products: None,\n },\n interface: None,\n installed: true,\n@@ -1043,6 +1043,80 @@ e...", - "path": "codex-rs/core/src/plugins/manager_tests.rs", - "status": "modified" - }, - { - "additions": 9, - "deletions": 6, - "patch_excerpt": "@@ -57,7 +57,7 @@ pub struct MarketplacePluginPolicy {\n pub authentication: MarketplacePluginAuthPolicy,\n // TODO: Surface or enforce product gating at the Codex/plugin consumer boundary instead of\n // only carrying it through core marketplace metadata.\n- pub products: Vec,\n+ pub products: Option>,\n }\n \n #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]\n@@ -169,9 +169,13 @@ pub fn resolve_marketplace_plugin(\n ..\n } = plugin;\n let install_policy = policy.installation;\n- let product_allowed = policy.products.is_empty()\n- || restriction_product\n- .is_some_and(|product| product.matches_product_restriction(&policy.products));\n+ let product_allowed = match policy.products.as_deref() {\n+ None => true,\n+ Some([]) => false,\n+ Some(products) => {\n+ restriction_product.is...", - "path": "codex-rs/core/src/plugins/marketplace.rs", - "status": "modified" - }, - { - "additions": 83, - "deletions": 10, - "patch_excerpt": "@@ -150,7 +150,7 @@ fn list_marketplaces_returns_home_and_repo_marketplaces() {\n policy: MarketplacePluginPolicy {\n installation: MarketplacePluginInstallPolicy::Available,\n authentication: MarketplacePluginAuthPolicy::OnInstall,\n- products: vec![],\n+ products: None,\n },\n interface: None,\n },\n@@ -162,7 +162,7 @@ fn list_marketplaces_returns_home_and_repo_marketplaces() {\n policy: MarketplacePluginPolicy {\n installation: MarketplacePluginInstallPolicy::Available,\n authentication: MarketplacePluginAuthPolicy::OnInstall,\n- products: vec![],\n+ products: None,\n ...", - "path": "codex-rs/core/src/plugins/marketplace_tests.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "Treat [] as no product allowed, empty as all products allowed.\r\n", - "labels": [], - "merged_at": "2026-03-20T03:02:40Z", - "number": 15263, - "state": "merged", - "title": "fix: Distinguish missing and empty plugin products", - "url": "https://github.com/openai/codex/pull/15263" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15264.json b/artifacts/github/bundles/openai-codex-pr-15264.json deleted file mode 100644 index a0a204f..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15264.json +++ /dev/null @@ -1,104 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "xl-openai", - "committed_at": "2026-03-20T02:05:51Z", - "message": "feat: Add One-Time Startup Remote Plugin Sync", - "sha": "56c33c2a7e363341b83f0a45f9bef8d9068103af", - "url": "https://github.com/openai/codex/commit/56c33c2a7e363341b83f0a45f9bef8d9068103af" - }, - { - "author": "xl-openai", - "committed_at": "2026-03-20T03:05:05Z", - "message": "Fix tests.", - "sha": "0754d11aa8df020e2efa6cc7fe186da5ece3f2db", - "url": "https://github.com/openai/codex/commit/0754d11aa8df020e2efa6cc7fe186da5ece3f2db" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "TODO", - "DEFAULT_TIMEOUT", - "TEST_CURATED_PLUGIN_SHA", - "STARTUP_REMOTE_PLUGIN_SYNC_MARKER_FILE", - "GET", - "JSONRPCR", - "DEFAULT_PLUGIN_VERSION", - "DEFAULT_APP_CONFIG_FILE", - "OPENAI_CURATED_MARKETPLACE_NAME", - "CURATED_REPO_SYNC_STARTED", - "MAX_CAPABILITY_SUMMARY_DESCRIPTION_LEN", - "FEATURED_PLUGIN_IDS_CACHE_TTL", - "CONFIG_TOML_FILE", - "STARTUP_REMOTE_PLUGIN_SYNC_PREREQUISITE_TIMEOUT" - ], - "files": [ - { - "additions": 4, - "deletions": 4, - "patch_excerpt": "@@ -425,16 +425,16 @@ impl CodexMessageProcessor {\n self.thread_manager.skills_manager().clear_cache();\n }\n \n- pub(crate) async fn maybe_start_curated_repo_sync_for_latest_config(&self) {\n+ pub(crate) async fn maybe_start_plugin_startup_tasks_for_latest_config(&self) {\n match self.load_latest_config(/*fallback_cwd*/ None).await {\n Ok(config) => self\n .thread_manager\n .plugins_manager()\n- .maybe_start_curated_repo_sync_for_config(\n+ .maybe_start_plugin_startup_tasks_for_config(\n &config,\n self.thread_manager.auth_manager(),\n ),\n- Err(err) => warn!(\"failed to load latest config for curated plugin sync: {err:?}\"),\n+ Err(err) => warn!(\"failed to load latest config for plugin startup tasks: {err:?}\"),\n }\n ...", - "path": "codex-rs/app-server/src/codex_message_processor.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 3, - "patch_excerpt": "@@ -246,7 +246,7 @@ impl MessageProcessor {\n // TODO(xl): Move into PluginManager once this no longer depends on config feature gating.\n thread_manager\n .plugins_manager()\n- .maybe_start_curated_repo_sync_for_config(&config, auth_manager.clone());\n+ .maybe_start_plugin_startup_tasks_for_config(&config, auth_manager.clone());\n let config_api = ConfigApi::new(\n config.codex_home.clone(),\n cli_overrides,\n@@ -790,7 +790,7 @@ impl MessageProcessor {\n Ok(response) => {\n self.codex_message_processor.clear_plugin_related_caches();\n self.codex_message_processor\n- .maybe_start_curated_repo_sync_for_latest_config()\n+ .maybe_start_plugin_startup_tasks_for_latest_config()\n .await;\n self.outgoing.send_res...", - "path": "codex-rs/app-server/src/message_processor.rs", - "status": "modified" - }, - { - "additions": 112, - "deletions": 5, - "patch_excerpt": "@@ -28,6 +28,7 @@ use wiremock::matchers::path;\n \n const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);\n const TEST_CURATED_PLUGIN_SHA: &str = \"0123456789abcdef0123456789abcdef01234567\";\n+const STARTUP_REMOTE_PLUGIN_SYNC_MARKER_FILE: &str = \".tmp/app-server-remote-plugin-sync-v1\";\n \n fn write_plugins_enabled_config(codex_home: &std::path::Path) -> std::io::Result<()> {\n std::fs::write(\n@@ -755,6 +756,91 @@ async fn plugin_list_force_remote_sync_reconciles_curated_plugin_state() -> Resu\n Ok(())\n }\n \n+#[tokio::test]\n+async fn app_server_startup_remote_plugin_sync_runs_once() -> Result<()> {\n+ let codex_home = TempDir::new()?;\n+ let server = MockServer::start().await;\n+ write_plugin_sync_config(codex_home.path(), &format!(\"{}/backend-api/\", server.uri()))?;\n+ write_chatgpt_auth(\n+ codex_home.path(),\n+ ChatGptAuthFixture::new(\"chatgpt-token\")\n+ ...", - "path": "codex-rs/app-server/tests/suite/v2/plugin_list.rs", - "status": "modified" - }, - { - "additions": 12, - "deletions": 4, - "patch_excerpt": "@@ -18,6 +18,7 @@ use super::remote::enable_remote_plugin;\n use super::remote::fetch_remote_featured_plugin_ids;\n use super::remote::fetch_remote_plugin_status;\n use super::remote::uninstall_remote_plugin;\n+use super::startup_sync::start_startup_remote_plugin_sync_once;\n use super::store::DEFAULT_PLUGIN_VERSION;\n use super::store::PluginId;\n use super::store::PluginIdError;\n@@ -58,7 +59,6 @@ use std::sync::Arc;\n use std::sync::RwLock;\n use std::sync::atomic::AtomicBool;\n use std::sync::atomic::Ordering;\n-use std::time::Duration;\n use std::time::Instant;\n use toml_edit::value;\n use tracing::info;\n@@ -70,7 +70,8 @@ const DEFAULT_APP_CONFIG_FILE: &str = \".app.json\";\n pub const OPENAI_CURATED_MARKETPLACE_NAME: &str = \"openai-curated\";\n static CURATED_REPO_SYNC_STARTED: AtomicBool = AtomicBool::new(false);\n const MAX_CAPABILITY_SUMMARY_DESCRIPTION_LEN: usize = 1024;\n-const FEATURED_PLUGIN_IDS...", - "path": "codex-rs/core/src/plugins/manager.rs", - "status": "modified" - }, - { - "additions": 101, - "deletions": 1, - "patch_excerpt": "@@ -1177,7 +1177,7 @@ plugins = false\n \n let config = load_config(tmp.path(), tmp.path()).await;\n let outcome = PluginsManager::new(tmp.path().to_path_buf())\n- .sync_plugins_from_remote(&config, None)\n+ .sync_plugins_from_remote(&config, None, /*additive_only*/ false)\n .await\n .unwrap();\n \n@@ -1533,6 +1533,7 @@ enabled = true\n .sync_plugins_from_remote(\n &config,\n Some(&CodexAuth::create_dummy_chatgpt_auth_for_testing()),\n+ /*additive_only*/ false,\n )\n .await\n .unwrap();\n@@ -1593,6 +1594,102 @@ enabled = true\n );\n }\n \n+#[tokio::test]\n+async fn sync_plugins_from_remote_additive_only_keeps_existing_plugins() {\n+ let tmp = tempfile::tempdir().unwrap();\n+ let curated_root = curated_plugins_repo_path(tmp.path());\n+ write_openai_curated_marketplace(&curated_root, &[\"linear\", \"g...", - "path": "codex-rs/core/src/plugins/manager_tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -6,6 +6,7 @@ mod manifest;\n mod marketplace;\n mod remote;\n mod render;\n+mod startup_sync;\n mod store;\n #[cfg(test)]\n pub(crate) mod test_support;", - "path": "codex-rs/core/src/plugins/mod.rs", - "status": "modified" - }, - { - "additions": 195, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,195 @@\n+use std::path::Path;\n+use std::path::PathBuf;\n+use std::sync::Arc;\n+use std::time::Duration;\n+\n+use tracing::info;\n+use tracing::warn;\n+\n+use crate::AuthManager;\n+use crate::config::Config;\n+\n+use super::PluginsManager;\n+\n+const STARTUP_REMOTE_PLUGIN_SYNC_MARKER_FILE: &str = \".tmp/app-server-remote-plugin-sync-v1\";\n+const STARTUP_REMOTE_PLUGIN_SYNC_PREREQUISITE_TIMEOUT: Duration = Duration::from_secs(5);\n+\n+pub(super) fn start_startup_remote_plugin_sync_once(\n+ manager: Arc,\n+ codex_home: PathBuf,\n+ config: Config,\n+ auth_manager: Arc,\n+) {\n+ let marker_path = startup_remote_plugin_sync_marker_path(codex_home.as_path());\n+ if marker_path.is_file() {\n+ return;\n+ }\n+\n+ tokio::spawn(async move {\n+ if marker_path.is_file() {\n+ return;\n+ }\n+\n+ if !wait_for_startup_remote_plugin_sync_...", - "path": "codex-rs/core/src/plugins/startup_sync.rs", - "status": "added" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints." - ], - "primary_pr": { - "body": "For early users who have already enabled apps, we should enable plugins as part of the initial setup.", - "labels": [], - "merged_at": "2026-03-20T05:01:39Z", - "number": 15264, - "state": "merged", - "title": "feat: Add One-Time Startup Remote Plugin Sync", - "url": "https://github.com/openai/codex/pull/15264" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15275.json b/artifacts/github/bundles/openai-codex-pr-15275.json deleted file mode 100644 index 7515f88..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15275.json +++ /dev/null @@ -1,110 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "xl-openai", - "committed_at": "2026-03-20T03:44:42Z", - "message": "refactor: move curated plugin sync into startup_sync", - "sha": "7a7d7a3ac5be9bbaf3f50a4030fabb472c6e61f1", - "url": "https://github.com/openai/codex/commit/7a7d7a3ac5be9bbaf3f50a4030fabb472c6e61f1" - }, - { - "author": "xl-openai", - "committed_at": "2026-03-20T03:45:11Z", - "message": "feat: prefer git for curated plugin sync", - "sha": "fa898ba62b3e013d37d7aa8f08742a91e017fba1", - "url": "https://github.com/openai/codex/commit/fa898ba62b3e013d37d7aa8f08742a91e017fba1" - }, - { - "author": "xl-openai", - "committed_at": "2026-03-20T04:49:18Z", - "message": "fallback in all errors", - "sha": "5af451390a8df947fcd672b1540ed4bb6ed856f1", - "url": "https://github.com/openai/codex/commit/5af451390a8df947fcd672b1540ed4bb6ed856f1" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "GITHUB_API_BASE_URL", - "GITHUB_API_ACCEPT_HEADER", - "GITHUB_API_VERSION_HEADER", - "OPENAI_PLUGINS_OWNER", - "OPENAI_PLUGINS_REPO", - "CURATED_PLUGINS_RELATIVE_DIR", - "CURATED_PLUGINS_SHA_FILE", - "CURATED_PLUGINS_HTTP_TIMEOUT", - "HEAD", - "GET", - "CURATED_PLUGINS_GIT_TIMEOUT", - "STARTUP_REMOTE_PLUGIN_SYNC_MARKER_FILE", - "STARTUP_REMOTE_PLUGIN_SYNC_PREREQUISITE_TIMEOUT", - "HTTP", - "GIT_OPTIONAL_LOCKS", - "--depth", - "CONFIG_TOML_FILE", - "TEST_CURATED_PLUGIN_SHA", - "EOF" - ], - "files": [ - { - "additions": 0, - "deletions": 356, - "patch_excerpt": "@@ -1,356 +0,0 @@\n-use crate::default_client::build_reqwest_client;\n-use reqwest::Client;\n-use serde::Deserialize;\n-use std::fs;\n-use std::io::Cursor;\n-#[cfg(unix)]\n-use std::os::unix::fs::PermissionsExt;\n-use std::path::Component;\n-use std::path::Path;\n-use std::path::PathBuf;\n-use std::time::Duration;\n-use zip::ZipArchive;\n-\n-const GITHUB_API_BASE_URL: &str = \"https://api.github.com\";\n-const GITHUB_API_ACCEPT_HEADER: &str = \"application/vnd.github+json\";\n-const GITHUB_API_VERSION_HEADER: &str = \"2022-11-28\";\n-const OPENAI_PLUGINS_OWNER: &str = \"openai\";\n-const OPENAI_PLUGINS_REPO: &str = \"plugins\";\n-const CURATED_PLUGINS_RELATIVE_DIR: &str = \".tmp/plugins\";\n-const CURATED_PLUGINS_SHA_FILE: &str = \".tmp/plugins.sha\";\n-const CURATED_PLUGINS_HTTP_TIMEOUT: Duration = Duration::from_secs(30);\n-\n-#[derive(Debug, Deserialize)]\n-struct GitHubRepositorySummary {\n- default_branch: String,\n-}\n...", - "path": "codex-rs/core/src/plugins/curated_repo.rs", - "status": "removed" - }, - { - "additions": 0, - "deletions": 159, - "patch_excerpt": "@@ -1,159 +0,0 @@\n-use super::*;\n-use pretty_assertions::assert_eq;\n-use std::io::Write;\n-use tempfile::tempdir;\n-use wiremock::Mock;\n-use wiremock::MockServer;\n-use wiremock::ResponseTemplate;\n-use wiremock::matchers::method;\n-use wiremock::matchers::path;\n-use zip::ZipWriter;\n-use zip::write::SimpleFileOptions;\n-\n-#[test]\n-fn curated_plugins_repo_path_uses_codex_home_tmp_dir() {\n- let tmp = tempdir().expect(\"tempdir\");\n- assert_eq!(\n- curated_plugins_repo_path(tmp.path()),\n- tmp.path().join(\".tmp/plugins\")\n- );\n-}\n-\n-#[test]\n-fn read_curated_plugins_sha_reads_trimmed_sha_file() {\n- let tmp = tempdir().expect(\"tempdir\");\n- fs::create_dir_all(tmp.path().join(\".tmp\")).expect(\"create tmp\");\n- fs::write(tmp.path().join(\".tmp/plugins.sha\"), \"abc123\\n\").expect(\"write sha\");\n-\n- assert_eq!(\n- read_curated_plugins_sha(tmp.path()).as_deref(),\n- So...", - "path": "codex-rs/core/src/plugins/curated_repo_tests.rs", - "status": "removed" - }, - { - "additions": 5, - "deletions": 0, - "patch_excerpt": "@@ -60,6 +60,7 @@ use std::sync::RwLock;\n use std::sync::atomic::AtomicBool;\n use std::sync::atomic::Ordering;\n use std::time::Instant;\n+use tokio::sync::Mutex;\n use toml_edit::value;\n use tracing::info;\n use tracing::warn;\n@@ -463,6 +464,7 @@ pub struct PluginsManager {\n store: PluginStore,\n featured_plugin_ids_cache: RwLock>,\n cached_enabled_outcome: RwLock>,\n+ remote_sync_lock: Mutex<()>,\n restriction_product: Option,\n analytics_events_client: RwLock>,\n }\n@@ -488,6 +490,7 @@ impl PluginsManager {\n store: PluginStore::new(codex_home),\n featured_plugin_ids_cache: RwLock::new(None),\n cached_enabled_outcome: RwLock::new(None),\n+ remote_sync_lock: Mutex::new(()),\n restriction_product,\n analytics_events...", - "path": "codex-rs/core/src/plugins/manager.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 4, - "patch_excerpt": "@@ -1,4 +1,3 @@\n-mod curated_repo;\n mod discoverable;\n mod injection;\n mod manager;\n@@ -12,9 +11,6 @@ mod store;\n pub(crate) mod test_support;\n mod toggles;\n \n-pub(crate) use curated_repo::curated_plugins_repo_path;\n-pub(crate) use curated_repo::read_curated_plugins_sha;\n-pub(crate) use curated_repo::sync_openai_plugins_repo;\n pub(crate) use discoverable::list_tool_suggest_discoverable_plugins;\n pub(crate) use injection::build_plugin_injections;\n pub use manager::AppConnectorId;\n@@ -52,5 +48,8 @@ pub use remote::RemotePluginFetchError;\n pub use remote::fetch_remote_featured_plugin_ids;\n pub(crate) use render::render_explicit_plugin_instructions;\n pub(crate) use render::render_plugins_section;\n+pub(crate) use startup_sync::curated_plugins_repo_path;\n+pub(crate) use startup_sync::read_curated_plugins_sha;\n+pub(crate) use startup_sync::sync_openai_plugins_repo;\n pub use store::PluginId;\n pu...", - "path": "codex-rs/core/src/plugins/mod.rs", - "status": "modified" - }, - { - "additions": 552, - "deletions": 83, - "patch_excerpt": "@@ -1,19 +1,143 @@\n+use crate::default_client::build_reqwest_client;\n use std::path::Path;\n use std::path::PathBuf;\n+use std::process::Command;\n+use std::process::Output;\n+use std::process::Stdio;\n use std::sync::Arc;\n use std::time::Duration;\n \n+use reqwest::Client;\n+use serde::Deserialize;\n use tracing::info;\n use tracing::warn;\n+use zip::ZipArchive;\n \n use crate::AuthManager;\n use crate::config::Config;\n \n use super::PluginsManager;\n \n+const GITHUB_API_BASE_URL: &str = \"https://api.github.com\";\n+const GITHUB_API_ACCEPT_HEADER: &str = \"application/vnd.github+json\";\n+const GITHUB_API_VERSION_HEADER: &str = \"2022-11-28\";\n+const OPENAI_PLUGINS_OWNER: &str = \"openai\";\n+const OPENAI_PLUGINS_REPO: &str = \"plugins\";\n+const CURATED_PLUGINS_RELATIVE_DIR: &str = \".tmp/plugins\";\n+const CURATED_PLUGINS_SHA_FILE: &str = \".tmp/plugins.sha\";\n+const CURATED_PLUGINS_GIT_TIMEOUT: Duration = Duration::fr...", - "path": "codex-rs/core/src/plugins/startup_sync.rs", - "status": "modified" - }, - { - "additions": 383, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,383 @@\n+use super::*;\n+use crate::auth::CodexAuth;\n+use crate::config::CONFIG_TOML_FILE;\n+use crate::plugins::test_support::TEST_CURATED_PLUGIN_SHA;\n+use crate::plugins::test_support::write_curated_plugin_sha;\n+use crate::plugins::test_support::write_file;\n+use crate::plugins::test_support::write_openai_curated_marketplace;\n+use pretty_assertions::assert_eq;\n+use std::io::Write;\n+use tempfile::tempdir;\n+use wiremock::Mock;\n+use wiremock::MockServer;\n+use wiremock::ResponseTemplate;\n+use wiremock::matchers::header;\n+use wiremock::matchers::method;\n+use wiremock::matchers::path;\n+use zip::ZipWriter;\n+use zip::write::SimpleFileOptions;\n+\n+#[test]\n+fn curated_plugins_repo_path_uses_codex_home_tmp_dir() {\n+ let tmp = tempdir().expect(\"tempdir\");\n+ assert_eq!(\n+ curated_plugins_repo_path(tmp.path()),\n+ tmp.path().join(\".tmp/plugins\")\n+ );\n+}\n+\n+#[test]\n+fn rea...", - "path": "codex-rs/core/src/plugins/startup_sync_tests.rs", - "status": "added" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Backfilled from compare range rust-v0.116.0...rust-v0.117.0-alpha.5" - ], - "primary_pr": { - "body": "start with git clone, fallback to http.\r\n", - "labels": [], - "merged_at": "2026-03-20T07:06:24Z", - "number": 15275, - "state": "merged", - "title": "feat: prefer git for curated plugin sync ", - "url": "https://github.com/openai/codex/pull/15275" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15279.json b/artifacts/github/bundles/openai-codex-pr-15279.json deleted file mode 100644 index 565e3e5..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15279.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "canvrno-oai", - "committed_at": "2026-03-20T05:49:28Z", - "message": "Label plugins as plugins, and hide skills/apps for given plugin from", - "sha": "4ce939c9a6b40d55e57940ecf1590eb87bed344b", - "url": "https://github.com/openai/codex/commit/4ce939c9a6b40d55e57940ecf1590eb87bed344b" - }, - { - "author": "canvrno-oai", - "committed_at": "2026-03-21T06:26:12Z", - "message": "tui: keep plugin-backed app mentions visible in skill picker", - "sha": "bb0beefd7e3ef28d5e22c9e41e5b48d5e3db63ca", - "url": "https://github.com/openai/codex/commit/bb0beefd7e3ef28d5e22c9e41e5b48d5e3db63ca" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "SKILL", - "MAX_POPUP_ROWS" - ], - "files": [ - { - "additions": 107, - "deletions": 1, - "patch_excerpt": "@@ -3575,6 +3575,13 @@ impl ChatComposer {\n \n fn mention_items(&self) -> Vec {\n let mut mentions = Vec::new();\n+ let plugin_display_names: HashSet =\n+ self.plugins.as_ref().map_or_else(HashSet::new, |plugins| {\n+ plugins\n+ .iter()\n+ .map(|plugin| plugin.display_name.to_ascii_lowercase())\n+ .collect()\n+ });\n \n if let Some(skills) = self.skills.as_ref() {\n for skill in skills {\n@@ -3657,18 +3664,30 @@ impl ChatComposer {\n if !connector.is_accessible || !connector.is_enabled {\n continue;\n }\n+ let plugin_backed_connector = connector\n+ .plugin_display_names\n+ .iter()\n+ .any(|name| plugin_display_names.contains(&name.to_...", - "path": "codex-rs/tui/src/bottom_pane/chat_composer.rs", - "status": "modified" - }, - { - "additions": 31, - "deletions": 1, - "patch_excerpt": "@@ -70,7 +70,7 @@ impl FileSearchPopup {\n }\n \n self.display_query = query.to_string();\n- self.matches = matches;\n+ self.matches = matches.into_iter().take(MAX_POPUP_ROWS).collect();\n self.waiting = false;\n let len = self.matches.len();\n self.state.clamp_selection(len);\n@@ -152,3 +152,33 @@ impl WidgetRef for &FileSearchPopup {\n );\n }\n }\n+\n+#[cfg(test)]\n+mod tests {\n+ use super::*;\n+ use codex_file_search::MatchType;\n+ use pretty_assertions::assert_eq;\n+\n+ fn file_match(index: usize) -> FileMatch {\n+ FileMatch {\n+ score: index as u32,\n+ path: PathBuf::from(format!(\"src/file_{index:02}.rs\")),\n+ match_type: MatchType::File,\n+ root: PathBuf::from(\"/tmp/repo\"),\n+ indices: None,\n+ }\n+ }\n+\n+ #[test]\n+ fn set_matches_keeps_only_the_first_page...", - "path": "codex-rs/tui/src/bottom_pane/file_search_popup.rs", - "status": "modified" - }, - { - "additions": 41, - "deletions": 0, - "patch_excerpt": "@@ -180,6 +180,7 @@ impl SkillPopup {\n })\n });\n \n+ out.truncate(MAX_POPUP_ROWS);\n out\n }\n }\n@@ -229,3 +230,43 @@ fn skill_popup_hint_line() -> Line<'static> {\n \" to close\".into(),\n ])\n }\n+\n+#[cfg(test)]\n+mod tests {\n+ use super::*;\n+ use pretty_assertions::assert_eq;\n+\n+ fn mention_item(index: usize) -> MentionItem {\n+ MentionItem {\n+ display_name: format!(\"Mention {index:02}\"),\n+ description: Some(format!(\"Description {index:02}\")),\n+ insert_text: format!(\"$mention-{index:02}\"),\n+ search_terms: vec![format!(\"mention-{index:02}\")],\n+ path: Some(format!(\"skill://mention-{index:02}\")),\n+ category_tag: Some(\"[Skill]\".to_string()),\n+ sort_rank: 1,\n+ }\n+ }\n+\n+ #[test]\n+ fn filtered_mentions_are_capped_to_max_popup_rows() {\n+ ...", - "path": "codex-rs/tui/src/bottom_pane/skill_popup.rs", - "status": "modified" - }, - { - "additions": 107, - "deletions": 1, - "patch_excerpt": "@@ -3590,6 +3590,13 @@ impl ChatComposer {\n \n fn mention_items(&self) -> Vec {\n let mut mentions = Vec::new();\n+ let plugin_display_names: HashSet =\n+ self.plugins.as_ref().map_or_else(HashSet::new, |plugins| {\n+ plugins\n+ .iter()\n+ .map(|plugin| plugin.display_name.to_ascii_lowercase())\n+ .collect()\n+ });\n \n if let Some(skills) = self.skills.as_ref() {\n for skill in skills {\n@@ -3672,18 +3679,30 @@ impl ChatComposer {\n if !connector.is_accessible || !connector.is_enabled {\n continue;\n }\n+ let plugin_backed_connector = connector\n+ .plugin_display_names\n+ .iter()\n+ .any(|name| plugin_display_names.contains(&name.to_...", - "path": "codex-rs/tui_app_server/src/bottom_pane/chat_composer.rs", - "status": "modified" - }, - { - "additions": 31, - "deletions": 1, - "patch_excerpt": "@@ -70,7 +70,7 @@ impl FileSearchPopup {\n }\n \n self.display_query = query.to_string();\n- self.matches = matches;\n+ self.matches = matches.into_iter().take(MAX_POPUP_ROWS).collect();\n self.waiting = false;\n let len = self.matches.len();\n self.state.clamp_selection(len);\n@@ -152,3 +152,33 @@ impl WidgetRef for &FileSearchPopup {\n );\n }\n }\n+\n+#[cfg(test)]\n+mod tests {\n+ use super::*;\n+ use codex_file_search::MatchType;\n+ use pretty_assertions::assert_eq;\n+\n+ fn file_match(index: usize) -> FileMatch {\n+ FileMatch {\n+ score: index as u32,\n+ path: PathBuf::from(format!(\"src/file_{index:02}.rs\")),\n+ match_type: MatchType::File,\n+ root: PathBuf::from(\"/tmp/repo\"),\n+ indices: None,\n+ }\n+ }\n+\n+ #[test]\n+ fn set_matches_keeps_only_the_first_page...", - "path": "codex-rs/tui_app_server/src/bottom_pane/file_search_popup.rs", - "status": "modified" - }, - { - "additions": 41, - "deletions": 0, - "patch_excerpt": "@@ -180,6 +180,7 @@ impl SkillPopup {\n })\n });\n \n+ out.truncate(MAX_POPUP_ROWS);\n out\n }\n }\n@@ -229,3 +230,43 @@ fn skill_popup_hint_line() -> Line<'static> {\n \" to close\".into(),\n ])\n }\n+\n+#[cfg(test)]\n+mod tests {\n+ use super::*;\n+ use pretty_assertions::assert_eq;\n+\n+ fn mention_item(index: usize) -> MentionItem {\n+ MentionItem {\n+ display_name: format!(\"Mention {index:02}\"),\n+ description: Some(format!(\"Description {index:02}\")),\n+ insert_text: format!(\"$mention-{index:02}\"),\n+ search_terms: vec![format!(\"mention-{index:02}\")],\n+ path: Some(format!(\"skill://mention-{index:02}\")),\n+ category_tag: Some(\"[Skill]\".to_string()),\n+ sort_rank: 1,\n+ }\n+ }\n+\n+ #[test]\n+ fn filtered_mentions_are_capped_to_max_popup_rows() {\n+ ...", - "path": "codex-rs/tui_app_server/src/bottom_pane/skill_popup.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Discovered via hourly merged-PR sync: https://github.com/openai/codex/pull/15279" - ], - "primary_pr": { - "body": "- Duplicate app mentions are now suppressed when they\u2019re plugin-backed with the same display name.\r\n- Remaining connector mentions now label category as [Plugin] when plugin metadata is present, otherwise [App].\r\n- Mention result lists are now capped to 8 rows after filtering.\r\n- Updates both tui and tui_app_server with the same changes.", - "labels": [], - "merged_at": "2026-03-23T17:10:17Z", - "number": 15279, - "state": "merged", - "title": "Label plugins as plugins, and hide skills/apps for given plugin", - "url": "https://github.com/openai/codex/pull/15279" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15348.json b/artifacts/github/bundles/openai-codex-pr-15348.json deleted file mode 100644 index 77c821b..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15348.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "alexsong-oai", - "committed_at": "2026-03-20T20:33:01Z", - "message": "Pass platform param to featured plugins", - "sha": "6ffe49c1bc285b41737b422964e535b12569908f", - "url": "https://github.com/openai/codex/commit/6ffe49c1bc285b41737b422964e535b12569908f" - }, - { - "author": "alexsong-oai", - "committed_at": "2026-03-20T22:01:11Z", - "message": "fix", - "sha": "a27e3b5feef99ef140c488e1fedd38823652633c", - "url": "https://github.com/openai/codex/commit/a27e3b5feef99ef140c488e1fedd38823652633c" - }, - { - "author": "alexsong-oai", - "committed_at": "2026-03-21T00:07:17Z", - "message": "use product", - "sha": "ef9876e534494f863052e28af2b64ff1be7d54a1", - "url": "https://github.com/openai/codex/commit/ef9876e534494f863052e28af2b64ff1be7d54a1" - }, - { - "author": "alexsong-oai", - "committed_at": "2026-03-21T01:13:32Z", - "message": "updates", - "sha": "26da090c2e9ce0721302ea1a785174a07e6c73bd", - "url": "https://github.com/openai/codex/commit/26da090c2e9ce0721302ea1a785174a07e6c73bd" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "DEFAULT_TIMEOUT", - "TEST_CURATED_PLUGIN_SHA", - "GET", - "CONFIG_TOML_FILE", - "REMOTE_FEATURED_PLUGIN_FETCH_TIMEOUT" - ], - "files": [ - { - "additions": 5, - "deletions": 0, - "patch_excerpt": "@@ -25,6 +25,7 @@ use wiremock::ResponseTemplate;\n use wiremock::matchers::header;\n use wiremock::matchers::method;\n use wiremock::matchers::path;\n+use wiremock::matchers::query_param;\n \n const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);\n const TEST_CURATED_PLUGIN_SHA: &str = \"0123456789abcdef0123456789abcdef01234567\";\n@@ -678,6 +679,7 @@ async fn plugin_list_force_remote_sync_reconciles_curated_plugin_state() -> Resu\n .await;\n Mock::given(method(\"GET\"))\n .and(path(\"/backend-api/plugins/featured\"))\n+ .and(query_param(\"platform\", \"codex\"))\n .and(header(\"authorization\", \"Bearer chatgpt-token\"))\n .and(header(\"chatgpt-account-id\", \"account-123\"))\n .respond_with(\n@@ -784,6 +786,7 @@ async fn app_server_startup_remote_plugin_sync_runs_once() -> Result<()> {\n .await;\n Mock::given(method(\"GET\"))\n .and(path(\"/backend-...", - "path": "codex-rs/app-server/tests/suite/v2/plugin_list.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 1, - "patch_excerpt": "@@ -622,7 +622,8 @@ impl PluginsManager {\n if let Some(featured_plugin_ids) = self.cached_featured_plugin_ids(&cache_key) {\n return Ok(featured_plugin_ids);\n }\n- let featured_plugin_ids = fetch_remote_featured_plugin_ids(config, auth).await?;\n+ let featured_plugin_ids =\n+ fetch_remote_featured_plugin_ids(config, auth, self.restriction_product).await?;\n self.write_featured_plugin_ids_cache(cache_key, &featured_plugin_ids);\n Ok(featured_plugin_ids)\n }", - "path": "codex-rs/core/src/plugins/manager.rs", - "status": "modified" - }, - { - "additions": 69, - "deletions": 0, - "patch_excerpt": "@@ -23,6 +23,7 @@ use wiremock::ResponseTemplate;\n use wiremock::matchers::header;\n use wiremock::matchers::method;\n use wiremock::matchers::path;\n+use wiremock::matchers::query_param;\n \n fn write_plugin(root: &Path, dir_name: &str, manifest_name: &str) {\n let plugin_root = root.join(dir_name);\n@@ -1899,6 +1900,74 @@ plugins = true\n );\n }\n \n+#[tokio::test]\n+async fn featured_plugin_ids_for_config_uses_restriction_product_query_param() {\n+ let tmp = tempfile::tempdir().unwrap();\n+ write_file(\n+ &tmp.path().join(CONFIG_TOML_FILE),\n+ r#\"[features]\n+plugins = true\n+\"#,\n+ );\n+\n+ let server = MockServer::start().await;\n+ Mock::given(method(\"GET\"))\n+ .and(path(\"/backend-api/plugins/featured\"))\n+ .and(query_param(\"platform\", \"chat\"))\n+ .and(header(\"authorization\", \"Bearer Access Token\"))\n+ .and(header(\"chatgpt-account-id\", \"account...", - "path": "codex-rs/core/src/plugins/manager_tests.rs", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -1,6 +1,7 @@\n use crate::auth::CodexAuth;\n use crate::config::Config;\n use crate::default_client::build_reqwest_client;\n+use codex_protocol::protocol::Product;\n use serde::Deserialize;\n use std::time::Duration;\n use url::Url;\n@@ -162,12 +163,17 @@ pub(crate) async fn fetch_remote_plugin_status(\n pub async fn fetch_remote_featured_plugin_ids(\n config: &Config,\n auth: Option<&CodexAuth>,\n+ product: Option,\n ) -> Result, RemotePluginFetchError> {\n let base_url = config.chatgpt_base_url.trim_end_matches('/');\n let url = format!(\"{base_url}/plugins/featured\");\n let client = build_reqwest_client();\n let mut request = client\n .get(&url)\n+ .query(&[(\n+ \"platform\",\n+ product.unwrap_or(Product::Codex).to_app_platform(),\n+ )])\n .timeout(REMOTE_FEATURED_PLUGIN_FETCH_TIMEOUT);\n \n if let Some(aut...", - "path": "codex-rs/core/src/plugins/remote.rs", - "status": "modified" - }, - { - "additions": 8, - "deletions": 0, - "patch_excerpt": "@@ -2978,6 +2978,14 @@ pub enum Product {\n Atlas,\n }\n impl Product {\n+ pub fn to_app_platform(self) -> &'static str {\n+ match self {\n+ Self::Chatgpt => \"chat\",\n+ Self::Codex => \"codex\",\n+ Self::Atlas => \"atlas\",\n+ }\n+ }\n+\n pub fn from_session_source_name(value: &str) -> Option {\n let normalized = value.trim().to_ascii_lowercase();\n match normalized.as_str() {", - "path": "codex-rs/protocol/src/protocol.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Discovered via hourly merged-PR sync: https://github.com/openai/codex/pull/15348" - ], - "primary_pr": { - "body": "", - "labels": [], - "merged_at": "2026-03-21T01:42:41Z", - "number": 15348, - "state": "merged", - "title": "Pass platform param to featured plugins", - "url": "https://github.com/openai/codex/pull/15348" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15376.json b/artifacts/github/bundles/openai-codex-pr-15376.json deleted file mode 100644 index 3e5c5b3..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15376.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "mzeng-openai", - "committed_at": "2026-03-21T06:50:21Z", - "message": "update", - "sha": "baed7661542915b37f526fcd516433fabbbfcfc7", - "url": "https://github.com/openai/codex/commit/baed7661542915b37f526fcd516433fabbbfcfc7" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "TUI", - "MCP" - ], - "files": [ - { - "additions": 83, - "deletions": 16, - "patch_excerpt": "@@ -720,6 +720,7 @@ pub(crate) struct ChatWidget {\n connectors_partial_snapshot: Option,\n connectors_prefetch_in_flight: bool,\n connectors_force_refetch_pending: bool,\n+ pending_mcp_output_requests: usize,\n plugins_cache: PluginsCacheState,\n plugins_fetch_state: PluginListFetchState,\n // Queue of interruptive UI events deferred during an active write cycle\n@@ -1455,9 +1456,6 @@ impl ChatWidget {\n cwds: Vec::new(),\n force_reload: true,\n });\n- if self.connectors_enabled() {\n- self.prefetch_connectors();\n- }\n if let Some(user_message) = self.initial_user_message.take() {\n self.submit_user_message(user_message);\n }\n@@ -2183,6 +2181,7 @@ impl ChatWidget {\n }\n \n fn on_mcp_startup_complete(&mut self, ev: McpStartupCompleteEvent) {\n+ let codex_apps_re...", - "path": "codex-rs/tui/src/chatwidget.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -1895,6 +1895,7 @@ async fn make_chatwidget_manual(\n connectors_partial_snapshot: None,\n connectors_prefetch_in_flight: false,\n connectors_force_refetch_pending: false,\n+ pending_mcp_output_requests: 0,\n plugins_cache: PluginsCacheState::default(),\n plugins_fetch_state: PluginListFetchState::default(),\n interrupts: InterruptManager::new(),", - "path": "codex-rs/tui/src/chatwidget/tests.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Discovered via hourly merged-PR sync: https://github.com/openai/codex/pull/15376" - ], - "primary_pr": { - "body": "- [x] Remove the app tools copy in TUI and reference the core tools instead, this reduces tools/list calls from 4 to just 1.", - "labels": [], - "merged_at": "2026-03-22T07:17:48Z", - "number": 15376, - "state": "merged", - "title": "[apps] Improve app tools loading for TUI.", - "url": "https://github.com/openai/codex/pull/15376" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15390.json b/artifacts/github/bundles/openai-codex-pr-15390.json deleted file mode 100644 index 314fd2b..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15390.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "etraut-openai", - "committed_at": "2026-03-21T15:31:22Z", - "message": "codex: remove tui_app_server legacy notification handling", - "sha": "fcb42221528b738e0bc84a8010ebb25acd21b211", - "url": "https://github.com/openai/codex/commit/fcb42221528b738e0bc84a8010ebb25acd21b211" - }, - { - "author": "etraut-openai", - "committed_at": "2026-03-21T15:43:36Z", - "message": "Merge remote-tracking branch 'origin/main' into codex/pr-15106-tui-legacy-notifications", - "sha": "05b63be19f905bfcc8d61c5adbd31ff931669358", - "url": "https://github.com/openai/codex/commit/05b63be19f905bfcc8d61c5adbd31ff931669358" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "TUI", - "IDE", - "MAX", - "JSONRPCE", - "JSONRPCN", - "APP_SERVER_TUI_STUB_MESSAGE" - ], - "files": [ - { - "additions": 10, - "deletions": 344, - "patch_excerpt": "@@ -464,8 +464,6 @@ enum ThreadBufferedEvent {\n Notification(ServerNotification),\n Request(ServerRequest),\n HistoryEntryResponse(GetHistoryEntryResponseEvent),\n- LegacyWarning(String),\n- LegacyRollback { num_turns: u32 },\n }\n \n #[derive(Debug)]\n@@ -474,7 +472,6 @@ struct ThreadEventStore {\n turns: Vec,\n buffer: VecDeque,\n pending_interactive_replay: PendingInteractiveReplayState,\n- pending_local_legacy_rollbacks: VecDeque,\n active_turn_id: Option,\n input_state: Option,\n capacity: usize,\n@@ -483,10 +480,7 @@ struct ThreadEventStore {\n \n impl ThreadEventStore {\n fn event_survives_session_refresh(event: &ThreadBufferedEvent) -> bool {\n- matches!(\n- event,\n- ThreadBufferedEvent::Request(_) | ThreadBufferedEvent::LegacyWarning(_)\n- )\n+ matches!(...", - "path": "codex-rs/tui_app_server/src/app.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 133, - "patch_excerpt": "@@ -21,7 +21,6 @@ use codex_app_server_client::AppServerEvent;\n use codex_app_server_protocol::AuthMode;\n use codex_app_server_protocol::ChatgptAuthTokensRefreshParams;\n use codex_app_server_protocol::JSONRPCErrorError;\n-use codex_app_server_protocol::JSONRPCNotification;\n use codex_app_server_protocol::RequestId;\n use codex_app_server_protocol::ServerNotification;\n use codex_app_server_protocol::ServerRequest;\n@@ -106,16 +105,9 @@ use codex_protocol::protocol::TurnAbortedEvent;\n use codex_protocol::protocol::TurnCompleteEvent;\n #[cfg(test)]\n use codex_protocol::protocol::TurnStartedEvent;\n-use serde_json::Value;\n #[cfg(test)]\n use std::time::Duration;\n \n-#[derive(Debug, PartialEq, Eq)]\n-enum LegacyThreadNotification {\n- Warning(String),\n- Rollback { num_turns: u32 },\n-}\n-\n impl App {\n pub(super) async fn handle_app_server_event(\n &mut self,\n@@ -133,37 +125,8 @@ impl Ap...", - "path": "codex-rs/tui_app_server/src/app/app_server_adapter.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 4, - "patch_excerpt": "@@ -9548,10 +9548,6 @@ impl ChatWidget {\n self.request_redraw();\n }\n \n- pub(crate) fn add_warning_message(&mut self, message: String) {\n- self.on_warning(message);\n- }\n-\n fn add_app_server_stub_message(&mut self, feature: &str) {\n warn!(feature, \"stubbed unsupported app-server TUI feature\");\n self.add_error_message(format!(\"{feature}: {APP_SERVER_TUI_STUB_MESSAGE}\"));", - "path": "codex-rs/tui_app_server/src/chatwidget.rs", - "status": "modified" - } - ], - "linked_issues": [ - "#15106" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Discovered via hourly merged-PR sync: https://github.com/openai/codex/pull/15390" - ], - "primary_pr": { - "body": "As part of moving the TUI onto the app server, we added some temporary handling of some legacy events. We've confirmed that these do not need to be supported, so this PR removes this support from the tui_app_server, allowing for additional simplifications in follow-on PRs. These events are needed only for very old rollouts. None of the other app server clients (IDE extension or app) support these either. \r\n\r\n## Summary\r\n- stop translating legacy `codex/event/*` notifications inside `tui_app_server`\r\n- remove the TUI-side legacy warning and rollback buffering/replay paths that were only fed by those notifications\r\n- keep the lower-level app-server and app-server-client legacy event plumbing intact so PR #15106 can rebase on top and handle the remaining exec/lower-layer migration separately\r\n", - "labels": [], - "merged_at": "2026-03-21T18:29:33Z", - "number": 15390, - "state": "merged", - "title": "Remove legacy app-server notification handling from tui_app_server", - "url": "https://github.com/openai/codex/pull/15390" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15409.json b/artifacts/github/bundles/openai-codex-pr-15409.json deleted file mode 100644 index 632760e..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15409.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "cconger", - "committed_at": "2026-03-21T19:31:38Z", - "message": "Add JIT entitlement for macosx", - "sha": "7ea53cad295e936bbd5288d76f56ba7700857b40", - "url": "https://github.com/openai/codex/commit/7ea53cad295e936bbd5288d76f56ba7700857b40" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "JIT", - "--enable", - "--keychain", - "APPLE_CODESIGN_KEYCHAIN", - "GITHUB_ACTION_PATH", - "TARGET", - "--force", - "--options", - "--timestamp", - "--sign", - "APPLE_CODESIGN_IDENTITY", - "--entitlements", - "UTF", - "DOCTYPE", - "PUBLIC", - "DTD", - "PLIST" - ], - "files": [ - { - "additions": 3, - "deletions": 1, - "patch_excerpt": "@@ -132,9 +132,11 @@ runs:\n keychain_args+=(--keychain \"${APPLE_CODESIGN_KEYCHAIN}\")\n fi\n \n+ entitlements_path=\"$GITHUB_ACTION_PATH/codex.entitlements.plist\"\n+\n for binary in codex codex-responses-api-proxy; do\n path=\"codex-rs/target/${TARGET}/release/${binary}\"\n- codesign --force --options runtime --timestamp --sign \"$APPLE_CODESIGN_IDENTITY\" \"${keychain_args[@]}\" \"$path\"\n+ codesign --force --options runtime --timestamp --entitlements \"$entitlements_path\" --sign \"$APPLE_CODESIGN_IDENTITY\" \"${keychain_args[@]}\" \"$path\"\n done\n \n - name: Notarize macOS binaries", - "path": ".github/actions/macos-code-sign/action.yml", - "status": "modified" - }, - { - "additions": 8, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,8 @@\n+\n+\n+\n+\n+\tcom.apple.security.cs.allow-jit\n+\t\n+\n+", - "path": ".github/actions/macos-code-sign/codex.entitlements.plist", - "status": "added" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Discovered via hourly merged-PR sync: https://github.com/openai/codex/pull/15409" - ], - "primary_pr": { - "body": "Without this entitlement, hardened mac os release binaries are unable to allocate the executable memory for the JIT compiled JS.\r\n\r\nTested with local signing. Without entitlement I reproduce the error:\r\n```\r\n#\r\n# Fatal process out of memory: Failed to reserve virtual memory for CodeRange\r\n#\r\n==== C stack trace ===============================\r\n\r\n 0 codex 0x00000001075d1acc codex + 85760716\r\n 1 codex 0x00000001075d6a64 codex + 85781092\r\n 2 codex 0x00000001075c7100 codex + 85717248\r\n 3 codex 0x0000000107637394 codex + 86176660\r\n 4 codex 0x0000000107823cfc codex + 88194300\r\n 5 codex 0x000000010777c438 codex + 87508024\r\n 6 codex 0x000000010777d130 codex + 87511344\r\n 7 codex 0x0000000107c87a54 codex + 92797524\r\n 8 codex 0x0000000107641188 codex + 86217096\r\n 9 codex 0x00000001076412d8 codex + 86217432\r\n 10 codex 0x0000000107553908 codex + 85244168\r\n 11 codex 0x000000010465f124 codex + 36008228\r\n 12 codex 0x000000010466a0d0 codex + 36053200\r\n 13 codex 0x000000010466ce78 codex + 36064888\r\n 14 codex 0x000000010734edb0 codex + 83127728\r\n 15 libsystem_pthread.dylib 0x00000001810d3c08 _pthread_start + 136\r\n 16 libsystem_pthread.dylib 0x00000001810ceba8 thread_start + 8\r\nzsh: trace trap target/release/codex exec --enable code_mode_only --enable code_mode --\r\n```\r\n\r\nWith the entitlement the exec succeeds.", - "labels": [], - "merged_at": "2026-03-21T20:43:15Z", - "number": 15409, - "state": "merged", - "title": "Add JIT entitlement for macosx", - "url": "https://github.com/openai/codex/pull/15409" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15414.json b/artifacts/github/bundles/openai-codex-pr-15414.json deleted file mode 100644 index 87ea85e..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15414.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "etraut-openai", - "committed_at": "2026-03-21T19:55:07Z", - "message": "Remove legacy auth and notification handling from tui_app_server", - "sha": "263c63ab9d2ed1f8b4fa8a7c6d623ea89ceadeec", - "url": "https://github.com/openai/codex/commit/263c63ab9d2ed1f8b4fa8a7c6d623ea89ceadeec" - }, - { - "author": "etraut-openai", - "committed_at": "2026-03-21T20:02:12Z", - "message": "codex: fix CI failure on PR #15414", - "sha": "e65c1371f22d86c595c1c740e1befb45c82e8851", - "url": "https://github.com/openai/codex/commit/e65c1371f22d86c595c1c740e1befb45c82e8851" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "JSONRPCE", - "TUI", - "JWT", - "URL_SAFE_NO_PAD" - ], - "files": [ - { - "additions": 0, - "deletions": 206, - "patch_excerpt": "@@ -16,12 +16,9 @@ use crate::app_event::AppEvent;\n use crate::app_server_session::AppServerSession;\n use crate::app_server_session::app_server_rate_limit_snapshot_to_core;\n use crate::app_server_session::status_account_display_from_auth_mode;\n-use crate::local_chatgpt_auth::load_local_chatgpt_auth;\n use codex_app_server_client::AppServerEvent;\n use codex_app_server_protocol::AuthMode;\n-use codex_app_server_protocol::ChatgptAuthTokensRefreshParams;\n use codex_app_server_protocol::JSONRPCErrorError;\n-use codex_app_server_protocol::RequestId;\n use codex_app_server_protocol::ServerNotification;\n use codex_app_server_protocol::ServerRequest;\n #[cfg(test)]\n@@ -129,15 +126,6 @@ impl App {\n tracing::debug!(\"ignoring legacy app-server notification in tui_app_server\");\n }\n AppServerEvent::ServerRequest(request) => {\n- if let ServerRequest::Ch...", - "path": "codex-rs/tui_app_server/src/app/app_server_adapter.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 11, - "patch_excerpt": "@@ -1,7 +1,6 @@\n use std::path::Path;\n \n use codex_app_server_protocol::AuthMode;\n-use codex_app_server_protocol::ChatgptAuthTokensRefreshResponse;\n use codex_core::auth::AuthCredentialsStoreMode;\n use codex_core::auth::load_auth_dot_json;\n \n@@ -12,16 +11,6 @@ pub(crate) struct LocalChatgptAuth {\n pub(crate) chatgpt_plan_type: Option,\n }\n \n-impl LocalChatgptAuth {\n- pub(crate) fn to_refresh_response(&self) -> ChatgptAuthTokensRefreshResponse {\n- ChatgptAuthTokensRefreshResponse {\n- access_token: self.access_token.clone(),\n- chatgpt_account_id: self.chatgpt_account_id.clone(),\n- chatgpt_plan_type: self.chatgpt_plan_type.clone(),\n- }\n- }\n-}\n-\n pub(crate) fn load_local_chatgpt_auth(\n codex_home: &Path,\n auth_credentials_store_mode: AuthCredentialsStoreMode,", - "path": "codex-rs/tui_app_server/src/local_chatgpt_auth.rs", - "status": "modified" - } - ], - "linked_issues": [ - "#15106", - "#15414" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Discovered via hourly merged-PR sync: https://github.com/openai/codex/pull/15414" - ], - "primary_pr": { - "body": "## Summary\n- remove `tui_app_server` handling for legacy app-server notifications\n- drop the local ChatGPT auth refresh request path from `tui_app_server`\n- remove the now-unused refresh response helper from local auth loading\n\nSplit out of #15106 so the `tui_app_server` cleanup can land separately from the larger `codex-exec` app-server migration.\n", - "labels": [], - "merged_at": "2026-03-21T21:06:11Z", - "number": 15414, - "state": "merged", - "title": "Remove legacy auth and notification handling from tui_app_server", - "url": "https://github.com/openai/codex/pull/15414" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15443.json b/artifacts/github/bundles/openai-codex-pr-15443.json deleted file mode 100644 index b655b60..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15443.json +++ /dev/null @@ -1,102 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "charley-oai", - "committed_at": "2026-03-22T08:14:41Z", - "message": "core: snapshot fork startup context injection", - "sha": "e3e3f29032d2b6723419ebf3edde4ea95eb5ede1", - "url": "https://github.com/openai/codex/commit/e3e3f29032d2b6723419ebf3edde4ea95eb5ede1" - }, - { - "author": "charley-oai", - "committed_at": "2026-03-22T08:16:44Z", - "message": "core: narrow fork snapshot fixture", - "sha": "c42549daab6b5791c75f47b2c2d8c9b7d03e6044", - "url": "https://github.com/openai/codex/commit/c42549daab6b5791c75f47b2c2d8c9b7d03e6044" - }, - { - "author": "charley-oai", - "committed_at": "2026-03-22T08:42:27Z", - "message": "core: fix fork snapshot helper borrow", - "sha": "20bb56962893c17d4e1196a2207f822641677de9", - "url": "https://github.com/openai/codex/commit/20bb56962893c17d4e1196a2207f822641677de9" - }, - { - "author": "charley-oai", - "committed_at": "2026-03-22T08:56:33Z", - "message": "core: snapshot full first forked request", - "sha": "bea065b40de0cce4711f20ab45ec0bdf4c885e0a", - "url": "https://github.com/openai/codex/commit/bea065b40de0cce4711f20ab45ec0bdf4c885e0a" - }, - { - "author": "charley-oai", - "committed_at": "2026-03-22T18:38:10Z", - "message": "core: stabilize fork snapshot across platforms", - "sha": "7464ec0906c0fcf80cfff8465d3e9559b370c370", - "url": "https://github.com/openai/codex/commit/7464ec0906c0fcf80cfff8465d3e9559b370c370" - }, - { - "author": "charley-oai", - "committed_at": "2026-03-23T00:25:06Z", - "message": "core: snippetize fork snapshot request text", - "sha": "b3f203b3ff2a61b9756488feb6f97a858e3521d1", - "url": "https://github.com/openai/codex/commit/b3f203b3ff2a61b9756488feb6f97a858e3521d1" - }, - { - "author": "charley-oai", - "committed_at": "2026-03-23T00:26:32Z", - "message": "core: drop redundant fork snapshot normalization", - "sha": "f07987854316a2f9370f125fe61dee955b08433d", - "url": "https://github.com/openai/codex/commit/f07987854316a2f9370f125fe61dee955b08433d" - }, - { - "author": "charley-oai", - "committed_at": "2026-03-23T00:31:29Z", - "message": "core: remove unused fork snapshot mock binding", - "sha": "97288e360aa532c50d2a7e991ad7aae8f5cdf63e", - "url": "https://github.com/openai/codex/commit/97288e360aa532c50d2a7e991ad7aae8f5cdf63e" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "MAX", - "PERMISSIONS_INSTRUCTIONS", - "ENVIRONMENT_CONTEXT", - "CWD" - ], - "files": [ - { - "additions": 119, - "deletions": 0, - "patch_excerpt": "@@ -64,17 +64,31 @@ use codex_execpolicy::NetworkRuleProtocol;\n use codex_execpolicy::Policy;\n use codex_network_proxy::NetworkProxyConfig;\n use codex_otel::TelemetryAuthMode;\n+use codex_protocol::config_types::CollaborationMode;\n+use codex_protocol::config_types::ModeKind;\n+use codex_protocol::config_types::Settings;\n use codex_protocol::models::BaseInstructions;\n use codex_protocol::models::ContentItem;\n use codex_protocol::models::DeveloperInstructions;\n use codex_protocol::models::ResponseInputItem;\n use codex_protocol::models::ResponseItem;\n use codex_protocol::openai_models::ModelsResponse;\n+use codex_protocol::protocol::AskForApproval;\n use codex_protocol::protocol::ConversationAudioParams;\n use codex_protocol::protocol::RealtimeAudioFrame;\n use codex_protocol::protocol::Submission;\n use codex_protocol::protocol::W3cTraceContext;\n+use core_test_support::context_snapshot;\n+use core...", - "path": "codex-rs/core/src/codex_tests.rs", - "status": "modified" - }, - { - "additions": 17, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,17 @@\n+---\n+source: core/src/codex_tests.rs\n+assertion_line: 1282\n+expression: snapshot\n+---\n+Scenario: First request after fork when fork startup changes approval policy and the first forked turn changes approval policy again and enters plan mode.\n+\n+## First Forked Turn Request\n+00:message/developer:\n+01:message/user:>\n+02:message/user:fork seed\n+03:message/developer:\n+04:message/user:>\n+05:message/developer[2]:\n+ [01] \n+ [02] Fork turn collaboration instructions.\n+06:message/user:after fork", - "path": "codex-rs/core/src/snapshots/codex_core__codex_tests__fork_startup_context_then_first_turn_diff.snap", - "status": "added" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Discovered via hourly merged-PR sync: https://github.com/openai/codex/pull/15443" - ], - "primary_pr": { - "body": "## Summary\n- add a snapshot-style core test for fork startup context injection followed by first-turn diff injection\n- capture the current duplicated startup-plus-turn context behavior without changing runtime logic\n\n## Testing\n- not run locally; relying on CI\n- just fmt", - "labels": [], - "merged_at": "2026-03-23T01:24:14Z", - "number": 15443, - "state": "merged", - "title": "core: snapshot fork startup context injection", - "url": "https://github.com/openai/codex/pull/15443" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15659.json b/artifacts/github/bundles/openai-codex-pr-15659.json deleted file mode 100644 index a8b5f84..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15659.json +++ /dev/null @@ -1,128 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-23T20:22:38Z", - "message": "Add outbound spans for responses and MCP", - "sha": "4322d47f7444f7c6e1ae5167c985ff077934022e", - "url": "https://github.com/openai/codex/commit/4322d47f7444f7c6e1ae5167c985ff077934022e" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-24T18:09:10Z", - "message": "Reduce outbound spans to semantic HTTP/MCP calls", - "sha": "dc0b2cafbc1e82cb9f99c9c967ff3d01e0f5ee5e", - "url": "https://github.com/openai/codex/commit/dc0b2cafbc1e82cb9f99c9c967ff3d01e0f5ee5e" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-24T18:29:22Z", - "message": "Make turn spans explicit tracing parents", - "sha": "7e0c8c06d0c7ca84f531bb42d02a5561a5fb0fc4", - "url": "https://github.com/openai/codex/commit/7e0c8c06d0c7ca84f531bb42d02a5561a5fb0fc4" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-25T01:42:55Z", - "message": "Add lower-level HTTP and MCP child spans", - "sha": "3601d9387a4b3ccbd8284fec48e7e02faa4a417f", - "url": "https://github.com/openai/codex/commit/3601d9387a4b3ccbd8284fec48e7e02faa4a417f" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-25T16:52:21Z", - "message": "Merge branch 'main' into nicholasclark/2026-03-23-server-spans", - "sha": "52c7ffc6eea413a7770dd2bfa43f0a1492e45801", - "url": "https://github.com/openai/codex/commit/52c7ffc6eea413a7770dd2bfa43f0a1492e45801" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-25T17:47:48Z", - "message": "Add connector metadata to MCP tool call spans", - "sha": "9d8e8256711bdbfc9ad62fbb9bdbe075b98c6e25", - "url": "https://github.com/openai/codex/commit/9d8e8256711bdbfc9ad62fbb9bdbe075b98c6e25" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-25T17:50:59Z", - "message": "Narrow tracing branch to MCP spans", - "sha": "171bbc836442a8d5cbec56994ceded37dc85a8e3", - "url": "https://github.com/openai/codex/commit/171bbc836442a8d5cbec56994ceded37dc85a8e3" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-25T18:28:17Z", - "message": "Simplify MCP span diff", - "sha": "9df628f38c84f99f03db67e8cb1f249b6106f89d", - "url": "https://github.com/openai/codex/commit/9df628f38c84f99f03db67e8cb1f249b6106f89d" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-25T18:50:32Z", - "message": "Keep top MCP span PR core-only", - "sha": "3f31bbb2715bf04ef2430bf202407d57c0922a4e", - "url": "https://github.com/openai/codex/commit/3f31bbb2715bf04ef2430bf202407d57c0922a4e" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-25T19:44:40Z", - "message": "Move MCP span assertion into unit test", - "sha": "c3dbb6392ef5c66ecfa598deace2f08f793b91cd", - "url": "https://github.com/openai/codex/commit/c3dbb6392ef5c66ecfa598deace2f08f793b91cd" - }, - { - "author": "nicholasclark-openai", - "committed_at": "2026-03-25T20:55:40Z", - "message": "Inline MCP span correlation fields", - "sha": "89161d4b9ba6956f1f58e2b78cb17082f1b406e3", - "url": "https://github.com/openai/codex/commit/89161d4b9ba6956f1f58e2b78cb17082f1b406e3" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "MCP", - "REPL", - "RMCP", - "HTTP", - "TRACE", - "FULL" - ], - "files": [ - { - "additions": 112, - "deletions": 11, - "patch_excerpt": "@@ -50,6 +50,10 @@ use serde::Serialize;\n use std::path::Path;\n use std::sync::Arc;\n use toml_edit::value;\n+use tracing::Instrument;\n+use tracing::Span;\n+use tracing::field::Empty;\n+use url::Url;\n \n /// Handles the specified tool call dispatches the appropriate\n /// `McpToolCallBegin` and `McpToolCallEnd` events to the `Session`.\n@@ -121,6 +125,19 @@ pub(crate) async fn handle_mcp_tool_call(\n }\n let request_meta =\n build_mcp_tool_call_request_meta(turn_context.as_ref(), &server, metadata.as_ref());\n+ let connector_id = metadata\n+ .as_ref()\n+ .and_then(|metadata| metadata.connector_id.clone());\n+ let connector_name = metadata\n+ .as_ref()\n+ .and_then(|metadata| metadata.connector_name.clone());\n+ let server_origin = sess\n+ .services\n+ .mcp_connection_manager\n+ .read()\n+ .await\n+ .server_origin(&server)\n...", - "path": "codex-rs/core/src/mcp_tool_call.rs", - "status": "modified" - }, - { - "additions": 55, - "deletions": 0, - "patch_excerpt": "@@ -18,6 +18,10 @@ use serde::Deserialize;\n use std::collections::HashMap;\n use std::sync::Arc;\n use tempfile::tempdir;\n+use tracing::Instrument;\n+use tracing::Level;\n+use tracing_subscriber::fmt::format::FmtSpan;\n+use tracing_test::internal::MockWriter;\n \n fn annotations(\n read_only: Option,\n@@ -119,6 +123,57 @@ fn approval_question_text_prepends_safety_reason() {\n );\n }\n \n+#[tokio::test]\n+async fn mcp_tool_call_span_records_expected_fields() {\n+ let buffer: &'static std::sync::Mutex> =\n+ Box::leak(Box::new(std::sync::Mutex::new(Vec::new())));\n+ let subscriber = tracing_subscriber::fmt()\n+ .with_level(true)\n+ .with_ansi(false)\n+ .with_max_level(Level::TRACE)\n+ .with_span_events(FmtSpan::FULL)\n+ .with_writer(MockWriter::new(buffer))\n+ .finish();\n+ let _guard = tracing::subscriber::set_default(subscriber);\n+\n+...", - "path": "codex-rs/core/src/mcp_tool_call_tests.rs", - "status": "modified" - } - ], - "linked_issues": [ - "#15805", - "#15792" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Discovered via hourly merged-PR sync: https://github.com/openai/codex/pull/15659" - ], - "primary_pr": { - "body": "## Summary\n- add an explicit `mcp.tools.call` span around MCP tool execution in core\n- keep MCP span validation local to `mcp_tool_call_tests` instead of broadening the integration test suite\n- inline the turn/session correlation fields directly in the span initializer\n\n## Included Changes\n- `codex-rs/core/src/mcp_tool_call.rs`: wrap the existing MCP tool call in `mcp.tools.call` and inline `conversation.id`, `session.id`, and `turn.id` in the span initializer\n- `codex-rs/core/src/mcp_tool_call_tests.rs`: assert the MCP span records the expected correlation and server fields\n\n## Testing\n- `cargo test -p codex-core`\n- `just fmt`\n\n## Notes\n- `cargo test -p codex-core` still hits existing unrelated failures in guardian-config tests and the sandboxed JS REPL `mktemp` test\n- metric work moved to sibling stacked PR #15805\n- transport-level RMCP spans and trace propagation remain in sibling stacked PR #15792\n- full workspace `cargo test` was not run", - "labels": [], - "merged_at": "2026-03-25T22:13:02Z", - "number": 15659, - "state": "merged", - "title": "Add MCP tool call spans", - "url": "https://github.com/openai/codex/pull/15659" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15759.json b/artifacts/github/bundles/openai-codex-pr-15759.json deleted file mode 100644 index dfb2a69..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15759.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "fcoury", - "committed_at": "2026-03-25T01:30:12Z", - "message": "feat(tui_app_server): preserve transcript events under backpressure", - "sha": "86d0792b591c405c06af3a4c96466ec372b47f44", - "url": "https://github.com/openai/codex/commit/86d0792b591c405c06af3a4c96466ec372b47f44" - }, - { - "author": "fcoury", - "committed_at": "2026-03-25T01:30:24Z", - "message": "test(app-server-client): add backpressure regression coverage", - "sha": "f45919b207f35d9a05aaa35e12fbfeb625aa3982", - "url": "https://github.com/openai/codex/commit/f45919b207f35d9a05aaa35e12fbfeb625aa3982" - }, - { - "author": "fcoury", - "committed_at": "2026-03-25T02:43:59Z", - "message": "docs(app-server-client): document backpressure forwarding internals", - "sha": "e189c1c12f4ccce1830ec12e9ad11e0fdc4db870", - "url": "https://github.com/openai/codex/commit/e189c1c12f4ccce1830ec12e9ad11e0fdc4db870" - }, - { - "author": "etraut-openai", - "committed_at": "2026-03-25T17:01:04Z", - "message": "Merge branch 'main' into fix/paragraph-render-issues", - "sha": "24b166cb539fc6d199c94ec696db60ae0b833ce5", - "url": "https://github.com/openai/codex/commit/24b166cb539fc6d199c94ec696db60ae0b833ce5" - }, - { - "author": "fcoury", - "committed_at": "2026-03-25T18:58:44Z", - "message": "test(core): relax remote backpressure event ordering assertion", - "sha": "b66ae15504be49bdddafe44557eb0c8be3920a6b", - "url": "https://github.com/openai/codex/commit/b66ae15504be49bdddafe44557eb0c8be3920a6b" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "TUI", - "JSON", - "JSONRPCE", - "JSONRPCM", - "SHUTDOWN_TIMEOUT" - ], - "files": [ - { - "additions": 415, - "deletions": 85, - "patch_excerpt": "@@ -85,17 +85,128 @@ impl From for AppServerEvent {\n }\n \n fn event_requires_delivery(event: &InProcessServerEvent) -> bool {\n- // These terminal events drive surface shutdown/completion state. Dropping\n- // them under backpressure can leave exec/TUI waiting forever even though\n- // the underlying turn has already ended.\n+ // These transcript and terminal events must remain lossless. Dropping\n+ // streamed assistant text or the authoritative completed item can leave\n+ // the TUI with permanently corrupted markdown, while dropping completion\n+ // notifications can leave surfaces waiting forever.\n+ match event {\n+ InProcessServerEvent::ServerNotification(notification) => {\n+ server_notification_requires_delivery(notification)\n+ }\n+ _ => false,\n+ }\n+}\n+\n+/// Returns `true` for notifications that must survive bac...", - "path": "codex-rs/app-server-client/src/lib.rs", - "status": "modified" - }, - { - "additions": 42, - "deletions": 4, - "patch_excerpt": "@@ -21,6 +21,7 @@ use crate::RequestResult;\n use crate::SHUTDOWN_TIMEOUT;\n use crate::TypedRequestError;\n use crate::request_method_name;\n+use crate::server_notification_requires_delivery;\n use codex_app_server_protocol::ClientInfo;\n use codex_app_server_protocol::ClientNotification;\n use codex_app_server_protocol::ClientRequest;\n@@ -854,11 +855,11 @@ async fn reject_if_server_request_dropped(\n \n fn event_requires_delivery(event: &AppServerEvent) -> bool {\n match event {\n- AppServerEvent::ServerNotification(ServerNotification::TurnCompleted(_)) => true,\n+ AppServerEvent::ServerNotification(notification) => {\n+ server_notification_requires_delivery(notification)\n+ }\n AppServerEvent::Disconnected { .. } => true,\n- AppServerEvent::Lagged { .. }\n- | AppServerEvent::ServerNotification(_)\n- | AppServerEvent::ServerRequest(_) => f...", - "path": "codex-rs/app-server-client/src/remote.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Discovered via hourly merged-PR sync: https://github.com/openai/codex/pull/15759" - ], - "primary_pr": { - "body": "## TL;DR\r\n\r\nWhen running codex with `-c features.tui_app_server=true` we see corruption when streaming large amounts of data. This PR marks other event types as _critical_ by making them _must-deliver_.\r\n\r\n## Problem\r\n\r\nWhen the TUI consumer falls behind the app-server event stream, the bounded `mpsc` channel fills up and the forwarding layer drops events via `try_send`. Previously only `TurnCompleted` was marked as must-deliver. Streamed assistant text (`AgentMessageDelta`) and the authoritative final item (`ItemCompleted`) were treated as droppable \u2014 the same as ephemeral command output deltas. Because the TUI renders markdown incrementally from these deltas, dropping any of them produces permanently corrupted or incomplete paragraphs that persist for the rest of the session.\r\n\r\n## Mental model\r\n\r\nThe app-server event stream has two tiers of importance:\r\n\r\n1. **Lossless (transcript + terminal):** Events that form the authoritative record of what the assistant said or that signal turn lifecycle transitions. Losing any of these corrupts the visible output or leaves surfaces waiting forever. These are: `AgentMessageDelta`, `PlanDelta`, `ReasoningSummaryTextDelta`, `ReasoningTextDelta`, `ItemCompleted`, and `TurnCompleted`.\r\n\r\n2. **Best-effort (everything else):** Ephemeral status events like `CommandExecutionOutputDelta` and progress notifications. Dropping these under load causes cosmetic gaps but no permanent corruption.\r\n\r\nThe forwarding layer uses `try_send` for best-effort events (dropping on backpressure) and blocking `send().await` for lossless events (applying back-pressure to the producer until the consumer catches up).\r\n\r\n## Non-goals\r\n\r\n- Eliminating backpressure entirely. The bounded queue is intentional; this change only widens the set of events that survive it.\r\n- Changing the event protocol or adding new notification types.\r\n- Addressing root causes of consumer slowness (e.g. TUI render cost).\r\n\r\n## Tradeoffs\r\n\r\nBlocking on transcript events means a slow consumer can now stall the producer for the duration of those events. This is acceptable because: (a) the alternative is permanently broken output, which is worse; (b) the consumer already had to keep up with `TurnCompleted` blocking sends; and (c) transcript events arrive at model-output speed, not burst speed, so sustained saturation is unlikely in practice.\r\n\r\n## Architecture\r\n\r\nTwo parallel changes, one per transport:\r\n\r\n- **In-process path** (`lib.rs`): The inline forwarding logic was extracted into `forward_in_process_event`, a standalone async function that encapsulates the lag-marker / must-deliver / try-send decision tree. The worker loop now delegates to it. A new `server_notification_requires_delivery` function (shared `pub(crate)`) centralizes the notification classification.\r\n\r\n- **Remote path** (`remote.rs`): The local `event_requires_delivery` now delegates to the same shared `server_notification_requires_delivery`, keeping both transports in sync.\r\n\r\n## Observability\r\n\r\nNo new metrics or log lines. The existing `warn!` on event drops continues to fire for best-effort events. Lossless events that block will not produce a log line (they simply wait).\r\n\r\n## Tests\r\n\r\n- `event_requires_delivery_marks_transcript_and_terminal_events`: unit test confirming the expanded classification covers `AgentMessageDelta`, `ItemCompleted`, `TurnCompleted`, and excludes `CommandExecutionOutputDelta` and `Lagged`.\r\n- `forward_in_process_event_preserves_transcript_notifications_under_backpressure`: integration-style test that fills a capacity-1 channel, verifies a best-effort event is dropped (skipped count increments), then sends lossless transcript events and confirms they all arrive in order with the correct lag marker preceding them.\r\n- `remote_backpressure_preserves_transcript_notifications`: end-to-end test over a real websocket that verifies the remote transport preserves transcript events under the same backpressure scenario.\r\n- `event_requires_delivery_marks_transcript_and_disconnect_events` (remote): unit test confirming the remote-side classification covers transcript events and `Disconnected`.", - "labels": [], - "merged_at": "2026-03-25T19:50:40Z", - "number": 15759, - "state": "merged", - "title": "fix(tui_app_server): preserve transcript events under backpressure", - "url": "https://github.com/openai/codex/pull/15759" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15785.json b/artifacts/github/bundles/openai-codex-pr-15785.json deleted file mode 100644 index 10fdfc3..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15785.json +++ /dev/null @@ -1,314 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "pakrym-oai", - "committed_at": "2026-03-25T16:47:56Z", - "message": "Add environment manager setup", - "sha": "2fdf36b5808ea7d86df10a9a39a12692ea9192d6", - "url": "https://github.com/openai/codex/commit/2fdf36b5808ea7d86df10a9a39a12692ea9192d6" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-25T17:04:53Z", - "message": "Add environment manager for exec url", - "sha": "9524bd2447d38b9086c39e9dfe12a4f307cd4ca4", - "url": "https://github.com/openai/codex/commit/9524bd2447d38b9086c39e9dfe12a4f307cd4ca4" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-25T17:58:53Z", - "message": "codex: fix CI failure on PR #15785", - "sha": "8606dd1a290c0086ed5bfeaa836f1c68b503f1be", - "url": "https://github.com/openai/codex/commit/8606dd1a290c0086ed5bfeaa836f1c68b503f1be" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-25T21:54:21Z", - "message": "codex: route tui app server through env manager", - "sha": "9c07e68e203fe4e736d9a71da58763ace5a963f9", - "url": "https://github.com/openai/codex/commit/9c07e68e203fe4e736d9a71da58763ace5a963f9" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-25T22:46:23Z", - "message": "codex: drop broken remote-aware shell test", - "sha": "8986e4411738927da8777f2da4f3db4658f1e6f9", - "url": "https://github.com/openai/codex/commit/8986e4411738927da8777f2da4f3db4658f1e6f9" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "CHANNEL_CAPACITY", - "URL", - "FEATURES", - "TOML", - "API", - "CODEX_EXEC_SERVER_URL_ENV_VAR", - "CODEX_EXEC_SERVER_URL", - "CLI", - "USER_AGENT_SUFFIX", - "HIDE_GPT_5_1_CODEX_MAX_MIGRATION_", - "HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG", - "--bin", - "TUI", - "BASH_SOURCE", - "CODEX_EXEC_SERVER_LISTEN_URL", - "CODEX_EXEC_SERVER_START_TIMEOUT_SECONDS", - "TMPDIR", - "XXXXXX", - "EXIT", - "INT", - "TERM", - "HUP", - "--listen", - "CODEX_EXEC_SERVER_URL=\"$exec_server_url\"", - "CODEX_EXEC_SERVER_URL=$CODEX_EXEC_SERVER_URL\"" - ], - "files": [ - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -2261,6 +2261,7 @@ dependencies = [\n \"anyhow\",\n \"codex-arg0\",\n \"codex-core\",\n+ \"codex-exec-server\",\n \"codex-features\",\n \"codex-protocol\",\n \"codex-shell-command\",\n@@ -2653,6 +2654,7 @@ dependencies = [\n \"codex-client\",\n \"codex-cloud-requirements\",\n \"codex-core\",\n+ \"codex-exec-server\",\n \"codex-features\",\n \"codex-feedback\",\n \"codex-file-search\",", - "path": "codex-rs/Cargo.lock", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -77,6 +77,7 @@ use codex_arg0::Arg0DispatchPaths;\n use codex_core::config::Config;\n use codex_core::config_loader::CloudRequirementsLoader;\n use codex_core::config_loader::LoaderOverrides;\n+use codex_exec_server::EnvironmentManager;\n use codex_feedback::CodexFeedback;\n use codex_protocol::protocol::SessionSource;\n use tokio::sync::mpsc;\n@@ -383,6 +384,7 @@ fn start_uninitialized(args: InProcessStartArgs) -> InProcessClientHandle {\n outgoing: Arc::clone(&processor_outgoing),\n arg0_paths: args.arg0_paths,\n config: args.config,\n+ environment_manager: Arc::new(EnvironmentManager::from_env()),\n cli_overrides: args.cli_overrides,\n loader_overrides: args.loader_overrides,\n cloud_requirements: args.cloud_requirements,", - "path": "codex-rs/app-server/src/in_process.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 0, - "patch_excerpt": "@@ -39,6 +39,7 @@ use codex_core::ExecPolicyError;\n use codex_core::check_execpolicy_for_warnings;\n use codex_core::config_loader::ConfigLoadError;\n use codex_core::config_loader::TextRange as CoreTextRange;\n+use codex_exec_server::EnvironmentManager;\n use codex_feedback::CodexFeedback;\n use codex_protocol::protocol::SessionSource;\n use codex_state::log_db;\n@@ -349,6 +350,7 @@ pub async fn run_main_with_transport(\n transport: AppServerTransport,\n session_source: SessionSource,\n ) -> IoResult<()> {\n+ let environment_manager = Arc::new(EnvironmentManager::from_env());\n let (transport_event_tx, mut transport_event_rx) =\n mpsc::channel::(CHANNEL_CAPACITY);\n let (outgoing_tx, mut outgoing_rx) = mpsc::channel::(CHANNEL_CAPACITY);\n@@ -613,6 +615,7 @@ pub async fn run_main_with_transport(\n outgoing: outgoing_message_sender,...", - "path": "codex-rs/app-server/src/lib.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 0, - "patch_excerpt": "@@ -66,6 +66,7 @@ use codex_core::default_client::get_codex_user_agent;\n use codex_core::default_client::set_default_client_residency_requirement;\n use codex_core::default_client::set_default_originator;\n use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;\n+use codex_exec_server::EnvironmentManager;\n use codex_features::Feature;\n use codex_feedback::CodexFeedback;\n use codex_login::auth::ExternalAuthRefreshContext;\n@@ -176,6 +177,7 @@ pub(crate) struct MessageProcessorArgs {\n pub(crate) outgoing: Arc,\n pub(crate) arg0_paths: Arg0DispatchPaths,\n pub(crate) config: Arc,\n+ pub(crate) environment_manager: Arc,\n pub(crate) cli_overrides: Vec<(String, TomlValue)>,\n pub(crate) loader_overrides: LoaderOverrides,\n pub(crate) cloud_requirements: CloudRequirementsLoader,\n@@ -194,6 +196,7 ...", - "path": "codex-rs/app-server/src/message_processor.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -24,6 +24,7 @@ use codex_core::config::Config;\n use codex_core::config::ConfigBuilder;\n use codex_core::config_loader::CloudRequirementsLoader;\n use codex_core::config_loader::LoaderOverrides;\n+use codex_exec_server::EnvironmentManager;\n use codex_feedback::CodexFeedback;\n use codex_protocol::protocol::SessionSource;\n use codex_protocol::protocol::W3cTraceContext;\n@@ -236,6 +237,7 @@ fn build_test_processor(\n outgoing,\n arg0_paths: Arg0DispatchPaths::default(),\n config,\n+ environment_manager: Arc::new(EnvironmentManager::new(/*exec_server_url*/ None)),\n cli_overrides: Vec::new(),\n loader_overrides: LoaderOverrides::default(),\n cloud_requirements: CloudRequirementsLoader::default(),", - "path": "codex-rs/app-server/src/message_processor/tracing_tests.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 4, - "patch_excerpt": "@@ -1914,10 +1914,6 @@\n \"experimental_compact_prompt_file\": {\n \"$ref\": \"#/definitions/AbsolutePathBuf\"\n },\n- \"experimental_exec_server_url\": {\n- \"description\": \"Experimental / do not use. Overrides the URL used when connecting to a remote exec server.\",\n- \"type\": \"string\"\n- },\n \"experimental_realtime_start_instructions\": {\n \"description\": \"Experimental / do not use. Replaces the built-in realtime start instructions inserted into developer messages when realtime becomes active.\",\n \"type\": \"string\"", - "path": "codex-rs/core/config.schema.json", - "status": "modified" - }, - { - "additions": 21, - "deletions": 0, - "patch_excerpt": "@@ -75,6 +75,9 @@ impl AgentControlHarness {\n CodexAuth::from_api_key(\"dummy\"),\n config.model_provider.clone(),\n config.codex_home.clone(),\n+ std::sync::Arc::new(codex_exec_server::EnvironmentManager::new(\n+ /*exec_server_url*/ None,\n+ )),\n );\n let control = manager.agent_control();\n Self {\n@@ -811,6 +814,9 @@ async fn spawn_agent_respects_max_threads_limit() {\n CodexAuth::from_api_key(\"dummy\"),\n config.model_provider.clone(),\n config.codex_home.clone(),\n+ std::sync::Arc::new(codex_exec_server::EnvironmentManager::new(\n+ /*exec_server_url*/ None,\n+ )),\n );\n let control = manager.agent_control();\n \n@@ -854,6 +860,9 @@ async fn spawn_agent_releases_slot_after_shutdown() {\n CodexAuth::from_api_key(\"dummy\"),\n config.model_pr...", - "path": "codex-rs/core/src/agent/control_tests.rs", - "status": "modified" - }, - { - "additions": 6, - "deletions": 3, - "patch_excerpt": "@@ -56,6 +56,7 @@ use chrono::Utc;\n use codex_app_server_protocol::McpServerElicitationRequest;\n use codex_app_server_protocol::McpServerElicitationRequestParams;\n use codex_exec_server::Environment;\n+use codex_exec_server::EnvironmentManager;\n use codex_features::FEATURES;\n use codex_features::Feature;\n use codex_features::unstable_features_warning_event;\n@@ -401,6 +402,7 @@ pub(crate) struct CodexSpawnArgs {\n pub(crate) config: Config,\n pub(crate) auth_manager: Arc,\n pub(crate) models_manager: Arc,\n+ pub(crate) environment_manager: Arc,\n pub(crate) skills_manager: Arc,\n pub(crate) plugins_manager: Arc,\n pub(crate) mcp_manager: Arc,\n@@ -454,6 +456,7 @@ impl Codex {\n mut config,\n auth_manager,\n models_manager,\n+ environment_m...", - "path": "codex-rs/core/src/codex.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 0, - "patch_excerpt": "@@ -4,6 +4,7 @@ use std::sync::Arc;\n use async_channel::Receiver;\n use async_channel::Sender;\n use codex_async_utils::OrCancelExt;\n+use codex_exec_server::EnvironmentManager;\n use codex_protocol::protocol::ApplyPatchApprovalRequestEvent;\n use codex_protocol::protocol::Event;\n use codex_protocol::protocol::EventMsg;\n@@ -76,6 +77,9 @@ pub(crate) async fn run_codex_thread_interactive(\n config,\n auth_manager,\n models_manager,\n+ environment_manager: Arc::new(EnvironmentManager::new(\n+ parent_ctx.environment.exec_server_url().map(str::to_owned),\n+ )),\n skills_manager: Arc::clone(&parent_session.services.skills_manager),\n plugins_manager: Arc::clone(&parent_session.services.plugins_manager),\n mcp_manager: Arc::clone(&parent_session.services.mcp_manager),", - "path": "codex-rs/core/src/codex_delegate.rs", - "status": "modified" - }, - { - "additions": 5, - "deletions": 2, - "patch_excerpt": "@@ -2552,6 +2552,9 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {\n agent_status_tx,\n InitialHistory::New,\n SessionSource::Exec,\n+ Arc::new(codex_exec_server::EnvironmentManager::new(\n+ /*exec_server_url*/ None,\n+ )),\n skills_manager,\n plugins_manager,\n mcp_manager,\n@@ -2649,7 +2652,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {\n ));\n let network_approval = Arc::new(NetworkApprovalService::default());\n let environment = Arc::new(\n- codex_exec_server::Environment::create(None)\n+ codex_exec_server::Environment::create(/*exec_server_url*/ None)\n .await\n .expect(\"create environment\"),\n );\n@@ -3484,7 +3487,7 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(\n ));\n let network_app...", - "path": "codex-rs/core/src/codex_tests.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -12,6 +12,7 @@ use crate::sandboxing::SandboxPermissions;\n use crate::tools::context::FunctionToolOutput;\n use crate::turn_diff_tracker::TurnDiffTracker;\n use codex_app_server_protocol::ConfigLayerSource;\n+use codex_exec_server::EnvironmentManager;\n use codex_execpolicy::Decision;\n use codex_execpolicy::Evaluation;\n use codex_execpolicy::RuleMatch;\n@@ -441,6 +442,7 @@ async fn guardian_subagent_does_not_inherit_parent_exec_policy_rules() {\n config,\n auth_manager,\n models_manager,\n+ environment_manager: Arc::new(EnvironmentManager::new(/*exec_server_url*/ None)),\n skills_manager,\n plugins_manager,\n mcp_manager,", - "path": "codex-rs/core/src/codex_tests_guardian.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 32, - "patch_excerpt": "@@ -4335,7 +4335,6 @@ fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {\n model_verbosity: None,\n personality: Some(Personality::Pragmatic),\n chatgpt_base_url: \"https://chatgpt.com/backend-api/\".to_string(),\n- experimental_exec_server_url: None,\n realtime_audio: RealtimeAudioConfig::default(),\n experimental_realtime_start_instructions: None,\n experimental_realtime_ws_base_url: None,\n@@ -4478,7 +4477,6 @@ fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {\n model_verbosity: None,\n personality: Some(Personality::Pragmatic),\n chatgpt_base_url: \"https://chatgpt.com/backend-api/\".to_string(),\n- experimental_exec_server_url: None,\n realtime_audio: RealtimeAudioConfig::default(),\n experimental_realtime_start_instructions: None,\n...", - "path": "codex-rs/core/src/config/config_tests.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 9, - "patch_excerpt": "@@ -526,10 +526,6 @@ pub struct Config {\n /// Base URL for requests to ChatGPT (as opposed to the OpenAI API).\n pub chatgpt_base_url: String,\n \n- /// Experimental / do not use. Overrides the URL used when connecting to\n- /// a remote exec server.\n- pub experimental_exec_server_url: Option,\n-\n /// Machine-local realtime audio device preferences used by realtime voice.\n pub realtime_audio: RealtimeAudioConfig,\n \n@@ -1319,10 +1315,6 @@ pub struct ConfigToml {\n /// Base URL override for the built-in `openai` model provider.\n pub openai_base_url: Option,\n \n- /// Experimental / do not use. Overrides the URL used when connecting to\n- /// a remote exec server.\n- pub experimental_exec_server_url: Option,\n-\n /// Machine-local realtime audio device preferences used by realtime voice.\n #[serde(default)]\n pub audio: Option<...", - "path": "codex-rs/core/src/config/mod.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 0, - "patch_excerpt": "@@ -484,6 +484,9 @@ mod phase2 {\n CodexAuth::from_api_key(\"dummy\"),\n config.model_provider.clone(),\n config.codex_home.clone(),\n+ std::sync::Arc::new(codex_exec_server::EnvironmentManager::new(\n+ /*exec_server_url*/ None,\n+ )),\n );\n let (mut session, _turn_context) = make_session_and_context().await;\n session.services.state_db = Some(Arc::clone(&state_db));", - "path": "codex-rs/core/src/memories/tests.rs", - "status": "modified" - }, - { - "additions": 8, - "deletions": 1, - "patch_excerpt": "@@ -7,6 +7,7 @@\n use std::path::PathBuf;\n use std::sync::Arc;\n \n+use codex_exec_server::EnvironmentManager;\n use codex_protocol::config_types::CollaborationModeMask;\n use codex_protocol::openai_models::ModelInfo;\n use codex_protocol::openai_models::ModelPreset;\n@@ -60,8 +61,14 @@ pub fn thread_manager_with_models_provider_and_home(\n auth: CodexAuth,\n provider: ModelProviderInfo,\n codex_home: PathBuf,\n+ environment_manager: Arc,\n ) -> ThreadManager {\n- ThreadManager::with_models_provider_and_home_for_tests(auth, provider, codex_home)\n+ ThreadManager::with_models_provider_and_home_for_tests(\n+ auth,\n+ provider,\n+ codex_home,\n+ environment_manager,\n+ )\n }\n \n pub async fn start_thread_with_user_shell_override(", - "path": "codex-rs/core/src/test_support.rs", - "status": "modified" - }, - { - "additions": 13, - "deletions": 2, - "patch_excerpt": "@@ -28,6 +28,7 @@ use crate::skills_watcher::SkillsWatcherEvent;\n use crate::tasks::interrupted_turn_history_marker;\n use codex_app_server_protocol::ThreadHistoryBuilder;\n use codex_app_server_protocol::TurnStatus;\n+use codex_exec_server::EnvironmentManager;\n use codex_protocol::ThreadId;\n use codex_protocol::config_types::CollaborationModeMask;\n #[cfg(test)]\n@@ -200,6 +201,7 @@ pub(crate) struct ThreadManagerState {\n thread_created_tx: broadcast::Sender,\n auth_manager: Arc,\n models_manager: Arc,\n+ environment_manager: Arc,\n skills_manager: Arc,\n plugins_manager: Arc,\n mcp_manager: Arc,\n@@ -215,6 +217,7 @@ impl ThreadManager {\n auth_manager: Arc,\n session_source: SessionSource,\n collaboration_modes_config: CollaborationModesC...", - "path": "codex-rs/core/src/thread_manager.rs", - "status": "modified" - }, - { - "additions": 15, - "deletions": 0, - "patch_excerpt": "@@ -244,6 +244,9 @@ async fn shutdown_all_threads_bounded_submits_shutdown_to_every_thread() {\n CodexAuth::from_api_key(\"dummy\"),\n config.model_provider.clone(),\n config.codex_home.clone(),\n+ Arc::new(codex_exec_server::EnvironmentManager::new(\n+ /*exec_server_url*/ None,\n+ )),\n );\n let thread_1 = manager\n .start_thread(config.clone())\n@@ -292,6 +295,9 @@ async fn new_uses_configured_openai_provider_for_model_refresh() {\n auth_manager,\n SessionSource::Exec,\n CollaborationModesConfig::default(),\n+ Arc::new(codex_exec_server::EnvironmentManager::new(\n+ /*exec_server_url*/ None,\n+ )),\n );\n \n let _ = manager.list_models(RefreshStrategy::Online).await;\n@@ -419,6 +425,9 @@ async fn interrupted_fork_snapshot_does_not_synthesize_turn_id_for_legacy_histor\n auth_manager...", - "path": "codex-rs/core/src/thread_manager_tests.rs", - "status": "modified" - }, - { - "additions": 15, - "deletions": 11, - "patch_excerpt": "@@ -105,8 +105,7 @@ impl TestEnv {\n pub async fn local() -> Result {\n let local_cwd_temp_dir = TempDir::new()?;\n let cwd = local_cwd_temp_dir.path().to_path_buf();\n- let environment =\n- codex_exec_server::Environment::create(/*experimental_exec_server_url*/ None).await?;\n+ let environment = codex_exec_server::Environment::create(/*exec_server_url*/ None).await?;\n Ok(Self {\n environment,\n cwd,\n@@ -119,8 +118,8 @@ impl TestEnv {\n &self.environment\n }\n \n- pub fn experimental_exec_server_url(&self) -> Option<&str> {\n- self.environment.experimental_exec_server_url()\n+ pub fn exec_server_url(&self) -> Option<&str> {\n+ self.environment.exec_server_url()\n }\n }\n \n@@ -390,17 +389,17 @@ impl TestCodexBuilder {\n server: &wiremock::MockServer,\n ) -> anyhow::Result Arc;\n }\n \n+#[derive(Debug, Default)]\n+pub struct EnvironmentManager {\n+ exec_server_url: Option,\n+ current_environment: OnceCell>,\n+}\n+\n+impl EnvironmentManager {\n+ pub fn new(exec_server_url: Option) -> Self {\n+ Self {\n+ exec_server_url: normalize_exec_server_url(exec_server_url),\n+ current_environment: OnceCell::new(),\n+ }\n+ }\n+...", - "path": "codex-rs/exec-server/src/environment.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -30,7 +30,9 @@ pub use codex_app_server_protocol::FsRemoveParams;\n pub use codex_app_server_protocol::FsRemoveResponse;\n pub use codex_app_server_protocol::FsWriteFileParams;\n pub use codex_app_server_protocol::FsWriteFileResponse;\n+pub use environment::CODEX_EXEC_SERVER_URL_ENV_VAR;\n pub use environment::Environment;\n+pub use environment::EnvironmentManager;\n pub use environment::ExecutorEnvironment;\n pub use file_system::CopyOptions;\n pub use file_system::CreateDirectoryOptions;", - "path": "codex-rs/exec-server/src/lib.rs", - "status": "modified" - }, - { - "additions": 9, - "deletions": 0, - "patch_excerpt": "@@ -10,6 +10,7 @@ use codex_app_server_protocol::FsRemoveParams;\n use codex_app_server_protocol::FsWriteFileParams;\n use codex_utils_absolute_path::AbsolutePathBuf;\n use tokio::io;\n+use tracing::trace;\n \n use crate::CopyOptions;\n use crate::CreateDirectoryOptions;\n@@ -30,13 +31,15 @@ pub(crate) struct RemoteFileSystem {\n \n impl RemoteFileSystem {\n pub(crate) fn new(client: ExecServerClient) -> Self {\n+ trace!(\"remote fs new\");\n Self { client }\n }\n }\n \n #[async_trait]\n impl ExecutorFileSystem for RemoteFileSystem {\n async fn read_file(&self, path: &AbsolutePathBuf) -> FileSystemResult> {\n+ trace!(\"remote fs read_file\");\n let response = self\n .client\n .fs_read_file(FsReadFileParams { path: path.clone() })\n@@ -51,6 +54,7 @@ impl ExecutorFileSystem for RemoteFileSystem {\n }\n \n async fn write_file(&self, path: &...", - "path": "codex-rs/exec-server/src/remote_file_system.rs", - "status": "modified" - }, - { - "additions": 7, - "deletions": 0, - "patch_excerpt": "@@ -1,5 +1,6 @@\n use async_trait::async_trait;\n use tokio::sync::broadcast;\n+use tracing::trace;\n \n use crate::ExecProcess;\n use crate::ExecServerClient;\n@@ -19,17 +20,20 @@ pub(crate) struct RemoteProcess {\n \n impl RemoteProcess {\n pub(crate) fn new(client: ExecServerClient) -> Self {\n+ trace!(\"remote process new\");\n Self { client }\n }\n }\n \n #[async_trait]\n impl ExecProcess for RemoteProcess {\n async fn start(&self, params: ExecParams) -> Result {\n+ trace!(\"remote process start\");\n self.client.exec(params).await\n }\n \n async fn read(&self, params: ReadParams) -> Result {\n+ trace!(\"remote process read\");\n self.client.read(params).await\n }\n \n@@ -38,14 +42,17 @@ impl ExecProcess for RemoteProcess {\n process_id: &str,\n chunk: Vec,\n ) -> Re...", - "path": "codex-rs/exec-server/src/remote_process.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -30,7 +30,7 @@ async fn create_process_context(use_remote: bool) -> Result {\n _server: Some(server),\n })\n } else {\n- let environment = Environment::create(None).await?;\n+ let environment = Environment::create(/*exec_server_url*/ None).await?;\n Ok(ProcessContext {\n process: environment.get_executor(),\n _server: None,", - "path": "codex-rs/exec-server/tests/exec_process.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -36,7 +36,7 @@ async fn create_file_system_context(use_remote: bool) -> Result IoResult<()> {\n+ let environment_manager = Arc::new(EnvironmentManager::from_env());\n // Parse CLI overrides once and derive the base Config eagerly so later\n // components do not need to work with raw TOML values.\n let cli_kv_overrides = cli_config_overrides.parse_overrides().map_err(|e| {\n@@ -127,7 +130,8 @@ pub async fn run_main(\n let mut processor = MessageProcessor::new(\n outgoing_message_sender,\n arg0_paths,\n- ...", - "path": "codex-rs/mcp-server/src/lib.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 1, - "patch_excerpt": "@@ -1,4 +1,5 @@\n use std::collections::HashMap;\n+use std::sync::Arc;\n \n use codex_arg0::Arg0DispatchPaths;\n use codex_core::AuthManager;\n@@ -7,6 +8,7 @@ use codex_core::config::Config;\n use codex_core::default_client::USER_AGENT_SUFFIX;\n use codex_core::default_client::get_codex_user_agent;\n use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;\n+use codex_exec_server::EnvironmentManager;\n use codex_features::Feature;\n use codex_protocol::ThreadId;\n use codex_protocol::protocol::SessionSource;\n@@ -27,7 +29,6 @@ use rmcp::model::RequestId;\n use rmcp::model::ServerCapabilities;\n use rmcp::model::ToolsCapability;\n use serde_json::json;\n-use std::sync::Arc;\n use tokio::sync::Mutex;\n use tokio::task;\n \n@@ -52,6 +53,7 @@ impl MessageProcessor {\n outgoing: OutgoingMessageSender,\n arg0_paths: Arg0DispatchPaths,\n config: Arc,\n+ ...", - "path": "codex-rs/mcp-server/src/message_processor.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -37,6 +37,7 @@ codex-chatgpt = { workspace = true }\n codex-client = { workspace = true }\n codex-cloud-requirements = { workspace = true }\n codex-core = { workspace = true }\n+codex-exec-server = { workspace = true }\n codex-features = { workspace = true }\n codex-feedback = { workspace = true }\n codex-file-search = { workspace = true }", - "path": "codex-rs/tui/Cargo.toml", - "status": "modified" - }, - { - "additions": 3, - "deletions": 0, - "patch_excerpt": "@@ -77,6 +77,7 @@ use codex_core::models_manager::model_presets::HIDE_GPT_5_1_CODEX_MAX_MIGRATION_\n use codex_core::models_manager::model_presets::HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG;\n #[cfg(target_os = \"windows\")]\n use codex_core::windows_sandbox::WindowsSandboxLevelExt;\n+use codex_exec_server::EnvironmentManager;\n use codex_features::Feature;\n use codex_otel::SessionTelemetry;\n use codex_otel::TelemetryAuthMode;\n@@ -2356,6 +2357,7 @@ impl App {\n \n let harness_overrides =\n normalize_harness_overrides_for_cwd(harness_overrides, &config.cwd)?;\n+ let environment_manager = Arc::new(EnvironmentManager::from_env());\n let thread_manager = Arc::new(ThreadManager::new(\n &config,\n auth_manager.clone(),\n@@ -2365,6 +2367,7 @@ impl App {\n .features\n .enabled(Feature::DefaultModeRequestUserInput),\n ...", - "path": "codex-rs/tui/src/app.rs", - "status": "modified" - }, - { - "additions": 5, - "deletions": 0, - "patch_excerpt": "@@ -14,6 +14,11 @@ codex *args:\n exec *args:\n cargo run --bin codex -- exec \"$@\"\n \n+# Start codex-exec-server, enable the app-server TUI, and run codex-tui.\n+[no-cd]\n+tui-with-exec-server *args:\n+ ./scripts/run_tui_with_exec_server.sh \"$@\"\n+\n # Run the CLI version of the file-search crate.\n file-search *args:\n cargo run --bin codex-file-search -- \"$@\"", - "path": "justfile", - "status": "modified" - }, - { - "additions": 61, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,61 @@\n+#!/usr/bin/env bash\n+\n+set -euo pipefail\n+\n+repo_root=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/..\" && pwd)\"\n+cargo_root=\"$repo_root/codex-rs\"\n+listen_url=\"${CODEX_EXEC_SERVER_LISTEN_URL:-ws://127.0.0.1:0}\"\n+start_timeout_seconds=\"${CODEX_EXEC_SERVER_START_TIMEOUT_SECONDS:-120}\"\n+tmp_dir=\"$(mktemp -d \"${TMPDIR:-/tmp}/codex-tui-with-exec-server.XXXXXX\")\"\n+stdout_log=\"$tmp_dir/exec-server.stdout\"\n+stderr_log=\"$tmp_dir/exec-server.stderr\"\n+server_pid=\"\"\n+exec_server_url=\"\"\n+\n+cleanup() {\n+ if [[ -n \"$server_pid\" ]]; then\n+ kill \"$server_pid\" >/dev/null 2>&1 || true\n+ wait \"$server_pid\" >/dev/null 2>&1 || true\n+ fi\n+ rm -rf \"$tmp_dir\"\n+}\n+\n+trap cleanup EXIT INT TERM HUP\n+\n+(\n+ cd \"$cargo_root\"\n+ cargo run -p codex-exec-server -- --listen \"$listen_url\"\n+) >\"$stdout_log\" 2>\"$stderr_log\" &\n+server_pid=\"$!\"\n+\n+# Wait for the server to print its bound websocket URL befor...", - "path": "scripts/run_tui_with_exec_server.sh", - "status": "added" - } - ], - "linked_issues": [ - "#15785" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Discovered via hourly merged-PR sync: https://github.com/openai/codex/pull/15785" - ], - "primary_pr": { - "body": "Add environment manager that is a singleton and is created early in app-server (before skill manager, before config loading).\r\n\r\nUse an environment variable to point to a running exec server.", - "labels": [], - "merged_at": "2026-03-25T23:14:36Z", - "number": 15785, - "state": "merged", - "title": "Add cached environment manager for exec server URL", - "url": "https://github.com/openai/codex/pull/15785" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-15802.json b/artifacts/github/bundles/openai-codex-pr-15802.json deleted file mode 100644 index dacd154..0000000 --- a/artifacts/github/bundles/openai-codex-pr-15802.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "canvrno-oai", - "committed_at": "2026-03-25T21:19:22Z", - "message": "Change label at app instalp step, hide /apps command", - "sha": "c61d828dfd0d9d4c70e38df67e705598c628af2a", - "url": "https://github.com/openai/codex/commit/c61d828dfd0d9d4c70e38df67e705598c628af2a" - }, - { - "author": "canvrno-oai", - "committed_at": "2026-03-25T21:33:04Z", - "message": "Cleaner display name for OpenAI Curated plugins marketplace", - "sha": "9ede25dfe05c3f730b2bc87b8f78fd65d6b42c32", - "url": "https://github.com/openai/codex/commit/9ede25dfe05c3f730b2bc87b8f78fd65d6b42c32" - }, - { - "author": "canvrno-oai", - "committed_at": "2026-03-25T21:35:04Z", - "message": "fmt", - "sha": "982b96dd911ced68a6bbb9dcff92da0a0da9fa35", - "url": "https://github.com/openai/codex/commit/982b96dd911ced68a6bbb9dcff92da0a0da9fa35" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "DEFAULT_SKILLS_DIR_NAME", - "DEFAULT_MCP_CONFIG_FILE", - "DEFAULT_APP_CONFIG_FILE", - "OPENAI_CURATED_MARKETPLACE_NAME", - "OPENAI_CURATED_MARKETPLACE_DISPLAY_NAME", - "CURATED_REPO_SYNC_STARTED", - "FEATURED_PLUGIN_IDS_CACHE_TTL" - ], - "files": [ - { - "additions": 13, - "deletions": 2, - "patch_excerpt": "@@ -78,6 +78,7 @@ const DEFAULT_SKILLS_DIR_NAME: &str = \"skills\";\n const DEFAULT_MCP_CONFIG_FILE: &str = \".mcp.json\";\n const DEFAULT_APP_CONFIG_FILE: &str = \".app.json\";\n pub const OPENAI_CURATED_MARKETPLACE_NAME: &str = \"openai-curated\";\n+pub const OPENAI_CURATED_MARKETPLACE_DISPLAY_NAME: &str = \"OpenAI Curated\";\n static CURATED_REPO_SYNC_STARTED: AtomicBool = AtomicBool::new(false);\n const FEATURED_PLUGIN_IDS_CACHE_TTL: std::time::Duration =\n std::time::Duration::from_secs(60 * 60 * 3);\n@@ -889,7 +890,13 @@ impl PluginsManager {\n (!plugins.is_empty()).then_some(ConfiguredMarketplace {\n name: marketplace.name,\n path: marketplace.path,\n- interface: marketplace.interface,\n+ interface: if marketplace_name == OPENAI_CURATED_MARKETPLACE_NAME {\n+ Some(MarketplaceInterface {...", - "path": "codex-rs/core/src/plugins/manager.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1386,7 +1386,7 @@ plugins = true\n path: AbsolutePathBuf::try_from(curated_root.join(\".agents/plugins/marketplace.json\"))\n .unwrap(),\n interface: Some(MarketplaceInterface {\n- display_name: Some(\"ChatGPT Official\".to_string()),\n+ display_name: Some(OPENAI_CURATED_MARKETPLACE_DISPLAY_NAME.to_string()),\n }),\n plugins: vec![ConfiguredMarketplacePlugin {\n id: \"linear@openai-curated\".to_string(),", - "path": "codex-rs/core/src/plugins/manager_tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -68,6 +68,7 @@ impl CommandPopup {\n slash_commands::builtins_for_input(flags.into())\n .into_iter()\n .filter(|(name, _)| !name.starts_with(\"debug\"))\n+ .filter(|(_, cmd)| *cmd != SlashCommand::Apps)\n .collect();\n // Exclude prompts that collide with builtin command names and sort by name.\n let exclude: HashSet = builtins.iter().map(|(n, _)| (*n).to_string()).collect();", - "path": "codex-rs/tui/src/bottom_pane/command_popup.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -418,7 +418,7 @@ impl ChatWidget {\n let status_label = if is_installed {\n \"Already installed in this session.\"\n } else {\n- \"Not installed yet.\"\n+ \"Install the required Apps in ChatGPT to continue:\"\n };\n let mut header = ColumnRenderable::new();\n header.push(Line::from(\"Plugins\".bold()));", - "path": "codex-rs/tui/src/chatwidget/plugins.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -68,6 +68,7 @@ impl CommandPopup {\n slash_commands::builtins_for_input(flags.into())\n .into_iter()\n .filter(|(name, _)| !name.starts_with(\"debug\"))\n+ .filter(|(_, cmd)| *cmd != SlashCommand::Apps)\n .collect();\n // Exclude prompts that collide with builtin command names and sort by name.\n let exclude: HashSet = builtins.iter().map(|(n, _)| (*n).to_string()).collect();", - "path": "codex-rs/tui_app_server/src/bottom_pane/command_popup.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -418,7 +418,7 @@ impl ChatWidget {\n let status_label = if is_installed {\n \"Already installed in this session.\"\n } else {\n- \"Not installed yet.\"\n+ \"Install the required Apps in ChatGPT to continue:\"\n };\n let mut header = ColumnRenderable::new();\n header.push(Line::from(\"Plugins\".bold()));", - "path": "codex-rs/tui_app_server/src/chatwidget/plugins.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Discovered via hourly merged-PR sync: https://github.com/openai/codex/pull/15802" - ], - "primary_pr": { - "body": "- Add \"OpenAI Curated\" display name for `openai-curated` marketplace\r\n- Hide /apps menu\r\n- Change app install phase display text", - "labels": [], - "merged_at": "2026-03-25T23:09:20Z", - "number": 15802, - "state": "merged", - "title": "TUI plugin menu polish", - "url": "https://github.com/openai/codex/pull/15802" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-16010.json b/artifacts/github/bundles/openai-codex-pr-16010.json deleted file mode 100644 index 0efc971..0000000 --- a/artifacts/github/bundles/openai-codex-pr-16010.json +++ /dev/null @@ -1,142 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "jif-oai", - "committed_at": "2026-03-27T14:54:35Z", - "message": "feat: add mailbox concept for wait", - "sha": "ddcdd1a72a006d33e1b25d9e4570ef0c977592d9", - "url": "https://github.com/openai/codex/commit/ddcdd1a72a006d33e1b25d9e4570ef0c977592d9" - }, - { - "author": "jif-oai", - "committed_at": "2026-03-27T15:12:07Z", - "message": "clippy", - "sha": "3670d0e1944a813c61f26b76dabb97a032f5271d", - "url": "https://github.com/openai/codex/commit/3670d0e1944a813c61f26b76dabb97a032f5271d" - }, - { - "author": "jif-oai", - "committed_at": "2026-03-27T17:07:14Z", - "message": "Merge remote-tracking branch 'origin/main' into jif/wait-mail-box", - "sha": "2fc9d2f604e66327f0f573ff85620b10f0b38f97", - "url": "https://github.com/openai/codex/commit/2fc9d2f604e66327f0f573ff85620b10f0b38f97" - }, - { - "author": "jif-oai", - "committed_at": "2026-03-30T09:12:57Z", - "message": "Merge remote-tracking branch 'origin/main' into jif/wait-mail-box", - "sha": "466a4ca32e4cf677d6ace57cd4298ca57bd542a8", - "url": "https://github.com/openai/codex/commit/466a4ca32e4cf677d6ace57cd4298ca57bd542a8" - }, - { - "author": "jif-oai", - "committed_at": "2026-03-30T09:33:53Z", - "message": "nit fixes", - "sha": "89fa34b83e4f7fd51b04fa696c348a36a52ab96a", - "url": "https://github.com/openai/codex/commit/89fa34b83e4f7fd51b04fa696c348a36a52ab96a" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "TODO", - "DEFAULT_WAIT_TIMEOUT_MS" - ], - "files": [ - { - "additions": 0, - "deletions": 19, - "patch_excerpt": "@@ -28,25 +28,6 @@ pub(crate) async fn resolve_agent_target(\n })\n }\n \n-/// Resolves multiple tool-facing agent targets to thread ids.\n-pub(crate) async fn resolve_agent_targets(\n- session: &Arc,\n- turn: &Arc,\n- targets: Vec,\n-) -> Result, FunctionCallError> {\n- if targets.is_empty() {\n- return Err(FunctionCallError::RespondToModel(\n- \"agent targets must be non-empty\".to_string(),\n- ));\n- }\n-\n- let mut resolved = Vec::with_capacity(targets.len());\n- for target in &targets {\n- resolved.push(resolve_agent_target(session, turn, target).await?);\n- }\n- Ok(resolved)\n-}\n-\n fn register_session_root(session: &Arc, turn: &Arc) {\n session\n .services", - "path": "codex-rs/core/src/agent/agent_resolver.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 6, - "patch_excerpt": "@@ -448,12 +448,7 @@ async fn send_inter_agent_communication_without_turn_queues_message_without_trig\n \n timeout(Duration::from_secs(5), async {\n loop {\n- if thread\n- .codex\n- .session\n- .has_queued_response_items_for_next_turn()\n- .await\n- {\n+ if thread.codex.session.has_pending_input().await {\n break;\n }\n sleep(Duration::from_millis(10)).await;", - "path": "codex-rs/core/src/agent/control_tests.rs", - "status": "modified" - }, - { - "additions": 161, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,161 @@\n+use codex_protocol::protocol::InterAgentCommunication;\n+use std::collections::VecDeque;\n+use std::sync::atomic::AtomicU64;\n+use std::sync::atomic::Ordering;\n+use tokio::sync::mpsc;\n+use tokio::sync::watch;\n+\n+#[cfg(test)]\n+use codex_protocol::AgentPath;\n+\n+pub(crate) struct Mailbox {\n+ tx: mpsc::UnboundedSender,\n+ next_seq: AtomicU64,\n+ seq_tx: watch::Sender,\n+}\n+\n+pub(crate) struct MailboxReceiver {\n+ rx: mpsc::UnboundedReceiver,\n+ pending_mails: VecDeque,\n+}\n+\n+impl Mailbox {\n+ pub(crate) fn new() -> (Self, MailboxReceiver) {\n+ let (tx, rx) = mpsc::unbounded_channel();\n+ let (seq_tx, _) = watch::channel(0);\n+ (\n+ Self {\n+ tx,\n+ next_seq: AtomicU64::new(0),\n+ seq_tx,\n+ },\n+ ...", - "path": "codex-rs/core/src/agent/mailbox.rs", - "status": "added" - }, - { - "additions": 3, - "deletions": 0, - "patch_excerpt": "@@ -1,11 +1,14 @@\n pub(crate) mod agent_resolver;\n pub(crate) mod control;\n+pub(crate) mod mailbox;\n mod registry;\n pub(crate) mod role;\n pub(crate) mod status;\n \n pub(crate) use codex_protocol::protocol::AgentStatus;\n pub(crate) use control::AgentControl;\n+pub(crate) use mailbox::Mailbox;\n+pub(crate) use mailbox::MailboxReceiver;\n pub(crate) use registry::exceeds_thread_spawn_depth_limit;\n pub(crate) use registry::next_thread_spawn_depth;\n pub(crate) use status::agent_status_from_event;", - "path": "codex-rs/core/src/agent/mod.rs", - "status": "modified" - }, - { - "additions": 54, - "deletions": 19, - "patch_excerpt": "@@ -11,6 +11,8 @@ use crate::CodexAuth;\n use crate::SandboxState;\n use crate::agent::AgentControl;\n use crate::agent::AgentStatus;\n+use crate::agent::Mailbox;\n+use crate::agent::MailboxReceiver;\n use crate::agent::agent_status_from_event;\n use crate::apps::render_apps_section;\n use crate::auth_env_telemetry::collect_auth_env_telemetry;\n@@ -97,6 +99,7 @@ use codex_protocol::permissions::FileSystemSandboxPolicy;\n use codex_protocol::permissions::NetworkSandboxPolicy;\n use codex_protocol::protocol::FileChange;\n use codex_protocol::protocol::HasLegacyEvent;\n+use codex_protocol::protocol::InterAgentCommunication;\n use codex_protocol::protocol::ItemCompletedEvent;\n use codex_protocol::protocol::ItemStartedEvent;\n use codex_protocol::protocol::RawResponseItemEvent;\n@@ -806,7 +809,9 @@ pub(crate) struct Session {\n pending_mcp_server_refresh_config: Mutex>,\n ...", - "path": "codex-rs/core/src/codex.rs", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -2737,6 +2737,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {\n skills_outcome,\n );\n \n+ let (mailbox, mailbox_rx) = crate::agent::Mailbox::new();\n let session = Session {\n conversation_id,\n tx_event,\n@@ -2747,6 +2748,8 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {\n pending_mcp_server_refresh_config: Mutex::new(None),\n conversation: Arc::new(RealtimeConversationManager::new()),\n active_turn: Mutex::new(None),\n+ mailbox,\n+ mailbox_rx: Mutex::new(mailbox_rx),\n idle_pending_input: Mutex::new(Vec::new()),\n guardian_review_session: crate::guardian::GuardianReviewSessionManager::default(),\n services,\n@@ -3577,6 +3580,7 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(\n skills_outcome,\n ));\n \n+ l...", - "path": "codex-rs/core/src/codex_tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 4, - "patch_excerpt": "@@ -180,10 +180,7 @@ impl CodexThread {\n .session\n .queue_response_items_for_next_turn(items)\n .await;\n- self.codex\n- .session\n- .ensure_task_for_queued_response_items()\n- .await;\n+ self.codex.session.ensure_task_for_pending_inputs().await;\n }\n \n Ok(submission_id)", - "path": "codex-rs/core/src/codex_thread.rs", - "status": "modified" - }, - { - "additions": 27, - "deletions": 4, - "patch_excerpt": "@@ -231,13 +231,17 @@ impl Session {\n };\n \n let queued_response_items = self.take_queued_response_items_for_next_turn().await;\n+ let mailbox_items = self.get_pending_input().await;\n let mut active = self.active_turn.lock().await;\n let mut turn = ActiveTurn::default();\n let mut turn_state = turn.turn_state.lock().await;\n turn_state.token_usage_at_turn_start = token_usage_at_turn_start;\n for item in queued_response_items {\n turn_state.push_pending_input(item);\n }\n+ for item in mailbox_items {\n+ turn_state.push_pending_input(item);\n+ }\n drop(turn_state);\n \n let timer = turn_context\n@@ -258,16 +262,35 @@ impl Session {\n *active = Some(turn);\n }\n \n- pub(crate) async fn ensure_task_for_queued_response_items(self: &Arc) {\n- if !self.has_queued_r...", - "path": "codex-rs/core/src/tasks/mod.rs", - "status": "modified" - }, - { - "additions": 332, - "deletions": 62, - "patch_excerpt": "@@ -940,12 +940,7 @@ async fn multi_agent_v2_send_message_interrupts_busy_child_without_triggering_tu\n \n timeout(Duration::from_secs(5), async {\n loop {\n- if !thread\n- .codex\n- .session\n- .has_queued_response_items_for_next_turn()\n- .await\n- {\n+ if !thread.codex.session.has_pending_input().await {\n tokio::time::sleep(Duration::from_millis(10)).await;\n continue;\n }\n@@ -1788,27 +1783,78 @@ async fn wait_agent_rejects_empty_targets() {\n }\n \n #[tokio::test]\n-async fn multi_agent_v2_wait_agent_accepts_targets_argument() {\n+async fn multi_agent_v2_wait_agent_accepts_timeout_only_argument() {\n let (mut session, mut turn) = make_session_and_context().await;\n- let target = ThreadId::new().to_string();\n let manager = thread_manager();\n+ ...", - "path": "codex-rs/core/src/tools/handlers/multi_agents_tests.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 4, - "patch_excerpt": "@@ -2,9 +2,7 @@\n \n use crate::agent::AgentStatus;\n use crate::agent::agent_resolver::resolve_agent_target;\n-use crate::agent::agent_resolver::resolve_agent_targets;\n use crate::agent::exceeds_thread_spawn_depth_limit;\n-use crate::codex::Session;\n use crate::function_tool::FunctionCallError;\n use crate::tools::context::ToolInvocation;\n use crate::tools::context::ToolOutput;\n@@ -15,12 +13,10 @@ use crate::tools::registry::ToolHandler;\n use crate::tools::registry::ToolKind;\n use async_trait::async_trait;\n use codex_protocol::AgentPath;\n-use codex_protocol::ThreadId;\n use codex_protocol::models::ResponseInputItem;\n use codex_protocol::openai_models::ReasoningEffort;\n use codex_protocol::protocol::CollabAgentInteractionBeginEvent;\n use codex_protocol::protocol::CollabAgentInteractionEndEvent;\n-use codex_protocol::protocol::CollabAgentRef;\n use codex_protocol::protocol::CollabAgentSpawnBeginEv...", - "path": "codex-rs/core/src/tools/handlers/multi_agents_v2.rs", - "status": "modified" - }, - { - "additions": 14, - "deletions": 117, - "patch_excerpt": "@@ -1,11 +1,6 @@\n use super::*;\n-use crate::agent::status::is_final;\n-use futures::FutureExt;\n-use futures::StreamExt;\n-use futures::stream::FuturesUnordered;\n use std::collections::HashMap;\n use std::time::Duration;\n-use tokio::sync::watch::Receiver;\n use tokio::time::Instant;\n use tokio::time::timeout_at;\n \n@@ -33,21 +28,6 @@ impl ToolHandler for Handler {\n } = invocation;\n let arguments = function_arguments(payload)?;\n let args: WaitArgs = parse_arguments(&arguments)?;\n- let receiver_thread_ids = resolve_agent_targets(&session, &turn, args.targets).await?;\n- let mut receiver_agents = Vec::with_capacity(receiver_thread_ids.len());\n- for receiver_thread_id in &receiver_thread_ids {\n- let agent_metadata = session\n- .services\n- .agent_control\n- .get_agent_metadata(*receiver_thread_id)\n- ...", - "path": "codex-rs/core/src/tools/handlers/multi_agents_v2/wait.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Discovered via hourly merged-PR sync: https://github.com/openai/codex/pull/16010" - ], - "primary_pr": { - "body": "Add a mailbox we can use for inter-agent communication\r\n`wait` is now based on it and don't take target anymore", - "labels": [], - "merged_at": "2026-03-30T09:47:21Z", - "number": 16010, - "state": "merged", - "title": "feat: add mailbox concept for wait", - "url": "https://github.com/openai/codex/pull/16010" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-16082.json b/artifacts/github/bundles/openai-codex-pr-16082.json deleted file mode 100644 index 4c9c170..0000000 --- a/artifacts/github/bundles/openai-codex-pr-16082.json +++ /dev/null @@ -1,320 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "mzeng-openai", - "committed_at": "2026-03-28T07:17:39Z", - "message": "update", - "sha": "9d85fa81b35c240a2e385d0e56c4f8d928e1ac1a", - "url": "https://github.com/openai/codex/commit/9d85fa81b35c240a2e385d0e56c4f8d928e1ac1a" - }, - { - "author": "mzeng-openai", - "committed_at": "2026-03-28T07:24:08Z", - "message": "update", - "sha": "c51c16fca9cdd4b6a9f10cb8d6661675a5d2c7b9", - "url": "https://github.com/openai/codex/commit/c51c16fca9cdd4b6a9f10cb8d6661675a5d2c7b9" - }, - { - "author": "mzeng-openai", - "committed_at": "2026-04-01T02:25:58Z", - "message": "Merge branch 'main' of github.com:openai/codex into dev/mzeng/mcp_apps_1", - "sha": "1e376a90d3f82ee0368bcf46b3bee555c6cedd14", - "url": "https://github.com/openai/codex/commit/1e376a90d3f82ee0368bcf46b3bee555c6cedd14" - }, - { - "author": "mzeng-openai", - "committed_at": "2026-04-01T03:17:03Z", - "message": "update", - "sha": "1c7bf9d35a5efdd91e7e7d6323344ecbe92e73a0", - "url": "https://github.com/openai/codex/commit/1c7bf9d35a5efdd91e7e7d6323344ecbe92e73a0" - }, - { - "author": "mzeng-openai", - "committed_at": "2026-04-01T03:27:16Z", - "message": "update", - "sha": "41052984fd37c1ab72c7d0433099eacf224610d0", - "url": "https://github.com/openai/codex/commit/41052984fd37c1ab72c7d0433099eacf224610d0" - }, - { - "author": "mzeng-openai", - "committed_at": "2026-04-01T19:17:10Z", - "message": "update", - "sha": "86f6eb8e450ed0323cfd9b0aaf73dd1ea3e1f344", - "url": "https://github.com/openai/codex/commit/86f6eb8e450ed0323cfd9b0aaf73dd1ea3e1f344" - }, - { - "author": "mzeng-openai", - "committed_at": "2026-04-01T19:30:48Z", - "message": "update", - "sha": "a5435ad3505d22c3e567ad7dbdc8f560b4d84468", - "url": "https://github.com/openai/codex/commit/a5435ad3505d22c3e567ad7dbdc8f560b4d84468" - }, - { - "author": "mzeng-openai", - "committed_at": "2026-04-06T17:29:04Z", - "message": "update", - "sha": "85f6767fa11caa8aaca283ec255ba0356a60a963", - "url": "https://github.com/openai/codex/commit/85f6767fa11caa8aaca283ec255ba0356a60a963" - }, - { - "author": "mzeng-openai", - "committed_at": "2026-04-06T17:29:12Z", - "message": "Merge branch 'main' of github.com:openai/codex into dev/mzeng/mcp_apps_1", - "sha": "e99714180fa725eebdcf2b4eeabc127b76d69577", - "url": "https://github.com/openai/codex/commit/e99714180fa725eebdcf2b4eeabc127b76d69577" - }, - { - "author": "mzeng-openai", - "committed_at": "2026-04-06T18:23:05Z", - "message": "Merge branch 'main' of github.com:openai/codex into dev/mzeng/mcp_apps_1", - "sha": "8f0f094120b4fc36a707b5d5a91a0b965da7bb3d", - "url": "https://github.com/openai/codex/commit/8f0f094120b4fc36a707b5d5a91a0b965da7bb3d" - }, - { - "author": "mzeng-openai", - "committed_at": "2026-04-06T19:04:58Z", - "message": "Merge branch 'main' of github.com:openai/codex into dev/mzeng/mcp_apps_1", - "sha": "4fbaed2074b62841fdf629d52dbb318056a8c71e", - "url": "https://github.com/openai/codex/commit/4fbaed2074b62841fdf629d52dbb318056a8c71e" - }, - { - "author": "mzeng-openai", - "committed_at": "2026-04-06T19:19:34Z", - "message": "update", - "sha": "4d75dfba665cac6767506ffa6727fa534fa71549", - "url": "https://github.com/openai/codex/commit/4d75dfba665cac6767506ffa6727fa534fa71549" - }, - { - "author": "mzeng-openai", - "committed_at": "2026-04-06T19:29:13Z", - "message": "Merge branch 'main' of github.com:openai/codex into dev/mzeng/mcp_apps_1", - "sha": "a1979b58c99b3e6ec806b048b88f54084b34f76a", - "url": "https://github.com/openai/codex/commit/a1979b58c99b3e6ec806b048b88f54084b34f76a" - }, - { - "author": "mzeng-openai", - "committed_at": "2026-04-06T20:17:18Z", - "message": "Merge branch 'main' of github.com:openai/codex into dev/mzeng/mcp_apps_1", - "sha": "b6896fd2c703c5cca5f287748e7eb7f25574963a", - "url": "https://github.com/openai/codex/commit/b6896fd2c703c5cca5f287748e7eb7f25574963a" - }, - { - "author": "mzeng-openai", - "committed_at": "2026-04-06T20:23:01Z", - "message": "update", - "sha": "cb3aad9628e88e6463ab082fa647cac641310bf4", - "url": "https://github.com/openai/codex/commit/cb3aad9628e88e6463ab082fa647cac641310bf4" - }, - { - "author": "mzeng-openai", - "committed_at": "2026-04-06T21:12:11Z", - "message": "Merge branch 'main' of github.com:openai/codex into dev/mzeng/mcp_apps_1", - "sha": "8891d14471f256ffa5397f9a2f4dad550c0eb656", - "url": "https://github.com/openai/codex/commit/8891d14471f256ffa5397f9a2f4dad550c0eb656" - }, - { - "author": "mzeng-openai", - "committed_at": "2026-04-06T21:53:07Z", - "message": "update", - "sha": "e7c6768e5454c7e05065382d709fd3151ed7f701", - "url": "https://github.com/openai/codex/commit/e7c6768e5454c7e05065382d709fd3151ed7f701" - }, - { - "author": "mzeng-openai", - "committed_at": "2026-04-06T22:59:47Z", - "message": "update", - "sha": "5c5e68d30d00b569826df03a85f76d5383ed024a", - "url": "https://github.com/openai/codex/commit/5c5e68d30d00b569826df03a85f76d5383ed024a" - }, - { - "author": "mzeng-openai", - "committed_at": "2026-04-06T23:51:12Z", - "message": "Merge branch 'main' of github.com:openai/codex into dev/mzeng/mcp_apps_1", - "sha": "4908e019b3ee82fae33bdf62cbec858713e891b5", - "url": "https://github.com/openai/codex/commit/4908e019b3ee82fae33bdf62cbec858713e891b5" - } - ], - "default_branch": "main", - "docs_refs": [ - "codex-rs/app-server/README.md" - ], - "examples_refs": [], - "extracted_flags": [ - "URI", - "MCP", - "GENERATED", - "CODE", - "NOT", - "MODIFY", - "HAND", - "JSONRPCE", - "INTERNAL_ERROR_CODE", - "INVALID_REQUEST_ERROR_CODE", - "JSONRPCR", - "JSON", - "DEFAULT_READ_TIMEOUT", - "TEST_RESOURCE_URI", - "TEST_BLOB_RESOURCE_URI", - "TEST_RESOURCE_BLOB", - "TEST_RESOURCE_TEXT", - "V_2025_06_18" - ], - "files": [ - { - "additions": 43, - "deletions": 0, - "patch_excerpt": "@@ -1219,6 +1219,25 @@\n }\n ]\n },\n+ \"McpResourceReadParams\": {\n+ \"properties\": {\n+ \"server\": {\n+ \"type\": \"string\"\n+ },\n+ \"threadId\": {\n+ \"type\": \"string\"\n+ },\n+ \"uri\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"server\",\n+ \"threadId\",\n+ \"uri\"\n+ ],\n+ \"type\": \"object\"\n+ },\n \"McpServerOauthLoginParams\": {\n \"properties\": {\n \"name\": {\n@@ -4438,6 +4457,30 @@\n \"title\": \"McpServerStatus/listRequest\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"id\": {\n+ \"$ref\": \"#/definitions/RequestId\"\n+ },\n+ \"method\": {\n+ \"enum\": [\n+ \"mcpServer/resource/read\"\n+ ],\n+ \"title\": \"McpServer/resource/readRequestMethod\",\n+ \"type\": \"string\"\n+ },\n+ ...", - "path": "codex-rs/app-server-protocol/schema/json/ClientRequest.json", - "status": "modified" - }, - { - "additions": 112, - "deletions": 0, - "patch_excerpt": "@@ -1201,6 +1201,30 @@\n \"title\": \"McpServerStatus/listRequest\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"id\": {\n+ \"$ref\": \"#/definitions/v2/RequestId\"\n+ },\n+ \"method\": {\n+ \"enum\": [\n+ \"mcpServer/resource/read\"\n+ ],\n+ \"title\": \"McpServer/resource/readRequestMethod\",\n+ \"type\": \"string\"\n+ },\n+ \"params\": {\n+ \"$ref\": \"#/definitions/v2/McpResourceReadParams\"\n+ }\n+ },\n+ \"required\": [\n+ \"id\",\n+ \"method\",\n+ \"params\"\n+ ],\n+ \"title\": \"McpServer/resource/readRequest\",\n+ \"type\": \"object\"\n+ },\n {\n \"properties\": {\n \"id\": {\n@@ -8940,6 +8964,43 @@\n ],\n \"type\": \"string\"\n ...", - "path": "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json", - "status": "modified" - }, - { - "additions": 112, - "deletions": 0, - "patch_excerpt": "@@ -1776,6 +1776,30 @@\n \"title\": \"McpServerStatus/listRequest\",\n \"type\": \"object\"\n },\n+ {\n+ \"properties\": {\n+ \"id\": {\n+ \"$ref\": \"#/definitions/RequestId\"\n+ },\n+ \"method\": {\n+ \"enum\": [\n+ \"mcpServer/resource/read\"\n+ ],\n+ \"title\": \"McpServer/resource/readRequestMethod\",\n+ \"type\": \"string\"\n+ },\n+ \"params\": {\n+ \"$ref\": \"#/definitions/McpResourceReadParams\"\n+ }\n+ },\n+ \"required\": [\n+ \"id\",\n+ \"method\",\n+ \"params\"\n+ ],\n+ \"title\": \"McpServer/resource/readRequest\",\n+ \"type\": \"object\"\n+ },\n {\n \"properties\": {\n \"id\": {\n@@ -5763,6 +5787,43 @@\n ],\n \"type\": \"string\"\n },\n+ ...", - "path": "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json", - "status": "modified" - }, - { - "additions": 21, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,21 @@\n+{\n+ \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n+ \"properties\": {\n+ \"server\": {\n+ \"type\": \"string\"\n+ },\n+ \"threadId\": {\n+ \"type\": \"string\"\n+ },\n+ \"uri\": {\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"server\",\n+ \"threadId\",\n+ \"uri\"\n+ ],\n+ \"title\": \"McpResourceReadParams\",\n+ \"type\": \"object\"\n+}\n\\ No newline at end of file", - "path": "codex-rs/app-server-protocol/schema/json/v2/McpResourceReadParams.json", - "status": "added" - }, - { - "additions": 69, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,69 @@\n+{\n+ \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n+ \"definitions\": {\n+ \"ResourceContent\": {\n+ \"anyOf\": [\n+ {\n+ \"properties\": {\n+ \"_meta\": true,\n+ \"mimeType\": {\n+ \"type\": [\n+ \"string\",\n+ \"null\"\n+ ]\n+ },\n+ \"text\": {\n+ \"type\": \"string\"\n+ },\n+ \"uri\": {\n+ \"description\": \"The URI of this resource.\",\n+ \"type\": \"string\"\n+ }\n+ },\n+ \"required\": [\n+ \"text\",\n+ \"uri\"\n+ ],\n+ \"type\": \"object\"\n+ },\n+ {\n+ \"properties\": {\n+ \"_meta\": true,\n+ \"blob\": {\n+ \"type\": \"string\"\n+ },\n+ \"mimeType\": {\n+ \"type\": [\n+ \"string\",\n+ ...", - "path": "codex-rs/app-server-protocol/schema/json/v2/McpResourceReadResponse.json", - "status": "added" - }, - { - "additions": 2, - "deletions": 1, - "patch_excerpt": "@@ -33,6 +33,7 @@ import type { FsWriteFileParams } from \"./v2/FsWriteFileParams\";\n import type { GetAccountParams } from \"./v2/GetAccountParams\";\n import type { ListMcpServerStatusParams } from \"./v2/ListMcpServerStatusParams\";\n import type { LoginAccountParams } from \"./v2/LoginAccountParams\";\n+import type { McpResourceReadParams } from \"./v2/McpResourceReadParams\";\n import type { McpServerOauthLoginParams } from \"./v2/McpServerOauthLoginParams\";\n import type { ModelListParams } from \"./v2/ModelListParams\";\n import type { PluginInstallParams } from \"./v2/PluginInstallParams\";\n@@ -64,4 +65,4 @@ import type { WindowsSandboxSetupStartParams } from \"./v2/WindowsSandboxSetupSta\n /**\n * Request from the client to the server.\n */\n-export type ClientRequest ={ \"method\": \"initialize\", id: RequestId, params: InitializeParams, } | { \"method\": \"thread/start\", id: RequestId, params: ThreadStartPa...", - "path": "codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts", - "status": "modified" - }, - { - "additions": 17, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,17 @@\n+// GENERATED CODE! DO NOT MODIFY BY HAND!\n+\n+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n+import type { JsonValue } from \"./serde_json/JsonValue\";\n+\n+/**\n+ * Contents returned when reading a resource from an MCP server.\n+ */\n+export type ResourceContent = { \n+/**\n+ * The URI of this resource.\n+ */\n+uri: string, mimeType?: string, text: string, _meta?: JsonValue, } | { \n+/**\n+ * The URI of this resource.\n+ */\n+uri: string, mimeType?: string, blob: string, _meta?: JsonValue, };", - "path": "codex-rs/app-server-protocol/schema/typescript/ResourceContent.ts", - "status": "added" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -55,6 +55,7 @@ export type { ReasoningItemReasoningSummary } from \"./ReasoningItemReasoningSumm\n export type { ReasoningSummary } from \"./ReasoningSummary\";\n export type { RequestId } from \"./RequestId\";\n export type { Resource } from \"./Resource\";\n+export type { ResourceContent } from \"./ResourceContent\";\n export type { ResourceTemplate } from \"./ResourceTemplate\";\n export type { ResponseItem } from \"./ResponseItem\";\n export type { ReviewDecision } from \"./ReviewDecision\";", - "path": "codex-rs/app-server-protocol/schema/typescript/index.ts", - "status": "modified" - }, - { - "additions": 5, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,5 @@\n+// GENERATED CODE! DO NOT MODIFY BY HAND!\n+\n+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n+\n+export type McpResourceReadParams = { threadId: string, server: string, uri: string, };", - "path": "codex-rs/app-server-protocol/schema/typescript/v2/McpResourceReadParams.ts", - "status": "added" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,6 @@\n+// GENERATED CODE! DO NOT MODIFY BY HAND!\n+\n+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n+import type { ResourceContent } from \"../ResourceContent\";\n+\n+export type McpResourceReadResponse = { contents: Array, };", - "path": "codex-rs/app-server-protocol/schema/typescript/v2/McpResourceReadResponse.ts", - "status": "added" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -172,6 +172,8 @@ export type { McpElicitationTitledSingleSelectEnumSchema } from \"./McpElicitatio\n export type { McpElicitationUntitledEnumItems } from \"./McpElicitationUntitledEnumItems\";\n export type { McpElicitationUntitledMultiSelectEnumSchema } from \"./McpElicitationUntitledMultiSelectEnumSchema\";\n export type { McpElicitationUntitledSingleSelectEnumSchema } from \"./McpElicitationUntitledSingleSelectEnumSchema\";\n+export type { McpResourceReadParams } from \"./McpResourceReadParams\";\n+export type { McpResourceReadResponse } from \"./McpResourceReadResponse\";\n export type { McpServerElicitationAction } from \"./McpServerElicitationAction\";\n export type { McpServerElicitationRequestParams } from \"./McpServerElicitationRequestParams\";\n export type { McpServerElicitationRequestResponse } from \"./McpServerElicitationRequestResponse\";", - "path": "codex-rs/app-server-protocol/schema/typescript/v2/index.ts", - "status": "modified" - }, - { - "additions": 5, - "deletions": 0, - "patch_excerpt": "@@ -459,6 +459,11 @@ client_request_definitions! {\n response: v2::ListMcpServerStatusResponse,\n },\n \n+ McpResourceRead => \"mcpServer/resource/read\" {\n+ params: v2::McpResourceReadParams,\n+ response: v2::McpResourceReadResponse,\n+ },\n+\n WindowsSandboxSetupStart => \"windowsSandbox/setupStart\" {\n params: v2::WindowsSandboxSetupStartParams,\n response: v2::WindowsSandboxSetupStartResponse,", - "path": "codex-rs/app-server-protocol/src/protocol/common.rs", - "status": "modified" - }, - { - "additions": 17, - "deletions": 0, - "patch_excerpt": "@@ -29,6 +29,7 @@ use codex_protocol::config_types::WebSearchToolConfig;\n use codex_protocol::items::AgentMessageContent as CoreAgentMessageContent;\n use codex_protocol::items::TurnItem as CoreTurnItem;\n use codex_protocol::mcp::Resource as McpResource;\n+pub use codex_protocol::mcp::ResourceContent as McpResourceContent;\n use codex_protocol::mcp::ResourceTemplate as McpResourceTemplate;\n use codex_protocol::mcp::Tool as McpTool;\n use codex_protocol::memory_citation::MemoryCitation as CoreMemoryCitation;\n@@ -1984,6 +1985,22 @@ pub struct ListMcpServerStatusResponse {\n pub next_cursor: Option,\n }\n \n+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]\n+#[serde(rename_all = \"camelCase\")]\n+#[ts(export_to = \"v2/\")]\n+pub struct McpResourceReadParams {\n+ pub thread_id: String,\n+ pub server: String,\n+ pub uri: String,\n+}\n+\n+#[derive(Serialize, Deseriali...", - "path": "codex-rs/app-server-protocol/src/protocol/v2.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 1, - "patch_excerpt": "@@ -187,7 +187,8 @@ Example with notification opt-out:\n - `mcpServer/oauth/login` \u2014 start an OAuth login for a configured MCP server; returns an `authorization_url` and later emits `mcpServer/oauthLogin/completed` once the browser flow finishes.\n - `tool/requestUserInput` \u2014 prompt the user with 1\u20133 short questions for a tool call and return their answers (experimental).\n - `config/mcpServer/reload` \u2014 reload MCP server config from disk and queue a refresh for loaded threads (applied on each thread's next active turn); returns `{}`. Use this after editing `config.toml` without restarting the server.\n-- `mcpServerStatus/list` \u2014 enumerate configured MCP servers with their tools and auth status, plus optional resources/resource templates when requested; supports cursor+limit pagination. If `detail` is omitted, the server defaults to `full`.\n+- `mcpServerStatus/list` \u2014 enumerate configured MCP...", - "path": "codex-rs/app-server/README.md", - "status": "modified" - }, - { - "additions": 58, - "deletions": 0, - "patch_excerpt": "@@ -76,6 +76,8 @@ use codex_app_server_protocol::LoginAccountResponse;\n use codex_app_server_protocol::LoginApiKeyParams;\n use codex_app_server_protocol::LogoutAccountResponse;\n use codex_app_server_protocol::MarketplaceInterface;\n+use codex_app_server_protocol::McpResourceReadParams;\n+use codex_app_server_protocol::McpResourceReadResponse;\n use codex_app_server_protocol::McpServerOauthLoginCompletedNotification;\n use codex_app_server_protocol::McpServerOauthLoginParams;\n use codex_app_server_protocol::McpServerOauthLoginResponse;\n@@ -878,6 +880,10 @@ impl CodexMessageProcessor {\n self.list_mcp_server_status(to_connection_request_id(request_id), params)\n .await;\n }\n+ ClientRequest::McpResourceRead { request_id, params } => {\n+ self.read_mcp_resource(to_connection_request_id(request_id), params)\n+ ...", - "path": "codex-rs/app-server/src/codex_message_processor.rs", - "status": "modified" - }, - { - "additions": 10, - "deletions": 0, - "patch_excerpt": "@@ -47,6 +47,7 @@ use codex_app_server_protocol::JSONRPCRequest;\n use codex_app_server_protocol::JSONRPCResponse;\n use codex_app_server_protocol::ListMcpServerStatusParams;\n use codex_app_server_protocol::LoginAccountParams;\n+use codex_app_server_protocol::McpResourceReadParams;\n use codex_app_server_protocol::MockExperimentalMethodParams;\n use codex_app_server_protocol::ModelListParams;\n use codex_app_server_protocol::PluginInstallParams;\n@@ -482,6 +483,15 @@ impl McpProcess {\n self.send_request(\"app/list\", params).await\n }\n \n+ /// Send an `mcpServer/resource/read` JSON-RPC request.\n+ pub async fn send_mcp_resource_read_request(\n+ &mut self,\n+ params: McpResourceReadParams,\n+ ) -> anyhow::Result {\n+ let params = Some(serde_json::to_value(params)?);\n+ self.send_request(\"mcpServer/resource/read\", params).await\n+ }\n+\n /// Send a ...", - "path": "codex-rs/app-server/tests/common/mcp_process.rs", - "status": "modified" - }, - { - "additions": 218, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,218 @@\n+use std::sync::Arc;\n+use std::time::Duration;\n+\n+use anyhow::Result;\n+use app_test_support::ChatGptAuthFixture;\n+use app_test_support::McpProcess;\n+use app_test_support::to_response;\n+use app_test_support::write_chatgpt_auth;\n+use axum::Router;\n+use codex_app_server_protocol::JSONRPCError;\n+use codex_app_server_protocol::JSONRPCResponse;\n+use codex_app_server_protocol::McpResourceContent;\n+use codex_app_server_protocol::McpResourceReadParams;\n+use codex_app_server_protocol::McpResourceReadResponse;\n+use codex_app_server_protocol::RequestId;\n+use codex_app_server_protocol::ThreadStartParams;\n+use codex_app_server_protocol::ThreadStartResponse;\n+use codex_login::AuthCredentialsStoreMode;\n+use core_test_support::responses;\n+use pretty_assertions::assert_eq;\n+use rmcp::handler::server::ServerHandler;\n+use rmcp::model::ProtocolVersion;\n+use rmcp::model::ReadResourceRequestP...", - "path": "codex-rs/app-server/tests/suite/v2/mcp_resource.rs", - "status": "added" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -14,6 +14,7 @@ mod experimental_api;\n mod experimental_feature_list;\n mod fs;\n mod initialize;\n+mod mcp_resource;\n mod mcp_server_elicitation;\n mod mcp_server_status;\n mod model_list;", - "path": "codex-rs/app-server/tests/suite/v2/mod.rs", - "status": "modified" - }, - { - "additions": 21, - "deletions": 0, - "patch_excerpt": "@@ -22,6 +22,7 @@ use codex_protocol::protocol::Submission;\n use codex_protocol::protocol::TokenUsage;\n use codex_protocol::protocol::W3cTraceContext;\n use codex_protocol::user_input::UserInput;\n+use rmcp::model::ReadResourceRequestParams;\n use std::path::PathBuf;\n use tokio::sync::Mutex;\n use tokio::sync::watch;\n@@ -199,6 +200,26 @@ impl CodexThread {\n self.codex.thread_config_snapshot().await\n }\n \n+ pub async fn read_mcp_resource(\n+ &self,\n+ server: &str,\n+ uri: &str,\n+ ) -> anyhow::Result {\n+ let result = self\n+ .codex\n+ .session\n+ .read_resource(\n+ server,\n+ ReadResourceRequestParams {\n+ meta: None,\n+ uri: uri.to_string(),\n+ },\n+ )\n+ .await?;\n+\n+ Ok(serde_json::to_value(result)...", - "path": "codex-rs/core/src/codex_thread.rs", - "status": "modified" - }, - { - "additions": 32, - "deletions": 0, - "patch_excerpt": "@@ -82,6 +82,38 @@ pub struct Resource {\n pub meta: Option,\n }\n \n+/// Contents returned when reading a resource from an MCP server.\n+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]\n+#[serde(untagged)]\n+pub enum ResourceContent {\n+ #[serde(rename_all = \"camelCase\")]\n+ #[ts(rename_all = \"camelCase\")]\n+ Text {\n+ /// The URI of this resource.\n+ uri: String,\n+ #[serde(default, skip_serializing_if = \"Option::is_none\")]\n+ #[ts(optional)]\n+ mime_type: Option,\n+ text: String,\n+ #[serde(rename = \"_meta\", default, skip_serializing_if = \"Option::is_none\")]\n+ #[ts(optional)]\n+ meta: Option,\n+ },\n+ #[serde(rename_all = \"camelCase\")]\n+ #[ts(rename_all = \"camelCase\")]\n+ Blob {\n+ /// The URI of this resource.\n+ uri: String,\n+ ...", - "path": "codex-rs/protocol/src/mcp.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints." - ], - "primary_pr": { - "body": "- [x] Add `mcpResource/read` method to read mcp resource.", - "labels": [], - "merged_at": "2026-04-07T02:17:14Z", - "number": 16082, - "state": "merged", - "title": "[mcp] Support MCP Apps part 1.", - "url": "https://github.com/openai/codex/pull/16082" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-16153.json b/artifacts/github/bundles/openai-codex-pr-16153.json deleted file mode 100644 index d8e1fc9..0000000 --- a/artifacts/github/bundles/openai-codex-pr-16153.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "pakrym-oai", - "committed_at": "2026-03-29T03:56:12Z", - "message": "Add setTimeout support to code mode", - "sha": "ee42b49260cdc0dbbc46207ae427a6066956c947", - "url": "https://github.com/openai/codex/commit/ee42b49260cdc0dbbc46207ae427a6066956c947" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-29T04:10:55Z", - "message": "Merge remote-tracking branch 'origin/main' into pakrym/add-settimeout-to-codemode", - "sha": "91133138abf1675e9c845650d0c8dd3a563c254b", - "url": "https://github.com/openai/codex/commit/91133138abf1675e9c845650d0c8dd3a563c254b" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-03-29T04:20:41Z", - "message": "Merge branch 'main' into pakrym/add-settimeout-to-codemode", - "sha": "b3ebcf1d3037b5043d611c435b585254d5cfb1d1", - "url": "https://github.com/openai/codex/commit/b3ebcf1d3037b5043d611c435b585254d5cfb1d1" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-04-06T22:47:24Z", - "message": "Merge remote-tracking branch 'origin/main' into pakrym/add-settimeout-to-codemode", - "sha": "e4a3a6fe659138d1139c451aad3d8abdf3e6cf3a", - "url": "https://github.com/openai/codex/commit/e4a3a6fe659138d1139c451aad3d8abdf3e6cf3a" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-04-06T22:55:11Z", - "message": "codex: fix CI failure on PR #16153", - "sha": "9abf7ea7a348fade837626eed4ea81cc6e5ed450", - "url": "https://github.com/openai/codex/commit/9abf7ea7a348fade837626eed4ea81cc6e5ed450" - }, - { - "author": "pakrym-oai", - "committed_at": "2026-04-06T23:03:26Z", - "message": "codex: fix CI failure on PR #16153", - "sha": "8764c435ed577bbe590681550c3801386f92d316", - "url": "https://github.com/openai/codex/commit/8764c435ed577bbe590681550c3801386f92d316" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "EXEC_DESCRIPTION_TEMPLATE", - "ALL_TOOLS", - "WAIT_DESCRIPTION_TEMPLATE", - "EXIT_SENTINEL", - "MAX", - "JSON" - ], - "files": [ - { - "additions": 9, - "deletions": 0, - "patch_excerpt": "@@ -26,6 +26,8 @@ const EXEC_DESCRIPTION_TEMPLATE: &str = r#\"Run JavaScript code to orchestrate/co\n - `store(key: string, value: any)`: stores a serializable value under a string key for later `exec` calls in the same session.\n - `load(key: string)`: returns the stored value for a string key, or `undefined` if it is missing.\n - `notify(value: string | number | boolean | undefined | null)`: immediately injects an extra `custom_tool_call_output` for the current `exec` call. Values are stringified like `text(...)`.\n+- `setTimeout(callback: () => void, delayMs?: number)`: schedules a callback to run later and returns a timeout id. Pending timeouts do not keep `exec` alive by themselves; await an explicit promise if you need to wait for one.\n+- `clearTimeout(timeoutId?: number)`: cancels a timeout created by `setTimeout`.\n - `ALL_TOOLS`: metadata for the enabled nested tools as `{ name, descr...", - "path": "codex-rs/code-mode/src/description.rs", - "status": "modified" - }, - { - "additions": 30, - "deletions": 0, - "patch_excerpt": "@@ -3,6 +3,7 @@ use crate::response::FunctionCallOutputContentItem;\n use super::EXIT_SENTINEL;\n use super::RuntimeEvent;\n use super::RuntimeState;\n+use super::timers;\n use super::value::json_to_v8;\n use super::value::normalize_output_image;\n use super::value::serialize_output_text;\n@@ -185,6 +186,35 @@ pub(super) fn notify_callback(\n retval.set(v8::undefined(scope).into());\n }\n \n+pub(super) fn set_timeout_callback(\n+ scope: &mut v8::PinScope<'_, '_>,\n+ args: v8::FunctionCallbackArguments,\n+ mut retval: v8::ReturnValue,\n+) {\n+ let timeout_id = match timers::schedule_timeout(scope, args) {\n+ Ok(timeout_id) => timeout_id,\n+ Err(error_text) => {\n+ throw_type_error(scope, &error_text);\n+ return;\n+ }\n+ };\n+\n+ retval.set(v8::Number::new(scope, timeout_id as f64).into());\n+}\n+\n+pub(super) fn clear_timeout_callback(\n+ ...", - "path": "codex-rs/code-mode/src/runtime/callbacks.rs", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -1,8 +1,10 @@\n use super::RuntimeState;\n+use super::callbacks::clear_timeout_callback;\n use super::callbacks::exit_callback;\n use super::callbacks::image_callback;\n use super::callbacks::load_callback;\n use super::callbacks::notify_callback;\n+use super::callbacks::set_timeout_callback;\n use super::callbacks::store_callback;\n use super::callbacks::text_callback;\n use super::callbacks::tool_callback;\n@@ -18,6 +20,8 @@ pub(super) fn install_globals(scope: &mut v8::PinScope<'_, '_>) -> Result<(), St\n \n let tools = build_tools_object(scope)?;\n let all_tools = build_all_tools_value(scope)?;\n+ let clear_timeout = helper_function(scope, \"clearTimeout\", clear_timeout_callback)?;\n+ let set_timeout = helper_function(scope, \"setTimeout\", set_timeout_callback)?;\n let text = helper_function(scope, \"text\", text_callback)?;\n let image = helper_function(scope, \"image\", image_call...", - "path": "codex-rs/code-mode/src/runtime/globals.rs", - "status": "modified" - }, - { - "additions": 24, - "deletions": 1, - "patch_excerpt": "@@ -1,6 +1,7 @@\n mod callbacks;\n mod globals;\n mod module_loader;\n+mod timers;\n mod value;\n \n use std::collections::HashMap;\n@@ -75,6 +76,7 @@ pub(crate) enum TurnMessage {\n pub(crate) enum RuntimeCommand {\n ToolResponse { id: String, result: JsonValue },\n ToolError { id: String, error_text: String },\n+ TimeoutFired { id: u64 },\n Terminate,\n }\n \n@@ -103,6 +105,7 @@ pub(crate) fn spawn_runtime(\n event_tx: mpsc::UnboundedSender,\n ) -> Result<(std_mpsc::Sender, v8::IsolateHandle), String> {\n let (command_tx, command_rx) = std_mpsc::channel();\n+ let runtime_command_tx = command_tx.clone();\n let (isolate_handle_tx, isolate_handle_rx) = std_mpsc::sync_channel(1);\n let enabled_tools = request\n .enabled_tools\n@@ -117,7 +120,13 @@ pub(crate) fn spawn_runtime(\n };\n \n thread::spawn(move || {\n- run_runtime(config,...", - "path": "codex-rs/code-mode/src/runtime/mod.rs", - "status": "modified" - }, - { - "additions": 114, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,114 @@\n+use std::thread;\n+use std::time::Duration;\n+\n+use super::RuntimeCommand;\n+use super::RuntimeState;\n+use super::value::value_to_error_text;\n+\n+pub(super) struct ScheduledTimeout {\n+ callback: v8::Global,\n+}\n+\n+pub(super) fn schedule_timeout(\n+ scope: &mut v8::PinScope<'_, '_>,\n+ args: v8::FunctionCallbackArguments,\n+) -> Result {\n+ let callback = args.get(0);\n+ if !callback.is_function() {\n+ return Err(\"setTimeout expects a function callback\".to_string());\n+ }\n+ let callback = v8::Local::::try_from(callback)\n+ .map_err(|_| \"setTimeout expects a function callback\".to_string())?;\n+\n+ let delay_ms = args\n+ .get(1)\n+ .number_value(scope)\n+ .map(normalize_delay_ms)\n+ .unwrap_or(0);\n+\n+ let callback = v8::Global::new(scope, callback);\n+ let state = scope\n+ ....", - "path": "codex-rs/code-mode/src/runtime/timers.rs", - "status": "added" - }, - { - "additions": 78, - "deletions": 0, - "patch_excerpt": "@@ -1626,6 +1626,34 @@ text({ json: true });\n Ok(())\n }\n \n+#[tokio::test(flavor = \"multi_thread\", worker_threads = 2)]\n+async fn code_mode_can_resume_after_set_timeout() -> Result<()> {\n+ skip_if_no_network!(Ok(()));\n+\n+ let server = responses::start_mock_server().await;\n+ let (_test, second_mock) = run_code_mode_turn(\n+ &server,\n+ \"use exec to wait for a timeout\",\n+ r#\"\n+await new Promise((resolve) => setTimeout(resolve, 10));\n+text(\"timer done\");\n+\"#,\n+ /*include_apply_patch*/ false,\n+ )\n+ .await?;\n+\n+ let req = second_mock.single_request();\n+ let (output, success) = custom_tool_output_body_and_success(&req, \"call-1\");\n+ assert_ne!(\n+ success,\n+ Some(false),\n+ \"exec setTimeout call failed unexpectedly: {output}\"\n+ );\n+ assert_eq!(output, \"timer done\");\n+\n+ Ok(())\n+}\n+\n #[tokio::test(flavor = \"multi_th...", - "path": "codex-rs/core/tests/suite/code_mode.rs", - "status": "modified" - } - ], - "linked_issues": [ - "#16153" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints." - ], - "primary_pr": { - "body": "The implementation is less than ideal - it starts a thread per timer. A better approach might be to switch to tokio and use their timer imlementation.\r\n", - "labels": [], - "merged_at": "2026-04-07T00:46:28Z", - "number": 16153, - "state": "merged", - "title": "Add setTimeout support to code mode", - "url": "https://github.com/openai/codex/pull/16153" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-16204.json b/artifacts/github/bundles/openai-codex-pr-16204.json deleted file mode 100644 index 409cbc4..0000000 --- a/artifacts/github/bundles/openai-codex-pr-16204.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "etraut-openai", - "committed_at": "2026-03-29T23:07:09Z", - "message": "Normalize Windows snapshot path in MCP startup test", - "sha": "324ebc1ba0ec5d86c42bc6e892b6c2ed692e18cf", - "url": "https://github.com/openai/codex/commit/324ebc1ba0ec5d86c42bc6e892b6c2ed692e18cf" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "MCP" - ], - "files": [ - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -11760,7 +11760,7 @@ async fn app_server_mcp_startup_failure_renders_warning_history() {\n \n assert_snapshot!(\n \"app_server_mcp_startup_failure_renders_warning_history\",\n- term.backend().vt100().screen().contents()\n+ normalize_snapshot_paths(term.backend().vt100().screen().contents())\n );\n }", - "path": "codex-rs/tui/src/chatwidget/tests.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Discovered via hourly merged-PR sync: https://github.com/openai/codex/pull/16204" - ], - "primary_pr": { - "body": "## Summary\nA Windows-only snapshot assertion in the app-server MCP startup warning test compared the raw rendered path, so CI saw `C:\\tmp\\project` instead of the normalized `/tmp/project` snapshot fixture.\n\n## Fix\nRoute that snapshot assertion through the existing `normalize_snapshot_paths(...)` helper so the test remains platform-stable.", - "labels": [], - "merged_at": "2026-03-29T23:54:18Z", - "number": 16204, - "state": "merged", - "title": "[codex] Normalize Windows path in MCP startup snapshot test", - "url": "https://github.com/openai/codex/pull/16204" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-16508.json b/artifacts/github/bundles/openai-codex-pr-16508.json deleted file mode 100644 index ae7dde1..0000000 --- a/artifacts/github/bundles/openai-codex-pr-16508.json +++ /dev/null @@ -1,855 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "aibrahim-oai", - "committed_at": "2026-04-03T00:37:13Z", - "message": "Extract models manager ownership from core", - "sha": "e67d6827e55dc797c24e6aefeda505245e4599f3", - "url": "https://github.com/openai/codex/commit/e67d6827e55dc797c24e6aefeda505245e4599f3" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-04-03T00:53:30Z", - "message": "codex: allow bundled models expect in PR #16508", - "sha": "2e105a99ef77d6dde1530bd557cb488070920d82", - "url": "https://github.com/openai/codex/commit/2e105a99ef77d6dde1530bd557cb488070920d82" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-04-03T01:56:31Z", - "message": "codex: restore bundled models fallback", - "sha": "4dd6b8e5bf26c77582d9edde2dcc0abbb7606049", - "url": "https://github.com/openai/codex/commit/4dd6b8e5bf26c77582d9edde2dcc0abbb7606049" - }, - { - "author": "aibrahim-oai", - "committed_at": "2026-04-03T05:47:06Z", - "message": "Address review style nits", - "sha": "22ddcb47853735c2b19b8396f05835d42023d59c", - "url": "https://github.com/openai/codex/commit/22ddcb47853735c2b19b8396f05835d42023d59c" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "API", - "PLAN", - "DEFAULT", - "EXECUTE", - "PAIR_PROGRAMMING", - "ZERO", - "TUI", - "MCP_SANDBOX_STATE_CAPABILITY", - "MCP_SANDBOX_STATE_METHOD", - "X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER", - "DEFAULT_LMSTUDIO_PORT", - "DEFAULT_OLLAMA_PORT", - "LMSTUDIO_OSS_PROVIDER_ID", - "OLLAMA_OSS_PROVIDER_ID", - "OPENAI_PROVIDER_ID", - "CARGO_PKG_VERSION_MAJOR", - "CARGO_PKG_VERSION_MINOR", - "CARGO_PKG_VERSION_PATCH", - "TEST_MODEL_PRESETS", - "BIG5", - "EUC_KR", - "GBK", - "ISO_8859_2", - "ISO_8859_3", - "ISO_8859_4", - "ISO_8859_5", - "ISO_8859_6", - "ISO_8859_7", - "ISO_8859_8", - "ISO_8859_10", - "ISO_8859_13", - "SHIFT_JIS", - "WINDOWS_874", - "WINDOWS_1250", - "WINDOWS_1251", - "WINDOWS_1253", - "WINDOWS_1254", - "WINDOWS_1255", - "WINDOWS_1256", - "WINDOWS_1257", - "WINDOWS_1258", - "UTF", - "WSL", - "CP1251", - "KOI8", - "CP866", - "IBM866", - "ASCII", - "CP1252", - "ISO", - "WINDOWS_1252", - "TIS", - "ANSI", - "USER_SHELL_COMMAND_FRAGMENT", - "INITIAL_DELAY_MS", - "CODEX_SANDBOX_ENV_VAR", - "REMOTE_MODEL_SLUG", - "DISCOVERABLE_GMAIL_ID", - "DEFAULT_LISTEN_URL", - "URL", - "FEEDBACK_DIAGNOSTICS_ATTACHMENT_FILENAME", - "UPLOAD_TIMEOUT_SECS", - "FEEDBACK_TAGS_TARGET", - "MAX_FEEDBACK_TAGS", - "CODEX_API_KEY_ENV_VAR", - "OPENAI_API_KEY_ENV_VAR", - "REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR", - "CBP", - "DEFAULT_STREAM_IDLE_TIMEOUT_MS", - "DEFAULT_STREAM_MAX_RETRIES", - "DEFAULT_REQUEST_MAX_RETRIES", - "DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS", - "MAX_STREAM_MAX_RETRIES", - "MAX_REQUEST_MAX_RETRIES", - "OPENAI_PROVIDER_NAME", - "CHAT_WIRE_API_REMOVED_ERROR", - "LEGACY_OLLAMA_CHAT_PROVIDER_ID", - "OLLAMA_CHAT_PROVIDER_REMOVED_ERROR", - "--local-provider", - "HTTP", - "COLLABORATION_MODE_DEFAULT", - "COLLABORATION_MODE_PLAN", - "TUI_VISIBLE_COLLABORATION_MODES", - "KNOWN_MODE_NAMES_TEMPLATE_KEY", - "KNOWN_MODE_NAMES", - "REQUEST_USER_INPUT_AVAILABILITY_TEMPLATE_KEY", - "REQUEST_USER_INPUT_AVAILABILITY", - "ASKING_QUESTIONS_GUIDANCE_TEMPLATE_KEY", - "ASKING_QUESTIONS_GUIDANCE", - "MODELS_REFRESH_TIMEOUT", - "TTL", - "BASE_INSTRUCTIONS", - "DEFAULT_PERSONALITY_HEADER", - "GPT", - "LOCAL_FRIENDLY_TEMPLATE", - "LOCAL_PRAGMATIC_TEMPLATE", - "PERSONALITY_PLACEHOLDER", - "ERROR_MESSAGE_UI_MAX_BYTES", - "SIGINT", - "CLI", - "TOO_MANY_REQUESTS", - "WINDOWS_1252_PUNCT", - "VSC", - "AUTH_ERROR_HEADER", - "X_ERROR_JSON_HEADER" - ], - "files": [ - { - "additions": 82, - "deletions": 4, - "patch_excerpt": "@@ -1367,7 +1367,9 @@ dependencies = [\n \"anyhow\",\n \"assert_matches\",\n \"async-trait\",\n+ \"base64 0.22.1\",\n \"bytes\",\n+ \"chrono\",\n \"codex-client\",\n \"codex-protocol\",\n \"codex-utils-rustls-provider\",\n@@ -1787,6 +1789,10 @@ dependencies = [\n \"v8\",\n ]\n \n+[[package]]\n+name = \"codex-collaboration-mode-templates\"\n+version = \"0.0.0\"\n+\n [[package]]\n name = \"codex-config\"\n version = \"0.0.0\"\n@@ -1837,7 +1843,6 @@ dependencies = [\n \"async-trait\",\n \"base64 0.22.1\",\n \"bm25\",\n- \"chardetng\",\n \"chrono\",\n \"clap\",\n \"codex-analytics\",\n@@ -1853,15 +1858,18 @@ dependencies = [\n \"codex-exec-server\",\n \"codex-execpolicy\",\n \"codex-features\",\n+ \"codex-feedback\",\n \"codex-git-utils\",\n \"codex-hooks\",\n \"codex-instructions\",\n \"codex-login\",\n \"codex-mcp\",\n+ \"codex-models-manager\",\n \"codex-network-proxy\",\n \"codex-otel\",\n \"codex-plugin\",\n \"codex-protocol\",\n+ \"codex-response-debug-context\",\n \"codex-r...", - "path": "codex-rs/Cargo.lock", - "status": "modified" - }, - { - "additions": 8, - "deletions": 0, - "patch_excerpt": "@@ -20,6 +20,7 @@ members = [\n \"cloud-tasks-client\",\n \"cloud-tasks-mock-client\",\n \"cli\",\n+ \"collaboration-mode-templates\",\n \"connectors\",\n \"config\",\n \"shell-command\",\n@@ -41,13 +42,16 @@ members = [\n \"login\",\n \"codex-mcp\",\n \"mcp-server\",\n+ \"model-provider-info\",\n+ \"models-manager\",\n \"network-proxy\",\n \"ollama\",\n \"process-hardening\",\n \"protocol\",\n \"rollout\",\n \"rmcp-client\",\n \"responses-api-proxy\",\n+ \"response-debug-context\",\n \"sandboxing\",\n \"stdio-to-uds\",\n \"otel\",\n@@ -112,6 +116,7 @@ codex-backend-client = { path = \"backend-client\" }\n codex-chatgpt = { path = \"chatgpt\" }\n codex-cli = { path = \"cli\" }\n codex-client = { path = \"codex-client\" }\n+codex-collaboration-mode-templates = { path = \"collaboration-mode-templates\" }\n codex-cloud-requirements = { path = \"cloud-requirements\" }\n codex-cloud-tasks-clie...", - "path": "codex-rs/Cargo.toml", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -202,6 +202,7 @@ use codex_core::config_loader::CloudRequirementsLoadErrorCode;\n use codex_core::config_loader::CloudRequirementsLoader;\n use codex_core::config_loader::LoaderOverrides;\n use codex_core::config_loader::load_config_layers_state;\n+use codex_core::default_client::set_default_client_residency_requirement;\n use codex_core::error::CodexErr;\n use codex_core::error::Result as CodexResult;\n use codex_core::exec::ExecCapturePolicy;\n@@ -244,7 +245,6 @@ use codex_login::ServerOptions as LoginServerOptions;\n use codex_login::ShutdownHandle;\n use codex_login::auth::login_with_chatgpt_auth_tokens;\n use codex_login::complete_device_code_login;\n-use codex_login::default_client::set_default_client_residency_requirement;\n use codex_login::login_with_api_key;\n use codex_login::request_device_code;\n use codex_login::run_login_server;", - "path": "codex-rs/app-server/src/codex_message_processor.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -6,7 +6,9 @@ license.workspace = true\n \n [dependencies]\n async-trait = { workspace = true }\n+base64 = { workspace = true }\n bytes = { workspace = true }\n+chrono = { workspace = true }\n codex-client = { workspace = true }\n codex-protocol = { workspace = true }\n codex-utils-rustls-provider = { workspace = true }", - "path": "codex-rs/codex-api/Cargo.toml", - "status": "modified" - }, - { - "additions": 17, - "deletions": 53, - "patch_excerpt": "@@ -1,24 +1,21 @@\n+use crate::AuthProvider as ApiAuthProvider;\n+use crate::TransportError;\n+use crate::error::ApiError;\n+use crate::rate_limits::parse_promo_message;\n+use crate::rate_limits::parse_rate_limit_for_limit;\n use base64::Engine;\n use chrono::DateTime;\n use chrono::Utc;\n-use codex_api::AuthProvider as ApiAuthProvider;\n-use codex_api::TransportError;\n-use codex_api::error::ApiError;\n-use codex_api::rate_limits::parse_promo_message;\n-use codex_api::rate_limits::parse_rate_limit_for_limit;\n-use codex_login::token_data::PlanType;\n+use codex_protocol::auth::PlanType;\n+use codex_protocol::error::CodexErr;\n+use codex_protocol::error::RetryLimitReachedError;\n+use codex_protocol::error::UnexpectedResponseError;\n+use codex_protocol::error::UsageLimitReachedError;\n use http::HeaderMap;\n use serde::Deserialize;\n use serde_json::Value;\n \n-use crate::error::CodexErr;\n-use crate::error::Retry...", - "path": "codex-rs/codex-api/src/api_bridge.rs", - "status": "renamed" - }, - { - "additions": 0, - "deletions": 0, - "patch_excerpt": null, - "path": "codex-rs/codex-api/src/api_bridge_tests.rs", - "status": "renamed" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -1,3 +1,4 @@\n+pub mod api_bridge;\n pub mod auth;\n pub mod common;\n pub mod endpoint;", - "path": "codex-rs/codex-api/src/lib.rs", - "status": "modified" - }, - { - "additions": 12, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,12 @@\n+load(\"//:defs.bzl\", \"codex_rust_crate\")\n+\n+codex_rust_crate(\n+ name = \"collaboration-mode-templates\",\n+ crate_name = \"codex_collaboration_mode_templates\",\n+ compile_data = glob([\"templates/*.md\"]),\n+)\n+\n+exports_files(\n+ glob([\"templates/*.md\"]),\n+ visibility = [\"//visibility:public\"],\n+)", - "path": "codex-rs/collaboration-mode-templates/BUILD.bazel", - "status": "added" - }, - { - "additions": 13, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,13 @@\n+[package]\n+edition.workspace = true\n+license.workspace = true\n+name = \"codex-collaboration-mode-templates\"\n+version.workspace = true\n+\n+[lib]\n+doctest = false\n+name = \"codex_collaboration_mode_templates\"\n+path = \"src/lib.rs\"\n+\n+[lints]\n+workspace = true", - "path": "codex-rs/collaboration-mode-templates/Cargo.toml", - "status": "added" - }, - { - "additions": 4, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,4 @@\n+pub const PLAN: &str = include_str!(\"../templates/plan.md\");\n+pub const DEFAULT: &str = include_str!(\"../templates/default.md\");\n+pub const EXECUTE: &str = include_str!(\"../templates/execute.md\");\n+pub const PAIR_PROGRAMMING: &str = include_str!(\"../templates/pair_programming.md\");", - "path": "codex-rs/collaboration-mode-templates/src/lib.rs", - "status": "added" - }, - { - "additions": 0, - "deletions": 0, - "patch_excerpt": null, - "path": "codex-rs/collaboration-mode-templates/templates/default.md", - "status": "renamed" - }, - { - "additions": 0, - "deletions": 0, - "patch_excerpt": null, - "path": "codex-rs/collaboration-mode-templates/templates/execute.md", - "status": "renamed" - }, - { - "additions": 0, - "deletions": 0, - "patch_excerpt": null, - "path": "codex-rs/collaboration-mode-templates/templates/pair_programming.md", - "status": "renamed" - }, - { - "additions": 0, - "deletions": 0, - "patch_excerpt": null, - "path": "codex-rs/collaboration-mode-templates/templates/plan.md", - "status": "renamed" - }, - { - "additions": 0, - "deletions": 11, - "patch_excerpt": "@@ -1,17 +1,8 @@\n load(\"//:defs.bzl\", \"codex_rust_crate\")\n \n-exports_files(\n- [\n- \"templates/collaboration_mode/default.md\",\n- \"templates/collaboration_mode/plan.md\",\n- ],\n- visibility = [\"//visibility:public\"],\n-)\n-\n filegroup(\n name = \"model_availability_nux_fixtures\",\n srcs = [\n- \"models.json\",\n \"tests/cli_responses_fixture.sse\",\n ],\n visibility = [\"//visibility:public\"],\n@@ -38,8 +29,6 @@ codex_rust_crate(\n },\n integration_compile_data_extra = [\n \"//codex-rs/apply-patch:apply_patch_tool_instructions.md\",\n- \"models.json\",\n- \"prompt.md\",\n ],\n test_data_extra = [\n \"config.schema.json\",", - "path": "codex-rs/core/BUILD.bazel", - "status": "modified" - }, - { - "additions": 3, - "deletions": 6, - "patch_excerpt": "@@ -23,7 +23,6 @@ async-channel = { workspace = true }\n async-trait = { workspace = true }\n base64 = { workspace = true }\n bm25 = { workspace = true }\n-chardetng = { workspace = true }\n chrono = { workspace = true, features = [\"serde\"] }\n clap = { workspace = true, features = [\"derive\"] }\n codex-analytics = { workspace = true }\n@@ -37,8 +36,10 @@ codex-config = { workspace = true }\n codex-core-skills = { workspace = true }\n codex-exec-server = { workspace = true }\n codex-features = { workspace = true }\n+codex-feedback = { workspace = true }\n codex-login = { workspace = true }\n codex-mcp = { workspace = true }\n+codex-models-manager = { workspace = true }\n codex-shell-command = { workspace = true }\n codex-execpolicy = { workspace = true }\n codex-git-utils = { workspace = true }\n@@ -48,6 +49,7 @@ codex-network-proxy = { workspace = true }\n codex-otel = { workspace = true }\n codex-plugin = {...", - "path": "codex-rs/core/Cargo.toml", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -21,7 +21,7 @@ pub(crate) async fn resolve_agent_target(\n .resolve_agent_reference(session.conversation_id, &turn.session_source, target)\n .await\n .map_err(|err| match err {\n- crate::error::CodexErr::UnsupportedOperation(message) => {\n+ codex_protocol::error::CodexErr::UnsupportedOperation(message) => {\n FunctionCallError::RespondToModel(message)\n }\n other => FunctionCallError::RespondToModel(other.to_string()),", - "path": "codex-rs/core/src/agent/agent_resolver.rs", - "status": "modified" - }, - { - "additions": 5, - "deletions": 5, - "patch_excerpt": "@@ -30,11 +30,6 @@ use std::sync::OnceLock;\n use std::sync::atomic::AtomicBool;\n use std::sync::atomic::Ordering;\n \n-use crate::api_bridge::CoreAuthProvider;\n-use crate::api_bridge::auth_provider_from_auth;\n-use crate::api_bridge::map_api_error;\n-use crate::auth_env_telemetry::AuthEnvTelemetry;\n-use crate::auth_env_telemetry::collect_auth_env_telemetry;\n use codex_api::CompactClient as ApiCompactClient;\n use codex_api::CompactionInput as ApiCompactionInput;\n use codex_api::MemoriesClient as ApiMemoriesClient;\n@@ -97,6 +92,11 @@ use tracing::instrument;\n use tracing::trace;\n use tracing::warn;\n \n+use crate::api_bridge::CoreAuthProvider;\n+use crate::api_bridge::auth_provider_from_auth;\n+use crate::api_bridge::map_api_error;\n+use crate::auth_env_telemetry::AuthEnvTelemetry;\n+use crate::auth_env_telemetry::collect_auth_env_telemetry;\n use crate::client_common::Prompt;\n use crate::client_comm...", - "path": "codex-rs/core/src/client.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 2, - "patch_excerpt": "@@ -2,6 +2,8 @@ use super::AuthRequestTelemetryContext;\n use super::ModelClient;\n use super::PendingUnauthorizedRetry;\n use super::UnauthorizedRecoveryExecution;\n+use crate::api_bridge::CoreAuthProvider;\n+use codex_login::AuthMode;\n use codex_otel::SessionTelemetry;\n use codex_protocol::ThreadId;\n use codex_protocol::openai_models::ModelInfo;\n@@ -105,8 +107,8 @@ async fn summarize_memories_returns_empty_for_empty_input() {\n #[test]\n fn auth_request_telemetry_context_tracks_attached_auth_and_retry_phase() {\n let auth_context = AuthRequestTelemetryContext::new(\n- Some(codex_login::AuthMode::Chatgpt),\n- &crate::api_bridge::CoreAuthProvider::for_test(Some(\"access-token\"), Some(\"workspace-123\")),\n+ Some(AuthMode::Chatgpt),\n+ &CoreAuthProvider::for_test(Some(\"access-token\"), Some(\"workspace-123\")),\n PendingUnauthorizedRetry::from_recovery(UnauthorizedRec...", - "path": "codex-rs/core/src/client_tests.rs", - "status": "modified" - }, - { - "additions": 8, - "deletions": 4, - "patch_excerpt": "@@ -570,7 +570,9 @@ impl Codex {\n // 1. config.base_instructions override\n // 2. conversation history => session_meta.base_instructions\n // 3. base_instructions for current model\n- let model_info = models_manager.get_model_info(model.as_str(), &config).await;\n+ let model_info = models_manager\n+ .get_model_info(model.as_str(), &config.to_models_manager_config())\n+ .await;\n let base_instructions = config\n .base_instructions\n .clone()\n@@ -903,7 +905,9 @@ impl TurnContext {\n pub(crate) async fn with_model(&self, model: String, models_manager: &ModelsManager) -> Self {\n let mut config = (*self.config).clone();\n config.model = Some(model.clone());\n- let model_info = models_manager.get_model_info(model.as_str(), &config).await;\n+ let model_info = models_manager\n+ ...", - "path": "codex-rs/core/src/codex.rs", - "status": "modified" - }, - { - "additions": 34, - "deletions": 14, - "patch_excerpt": "@@ -9,12 +9,12 @@ use crate::config_loader::NetworkDomainPermissionsToml;\n use crate::config_loader::RequirementSource;\n use crate::config_loader::Sourced;\n use crate::exec::ExecCapturePolicy;\n-use crate::exec::ExecToolCallOutput;\n use crate::function_tool::FunctionCallError;\n use crate::models_manager::model_info;\n use crate::shell::default_user_shell;\n use crate::tools::format_exec_output_str;\n \n+use crate::exec::ExecToolCallOutput;\n use codex_features::Features;\n use codex_login::CodexAuth;\n use codex_mcp::mcp_connection_manager::ToolInfo;\n@@ -63,7 +63,6 @@ use codex_protocol::models::ContentItem;\n use codex_protocol::models::DeveloperInstructions;\n use codex_protocol::models::ResponseInputItem;\n use codex_protocol::models::ResponseItem;\n-use codex_protocol::openai_models::ModelsResponse;\n use codex_protocol::protocol::AskForApproval;\n use codex_protocol::protocol::CompactedItem;\n use...", - "path": "codex-rs/core/src/codex_tests.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -4242,8 +4242,8 @@ fn load_config_rejects_unsafe_agent_role_nickname_candidates() -> std::io::Resul\n fn model_catalog_json_loads_from_path() -> std::io::Result<()> {\n let codex_home = TempDir::new()?;\n let catalog_path = codex_home.path().join(\"catalog.json\");\n- let mut catalog: ModelsResponse =\n- serde_json::from_str(include_str!(\"../../models.json\")).expect(\"valid models.json\");\n+ let mut catalog = codex_models_manager::bundled_models_response()\n+ .unwrap_or_else(|err| panic!(\"bundled models.json should parse: {err}\"));\n catalog.models = catalog.models.into_iter().take(1).collect();\n std::fs::write(\n &catalog_path,", - "path": "codex-rs/core/src/config/config_tests.rs", - "status": "modified" - }, - { - "additions": 13, - "deletions": 0, - "patch_excerpt": "@@ -65,6 +65,7 @@ use codex_features::FeaturesToml;\n use codex_git_utils::resolve_root_git_project_for_trust;\n use codex_login::AuthCredentialsStoreMode;\n use codex_mcp::mcp::McpConfig;\n+use codex_models_manager::ModelsManagerConfig;\n use codex_protocol::config_types::AltScreenMode;\n use codex_protocol::config_types::ForcedLoginMethod;\n use codex_protocol::config_types::Personality;\n@@ -683,6 +684,18 @@ impl ConfigBuilder {\n }\n \n impl Config {\n+ pub fn to_models_manager_config(&self) -> ModelsManagerConfig {\n+ ModelsManagerConfig {\n+ model_context_window: self.model_context_window,\n+ model_auto_compact_token_limit: self.model_auto_compact_token_limit,\n+ tool_output_token_limit: self.tool_output_token_limit,\n+ base_instructions: self.base_instructions.clone(),\n+ personality_enabled: self.features.enabled(Feature::Personality...", - "path": "codex-rs/core/src/config/mod.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 67, - "patch_excerpt": "@@ -26,9 +26,10 @@ use crate::sandboxing::SandboxPermissions;\n use crate::spawn::SpawnChildRequest;\n use crate::spawn::StdioPolicy;\n use crate::spawn::spawn_child_async;\n-use crate::text_encoding::bytes_to_string_smart;\n use codex_network_proxy::NetworkProxy;\n use codex_protocol::config_types::WindowsSandboxLevel;\n+pub use codex_protocol::exec_output::ExecToolCallOutput;\n+pub use codex_protocol::exec_output::StreamOutput;\n use codex_protocol::permissions::FileSystemSandboxKind;\n use codex_protocol::permissions::FileSystemSandboxPolicy;\n use codex_protocol::permissions::NetworkSandboxPolicy;\n@@ -632,25 +633,6 @@ fn finalize_exec_result(\n }\n }\n \n-pub(crate) mod errors {\n- use super::CodexErr;\n- use codex_sandboxing::SandboxTransformError;\n-\n- impl From for CodexErr {\n- fn from(err: SandboxTransformError) -> Self {\n- match err {\n- ...", - "path": "codex-rs/core/src/exec.rs", - "status": "modified" - }, - { - "additions": 27, - "deletions": 18, - "patch_excerpt": "@@ -5,11 +5,13 @@\n // the TUI or the tracing stack).\n #![deny(clippy::print_stdout, clippy::print_stderr)]\n \n-pub mod api_bridge;\n+pub use codex_login::api_bridge;\n mod apply_patch;\n mod apps;\n mod arc_monitor;\n-mod auth_env_telemetry;\n+pub use codex_login as auth;\n+pub use codex_login::auth_env_telemetry;\n+pub use codex_login::default_client;\n mod client;\n mod client_common;\n pub mod codex;\n@@ -30,7 +32,7 @@ pub mod connectors;\n mod context_manager;\n mod contextual_user_message;\n mod environment_context;\n-pub mod error;\n+pub use codex_protocol::error;\n pub mod exec;\n pub mod exec_env;\n mod exec_policy;\n@@ -46,21 +48,24 @@ pub mod landlock;\n pub mod mcp;\n mod mcp_skill_dependencies;\n mod mcp_tool_approval_templates;\n-pub mod models_manager;\n+pub use codex_models_manager as models_manager;\n mod network_policy_decision;\n pub mod network_proxy_loader;\n mod original_image_detail;\n-pub use te...", - "path": "codex-rs/core/src/lib.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -228,7 +228,7 @@ async fn build_request_context(session: &Arc, config: &Config) -> Reque\n let model = session\n .services\n .models_manager\n- .get_model_info(&model_name, config)\n+ .get_model_info(&model_name, &config.to_models_manager_config())\n .await;\n let turn_context = session.new_default_turn().await;\n RequestContext::from_turn_context(\n@@ -466,7 +466,7 @@ mod job {\n /// Serializes filtered stage-1 memory items for prompt inclusion.\n pub(super) fn serialize_filtered_rollout_response_items(\n items: &[RolloutItem],\n- ) -> crate::error::Result {\n+ ) -> codex_protocol::error::Result {\n let filtered = items\n .iter()\n .filter_map(|item| {", - "path": "codex-rs/core/src/memories/phase1.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 15, - "patch_excerpt": "@@ -1,15 +0,0 @@\n-pub mod cache;\n-pub mod collaboration_mode_presets;\n-pub mod manager;\n-pub mod model_info;\n-pub mod model_presets;\n-\n-/// Convert the client version string to a whole version string (e.g. \"1.2.3-alpha.4\" -> \"1.2.3\").\n-pub fn client_version_to_whole() -> String {\n- format!(\n- \"{}.{}.{}\",\n- env!(\"CARGO_PKG_VERSION_MAJOR\"),\n- env!(\"CARGO_PKG_VERSION_MINOR\"),\n- env!(\"CARGO_PKG_VERSION_PATCH\")\n- )\n-}", - "path": "codex-rs/core/src/models_manager/mod.rs", - "status": "removed" - }, - { - "additions": 1, - "deletions": 20, - "patch_excerpt": "@@ -1,25 +1,12 @@\n use codex_execpolicy::Decision as ExecPolicyDecision;\n use codex_execpolicy::NetworkRuleProtocol as ExecPolicyNetworkRuleProtocol;\n use codex_network_proxy::BlockedRequest;\n-use codex_network_proxy::NetworkDecisionSource;\n use codex_network_proxy::NetworkPolicyDecision;\n use codex_protocol::approvals::NetworkApprovalContext;\n use codex_protocol::approvals::NetworkApprovalProtocol;\n use codex_protocol::approvals::NetworkPolicyAmendment;\n use codex_protocol::approvals::NetworkPolicyRuleAction;\n-use serde::Deserialize;\n-\n-#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]\n-#[serde(rename_all = \"camelCase\")]\n-pub struct NetworkPolicyDecisionPayload {\n- pub decision: NetworkPolicyDecision,\n- pub source: NetworkDecisionSource,\n- #[serde(default)]\n- pub protocol: Option,\n- pub host: Option,\n- pub reason: Option,\n- ...", - "path": "codex-rs/core/src/network_policy_decision.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -1,5 +1,6 @@\n use super::*;\n use codex_network_proxy::BlockedRequest;\n+use codex_network_proxy::NetworkDecisionSource;\n use codex_protocol::approvals::NetworkPolicyAmendment;\n use codex_protocol::approvals::NetworkPolicyRuleAction;\n use pretty_assertions::assert_eq;", - "path": "codex-rs/core/src/network_policy_decision_tests.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -140,14 +140,14 @@ impl ExecRequest {\n pub async fn execute_env(\n exec_request: ExecRequest,\n stdout_stream: Option,\n-) -> crate::error::Result {\n+) -> codex_protocol::error::Result {\n execute_exec_request(exec_request, stdout_stream, /*after_spawn*/ None).await\n }\n \n pub async fn execute_exec_request_with_after_spawn(\n exec_request: ExecRequest,\n stdout_stream: Option,\n after_spawn: Option>,\n-) -> crate::error::Result {\n+) -> codex_protocol::error::Result {\n execute_exec_request(exec_request, stdout_stream, after_spawn).await\n }", - "path": "codex-rs/core/src/sandboxing/mod.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 6, - "patch_excerpt": "@@ -11,7 +11,6 @@ use codex_exec_server::EnvironmentManager;\n use codex_protocol::config_types::CollaborationModeMask;\n use codex_protocol::openai_models::ModelInfo;\n use codex_protocol::openai_models::ModelPreset;\n-use codex_protocol::openai_models::ModelsResponse;\n use once_cell::sync::Lazy;\n \n use crate::ModelProviderInfo;\n@@ -25,8 +24,7 @@ use codex_login::AuthManager;\n use codex_login::CodexAuth;\n \n static TEST_MODEL_PRESETS: Lazy> = Lazy::new(|| {\n- let file_contents = include_str!(\"../models.json\");\n- let mut response: ModelsResponse = serde_json::from_str(file_contents)\n+ let mut response = codex_models_manager::bundled_models_response()\n .unwrap_or_else(|err| panic!(\"bundled models.json should parse: {err}\"));\n response.models.sort_by(|a, b| a.priority.cmp(&b.priority));\n let mut presets: Vec = response.models.into_iter().ma...", - "path": "codex-rs/core/src/test_support.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 340, - "patch_excerpt": "@@ -1,340 +0,0 @@\n-use super::*;\n-use encoding_rs::BIG5;\n-use encoding_rs::EUC_KR;\n-use encoding_rs::GBK;\n-use encoding_rs::ISO_8859_2;\n-use encoding_rs::ISO_8859_3;\n-use encoding_rs::ISO_8859_4;\n-use encoding_rs::ISO_8859_5;\n-use encoding_rs::ISO_8859_6;\n-use encoding_rs::ISO_8859_7;\n-use encoding_rs::ISO_8859_8;\n-use encoding_rs::ISO_8859_10;\n-use encoding_rs::ISO_8859_13;\n-use encoding_rs::SHIFT_JIS;\n-use encoding_rs::WINDOWS_874;\n-use encoding_rs::WINDOWS_1250;\n-use encoding_rs::WINDOWS_1251;\n-use encoding_rs::WINDOWS_1253;\n-use encoding_rs::WINDOWS_1254;\n-use encoding_rs::WINDOWS_1255;\n-use encoding_rs::WINDOWS_1256;\n-use encoding_rs::WINDOWS_1257;\n-use encoding_rs::WINDOWS_1258;\n-use pretty_assertions::assert_eq;\n-\n-#[test]\n-fn test_utf8_passthrough() {\n- // Fast path: when UTF-8 is valid we should avoid copies and return as-is.\n- let utf8_text = \"Hello, \u043c\u0438\u0440! \u4e16\u754c\";\n- let by...", - "path": "codex-rs/core/src/text_encoding_tests.rs", - "status": "removed" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -292,7 +292,7 @@ pub(crate) async fn apply_requested_spawn_agent_model_overrides(\n let selected_model_info = session\n .services\n .models_manager\n- .get_model_info(&selected_model_name, config)\n+ .get_model_info(&selected_model_name, &config.to_models_manager_config())\n .await;\n \n config.model = Some(selected_model_name.clone());", - "path": "codex-rs/core/src/tools/handlers/multi_agents_common.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 3, - "patch_excerpt": "@@ -4,6 +4,7 @@ use crate::error::SandboxErr;\n use crate::exec::ExecCapturePolicy;\n use crate::exec::ExecExpiration;\n use crate::exec::ExecToolCallOutput;\n+use crate::exec::StreamOutput;\n use crate::exec::is_likely_sandbox_denied;\n use crate::guardian::GuardianApprovalRequest;\n use crate::guardian::review_approval_request;\n@@ -901,9 +902,9 @@ fn map_exec_result(\n ) -> Result {\n let output = ExecToolCallOutput {\n exit_code: result.exit_code,\n- stdout: crate::exec::StreamOutput::new(result.stdout.clone()),\n- stderr: crate::exec::StreamOutput::new(result.stderr.clone()),\n- aggregated_output: crate::exec::StreamOutput::new(result.output.clone()),\n+ stdout: StreamOutput::new(result.stdout.clone()),\n+ stderr: StreamOutput::new(result.stderr.clone()),\n+ aggregated_output: StreamOutput::new(result.output.clone()...", - "path": "codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs", - "status": "modified" - }, - { - "additions": 13, - "deletions": 15, - "patch_excerpt": "@@ -1,8 +1,8 @@\n use crate::config::test_config;\n-use crate::models_manager::manager::ModelsManager;\n use crate::models_manager::model_info::with_config_overrides;\n use crate::shell::Shell;\n use crate::shell::ShellType;\n+use crate::test_support::construct_model_info_offline;\n use crate::tools::ToolRouter;\n use crate::tools::registry::tool_handler_key;\n use crate::tools::router::ToolRouterParams;\n@@ -14,7 +14,6 @@ use codex_protocol::config_types::WebSearchMode;\n use codex_protocol::config_types::WindowsSandboxLevel;\n use codex_protocol::openai_models::ConfigShellToolType;\n use codex_protocol::openai_models::ModelInfo;\n-use codex_protocol::openai_models::ModelsResponse;\n use codex_protocol::protocol::SandboxPolicy;\n use codex_protocol::protocol::SessionSource;\n use codex_tools::ConfiguredToolSpec;\n@@ -73,8 +72,7 @@ fn discoverable_connector(id: &str, name: &str, description: &str) -> Disc...", - "path": "codex-rs/core/src/tools/spec_tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -1,10 +1,10 @@\n use std::time::Duration;\n \n+use crate::exec::ExecToolCallOutput;\n use codex_protocol::models::ResponseItem;\n \n use crate::codex::TurnContext;\n use crate::contextual_user_message::USER_SHELL_COMMAND_FRAGMENT;\n-use crate::exec::ExecToolCallOutput;\n use crate::tools::format_exec_output_str;\n \n fn format_duration_line(duration: Duration) -> String {", - "path": "codex-rs/core/src/user_shell_command.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 116, - "patch_excerpt": "@@ -6,7 +6,10 @@ use codex_protocol::ThreadId;\n use rand::Rng;\n use tracing::error;\n \n-use crate::auth_env_telemetry::AuthEnvTelemetry;\n+pub(crate) use codex_feedback::FeedbackRequestTags;\n+#[cfg(test)]\n+pub(crate) use codex_feedback::emit_feedback_request_tags;\n+pub(crate) use codex_feedback::emit_feedback_request_tags_with_auth_env;\n use codex_shell_command::parse_command::shlex_join;\n \n const INITIAL_DELAY_MS: u64 = 200;\n@@ -37,40 +40,6 @@ macro_rules! feedback_tags {\n };\n }\n \n-pub(crate) struct FeedbackRequestTags<'a> {\n- pub endpoint: &'a str,\n- pub auth_header_attached: bool,\n- pub auth_header_name: Option<&'a str>,\n- pub auth_mode: Option<&'a str>,\n- pub auth_retry_after_unauthorized: Option,\n- pub auth_recovery_mode: Option<&'a str>,\n- pub auth_recovery_phase: Option<&'a str>,\n- pub auth_connection_reused: Option,\n- pub auth_request_id: ...", - "path": "codex-rs/core/src/util.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 1, - "patch_excerpt": "@@ -1,5 +1,8 @@\n use super::*;\n-use crate::auth_env_telemetry::AuthEnvTelemetry;\n+use crate::util::FeedbackRequestTags;\n+use crate::util::emit_feedback_request_tags;\n+use crate::util::emit_feedback_request_tags_with_auth_env;\n+use codex_login::AuthEnvTelemetry;\n use std::collections::BTreeMap;\n use std::sync::Arc;\n use std::sync::Mutex;", - "path": "codex-rs/core/src/util_tests.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 0, - "patch_excerpt": "@@ -1,3 +1,5 @@\n // Single integration test binary that aggregates all test modules.\n // The submodules live in `tests/all/`.\n+pub use codex_core::error;\n+\n mod suite;", - "path": "codex-rs/core/tests/all.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -19,6 +19,7 @@ codex-core = { workspace = true }\n codex-exec-server = { workspace = true }\n codex-features = { workspace = true }\n codex-login = { workspace = true }\n+codex-models-manager = { workspace = true }\n codex-protocol = { workspace = true }\n codex-utils-absolute-path = { workspace = true }\n codex-utils-cargo-bin = { workspace = true }", - "path": "codex-rs/core/tests/common/Cargo.toml", - "status": "modified" - }, - { - "additions": 2, - "deletions": 11, - "patch_excerpt": "@@ -609,17 +609,8 @@ fn ensure_test_model_catalog(config: &mut Config) -> Result<()> {\n return Ok(());\n }\n \n- let bundled_models_path = codex_utils_cargo_bin::find_resource!(\"../../models.json\")\n- .context(\"bundled models.json\")?;\n- let bundled_models_contents =\n- std::fs::read_to_string(&bundled_models_path).with_context(|| {\n- format!(\n- \"read bundled models.json from {}\",\n- bundled_models_path.display()\n- )\n- })?;\n- let bundled_models: ModelsResponse =\n- serde_json::from_str(&bundled_models_contents).context(\"parse bundled models.json\")?;\n+ let bundled_models = codex_models_manager::bundled_models_response()\n+ .unwrap_or_else(|err| panic!(\"bundled models.json should parse: {err}\"));\n let mut model = bundled_models\n .models\n .iter()", - "path": "codex-rs/core/tests/common/test_codex.rs", - "status": "modified" - }, - { - "additions": 5, - "deletions": 6, - "patch_excerpt": "@@ -1,3 +1,4 @@\n+use crate::error::CodexErr;\n use codex_core::ModelClient;\n use codex_core::ModelProviderInfo;\n use codex_core::NewThread;\n@@ -6,7 +7,6 @@ use codex_core::ResponseEvent;\n use codex_core::ThreadManager;\n use codex_core::WireApi;\n use codex_core::built_in_model_providers;\n-use codex_core::error::CodexErr;\n use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;\n use codex_features::Feature;\n use codex_login::AuthCredentialsStoreMode;\n@@ -34,7 +34,6 @@ use codex_protocol::models::ReasoningItemContent;\n use codex_protocol::models::ReasoningItemReasoningSummary;\n use codex_protocol::models::ResponseItem;\n use codex_protocol::models::WebSearchAction;\n-use codex_protocol::openai_models::ModelsResponse;\n use codex_protocol::openai_models::ReasoningEffort;\n use codex_protocol::protocol::EventMsg;\n use codex_protocol::protocol::Op;\n@@ -1636,8 +1635,8 @...", - "path": "codex-rs/core/tests/suite/client.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -103,8 +103,8 @@ fn non_openai_model_provider(server: &MockServer) -> ModelProviderInfo {\n }\n \n fn model_info_with_context_window(slug: &str, context_window: i64) -> ModelInfo {\n- let models_response: ModelsResponse =\n- serde_json::from_str(include_str!(\"../../models.json\")).expect(\"valid models.json\");\n+ let models_response = codex_models_manager::bundled_models_response()\n+ .unwrap_or_else(|err| panic!(\"bundled models.json should parse: {err}\"));\n let mut model_info = models_response\n .models\n .into_iter()", - "path": "codex-rs/core/tests/suite/compact.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 2, - "patch_excerpt": "@@ -3,6 +3,7 @@\n use std::collections::HashMap;\n use std::string::ToString;\n \n+use crate::error::Result;\n use codex_core::exec::ExecCapturePolicy;\n use codex_core::exec::ExecParams;\n use codex_core::exec::ExecToolCallOutput;\n@@ -17,8 +18,6 @@ use codex_sandboxing::SandboxType;\n use codex_sandboxing::get_platform_sandbox;\n use tempfile::TempDir;\n \n-use codex_core::error::Result;\n-\n fn skip_test() -> bool {\n if std::env::var(CODEX_SANDBOX_ENV_VAR) == Ok(\"seatbelt\".to_string()) {\n eprintln!(\"{CODEX_SANDBOX_ENV_VAR} is set to 'seatbelt', skipping test.\");", - "path": "codex-rs/core/tests/suite/exec.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 3, - "patch_excerpt": "@@ -77,7 +77,6 @@ mod agent_websocket;\n mod apply_patch_cli;\n #[cfg(not(target_os = \"windows\"))]\n mod approvals;\n-mod auth_refresh;\n mod cli_stream;\n mod client;\n mod client_websockets;\n@@ -101,7 +100,6 @@ mod json_result;\n mod live_cli;\n mod live_reload;\n mod memories;\n-mod model_info_overrides;\n mod model_overrides;\n mod model_switching;\n mod model_visible_layout;\n@@ -142,7 +140,6 @@ mod sqlite_state;\n mod stream_error_allows_next_turn;\n mod stream_no_completed;\n mod subagent_notifications;\n-mod text_encoding_fix;\n mod tool_harness;\n mod tool_parallelism;\n mod tool_suggest;", - "path": "codex-rs/core/tests/suite/mod.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -961,11 +961,11 @@ async fn model_switch_to_smaller_model_updates_token_context_window() -> Result<\n \"expected {smaller_model_slug} to be available in remote model list\"\n );\n let large_model_info = models_manager\n- .get_model_info(large_model_slug, &test.config)\n+ .get_model_info(large_model_slug, &test.config.to_models_manager_config())\n .await;\n assert_eq!(large_model_info.context_window, Some(large_context_window));\n let smaller_model_info = models_manager\n- .get_model_info(smaller_model_slug, &test.config)\n+ .get_model_info(smaller_model_slug, &test.config.to_models_manager_config())\n .await;\n assert_eq!(\n smaller_model_info.context_window,", - "path": "codex-rs/core/tests/suite/model_switching.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -139,7 +139,7 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> {\n .model\n .as_deref()\n .expect(\"test config should have a model\"),\n- &config,\n+ &config.to_models_manager_config(),\n )\n .await\n .base_instructions;", - "path": "codex-rs/core/tests/suite/prompt_caching.rs", - "status": "modified" - }, - { - "additions": 15, - "deletions": 7, - "patch_excerpt": "@@ -105,7 +105,9 @@ async fn remote_models_get_model_info_uses_longest_matching_prefix() -> Result<(\n \n manager.list_models(RefreshStrategy::OnlineIfUncached).await;\n \n- let model_info = manager.get_model_info(\"gpt-5.3-codex-test\", &config).await;\n+ let model_info = manager\n+ .get_model_info(\"gpt-5.3-codex-test\", &config.to_models_manager_config())\n+ .await;\n \n assert_eq!(model_info.slug, \"gpt-5.3-codex-test\");\n assert_eq!(model_info.base_instructions, specific.base_instructions);\n@@ -348,7 +350,7 @@ async fn remote_models_remote_model_uses_unified_exec() -> Result<()> {\n assert_eq!(requests[0].url.path(), \"/v1/models\");\n \n let model_info = models_manager\n- .get_model_info(REMOTE_MODEL_SLUG, &config)\n+ .get_model_info(REMOTE_MODEL_SLUG, &config.to_models_manager_config())\n .await;\n assert_eq!(model_info.shell_type, ConfigS...", - "path": "codex-rs/core/tests/suite/remote_models.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 3, - "patch_excerpt": "@@ -5,7 +5,6 @@ use anyhow::Result;\n use codex_core::config::Config;\n use codex_features::Feature;\n use codex_login::CodexAuth;\n-use codex_protocol::openai_models::ModelsResponse;\n use codex_protocol::protocol::AskForApproval;\n use codex_protocol::protocol::EventMsg;\n use codex_protocol::protocol::McpInvocation;\n@@ -94,8 +93,8 @@ fn configure_apps_without_tool_search(config: &mut Config, apps_base_url: &str)\n config.chatgpt_base_url = apps_base_url.to_string();\n config.model = Some(\"gpt-5-codex\".to_string());\n \n- let mut model_catalog: ModelsResponse =\n- serde_json::from_str(include_str!(\"../../models.json\")).expect(\"valid models.json\");\n+ let mut model_catalog = codex_models_manager::bundled_models_response()\n+ .unwrap_or_else(|err| panic!(\"bundled models.json should parse: {err}\"));\n let model = model_catalog\n .models\n .iter_mut()", - "path": "codex-rs/core/tests/suite/search_tool.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 3, - "patch_excerpt": "@@ -7,7 +7,6 @@ use codex_config::types::ToolSuggestDiscoverableType;\n use codex_core::config::Config;\n use codex_features::Feature;\n use codex_login::CodexAuth;\n-use codex_protocol::openai_models::ModelsResponse;\n use codex_protocol::protocol::AskForApproval;\n use codex_protocol::protocol::SandboxPolicy;\n use core_test_support::apps_test_server::AppsTestServer;\n@@ -78,8 +77,8 @@ fn configure_apps_without_search_tool(config: &mut Config, apps_base_url: &str)\n id: DISCOVERABLE_GMAIL_ID.to_string(),\n }];\n \n- let mut model_catalog: ModelsResponse =\n- serde_json::from_str(include_str!(\"../../models.json\")).expect(\"valid models.json\");\n+ let mut model_catalog = codex_models_manager::bundled_models_response()\n+ .unwrap_or_else(|err| panic!(\"bundled models.json should parse: {err}\"));\n let model = model_catalog\n .models\n .iter_mut()", - "path": "codex-rs/core/tests/suite/tool_suggest.rs", - "status": "modified" - }, - { - "additions": 8, - "deletions": 2, - "patch_excerpt": "@@ -1,3 +1,5 @@\n+use std::net::SocketAddr;\n+\n use pretty_assertions::assert_eq;\n \n use super::DEFAULT_LISTEN_URL;\n@@ -9,7 +11,9 @@ fn parse_listen_url_accepts_default_websocket_url() {\n parse_listen_url(DEFAULT_LISTEN_URL).expect(\"default listen URL should parse\");\n assert_eq!(\n bind_address,\n- \"127.0.0.1:0\".parse().expect(\"valid socket address\")\n+ \"127.0.0.1:0\"\n+ .parse::()\n+ .expect(\"valid socket address\")\n );\n }\n \n@@ -19,7 +23,9 @@ fn parse_listen_url_accepts_websocket_url() {\n parse_listen_url(\"ws://127.0.0.1:1234\").expect(\"websocket listen URL should parse\");\n assert_eq!(\n bind_address,\n- \"127.0.0.1:1234\".parse().expect(\"valid socket address\")\n+ \"127.0.0.1:1234\"\n+ .parse::()\n+ .expect(\"valid socket address\")\n );\n }", - "path": "codex-rs/exec-server/src/server/transport_tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -9,6 +9,7 @@ workspace = true\n \n [dependencies]\n anyhow = { workspace = true }\n+codex-login = { workspace = true }\n codex-protocol = { workspace = true }\n sentry = { version = \"0.46\" }\n tracing = { workspace = true }", - "path": "codex-rs/feedback/Cargo.toml", - "status": "modified" - }, - { - "additions": 123, - "deletions": 0, - "patch_excerpt": "@@ -11,6 +11,7 @@ use std::time::Duration;\n \n use anyhow::Result;\n use anyhow::anyhow;\n+use codex_login::AuthEnvTelemetry;\n use codex_protocol::ThreadId;\n use codex_protocol::protocol::SessionSource;\n use feedback_diagnostics::FEEDBACK_DIAGNOSTICS_ATTACHMENT_FILENAME;\n@@ -32,6 +33,128 @@ const UPLOAD_TIMEOUT_SECS: u64 = 10;\n const FEEDBACK_TAGS_TARGET: &str = \"feedback_tags\";\n const MAX_FEEDBACK_TAGS: usize = 64;\n \n+/// Structured request/auth fields that should be attached to feedback uploads.\n+pub struct FeedbackRequestTags<'a> {\n+ pub endpoint: &'a str,\n+ pub auth_header_attached: bool,\n+ pub auth_header_name: Option<&'a str>,\n+ pub auth_mode: Option<&'a str>,\n+ pub auth_retry_after_unauthorized: Option,\n+ pub auth_recovery_mode: Option<&'a str>,\n+ pub auth_recovery_phase: Option<&'a str>,\n+ pub auth_connection_reused: Option,\n+ pub auth_request_...", - "path": "codex-rs/feedback/src/lib.rs", - "status": "modified" - }, - { - "additions": 3, - "deletions": 0, - "patch_excerpt": "@@ -12,9 +12,12 @@ async-trait = { workspace = true }\n base64 = { workspace = true }\n chrono = { workspace = true, features = [\"serde\"] }\n codex-app-server-protocol = { workspace = true }\n+codex-api = { workspace = true }\n codex-client = { workspace = true }\n codex-config = { workspace = true }\n codex-keyring-store = { workspace = true }\n+codex-model-provider-info = { workspace = true }\n+codex-otel = { workspace = true }\n codex-protocol = { workspace = true }\n codex-terminal-detection = { workspace = true }\n codex-utils-template = { workspace = true }", - "path": "codex-rs/login/Cargo.toml", - "status": "modified" - }, - { - "additions": 38, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,38 @@\n+use codex_model_provider_info::ModelProviderInfo;\n+\n+pub use codex_api::api_bridge::CoreAuthProvider;\n+pub use codex_api::api_bridge::map_api_error;\n+\n+use crate::CodexAuth;\n+\n+pub fn auth_provider_from_auth(\n+ auth: Option,\n+ provider: &ModelProviderInfo,\n+) -> codex_protocol::error::Result {\n+ if let Some(api_key) = provider.api_key()? {\n+ return Ok(CoreAuthProvider {\n+ token: Some(api_key),\n+ account_id: None,\n+ });\n+ }\n+\n+ if let Some(token) = provider.experimental_bearer_token.clone() {\n+ return Ok(CoreAuthProvider {\n+ token: Some(token),\n+ account_id: None,\n+ });\n+ }\n+\n+ if let Some(auth) = auth {\n+ let token = auth.get_token()?;\n+ Ok(CoreAuthProvider {\n+ token: Some(token),\n+ account_id: auth.get_account_id(...", - "path": "codex-rs/login/src/api_bridge.rs", - "status": "added" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -2,9 +2,9 @@ use super::*;\n use crate::auth::storage::FileAuthStorage;\n use crate::auth::storage::get_auth_file;\n use crate::token_data::IdTokenInfo;\n-use crate::token_data::KnownPlan as InternalKnownPlan;\n-use crate::token_data::PlanType as InternalPlanType;\n use codex_protocol::account::PlanType as AccountPlanType;\n+use codex_protocol::auth::KnownPlan as InternalKnownPlan;\n+use codex_protocol::auth::PlanType as InternalPlanType;\n \n use base64::Engine;\n use codex_protocol::config_types::ForcedLoginMethod;", - "path": "codex-rs/login/src/auth/auth_tests.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 25, - "patch_excerpt": "@@ -1,25 +1,2 @@\n-use thiserror::Error;\n-\n-#[derive(Debug, Clone, PartialEq, Eq, Error)]\n-#[error(\"{message}\")]\n-pub struct RefreshTokenFailedError {\n- pub reason: RefreshTokenFailedReason,\n- pub message: String,\n-}\n-\n-impl RefreshTokenFailedError {\n- pub fn new(reason: RefreshTokenFailedReason, message: impl Into) -> Self {\n- Self {\n- reason,\n- message: message.into(),\n- }\n- }\n-}\n-\n-#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n-pub enum RefreshTokenFailedReason {\n- Expired,\n- Exhausted,\n- Revoked,\n- Other,\n-}\n+pub use codex_protocol::auth::RefreshTokenFailedError;\n+pub use codex_protocol::auth::RefreshTokenFailedReason;", - "path": "codex-rs/login/src/auth/error.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 4, - "patch_excerpt": "@@ -19,21 +19,21 @@ use codex_protocol::config_types::ForcedLoginMethod;\n use codex_protocol::config_types::ModelProviderAuthInfo;\n \n use super::external_bearer::BearerTokenRefresher;\n-use crate::auth::error::RefreshTokenFailedError;\n-use crate::auth::error::RefreshTokenFailedReason;\n pub use crate::auth::storage::AuthCredentialsStoreMode;\n pub use crate::auth::storage::AuthDotJson;\n use crate::auth::storage::AuthStorageBackend;\n use crate::auth::storage::create_auth_storage;\n use crate::auth::util::try_parse_error_message;\n use crate::default_client::create_client;\n-use crate::token_data::KnownPlan as InternalKnownPlan;\n-use crate::token_data::PlanType as InternalPlanType;\n use crate::token_data::TokenData;\n use crate::token_data::parse_chatgpt_jwt_claims;\n use crate::token_data::parse_jwt_expiration;\n use codex_client::CodexHttpClient;\n use codex_protocol::account::PlanType as AccountP...", - "path": "codex-rs/login/src/auth/manager.rs", - "status": "modified" - }, - { - "additions": 15, - "deletions": 15, - "patch_excerpt": "@@ -1,22 +1,22 @@\n use codex_otel::AuthEnvTelemetryMetadata;\n \n-use crate::model_provider_info::ModelProviderInfo;\n-use codex_login::CODEX_API_KEY_ENV_VAR;\n-use codex_login::OPENAI_API_KEY_ENV_VAR;\n-use codex_login::REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR;\n+use crate::CODEX_API_KEY_ENV_VAR;\n+use crate::ModelProviderInfo;\n+use crate::OPENAI_API_KEY_ENV_VAR;\n+use crate::REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR;\n \n #[derive(Debug, Clone, Default, PartialEq, Eq)]\n-pub(crate) struct AuthEnvTelemetry {\n- pub(crate) openai_api_key_env_present: bool,\n- pub(crate) codex_api_key_env_present: bool,\n- pub(crate) codex_api_key_env_enabled: bool,\n- pub(crate) provider_env_key_name: Option,\n- pub(crate) provider_env_key_present: Option,\n- pub(crate) refresh_token_url_override_present: bool,\n+pub struct AuthEnvTelemetry {\n+ pub openai_api_key_env_present: bool,\n+ pub codex_api...", - "path": "codex-rs/login/src/auth_env_telemetry.rs", - "status": "renamed" - }, - { - "additions": 20, - "deletions": 0, - "patch_excerpt": "@@ -1,4 +1,7 @@\n+pub mod api_bridge;\n pub mod auth;\n+pub mod auth_env_telemetry;\n+pub mod provider_auth;\n pub mod token_data;\n \n mod device_code_auth;\n@@ -15,6 +18,9 @@ pub use server::ServerOptions;\n pub use server::ShutdownHandle;\n pub use server::run_login_server;\n \n+pub use api_bridge::CoreAuthProvider;\n+pub use api_bridge::auth_provider_from_auth;\n+pub use api_bridge::map_api_error;\n pub use auth::AuthConfig;\n pub use auth::AuthCredentialsStoreMode;\n pub use auth::AuthDotJson;\n@@ -38,5 +44,19 @@ pub use auth::login_with_api_key;\n pub use auth::logout;\n pub use auth::read_openai_api_key_from_env;\n pub use auth::save_auth;\n+pub use auth_env_telemetry::AuthEnvTelemetry;\n+pub use auth_env_telemetry::collect_auth_env_telemetry;\n pub use codex_app_server_protocol::AuthMode;\n+pub use codex_model_provider_info as model_provider_info;\n+pub use codex_model_provider_info::DEFAULT_LMSTUDIO_PORT...", - "path": "codex-rs/login/src/lib.rs", - "status": "modified" - }, - { - "additions": 4, - "deletions": 4, - "patch_excerpt": "@@ -1,12 +1,12 @@\n use std::sync::Arc;\n \n-use crate::model_provider_info::ModelProviderInfo;\n-use codex_login::AuthManager;\n+use crate::AuthManager;\n+use crate::ModelProviderInfo;\n \n /// Returns the provider-scoped auth manager when this provider uses command-backed auth.\n ///\n /// Providers without custom auth continue using the caller-supplied base manager.\n-pub(crate) fn auth_manager_for_provider(\n+pub fn auth_manager_for_provider(\n auth_manager: Option>,\n provider: &ModelProviderInfo,\n ) -> Option> {\n@@ -20,7 +20,7 @@ pub(crate) fn auth_manager_for_provider(\n ///\n /// Providers with command-backed auth get a bearer-only manager; otherwise the caller's manager\n /// is reused unchanged.\n-pub(crate) fn required_auth_manager_for_provider(\n+pub fn required_auth_manager_for_provider(\n auth_manager: Arc,\n provider: &ModelProvide...", - "path": "codex-rs/login/src/provider_auth.rs", - "status": "renamed" - }, - { - "additions": 1, - "deletions": 89, - "patch_excerpt": "@@ -1,6 +1,7 @@\n use base64::Engine;\n use chrono::DateTime;\n use chrono::Utc;\n+use codex_protocol::auth::PlanType;\n use serde::Deserialize;\n use serde::Serialize;\n use serde::de::DeserializeOwned;\n@@ -61,95 +62,6 @@ impl IdTokenInfo {\n }\n }\n \n-#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]\n-#[serde(untagged)]\n-pub enum PlanType {\n- Known(KnownPlan),\n- Unknown(String),\n-}\n-\n-impl PlanType {\n- pub fn from_raw_value(raw: &str) -> Self {\n- match raw.to_ascii_lowercase().as_str() {\n- \"free\" => Self::Known(KnownPlan::Free),\n- \"go\" => Self::Known(KnownPlan::Go),\n- \"plus\" => Self::Known(KnownPlan::Plus),\n- \"pro\" => Self::Known(KnownPlan::Pro),\n- \"team\" => Self::Known(KnownPlan::Team),\n- \"self_serve_business_usage_based\" => {\n- Self::Known(KnownPlan::SelfServeBusinessUsageBased)\n- ...", - "path": "codex-rs/login/src/token_data.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -1,6 +1,7 @@\n use super::*;\n use chrono::TimeZone;\n use chrono::Utc;\n+use codex_protocol::auth::KnownPlan;\n use pretty_assertions::assert_eq;\n use serde::Serialize;", - "path": "codex-rs/login/src/token_data_tests.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -4,7 +4,6 @@ use base64::Engine;\n use chrono::Duration;\n use chrono::Utc;\n use codex_app_server_protocol::AuthMode;\n-use codex_core::error::RefreshTokenFailedReason;\n use codex_login::AuthCredentialsStoreMode;\n use codex_login::AuthDotJson;\n use codex_login::AuthManager;\n@@ -14,6 +13,7 @@ use codex_login::load_auth_dot_json;\n use codex_login::save_auth;\n use codex_login::token_data::IdTokenInfo;\n use codex_login::token_data::TokenData;\n+use codex_protocol::auth::RefreshTokenFailedReason;\n use core_test_support::skip_if_no_network;\n use pretty_assertions::assert_eq;\n use serde::Serialize;", - "path": "codex-rs/login/tests/suite/auth_refresh.rs", - "status": "renamed" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -1,3 +1,4 @@\n // Aggregates all former standalone integration tests as modules.\n+mod auth_refresh;\n mod device_code_login;\n mod login_server_e2e;", - "path": "codex-rs/login/tests/suite/mod.rs", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,6 @@\n+load(\"//:defs.bzl\", \"codex_rust_crate\")\n+\n+codex_rust_crate(\n+ name = \"model-provider-info\",\n+ crate_name = \"codex_model_provider_info\",\n+)", - "path": "codex-rs/model-provider-info/BUILD.bazel", - "status": "added" - }, - { - "additions": 28, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,28 @@\n+[package]\n+edition.workspace = true\n+license.workspace = true\n+name = \"codex-model-provider-info\"\n+version.workspace = true\n+\n+[lib]\n+doctest = false\n+name = \"codex_model_provider_info\"\n+path = \"src/lib.rs\"\n+\n+[lints]\n+workspace = true\n+\n+[dependencies]\n+codex-api = { workspace = true }\n+codex-app-server-protocol = { workspace = true }\n+codex-protocol = { workspace = true }\n+http = { workspace = true }\n+schemars = { workspace = true }\n+serde = { workspace = true, features = [\"derive\"] }\n+\n+[dev-dependencies]\n+codex-utils-absolute-path = { workspace = true }\n+maplit = { workspace = true }\n+pretty_assertions = { workspace = true }\n+tempfile = { workspace = true }\n+toml = { workspace = true }", - "path": "codex-rs/model-provider-info/Cargo.toml", - "status": "added" - }, - { - "additions": 13, - "deletions": 26, - "patch_excerpt": "@@ -5,11 +5,13 @@\n //! 2. User-defined entries inside `~/.codex/config.toml` under the `model_providers`\n //! key. These override or extend the defaults at runtime.\n \n-use crate::error::EnvVarError;\n use codex_api::Provider as ApiProvider;\n use codex_api::provider::RetryConfig as ApiRetryConfig;\n-use codex_login::AuthMode;\n+use codex_app_server_protocol::AuthMode;\n use codex_protocol::config_types::ModelProviderAuthInfo;\n+use codex_protocol::error::CodexErr;\n+use codex_protocol::error::EnvVarError;\n+use codex_protocol::error::Result as CodexResult;\n use http::HeaderMap;\n use http::header::HeaderName;\n use http::header::HeaderValue;\n@@ -23,7 +25,7 @@ use std::time::Duration;\n const DEFAULT_STREAM_IDLE_TIMEOUT_MS: u64 = 300_000;\n const DEFAULT_STREAM_MAX_RETRIES: u64 = 5;\n const DEFAULT_REQUEST_MAX_RETRIES: u64 = 4;\n-pub(crate) const DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS: u64 = 15_00...", - "path": "codex-rs/model-provider-info/src/lib.rs", - "status": "renamed" - }, - { - "additions": 0, - "deletions": 0, - "patch_excerpt": null, - "path": "codex-rs/model-provider-info/src/model_provider_info_tests.rs", - "status": "renamed" - }, - { - "additions": 10, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,10 @@\n+load(\"//:defs.bzl\", \"codex_rust_crate\")\n+\n+codex_rust_crate(\n+ name = \"models-manager\",\n+ crate_name = \"codex_models_manager\",\n+ compile_data = [\n+ \"models.json\",\n+ \"prompt.md\",\n+ ],\n+)", - "path": "codex-rs/models-manager/BUILD.bazel", - "status": "added" - }, - { - "additions": 41, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,41 @@\n+[package]\n+edition.workspace = true\n+license.workspace = true\n+name = \"codex-models-manager\"\n+version.workspace = true\n+\n+[lib]\n+doctest = false\n+name = \"codex_models_manager\"\n+path = \"src/lib.rs\"\n+\n+[lints]\n+workspace = true\n+\n+[dependencies]\n+chrono = { workspace = true, features = [\"serde\"] }\n+codex-api = { workspace = true }\n+codex-collaboration-mode-templates = { workspace = true }\n+codex-feedback = { workspace = true }\n+codex-login = { workspace = true }\n+codex-otel = { workspace = true }\n+codex-protocol = { workspace = true }\n+codex-response-debug-context = { workspace = true }\n+codex-utils-output-truncation = { workspace = true }\n+codex-utils-template = { workspace = true }\n+http = { workspace = true }\n+serde = { workspace = true, features = [\"derive\"] }\n+serde_json = { workspace = true }\n+tokio = { workspace = true, features = [\"fs\", \"sync\", \"time\"] }\n+tracing ...", - "path": "codex-rs/models-manager/Cargo.toml", - "status": "added" - }, - { - "additions": 0, - "deletions": 0, - "patch_excerpt": null, - "path": "codex-rs/models-manager/models.json", - "status": "renamed" - }, - { - "additions": 0, - "deletions": 0, - "patch_excerpt": null, - "path": "codex-rs/models-manager/prompt.md", - "status": "renamed" - }, - { - "additions": 0, - "deletions": 0, - "patch_excerpt": null, - "path": "codex-rs/models-manager/src/cache.rs", - "status": "renamed" - }, - { - "additions": 2, - "deletions": 3, - "patch_excerpt": "@@ -1,13 +1,12 @@\n+use codex_collaboration_mode_templates::DEFAULT as COLLABORATION_MODE_DEFAULT;\n+use codex_collaboration_mode_templates::PLAN as COLLABORATION_MODE_PLAN;\n use codex_protocol::config_types::CollaborationModeMask;\n use codex_protocol::config_types::ModeKind;\n use codex_protocol::config_types::TUI_VISIBLE_COLLABORATION_MODES;\n use codex_protocol::openai_models::ReasoningEffort;\n use codex_utils_template::Template;\n use std::sync::LazyLock;\n \n-const COLLABORATION_MODE_PLAN: &str = include_str!(\"../../templates/collaboration_mode/plan.md\");\n-const COLLABORATION_MODE_DEFAULT: &str =\n- include_str!(\"../../templates/collaboration_mode/default.md\");\n const KNOWN_MODE_NAMES_TEMPLATE_KEY: &str = \"KNOWN_MODE_NAMES\";\n const REQUEST_USER_INPUT_AVAILABILITY_TEMPLATE_KEY: &str = \"REQUEST_USER_INPUT_AVAILABILITY\";\n const ASKING_QUESTIONS_GUIDANCE_TEMPLATE_KEY: &str = \"ASKING_QUESTION...", - "path": "codex-rs/models-manager/src/collaboration_mode_presets.rs", - "status": "renamed" - }, - { - "additions": 0, - "deletions": 0, - "patch_excerpt": null, - "path": "codex-rs/models-manager/src/collaboration_mode_presets_tests.rs", - "status": "renamed" - }, - { - "additions": 12, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,12 @@\n+use codex_protocol::openai_models::ModelsResponse;\n+\n+#[derive(Debug, Clone, Default)]\n+pub struct ModelsManagerConfig {\n+ pub model_context_window: Option,\n+ pub model_auto_compact_token_limit: Option,\n+ pub tool_output_token_limit: Option,\n+ pub base_instructions: Option,\n+ pub personality_enabled: bool,\n+ pub model_supports_reasoning_summaries: Option,\n+ pub model_catalog: Option,\n+}", - "path": "codex-rs/models-manager/src/config.rs", - "status": "added" - }, - { - "additions": 30, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,30 @@\n+pub mod cache;\n+pub mod collaboration_mode_presets;\n+pub mod config;\n+pub mod manager;\n+pub mod model_info;\n+pub mod model_presets;\n+\n+pub use codex_login::AuthCredentialsStoreMode;\n+pub use codex_login::AuthManager;\n+pub use codex_login::AuthMode;\n+pub use codex_login::CodexAuth;\n+pub use codex_login::ModelProviderInfo;\n+pub use codex_login::WireApi;\n+pub use config::ModelsManagerConfig;\n+\n+/// Load the bundled model catalog shipped with `codex-models-manager`.\n+pub fn bundled_models_response()\n+-> std::result::Result {\n+ serde_json::from_str(include_str!(\"../models.json\"))\n+}\n+\n+/// Convert the client version string to a whole version string (e.g. \"1.2.3-alpha.4\" -> \"1.2.3\").\n+pub fn client_version_to_whole() -> String {\n+ format!(\n+ \"{}.{}.{}\",\n+ env!(\"CARGO_PKG_VERSION_MAJOR\"),\n+ ...", - "path": "codex-rs/models-manager/src/lib.rs", - "status": "added" - }, - { - "additions": 27, - "deletions": 32, - "patch_excerpt": "@@ -1,33 +1,33 @@\n use super::cache::ModelsCacheManager;\n-use crate::api_bridge::auth_provider_from_auth;\n-use crate::api_bridge::map_api_error;\n-use crate::auth_env_telemetry::AuthEnvTelemetry;\n-use crate::auth_env_telemetry::collect_auth_env_telemetry;\n-use crate::config::Config;\n-use crate::error::CodexErr;\n-use crate::error::Result as CoreResult;\n-use crate::model_provider_info::ModelProviderInfo;\n-use crate::models_manager::collaboration_mode_presets::CollaborationModesConfig;\n-use crate::models_manager::collaboration_mode_presets::builtin_collaboration_mode_presets;\n-use crate::models_manager::model_info;\n-use crate::provider_auth::required_auth_manager_for_provider;\n-use crate::response_debug_context::extract_response_debug_context;\n-use crate::response_debug_context::telemetry_transport_error_message;\n-use crate::util::FeedbackRequestTags;\n-use crate::util::emit_feedback_request_...", - "path": "codex-rs/models-manager/src/manager.rs", - "status": "renamed" - }, - { - "additions": 15, - "deletions": 28, - "patch_excerpt": "@@ -1,14 +1,15 @@\n use super::*;\n-use crate::config::ConfigBuilder;\n-use crate::model_provider_info::WireApi;\n+use crate::ModelsManagerConfig;\n use base64::Engine as _;\n use chrono::Utc;\n use codex_api::TransportError;\n use codex_login::AuthCredentialsStoreMode;\n use codex_login::AuthManager;\n use codex_login::CodexAuth;\n+use codex_login::WireApi;\n use codex_protocol::config_types::ModelProviderAuthInfo;\n use codex_protocol::openai_models::ModelsResponse;\n+use codex_utils_absolute_path::AbsolutePathBuf;\n use core_test_support::responses::mount_models_once;\n use http::HeaderMap;\n use http::StatusCode;\n@@ -35,6 +36,9 @@ use wiremock::matchers::header_regex;\n use wiremock::matchers::method;\n use wiremock::matchers::path;\n \n+#[path = \"model_info_overrides_tests.rs\"]\n+mod model_info_overrides_tests;\n+\n fn remote_model(slug: &str, display: &str, priority: i32) -> ModelInfo {\n remote_model_...", - "path": "codex-rs/models-manager/src/manager_tests.rs", - "status": "renamed" - }, - { - "additions": 5, - "deletions": 6, - "patch_excerpt": "@@ -9,19 +9,18 @@ use codex_protocol::openai_models::TruncationPolicyConfig;\n use codex_protocol::openai_models::WebSearchToolType;\n use codex_protocol::openai_models::default_input_modalities;\n \n-use crate::config::Config;\n-use codex_features::Feature;\n+use crate::config::ModelsManagerConfig;\n use codex_utils_output_truncation::approx_bytes_for_tokens;\n use tracing::warn;\n \n-pub const BASE_INSTRUCTIONS: &str = include_str!(\"../../prompt.md\");\n+pub const BASE_INSTRUCTIONS: &str = include_str!(\"../prompt.md\");\n const DEFAULT_PERSONALITY_HEADER: &str = \"You are Codex, a coding agent based on GPT-5. You and the user share the same workspace and collaborate to achieve the user's goals.\";\n const LOCAL_FRIENDLY_TEMPLATE: &str =\n \"You optimize for team morale and being a supportive teammate as much as code quality.\";\n const LOCAL_PRAGMATIC_TEMPLATE: &str = \"You are a deeply pragmatic, effec...", - "path": "codex-rs/models-manager/src/model_info.rs", - "status": "renamed" - }, - { - "additions": 13, - "deletions": 13, - "patch_excerpt": "@@ -1,20 +1,21 @@\n-use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;\n-use codex_core::models_manager::manager::ModelsManager;\n+use codex_login::AuthManager;\n use codex_login::CodexAuth;\n+\n+use crate::ModelsManagerConfig;\n+use crate::collaboration_mode_presets::CollaborationModesConfig;\n+use crate::manager::ModelsManager;\n use codex_protocol::openai_models::TruncationPolicyConfig;\n-use core_test_support::load_default_config_for_test;\n use pretty_assertions::assert_eq;\n use tempfile::TempDir;\n \n #[tokio::test(flavor = \"multi_thread\", worker_threads = 2)]\n async fn offline_model_info_without_tool_output_override() {\n let codex_home = TempDir::new().expect(\"create temp dir\");\n- let config = load_default_config_for_test(&codex_home).await;\n- let auth_manager = codex_core::test_support::auth_manager_from_auth(\n- CodexAuth::create_dummy_chatg...", - "path": "codex-rs/models-manager/src/model_info_overrides_tests.rs", - "status": "renamed" - }, - { - "additions": 4, - "deletions": 4, - "patch_excerpt": "@@ -1,11 +1,11 @@\n use super::*;\n-use crate::config::test_config;\n+use crate::ModelsManagerConfig;\n use pretty_assertions::assert_eq;\n \n #[test]\n fn reasoning_summaries_override_true_enables_support() {\n let model = model_info_from_slug(\"unknown-model\");\n- let mut config = test_config();\n+ let mut config = ModelsManagerConfig::default();\n config.model_supports_reasoning_summaries = Some(true);\n \n let updated = with_config_overrides(model.clone(), &config);\n@@ -19,7 +19,7 @@ fn reasoning_summaries_override_true_enables_support() {\n fn reasoning_summaries_override_false_does_not_disable_support() {\n let mut model = model_info_from_slug(\"unknown-model\");\n model.supports_reasoning_summaries = true;\n- let mut config = test_config();\n+ let mut config = ModelsManagerConfig::default();\n config.model_supports_reasoning_summaries = Some(false);\n \n let update...", - "path": "codex-rs/models-manager/src/model_info_tests.rs", - "status": "renamed" - }, - { - "additions": 0, - "deletions": 0, - "patch_excerpt": null, - "path": "codex-rs/models-manager/src/model_presets.rs", - "status": "renamed" - }, - { - "additions": 13, - "deletions": 0, - "patch_excerpt": "@@ -12,23 +12,31 @@ path = \"src/lib.rs\"\n workspace = true\n \n [dependencies]\n+chardetng = { workspace = true }\n+chrono = { workspace = true, features = [\"serde\"] }\n+codex-async-utils = { workspace = true }\n codex-execpolicy = { workspace = true }\n codex-git-utils = { workspace = true }\n+codex-network-proxy = { workspace = true }\n codex-utils-absolute-path = { workspace = true }\n codex-utils-image = { workspace = true }\n codex-utils-string = { workspace = true }\n codex-utils-template = { workspace = true }\n+encoding_rs = { workspace = true }\n icu_decimal = { workspace = true }\n icu_locale_core = { workspace = true }\n icu_provider = { workspace = true, features = [\"sync\"] }\n quick-xml = { workspace = true, features = [\"serialize\"] }\n+reqwest = { workspace = true }\n schemars = { workspace = true }\n serde = { workspace = true, features = [\"derive\"] }\n serde_json = { workspace = true }\n serde_...", - "path": "codex-rs/protocol/Cargo.toml", - "status": "modified" - }, - { - "additions": 116, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,116 @@\n+use serde::Deserialize;\n+use serde::Serialize;\n+use thiserror::Error;\n+\n+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]\n+#[serde(untagged)]\n+pub enum PlanType {\n+ Known(KnownPlan),\n+ Unknown(String),\n+}\n+\n+impl PlanType {\n+ pub fn from_raw_value(raw: &str) -> Self {\n+ match raw.to_ascii_lowercase().as_str() {\n+ \"free\" => Self::Known(KnownPlan::Free),\n+ \"go\" => Self::Known(KnownPlan::Go),\n+ \"plus\" => Self::Known(KnownPlan::Plus),\n+ \"pro\" => Self::Known(KnownPlan::Pro),\n+ \"team\" => Self::Known(KnownPlan::Team),\n+ \"self_serve_business_usage_based\" => {\n+ Self::Known(KnownPlan::SelfServeBusinessUsageBased)\n+ }\n+ \"business\" => Self::Known(KnownPlan::Business),\n+ \"enterprise_cbp_usage_based\" => Self::Known(KnownPlan::EnterpriseCbpU...", - "path": "codex-rs/protocol/src/auth.rs", - "status": "added" - }, - { - "additions": 86, - "deletions": 111, - "patch_excerpt": "@@ -1,20 +1,21 @@\n-use crate::exec::ExecToolCallOutput;\n-use crate::network_policy_decision::NetworkPolicyDecisionPayload;\n+use crate::ThreadId;\n+use crate::auth::KnownPlan;\n+use crate::auth::PlanType;\n+pub use crate::auth::RefreshTokenFailedError;\n+pub use crate::auth::RefreshTokenFailedReason;\n+use crate::exec_output::ExecToolCallOutput;\n+use crate::network_policy::NetworkPolicyDecisionPayload;\n+use crate::protocol::CodexErrorInfo;\n+use crate::protocol::ErrorEvent;\n+use crate::protocol::RateLimitSnapshot;\n+use crate::protocol::TruncationPolicy;\n use chrono::DateTime;\n use chrono::Datelike;\n use chrono::Local;\n use chrono::Utc;\n use codex_async_utils::CancelErr;\n-pub use codex_login::auth::RefreshTokenFailedError;\n-pub use codex_login::auth::RefreshTokenFailedReason;\n-use codex_login::token_data::KnownPlan;\n-use codex_login::token_data::PlanType;\n-use codex_protocol::ThreadId;\n-use code...", - "path": "codex-rs/protocol/src/error.rs", - "status": "renamed" - }, - { - "additions": 4, - "deletions": 3, - "patch_excerpt": "@@ -1,10 +1,11 @@\n use super::*;\n-use crate::exec::StreamOutput;\n+use crate::exec_output::StreamOutput;\n+use crate::protocol::RateLimitWindow;\n use chrono::DateTime;\n use chrono::Duration as ChronoDuration;\n use chrono::TimeZone;\n use chrono::Utc;\n-use codex_protocol::protocol::RateLimitWindow;\n+use http::Response as HttpResponse;\n use pretty_assertions::assert_eq;\n use reqwest::Response;\n use reqwest::ResponseBuilderExt;\n@@ -123,7 +124,7 @@ fn sandbox_denied_reports_stdout_when_no_stderr() {\n \n #[test]\n fn to_error_event_handles_response_stream_failed() {\n- let response = http::Response::builder()\n+ let response = HttpResponse::builder()\n .status(StatusCode::TOO_MANY_REQUESTS)\n .url(Url::parse(\"http://example.com\").unwrap())\n .body(\"\")", - "path": "codex-rs/protocol/src/error_tests.rs", - "status": "renamed" - }, - { - "additions": 50, - "deletions": 2, - "patch_excerpt": "@@ -10,6 +10,54 @@ use chardetng::EncodingDetector;\n use encoding_rs::Encoding;\n use encoding_rs::IBM866;\n use encoding_rs::WINDOWS_1252;\n+use std::time::Duration;\n+\n+#[derive(Debug, Clone)]\n+pub struct StreamOutput {\n+ pub text: T,\n+ pub truncated_after_lines: Option,\n+}\n+\n+impl StreamOutput {\n+ pub fn new(text: String) -> Self {\n+ Self {\n+ text,\n+ truncated_after_lines: None,\n+ }\n+ }\n+}\n+\n+impl StreamOutput> {\n+ pub fn from_utf8_lossy(&self) -> StreamOutput {\n+ StreamOutput {\n+ text: bytes_to_string_smart(&self.text),\n+ truncated_after_lines: self.truncated_after_lines,\n+ }\n+ }\n+}\n+\n+#[derive(Clone, Debug)]\n+pub struct ExecToolCallOutput {\n+ pub exit_code: i32,\n+ pub stdout: StreamOutput,\n+ pub stderr: StreamOutput,\n+ pub aggregate...", - "path": "codex-rs/protocol/src/exec_output.rs", - "status": "renamed" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -3,7 +3,7 @@\n //! These tests simulate VSCode's shell preview on Windows/WSL where the output\n //! may be encoded with a legacy code page before it reaches Codex.\n \n-use codex_core::exec::StreamOutput;\n+use super::StreamOutput;\n use pretty_assertions::assert_eq;\n \n #[test]", - "path": "codex-rs/protocol/src/exec_output_tests.rs", - "status": "renamed" - }, - { - "additions": 4, - "deletions": 0, - "patch_excerpt": "@@ -1,16 +1,20 @@\n pub mod account;\n mod agent_path;\n+pub mod auth;\n mod thread_id;\n pub use agent_path::AgentPath;\n pub use thread_id::ThreadId;\n pub mod approvals;\n pub mod config_types;\n pub mod dynamic_tools;\n+pub mod error;\n+pub mod exec_output;\n pub mod items;\n pub mod mcp;\n pub mod memory_citation;\n pub mod message_history;\n pub mod models;\n+pub mod network_policy;\n pub mod num_format;\n pub mod openai_models;\n pub mod parse_command;", - "path": "codex-rs/protocol/src/lib.rs", - "status": "modified" - }, - { - "additions": 22, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,22 @@\n+use crate::approvals::NetworkApprovalProtocol;\n+use codex_network_proxy::NetworkDecisionSource;\n+use codex_network_proxy::NetworkPolicyDecision;\n+use serde::Deserialize;\n+\n+#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]\n+#[serde(rename_all = \"camelCase\")]\n+pub struct NetworkPolicyDecisionPayload {\n+ pub decision: NetworkPolicyDecision,\n+ pub source: NetworkDecisionSource,\n+ #[serde(default)]\n+ pub protocol: Option,\n+ pub host: Option,\n+ pub reason: Option,\n+ pub port: Option,\n+}\n+\n+impl NetworkPolicyDecisionPayload {\n+ pub fn is_ask_from_decider(&self) -> bool {\n+ self.decision == NetworkPolicyDecision::Ask && self.source == NetworkDecisionSource::Decider\n+ }\n+}", - "path": "codex-rs/protocol/src/network_policy.rs", - "status": "added" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,6 @@\n+load(\"//:defs.bzl\", \"codex_rust_crate\")\n+\n+codex_rust_crate(\n+ name = \"response-debug-context\",\n+ crate_name = \"codex_response_debug_context\",\n+)", - "path": "codex-rs/response-debug-context/BUILD.bazel", - "status": "added" - }, - { - "additions": 22, - "deletions": 0, - "patch_excerpt": "@@ -0,0 +1,22 @@\n+[package]\n+edition.workspace = true\n+license.workspace = true\n+name = \"codex-response-debug-context\"\n+version.workspace = true\n+\n+[lib]\n+doctest = false\n+name = \"codex_response_debug_context\"\n+path = \"src/lib.rs\"\n+\n+[lints]\n+workspace = true\n+\n+[dependencies]\n+base64 = { workspace = true }\n+codex-api = { workspace = true }\n+http = { workspace = true }\n+serde_json = { workspace = true }\n+\n+[dev-dependencies]\n+pretty_assertions = { workspace = true }", - "path": "codex-rs/response-debug-context/Cargo.toml", - "status": "added" - }, - { - "additions": 9, - "deletions": 11, - "patch_excerpt": "@@ -9,14 +9,14 @@ const AUTH_ERROR_HEADER: &str = \"x-openai-authorization-error\";\n const X_ERROR_JSON_HEADER: &str = \"x-error-json\";\n \n #[derive(Debug, Default, Clone, PartialEq, Eq)]\n-pub(crate) struct ResponseDebugContext {\n- pub(crate) request_id: Option,\n- pub(crate) cf_ray: Option,\n- pub(crate) auth_error: Option,\n- pub(crate) auth_error_code: Option,\n+pub struct ResponseDebugContext {\n+ pub request_id: Option,\n+ pub cf_ray: Option,\n+ pub auth_error: Option,\n+ pub auth_error_code: Option,\n }\n \n-pub(crate) fn extract_response_debug_context(transport: &TransportError) -> ResponseDebugContext {\n+pub fn extract_response_debug_context(transport: &TransportError) -> ResponseDebugContext {\n let mut context = ResponseDebugContext::default();\n \n let TransportError::Http {\n@@ -53,16 +53,14 @@ pub(c...", - "path": "codex-rs/response-debug-context/src/lib.rs", - "status": "renamed" - }, - { - "additions": 16, - "deletions": 0, - "patch_excerpt": "@@ -19,7 +19,23 @@ pub use manager::SandboxType;\n pub use manager::SandboxablePreference;\n pub use manager::get_platform_sandbox;\n \n+use codex_protocol::error::CodexErr;\n+\n #[cfg(not(target_os = \"linux\"))]\n pub fn system_bwrap_warning() -> Option {\n None\n }\n+\n+impl From for CodexErr {\n+ fn from(err: SandboxTransformError) -> Self {\n+ match err {\n+ SandboxTransformError::MissingLinuxSandboxExecutable => {\n+ CodexErr::LandlockSandboxExecutableNotProvided\n+ }\n+ #[cfg(not(target_os = \"macos\"))]\n+ SandboxTransformError::SeatbeltUnavailable => CodexErr::UnsupportedOperation(\n+ \"seatbelt sandbox is only available on macOS\".to_string(),\n+ ),\n+ }\n+ }\n+}", - "path": "codex-rs/sandboxing/src/lib.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -12,8 +12,8 @@ codex_rust_crate(\n ],\n allow_empty = True,\n ) + [\n- \"//codex-rs/core:templates/collaboration_mode/default.md\",\n- \"//codex-rs/core:templates/collaboration_mode/plan.md\",\n+ \"//codex-rs/collaboration-mode-templates:templates/default.md\",\n+ \"//codex-rs/collaboration-mode-templates:templates/plan.md\",\n ],\n test_data_extra = glob([\"src/**/snapshots/**\"]) + [\"//codex-rs/core:model_availability_nux_fixtures\"],\n integration_compile_data_extra = [\"src/test_backend.rs\"],", - "path": "codex-rs/tui/BUILD.bazel", - "status": "modified" - }, - { - "additions": 1, - "deletions": 0, - "patch_excerpt": "@@ -133,6 +133,7 @@ arboard = { workspace = true }\n codex-cli = { workspace = true }\n codex-core = { workspace = true }\n codex-mcp = { workspace = true }\n+codex-models-manager = { workspace = true }\n codex-utils-cargo-bin = { workspace = true }\n codex-utils-pty = { workspace = true }\n assert_matches = { workspace = true }", - "path": "codex-rs/tui/Cargo.toml", - "status": "modified" - }, - { - "additions": 2, - "deletions": 3, - "patch_excerpt": "@@ -19,9 +19,8 @@ async fn resume_startup_does_not_consume_model_availability_nux_count() -> Resul\n let repo_root = codex_utils_cargo_bin::repo_root()?;\n let codex_home = tempdir()?;\n \n- let source_catalog_path = codex_utils_cargo_bin::find_resource!(\"../core/models.json\")?;\n- let source_catalog = std::fs::read_to_string(&source_catalog_path)?;\n- let mut source_catalog: JsonValue = serde_json::from_str(&source_catalog)?;\n+ let mut source_catalog: JsonValue =\n+ serde_json::to_value(codex_models_manager::bundled_models_response()?)?;\n let models = source_catalog\n .get_mut(\"models\")\n .and_then(JsonValue::as_array_mut)", - "path": "codex-rs/tui/tests/suite/model_availability_nux.rs", - "status": "modified" - } - ], - "linked_issues": [ - "#16508" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints." - ], - "primary_pr": { - "body": "## Summary\n- split `models-manager` out of `core` and add `ModelsManagerConfig` plus `Config::to_models_manager_config()` so model metadata paths stop depending on `core::Config`\n- move login-owned/auth-owned code out of `core` into `codex-login`, move model provider config into `codex-model-provider-info`, move API bridge mapping into `codex-api`, move protocol-owned types/impls into `codex-protocol`, and move response debug helpers into a dedicated `response-debug-context` crate\n- move feedback tag emission into `codex-feedback`, relocate tests to the crates that now own the code, and keep broad temporary re-exports so this PR avoids a giant import-only rewrite\n\n## Major moves and decisions\n- created `codex-models-manager` as the owner for model cache/catalog/config/model info logic, including the new `ModelsManagerConfig` struct\n- created `codex-model-provider-info` as the owner for provider config parsing/defaults and kept temporary `codex-login`/`codex-core` re-exports for old import paths\n- moved `api_bridge` error mapping + `CoreAuthProvider` into `codex-api`, while `codex-login::api_bridge` temporarily re-exports those symbols and keeps the `auth_provider_from_auth` wrapper\n- moved `auth_env_telemetry` and `provider_auth` ownership to `codex-login`\n- moved `CodexErr` ownership to `codex-protocol::error`, plus `StreamOutput`, `bytes_to_string_smart`, and network policy helpers to protocol-owned modules\n- created `codex-response-debug-context` for `extract_response_debug_context`, `telemetry_transport_error_message`, and related response-debug plumbing instead of leaving that behavior in `core`\n- moved `FeedbackRequestTags`, `emit_feedback_request_tags`, and `emit_feedback_request_tags_with_auth_env` to `codex-feedback`\n- deferred removal of temporary re-exports and the mechanical import rewrites to a stacked follow-up PR so this PR stays reviewable\n\n## Test moves\n- moved auth refresh coverage from `core/tests/suite/auth_refresh.rs` to `login/tests/suite/auth_refresh.rs`\n- moved text encoding coverage from `core/tests/suite/text_encoding_fix.rs` to `protocol/src/exec_output_tests.rs`\n- moved model info override coverage from `core/tests/suite/model_info_overrides.rs` to `models-manager/src/model_info_overrides_tests.rs`\n", - "labels": [], - "merged_at": "2026-04-03T06:00:02Z", - "number": 16508, - "state": "merged", - "title": "extract models manager and related ownership from core", - "url": "https://github.com/openai/codex/pull/16508" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-16631.json b/artifacts/github/bundles/openai-codex-pr-16631.json deleted file mode 100644 index 4143efa..0000000 --- a/artifacts/github/bundles/openai-codex-pr-16631.json +++ /dev/null @@ -1,113 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "bolinfest", - "committed_at": "2026-04-02T23:15:18Z", - "message": "core: use native async SessionTask trait", - "sha": "2cafd783ac22e70083773157387068e6883b75eb", - "url": "https://github.com/openai/codex/commit/2cafd783ac22e70083773157387068e6883b75eb" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [ - "--lib", - "L129", - "L69", - "RPITIT", - "MIR_", - "SNAPSHOT_WARNING_THRESHOLD" - ], - "files": [ - { - "additions": 0, - "deletions": 2, - "patch_excerpt": "@@ -3119,7 +3119,6 @@ async fn spawn_task_turn_span_inherits_dispatch_trace_context() {\n captured_trace: Arc>>,\n }\n \n- #[async_trait::async_trait]\n impl SessionTask for TraceCaptureTask {\n fn kind(&self) -> TaskKind {\n TaskKind::Regular\n@@ -4375,7 +4374,6 @@ struct NeverEndingTask {\n listen_to_cancellation_token: bool,\n }\n \n-#[async_trait::async_trait]\n impl SessionTask for NeverEndingTask {\n fn kind(&self) -> TaskKind {\n self.kind", - "path": "codex-rs/core/src/codex_tests.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 2, - "patch_excerpt": "@@ -18,7 +18,7 @@ use rmcp::model::RequestId;\n use tokio::sync::oneshot;\n \n use crate::codex::TurnContext;\n-use crate::tasks::SessionTask;\n+use crate::tasks::AnySessionTask;\n use codex_protocol::models::PermissionProfile;\n use codex_protocol::protocol::ReviewDecision;\n use codex_protocol::protocol::TokenUsage;\n@@ -69,7 +69,7 @@ pub(crate) enum TaskKind {\n pub(crate) struct RunningTask {\n pub(crate) done: Arc,\n pub(crate) kind: TaskKind,\n- pub(crate) task: Arc,\n+ pub(crate) task: Arc,\n pub(crate) cancellation_token: CancellationToken,\n pub(crate) handle: Arc>,\n pub(crate) turn_context: Arc,", - "path": "codex-rs/core/src/state/turn.rs", - "status": "modified" - }, - { - "additions": 2, - "deletions": 4, - "patch_excerpt": "@@ -4,14 +4,12 @@ use super::SessionTask;\n use super::SessionTaskContext;\n use crate::codex::TurnContext;\n use crate::state::TaskKind;\n-use async_trait::async_trait;\n use codex_protocol::user_input::UserInput;\n use tokio_util::sync::CancellationToken;\n \n #[derive(Clone, Copy, Default)]\n pub(crate) struct CompactTask;\n \n-#[async_trait]\n impl SessionTask for CompactTask {\n fn kind(&self) -> TaskKind {\n TaskKind::Compact\n@@ -30,14 +28,14 @@ impl SessionTask for CompactTask {\n ) -> Option {\n let session = session.clone_session();\n let _ = if crate::compact::should_use_remote_compact_task(&ctx.provider) {\n- let _ = session.services.session_telemetry.counter(\n+ session.services.session_telemetry.counter(\n \"codex.task.compact\",\n /*inc*/ 1,\n &[(\"type\", \"remote\")],\n );\n ...", - "path": "codex-rs/core/src/tasks/compact.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 2, - "patch_excerpt": "@@ -2,7 +2,6 @@ use crate::codex::TurnContext;\n use crate::state::TaskKind;\n use crate::tasks::SessionTask;\n use crate::tasks::SessionTaskContext;\n-use async_trait::async_trait;\n use codex_git_utils::CreateGhostCommitOptions;\n use codex_git_utils::GhostSnapshotReport;\n use codex_git_utils::GitToolingError;\n@@ -26,7 +25,6 @@ pub(crate) struct GhostSnapshotTask {\n \n const SNAPSHOT_WARNING_THRESHOLD: Duration = Duration::from_secs(240);\n \n-#[async_trait]\n impl SessionTask for GhostSnapshotTask {\n fn kind(&self) -> TaskKind {\n TaskKind::Regular", - "path": "codex-rs/core/src/tasks/ghost_snapshot.rs", - "status": "modified" - }, - { - "additions": 69, - "deletions": 7, - "patch_excerpt": "@@ -9,7 +9,7 @@ use std::sync::Arc;\n use std::time::Duration;\n use std::time::Instant;\n \n-use async_trait::async_trait;\n+use futures::future::BoxFuture;\n use tokio::select;\n use tokio::sync::Notify;\n use tokio_util::sync::CancellationToken;\n@@ -126,7 +126,6 @@ impl SessionTaskContext {\n /// intentionally small: implementers identify themselves via\n /// [`SessionTask::kind`], perform their work in [`SessionTask::run`], and may\n /// release resources in [`SessionTask::abort`].\n-#[async_trait]\n pub(crate) trait SessionTask: Send + Sync + 'static {\n /// Describes the type of work the task performs so the session can\n /// surface it in telemetry and UI.\n@@ -143,21 +142,84 @@ pub(crate) trait SessionTask: Send + Sync + 'static {\n /// abort; implementers should watch for it and terminate quickly once it\n /// fires. Returning [`Some`] yields a final message that\n /// [`Sessio...", - "path": "codex-rs/core/src/tasks/mod.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 2, - "patch_excerpt": "@@ -1,6 +1,5 @@\n use std::sync::Arc;\n \n-use async_trait::async_trait;\n use tokio_util::sync::CancellationToken;\n \n use crate::codex::TurnContext;\n@@ -25,7 +24,6 @@ impl RegularTask {\n }\n }\n \n-#[async_trait]\n impl SessionTask for RegularTask {\n fn kind(&self) -> TaskKind {\n TaskKind::Regular", - "path": "codex-rs/core/src/tasks/regular.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 3, - "patch_excerpt": "@@ -1,7 +1,6 @@\n use std::borrow::Cow;\n use std::sync::Arc;\n \n-use async_trait::async_trait;\n use codex_protocol::config_types::WebSearchMode;\n use codex_protocol::items::TurnItem;\n use codex_protocol::models::ContentItem;\n@@ -48,7 +47,6 @@ impl ReviewTask {\n }\n }\n \n-#[async_trait]\n impl SessionTask for ReviewTask {\n fn kind(&self) -> TaskKind {\n TaskKind::Review\n@@ -65,7 +63,7 @@ impl SessionTask for ReviewTask {\n input: Vec,\n cancellation_token: CancellationToken,\n ) -> Option {\n- let _ = session.session.services.session_telemetry.counter(\n+ session.session.services.session_telemetry.counter(\n \"codex.task.review\",\n /*inc*/ 1,\n &[],", - "path": "codex-rs/core/src/tasks/review.rs", - "status": "modified" - }, - { - "additions": 5, - "deletions": 7, - "patch_excerpt": "@@ -4,7 +4,6 @@ use crate::codex::TurnContext;\n use crate::state::TaskKind;\n use crate::tasks::SessionTask;\n use crate::tasks::SessionTaskContext;\n-use async_trait::async_trait;\n use codex_git_utils::RestoreGhostCommitOptions;\n use codex_git_utils::restore_ghost_commit_with_options;\n use codex_protocol::models::ResponseItem;\n@@ -25,7 +24,6 @@ impl UndoTask {\n }\n }\n \n-#[async_trait]\n impl SessionTask for UndoTask {\n fn kind(&self) -> TaskKind {\n TaskKind::Regular\n@@ -42,11 +40,11 @@ impl SessionTask for UndoTask {\n _input: Vec,\n cancellation_token: CancellationToken,\n ) -> Option {\n- let _ = session.session.services.session_telemetry.counter(\n- \"codex.task.undo\",\n- /*inc*/ 1,\n- &[],\n- );\n+ session\n+ .session\n+ .services\n+ .session_telemetry\n+ ...", - "path": "codex-rs/core/src/tasks/undo.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 2, - "patch_excerpt": "@@ -1,7 +1,6 @@\n use std::sync::Arc;\n use std::time::Duration;\n \n-use async_trait::async_trait;\n use codex_async_utils::CancelErr;\n use codex_async_utils::OrCancelExt;\n use codex_protocol::user_input::UserInput;\n@@ -62,7 +61,6 @@ impl UserShellCommandTask {\n }\n }\n \n-#[async_trait]\n impl SessionTask for UserShellCommandTask {\n fn kind(&self) -> TaskKind {\n TaskKind::Regular", - "path": "codex-rs/core/src/tasks/user_shell.rs", - "status": "modified" - }, - { - "additions": 0, - "deletions": 1, - "patch_excerpt": "@@ -115,7 +115,6 @@ fn history_contains_inter_agent_communication(\n #[derive(Clone, Copy)]\n struct NeverEndingTask;\n \n-#[async_trait::async_trait]\n impl SessionTask for NeverEndingTask {\n fn kind(&self) -> TaskKind {\n TaskKind::Regular", - "path": "codex-rs/core/src/tools/handlers/multi_agents_tests.rs", - "status": "modified" - } - ], - "linked_issues": [ - "#16630" - ], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints.", - "Discovered via hourly merged-PR sync: https://github.com/openai/codex/pull/16631" - ], - "primary_pr": { - "body": "## Why\n\nThis continues the compile-time cleanup from #16630. `SessionTask` implementations are monomorphized, but `Session` stores the task behind a `dyn` boundary so it can drive and abort heterogenous turn tasks uniformly. That means we can move the `#[async_trait]` expansion off the implementation trait, keep a small boxed adapter only at the storage boundary, and preserve the existing task lifecycle semantics while reducing the amount of generated async-trait glue in `codex-core`.\n\nOne measurement caveat showed up while exploring this: a warm incremental benchmark based on `touch core/src/tasks/mod.rs && cargo check -p codex-core --lib` was basically flat, but that was the wrong benchmark for this change. Using package-clean `codex-core` rebuilds, like #16630, shows the real win.\n\nRelevant pre-change code:\n\n- [`SessionTask` with `#[async_trait]`](https://github.com/openai/codex/blob/3c7f013f9735e67796c70d95f75f436b7f97e3ec/codex-rs/core/src/tasks/mod.rs#L129-L182)\n- [`RunningTask` storing `Arc`](https://github.com/openai/codex/blob/3c7f013f9735e67796c70d95f75f436b7f97e3ec/codex-rs/core/src/state/turn.rs#L69-L77)\n\n## What changed\n\n- Switched `SessionTask::{run, abort}` to native RPITIT futures with explicit `Send` bounds.\n- Added a private `AnySessionTask` adapter that boxes those futures only at the `Arc` storage boundary.\n- Updated `RunningTask` to store `Arc` and removed `#[async_trait]` from the concrete task impls plus test-only `SessionTask` impls.\n\n## Timing\n\nBenchmarked package-clean `codex-core` rebuilds with dependencies left warm:\n\n```shell\ncargo check -p codex-core --lib >/dev/null\ncargo clean -p codex-core >/dev/null\n/usr/bin/time -p cargo +nightly rustc -p codex-core --lib -- \\\n -Z time-passes \\\n -Z time-passes-format=json >/dev/null\n```\n\n| revision | rustc `total` | process `real` | `generate_crate_metadata` | `MIR_borrow_checking` | `monomorphization_collector_graph_walk` |\n| --- | ---: | ---: | ---: | ---: | ---: |\n| parent `3c7f013f9735` | 67.21s | 67.71s | 24.61s | 23.43s | 22.43s |\n| this PR `2cafd783ac22` | 35.08s | 35.60s | 8.01s | 7.25s | 7.15s |\n| delta | -47.8% | -47.4% | -67.5% | -69.1% | -68.1% |\n\nFor completeness, the warm touched-file benchmark stayed flat (`1.96s` parent vs `1.97s` this PR), which is why that benchmark should not be used to evaluate this refactor.\n\n## Verification\n\n- Ran `cargo test -p codex-core`; this change compiled and task-related tests passed before hitting the same unrelated 5 `config::tests::*guardian*` failures already present on the parent stack.\n", - "labels": [], - "merged_at": "2026-04-02T23:39:57Z", - "number": 16631, - "state": "merged", - "title": "core: cut codex-core compile time 48% with native async SessionTask", - "url": "https://github.com/openai/codex/pull/16631" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-16633.json b/artifacts/github/bundles/openai-codex-pr-16633.json deleted file mode 100644 index 566c91e..0000000 --- a/artifacts/github/bundles/openai-codex-pr-16633.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "bolinfest", - "committed_at": "2026-04-02T23:58:05Z", - "message": "fix: address unused variable on windows", - "sha": "14da169c4bbfc7408837edbabf9b419701ec3516", - "url": "https://github.com/openai/codex/commit/14da169c4bbfc7408837edbabf9b419701ec3516" - } - ], - "default_branch": "main", - "docs_refs": [], - "examples_refs": [], - "extracted_flags": [], - "files": [ - { - "additions": 1, - "deletions": 2, - "patch_excerpt": "@@ -569,11 +569,10 @@ impl Tui {\n terminal.invalidate_viewport();\n }\n \n- let area = terminal.viewport_area;\n-\n // Update the y position for suspending so Ctrl-Z can place the cursor correctly.\n #[cfg(unix)]\n {\n+ let area = terminal.viewport_area;\n let inline_area_bottom = if self.alt_screen_active.load(Ordering::Relaxed) {\n self.alt_saved_viewport\n .map(|r| r.bottom().saturating_sub(1))", - "path": "codex-rs/tui/src/tui.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints." - ], - "primary_pr": { - "body": "This slipped in during https://github.com/openai/codex/pull/16578. I am still working on getting Windows working properly with Bazel on PRs.", - "labels": [], - "merged_at": "2026-04-03T00:05:45Z", - "number": 16633, - "state": "merged", - "title": "fix: address unused variable on windows", - "url": "https://github.com/openai/codex/pull/16633" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -} diff --git a/artifacts/github/bundles/openai-codex-pr-16946.json b/artifacts/github/bundles/openai-codex-pr-16946.json deleted file mode 100644 index de75519..0000000 --- a/artifacts/github/bundles/openai-codex-pr-16946.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "analysis_mode": "pr_first", - "commits": [ - { - "author": "viyatb-oai", - "committed_at": "2026-04-06T21:17:17Z", - "message": "feat(network): add danger-full-access denylist-only mode", - "sha": "9d932536967fd92c53ccd5521a259a830c0b991c", - "url": "https://github.com/openai/codex/commit/9d932536967fd92c53ccd5521a259a830c0b991c" - }, - { - "author": "viyatb-oai", - "committed_at": "2026-04-06T21:36:16Z", - "message": "fix(network): broaden danger-full-access denylist-only mode", - "sha": "4df276f319eba642876c22292f1da196ea85b2a8", - "url": "https://github.com/openai/codex/commit/4df276f319eba642876c22292f1da196ea85b2a8" - } - ], - "default_branch": "main", - "docs_refs": [ - "codex-rs/app-server/README.md" - ], - "examples_refs": [], - "extracted_flags": [ - "API", - "TUI", - "--check", - "MDM", - "GLOBAL_ALLOWLIST_PATTERN" - ], - "files": [ - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -9473,6 +9473,12 @@\n \"null\"\n ]\n },\n+ \"dangerFullAccessDenylistOnly\": {\n+ \"type\": [\n+ \"boolean\",\n+ \"null\"\n+ ]\n+ },\n \"dangerouslyAllowAllUnixSockets\": {\n \"type\": [\n \"boolean\",", - "path": "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -6296,6 +6296,12 @@\n \"null\"\n ]\n },\n+ \"dangerFullAccessDenylistOnly\": {\n+ \"type\": [\n+ \"boolean\",\n+ \"null\"\n+ ]\n+ },\n \"dangerouslyAllowAllUnixSockets\": {\n \"type\": [\n \"boolean\",", - "path": "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json", - "status": "modified" - }, - { - "additions": 6, - "deletions": 0, - "patch_excerpt": "@@ -151,6 +151,12 @@\n \"null\"\n ]\n },\n+ \"dangerFullAccessDenylistOnly\": {\n+ \"type\": [\n+ \"boolean\",\n+ \"null\"\n+ ]\n+ },\n \"dangerouslyAllowAllUnixSockets\": {\n \"type\": [\n \"boolean\",", - "path": "codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -29,4 +29,4 @@ unixSockets: { [key in string]?: NetworkUnixSocketPermission } | null,\n /**\n * Legacy compatibility view derived from `unix_sockets`.\n */\n-allowUnixSockets: Array | null, allowLocalBinding: boolean | null, };\n+allowUnixSockets: Array | null, allowLocalBinding: boolean | null, dangerFullAccessDenylistOnly: boolean | null, };", - "path": "codex-rs/app-server-protocol/schema/typescript/v2/NetworkRequirements.ts", - "status": "modified" - }, - { - "additions": 4, - "deletions": 0, - "patch_excerpt": "@@ -884,6 +884,7 @@ pub struct NetworkRequirements {\n /// Legacy compatibility view derived from `unix_sockets`.\n pub allow_unix_sockets: Option>,\n pub allow_local_binding: Option,\n+ pub danger_full_access_denylist_only: Option,\n }\n \n #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]\n@@ -7782,6 +7783,7 @@ mod tests {\n dangerously_allow_all_unix_sockets: None,\n domains: None,\n managed_allowed_domains_only: None,\n+ danger_full_access_denylist_only: None,\n allowed_domains: Some(vec![\"api.openai.com\".to_string()]),\n denied_domains: Some(vec![\"blocked.example.com\".to_string()]),\n unix_sockets: None,\n@@ -7808,6 +7810,7 @@ mod tests {\n ),\n ])),\n managed_allowed_domains_only...", - "path": "codex-rs/app-server-protocol/src/protocol/v2.rs", - "status": "modified" - }, - { - "additions": 1, - "deletions": 1, - "patch_excerpt": "@@ -194,7 +194,7 @@ Example with notification opt-out:\n - `externalAgentConfig/import` \u2014 apply selected external-agent migration items by passing explicit `migrationItems` with `cwd` (`null` for home).\n - `config/value/write` \u2014 write a single config key/value to the user's config.toml on disk.\n - `config/batchWrite` \u2014 apply multiple config edits atomically to the user's config.toml on disk, with optional `reloadUserConfig: true` to hot-reload loaded threads.\n-- `configRequirements/read` \u2014 fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), pinned feature values (`featureRequirements`), `enforceResidency`, and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly`.\n+- `configRequirements/rea...", - "path": "codex-rs/app-server/README.md", - "status": "modified" - }, - { - "additions": 5, - "deletions": 0, - "patch_excerpt": "@@ -449,6 +449,7 @@ fn map_network_requirements_to_api(\n .collect()\n }),\n managed_allowed_domains_only: network.managed_allowed_domains_only,\n+ danger_full_access_denylist_only: network.danger_full_access_denylist_only,\n allowed_domains,\n denied_domains,\n unix_sockets: network.unix_sockets.map(|unix_sockets| {\n@@ -594,6 +595,7 @@ mod tests {\n ]),\n }),\n managed_allowed_domains_only: Some(false),\n+ danger_full_access_denylist_only: Some(true),\n unix_sockets: Some(CoreNetworkUnixSocketPermissionsToml {\n entries: std::collections::BTreeMap::from([(\n \"/tmp/proxy.sock\".to_string(),\n@@ -653,6 +655,7 @@ mod tests {\n (\"example.com\".to_string(), NetworkDomainPermission::Deny),\n ...", - "path": "codex-rs/app-server/src/config_api.rs", - "status": "modified" - }, - { - "additions": 20, - "deletions": 0, - "patch_excerpt": "@@ -237,6 +237,8 @@ pub struct NetworkRequirementsToml {\n /// When true, only managed `allowed_domains` are respected while managed\n /// network enforcement is active. User allowlist entries are ignored.\n pub managed_allowed_domains_only: Option,\n+ /// In danger-full-access mode, allow all network access and enforce managed deny entries.\n+ pub danger_full_access_denylist_only: Option,\n pub unix_sockets: Option,\n pub allow_local_binding: Option,\n }\n@@ -255,6 +257,8 @@ struct RawNetworkRequirementsToml {\n /// When true, only managed `allowed_domains` are respected while managed\n /// network enforcement is active. User allowlist entries are ignored.\n managed_allowed_domains_only: Option,\n+ /// In danger-full-access mode, allow all network access and enforce managed deny entries.\n+ danger_fu...", - "path": "codex-rs/config/src/config_requirements.rs", - "status": "modified" - }, - { - "additions": 55, - "deletions": 27, - "patch_excerpt": "@@ -20,6 +20,8 @@ use codex_protocol::protocol::SandboxPolicy;\n use std::collections::HashSet;\n use std::sync::Arc;\n \n+const GLOBAL_ALLOWLIST_PATTERN: &str = \"*\";\n+\n #[derive(Debug, Clone, PartialEq, Eq)]\n pub struct NetworkProxySpec {\n config: NetworkProxyConfig,\n@@ -195,6 +197,8 @@ impl NetworkProxySpec {\n let allowlist_expansion_enabled =\n Self::allowlist_expansion_enabled(sandbox_policy, hard_deny_allowlist_misses);\n let denylist_expansion_enabled = Self::denylist_expansion_enabled(sandbox_policy);\n+ let danger_full_access_denylist_only =\n+ Self::danger_full_access_denylist_only_enabled(requirements, sandbox_policy);\n \n if let Some(enabled) = requirements.enabled {\n config.network.enabled = enabled;\n@@ -225,37 +229,43 @@ impl NetworkProxySpec {\n constraints.dangerously_allow_all_unix_sockets =\n ...", - "path": "codex-rs/core/src/config/network_proxy_spec.rs", - "status": "modified" - }, - { - "additions": 144, - "deletions": 0, - "patch_excerpt": "@@ -1,8 +1,11 @@\n use super::*;\n use crate::config_loader::NetworkDomainPermissionToml;\n use crate::config_loader::NetworkDomainPermissionsToml;\n+use crate::config_loader::NetworkUnixSocketPermissionToml;\n+use crate::config_loader::NetworkUnixSocketPermissionsToml;\n use codex_network_proxy::NetworkDomainPermission;\n use pretty_assertions::assert_eq;\n+use std::collections::BTreeMap;\n \n fn domain_permissions(\n entries: impl IntoIterator,\n@@ -178,6 +181,147 @@ fn danger_full_access_keeps_managed_allowlist_and_denylist_fixed() {\n assert_eq!(spec.constraints.denylist_expansion_enabled, Some(false));\n }\n \n+#[test]\n+fn danger_full_access_denylist_only_allows_all_domains_and_enforces_managed_denies() {\n+ let mut config = NetworkProxyConfig::default();\n+ config\n+ .network\n+ .set_allowed_domains(vec![\"evil.com\".to_stri...", - "path": "codex-rs/core/src/config/network_proxy_spec_tests.rs", - "status": "modified" - }, - { - "additions": 8, - "deletions": 1, - "patch_excerpt": "@@ -337,6 +337,7 @@ fn format_network_constraints(network: &NetworkConstraints) -> String {\n dangerously_allow_all_unix_sockets,\n domains,\n managed_allowed_domains_only,\n+ danger_full_access_denylist_only,\n unix_sockets,\n allow_local_binding,\n } = network;\n@@ -374,6 +375,11 @@ fn format_network_constraints(network: &NetworkConstraints) -> String {\n \"managed_allowed_domains_only={managed_allowed_domains_only}\"\n ));\n }\n+ if let Some(danger_full_access_denylist_only) = danger_full_access_denylist_only {\n+ parts.push(format!(\n+ \"danger_full_access_denylist_only={danger_full_access_denylist_only}\"\n+ ));\n+ }\n if let Some(unix_sockets) = unix_sockets {\n parts.push(format!(\n \"unix_sockets={}\",\n@@ -557,6 +563,7 @@ mod tests {\n NetworkDomainP...", - "path": "codex-rs/tui/src/debug_config.rs", - "status": "modified" - } - ], - "linked_issues": [], - "notes": [ - "Built from GitHub pull-request, commits, files, and repo endpoints." - ], - "primary_pr": { - "body": "## Summary\n\nThis adds `experimental_network.danger_full_access_denylist_only` for orgs that want yolo / danger-full-access sessions to keep full network access while still enforcing centrally managed deny rules.\n\nWhen the flag is true and the session sandbox is `danger-full-access`, the network proxy starts with:\n\n- domain allowlist set to `*`\n- managed domain `deny` entries enforced\n- upstream proxy use allowed\n- all Unix sockets allowed\n- local/private binding allowed\n\nCaveat: the denylist is best effort only. In yolo / danger-full-access mode, Codex or the model can use an allowed socket or other local/private network path to bypass the proxy denylist, so this should not be treated as a hard security boundary.\n\nThe flag is intentionally scoped to `SandboxPolicy::DangerFullAccess`. Read-only and workspace-write modes keep the existing managed/user allowlist, denylist, Unix socket, and local-binding behavior. This does not enable the non-loopback proxy listener setting; that still requires its own explicit config.\n\nThis also threads the new field through config requirements parsing, app-server protocol/schema output, config API mapping, and the TUI debug config output.\n\n## How to use\n\nAdd the flag under `[experimental_network]` in the network policy config that is delivered to Codex. The setting is not under `[permissions]`.\n\n```toml\n[experimental_network]\nenabled = true\ndanger_full_access_denylist_only = true\n\n[experimental_network.domains]\n\"blocked.example.com\" = \"deny\"\n\"*.blocked.example.com\" = \"deny\"\n```\n\nWith that configuration, yolo / danger-full-access sessions get broad network access except for the managed denied domains above. The denylist remains a best-effort proxy policy because the session may still use allowed sockets to bypass it. Other sandbox modes do not get the wildcard domain allowlist or the socket/local-binding relaxations from this flag.\n\n## Verification\n\n- `cargo test -p codex-config network_requirements`\n- `cargo test -p codex-core network_proxy_spec`\n- `cargo test -p codex-app-server map_requirements_toml_to_api`\n- `cargo test -p codex-tui debug_config_output`\n- `cargo test -p codex-app-server-protocol`\n- `just write-app-server-schema`\n- `just fmt`\n- `just fix -p codex-config -p codex-core -p codex-app-server-protocol -p codex-app-server -p codex-tui`\n- `just fix -p codex-core -p codex-config`\n- `git diff --check`\n- `cargo clean`", - "labels": [], - "merged_at": "2026-04-07T02:38:51Z", - "number": 16946, - "state": "merged", - "title": "[codex] Add danger-full-access denylist-only network mode", - "url": "https://github.com/openai/codex/pull/16946" - }, - "repo": "openai/codex", - "schema": "github_change_bundle/v1" -}