From 833212115e18e9eb75b0a75dcecf4034b8bab6ac Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Tue, 21 Apr 2026 08:33:58 -0700 Subject: [PATCH 01/16] Move external agent config out of core (#18850) ## Summary - Move external agent config migration logic and tests from `codex-core` into `app-server/src/config`. - Keep the migration service crate-private to app-server and update the API adapter imports. - Remove stale core re-exports and expose only the needed marketplace source helper. ## Testing - `cargo test -p codex-app-server config::external_agent_config` - `just fmt` - `just fix -p codex-app-server` - `just fix -p codex-core` - `git diff --check` --- .../src/config}/external_agent_config.rs | 67 +++++++++++-------- .../config}/external_agent_config_tests.rs | 0 codex-rs/app-server/src/config/mod.rs | 1 + .../src/external_agent_config_api.rs | 14 ++-- codex-rs/app-server/src/lib.rs | 1 + codex-rs/core/src/lib.rs | 1 - codex-rs/core/src/plugins/marketplace_add.rs | 2 +- codex-rs/core/src/plugins/mod.rs | 5 +- 8 files changed, 50 insertions(+), 41 deletions(-) rename codex-rs/{core/src => app-server/src/config}/external_agent_config.rs (95%) rename codex-rs/{core/src => app-server/src/config}/external_agent_config_tests.rs (100%) create mode 100644 codex-rs/app-server/src/config/mod.rs diff --git a/codex-rs/core/src/external_agent_config.rs b/codex-rs/app-server/src/config/external_agent_config.rs similarity index 95% rename from codex-rs/core/src/external_agent_config.rs rename to codex-rs/app-server/src/config/external_agent_config.rs index 18bf032277..1e57cd4a4d 100644 --- a/codex-rs/core/src/external_agent_config.rs +++ b/codex-rs/app-server/src/config/external_agent_config.rs @@ -1,18 +1,18 @@ -use crate::config::Config; -use crate::config::ConfigBuilder; -use crate::plugins::MarketplaceAddRequest; -use crate::plugins::PluginId; -use crate::plugins::PluginInstallRequest; -use crate::plugins::PluginsManager; -use crate::plugins::add_marketplace; -use crate::plugins::configured_plugins_from_stack; -use crate::plugins::find_marketplace_manifest_path; -use crate::plugins::is_local_marketplace_source; -use crate::plugins::parse_marketplace_source; +use codex_config::types::PluginConfig; +use codex_core::config::Config; +use codex_core::config::ConfigBuilder; +use codex_core::plugins::MarketplaceAddRequest; +use codex_core::plugins::PluginId; +use codex_core::plugins::PluginInstallRequest; +use codex_core::plugins::PluginsManager; +use codex_core::plugins::add_marketplace; +use codex_core::plugins::is_local_marketplace_source; use codex_core_plugins::marketplace::MarketplacePluginInstallPolicy; +use codex_core_plugins::marketplace::find_marketplace_manifest_path; use codex_protocol::protocol::Product; use serde_json::Value as JsonValue; use std::collections::BTreeMap; +use std::collections::HashMap; use std::collections::HashSet; use std::ffi::OsString; use std::fs; @@ -29,13 +29,13 @@ const EXTERNAL_OFFICIAL_MARKETPLACE_NAME: &str = "claude-plugins-official"; const EXTERNAL_OFFICIAL_MARKETPLACE_SOURCE: &str = "anthropics/claude-plugins-official"; #[derive(Debug, Clone, PartialEq, Eq)] -pub struct ExternalAgentConfigDetectOptions { +pub(crate) struct ExternalAgentConfigDetectOptions { pub include_home: bool, pub cwds: Option>, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ExternalAgentConfigMigrationItemType { +pub(crate) enum ExternalAgentConfigMigrationItemType { Config, Skills, AgentsMd, @@ -44,24 +44,24 @@ pub enum ExternalAgentConfigMigrationItemType { } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct PluginsMigration { +pub(crate) struct PluginsMigration { pub marketplace_name: String, pub plugin_names: Vec, } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct MigrationDetails { +pub(crate) struct MigrationDetails { pub plugins: Vec, } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct PendingPluginImport { +pub(crate) struct PendingPluginImport { pub cwd: Option, pub details: MigrationDetails, } #[derive(Debug, Clone, Default, PartialEq, Eq)] -pub struct PluginImportOutcome { +pub(crate) struct PluginImportOutcome { pub succeeded_marketplaces: Vec, pub succeeded_plugin_ids: Vec, pub failed_marketplaces: Vec, @@ -69,7 +69,7 @@ pub struct PluginImportOutcome { } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct ExternalAgentConfigMigrationItem { +pub(crate) struct ExternalAgentConfigMigrationItem { pub item_type: ExternalAgentConfigMigrationItemType, pub description: String, pub cwd: Option, @@ -77,13 +77,13 @@ pub struct ExternalAgentConfigMigrationItem { } #[derive(Clone)] -pub struct ExternalAgentConfigService { +pub(crate) struct ExternalAgentConfigService { codex_home: PathBuf, external_agent_home: PathBuf, } impl ExternalAgentConfigService { - pub fn new(codex_home: PathBuf) -> Self { + pub(crate) fn new(codex_home: PathBuf) -> Self { let external_agent_home = default_external_agent_home(); Self { codex_home, @@ -99,7 +99,7 @@ impl ExternalAgentConfigService { } } - pub async fn detect( + pub(crate) async fn detect( &self, params: ExternalAgentConfigDetectOptions, ) -> io::Result> { @@ -119,7 +119,7 @@ impl ExternalAgentConfigService { Ok(items) } - pub async fn import( + pub(crate) async fn import( &self, migration_items: Vec, ) -> io::Result> { @@ -297,10 +297,21 @@ impl ExternalAgentConfigService { .await { Ok(config) => { - let configured_plugin_ids = - configured_plugins_from_stack(&config.config_layer_stack) - .into_keys() - .collect::>(); + let configured_plugin_ids = config + .config_layer_stack + .get_user_layer() + .and_then(|user_layer| user_layer.config.get("plugins")) + .and_then(|plugins| { + match plugins.clone().try_into::>() { + Ok(plugins) => Some(plugins), + Err(err) => { + tracing::warn!("invalid plugins config: {err}"); + None + } + } + }) + .map(|plugins| plugins.into_keys().collect::>()) + .unwrap_or_default(); let configured_marketplace_plugins = configured_marketplace_plugins( &config, &PluginsManager::new(self.codex_home.clone()), @@ -410,7 +421,7 @@ impl ExternalAgentConfigService { Ok((local_details, remote_details)) } - pub async fn import_plugins( + pub(crate) async fn import_plugins( &self, cwd: Option<&Path>, details: Option, @@ -638,7 +649,7 @@ fn extract_plugin_migration_details( let loadable_marketplaces = collect_marketplace_import_sources(settings, source_root) .into_iter() .filter_map(|(marketplace_name, source)| { - parse_marketplace_source(&source.source, source.ref_name) + is_local_marketplace_source(&source.source, source.ref_name) .ok() .map(|_| marketplace_name) }) diff --git a/codex-rs/core/src/external_agent_config_tests.rs b/codex-rs/app-server/src/config/external_agent_config_tests.rs similarity index 100% rename from codex-rs/core/src/external_agent_config_tests.rs rename to codex-rs/app-server/src/config/external_agent_config_tests.rs diff --git a/codex-rs/app-server/src/config/mod.rs b/codex-rs/app-server/src/config/mod.rs new file mode 100644 index 0000000000..95d64d152a --- /dev/null +++ b/codex-rs/app-server/src/config/mod.rs @@ -0,0 +1 @@ +pub(crate) mod external_agent_config; diff --git a/codex-rs/app-server/src/external_agent_config_api.rs b/codex-rs/app-server/src/external_agent_config_api.rs index b381c353da..0741ad5bd8 100644 --- a/codex-rs/app-server/src/external_agent_config_api.rs +++ b/codex-rs/app-server/src/external_agent_config_api.rs @@ -1,3 +1,8 @@ +use crate::config::external_agent_config::ExternalAgentConfigDetectOptions; +use crate::config::external_agent_config::ExternalAgentConfigMigrationItem as CoreMigrationItem; +use crate::config::external_agent_config::ExternalAgentConfigMigrationItemType as CoreMigrationItemType; +use crate::config::external_agent_config::ExternalAgentConfigService; +use crate::config::external_agent_config::PendingPluginImport; use crate::error_code::INTERNAL_ERROR_CODE; use codex_app_server_protocol::ExternalAgentConfigDetectParams; use codex_app_server_protocol::ExternalAgentConfigDetectResponse; @@ -7,11 +12,6 @@ use codex_app_server_protocol::ExternalAgentConfigMigrationItemType; use codex_app_server_protocol::JSONRPCErrorError; use codex_app_server_protocol::MigrationDetails; use codex_app_server_protocol::PluginsMigration; -use codex_core::external_agent_config::ExternalAgentConfigDetectOptions; -use codex_core::external_agent_config::ExternalAgentConfigMigrationItem as CoreMigrationItem; -use codex_core::external_agent_config::ExternalAgentConfigMigrationItemType as CoreMigrationItemType; -use codex_core::external_agent_config::ExternalAgentConfigService; -use codex_core::external_agent_config::PendingPluginImport; use std::io; use std::path::PathBuf; @@ -108,12 +108,12 @@ impl ExternalAgentConfigApi { description: migration_item.description, cwd: migration_item.cwd, details: migration_item.details.map(|details| { - codex_core::external_agent_config::MigrationDetails { + crate::config::external_agent_config::MigrationDetails { plugins: details .plugins .into_iter() .map(|plugin| { - codex_core::external_agent_config::PluginsMigration { + crate::config::external_agent_config::PluginsMigration { marketplace_name: plugin.marketplace_name, plugin_names: plugin.plugin_names, } diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index d9c108033f..0eac54df2a 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -70,6 +70,7 @@ mod app_server_tracing; mod bespoke_event_handling; mod codex_message_processor; mod command_exec; +mod config; mod config_api; mod dynamic_tools; mod error_code; diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index fc96aceaf9..88a3174443 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -32,7 +32,6 @@ mod context_manager; pub mod exec; pub mod exec_env; mod exec_policy; -pub mod external_agent_config; pub mod file_watcher; mod flags; #[cfg(test)] diff --git a/codex-rs/core/src/plugins/marketplace_add.rs b/codex-rs/core/src/plugins/marketplace_add.rs index 00bc19e40d..a03a2134e5 100644 --- a/codex-rs/core/src/plugins/marketplace_add.rs +++ b/codex-rs/core/src/plugins/marketplace_add.rs @@ -56,7 +56,7 @@ pub async fn add_marketplace( .map_err(|err| MarketplaceAddError::Internal(format!("failed to add marketplace: {err}")))? } -pub(crate) fn is_local_marketplace_source( +pub fn is_local_marketplace_source( source: &str, explicit_ref: Option, ) -> Result { diff --git a/codex-rs/core/src/plugins/mod.rs b/codex-rs/core/src/plugins/mod.rs index 5806016b9a..55b9ff8a9e 100644 --- a/codex-rs/core/src/plugins/mod.rs +++ b/codex-rs/core/src/plugins/mod.rs @@ -25,7 +25,6 @@ pub use codex_plugin::validate_plugin_segment; pub type LoadedPlugin = codex_plugin::LoadedPlugin; pub type PluginLoadOutcome = codex_plugin::PluginLoadOutcome; -pub(crate) use codex_core_plugins::marketplace::find_marketplace_manifest_path; pub(crate) use discoverable::list_tool_suggest_discoverable_plugins; pub(crate) use injection::build_plugin_injections; pub use installed_marketplaces::INSTALLED_MARKETPLACES_DIR; @@ -46,13 +45,11 @@ pub use manager::PluginRemoteSyncError; pub use manager::PluginUninstallError; pub use manager::PluginsManager; pub use manager::RemotePluginSyncResult; -pub(crate) use manager::configured_plugins_from_stack; pub use marketplace_add::MarketplaceAddError; pub use marketplace_add::MarketplaceAddOutcome; pub use marketplace_add::MarketplaceAddRequest; pub use marketplace_add::add_marketplace; -pub(crate) use marketplace_add::is_local_marketplace_source; -pub(crate) use marketplace_add::parse_marketplace_source; +pub use marketplace_add::is_local_marketplace_source; pub use marketplace_remove::MarketplaceRemoveError; pub use marketplace_remove::MarketplaceRemoveOutcome; pub use marketplace_remove::MarketplaceRemoveRequest; From 53cf12cd529c0dd289731dce3bf1635cb5e48799 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Tue, 21 Apr 2026 09:00:40 -0700 Subject: [PATCH 02/16] build: reduce Rust dev debuginfo (#18844) ## What changed This PR makes the default Cargo dev profile use line-tables-only debug info: ```toml [profile.dev] debug = 1 ``` That keeps useful backtraces while avoiding the cost of full variable debug info in normal local dev builds. This also makes the Bazel CI setting explicit with `-Cdebuginfo=0` for target and exec-configuration Rust actions. Bazel/rules_rust does not read Cargo profiles for this setting, and the current fastbuild action already emitted `--codegen=debuginfo=0`; the Bazel part of this PR makes that choice direct in our build configuration. ## Why The slow codex-core rebuilds are dominated by debug-info codegen, not parsing or type checking. On a warm-dependency package rebuild, the baseline codex-core compile was about 39.5s wall / 38.9s rustc total, with codegen_crate around 14.0s and LLVM_passes around 13.4s. Setting codex-core to line-tables-only debug info brought that to about 27.2s wall / 26.7s rustc total, with codegen_crate around 3.1s and LLVM_passes around 2.8s. `debug = 0` was only about another 0.7s faster than `debug = 1` in the codex-core measurement, so `debug = 1` is the better default dev tradeoff: it captures nearly all of the compile-time win while preserving basic debuggability. I also sampled other first-party crates instead of keeping a codex-core-only package override. codex-app-server showed the same pattern: rustc total dropped from 15.85s to 10.48s, while codegen_crate plus LLVM_passes dropped from about 13.47s to 3.23s. codex-app-server-protocol had a smaller but still real improvement, 16.05s to 14.58s total, and smaller crates showed modest wins. That points to a workspace dev-profile policy rather than a hand-maintained list of large crates. ## Relationship to #18612 [#18612](https://github.com/openai/codex/pull/18612) added the `dev-small` profile. That remains useful when someone wants a working local build quickly and is willing to opt in with `cargo build --profile dev-small`. This PR is deliberately less aggressive: it changes the common default dev profile while preserving line tables/backtraces. `dev-small` remains the explicit "build quickly, no debuggability concern" path. ## Other investigation I looked for another structural win comparable to [#16631](https://github.com/openai/codex/pull/16631) and [#16630](https://github.com/openai/codex/pull/16630), but did not find one. The attempted TOML monomorphization changes were noisy or worse in measurement, and the async task changes reduced some instantiations but only translated to roughly a one-second improvement while being much more disruptive. The debug-info setting was the one repeatable, material win that survived measurement. ## Verification - `just bazel-lock-update` - `just bazel-lock-check` - `cargo check -p codex-core --lib` - `cargo test -p codex-core --lib` - Bazel `aquery --config=ci-linux` confirmed `--codegen=debuginfo=0` and `-Cdebuginfo=0` for `//codex-rs/core:core` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/18844). * #18846 * __->__ #18844 --- .bazelrc | 4 ++++ codex-rs/Cargo.toml | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/.bazelrc b/.bazelrc index fb6e93e58c..112c2301e2 100644 --- a/.bazelrc +++ b/.bazelrc @@ -65,6 +65,10 @@ common:ci --verbose_failures common:ci --build_metadata=REPO_URL=https://github.com/openai/codex.git common:ci --build_metadata=ROLE=CI common:ci --build_metadata=VISIBILITY=PUBLIC +# rules_rust derives debug level from Bazel toolchain/compilation-mode settings, +# not Cargo profiles. Keep CI Rust actions explicit and lean. +common:ci --@rules_rust//rust/settings:extra_rustc_flag=-Cdebuginfo=0 +common:ci --@rules_rust//rust/settings:extra_exec_rustc_flag=-Cdebuginfo=0 # Disable disk cache in CI since we have a remote one and aren't using persistent workers. common:ci --disk_cache= diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index f4be9c1abc..b83674e591 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -430,6 +430,11 @@ ignored = [ "codex-v8-poc", ] +[profile.dev] +# Keep line tables/backtraces while avoiding expensive full variable debug info +# across local dev builds. +debug = 1 + [profile.dev-small] inherits = "dev" opt-level = 0 From bf2a34b4b2bf42fc9bef32f7d56243ba69f47300 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Tue, 21 Apr 2026 17:24:30 +0100 Subject: [PATCH 03/16] feat: baseline lib (#18848) This add with 2 entry point: * `reset_git_repository` that takes a directory and set it as a new git root * `diff_since_latest_init` this returns the diff for a given directory since the last `reset_git_repository` --- MODULE.bazel.lock | 76 +- codex-rs/Cargo.lock | 1082 +++++++++++++++++++++++++++- codex-rs/Cargo.toml | 1 + codex-rs/git-utils/Cargo.toml | 4 + codex-rs/git-utils/README.md | 6 +- codex-rs/git-utils/src/baseline.rs | 645 +++++++++++++++++ 6 files changed, 1784 insertions(+), 30 deletions(-) create mode 100644 codex-rs/git-utils/src/baseline.rs diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index b085bb68b4..1dbc873796 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -732,6 +732,7 @@ "clap_lex_1.0.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0.14\"}],\"features\":{}}", "clap_lex_1.1.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0.16\"}],\"features\":{}}", "clipboard-win_5.4.1": "{\"dependencies\":[{\"name\":\"error-code\",\"req\":\"^3\",\"target\":\"cfg(windows)\"},{\"name\":\"windows-win\",\"optional\":true,\"req\":\"^3\",\"target\":\"cfg(windows)\"}],\"features\":{\"monitor\":[\"windows-win\"],\"std\":[\"error-code/std\"]}}", + "clru_0.6.3": "{\"dependencies\":[{\"name\":\"hashbrown\",\"req\":\"^0.16\"}],\"features\":{}}", "cmake_0.1.57": "{\"dependencies\":[{\"name\":\"cc\",\"req\":\"^1.2.46\"}],\"features\":{}}", "cmp_any_0.8.1": "{\"dependencies\":[],\"features\":{}}", "codespan-reporting_0.13.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.6.3\"},{\"kind\":\"dev\",\"name\":\"peg\",\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"pico-args\",\"req\":\"^0.5.0\"},{\"kind\":\"dev\",\"name\":\"rustyline\",\"req\":\"^6\"},{\"default_features\":false,\"features\":[\"derive\",\"alloc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"termcolor\",\"optional\":true,\"req\":\"^1.0.4\"},{\"name\":\"unicode-width\",\"req\":\">=0.1, <0.3\"},{\"kind\":\"dev\",\"name\":\"unindent\",\"req\":\"^0.1\"}],\"features\":{\"ascii-only\":[],\"default\":[\"std\",\"termcolor\"],\"serialization\":[\"serde\"],\"std\":[\"serde?/std\"],\"termcolor\":[\"std\",\"dep:termcolor\"]}}", @@ -797,6 +798,7 @@ "darling_macro_0.20.11": "{\"dependencies\":[{\"name\":\"darling_core\",\"req\":\"=0.20.11\"},{\"name\":\"quote\",\"req\":\"^1.0.18\"},{\"name\":\"syn\",\"req\":\"^2.0.15\"}],\"features\":{}}", "darling_macro_0.21.3": "{\"dependencies\":[{\"name\":\"darling_core\",\"req\":\"=0.21.3\"},{\"name\":\"quote\",\"req\":\"^1.0.18\"},{\"name\":\"syn\",\"req\":\"^2.0.15\"}],\"features\":{}}", "darling_macro_0.23.0": "{\"dependencies\":[{\"name\":\"darling_core\",\"req\":\"=0.23.0\"},{\"name\":\"quote\",\"req\":\"^1.0.18\"},{\"name\":\"syn\",\"req\":\"^2.0.15\"}],\"features\":{}}", + "dashmap_6.1.0": "{\"dependencies\":[{\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.3.0\"},{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"name\":\"crossbeam-utils\",\"req\":\"^0.8\"},{\"default_features\":false,\"features\":[\"raw\"],\"name\":\"hashbrown\",\"req\":\"^0.14.0\"},{\"name\":\"lock_api\",\"req\":\"^0.4.10\"},{\"name\":\"once_cell\",\"req\":\"^1.18.0\"},{\"name\":\"parking_lot_core\",\"req\":\"^0.9.8\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.7.0\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.188\"},{\"default_features\":false,\"name\":\"typesize\",\"optional\":true,\"req\":\"^0.1.8\"}],\"features\":{\"inline\":[\"hashbrown/inline-more\"],\"raw-api\":[],\"typesize\":[\"dep:typesize\"]}}", "dasp_sample_0.11.0": "{\"dependencies\":[],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "data-encoding_2.10.0": "{\"dependencies\":[],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"std\":[\"alloc\"]}}", "dbus-secret-service_4.1.0": "{\"dependencies\":[{\"name\":\"aes\",\"optional\":true,\"req\":\"^0.8\"},{\"features\":[\"std\"],\"name\":\"block-padding\",\"optional\":true,\"req\":\"^0.3\"},{\"features\":[\"block-padding\",\"alloc\"],\"name\":\"cbc\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"dbus\",\"req\":\"^0.9\"},{\"name\":\"fastrand\",\"optional\":true,\"req\":\"^2.3\"},{\"name\":\"hkdf\",\"optional\":true,\"req\":\"^0.12\"},{\"name\":\"num\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"once_cell\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"openssl\",\"optional\":true,\"req\":\"^0.10.55\"},{\"name\":\"sha2\",\"optional\":true,\"req\":\"^0.10\"},{\"features\":[\"derive\"],\"name\":\"zeroize\",\"req\":\"^1.8\"}],\"features\":{\"crypto-openssl\":[\"dep:fastrand\",\"dep:num\",\"dep:once_cell\",\"dep:openssl\"],\"crypto-rust\":[\"dep:aes\",\"dep:block-padding\",\"dep:cbc\",\"dep:fastrand\",\"dep:hkdf\",\"dep:num\",\"dep:once_cell\",\"dep:sha2\"],\"vendored\":[\"dbus/vendored\",\"openssl?/vendored\"]}}", @@ -871,6 +873,7 @@ "event-listener_5.4.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"concurrent-queue\",\"req\":\"^2.4.0\"},{\"default_features\":false,\"features\":[\"cargo_bench_support\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7\"},{\"default_features\":false,\"name\":\"critical-section\",\"optional\":true,\"req\":\"^1.2.0\"},{\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"critical-section\",\"req\":\"^1.2.0\"},{\"kind\":\"dev\",\"name\":\"futures-lite\",\"req\":\"^2.0.0\"},{\"name\":\"loom\",\"optional\":true,\"req\":\"^0.7\",\"target\":\"cfg(loom)\"},{\"name\":\"parking\",\"optional\":true,\"req\":\"^2.0.0\",\"target\":\"cfg(not(target_family = \\\"wasm\\\"))\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.12\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"portable-atomic-util\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"portable_atomic_crate\",\"optional\":true,\"package\":\"portable-atomic\",\"req\":\"^1.2.0\"},{\"kind\":\"dev\",\"name\":\"try-lock\",\"req\":\"^0.2.5\"},{\"kind\":\"dev\",\"name\":\"waker-fn\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(target_family = \\\"wasm\\\")\"}],\"features\":{\"default\":[\"std\"],\"loom\":[\"concurrent-queue/loom\",\"parking?/loom\",\"dep:loom\"],\"portable-atomic\":[\"portable-atomic-util\",\"portable_atomic_crate\",\"concurrent-queue/portable-atomic\"],\"std\":[\"concurrent-queue/std\",\"parking\"]}}", "eventsource-stream_0.2.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"http\",\"req\":\"^0.2\"},{\"default_features\":false,\"name\":\"nom\",\"req\":\"^7.1\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.8\"},{\"features\":[\"stream\"],\"kind\":\"dev\",\"name\":\"reqwest\",\"req\":\"^0.11\"},{\"features\":[\"macros\",\"rt\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"url\",\"req\":\"^2.2\"}],\"features\":{\"default\":[\"std\"],\"std\":[\"futures-core/std\",\"nom/std\"]}}", "eyre_0.6.12": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.28\"},{\"kind\":\"dev\",\"name\":\"backtrace\",\"req\":\"^0.3.46\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\"},{\"name\":\"indenter\",\"req\":\"^0.3.0\"},{\"name\":\"once_cell\",\"req\":\"^1.18.0\"},{\"default_features\":false,\"name\":\"pyo3\",\"optional\":true,\"req\":\"^0.20\"},{\"default_features\":false,\"features\":[\"auto-initialize\"],\"kind\":\"dev\",\"name\":\"pyo3\",\"req\":\"^0.20\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"syn\",\"req\":\"^2.0\"},{\"kind\":\"dev\",\"name\":\"thiserror\",\"req\":\"^1.0\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.19\"}],\"features\":{\"auto-install\":[],\"default\":[\"auto-install\",\"track-caller\"],\"track-caller\":[]}}", + "faster-hex_0.10.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bytes\",\"req\":\"^1.4.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"name\":\"heapless\",\"req\":\"^0.8\",\"target\":\"cfg(not(feature = \\\"alloc\\\"))\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.3.2\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rustc-hex\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"alloc\":[],\"default\":[\"std\",\"serde\"],\"serde\":[\"dep:serde\",\"alloc\"],\"std\":[\"alloc\",\"serde?/std\"]}}", "fastrand_2.3.0": "{\"dependencies\":[{\"features\":[\"js\"],\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.2\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.2\"},{\"features\":[\"js\"],\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.2\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"kind\":\"dev\",\"name\":\"wyhash\",\"req\":\"^0.5\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"js\":[\"std\",\"getrandom\"],\"std\":[\"alloc\"]}}", "fax_0.2.6": "{\"dependencies\":[{\"name\":\"fax_derive\",\"req\":\"^0.2.0\"},{\"kind\":\"dev\",\"name\":\"tiff\",\"req\":\"^0.9\"}],\"features\":{\"debug\":[]}}", "fax_derive_0.2.0": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}", @@ -944,6 +947,59 @@ "git+https://github.com/openai-oss-forks/tungstenite-rs?rev=9200079d3b54a1ff51072e24d81fd354f085156f#9200079d3b54a1ff51072e24d81fd354f085156f_tungstenite": "{\"dependencies\":[{\"name\":\"bytes\"},{\"default_features\":true,\"features\":[],\"name\":\"data-encoding\",\"optional\":true},{\"default_features\":false,\"features\":[\"zlib\"],\"name\":\"flate2\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"headers\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"http\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"httparse\",\"optional\":true},{\"name\":\"log\"},{\"default_features\":true,\"features\":[],\"name\":\"native-tls-crate\",\"optional\":true,\"package\":\"native-tls\"},{\"name\":\"rand\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"rustls\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"rustls-native-certs\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"rustls-pki-types\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"sha1\",\"optional\":true},{\"name\":\"thiserror\"},{\"default_features\":true,\"features\":[],\"name\":\"url\",\"optional\":true},{\"name\":\"utf-8\"},{\"default_features\":true,\"features\":[],\"name\":\"webpki-roots\",\"optional\":true}],\"features\":{\"__rustls-tls\":[\"rustls\",\"rustls-pki-types\"],\"default\":[\"handshake\"],\"deflate\":[\"headers\",\"flate2\"],\"handshake\":[\"data-encoding\",\"headers\",\"httparse\",\"sha1\"],\"headers\":[\"http\",\"dep:headers\"],\"native-tls\":[\"native-tls-crate\"],\"native-tls-vendored\":[\"native-tls\",\"native-tls-crate/vendored\"],\"proxy\":[\"handshake\"],\"rustls-tls-native-roots\":[\"__rustls-tls\",\"rustls-native-certs\"],\"rustls-tls-webpki-roots\":[\"__rustls-tls\",\"webpki-roots\"],\"url\":[\"dep:url\"]},\"strip_prefix\":\"\"}", "git+https://github.com/rust-lang/rust-clippy?rev=20ce69b9a63bcd2756cd906fe0964d1e901e042a#20ce69b9a63bcd2756cd906fe0964d1e901e042a_clippy_utils": "{\"dependencies\":[{\"default_features\":false,\"features\":[],\"name\":\"arrayvec\",\"optional\":false},{\"name\":\"itertools\"},{\"name\":\"rustc_apfloat\"},{\"default_features\":true,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":false}],\"features\":{},\"strip_prefix\":\"clippy_utils\"}", "git2_0.20.4": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"^4.4.13\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"name\":\"libgit2-sys\",\"req\":\"^0.18.3\"},{\"name\":\"log\",\"req\":\"^0.4.8\"},{\"name\":\"openssl-probe\",\"optional\":true,\"req\":\"^0.1\",\"target\":\"cfg(all(unix, not(target_os = \\\"macos\\\")))\"},{\"name\":\"openssl-sys\",\"optional\":true,\"req\":\"^0.9.45\",\"target\":\"cfg(all(unix, not(target_os = \\\"macos\\\")))\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.1.0\"},{\"features\":[\"formatting\"],\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.37\"},{\"name\":\"url\",\"req\":\"^2.5.4\"}],\"features\":{\"default\":[\"ssh\",\"https\"],\"https\":[\"libgit2-sys/https\",\"openssl-sys\",\"openssl-probe\"],\"ssh\":[\"libgit2-sys/ssh\"],\"unstable\":[],\"vendored-libgit2\":[\"libgit2-sys/vendored\"],\"vendored-openssl\":[\"openssl-sys/vendored\",\"libgit2-sys/vendored-openssl\"],\"zlib-ng-compat\":[\"libgit2-sys/zlib-ng-compat\"]}}", + "gix-actor_0.40.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\",\"unicode\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"gix-date\",\"req\":\"^0.15.0\"},{\"name\":\"gix-error\",\"req\":\"^0.2.0\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"features\":[\"simd\"],\"name\":\"winnow\",\"req\":\"^0.7.14\"}],\"features\":{\"serde\":[\"dep:serde\",\"bstr/serde\",\"gix-date/serde\"]}}", + "gix-archive_0.30.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"zlib-rs\"],\"name\":\"flate2\",\"optional\":true,\"req\":\"^1.1.9\"},{\"name\":\"gix-date\",\"req\":\"^0.15.1\"},{\"name\":\"gix-error\",\"req\":\"^0.2.1\"},{\"name\":\"gix-object\",\"req\":\"^0.58.0\"},{\"name\":\"gix-path\",\"optional\":true,\"req\":\"^0.11.2\"},{\"name\":\"gix-worktree-stream\",\"req\":\"^0.30.0\"},{\"name\":\"rawzip\",\"optional\":true,\"req\":\"^0.4.3\"},{\"name\":\"tar\",\"optional\":true,\"req\":\"^0.4.38\"}],\"features\":{\"default\":[\"tar\",\"tar_gz\",\"zip\"],\"sha1\":[\"gix-worktree-stream/sha1\",\"gix-object/sha1\"],\"tar\":[\"dep:tar\",\"dep:gix-path\"],\"tar_gz\":[\"tar\",\"dep:flate2\"],\"zip\":[\"dep:rawzip\",\"dep:flate2\"]}}", + "gix-attributes_0.31.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\",\"unicode\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.1\"},{\"name\":\"gix-glob\",\"req\":\"^0.24.0\"},{\"name\":\"gix-path\",\"req\":\"^0.11.1\"},{\"name\":\"gix-quote\",\"req\":\"^0.7.0\"},{\"name\":\"gix-trace\",\"req\":\"^0.1.18\"},{\"name\":\"kstring\",\"req\":\"^2.0.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"name\":\"smallvec\",\"req\":\"^1.15.1\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"},{\"name\":\"unicode-bom\",\"req\":\"^2.0.3\"}],\"features\":{\"serde\":[\"dep:serde\",\"bstr/serde\",\"gix-glob/serde\",\"kstring/serde\"]}}", + "gix-bitmap_0.3.0": "{\"dependencies\":[{\"name\":\"gix-error\",\"req\":\"^0.2.0\"}],\"features\":{}}", + "gix-blame_0.11.0": "{\"dependencies\":[{\"name\":\"gix-commitgraph\",\"req\":\"^0.35.0\"},{\"name\":\"gix-date\",\"req\":\"^0.15.1\"},{\"default_features\":false,\"features\":[\"blob\",\"blob-experimental\"],\"name\":\"gix-diff\",\"req\":\"^0.61.0\"},{\"name\":\"gix-error\",\"req\":\"^0.2.1\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"name\":\"gix-object\",\"req\":\"^0.58.0\"},{\"name\":\"gix-revwalk\",\"req\":\"^0.29.0\"},{\"name\":\"gix-trace\",\"req\":\"^0.1.18\"},{\"name\":\"gix-traverse\",\"req\":\"^0.55.0\"},{\"default_features\":false,\"features\":[\"attributes\"],\"name\":\"gix-worktree\",\"req\":\"^0.50.0\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.4.0\"},{\"name\":\"smallvec\",\"req\":\"^1.15.1\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"}],\"features\":{\"sha1\":[\"gix-commitgraph/sha1\",\"gix-diff/sha1\",\"gix-hash/sha1\",\"gix-object/sha1\",\"gix-revwalk/sha1\",\"gix-traverse/sha1\",\"gix-worktree/sha1\"]}}", + "gix-chunk_0.7.0": "{\"dependencies\":[{\"name\":\"gix-error\",\"req\":\"^0.2.0\"}],\"features\":{}}", + "gix-command_0.8.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\",\"unicode\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"gix-path\",\"req\":\"^0.11.1\"},{\"name\":\"gix-quote\",\"req\":\"^0.7.0\"},{\"name\":\"gix-trace\",\"req\":\"^0.1.18\"},{\"name\":\"shell-words\",\"req\":\"^1.1\"}],\"features\":{}}", + "gix-commitgraph_0.35.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"gix-chunk\",\"req\":\"^0.7.0\"},{\"name\":\"gix-error\",\"req\":\"^0.2.1\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"name\":\"memmap2\",\"req\":\"^0.9.10\"},{\"name\":\"nonempty\",\"req\":\"^0.12.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"}],\"features\":{\"serde\":[\"dep:serde\",\"gix-hash/serde\",\"bstr/serde\"],\"sha1\":[\"gix-hash/sha1\"]}}", + "gix-config-value_0.17.1": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"gix-path\",\"req\":\"^0.11.1\"},{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(not(windows))\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"}],\"features\":{\"serde\":[\"dep:serde\",\"bstr/serde\"]}}", + "gix-config_0.54.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8.2\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"gix-config-value\",\"req\":\"^0.17.1\"},{\"name\":\"gix-features\",\"req\":\"^0.46.2\"},{\"name\":\"gix-glob\",\"req\":\"^0.24.0\"},{\"name\":\"gix-path\",\"req\":\"^0.11.2\"},{\"name\":\"gix-ref\",\"req\":\"^0.61.0\"},{\"name\":\"gix-sec\",\"req\":\"^0.13.2\"},{\"name\":\"memchr\",\"req\":\"^2\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"name\":\"smallvec\",\"req\":\"^1.15.1\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"},{\"name\":\"unicode-bom\",\"req\":\"^2.0.3\"},{\"features\":[\"simd\"],\"name\":\"winnow\",\"req\":\"^0.7.14\"}],\"features\":{\"serde\":[\"dep:serde\",\"bstr/serde\",\"gix-sec/serde\",\"gix-ref/serde\",\"gix-glob/serde\",\"gix-config-value/serde\"],\"sha1\":[\"gix-ref/sha1\"]}}", + "gix-date_0.15.1": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"gix-error\",\"req\":\"^0.2.1\"},{\"name\":\"itoa\",\"req\":\"^1.0.17\"},{\"name\":\"jiff\",\"req\":\"^0.2.21\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.4.1\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"features\":[\"write\"],\"name\":\"smallvec\",\"req\":\"^1.15.1\"}],\"features\":{\"serde\":[\"dep:serde\",\"bstr/serde\"]}}", + "gix-diff_0.61.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8.2\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"js\"],\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.2.17\"},{\"name\":\"gix-attributes\",\"optional\":true,\"req\":\"^0.31.0\"},{\"name\":\"gix-command\",\"optional\":true,\"req\":\"^0.8.0\"},{\"name\":\"gix-filter\",\"optional\":true,\"req\":\"^0.28.0\"},{\"name\":\"gix-fs\",\"optional\":true,\"req\":\"^0.19.2\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"name\":\"gix-index\",\"optional\":true,\"req\":\"^0.49.0\"},{\"name\":\"gix-object\",\"req\":\"^0.58.0\"},{\"name\":\"gix-path\",\"optional\":true,\"req\":\"^0.11.2\"},{\"name\":\"gix-pathspec\",\"optional\":true,\"req\":\"^0.16.1\"},{\"name\":\"gix-tempfile\",\"optional\":true,\"req\":\"^21.0.0\"},{\"name\":\"gix-trace\",\"optional\":true,\"req\":\"^0.1.18\"},{\"name\":\"gix-traverse\",\"optional\":true,\"req\":\"^0.55.0\"},{\"default_features\":false,\"features\":[\"attributes\"],\"name\":\"gix-worktree\",\"optional\":true,\"req\":\"^0.50.0\"},{\"name\":\"imara-diff\",\"optional\":true,\"req\":\"^0.1.8\"},{\"name\":\"imara-diff-v2\",\"optional\":true,\"package\":\"imara-diff\",\"req\":\"^0.2.0\"},{\"kind\":\"dev\",\"name\":\"imara-diff-v2\",\"package\":\"imara-diff\",\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"}],\"features\":{\"blob\":[\"dep:imara-diff\",\"dep:gix-filter\",\"dep:gix-worktree\",\"dep:gix-path\",\"dep:gix-fs\",\"dep:gix-command\",\"dep:gix-tempfile\",\"dep:gix-trace\",\"dep:gix-traverse\"],\"blob-experimental\":[\"dep:imara-diff-v2\"],\"default\":[\"blob\",\"index\"],\"index\":[\"dep:gix-index\",\"dep:gix-pathspec\",\"dep:gix-attributes\"],\"serde\":[\"dep:serde\",\"gix-hash/serde\",\"gix-object/serde\",\"gix-index?/serde\"],\"sha1\":[\"gix-hash/sha1\"],\"wasm\":[\"dep:getrandom\"]}}", + "gix-dir_0.23.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"gix-discover\",\"req\":\"^0.49.0\"},{\"name\":\"gix-fs\",\"req\":\"^0.19.2\"},{\"name\":\"gix-ignore\",\"req\":\"^0.19.1\"},{\"name\":\"gix-index\",\"req\":\"^0.49.0\"},{\"name\":\"gix-object\",\"req\":\"^0.58.0\"},{\"name\":\"gix-path\",\"req\":\"^0.11.2\"},{\"name\":\"gix-pathspec\",\"req\":\"^0.16.1\"},{\"name\":\"gix-trace\",\"req\":\"^0.1.18\"},{\"features\":[\"bstr\"],\"name\":\"gix-utils\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"name\":\"gix-worktree\",\"req\":\"^0.50.0\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.4.0\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"}],\"features\":{\"sha1\":[\"gix-discover/sha1\",\"gix-index/sha1\",\"gix-object/sha1\",\"gix-worktree/sha1\"]}}", + "gix-discover_0.49.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\",\"unicode\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"kind\":\"dev\",\"name\":\"defer\",\"req\":\"^0.2.1\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"name\":\"dunce\",\"req\":\"^1.0.3\",\"target\":\"cfg(windows)\"},{\"name\":\"gix-fs\",\"req\":\"^0.19.2\"},{\"name\":\"gix-path\",\"req\":\"^0.11.2\"},{\"name\":\"gix-ref\",\"req\":\"^0.61.0\"},{\"name\":\"gix-sec\",\"req\":\"^0.13.2\"},{\"kind\":\"dev\",\"name\":\"is_ci\",\"req\":\"^1.1.1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^3.4.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.26.0\",\"target\":\"cfg(any(unix, windows))\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"}],\"features\":{\"sha1\":[\"gix-ref/sha1\"]}}", + "gix-error_0.2.1": "{\"dependencies\":[{\"name\":\"anyhow\",\"optional\":true,\"req\":\"^1.0.102\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.46.3\"}],\"features\":{\"anyhow\":[\"dep:anyhow\"],\"auto-chain-error\":[],\"tree-error\":[]}}", + "gix-features_0.46.2": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"bytes\",\"optional\":true,\"req\":\"^1.11.1\"},{\"name\":\"bytesize\",\"optional\":true,\"req\":\"^2.3.1\"},{\"name\":\"crc32fast\",\"optional\":true,\"req\":\"^1.5.0\"},{\"name\":\"crossbeam-channel\",\"optional\":true,\"req\":\"^0.5.15\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"gix-path\",\"optional\":true,\"req\":\"^0.11.2\"},{\"name\":\"gix-trace\",\"req\":\"^0.1.18\"},{\"name\":\"gix-utils\",\"optional\":true,\"req\":\"^0.3.1\"},{\"name\":\"libc\",\"req\":\"^0.2.182\",\"target\":\"cfg(unix)\"},{\"name\":\"once_cell\",\"optional\":true,\"req\":\"^1.21.3\"},{\"default_features\":false,\"name\":\"parking_lot\",\"optional\":true,\"req\":\"^0.12.4\"},{\"name\":\"prodash\",\"optional\":true,\"req\":\"^31.0.0\"},{\"name\":\"thiserror\",\"optional\":true,\"req\":\"^2.0.18\"},{\"name\":\"walkdir\",\"optional\":true,\"req\":\"^2.3.2\"},{\"name\":\"zlib-rs\",\"optional\":true,\"req\":\"^0.6.2\"}],\"features\":{\"cache-efficiency-debug\":[],\"crc32\":[\"dep:crc32fast\"],\"default\":[],\"fs-read-dir\":[\"dep:gix-utils\"],\"io-pipe\":[\"dep:bytes\"],\"once_cell\":[\"dep:once_cell\"],\"parallel\":[\"dep:crossbeam-channel\",\"dep:parking_lot\"],\"progress\":[\"prodash\"],\"progress-unit-bytes\":[\"dep:bytesize\",\"prodash?/unit-bytes\"],\"progress-unit-human-numbers\":[\"prodash?/unit-human\"],\"tracing\":[\"gix-trace/tracing\"],\"tracing-detail\":[\"gix-trace/tracing-detail\"],\"walkdir\":[\"dep:walkdir\",\"dep:gix-path\",\"dep:gix-utils\"],\"zlib\":[\"dep:zlib-rs\",\"dep:thiserror\"]}}", + "gix-filter_0.28.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"encoding_rs\",\"req\":\"^0.8.32\"},{\"name\":\"gix-attributes\",\"req\":\"^0.31.0\"},{\"name\":\"gix-command\",\"req\":\"^0.8.0\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"name\":\"gix-object\",\"req\":\"^0.58.0\"},{\"features\":[\"blocking-io\"],\"name\":\"gix-packetline\",\"req\":\"^0.21.2\"},{\"name\":\"gix-path\",\"req\":\"^0.11.2\"},{\"name\":\"gix-quote\",\"req\":\"^0.7.0\"},{\"name\":\"gix-trace\",\"req\":\"^0.1.18\"},{\"name\":\"gix-utils\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^3.4.0\"},{\"name\":\"smallvec\",\"req\":\"^1.15.1\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"}],\"features\":{\"sha1\":[\"gix-hash/sha1\",\"gix-object/sha1\"]}}", + "gix-fs_0.19.2": "{\"dependencies\":[{\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"kind\":\"dev\",\"name\":\"crossbeam-channel\",\"req\":\"^0.5.15\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"fastrand\",\"req\":\"^2.1.0\"},{\"features\":[\"fs-read-dir\"],\"name\":\"gix-features\",\"req\":\"^0.46.2\"},{\"name\":\"gix-path\",\"req\":\"^0.11.2\"},{\"name\":\"gix-utils\",\"req\":\"^0.3.1\"},{\"kind\":\"dev\",\"name\":\"is_ci\",\"req\":\"^1.1.1\"},{\"default_features\":false,\"features\":[\"std\",\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.26.0\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"}],\"features\":{\"serde\":[\"dep:serde\"]}}", + "gix-glob_0.24.0": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"gix-features\",\"req\":\"^0.46.0\"},{\"name\":\"gix-path\",\"req\":\"^0.11.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"}],\"features\":{\"serde\":[\"dep:serde\",\"bstr/serde\",\"bitflags/serde\"]}}", + "gix-hash_0.23.0": "{\"dependencies\":[{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"faster-hex\",\"req\":\"^0.10.0\"},{\"features\":[\"progress\"],\"name\":\"gix-features\",\"req\":\"^0.46.2\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"default_features\":false,\"name\":\"sha1-checked\",\"optional\":true,\"req\":\"^0.10.0\"},{\"default_features\":false,\"name\":\"sha2\",\"optional\":true,\"req\":\"^0.10.0\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"}],\"features\":{\"default\":[],\"serde\":[\"dep:serde\",\"faster-hex/serde\"],\"sha1\":[\"dep:sha1-checked\"],\"sha256\":[\"dep:sha2\"]}}", + "gix-hashtable_0.13.0": "{\"dependencies\":[{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"default_features\":false,\"features\":[\"inline-more\"],\"name\":\"hashbrown\",\"req\":\"^0.16.0\"},{\"name\":\"parking_lot\",\"req\":\"^0.12.4\"}],\"features\":{\"sha1\":[\"gix-hash/sha1\"]}}", + "gix-ignore_0.19.1": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\",\"unicode\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.1\"},{\"name\":\"gix-glob\",\"req\":\"^0.24.0\"},{\"name\":\"gix-path\",\"req\":\"^0.11.2\"},{\"name\":\"gix-trace\",\"req\":\"^0.1.16\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"name\":\"unicode-bom\",\"req\":\"^2.0.3\"}],\"features\":{\"serde\":[\"dep:serde\",\"bstr/serde\",\"gix-glob/serde\"]}}", + "gix-index_0.49.0": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"default_features\":false,\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"filetime\",\"req\":\"^0.2.27\"},{\"name\":\"fnv\",\"req\":\"^1.0.7\"},{\"name\":\"gix-bitmap\",\"req\":\"^0.3.0\"},{\"features\":[\"progress\"],\"name\":\"gix-features\",\"req\":\"^0.46.2\"},{\"name\":\"gix-fs\",\"req\":\"^0.19.2\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"name\":\"gix-lock\",\"req\":\"^21.0.0\"},{\"name\":\"gix-object\",\"req\":\"^0.58.0\"},{\"name\":\"gix-traverse\",\"req\":\"^0.55.0\"},{\"name\":\"gix-utils\",\"req\":\"^0.3.1\"},{\"name\":\"gix-validate\",\"req\":\"^0.11.0\"},{\"name\":\"hashbrown\",\"req\":\"^0.16.0\"},{\"name\":\"itoa\",\"req\":\"^1.0.17\"},{\"name\":\"libc\",\"req\":\"^0.2.182\",\"target\":\"cfg(not(windows))\"},{\"name\":\"memmap2\",\"req\":\"^0.9.10\"},{\"default_features\":false,\"features\":[\"std\",\"fs\"],\"name\":\"rustix\",\"req\":\"^1.1.2\",\"target\":\"cfg(not(windows))\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"name\":\"smallvec\",\"req\":\"^1.15.1\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"}],\"features\":{\"serde\":[\"dep:serde\",\"smallvec/serde\",\"gix-hash/serde\"],\"sha1\":[\"gix-hash/sha1\"]}}", + "gix-lock_21.0.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"gix-tempfile\",\"req\":\"^21.0.0\"},{\"default_features\":false,\"name\":\"gix-utils\",\"req\":\"^0.3.1\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.26.0\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"}],\"features\":{}}", + "gix-merge_0.14.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"gix-command\",\"req\":\"^0.8.0\"},{\"default_features\":false,\"features\":[\"blob\"],\"name\":\"gix-diff\",\"req\":\"^0.61.0\"},{\"name\":\"gix-filter\",\"req\":\"^0.28.0\"},{\"name\":\"gix-fs\",\"req\":\"^0.19.2\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"name\":\"gix-index\",\"req\":\"^0.49.0\"},{\"name\":\"gix-object\",\"req\":\"^0.58.0\"},{\"name\":\"gix-path\",\"req\":\"^0.11.2\"},{\"name\":\"gix-quote\",\"req\":\"^0.7.0\"},{\"default_features\":false,\"features\":[\"merge_base\"],\"name\":\"gix-revision\",\"req\":\"^0.43.0\"},{\"name\":\"gix-revwalk\",\"req\":\"^0.29.0\"},{\"name\":\"gix-tempfile\",\"req\":\"^21.0.0\"},{\"name\":\"gix-trace\",\"req\":\"^0.1.18\"},{\"default_features\":false,\"features\":[\"attributes\"],\"name\":\"gix-worktree\",\"req\":\"^0.50.0\"},{\"name\":\"imara-diff\",\"req\":\"^0.1.8\"},{\"name\":\"nonempty\",\"req\":\"^0.12.0\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.4.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"kind\":\"dev\",\"name\":\"termtree\",\"req\":\"^1.0.0\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"}],\"features\":{\"serde\":[\"dep:serde\",\"gix-hash/serde\",\"gix-object/serde\"],\"sha1\":[\"gix-hash/sha1\"]}}", + "gix-negotiate_0.29.0": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"name\":\"gix-commitgraph\",\"req\":\"^0.35.0\"},{\"name\":\"gix-date\",\"req\":\"^0.15.1\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"name\":\"gix-object\",\"req\":\"^0.58.0\"},{\"name\":\"gix-revwalk\",\"req\":\"^0.29.0\"}],\"features\":{\"sha1\":[\"gix-commitgraph/sha1\",\"gix-hash/sha1\",\"gix-object/sha1\",\"gix-revwalk/sha1\"]}}", + "gix-object_0.58.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\",\"unicode\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8.2\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"gix-actor\",\"req\":\"^0.40.0\"},{\"name\":\"gix-date\",\"req\":\"^0.15.1\"},{\"features\":[\"progress\"],\"name\":\"gix-features\",\"req\":\"^0.46.2\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"name\":\"gix-hashtable\",\"req\":\"^0.13.0\"},{\"name\":\"gix-path\",\"req\":\"^0.11.2\"},{\"name\":\"gix-utils\",\"req\":\"^0.3.1\"},{\"name\":\"gix-validate\",\"req\":\"^0.11.0\"},{\"name\":\"itoa\",\"req\":\"^1.0.17\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"features\":[\"write\"],\"name\":\"smallvec\",\"req\":\"^1.15.1\"},{\"kind\":\"dev\",\"name\":\"termtree\",\"req\":\"^1.0.0\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"},{\"features\":[\"simd\"],\"name\":\"winnow\",\"req\":\"^0.7.14\"}],\"features\":{\"serde\":[\"dep:serde\",\"bstr/serde\",\"smallvec/serde\",\"gix-hash/serde\",\"gix-actor/serde\"],\"sha1\":[\"gix-hash/sha1\"],\"verbose-object-parsing-errors\":[\"winnow/std\"]}}", + "gix-odb_0.78.0": "{\"dependencies\":[{\"name\":\"arc-swap\",\"req\":\"^1.8.2\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"features\":[\"walkdir\",\"zlib\",\"crc32\"],\"name\":\"gix-features\",\"req\":\"^0.46.2\"},{\"name\":\"gix-fs\",\"req\":\"^0.19.2\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"name\":\"gix-hashtable\",\"req\":\"^0.13.0\"},{\"name\":\"gix-object\",\"req\":\"^0.58.0\"},{\"default_features\":false,\"name\":\"gix-pack\",\"req\":\"^0.68.0\"},{\"name\":\"gix-path\",\"req\":\"^0.11.2\"},{\"name\":\"gix-quote\",\"req\":\"^0.7.0\"},{\"name\":\"parking_lot\",\"req\":\"^0.12.4\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"name\":\"tempfile\",\"req\":\"^3.26.0\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"}],\"features\":{\"serde\":[\"dep:serde\",\"gix-hash/serde\",\"gix-object/serde\",\"gix-pack/serde\"],\"sha1\":[\"gix-hash/sha1\"]}}", + "gix-pack_0.68.0": "{\"dependencies\":[{\"name\":\"clru\",\"optional\":true,\"req\":\"^0.6.1\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"gix-chunk\",\"req\":\"^0.7.0\"},{\"default_features\":false,\"name\":\"gix-diff\",\"optional\":true,\"req\":\"^0.61.0\"},{\"name\":\"gix-error\",\"req\":\"^0.2.1\"},{\"features\":[\"crc32\",\"progress\",\"zlib\"],\"name\":\"gix-features\",\"req\":\"^0.46.2\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"features\":[\"sha256\"],\"kind\":\"dev\",\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"name\":\"gix-hashtable\",\"optional\":true,\"req\":\"^0.13.0\"},{\"name\":\"gix-object\",\"req\":\"^0.58.0\"},{\"name\":\"gix-path\",\"req\":\"^0.11.2\"},{\"default_features\":false,\"name\":\"gix-tempfile\",\"optional\":true,\"req\":\"^21.0.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"gix-traverse\",\"optional\":true,\"req\":\"^0.55.0\"},{\"name\":\"memmap2\",\"req\":\"^0.9.10\"},{\"default_features\":false,\"name\":\"parking_lot\",\"optional\":true,\"req\":\"^0.12.4\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"name\":\"smallvec\",\"req\":\"^1.15.1\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"},{\"name\":\"uluru\",\"optional\":true,\"req\":\"^3.0.0\"}],\"features\":{\"default\":[\"generate\",\"streaming-input\"],\"generate\":[\"dep:gix-traverse\",\"dep:gix-diff\",\"dep:parking_lot\",\"dep:gix-hashtable\"],\"object-cache-dynamic\":[\"dep:clru\",\"dep:gix-hashtable\"],\"pack-cache-lru-dynamic\":[\"dep:clru\"],\"pack-cache-lru-static\":[\"dep:uluru\"],\"serde\":[\"dep:serde\",\"gix-object/serde\"],\"sha1\":[\"gix-hash/sha1\"],\"streaming-input\":[\"dep:parking_lot\",\"dep:gix-tempfile\"],\"wasm\":[\"gix-diff?/wasm\"]}}", + "gix-packetline_0.21.2": "{\"dependencies\":[{\"features\":[\"attributes\"],\"kind\":\"dev\",\"name\":\"async-std\",\"req\":\"^1.9.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"faster-hex\",\"req\":\"^0.10.0\"},{\"name\":\"futures-io\",\"optional\":true,\"req\":\"^0.3.32\"},{\"name\":\"futures-lite\",\"optional\":true,\"req\":\"^2.1.0\"},{\"name\":\"gix-trace\",\"req\":\"^0.1.18\"},{\"kind\":\"dev\",\"name\":\"maybe-async\",\"req\":\"^0.2.6\"},{\"name\":\"pin-project-lite\",\"optional\":true,\"req\":\"^0.2.6\"},{\"default_features\":false,\"features\":[\"std\",\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"}],\"features\":{\"async-io\":[\"dep:futures-io\",\"futures-lite\",\"dep:pin-project-lite\"],\"blocking-io\":[],\"default\":[],\"serde\":[\"dep:serde\",\"bstr/serde\",\"faster-hex/serde\"]}}", + "gix-path_0.11.2": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"gix-trace\",\"req\":\"^0.1.18\"},{\"name\":\"gix-validate\",\"req\":\"^0.11.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^3.4.0\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"},{\"features\":[\"Win32_System_Com\",\"Win32_System_Threading\",\"Win32_UI_Shell\"],\"kind\":\"dev\",\"name\":\"windows\",\"req\":\"^0.62.1\",\"target\":\"cfg(windows)\"},{\"kind\":\"dev\",\"name\":\"winreg\",\"req\":\"^0.55.0\",\"target\":\"cfg(windows)\"}],\"features\":{}}", + "gix-pathspec_0.16.1": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"gix-attributes\",\"req\":\"^0.31.0\"},{\"name\":\"gix-config-value\",\"req\":\"^0.17.1\"},{\"name\":\"gix-glob\",\"req\":\"^0.24.0\"},{\"name\":\"gix-path\",\"req\":\"^0.11.2\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^3.4.0\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"}],\"features\":{}}", + "gix-protocol_0.59.0": "{\"dependencies\":[{\"features\":[\"attributes\"],\"kind\":\"dev\",\"name\":\"async-std\",\"req\":\"^1.9.0\"},{\"name\":\"async-trait\",\"optional\":true,\"req\":\"^0.1.51\"},{\"default_features\":false,\"features\":[\"std\",\"unicode\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"futures-io\",\"optional\":true,\"req\":\"^0.3.32\"},{\"name\":\"futures-lite\",\"optional\":true,\"req\":\"^2.1.0\"},{\"name\":\"gix-credentials\",\"optional\":true,\"req\":\"^0.37.1\"},{\"name\":\"gix-date\",\"req\":\"^0.15.1\"},{\"features\":[\"progress\"],\"name\":\"gix-features\",\"req\":\"^0.46.2\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"name\":\"gix-lock\",\"optional\":true,\"req\":\"^21.0.0\"},{\"name\":\"gix-negotiate\",\"optional\":true,\"req\":\"^0.29.0\"},{\"name\":\"gix-object\",\"optional\":true,\"req\":\"^0.58.0\"},{\"kind\":\"dev\",\"name\":\"gix-packetline\",\"req\":\"^0.21.2\"},{\"name\":\"gix-ref\",\"req\":\"^0.61.0\"},{\"name\":\"gix-refspec\",\"optional\":true,\"req\":\"^0.39.0\"},{\"name\":\"gix-revwalk\",\"optional\":true,\"req\":\"^0.29.0\"},{\"name\":\"gix-shallow\",\"req\":\"^0.10.0\"},{\"name\":\"gix-trace\",\"optional\":true,\"req\":\"^0.1.18\"},{\"name\":\"gix-transport\",\"req\":\"^0.55.1\"},{\"name\":\"gix-utils\",\"req\":\"^0.3.1\"},{\"name\":\"maybe-async\",\"req\":\"^0.2.6\"},{\"name\":\"nonempty\",\"req\":\"^0.12.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"},{\"features\":[\"simd\"],\"name\":\"winnow\",\"req\":\"^0.7.14\"}],\"features\":{\"async-client\":[\"gix-transport/async-client\",\"dep:async-trait\",\"dep:futures-io\",\"futures-lite\",\"handshake\",\"fetch\"],\"blocking-client\":[\"gix-transport/blocking-client\",\"maybe-async/is_sync\",\"handshake\",\"fetch\"],\"fetch\":[\"dep:gix-negotiate\",\"dep:gix-object\",\"dep:gix-revwalk\",\"dep:gix-lock\",\"dep:gix-refspec\",\"dep:gix-trace\"],\"handshake\":[\"dep:gix-credentials\"],\"serde\":[\"dep:serde\",\"bstr/serde\",\"gix-transport/serde\",\"gix-hash/serde\",\"gix-shallow/serde\"],\"sha1\":[\"gix-hash/sha1\"]}}", + "gix-quote_0.7.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"gix-error\",\"req\":\"^0.2.0\"},{\"name\":\"gix-utils\",\"req\":\"^0.3.1\"}],\"features\":{}}", + "gix-ref_0.61.0": "{\"dependencies\":[{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.1\"},{\"name\":\"gix-actor\",\"req\":\"^0.40.0\"},{\"features\":[\"walkdir\"],\"name\":\"gix-features\",\"req\":\"^0.46.2\"},{\"name\":\"gix-fs\",\"req\":\"^0.19.2\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"name\":\"gix-lock\",\"req\":\"^21.0.0\"},{\"name\":\"gix-object\",\"req\":\"^0.58.0\"},{\"name\":\"gix-path\",\"req\":\"^0.11.2\"},{\"default_features\":false,\"name\":\"gix-tempfile\",\"req\":\"^21.0.0\"},{\"name\":\"gix-utils\",\"req\":\"^0.3.1\"},{\"name\":\"gix-validate\",\"req\":\"^0.11.0\"},{\"name\":\"memmap2\",\"req\":\"^0.9.10\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"},{\"features\":[\"simd\"],\"name\":\"winnow\",\"req\":\"^0.7.14\"}],\"features\":{\"serde\":[\"dep:serde\",\"gix-hash/serde\",\"gix-actor/serde\",\"gix-object/serde\"],\"sha1\":[\"gix-hash/sha1\"]}}", + "gix-refspec_0.39.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"gix-error\",\"req\":\"^0.2.1\"},{\"name\":\"gix-glob\",\"req\":\"^0.24.0\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"default_features\":false,\"name\":\"gix-revision\",\"req\":\"^0.43.0\"},{\"name\":\"gix-validate\",\"req\":\"^0.11.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.46.3\"},{\"name\":\"smallvec\",\"req\":\"^1.15.1\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"}],\"features\":{\"sha1\":[\"gix-hash/sha1\"]}}", + "gix-revision_0.43.0": "{\"dependencies\":[{\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.1\"},{\"name\":\"gix-commitgraph\",\"req\":\"^0.35.0\"},{\"name\":\"gix-date\",\"req\":\"^0.15.1\"},{\"name\":\"gix-error\",\"req\":\"^0.2.1\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"name\":\"gix-hashtable\",\"optional\":true,\"req\":\"^0.13.0\"},{\"name\":\"gix-object\",\"req\":\"^0.58.0\"},{\"name\":\"gix-revwalk\",\"req\":\"^0.29.0\"},{\"name\":\"gix-trace\",\"optional\":true,\"req\":\"^0.1.18\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.46.3\"},{\"name\":\"nonempty\",\"req\":\"^0.12.0\"},{\"kind\":\"dev\",\"name\":\"permutohedron\",\"req\":\"^0.2.4\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"}],\"features\":{\"default\":[\"describe\",\"merge_base\"],\"describe\":[\"dep:gix-trace\",\"dep:gix-hashtable\"],\"merge_base\":[\"dep:gix-trace\",\"dep:bitflags\"],\"serde\":[\"dep:serde\",\"gix-hash/serde\",\"gix-object/serde\"],\"sha1\":[\"gix-hash/sha1\"]}}", + "gix-revwalk_0.29.0": "{\"dependencies\":[{\"name\":\"gix-commitgraph\",\"req\":\"^0.35.0\"},{\"name\":\"gix-date\",\"req\":\"^0.15.1\"},{\"name\":\"gix-error\",\"req\":\"^0.2.1\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"name\":\"gix-hashtable\",\"req\":\"^0.13.0\"},{\"name\":\"gix-object\",\"req\":\"^0.58.0\"},{\"name\":\"smallvec\",\"req\":\"^1.15.1\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"}],\"features\":{\"sha1\":[\"gix-commitgraph/sha1\",\"gix-hash/sha1\",\"gix-hashtable/sha1\",\"gix-object/sha1\"]}}", + "gix-sec_0.13.2": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.1\"},{\"name\":\"gix-path\",\"req\":\"^0.11.2\",\"target\":\"cfg(windows)\"},{\"name\":\"libc\",\"req\":\"^0.2.182\",\"target\":\"cfg(not(windows))\"},{\"default_features\":false,\"features\":[\"std\",\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.26.0\"},{\"features\":[\"Win32_Foundation\",\"Win32_Security_Authorization\",\"Win32_Storage_FileSystem\",\"Win32_System_Memory\",\"Win32_System_Threading\"],\"name\":\"windows-sys\",\"req\":\"^0.61.1\",\"target\":\"cfg(windows)\"}],\"features\":{\"serde\":[\"dep:serde\",\"bitflags/serde\"]}}", + "gix-shallow_0.10.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"name\":\"gix-lock\",\"req\":\"^21.0.0\"},{\"name\":\"nonempty\",\"req\":\"^0.12.0\"},{\"default_features\":false,\"features\":[\"std\",\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"}],\"features\":{\"serde\":[\"dep:serde\",\"gix-hash/serde\",\"nonempty/serialize\"],\"sha1\":[\"gix-hash/sha1\"]}}", + "gix-status_0.28.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"filetime\",\"req\":\"^0.2.27\"},{\"default_features\":false,\"features\":[\"blob\"],\"name\":\"gix-diff\",\"optional\":true,\"req\":\"^0.61.0\"},{\"name\":\"gix-dir\",\"optional\":true,\"req\":\"^0.23.0\"},{\"features\":[\"progress\"],\"name\":\"gix-features\",\"req\":\"^0.46.2\"},{\"name\":\"gix-filter\",\"req\":\"^0.28.0\"},{\"name\":\"gix-fs\",\"req\":\"^0.19.2\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"name\":\"gix-index\",\"req\":\"^0.49.0\"},{\"name\":\"gix-object\",\"req\":\"^0.58.0\"},{\"name\":\"gix-path\",\"req\":\"^0.11.2\"},{\"name\":\"gix-pathspec\",\"req\":\"^0.16.1\"},{\"default_features\":false,\"features\":[\"attributes\"],\"name\":\"gix-worktree\",\"req\":\"^0.50.0\"},{\"name\":\"portable-atomic\",\"req\":\"^1\",\"target\":\"cfg(not(target_has_atomic = \\\"64\\\"))\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"}],\"features\":{\"sha1\":[\"gix-hash/sha1\"],\"worktree-rewrites\":[\"dep:gix-dir\",\"dep:gix-diff\"]}}", + "gix-submodule_0.28.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"gix-config\",\"req\":\"^0.54.0\"},{\"name\":\"gix-path\",\"req\":\"^0.11.2\"},{\"name\":\"gix-pathspec\",\"req\":\"^0.16.1\"},{\"name\":\"gix-refspec\",\"req\":\"^0.39.0\"},{\"name\":\"gix-url\",\"req\":\"^0.35.2\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"}],\"features\":{\"sha1\":[\"gix-config/sha1\",\"gix-refspec/sha1\"]}}", + "gix-tempfile_21.0.2": "{\"dependencies\":[{\"name\":\"dashmap\",\"optional\":true,\"req\":\"^6.0.1\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"gix-fs\",\"req\":\"^0.19.2\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.182\",\"target\":\"cfg(not(windows))\"},{\"name\":\"parking_lot\",\"req\":\"^0.12.4\"},{\"default_features\":false,\"name\":\"signal-hook\",\"optional\":true,\"req\":\"^0.4.3\"},{\"name\":\"signal-hook-registry\",\"optional\":true,\"req\":\"^1.4.8\"},{\"name\":\"tempfile\",\"req\":\"^3.26.0\"}],\"features\":{\"default\":[\"hp-hashmap\"],\"hp-hashmap\":[\"dep:dashmap\"],\"signals\":[\"dep:signal-hook\",\"dep:signal-hook-registry\"]}}", + "gix-trace_0.1.18": "{\"dependencies\":[{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1.44\"}],\"features\":{\"default\":[],\"tracing\":[\"dep:tracing\"],\"tracing-detail\":[]}}", + "gix-transport_0.55.1": "{\"dependencies\":[{\"name\":\"async-std\",\"optional\":true,\"req\":\"^1.12.0\"},{\"features\":[\"attributes\"],\"kind\":\"dev\",\"name\":\"async-std\",\"req\":\"^1.9.0\"},{\"name\":\"async-trait\",\"optional\":true,\"req\":\"^0.1.51\"},{\"name\":\"base64\",\"optional\":true,\"req\":\"^0.22.1\"},{\"kind\":\"dev\",\"name\":\"blocking\",\"req\":\"^1.6.2\"},{\"default_features\":false,\"features\":[\"std\",\"unicode\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"default_features\":false,\"name\":\"curl\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"futures-io\",\"optional\":true,\"req\":\"^0.3.32\"},{\"name\":\"futures-lite\",\"optional\":true,\"req\":\"^2.1.0\"},{\"name\":\"gix-command\",\"req\":\"^0.8.0\"},{\"name\":\"gix-credentials\",\"optional\":true,\"req\":\"^0.37.1\"},{\"name\":\"gix-features\",\"req\":\"^0.46.2\"},{\"name\":\"gix-packetline\",\"req\":\"^0.21.2\"},{\"name\":\"gix-quote\",\"req\":\"^0.7.0\"},{\"name\":\"gix-sec\",\"req\":\"^0.13.2\"},{\"name\":\"gix-url\",\"req\":\"^0.35.2\"},{\"kind\":\"dev\",\"name\":\"maybe-async\",\"req\":\"^0.2.6\"},{\"name\":\"pin-project-lite\",\"optional\":true,\"req\":\"^0.2.6\"},{\"default_features\":false,\"features\":[\"blocking\",\"charset\",\"http2\"],\"name\":\"reqwest\",\"optional\":true,\"req\":\"^0.13.2\"},{\"default_features\":false,\"features\":[\"std\",\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"}],\"features\":{\"async-client\":[\"gix-packetline/async-io\",\"async-trait\",\"futures-lite\",\"futures-io\",\"pin-project-lite\"],\"blocking-client\":[\"gix-packetline/blocking-io\"],\"default\":[],\"http-client\":[\"base64\",\"gix-features/io-pipe\",\"blocking-client\",\"gix-credentials\"],\"http-client-curl\":[\"curl\",\"http-client\"],\"http-client-curl-openssl\":[\"http-client-curl\",\"curl/ssl\"],\"http-client-curl-rust-tls\":[\"http-client-curl\",\"curl/rustls\"],\"http-client-insecure-credentials\":[],\"http-client-reqwest\":[\"reqwest\",\"http-client\"],\"http-client-reqwest-native-tls\":[\"http-client-reqwest\",\"reqwest/native-tls\"],\"http-client-reqwest-rust-tls\":[\"http-client-reqwest\",\"reqwest/rustls\"],\"http-client-reqwest-rust-tls-trust-dns\":[\"http-client-reqwest\",\"reqwest/rustls\",\"reqwest/hickory-dns\"],\"serde\":[\"dep:serde\",\"bstr/serde\"]}}", + "gix-traverse_0.55.0": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"name\":\"gix-commitgraph\",\"req\":\"^0.35.0\"},{\"name\":\"gix-date\",\"req\":\"^0.15.1\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"name\":\"gix-hashtable\",\"req\":\"^0.13.0\"},{\"name\":\"gix-object\",\"req\":\"^0.58.0\"},{\"name\":\"gix-revwalk\",\"req\":\"^0.29.0\"},{\"name\":\"smallvec\",\"req\":\"^1.15.1\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"}],\"features\":{\"sha1\":[\"gix-hash/sha1\"]}}", + "gix-url_0.35.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"assert_matches\",\"req\":\"^1.5.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"gix-path\",\"req\":\"^0.11.1\"},{\"name\":\"percent-encoding\",\"req\":\"^2.3.1\"},{\"default_features\":false,\"features\":[\"std\",\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"}],\"features\":{\"serde\":[\"dep:serde\",\"bstr/serde\"]}}", + "gix-utils_0.3.1": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bstr\",\"optional\":true,\"req\":\"^1.12.0\"},{\"name\":\"fastrand\",\"req\":\"^2.0.0\"},{\"default_features\":false,\"name\":\"unicode-normalization\",\"req\":\"^0.1.19\"}],\"features\":{\"bstr\":[\"dep:bstr\"]}}", + "gix-validate_0.11.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"}],\"features\":{}}", + "gix-worktree-state_0.28.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"gix-features\",\"req\":\"^0.46.2\"},{\"name\":\"gix-filter\",\"req\":\"^0.28.0\"},{\"name\":\"gix-fs\",\"req\":\"^0.19.2\"},{\"name\":\"gix-index\",\"req\":\"^0.49.0\"},{\"name\":\"gix-object\",\"req\":\"^0.58.0\"},{\"name\":\"gix-path\",\"req\":\"^0.11.2\"},{\"default_features\":false,\"features\":[\"attributes\"],\"name\":\"gix-worktree\",\"req\":\"^0.50.0\"},{\"name\":\"io-close\",\"req\":\"^0.3.7\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"}],\"features\":{\"sha1\":[\"gix-filter/sha1\",\"gix-index/sha1\",\"gix-object/sha1\",\"gix-worktree/sha1\"]}}", + "gix-worktree-stream_0.30.0": "{\"dependencies\":[{\"name\":\"gix-attributes\",\"req\":\"^0.31.0\"},{\"name\":\"gix-error\",\"req\":\"^0.2.1\"},{\"features\":[\"progress\",\"io-pipe\"],\"name\":\"gix-features\",\"req\":\"^0.46.2\"},{\"name\":\"gix-filter\",\"req\":\"^0.28.0\"},{\"name\":\"gix-fs\",\"req\":\"^0.19.2\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"name\":\"gix-object\",\"req\":\"^0.58.0\"},{\"name\":\"gix-path\",\"req\":\"^0.11.2\"},{\"name\":\"gix-traverse\",\"req\":\"^0.55.0\"},{\"name\":\"parking_lot\",\"req\":\"^0.12.4\"}],\"features\":{\"sha1\":[\"gix-filter/sha1\",\"gix-hash/sha1\",\"gix-object/sha1\",\"gix-traverse/sha1\"]}}", + "gix-worktree_0.50.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"gix-attributes\",\"optional\":true,\"req\":\"^0.31.0\"},{\"name\":\"gix-fs\",\"req\":\"^0.19.2\"},{\"name\":\"gix-glob\",\"req\":\"^0.24.0\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"name\":\"gix-ignore\",\"req\":\"^0.19.1\"},{\"name\":\"gix-index\",\"req\":\"^0.49.0\"},{\"name\":\"gix-object\",\"req\":\"^0.58.0\"},{\"name\":\"gix-path\",\"req\":\"^0.11.2\"},{\"name\":\"gix-validate\",\"optional\":true,\"req\":\"^0.11.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"}],\"features\":{\"attributes\":[\"dep:gix-attributes\",\"dep:gix-validate\"],\"default\":[\"attributes\"],\"serde\":[\"dep:serde\",\"bstr/serde\",\"gix-index/serde\",\"gix-hash/serde\",\"gix-object/serde\",\"gix-attributes?/serde\",\"gix-ignore/serde\"],\"sha1\":[\"gix-hash/sha1\"]}}", + "gix_0.81.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1\"},{\"name\":\"async-std\",\"optional\":true,\"req\":\"^1.12.0\"},{\"features\":[\"attributes\"],\"kind\":\"dev\",\"name\":\"async-std\",\"req\":\"^1.12.0\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"gix-actor\",\"req\":\"^0.40.0\"},{\"default_features\":false,\"name\":\"gix-archive\",\"optional\":true,\"req\":\"^0.30.0\"},{\"name\":\"gix-attributes\",\"optional\":true,\"req\":\"^0.31.0\"},{\"name\":\"gix-blame\",\"optional\":true,\"req\":\"^0.11.0\"},{\"name\":\"gix-command\",\"optional\":true,\"req\":\"^0.8.0\"},{\"name\":\"gix-commitgraph\",\"req\":\"^0.35.0\"},{\"name\":\"gix-config\",\"req\":\"^0.54.0\"},{\"name\":\"gix-credentials\",\"optional\":true,\"req\":\"^0.37.1\"},{\"name\":\"gix-date\",\"req\":\"^0.15.1\"},{\"default_features\":false,\"name\":\"gix-diff\",\"req\":\"^0.61.0\"},{\"name\":\"gix-dir\",\"optional\":true,\"req\":\"^0.23.0\"},{\"name\":\"gix-discover\",\"req\":\"^0.49.0\"},{\"name\":\"gix-error\",\"req\":\"^0.2.1\"},{\"features\":[\"progress\",\"once_cell\"],\"name\":\"gix-features\",\"req\":\"^0.46.2\"},{\"name\":\"gix-filter\",\"optional\":true,\"req\":\"^0.28.0\"},{\"name\":\"gix-fs\",\"req\":\"^0.19.2\"},{\"name\":\"gix-glob\",\"req\":\"^0.24.0\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"features\":[\"sha256\"],\"kind\":\"dev\",\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"name\":\"gix-hashtable\",\"req\":\"^0.13.0\"},{\"name\":\"gix-ignore\",\"optional\":true,\"req\":\"^0.19.1\"},{\"name\":\"gix-index\",\"optional\":true,\"req\":\"^0.49.0\"},{\"name\":\"gix-lock\",\"req\":\"^21.0.0\"},{\"name\":\"gix-mailmap\",\"optional\":true,\"req\":\"^0.32.0\"},{\"default_features\":false,\"name\":\"gix-merge\",\"optional\":true,\"req\":\"^0.14.0\"},{\"name\":\"gix-negotiate\",\"optional\":true,\"req\":\"^0.29.0\"},{\"name\":\"gix-object\",\"req\":\"^0.58.0\"},{\"name\":\"gix-odb\",\"req\":\"^0.78.0\"},{\"default_features\":false,\"features\":[\"object-cache-dynamic\"],\"name\":\"gix-pack\",\"req\":\"^0.68.0\"},{\"name\":\"gix-path\",\"req\":\"^0.11.2\"},{\"name\":\"gix-pathspec\",\"optional\":true,\"req\":\"^0.16.1\"},{\"name\":\"gix-prompt\",\"optional\":true,\"req\":\"^0.14.1\"},{\"name\":\"gix-protocol\",\"req\":\"^0.59.0\"},{\"name\":\"gix-ref\",\"req\":\"^0.61.0\"},{\"name\":\"gix-refspec\",\"req\":\"^0.39.0\"},{\"default_features\":false,\"name\":\"gix-revision\",\"req\":\"^0.43.0\"},{\"name\":\"gix-revwalk\",\"req\":\"^0.29.0\"},{\"name\":\"gix-sec\",\"req\":\"^0.13.2\"},{\"name\":\"gix-shallow\",\"req\":\"^0.10.0\"},{\"features\":[\"worktree-rewrites\"],\"name\":\"gix-status\",\"optional\":true,\"req\":\"^0.28.0\"},{\"name\":\"gix-submodule\",\"optional\":true,\"req\":\"^0.28.0\"},{\"default_features\":false,\"name\":\"gix-tempfile\",\"req\":\"^21.0.0\"},{\"name\":\"gix-trace\",\"req\":\"^0.1.18\"},{\"name\":\"gix-transport\",\"optional\":true,\"req\":\"^0.55.1\"},{\"name\":\"gix-traverse\",\"req\":\"^0.55.0\"},{\"name\":\"gix-url\",\"req\":\"^0.35.2\"},{\"name\":\"gix-utils\",\"req\":\"^0.3.1\"},{\"name\":\"gix-validate\",\"req\":\"^0.11.0\"},{\"default_features\":false,\"name\":\"gix-worktree\",\"optional\":true,\"req\":\"^0.50.0\"},{\"name\":\"gix-worktree-state\",\"optional\":true,\"req\":\"^0.28.0\"},{\"name\":\"gix-worktree-stream\",\"optional\":true,\"req\":\"^0.30.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.46.3\"},{\"kind\":\"dev\",\"name\":\"is_ci\",\"req\":\"^1.1.1\"},{\"name\":\"nonempty\",\"req\":\"^0.12.0\"},{\"name\":\"parking_lot\",\"optional\":true,\"req\":\"^0.12.4\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.4.0\"},{\"features\":[\"progress-tree\"],\"name\":\"prodash\",\"optional\":true,\"req\":\"^31.0.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"regex\",\"optional\":true,\"req\":\"^1.12.3\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^3.4.0\"},{\"default_features\":false,\"name\":\"signal-hook\",\"optional\":true,\"req\":\"^0.4.3\"},{\"name\":\"smallvec\",\"req\":\"^1.15.1\"},{\"kind\":\"dev\",\"name\":\"termtree\",\"req\":\"^1.0.0\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.3.2\"}],\"features\":{\"async-network-client\":[\"gix-protocol/async-client\",\"gix-pack/streaming-input\",\"dep:gix-transport\",\"attributes\",\"credentials\"],\"async-network-client-async-std\":[\"async-std\",\"async-network-client\",\"gix-transport/async-std\"],\"attributes\":[\"excludes\",\"dep:gix-filter\",\"dep:gix-pathspec\",\"dep:gix-attributes\",\"dep:gix-submodule\",\"gix-worktree?/attributes\",\"command\"],\"auto-chain-error\":[\"gix-error/auto-chain-error\"],\"basic\":[\"blob-diff\",\"revision\",\"index\"],\"blame\":[\"dep:gix-blame\",\"blob-diff\"],\"blob-diff\":[\"gix-diff/blob\",\"attributes\"],\"blocking-http-transport-curl\":[\"blocking-network-client\",\"gix-transport/http-client-curl\"],\"blocking-http-transport-curl-openssl\":[\"blocking-http-transport-curl\",\"gix-transport/http-client-curl-openssl\"],\"blocking-http-transport-curl-rustls\":[\"blocking-http-transport-curl\",\"gix-transport/http-client-curl-rust-tls\"],\"blocking-http-transport-reqwest\":[\"blocking-network-client\",\"gix-transport/http-client-reqwest\"],\"blocking-http-transport-reqwest-native-tls\":[\"blocking-http-transport-reqwest\",\"gix-transport/http-client-reqwest-native-tls\"],\"blocking-http-transport-reqwest-rust-tls\":[\"blocking-http-transport-reqwest\",\"gix-transport/http-client-reqwest-rust-tls\"],\"blocking-http-transport-reqwest-rust-tls-trust-dns\":[\"blocking-http-transport-reqwest\",\"gix-transport/http-client-reqwest-rust-tls-trust-dns\"],\"blocking-network-client\":[\"gix-protocol/blocking-client\",\"gix-pack/streaming-input\",\"dep:gix-transport\",\"attributes\",\"credentials\"],\"cache-efficiency-debug\":[\"gix-features/cache-efficiency-debug\"],\"comfort\":[\"gix-features/progress-unit-bytes\",\"gix-features/progress-unit-human-numbers\"],\"command\":[\"dep:gix-command\"],\"credentials\":[\"dep:gix-credentials\",\"dep:gix-prompt\",\"dep:gix-negotiate\"],\"default\":[\"max-performance-safe\",\"comfort\",\"basic\",\"extras\",\"auto-chain-error\",\"sha1\"],\"dirwalk\":[\"dep:gix-dir\",\"attributes\",\"excludes\"],\"excludes\":[\"dep:gix-ignore\",\"dep:gix-worktree\",\"index\"],\"extras\":[\"worktree-stream\",\"worktree-archive\",\"revparse-regex\",\"mailmap\",\"excludes\",\"attributes\",\"worktree-mutation\",\"credentials\",\"interrupt\",\"status\",\"dirwalk\",\"blame\"],\"hp-tempfile-registry\":[\"gix-tempfile/hp-hashmap\"],\"index\":[\"dep:gix-index\"],\"interrupt\":[\"dep:signal-hook\",\"gix-tempfile/signals\",\"dep:parking_lot\"],\"mailmap\":[\"dep:gix-mailmap\",\"revision\"],\"max-control\":[\"parallel\",\"pack-cache-lru-static\",\"pack-cache-lru-dynamic\"],\"max-performance\":[\"max-performance-safe\"],\"max-performance-safe\":[\"max-control\"],\"merge\":[\"tree-editor\",\"blob-diff\",\"dep:gix-merge\",\"attributes\"],\"need-more-recent-msrv\":[\"merge\",\"tree-editor\"],\"pack-cache-lru-dynamic\":[\"gix-pack/pack-cache-lru-dynamic\"],\"pack-cache-lru-static\":[\"gix-pack/pack-cache-lru-static\"],\"parallel\":[\"gix-features/parallel\"],\"progress-tree\":[\"prodash/progress-tree\"],\"revision\":[\"gix-revision/describe\",\"gix-revision/merge_base\",\"index\"],\"revparse-regex\":[\"regex\",\"revision\"],\"serde\":[\"dep:serde\",\"gix-pack/serde\",\"gix-object/serde\",\"gix-protocol/serde\",\"gix-transport?/serde\",\"gix-ref/serde\",\"gix-odb/serde\",\"gix-index?/serde\",\"gix-mailmap?/serde\",\"gix-url/serde\",\"gix-attributes?/serde\",\"gix-ignore?/serde\",\"gix-revision/serde\",\"gix-worktree?/serde\",\"gix-commitgraph/serde\",\"gix-credentials?/serde\"],\"sha1\":[\"gix-archive?/sha1\",\"gix-blame?/sha1\",\"gix-commitgraph/sha1\",\"gix-config/sha1\",\"gix-diff/sha1\",\"gix-dir?/sha1\",\"gix-discover/sha1\",\"gix-filter?/sha1\",\"gix-hash/sha1\",\"gix-hashtable/sha1\",\"gix-index?/sha1\",\"gix-merge?/sha1\",\"gix-negotiate?/sha1\",\"gix-object/sha1\",\"gix-odb/sha1\",\"gix-pack/sha1\",\"gix-protocol/sha1\",\"gix-ref/sha1\",\"gix-refspec/sha1\",\"gix-revision/sha1\",\"gix-revwalk/sha1\",\"gix-shallow/sha1\",\"gix-status?/sha1\",\"gix-submodule?/sha1\",\"gix-traverse/sha1\",\"gix-worktree?/sha1\",\"gix-worktree-state?/sha1\",\"gix-worktree-stream?/sha1\"],\"status\":[\"gix-status\",\"dirwalk\",\"index\",\"blob-diff\",\"gix-diff/index\"],\"tracing\":[\"gix-features/tracing\"],\"tracing-detail\":[\"gix-features/tracing-detail\",\"tracing\"],\"tree-editor\":[],\"tree-error\":[\"gix-error/tree-error\"],\"verbose-object-parsing-errors\":[\"gix-object/verbose-object-parsing-errors\"],\"worktree-archive\":[\"gix-archive\",\"worktree-stream\",\"attributes\"],\"worktree-mutation\":[\"attributes\",\"dep:gix-worktree-state\"],\"worktree-stream\":[\"gix-worktree-stream\",\"attributes\"],\"zlib-ng\":[\"gix-features/zlib\"],\"zlib-ng-compat\":[\"gix-features/zlib\"],\"zlib-rs\":[\"gix-features/zlib\"],\"zlib-stock\":[\"gix-features/zlib\"]}}", "glib-macros_0.21.5": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"glib\",\"req\":\"^0.21\"},{\"name\":\"heck\",\"req\":\"^0.5\"},{\"name\":\"proc-macro-crate\",\"req\":\"^3.3\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2.0.104\"},{\"kind\":\"dev\",\"name\":\"trybuild2\",\"req\":\"^1.2\"}],\"features\":{}}", "glib-sys_0.21.5": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"shell-words\",\"req\":\"^1.0.0\"},{\"kind\":\"build\",\"name\":\"system-deps\",\"req\":\"^7\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"v2_58\":[],\"v2_60\":[\"v2_58\"],\"v2_62\":[\"v2_60\"],\"v2_64\":[\"v2_62\"],\"v2_66\":[\"v2_64\"],\"v2_68\":[\"v2_66\"],\"v2_70\":[\"v2_68\"],\"v2_72\":[\"v2_70\"],\"v2_74\":[\"v2_72\"],\"v2_76\":[\"v2_74\"],\"v2_78\":[\"v2_76\"],\"v2_80\":[\"v2_78\"],\"v2_82\":[\"v2_80\"],\"v2_84\":[\"v2_82\"],\"v2_86\":[\"v2_84\"]}}", "glib_0.21.5": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.9\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7.0\"},{\"name\":\"futures-channel\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"name\":\"futures-executor\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-task\",\"req\":\"^0.3\"},{\"name\":\"futures-util\",\"req\":\"^0.3\"},{\"name\":\"gio-sys\",\"optional\":true,\"req\":\"^0.21\"},{\"kind\":\"dev\",\"name\":\"gir-format-check\",\"req\":\"^0.1\"},{\"name\":\"glib-macros\",\"req\":\"^0.21\"},{\"name\":\"glib-sys\",\"req\":\"^0.21\"},{\"name\":\"gobject-sys\",\"req\":\"^0.21\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"name\":\"memchr\",\"req\":\"^2.7.5\"},{\"name\":\"rs-log\",\"optional\":true,\"package\":\"log\",\"req\":\"^0.4\"},{\"features\":[\"union\",\"const_generics\",\"const_new\"],\"name\":\"smallvec\",\"req\":\"^1.15\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"},{\"kind\":\"dev\",\"name\":\"trybuild2\",\"req\":\"^1\"}],\"features\":{\"compiletests\":[],\"default\":[\"gio\"],\"gio\":[\"gio-sys\"],\"log\":[\"rs-log\"],\"log_macros\":[\"log\"],\"v2_58\":[\"glib-sys/v2_58\",\"gobject-sys/v2_58\"],\"v2_60\":[\"v2_58\",\"glib-sys/v2_60\"],\"v2_62\":[\"v2_60\",\"glib-sys/v2_62\",\"gobject-sys/v2_62\"],\"v2_64\":[\"v2_62\",\"glib-sys/v2_64\"],\"v2_66\":[\"v2_64\",\"glib-sys/v2_66\",\"gobject-sys/v2_66\"],\"v2_68\":[\"v2_66\",\"glib-sys/v2_68\",\"gobject-sys/v2_68\"],\"v2_70\":[\"v2_68\",\"glib-sys/v2_70\",\"gobject-sys/v2_70\"],\"v2_72\":[\"v2_70\",\"glib-sys/v2_72\",\"gobject-sys/v2_72\"],\"v2_74\":[\"v2_72\",\"glib-sys/v2_74\",\"gobject-sys/v2_74\"],\"v2_76\":[\"v2_74\",\"glib-sys/v2_76\",\"gobject-sys/v2_76\"],\"v2_78\":[\"v2_76\",\"glib-sys/v2_78\",\"gobject-sys/v2_78\"],\"v2_80\":[\"v2_78\",\"glib-sys/v2_80\",\"gobject-sys/v2_80\"],\"v2_82\":[\"v2_80\",\"glib-sys/v2_82\",\"gobject-sys/v2_82\"],\"v2_84\":[\"v2_82\",\"glib-sys/v2_84\",\"gobject-sys/v2_84\"],\"v2_86\":[\"v2_84\",\"glib-sys/v2_86\",\"gobject-sys/v2_86\"]}}", @@ -954,6 +1010,7 @@ "h2_0.4.13": "{\"dependencies\":[{\"name\":\"atomic-waker\",\"req\":\"^1.0.0\"},{\"name\":\"bytes\",\"req\":\"^1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\"},{\"name\":\"fnv\",\"req\":\"^1.0.5\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-sink\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"name\":\"http\",\"req\":\"^1\"},{\"features\":[\"std\"],\"name\":\"indexmap\",\"req\":\"^2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.4\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.0\"},{\"name\":\"slab\",\"req\":\"^0.4.2\"},{\"features\":[\"io-util\"],\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"rt-multi-thread\",\"macros\",\"sync\",\"net\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-rustls\",\"req\":\"^0.26\"},{\"features\":[\"codec\",\"io\"],\"name\":\"tokio-util\",\"req\":\"^0.7.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"req\":\"^0.1.35\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.3.2\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^1\"}],\"features\":{\"stream\":[],\"unstable\":[]}}", "h2_0.4.6": "{\"dependencies\":[{\"name\":\"atomic-waker\",\"req\":\"^1.0.0\"},{\"name\":\"bytes\",\"req\":\"^1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\"},{\"name\":\"fnv\",\"req\":\"^1.0.5\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-sink\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"name\":\"http\",\"req\":\"^1\"},{\"features\":[\"std\"],\"name\":\"indexmap\",\"req\":\"^2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.4\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.0\"},{\"name\":\"slab\",\"req\":\"^0.4.2\"},{\"features\":[\"io-util\"],\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"rt-multi-thread\",\"macros\",\"sync\",\"net\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-rustls\",\"req\":\"^0.26\"},{\"features\":[\"codec\",\"io\"],\"name\":\"tokio-util\",\"req\":\"^0.7.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"req\":\"^0.1.35\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.3.2\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^0.26\"}],\"features\":{\"stream\":[],\"unstable\":[]}}", "half_2.7.1": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.4.1\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"bytemuck\",\"optional\":true,\"req\":\"^1.4.1\"},{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"name\":\"crunchy\",\"req\":\"^0.2.2\",\"target\":\"cfg(target_arch = \\\"spirv\\\")\"},{\"kind\":\"dev\",\"name\":\"crunchy\",\"req\":\"^0.2.2\"},{\"default_features\":false,\"features\":[\"libm\"],\"name\":\"num-traits\",\"optional\":true,\"req\":\"^0.2.16\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"quickcheck_macros\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"thread_rng\"],\"name\":\"rand\",\"optional\":true,\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9.0\"},{\"default_features\":false,\"name\":\"rand_distr\",\"optional\":true,\"req\":\"^0.5.0\"},{\"name\":\"rkyv\",\"optional\":true,\"req\":\"^0.8.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"derive\",\"simd\"],\"name\":\"zerocopy\",\"req\":\"^0.8.26\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"nightly\":[],\"rand_distr\":[\"dep:rand\",\"dep:rand_distr\"],\"std\":[\"alloc\"],\"use-intrinsics\":[],\"zerocopy\":[]}}", + "hash32_0.3.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"byteorder\",\"req\":\"^1.2.2\"}],\"features\":{}}", "hashbrown_0.12.3": "{\"dependencies\":[{\"default_features\":false,\"name\":\"ahash\",\"optional\":true,\"req\":\"^0.7.0\"},{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"name\":\"bumpalo\",\"optional\":true,\"req\":\"^3.5.0\"},{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1.2\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.1\"},{\"kind\":\"dev\",\"name\":\"fnv\",\"req\":\"^1.0.7\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1.4\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.3\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.25\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"ahash-compile-time-rng\":[\"ahash/compile-time-rng\"],\"default\":[\"ahash\",\"inline-more\"],\"inline-more\":[],\"nightly\":[],\"raw\":[],\"rustc-dep-of-std\":[\"nightly\",\"core\",\"compiler_builtins\",\"alloc\",\"rustc-internal-api\"],\"rustc-internal-api\":[]}}", "hashbrown_0.14.5": "{\"dependencies\":[{\"default_features\":false,\"name\":\"ahash\",\"optional\":true,\"req\":\"^0.8.7\"},{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"allocator-api2\",\"optional\":true,\"req\":\"^0.2.9\"},{\"features\":[\"allocator-api2\"],\"kind\":\"dev\",\"name\":\"bumpalo\",\"req\":\"^3.13.0\"},{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1.2\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"name\":\"equivalent\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"fnv\",\"req\":\"^1.0.7\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1.4\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.3\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"rkyv\",\"optional\":true,\"req\":\"^0.7.42\"},{\"features\":[\"validation\"],\"kind\":\"dev\",\"name\":\"rkyv\",\"req\":\"^0.7.42\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.25\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"ahash\",\"inline-more\",\"allocator-api2\"],\"inline-more\":[],\"nightly\":[\"allocator-api2?/nightly\",\"bumpalo/allocator_api\"],\"raw\":[],\"rustc-dep-of-std\":[\"nightly\",\"core\",\"compiler_builtins\",\"alloc\",\"rustc-internal-api\"],\"rustc-internal-api\":[]}}", "hashbrown_0.15.5": "{\"dependencies\":[{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"allocator-api2\",\"optional\":true,\"req\":\"^0.2.9\"},{\"features\":[\"allocator-api2\"],\"kind\":\"dev\",\"name\":\"bumpalo\",\"req\":\"^3.13.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"name\":\"equivalent\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"fnv\",\"req\":\"^1.0.7\"},{\"default_features\":false,\"name\":\"foldhash\",\"optional\":true,\"req\":\"^0.1.2\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1.4\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9.0\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.2\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.2\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.25\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"default-hasher\",\"inline-more\",\"allocator-api2\",\"equivalent\",\"raw-entry\"],\"default-hasher\":[\"dep:foldhash\"],\"inline-more\":[],\"nightly\":[\"bumpalo/allocator_api\"],\"raw-entry\":[],\"rustc-dep-of-std\":[\"nightly\",\"core\",\"alloc\",\"rustc-internal-api\"],\"rustc-internal-api\":[]}}", @@ -961,6 +1018,7 @@ "hashlink_0.10.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"default-hasher\",\"inline-more\"],\"name\":\"hashbrown\",\"req\":\"^0.15\"},{\"kind\":\"dev\",\"name\":\"rustc-hash\",\"req\":\"^2\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"serde_impl\":[\"serde\"]}}", "headers-core_0.3.0": "{\"dependencies\":[{\"name\":\"http\",\"req\":\"^1.0.0\"}],\"features\":{}}", "headers_0.4.1": "{\"dependencies\":[{\"name\":\"base64\",\"req\":\"^0.22\"},{\"name\":\"bytes\",\"req\":\"^1\"},{\"name\":\"headers-core\",\"req\":\"^0.3\"},{\"name\":\"http\",\"req\":\"^1.0.0\"},{\"name\":\"httpdate\",\"req\":\"^1\"},{\"name\":\"mime\",\"req\":\"^0.3.14\"},{\"name\":\"sha1\",\"req\":\"^0.10\"}],\"features\":{\"nightly\":[]}}", + "heapless_0.8.0": "{\"dependencies\":[{\"name\":\"defmt\",\"optional\":true,\"req\":\">=0.2.0, <0.4\"},{\"name\":\"hash32\",\"req\":\"^0.3.0\"},{\"name\":\"portable-atomic\",\"optional\":true,\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"name\":\"stable_deref_trait\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"ufmt\",\"req\":\"^0.2\"},{\"name\":\"ufmt-write\",\"optional\":true,\"req\":\"^0.1\"}],\"features\":{\"defmt-03\":[\"dep:defmt\"],\"mpmc_large\":[],\"portable-atomic\":[\"dep:portable-atomic\"],\"portable-atomic-critical-section\":[\"dep:portable-atomic\",\"portable-atomic\",\"portable-atomic?/critical-section\"],\"portable-atomic-unsafe-assume-single-core\":[\"dep:portable-atomic\",\"portable-atomic\",\"portable-atomic?/unsafe-assume-single-core\"],\"serde\":[\"dep:serde\"],\"ufmt\":[\"dep:ufmt-write\"]}}", "heck_0.4.1": "{\"dependencies\":[{\"name\":\"unicode-segmentation\",\"optional\":true,\"req\":\"^1.2.0\"}],\"features\":{\"default\":[],\"unicode\":[\"unicode-segmentation\"]}}", "heck_0.5.0": "{\"dependencies\":[],\"features\":{}}", "hermit-abi_0.5.2": "{\"dependencies\":[{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"}],\"features\":{\"default\":[],\"rustc-dep-of-std\":[\"core\",\"alloc\"]}}", @@ -1011,6 +1069,8 @@ "ignore_0.4.25": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"bstr\",\"req\":\"^1.6.2\"},{\"kind\":\"dev\",\"name\":\"crossbeam-channel\",\"req\":\"^0.5.15\"},{\"name\":\"crossbeam-deque\",\"req\":\"^0.8.3\"},{\"name\":\"globset\",\"req\":\"^0.4.18\"},{\"name\":\"log\",\"req\":\"^0.4.20\"},{\"name\":\"memchr\",\"req\":\"^2.6.3\"},{\"default_features\":false,\"features\":[\"std\",\"perf\",\"syntax\",\"meta\",\"nfa\",\"hybrid\",\"dfa-onepass\"],\"name\":\"regex-automata\",\"req\":\"^0.4.0\"},{\"name\":\"same-file\",\"req\":\"^1.0.6\"},{\"name\":\"walkdir\",\"req\":\"^2.4.0\"},{\"name\":\"winapi-util\",\"req\":\"^0.1.2\",\"target\":\"cfg(windows)\"}],\"features\":{\"simd-accel\":[]}}", "image-webp_0.2.4": "{\"dependencies\":[{\"name\":\"byteorder-lite\",\"req\":\"^0.1.0\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1.0.14\"},{\"kind\":\"dev\",\"name\":\"png\",\"req\":\"^0.17.12\"},{\"name\":\"quick-error\",\"req\":\"^2.0.1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.5\"},{\"kind\":\"dev\",\"name\":\"webp\",\"req\":\"^0.3.0\"}],\"features\":{\"_benchmarks\":[]}}", "image_0.25.9": "{\"dependencies\":[{\"features\":[\"extern_crate_alloc\"],\"name\":\"bytemuck\",\"req\":\"^1.8.0\"},{\"name\":\"byteorder-lite\",\"req\":\"^0.1.0\"},{\"name\":\"color_quant\",\"optional\":true,\"req\":\"^1.1\"},{\"kind\":\"dev\",\"name\":\"crc32fast\",\"req\":\"^1.2.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\"},{\"name\":\"dav1d\",\"optional\":true,\"req\":\"^0.10.3\"},{\"default_features\":false,\"name\":\"exr\",\"optional\":true,\"req\":\"^1.74.0\"},{\"name\":\"gif\",\"optional\":true,\"req\":\"^0.14.0\"},{\"kind\":\"dev\",\"name\":\"glob\",\"req\":\"^0.3\"},{\"name\":\"image-webp\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"moxcms\",\"req\":\"^0.7.4\"},{\"name\":\"mp4parse\",\"optional\":true,\"req\":\"^0.17.0\"},{\"kind\":\"dev\",\"name\":\"num-complex\",\"req\":\"^0.4\"},{\"name\":\"num-traits\",\"req\":\"^0.2.0\"},{\"name\":\"png\",\"optional\":true,\"req\":\"^0.18.0\"},{\"name\":\"qoi\",\"optional\":true,\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"ravif\",\"optional\":true,\"req\":\"^0.12\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.7.0\"},{\"default_features\":false,\"name\":\"rgb\",\"optional\":true,\"req\":\"^0.8.48\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.214\"},{\"name\":\"tiff\",\"optional\":true,\"req\":\"^0.10.3\"},{\"default_features\":false,\"name\":\"zune-core\",\"optional\":true,\"req\":\"^0.5.0\"},{\"name\":\"zune-jpeg\",\"optional\":true,\"req\":\"^0.5.5\"}],\"features\":{\"avif\":[\"dep:ravif\",\"dep:rgb\"],\"avif-native\":[\"dep:mp4parse\",\"dep:dav1d\"],\"benchmarks\":[],\"bmp\":[],\"color_quant\":[\"dep:color_quant\"],\"dds\":[],\"default\":[\"rayon\",\"default-formats\"],\"default-formats\":[\"avif\",\"bmp\",\"dds\",\"exr\",\"ff\",\"gif\",\"hdr\",\"ico\",\"jpeg\",\"png\",\"pnm\",\"qoi\",\"tga\",\"tiff\",\"webp\"],\"exr\":[\"dep:exr\"],\"ff\":[],\"gif\":[\"dep:gif\",\"dep:color_quant\"],\"hdr\":[],\"ico\":[\"bmp\",\"png\"],\"jpeg\":[\"dep:zune-core\",\"dep:zune-jpeg\"],\"nasm\":[\"ravif?/asm\"],\"png\":[\"dep:png\"],\"pnm\":[],\"qoi\":[\"dep:qoi\"],\"rayon\":[\"dep:rayon\",\"ravif?/threading\",\"exr?/rayon\"],\"serde\":[\"dep:serde\"],\"tga\":[],\"tiff\":[\"dep:tiff\"],\"webp\":[\"dep:image-webp\"]}}", + "imara-diff_0.1.8": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"expect-test\",\"req\":\"^1.4.0\"},{\"default_features\":false,\"features\":[\"default-hasher\",\"inline-more\"],\"name\":\"hashbrown\",\"req\":\"^0.15\"}],\"features\":{\"default\":[\"unified_diff\"],\"unified_diff\":[]}}", + "imara-diff_0.2.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"expect-test\",\"req\":\"^1.4.0\"},{\"default_features\":false,\"features\":[\"default-hasher\",\"inline-more\"],\"name\":\"hashbrown\",\"req\":\"^0.15\"},{\"name\":\"memchr\",\"req\":\"^2.7.4\"}],\"features\":{\"default\":[\"unified_diff\"],\"unified_diff\":[]}}", "impl-more_0.1.9": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"eyre\",\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1\"}],\"features\":{}}", "include_dir_0.7.4": "{\"dependencies\":[{\"name\":\"glob\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"include_dir_macros\",\"req\":\"^0.7.4\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"default\":[],\"metadata\":[\"include_dir_macros/metadata\"],\"nightly\":[\"include_dir_macros/nightly\"]}}", "include_dir_macros_0.7.4": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"}],\"features\":{\"metadata\":[],\"nightly\":[]}}", @@ -1026,6 +1086,7 @@ "intl-memoizer_0.5.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"fluent-langneg\",\"req\":\"^0.13\"},{\"kind\":\"dev\",\"name\":\"intl_pluralrules\",\"req\":\"^7.0\"},{\"name\":\"type-map\",\"req\":\"^0.5\"},{\"name\":\"unic-langid\",\"req\":\"^0.9\"}],\"features\":{}}", "intl_pluralrules_7.0.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"name\":\"unic-langid\",\"req\":\"^0.9\"},{\"features\":[\"macros\"],\"kind\":\"dev\",\"name\":\"unic-langid\",\"req\":\"^0.9\"}],\"features\":{}}", "inventory_0.3.21": "{\"dependencies\":[{\"name\":\"rustversion\",\"req\":\"^1.0\",\"target\":\"cfg(target_family = \\\"wasm\\\")\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.89\"}],\"features\":{}}", + "io-close_0.3.7": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2.80\",\"target\":\"cfg(unix)\"},{\"name\":\"os_pipe\",\"optional\":true,\"req\":\"^0.9.2\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.1.0\"},{\"features\":[\"handleapi\",\"std\",\"winsock2\"],\"name\":\"winapi\",\"req\":\"^0.3.9\",\"target\":\"cfg(windows)\"}],\"features\":{}}", "io_tee_0.1.1": "{\"dependencies\":[],\"features\":{}}", "ipconfig_0.3.2": "{\"dependencies\":[{\"name\":\"socket2\",\"req\":\"^0.5.1\",\"target\":\"cfg(windows)\"},{\"name\":\"widestring\",\"req\":\"^1.0.2\",\"target\":\"cfg(windows)\"},{\"features\":[\"Win32_Foundation\",\"Win32_Networking_WinSock\",\"Win32_System_Registry\"],\"name\":\"windows-sys\",\"req\":\"^0.48.0\",\"target\":\"cfg(windows)\"},{\"name\":\"winreg\",\"optional\":true,\"req\":\"^0.50.0\",\"target\":\"cfg(windows)\"}],\"features\":{\"computer\":[\"winreg\"],\"default\":[\"computer\"]}}", "ipnet_2.11.0": "{\"dependencies\":[{\"name\":\"heapless\",\"optional\":true,\"req\":\"^0\"},{\"name\":\"schemars\",\"optional\":true,\"req\":\"^0.8\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"package\":\"serde\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1\"}],\"features\":{\"default\":[\"std\"],\"json\":[\"serde\",\"schemars\"],\"ser_as_str\":[\"heapless\"],\"std\":[]}}", @@ -1041,9 +1102,9 @@ "itoa_1.0.17": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\",\"target\":\"cfg(not(miri))\"},{\"name\":\"no-panic\",\"optional\":true,\"req\":\"^0.1\"}],\"features\":{}}", "itoa_1.0.18": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\",\"target\":\"cfg(not(miri))\"},{\"name\":\"no-panic\",\"optional\":true,\"req\":\"^0.1\"}],\"features\":{}}", "ixdtf_0.6.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"serde-json-core\",\"req\":\"^0.6.0\"}],\"features\":{\"default\":[\"duration\"],\"duration\":[]}}", - "jiff-static_0.2.18": "{\"dependencies\":[{\"name\":\"jiff-tzdb\",\"optional\":true,\"req\":\"^0.1.4\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.93\"},{\"name\":\"quote\",\"req\":\"^1.0.38\"},{\"name\":\"syn\",\"req\":\"^2.0.98\"}],\"features\":{\"default\":[],\"perf-inline\":[],\"tz-fat\":[],\"tzdb\":[\"dep:jiff-tzdb\"]}}", "jiff-static_0.2.23": "{\"dependencies\":[{\"name\":\"jiff-tzdb\",\"optional\":true,\"req\":\"^0.1.6\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.93\"},{\"name\":\"quote\",\"req\":\"^1.0.38\"},{\"name\":\"syn\",\"req\":\"^2.0.98\"}],\"features\":{\"default\":[],\"perf-inline\":[],\"tz-fat\":[],\"tzdb\":[\"dep:jiff-tzdb\"]}}", - "jiff_0.2.18": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.81\"},{\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"chrono\",\"req\":\"^0.4.38\"},{\"kind\":\"dev\",\"name\":\"chrono-tz\",\"req\":\"^0.10.0\"},{\"kind\":\"dev\",\"name\":\"hifitime\",\"req\":\"^3.9.0\",\"target\":\"cfg(not(target_family = \\\"wasm\\\"))\"},{\"kind\":\"dev\",\"name\":\"humantime\",\"req\":\"^2.1.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.39.0\"},{\"name\":\"jiff-static\",\"req\":\"=0.2.18\",\"target\":\"cfg(any())\"},{\"name\":\"jiff-static\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"jiff-tzdb\",\"optional\":true,\"req\":\"^0.1.5\"},{\"name\":\"jiff-tzdb-platform\",\"optional\":true,\"req\":\"^0.1.3\",\"target\":\"cfg(any(windows, target_family = \\\"wasm\\\"))\"},{\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3.50\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"default_features\":false,\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.21\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4.21\"},{\"default_features\":false,\"name\":\"portable-atomic\",\"req\":\"^1.10.0\",\"target\":\"cfg(not(target_has_atomic = \\\"ptr\\\"))\"},{\"default_features\":false,\"name\":\"portable-atomic-util\",\"req\":\"^0.2.4\",\"target\":\"cfg(not(target_has_atomic = \\\"ptr\\\"))\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.203\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.221\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.117\"},{\"kind\":\"dev\",\"name\":\"serde_yaml\",\"req\":\"^0.9.34\"},{\"kind\":\"dev\",\"name\":\"tabwriter\",\"req\":\"^1.4.0\"},{\"features\":[\"local-offset\",\"macros\",\"parsing\"],\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.36\"},{\"kind\":\"dev\",\"name\":\"tzfile\",\"req\":\"^0.1.3\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5.0\"},{\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2.70\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"default_features\":false,\"features\":[\"Win32_Foundation\",\"Win32_System_Time\"],\"name\":\"windows-sys\",\"optional\":true,\"req\":\">=0.52.0, <=0.61\",\"target\":\"cfg(windows)\"}],\"features\":{\"alloc\":[\"serde_core?/alloc\",\"portable-atomic-util/alloc\"],\"default\":[\"std\",\"tz-system\",\"tz-fat\",\"tzdb-bundle-platform\",\"tzdb-zoneinfo\",\"tzdb-concatenated\",\"perf-inline\"],\"js\":[\"dep:wasm-bindgen\",\"dep:js-sys\"],\"logging\":[\"dep:log\"],\"perf-inline\":[],\"serde\":[\"dep:serde_core\"],\"static\":[\"static-tz\",\"jiff-static?/tzdb\"],\"static-tz\":[\"dep:jiff-static\"],\"std\":[\"alloc\",\"log?/std\",\"serde_core?/std\"],\"tz-fat\":[\"jiff-static?/tz-fat\"],\"tz-system\":[\"std\",\"dep:windows-sys\"],\"tzdb-bundle-always\":[\"dep:jiff-tzdb\",\"alloc\"],\"tzdb-bundle-platform\":[\"dep:jiff-tzdb-platform\",\"alloc\"],\"tzdb-concatenated\":[\"std\"],\"tzdb-zoneinfo\":[\"std\"]}}", + "jiff-tzdb-platform_0.1.3": "{\"dependencies\":[{\"name\":\"jiff-tzdb\",\"req\":\"^0.1.4\"}],\"features\":{}}", + "jiff-tzdb_0.1.6": "{\"dependencies\":[],\"features\":{}}", "jiff_0.2.23": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.81\"},{\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"chrono\",\"req\":\"^0.4.38\"},{\"kind\":\"dev\",\"name\":\"chrono-tz\",\"req\":\"^0.10.0\"},{\"kind\":\"dev\",\"name\":\"hifitime\",\"req\":\"^3.9.0\",\"target\":\"cfg(not(target_family = \\\"wasm\\\"))\"},{\"kind\":\"dev\",\"name\":\"humantime\",\"req\":\"^2.1.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.39.0\"},{\"name\":\"jiff-static\",\"req\":\"=0.2.23\",\"target\":\"cfg(any())\"},{\"name\":\"jiff-static\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"jiff-tzdb\",\"optional\":true,\"req\":\"^0.1.6\"},{\"name\":\"jiff-tzdb-platform\",\"optional\":true,\"req\":\"^0.1.3\",\"target\":\"cfg(any(windows, target_family = \\\"wasm\\\"))\"},{\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3.50\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"default_features\":false,\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.21\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4.21\"},{\"default_features\":false,\"name\":\"portable-atomic\",\"req\":\"^1.10.0\",\"target\":\"cfg(not(target_has_atomic = \\\"ptr\\\"))\"},{\"default_features\":false,\"name\":\"portable-atomic-util\",\"req\":\"^0.2.4\",\"target\":\"cfg(not(target_has_atomic = \\\"ptr\\\"))\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.203\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.221\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.117\"},{\"kind\":\"dev\",\"name\":\"serde_yaml\",\"req\":\"^0.9.34\"},{\"kind\":\"dev\",\"name\":\"tabwriter\",\"req\":\"^1.4.0\"},{\"features\":[\"local-offset\",\"macros\",\"parsing\"],\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.36\"},{\"kind\":\"dev\",\"name\":\"time-tz\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"tzfile\",\"req\":\"^0.1.3\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5.0\"},{\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2.70\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"default_features\":false,\"features\":[\"Win32_Foundation\",\"Win32_System_Time\"],\"name\":\"windows-sys\",\"optional\":true,\"req\":\">=0.52.0, <=0.61\",\"target\":\"cfg(windows)\"}],\"features\":{\"alloc\":[\"serde_core?/alloc\",\"portable-atomic-util/alloc\"],\"default\":[\"std\",\"tz-system\",\"tz-fat\",\"tzdb-bundle-platform\",\"tzdb-zoneinfo\",\"tzdb-concatenated\",\"perf-inline\"],\"js\":[\"dep:wasm-bindgen\",\"dep:js-sys\"],\"logging\":[\"dep:log\"],\"perf-inline\":[],\"serde\":[\"dep:serde_core\"],\"static\":[\"static-tz\",\"jiff-static?/tzdb\"],\"static-tz\":[\"dep:jiff-static\"],\"std\":[\"alloc\",\"log?/std\",\"serde_core?/std\"],\"tz-fat\":[\"jiff-static?/tz-fat\"],\"tz-system\":[\"std\",\"dep:windows-sys\"],\"tzdb-bundle-always\":[\"dep:jiff-tzdb\",\"alloc\"],\"tzdb-bundle-platform\":[\"dep:jiff-tzdb-platform\",\"alloc\"],\"tzdb-concatenated\":[\"std\"],\"tzdb-zoneinfo\":[\"std\"]}}", "jni-sys_0.3.0": "{\"dependencies\":[],\"features\":{}}", "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\":{\"default\":[],\"invocation\":[\"java-locator\",\"libloading\"]}}", @@ -1053,6 +1114,7 @@ "keyring_3.6.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"base64\",\"req\":\"^0.22\"},{\"name\":\"byteorder\",\"optional\":true,\"req\":\"^1.2\",\"target\":\"cfg(target_os = \\\"windows\\\")\"},{\"features\":[\"derive\",\"wrap_help\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"^4\"},{\"name\":\"dbus-secret-service\",\"optional\":true,\"req\":\"^4.0.0-rc.1\",\"target\":\"cfg(target_os = \\\"openbsd\\\")\"},{\"name\":\"dbus-secret-service\",\"optional\":true,\"req\":\"^4.0.0-rc.2\",\"target\":\"cfg(target_os = \\\"linux\\\")\"},{\"name\":\"dbus-secret-service\",\"optional\":true,\"req\":\"^4.0.1\",\"target\":\"cfg(target_os = \\\"freebsd\\\")\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11.5\"},{\"kind\":\"dev\",\"name\":\"fastrand\",\"req\":\"^2\"},{\"features\":[\"std\"],\"name\":\"linux-keyutils\",\"optional\":true,\"req\":\"^0.2\",\"target\":\"cfg(target_os = \\\"linux\\\")\"},{\"name\":\"log\",\"req\":\"^0.4.22\"},{\"name\":\"openssl\",\"optional\":true,\"req\":\"^0.10.66\"},{\"kind\":\"dev\",\"name\":\"rpassword\",\"req\":\"^7\"},{\"kind\":\"dev\",\"name\":\"rprompt\",\"req\":\"^2\"},{\"name\":\"secret-service\",\"optional\":true,\"req\":\"^4\",\"target\":\"cfg(target_os = \\\"freebsd\\\")\"},{\"name\":\"secret-service\",\"optional\":true,\"req\":\"^4\",\"target\":\"cfg(target_os = \\\"linux\\\")\"},{\"name\":\"secret-service\",\"optional\":true,\"req\":\"^4\",\"target\":\"cfg(target_os = \\\"openbsd\\\")\"},{\"name\":\"security-framework\",\"optional\":true,\"req\":\"^2\",\"target\":\"cfg(target_os = \\\"ios\\\")\"},{\"name\":\"security-framework\",\"optional\":true,\"req\":\"^3\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"kind\":\"dev\",\"name\":\"whoami\",\"req\":\"^1.5\"},{\"features\":[\"Win32_Foundation\",\"Win32_Security_Credentials\"],\"name\":\"windows-sys\",\"optional\":true,\"req\":\"^0.60\",\"target\":\"cfg(target_os = \\\"windows\\\")\"},{\"name\":\"zbus\",\"optional\":true,\"req\":\"^4\",\"target\":\"cfg(target_os = \\\"freebsd\\\")\"},{\"name\":\"zbus\",\"optional\":true,\"req\":\"^4\",\"target\":\"cfg(target_os = \\\"linux\\\")\"},{\"name\":\"zbus\",\"optional\":true,\"req\":\"^4\",\"target\":\"cfg(target_os = \\\"openbsd\\\")\"},{\"name\":\"zeroize\",\"req\":\"^1.8.1\",\"target\":\"cfg(target_os = \\\"windows\\\")\"}],\"features\":{\"apple-native\":[\"dep:security-framework\"],\"async-io\":[\"zbus?/async-io\"],\"async-secret-service\":[\"dep:secret-service\",\"dep:zbus\"],\"crypto-openssl\":[\"dbus-secret-service?/crypto-openssl\",\"secret-service?/crypto-openssl\"],\"crypto-rust\":[\"dbus-secret-service?/crypto-rust\",\"secret-service?/crypto-rust\"],\"linux-native\":[\"dep:linux-keyutils\"],\"linux-native-async-persistent\":[\"linux-native\",\"async-secret-service\"],\"linux-native-sync-persistent\":[\"linux-native\",\"sync-secret-service\"],\"sync-secret-service\":[\"dep:dbus-secret-service\"],\"tokio\":[\"zbus?/tokio\"],\"vendored\":[\"dbus-secret-service?/vendored\",\"openssl?/vendored\"],\"windows-native\":[\"dep:windows-sys\",\"dep:byteorder\"]}}", "kqueue-sys_1.0.4": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^1.2.1\"},{\"name\":\"libc\",\"req\":\"^0.2.74\"}],\"features\":{}}", "kqueue_1.1.1": "{\"dependencies\":[{\"features\":[\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"dhat\",\"req\":\"^0.3.2\"},{\"name\":\"kqueue-sys\",\"req\":\"^1.0.4\"},{\"name\":\"libc\",\"req\":\"^0.2.17\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.1.0\"}],\"features\":{}}", + "kstring_2.0.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.4.0\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"static_assertions\",\"req\":\"^1.1.0\"}],\"features\":{\"arc\":[],\"default\":[\"std\",\"unsafe\"],\"max_inline\":[],\"std\":[],\"unsafe\":[],\"unstable_bench_subset\":[]}}", "lalrpop-util_0.19.12": "{\"dependencies\":[{\"name\":\"regex\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"default\":[\"std\"],\"lexer\":[\"regex/std\",\"std\"],\"std\":[]}}", "lalrpop_0.19.12": "{\"dependencies\":[{\"default_features\":false,\"name\":\"ascii-canvas\",\"req\":\"^3.0\"},{\"default_features\":false,\"name\":\"bit-set\",\"req\":\"^0.5.2\"},{\"default_features\":false,\"name\":\"diff\",\"req\":\"^0.1.12\"},{\"default_features\":false,\"name\":\"ena\",\"req\":\"^0.14\"},{\"name\":\"is-terminal\",\"req\":\"^0.4.2\"},{\"default_features\":false,\"features\":[\"use_std\"],\"name\":\"itertools\",\"req\":\"^0.10\"},{\"name\":\"lalrpop-util\",\"req\":\"^0.19.12\"},{\"default_features\":false,\"name\":\"petgraph\",\"req\":\"^0.6\"},{\"default_features\":false,\"name\":\"pico-args\",\"optional\":true,\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"regex\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"std\",\"unicode-case\",\"unicode-perl\"],\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"unicode\"],\"name\":\"regex-syntax\",\"req\":\"^0.6\"},{\"default_features\":false,\"features\":[\"unicode-case\",\"unicode-perl\"],\"kind\":\"dev\",\"name\":\"regex-syntax\",\"req\":\"^0.6\"},{\"default_features\":false,\"name\":\"string_cache\",\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"term\",\"req\":\"^0.7\"},{\"features\":[\"sha3\"],\"name\":\"tiny-keccak\",\"req\":\"^2.0.2\"},{\"default_features\":false,\"name\":\"unicode-xid\",\"req\":\"^0.2\"}],\"features\":{\"default\":[\"lexer\"],\"lexer\":[\"lalrpop-util/lexer\"],\"test\":[]}}", "landlock_0.4.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0\"},{\"name\":\"enumflags2\",\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1\"},{\"name\":\"libc\",\"req\":\"^0.2.175\"},{\"kind\":\"dev\",\"name\":\"strum\",\"req\":\"^0.26\"},{\"kind\":\"dev\",\"name\":\"strum_macros\",\"req\":\"^0.26\"},{\"name\":\"thiserror\",\"req\":\"^2.0\"}],\"features\":{}}", @@ -1074,7 +1136,6 @@ "link-cplusplus_1.0.12": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1\"}],\"features\":{\"default\":[],\"libc++\":[],\"libcxx\":[\"libc++\"],\"libstdc++\":[],\"libstdcxx\":[\"libstdc++\"],\"nothing\":[]}}", "linked-hash-map_0.5.6": "{\"dependencies\":[{\"name\":\"heapsize\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"heapsize_impl\":[\"heapsize\"],\"nightly\":[],\"serde_impl\":[\"serde\"]}}", "linux-keyutils_0.2.4": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bitflags\",\"req\":\"^2.4\"},{\"default_features\":false,\"features\":[\"std\",\"derive\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"^4.4.11\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.132\"},{\"kind\":\"dev\",\"name\":\"zeroize\",\"req\":\"^1.5.7\"}],\"features\":{\"default\":[],\"std\":[\"bitflags/std\"]}}", - "linux-raw-sys_0.11.0": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.100\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"}],\"features\":{\"auxvec\":[],\"bootparam\":[],\"btrfs\":[],\"default\":[\"std\",\"general\",\"errno\"],\"elf\":[],\"elf_uapi\":[],\"errno\":[],\"general\":[],\"if_arp\":[],\"if_ether\":[],\"if_packet\":[],\"image\":[],\"io_uring\":[],\"ioctl\":[],\"landlock\":[],\"loop_device\":[],\"mempolicy\":[],\"net\":[],\"netlink\":[],\"no_std\":[],\"prctl\":[],\"ptrace\":[],\"rustc-dep-of-std\":[\"core\",\"no_std\"],\"std\":[],\"system\":[],\"xdp\":[]}}", "linux-raw-sys_0.12.1": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.100\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"}],\"features\":{\"auxvec\":[],\"bootparam\":[],\"btrfs\":[],\"default\":[\"std\",\"general\",\"errno\"],\"elf\":[],\"elf_uapi\":[],\"errno\":[],\"general\":[],\"if_arp\":[],\"if_ether\":[],\"if_packet\":[],\"if_tun\":[],\"image\":[],\"io_uring\":[],\"ioctl\":[],\"landlock\":[],\"loop_device\":[],\"mempolicy\":[],\"net\":[],\"netlink\":[],\"no_std\":[],\"prctl\":[],\"ptrace\":[],\"rustc-dep-of-std\":[\"core\",\"no_std\"],\"std\":[],\"system\":[],\"vm_sockets\":[],\"xdp\":[]}}", "linux-raw-sys_0.4.15": "{\"dependencies\":[{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1.49\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.100\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"}],\"features\":{\"bootparam\":[],\"btrfs\":[],\"default\":[\"std\",\"general\",\"errno\"],\"elf\":[],\"elf_uapi\":[],\"errno\":[],\"general\":[],\"if_arp\":[],\"if_ether\":[],\"if_packet\":[],\"io_uring\":[],\"ioctl\":[],\"landlock\":[],\"loop_device\":[],\"mempolicy\":[],\"net\":[],\"netlink\":[],\"no_std\":[],\"prctl\":[],\"ptrace\":[],\"rustc-dep-of-std\":[\"core\",\"compiler_builtins\",\"no_std\"],\"std\":[],\"system\":[],\"xdp\":[]}}", "litemap_0.8.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.1\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"use-std\"],\"kind\":\"dev\",\"name\":\"postcard\",\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"features\":[\"validation\"],\"kind\":\"dev\",\"name\":\"rkyv\",\"req\":\"^0.7\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.220\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"serde_core\",\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.45\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"yoke\",\"optional\":true,\"req\":\"^0.8.0\"}],\"features\":{\"alloc\":[],\"databake\":[\"dep:databake\"],\"default\":[\"alloc\"],\"serde\":[\"dep:serde_core\",\"alloc\"],\"testing\":[\"alloc\"],\"yoke\":[\"dep:yoke\"]}}", @@ -1096,10 +1157,12 @@ "matchit_0.7.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"actix-router\",\"req\":\"^0.2.7\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3.4\"},{\"kind\":\"dev\",\"name\":\"gonzales\",\"req\":\"^0.0.3-beta\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^0.14\"},{\"kind\":\"dev\",\"name\":\"path-tree\",\"req\":\"^0.2.2\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.5.4\"},{\"kind\":\"dev\",\"name\":\"route-recognizer\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"routefinder\",\"req\":\"^0.5.2\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"make\",\"util\"],\"kind\":\"dev\",\"name\":\"tower\",\"req\":\"^0.4\"}],\"features\":{\"__test_helpers\":[],\"default\":[]}}", "matchit_0.8.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"actix-router\",\"req\":\"^0.2.7\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3.4\"},{\"kind\":\"dev\",\"name\":\"gonzales\",\"req\":\"^0.0.3-beta\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^0.14\"},{\"kind\":\"dev\",\"name\":\"path-tree\",\"req\":\"^0.2.2\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.5.4\"},{\"kind\":\"dev\",\"name\":\"route-recognizer\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"routefinder\",\"req\":\"^0.5.2\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"make\",\"util\"],\"kind\":\"dev\",\"name\":\"tower\",\"req\":\"^0.4\"}],\"features\":{\"__test_helpers\":[],\"default\":[]}}", "matchit_0.9.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"actix-router\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"gonzales\",\"req\":\"^0.0.3-beta\"},{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1\"},{\"features\":[\"http1\",\"server\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^1\"},{\"features\":[\"tokio\"],\"kind\":\"dev\",\"name\":\"hyper-util\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"path-tree\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"route-recognizer\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"routefinder\",\"req\":\"^0.5\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"make\",\"util\"],\"kind\":\"dev\",\"name\":\"tower\",\"req\":\"^0.5.2\"},{\"kind\":\"dev\",\"name\":\"wayfind\",\"req\":\"^0.8\"}],\"features\":{\"__test_helpers\":[],\"default\":[]}}", + "maybe-async_0.2.10": "{\"dependencies\":[{\"features\":[\"attributes\"],\"kind\":\"dev\",\"name\":\"async-std\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"async-trait\",\"req\":\"^0.1\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"features\":[\"visit-mut\",\"full\"],\"name\":\"syn\",\"req\":\"^2.0\"},{\"features\":[\"macros\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1\"}],\"features\":{\"default\":[],\"is_sync\":[]}}", "md-5_0.10.6": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"features\":[\"dev\"],\"kind\":\"dev\",\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.2.2\"},{\"name\":\"md5-asm\",\"optional\":true,\"req\":\"^0.5\",\"target\":\"cfg(any(target_arch = \\\"x86\\\", target_arch = \\\"x86_64\\\"))\"}],\"features\":{\"asm\":[\"md5-asm\"],\"default\":[\"std\"],\"force-soft\":[],\"loongarch64_asm\":[],\"oid\":[\"digest/oid\"],\"std\":[\"digest/std\"]}}", "md5_0.8.0": "{\"dependencies\":[],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "memchr_2.7.6": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.20\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"libc\":[],\"logging\":[\"dep:log\"],\"rustc-dep-of-std\":[\"core\"],\"std\":[\"alloc\"],\"use_std\":[\"std\"]}}", "memchr_2.8.0": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.20\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"libc\":[],\"logging\":[\"dep:log\"],\"rustc-dep-of-std\":[\"core\"],\"std\":[\"alloc\"],\"use_std\":[\"std\"]}}", + "memmap2_0.9.10": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2.151\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"owning_ref\",\"req\":\"^0.4.1\"},{\"name\":\"stable_deref_trait\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{}}", "memoffset_0.6.5": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"autocfg\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"}],\"features\":{\"default\":[],\"unstable_const\":[]}}", "memoffset_0.9.1": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"autocfg\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"}],\"features\":{\"default\":[],\"unstable_const\":[],\"unstable_offset_of\":[]}}", "mime_0.3.17": "{\"dependencies\":[],\"features\":{}}", @@ -1122,6 +1185,7 @@ "nix_0.30.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"assert-impl\",\"req\":\"^0.1\"},{\"name\":\"bitflags\",\"req\":\"^2.3.3\"},{\"kind\":\"dev\",\"name\":\"caps\",\"req\":\"^0.5.3\",\"target\":\"cfg(any(target_os = \\\"android\\\", target_os = \\\"linux\\\"))\"},{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"kind\":\"build\",\"name\":\"cfg_aliases\",\"req\":\"^0.2.1\"},{\"features\":[\"extra_traits\"],\"name\":\"libc\",\"req\":\"^0.2.171\"},{\"name\":\"memoffset\",\"optional\":true,\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"parking_lot\",\"req\":\"^0.12\"},{\"name\":\"pin-utils\",\"optional\":true,\"req\":\"^0.1.0\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"semver\",\"req\":\"^1.0.7\"},{\"kind\":\"dev\",\"name\":\"sysctl\",\"req\":\"^0.4\",\"target\":\"cfg(target_os = \\\"freebsd\\\")\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.7.1\"}],\"features\":{\"acct\":[],\"aio\":[\"pin-utils\"],\"default\":[],\"dir\":[\"fs\"],\"env\":[],\"event\":[\"poll\"],\"fanotify\":[],\"feature\":[],\"fs\":[],\"hostname\":[],\"inotify\":[],\"ioctl\":[],\"kmod\":[],\"mman\":[],\"mount\":[\"uio\"],\"mqueue\":[\"fs\"],\"net\":[\"socket\"],\"personality\":[],\"poll\":[],\"process\":[],\"pthread\":[],\"ptrace\":[\"process\"],\"quota\":[],\"reboot\":[],\"resource\":[],\"sched\":[\"process\"],\"signal\":[\"process\"],\"socket\":[\"memoffset\"],\"syslog\":[],\"term\":[],\"time\":[],\"ucontext\":[\"signal\"],\"uio\":[],\"user\":[\"feature\"],\"zerocopy\":[\"fs\",\"uio\"]}}", "nom_7.1.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"memchr\",\"req\":\"^2.3\"},{\"default_features\":false,\"name\":\"minimal-lexical\",\"req\":\"^0.2.0\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.0.0\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"docsrs\":[],\"std\":[\"alloc\",\"memchr/std\",\"minimal-lexical/std\"]}}", "nom_8.0.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"memchr\",\"req\":\"^2.3\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"=1.0.0\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"docsrs\":[],\"std\":[\"alloc\",\"memchr/std\"]}}", + "nonempty_0.12.0": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"bincode\",\"optional\":true,\"req\":\"^2.0.1\"},{\"default_features\":false,\"features\":[\"derive\",\"alloc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"}],\"features\":{\"arbitrary\":[\"dep:arbitrary\"],\"bincode\":[\"dep:bincode\"],\"default\":[\"std\"],\"serialize\":[\"dep:serde\"],\"std\":[]}}", "normalize-line-endings_0.3.0": "{\"dependencies\":[],\"features\":{}}", "notify-types_2.1.0": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.34.0\"},{\"kind\":\"dev\",\"name\":\"rstest\",\"req\":\"^0.26.0\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.89\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.39\"},{\"name\":\"web-time\",\"optional\":true,\"req\":\"^1.1.0\"}],\"features\":{\"serde\":[\"dep:serde\",\"bitflags/serde\"],\"serialization-compat-6\":[]}}", "notify_8.2.0": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.7.0\",\"target\":\"cfg(target_os=\\\"macos\\\")\"},{\"name\":\"crossbeam-channel\",\"optional\":true,\"req\":\"^0.5.0\"},{\"name\":\"flume\",\"optional\":true,\"req\":\"^0.11.1\"},{\"name\":\"fsevent-sys\",\"optional\":true,\"req\":\"^4.0.0\",\"target\":\"cfg(target_os=\\\"macos\\\")\"},{\"default_features\":false,\"name\":\"inotify\",\"req\":\"^0.11.0\",\"target\":\"cfg(any(target_os=\\\"linux\\\", target_os=\\\"android\\\"))\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.34.0\"},{\"name\":\"kqueue\",\"req\":\"^1.1.1\",\"target\":\"cfg(any(target_os=\\\"freebsd\\\", target_os=\\\"openbsd\\\", target_os = \\\"netbsd\\\", target_os = \\\"dragonflybsd\\\", target_os = \\\"ios\\\"))\"},{\"name\":\"kqueue\",\"optional\":true,\"req\":\"^1.1.1\",\"target\":\"cfg(target_os=\\\"macos\\\")\"},{\"name\":\"libc\",\"req\":\"^0.2.4\"},{\"name\":\"log\",\"req\":\"^0.4.17\"},{\"features\":[\"os-ext\"],\"name\":\"mio\",\"req\":\"^1.0\",\"target\":\"cfg(any(target_os=\\\"freebsd\\\", target_os=\\\"openbsd\\\", target_os = \\\"netbsd\\\", target_os = \\\"dragonflybsd\\\", target_os = \\\"ios\\\"))\"},{\"features\":[\"os-ext\"],\"name\":\"mio\",\"req\":\"^1.0\",\"target\":\"cfg(any(target_os=\\\"linux\\\", target_os=\\\"android\\\"))\"},{\"features\":[\"os-ext\"],\"name\":\"mio\",\"optional\":true,\"req\":\"^1.0\",\"target\":\"cfg(target_os=\\\"macos\\\")\"},{\"kind\":\"dev\",\"name\":\"nix\",\"req\":\"^0.29.0\"},{\"name\":\"notify-types\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.39\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.10.0\"},{\"kind\":\"dev\",\"name\":\"trash\",\"req\":\"^5.2.2\",\"target\":\"cfg(target_os = \\\"windows\\\")\"},{\"name\":\"walkdir\",\"req\":\"^2.4.0\"},{\"features\":[\"Win32_System_Threading\",\"Win32_Foundation\",\"Win32_Storage_FileSystem\",\"Win32_Security\",\"Win32_System_WindowsProgramming\",\"Win32_System_IO\"],\"name\":\"windows-sys\",\"req\":\"^0.60.1\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"macos_fsevent\"],\"macos_fsevent\":[\"fsevent-sys\"],\"macos_kqueue\":[\"kqueue\",\"mio\"],\"serde\":[\"notify-types/serde\"],\"serialization-compat-6\":[\"notify-types/serialization-compat-6\"]}}", @@ -1239,6 +1303,7 @@ "proc-macro-error2_2.0.1": "{\"dependencies\":[{\"name\":\"proc-macro-error-attr2\",\"req\":\"=2.0.0\"},{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"syn\",\"optional\":true,\"req\":\"^2\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"syn\",\"req\":\"^2\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.99\"}],\"features\":{\"default\":[\"syn-error\"],\"nightly\":[],\"syn-error\":[\"dep:syn\"]}}", "proc-macro2_1.0.106": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quote\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tar\",\"req\":\"^0.4\"},{\"name\":\"unicode-ident\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"proc-macro\"],\"nightly\":[],\"proc-macro\":[],\"span-locations\":[]}}", "process-wrap_9.0.1": "{\"dependencies\":[{\"name\":\"futures\",\"optional\":true,\"req\":\"^0.3.30\"},{\"name\":\"indexmap\",\"req\":\"^2.9.0\"},{\"default_features\":false,\"features\":[\"fs\",\"poll\",\"signal\"],\"name\":\"nix\",\"optional\":true,\"req\":\"^0.30.1\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"remoteprocess\",\"req\":\"^0.5.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.20.0\"},{\"features\":[\"io-util\",\"macros\",\"process\",\"rt\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1.38.2\"},{\"features\":[\"io-util\",\"macros\",\"process\",\"rt\",\"rt-multi-thread\",\"time\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.38.2\"},{\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1.40\"},{\"name\":\"windows\",\"optional\":true,\"req\":\"^0.62.2\",\"target\":\"cfg(windows)\"}],\"features\":{\"creation-flags\":[\"dep:windows\",\"windows/Win32_System_Threading\"],\"default\":[\"creation-flags\",\"job-object\",\"kill-on-drop\",\"process-group\",\"process-session\",\"tracing\"],\"job-object\":[\"dep:windows\",\"windows/Win32_Security\",\"windows/Win32_System_Diagnostics_ToolHelp\",\"windows/Win32_System_IO\",\"windows/Win32_System_JobObjects\",\"windows/Win32_System_Threading\"],\"kill-on-drop\":[],\"process-group\":[],\"process-session\":[\"process-group\"],\"reset-sigmask\":[],\"std\":[\"dep:nix\"],\"tokio1\":[\"dep:nix\",\"dep:futures\",\"dep:tokio\"],\"tracing\":[\"dep:tracing\"]}}", + "prodash_31.0.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"argh\",\"req\":\"^0.1.3\"},{\"kind\":\"dev\",\"name\":\"async-executor\",\"req\":\"^1.1.0\"},{\"name\":\"async-io\",\"optional\":true,\"req\":\"^2.2.1\"},{\"kind\":\"dev\",\"name\":\"async-io\",\"req\":\"^2.2.1\"},{\"kind\":\"dev\",\"name\":\"blocking\",\"req\":\"^1.0.0\"},{\"name\":\"bytesize\",\"optional\":true,\"req\":\"^2.0.1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8.1\"},{\"default_features\":false,\"name\":\"crosstermion\",\"optional\":true,\"req\":\"^0.16.0\"},{\"default_features\":false,\"features\":[\"termination\"],\"name\":\"ctrlc\",\"optional\":true,\"req\":\"^3.1.4\"},{\"default_features\":false,\"name\":\"dashmap\",\"optional\":true,\"req\":\"^6.0.1\"},{\"default_features\":false,\"features\":[\"humantime\"],\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11.0\"},{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3.5\"},{\"default_features\":false,\"name\":\"futures-core\",\"optional\":true,\"req\":\"^0.3.4\"},{\"name\":\"futures-lite\",\"optional\":true,\"req\":\"^2.1.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.4\"},{\"name\":\"human_format\",\"optional\":true,\"req\":\"^1.0.3\"},{\"name\":\"is-terminal\",\"optional\":true,\"req\":\"^0.4.9\"},{\"kind\":\"dev\",\"name\":\"is-terminal\",\"req\":\"^0.4.9\"},{\"name\":\"jiff\",\"optional\":true,\"req\":\"^0.2.4\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.8\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1.4.0\"},{\"default_features\":false,\"name\":\"parking_lot\",\"optional\":true,\"req\":\"^0.12.1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.1\"},{\"default_features\":false,\"name\":\"signal-hook\",\"optional\":true,\"req\":\"^0.4.1\"},{\"default_features\":false,\"name\":\"tui\",\"optional\":true,\"package\":\"ratatui\",\"req\":\"^0.30.0\"},{\"name\":\"tui-react\",\"optional\":true,\"req\":\"^0.24.0\"},{\"name\":\"unicode-segmentation\",\"optional\":true,\"req\":\"^1.6.0\"},{\"name\":\"unicode-width\",\"optional\":true,\"req\":\"^0.2.2\"}],\"features\":{\"default\":[\"progress-tree\"],\"local-time\":[\"jiff\"],\"progress-log\":[\"log\"],\"progress-tree\":[\"parking_lot\"],\"progress-tree-hp-hashmap\":[\"dashmap\"],\"progress-tree-log\":[\"log\"],\"render-line\":[\"crosstermion/color\",\"jiff\",\"unicode-width\"],\"render-line-autoconfigure\":[\"is-terminal\"],\"render-line-crossterm\":[\"crosstermion/crossterm\"],\"render-tui\":[\"tui\",\"unicode-segmentation\",\"unicode-width\",\"crosstermion/input-async\",\"tui-react\",\"futures-lite\",\"futures-core\",\"async-io\",\"jiff\"],\"render-tui-crossterm\":[\"crosstermion/tui-react-crossterm\",\"crosstermion/input-async-crossterm\"],\"unit-bytes\":[\"bytesize\"],\"unit-duration\":[\"jiff\"],\"unit-human\":[\"human_format\"]}}", "proptest_1.9.0": "{\"dependencies\":[{\"name\":\"bit-set\",\"optional\":true,\"req\":\"^0.8.0\"},{\"name\":\"bit-vec\",\"optional\":true,\"req\":\"^0.8.0\"},{\"name\":\"bitflags\",\"req\":\"^2.9\"},{\"default_features\":false,\"name\":\"num-traits\",\"req\":\"^0.2.15\"},{\"name\":\"proptest-macro\",\"optional\":true,\"req\":\"^0.4.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"rand\",\"req\":\"^0.9\"},{\"default_features\":false,\"name\":\"rand_chacha\",\"req\":\"^0.9\"},{\"name\":\"rand_xorshift\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.0\"},{\"name\":\"regex-syntax\",\"optional\":true,\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"rusty-fork\",\"optional\":true,\"req\":\"^0.3.0\"},{\"name\":\"tempfile\",\"optional\":true,\"req\":\"^3.0\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"=1.0.112\"},{\"name\":\"unarray\",\"req\":\"^0.1.4\"},{\"name\":\"x86\",\"optional\":true,\"req\":\"^0.52.0\"}],\"features\":{\"alloc\":[],\"atomic64bit\":[],\"attr-macro\":[\"proptest-macro\"],\"bit-set\":[\"dep:bit-set\",\"dep:bit-vec\"],\"default\":[\"std\",\"fork\",\"timeout\",\"bit-set\"],\"default-code-coverage\":[\"std\",\"fork\",\"timeout\",\"bit-set\"],\"fork\":[\"std\",\"rusty-fork\",\"tempfile\"],\"handle-panics\":[\"std\"],\"hardware-rng\":[\"x86\"],\"no_std\":[\"num-traits/libm\"],\"std\":[\"rand/std\",\"rand/os_rng\",\"regex-syntax\",\"num-traits/std\"],\"timeout\":[\"fork\",\"rusty-fork/timeout\"],\"unstable\":[]}}", "prost-build_0.12.6": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bytes\",\"req\":\"^1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\"},{\"name\":\"heck\",\"req\":\">=0.4, <=0.5\"},{\"default_features\":false,\"features\":[\"use_alloc\"],\"name\":\"itertools\",\"req\":\">=0.10, <=0.12\"},{\"name\":\"log\",\"req\":\"^0.4.4\"},{\"default_features\":false,\"name\":\"multimap\",\"req\":\">=0.8, <=0.10\"},{\"name\":\"once_cell\",\"req\":\"^1.17.1\"},{\"default_features\":false,\"name\":\"petgraph\",\"req\":\"^0.6\"},{\"name\":\"prettyplease\",\"optional\":true,\"req\":\"^0.2\"},{\"default_features\":false,\"name\":\"prost\",\"req\":\"^0.12.6\"},{\"default_features\":false,\"name\":\"prost-types\",\"req\":\"^0.12.6\"},{\"default_features\":false,\"name\":\"pulldown-cmark\",\"optional\":true,\"req\":\"^0.9.1\"},{\"name\":\"pulldown-cmark-to-cmark\",\"optional\":true,\"req\":\"^10.0.1\"},{\"default_features\":false,\"features\":[\"std\",\"unicode-bool\"],\"name\":\"regex\",\"req\":\"^1.8.1\"},{\"features\":[\"full\"],\"name\":\"syn\",\"optional\":true,\"req\":\"^2\"},{\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"cleanup-markdown\":[\"dep:pulldown-cmark\",\"dep:pulldown-cmark-to-cmark\"],\"default\":[\"format\"],\"format\":[\"dep:prettyplease\",\"dep:syn\"]}}", "prost-build_0.13.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bytes\",\"req\":\"^1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"name\":\"heck\",\"req\":\">=0.4, <=0.5\"},{\"default_features\":false,\"features\":[\"use_alloc\"],\"name\":\"itertools\",\"req\":\">=0.10, <=0.13\"},{\"name\":\"log\",\"req\":\"^0.4.4\"},{\"default_features\":false,\"name\":\"multimap\",\"req\":\">=0.8, <=0.10\"},{\"name\":\"once_cell\",\"req\":\"^1.17.1\"},{\"default_features\":false,\"name\":\"petgraph\",\"req\":\"^0.6\"},{\"name\":\"prettyplease\",\"optional\":true,\"req\":\"^0.2\"},{\"default_features\":false,\"name\":\"prost\",\"req\":\"^0.13.1\"},{\"default_features\":false,\"name\":\"prost-types\",\"req\":\"^0.13.1\"},{\"default_features\":false,\"name\":\"pulldown-cmark\",\"optional\":true,\"req\":\"^0.9.1\"},{\"name\":\"pulldown-cmark-to-cmark\",\"optional\":true,\"req\":\"^10.0.1\"},{\"default_features\":false,\"features\":[\"std\",\"unicode-bool\"],\"name\":\"regex\",\"req\":\"^1.8.1\"},{\"features\":[\"full\"],\"name\":\"syn\",\"optional\":true,\"req\":\"^2\"},{\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"cleanup-markdown\":[\"dep:pulldown-cmark\",\"dep:pulldown-cmark-to-cmark\"],\"default\":[\"format\"],\"format\":[\"dep:prettyplease\",\"dep:syn\"]}}", @@ -1331,7 +1396,6 @@ "rustfix_0.8.7": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.86\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.5.0\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0.204\"},{\"name\":\"serde_json\",\"req\":\"^1.0.120\"},{\"kind\":\"dev\",\"name\":\"similar\",\"req\":\"^2.6.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.10.1\"},{\"name\":\"thiserror\",\"req\":\"^1.0.63\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"req\":\"^0.1.40\"},{\"features\":[\"env-filter\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3.18\"}],\"features\":{}}", "rusticata-macros_4.1.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"nom\",\"req\":\"^7.0\"}],\"features\":{}}", "rustix_0.38.44": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bitflags\",\"req\":\"^2.4.0\"},{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1.49\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4\",\"target\":\"cfg(all(criterion, not(any(target_os = \\\"emscripten\\\", target_os = \\\"wasi\\\"))))\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"itoa\",\"optional\":true,\"req\":\"^1.0.13\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.161\",\"target\":\"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.161\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.161\"},{\"default_features\":false,\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(windows)\"},{\"default_features\":false,\"name\":\"libc_errno\",\"optional\":true,\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\"},{\"default_features\":false,\"features\":[\"general\",\"ioctl\",\"no_std\"],\"name\":\"linux-raw-sys\",\"req\":\"^0.4.14\",\"target\":\"cfg(all(any(target_os = \\\"android\\\", target_os = \\\"linux\\\"), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"features\":[\"general\",\"errno\",\"ioctl\",\"no_std\",\"elf\"],\"name\":\"linux-raw-sys\",\"req\":\"^0.4.14\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"kind\":\"dev\",\"name\":\"memoffset\",\"req\":\"^0.9.0\"},{\"name\":\"once_cell\",\"optional\":true,\"req\":\"^1.5.2\",\"target\":\"cfg(any(target_os = \\\"android\\\", target_os = \\\"linux\\\"))\"},{\"name\":\"rustc-std-workspace-alloc\",\"optional\":true,\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.5.0\"},{\"features\":[\"Win32_Foundation\",\"Win32_Networking_WinSock\",\"Win32_NetworkManagement_IpHelper\",\"Win32_System_Threading\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <=0.59\",\"target\":\"cfg(windows)\"}],\"features\":{\"all-apis\":[\"event\",\"fs\",\"io_uring\",\"mm\",\"mount\",\"net\",\"param\",\"pipe\",\"process\",\"procfs\",\"pty\",\"rand\",\"runtime\",\"shm\",\"stdio\",\"system\",\"termios\",\"thread\",\"time\"],\"alloc\":[],\"cc\":[],\"default\":[\"std\",\"use-libc-auxv\"],\"event\":[],\"fs\":[],\"io_uring\":[\"event\",\"fs\",\"net\",\"linux-raw-sys/io_uring\"],\"libc-extra-traits\":[\"libc?/extra_traits\"],\"linux_4_11\":[],\"linux_latest\":[\"linux_4_11\"],\"mm\":[],\"mount\":[],\"net\":[\"linux-raw-sys/net\",\"linux-raw-sys/netlink\",\"linux-raw-sys/if_ether\",\"linux-raw-sys/xdp\"],\"param\":[\"fs\"],\"pipe\":[],\"process\":[\"linux-raw-sys/prctl\"],\"procfs\":[\"once_cell\",\"itoa\",\"fs\"],\"pty\":[\"itoa\",\"fs\"],\"rand\":[],\"runtime\":[\"linux-raw-sys/prctl\"],\"rustc-dep-of-std\":[\"core\",\"rustc-std-workspace-alloc\",\"compiler_builtins\",\"linux-raw-sys/rustc-dep-of-std\",\"bitflags/rustc-dep-of-std\",\"compiler_builtins?/rustc-dep-of-std\"],\"shm\":[\"fs\"],\"std\":[\"bitflags/std\",\"alloc\",\"libc?/std\",\"libc_errno?/std\",\"libc-extra-traits\"],\"stdio\":[],\"system\":[\"linux-raw-sys/system\"],\"termios\":[],\"thread\":[\"linux-raw-sys/prctl\"],\"time\":[],\"try_close\":[],\"use-explicitly-provided-auxv\":[],\"use-libc\":[\"libc_errno\",\"libc\",\"libc-extra-traits\"],\"use-libc-auxv\":[]}}", - "rustix_1.1.3": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bitflags\",\"req\":\"^2.4.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4\",\"target\":\"cfg(all(criterion, not(any(target_os = \\\"emscripten\\\", target_os = \\\"wasi\\\"))))\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.177\",\"target\":\"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.177\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.171\"},{\"default_features\":false,\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(windows)\"},{\"default_features\":false,\"name\":\"libc_errno\",\"optional\":true,\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\"},{\"default_features\":false,\"features\":[\"general\",\"ioctl\",\"no_std\"],\"name\":\"linux-raw-sys\",\"req\":\"^0.11.0\",\"target\":\"cfg(all(any(target_os = \\\"linux\\\", target_os = \\\"android\\\"), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"features\":[\"auxvec\",\"general\",\"errno\",\"ioctl\",\"no_std\",\"elf\"],\"name\":\"linux-raw-sys\",\"req\":\"^0.11.0\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"kind\":\"dev\",\"name\":\"memoffset\",\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1.20.3\",\"target\":\"cfg(windows)\"},{\"name\":\"rustc-std-workspace-alloc\",\"optional\":true,\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.5.0\"},{\"features\":[\"Win32_Foundation\",\"Win32_Networking_WinSock\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <0.62\",\"target\":\"cfg(windows)\"}],\"features\":{\"all-apis\":[\"event\",\"fs\",\"io_uring\",\"mm\",\"mount\",\"net\",\"param\",\"pipe\",\"process\",\"pty\",\"rand\",\"runtime\",\"shm\",\"stdio\",\"system\",\"termios\",\"thread\",\"time\"],\"alloc\":[],\"default\":[\"std\"],\"event\":[],\"fs\":[],\"io_uring\":[\"event\",\"fs\",\"net\",\"thread\",\"linux-raw-sys/io_uring\"],\"linux_4_11\":[],\"linux_5_1\":[\"linux_4_11\"],\"linux_5_11\":[\"linux_5_1\"],\"linux_latest\":[\"linux_5_11\"],\"mm\":[],\"mount\":[],\"net\":[\"linux-raw-sys/net\",\"linux-raw-sys/netlink\",\"linux-raw-sys/if_ether\",\"linux-raw-sys/xdp\"],\"param\":[],\"pipe\":[],\"process\":[\"linux-raw-sys/prctl\"],\"pty\":[\"fs\"],\"rand\":[],\"runtime\":[\"linux-raw-sys/prctl\"],\"rustc-dep-of-std\":[\"core\",\"rustc-std-workspace-alloc\",\"linux-raw-sys/rustc-dep-of-std\",\"bitflags/rustc-dep-of-std\"],\"shm\":[\"fs\"],\"std\":[\"bitflags/std\",\"alloc\",\"libc?/std\",\"libc_errno?/std\"],\"stdio\":[],\"system\":[\"linux-raw-sys/system\"],\"termios\":[],\"thread\":[\"linux-raw-sys/prctl\"],\"time\":[],\"try_close\":[],\"use-explicitly-provided-auxv\":[],\"use-libc\":[\"libc_errno\",\"libc\"],\"use-libc-auxv\":[]}}", "rustix_1.1.4": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bitflags\",\"req\":\"^2.4.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4\",\"target\":\"cfg(all(criterion, not(any(target_os = \\\"emscripten\\\", target_os = \\\"wasi\\\"))))\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.182\",\"target\":\"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.182\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.171\"},{\"default_features\":false,\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(windows)\"},{\"default_features\":false,\"name\":\"libc_errno\",\"optional\":true,\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\"},{\"default_features\":false,\"features\":[\"general\",\"ioctl\",\"no_std\"],\"name\":\"linux-raw-sys\",\"req\":\"^0.12\",\"target\":\"cfg(all(any(target_os = \\\"linux\\\", target_os = \\\"android\\\"), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"features\":[\"auxvec\",\"general\",\"errno\",\"ioctl\",\"no_std\",\"elf\"],\"name\":\"linux-raw-sys\",\"req\":\"^0.12\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"kind\":\"dev\",\"name\":\"memoffset\",\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1.20.3\",\"target\":\"cfg(windows)\"},{\"name\":\"rustc-std-workspace-alloc\",\"optional\":true,\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.5.0\"},{\"features\":[\"Win32_Foundation\",\"Win32_Networking_WinSock\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <0.62\",\"target\":\"cfg(windows)\"}],\"features\":{\"all-apis\":[\"event\",\"fs\",\"io_uring\",\"mm\",\"mount\",\"net\",\"param\",\"pipe\",\"process\",\"pty\",\"rand\",\"runtime\",\"shm\",\"stdio\",\"system\",\"termios\",\"thread\",\"time\"],\"alloc\":[],\"default\":[\"std\"],\"event\":[],\"fs\":[],\"io_uring\":[\"event\",\"fs\",\"net\",\"thread\",\"linux-raw-sys/io_uring\"],\"linux_4_11\":[],\"linux_5_1\":[\"linux_4_11\"],\"linux_5_11\":[\"linux_5_1\"],\"linux_latest\":[\"linux_5_11\"],\"mm\":[],\"mount\":[],\"net\":[\"linux-raw-sys/net\",\"linux-raw-sys/netlink\",\"linux-raw-sys/if_ether\",\"linux-raw-sys/xdp\"],\"param\":[],\"pipe\":[],\"process\":[\"linux-raw-sys/prctl\"],\"pty\":[\"fs\"],\"rand\":[],\"runtime\":[\"linux-raw-sys/prctl\"],\"rustc-dep-of-std\":[\"core\",\"rustc-std-workspace-alloc\",\"linux-raw-sys/rustc-dep-of-std\",\"bitflags/rustc-dep-of-std\"],\"shm\":[\"fs\"],\"std\":[\"bitflags/std\",\"alloc\",\"libc?/std\",\"libc_errno?/std\"],\"stdio\":[],\"system\":[\"linux-raw-sys/system\"],\"termios\":[],\"thread\":[\"linux-raw-sys/prctl\"],\"time\":[],\"try_close\":[],\"use-explicitly-provided-auxv\":[],\"use-libc\":[\"libc_errno\",\"libc\"],\"use-libc-auxv\":[]}}", "rustls-native-certs_0.8.3": "{\"dependencies\":[{\"name\":\"openssl-probe\",\"req\":\"^0.2\",\"target\":\"cfg(all(unix, not(target_os = \\\"macos\\\")))\"},{\"features\":[\"std\"],\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.10\"},{\"kind\":\"dev\",\"name\":\"ring\",\"req\":\"^0.17\"},{\"kind\":\"dev\",\"name\":\"rustls\",\"req\":\"^0.23\"},{\"kind\":\"dev\",\"name\":\"rustls-webpki\",\"req\":\"^0.103\"},{\"name\":\"schannel\",\"req\":\"^0.1\",\"target\":\"cfg(windows)\"},{\"name\":\"security-framework\",\"req\":\"^3\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^3\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.5\"},{\"kind\":\"dev\",\"name\":\"untrusted\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.18\"}],\"features\":{}}", "rustls-pki-types_1.14.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"crabgrind\",\"req\":\"=0.1.9\",\"target\":\"cfg(all(target_os = \\\"linux\\\", target_arch = \\\"x86_64\\\"))\"},{\"name\":\"web-time\",\"optional\":true,\"req\":\"^1\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"},{\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"alloc\":[\"dep:zeroize\"],\"default\":[\"alloc\"],\"std\":[\"alloc\"],\"web\":[\"web-time\"]}}", @@ -1391,6 +1455,7 @@ "serial2_0.2.33": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"assert2\",\"req\":\"^0.3.11\"},{\"name\":\"cfg-if\",\"req\":\"^1.0.0\",\"target\":\"cfg(unix)\"},{\"name\":\"libc\",\"req\":\"^0.2.109\",\"target\":\"cfg(unix)\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.108\"},{\"features\":[\"commapi\",\"fileapi\",\"handleapi\",\"ioapiset\",\"std\",\"synchapi\",\"winbase\",\"winerror\",\"winreg\"],\"name\":\"winapi\",\"req\":\"^0.3.9\",\"target\":\"cfg(windows)\"}],\"features\":{\"doc\":[],\"doc-cfg\":[],\"rs4xx\":[],\"serde\":[\"dep:serde\"],\"unix\":[],\"windows\":[]}}", "serial_test_3.3.1": "{\"dependencies\":[{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2\"},{\"default_features\":false,\"name\":\"env_logger\",\"optional\":true,\"req\":\">=0.6.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"fslock\",\"optional\":true,\"req\":\"^0.2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"futures-executor\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"use_std\"],\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\">=0.4\"},{\"name\":\"log\",\"optional\":true,\"req\":\">=0.4.4\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"once_cell\",\"req\":\"^1.19\"},{\"default_features\":false,\"name\":\"parking_lot\",\"req\":\"^0.12\"},{\"default_features\":false,\"name\":\"scc\",\"req\":\"^2\"},{\"name\":\"serial_test_derive\",\"req\":\"~3.3.1\"}],\"features\":{\"async\":[\"dep:futures-executor\",\"dep:futures-util\",\"serial_test_derive/async\"],\"default\":[\"logging\",\"async\"],\"docsrs\":[\"dep:document-features\"],\"file_locks\":[\"dep:fslock\"],\"logging\":[\"dep:log\"],\"test_logging\":[\"logging\",\"dep:env_logger\",\"serial_test_derive/test_logging\"]}}", "serial_test_derive_3.3.1": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\">=0.6.1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"prettyplease\",\"req\":\"^0.2\"},{\"default_features\":false,\"features\":[\"proc-macro\"],\"name\":\"proc-macro2\",\"req\":\"^1.0.60\"},{\"default_features\":false,\"name\":\"quote\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"full\",\"printing\",\"parsing\",\"clone-impls\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{\"async\":[],\"default\":[],\"file_locks\":[],\"test_logging\":[]}}", + "sha1-checked_0.10.0": "{\"dependencies\":[{\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"features\":[\"dev\"],\"kind\":\"dev\",\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.4\"},{\"default_features\":false,\"features\":[\"compress\"],\"name\":\"sha1\",\"req\":\"^0.10.6\"},{\"default_features\":false,\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1.7\"}],\"features\":{\"default\":[\"oid\",\"std\"],\"oid\":[\"digest/oid\",\"sha1/oid\"],\"std\":[\"digest/std\",\"sha1/std\"],\"zeroize\":[\"dep:zeroize\"]}}", "sha1_0.10.6": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"name\":\"cpufeatures\",\"req\":\"^0.2\",\"target\":\"cfg(any(target_arch = \\\"aarch64\\\", target_arch = \\\"x86\\\", target_arch = \\\"x86_64\\\"))\"},{\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"features\":[\"dev\"],\"kind\":\"dev\",\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.2.2\"},{\"name\":\"sha1-asm\",\"optional\":true,\"req\":\"^0.5\",\"target\":\"cfg(any(target_arch = \\\"aarch64\\\", target_arch = \\\"x86\\\", target_arch = \\\"x86_64\\\"))\"}],\"features\":{\"asm\":[\"sha1-asm\"],\"compress\":[],\"default\":[\"std\"],\"force-soft\":[],\"loongarch64_asm\":[],\"oid\":[\"digest/oid\"],\"std\":[\"digest/std\"]}}", "sha1_smol_1.0.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"openssl\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.4\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"alloc\":[],\"std\":[\"alloc\"]}}", "sha2_0.10.9": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"name\":\"cpufeatures\",\"req\":\"^0.2\",\"target\":\"cfg(any(target_arch = \\\"aarch64\\\", target_arch = \\\"x86_64\\\", target_arch = \\\"x86\\\"))\"},{\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"features\":[\"dev\"],\"kind\":\"dev\",\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.2.2\"},{\"name\":\"sha2-asm\",\"optional\":true,\"req\":\"^0.6.1\",\"target\":\"cfg(any(target_arch = \\\"aarch64\\\", target_arch = \\\"x86_64\\\", target_arch = \\\"x86\\\"))\"}],\"features\":{\"asm\":[\"sha2-asm\"],\"asm-aarch64\":[\"asm\"],\"compress\":[],\"default\":[\"std\"],\"force-soft\":[],\"force-soft-compact\":[],\"loongarch64_asm\":[],\"oid\":[\"digest/oid\"],\"std\":[\"digest/std\"]}}", @@ -1458,7 +1523,6 @@ "tagptr_0.2.0": "{\"dependencies\":[],\"features\":{}}", "tar_0.4.44": "{\"dependencies\":[{\"name\":\"filetime\",\"req\":\"^0.2.8\"},{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"},{\"name\":\"xattr\",\"optional\":true,\"req\":\"^1.1.3\",\"target\":\"cfg(unix)\"}],\"features\":{\"default\":[\"xattr\"]}}", "target-lexicon_0.13.3": "{\"dependencies\":[{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"arch_z80\":[],\"arch_zkasm\":[],\"default\":[],\"serde_support\":[\"serde\",\"std\"],\"std\":[]}}", - "tempfile_3.24.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"name\":\"fastrand\",\"req\":\"^2.1.1\"},{\"default_features\":false,\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.3.0\",\"target\":\"cfg(any(unix, windows, target_os = \\\"wasi\\\"))\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"once_cell\",\"req\":\"^1.19.0\"},{\"features\":[\"fs\"],\"name\":\"rustix\",\"req\":\"^1.1.3\",\"target\":\"cfg(any(unix, target_os = \\\"wasi\\\"))\"},{\"features\":[\"Win32_Storage_FileSystem\",\"Win32_Foundation\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <0.62\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"getrandom\"],\"nightly\":[]}}", "tempfile_3.27.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"name\":\"fastrand\",\"req\":\"^2.1.1\"},{\"default_features\":false,\"name\":\"getrandom\",\"optional\":true,\"req\":\">=0.3.0, <0.5\",\"target\":\"cfg(any(unix, windows, target_os = \\\"wasi\\\"))\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"once_cell\",\"req\":\"^1.19.0\"},{\"features\":[\"fs\"],\"name\":\"rustix\",\"req\":\"^1.1.4\",\"target\":\"cfg(any(unix, target_os = \\\"wasi\\\"))\"},{\"features\":[\"Win32_Storage_FileSystem\",\"Win32_Foundation\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <0.62\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"getrandom\"],\"nightly\":[]}}", "temporal_capi_0.1.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"diplomat\",\"req\":\"^0.14.0\"},{\"default_features\":false,\"name\":\"diplomat-runtime\",\"req\":\"^0.14.0\"},{\"default_features\":false,\"features\":[\"unstable\"],\"name\":\"icu_calendar\",\"req\":\"^2.1.0\"},{\"name\":\"icu_locale\",\"req\":\"^2.1.0\"},{\"default_features\":false,\"name\":\"num-traits\",\"req\":\"^0.2.19\"},{\"default_features\":false,\"name\":\"temporal_rs\",\"req\":\"^0.1.2\"},{\"name\":\"timezone_provider\",\"req\":\"^0.1.2\"},{\"name\":\"writeable\",\"req\":\"^0.6.0\"},{\"name\":\"zoneinfo64\",\"optional\":true,\"req\":\"^0.2.0\"}],\"features\":{\"compiled_data\":[\"temporal_rs/compiled_data\"],\"zoneinfo64\":[\"dep:zoneinfo64\",\"timezone_provider/zoneinfo64\"]}}", "temporal_rs_0.1.2": "{\"dependencies\":[{\"name\":\"core_maths\",\"req\":\"^0.1.1\"},{\"name\":\"iana-time-zone\",\"optional\":true,\"req\":\"^0.1.64\"},{\"default_features\":false,\"features\":[\"unstable\",\"compiled_data\"],\"name\":\"icu_calendar\",\"req\":\"^2.1.0\"},{\"name\":\"icu_locale\",\"req\":\"^2.1.0\"},{\"features\":[\"duration\"],\"name\":\"ixdtf\",\"req\":\"^0.6.4\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.28\"},{\"default_features\":false,\"name\":\"num-traits\",\"req\":\"^0.2.19\"},{\"name\":\"timezone_provider\",\"req\":\"^0.1.2\"},{\"name\":\"tinystr\",\"req\":\"^0.8.0\"},{\"name\":\"web-time\",\"optional\":true,\"req\":\"^1.1.0\"},{\"name\":\"writeable\",\"req\":\"^0.6.0\"}],\"features\":{\"compiled_data\":[\"tzdb\"],\"default\":[\"sys\"],\"float64_representable_durations\":[],\"log\":[\"dep:log\"],\"std\":[],\"sys\":[\"std\",\"compiled_data\",\"dep:web-time\",\"dep:iana-time-zone\"],\"tzdb\":[\"std\",\"timezone_provider/tzif\"]}}", @@ -1549,6 +1613,7 @@ "unic-langid_0.9.6": "{\"dependencies\":[{\"name\":\"unic-langid-impl\",\"req\":\"^0.9.6\"},{\"name\":\"unic-langid-macros\",\"optional\":true,\"req\":\"^0.9.6\"},{\"kind\":\"dev\",\"name\":\"unic-langid-macros\",\"req\":\"^0.9.6\"}],\"features\":{\"default\":[],\"likelysubtags\":[\"unic-langid-impl/likelysubtags\"],\"macros\":[\"unic-langid-macros\"],\"serde\":[\"unic-langid-impl/serde\"]}}", "unicase_2.9.0": "{\"dependencies\":[],\"features\":{\"nightly\":[]}}", "unicode-bidi_0.3.18": "{\"dependencies\":[{\"name\":\"flame\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"flamer\",\"optional\":true,\"req\":\"^0.4\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\">=0.8, <2.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\">=0.8, <2.0\"},{\"features\":[\"union\"],\"name\":\"smallvec\",\"optional\":true,\"req\":\">=1.13\"}],\"features\":{\"bench_it\":[],\"default\":[\"std\",\"hardcoded-data\"],\"flame_it\":[\"flame\",\"flamer\"],\"hardcoded-data\":[],\"std\":[],\"unstable\":[],\"with_serde\":[\"serde\"]}}", + "unicode-bom_2.0.3": "{\"dependencies\":[],\"features\":{}}", "unicode-ident_1.0.22": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"fst\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"roaring\",\"req\":\"^0.11\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"ucd-trie\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"unicode-xid\",\"req\":\"^0.2.6\"}],\"features\":{}}", "unicode-ident_1.0.24": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\",\"target\":\"cfg(not(miri))\"},{\"kind\":\"dev\",\"name\":\"fst\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"roaring\",\"req\":\"^0.11\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"ucd-trie\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"unicode-xid\",\"req\":\"^0.2.6\"}],\"features\":{}}", "unicode-linebreak_0.1.5": "{\"dependencies\":[],\"features\":{}}", @@ -1723,6 +1788,7 @@ "zerovec_0.11.5": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.220\"},{\"default_features\":false,\"features\":[\"xxhash64\"],\"name\":\"twox-hash\",\"optional\":true,\"req\":\"^2.0.0\"},{\"default_features\":false,\"name\":\"yoke\",\"optional\":true,\"req\":\"^0.8.0\"},{\"default_features\":false,\"name\":\"zerofrom\",\"req\":\"^0.1.3\"},{\"default_features\":false,\"name\":\"zerovec-derive\",\"optional\":true,\"req\":\"^0.11.1\"}],\"features\":{\"alloc\":[\"serde?/alloc\"],\"databake\":[\"dep:databake\"],\"derive\":[\"dep:zerovec-derive\"],\"hashmap\":[\"dep:twox-hash\",\"alloc\"],\"serde\":[\"dep:serde\"],\"std\":[],\"yoke\":[\"dep:yoke\"]}}", "zip_0.6.6": "{\"dependencies\":[{\"name\":\"aes\",\"optional\":true,\"req\":\"^0.8.2\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"name\":\"byteorder\",\"req\":\"^1.4.3\"},{\"name\":\"bzip2\",\"optional\":true,\"req\":\"^0.4.3\"},{\"name\":\"constant_time_eq\",\"optional\":true,\"req\":\"^0.1.5\"},{\"name\":\"crc32fast\",\"req\":\"^1.3.2\"},{\"name\":\"crossbeam-utils\",\"req\":\"^0.8.8\",\"target\":\"cfg(any(all(target_arch = \\\"arm\\\", target_pointer_width = \\\"32\\\"), target_arch = \\\"mips\\\", target_arch = \\\"powerpc\\\"))\"},{\"default_features\":false,\"name\":\"flate2\",\"optional\":true,\"req\":\"^1.0.23\"},{\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.2.5\"},{\"features\":[\"reset\"],\"name\":\"hmac\",\"optional\":true,\"req\":\"^0.12.1\"},{\"name\":\"pbkdf2\",\"optional\":true,\"req\":\"^0.11.0\"},{\"name\":\"sha1\",\"optional\":true,\"req\":\"^0.10.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"time\",\"optional\":true,\"req\":\"^0.3.7\"},{\"features\":[\"formatting\",\"macros\"],\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.7\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.3.2\"},{\"name\":\"zstd\",\"optional\":true,\"req\":\"^0.11.2\"}],\"features\":{\"aes-crypto\":[\"aes\",\"constant_time_eq\",\"hmac\",\"pbkdf2\",\"sha1\"],\"default\":[\"aes-crypto\",\"bzip2\",\"deflate\",\"time\",\"zstd\"],\"deflate\":[\"flate2/rust_backend\"],\"deflate-miniz\":[\"flate2/default\"],\"deflate-zlib\":[\"flate2/zlib\"],\"unreserved\":[]}}", "zip_2.4.2": "{\"dependencies\":[{\"name\":\"aes\",\"optional\":true,\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.95\"},{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"req\":\"^1.4.1\",\"target\":\"cfg(fuzzing)\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"name\":\"bzip2\",\"optional\":true,\"req\":\"^0.5.0\"},{\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"=4.4.18\"},{\"name\":\"constant_time_eq\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"crc32fast\",\"req\":\"^1.4\"},{\"name\":\"crossbeam-utils\",\"req\":\"^0.8.21\",\"target\":\"cfg(any(all(target_arch = \\\"arm\\\", target_pointer_width = \\\"32\\\"), target_arch = \\\"mips\\\", target_arch = \\\"powerpc\\\"))\"},{\"name\":\"deflate64\",\"optional\":true,\"req\":\"^0.1.9\"},{\"default_features\":false,\"name\":\"displaydoc\",\"req\":\"^0.2\"},{\"default_features\":false,\"name\":\"flate2\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"wasm_js\",\"std\"],\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.3.1\"},{\"features\":[\"wasm_js\",\"std\"],\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.3.1\"},{\"features\":[\"reset\"],\"name\":\"hmac\",\"optional\":true,\"req\":\"^0.12\"},{\"name\":\"indexmap\",\"req\":\"^2\"},{\"default_features\":false,\"name\":\"lzma-rs\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"memchr\",\"req\":\"^2.7\"},{\"default_features\":false,\"name\":\"nt-time\",\"optional\":true,\"req\":\"^0.10.6\"},{\"name\":\"pbkdf2\",\"optional\":true,\"req\":\"^0.12\"},{\"name\":\"sha1\",\"optional\":true,\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.15\"},{\"name\":\"thiserror\",\"req\":\"^2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"time\",\"optional\":true,\"req\":\"^0.3.37\"},{\"default_features\":false,\"features\":[\"formatting\",\"macros\"],\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.37\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5\"},{\"name\":\"xz2\",\"optional\":true,\"req\":\"^0.1.7\"},{\"features\":[\"zeroize_derive\"],\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1.8\"},{\"name\":\"zopfli\",\"optional\":true,\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"zstd\",\"optional\":true,\"req\":\"^0.13\"}],\"features\":{\"_all-features\":[],\"_deflate-any\":[],\"aes-crypto\":[\"aes\",\"constant_time_eq\",\"hmac\",\"pbkdf2\",\"sha1\",\"getrandom\",\"zeroize\"],\"chrono\":[\"chrono/default\"],\"default\":[\"aes-crypto\",\"bzip2\",\"deflate64\",\"deflate\",\"lzma\",\"time\",\"zstd\",\"xz\"],\"deflate\":[\"flate2/rust_backend\",\"deflate-zopfli\",\"deflate-flate2\"],\"deflate-flate2\":[\"_deflate-any\"],\"deflate-miniz\":[\"deflate\",\"deflate-flate2\"],\"deflate-zlib\":[\"flate2/zlib\",\"deflate-flate2\"],\"deflate-zlib-ng\":[\"flate2/zlib-ng\",\"deflate-flate2\"],\"deflate-zopfli\":[\"zopfli\",\"_deflate-any\"],\"lzma\":[\"lzma-rs/stream\"],\"nt-time\":[\"dep:nt-time\"],\"unreserved\":[],\"xz\":[\"dep:xz2\"]}}", + "zlib-rs_0.6.3": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"crc32fast\",\"req\":\"^1.3.2\"},{\"kind\":\"dev\",\"name\":\"memoffset\",\"req\":\"^0.9.1\"},{\"default_features\":false,\"name\":\"quickcheck\",\"optional\":true,\"req\":\"^1.0.3\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"}],\"features\":{\"ZLIB_DEBUG\":[],\"__internal-api\":[],\"__internal-fuzz\":[\"arbitrary\"],\"__internal-fuzz-disable-checksum\":[],\"__internal-test\":[\"quickcheck\"],\"avx512\":[\"vpclmulqdq\"],\"c-allocator\":[],\"default\":[\"std\",\"c-allocator\"],\"rust-allocator\":[],\"std\":[\"rust-allocator\"],\"vpclmulqdq\":[]}}", "zmij_1.0.19": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\",\"target\":\"cfg(not(miri))\"},{\"name\":\"no-panic\",\"optional\":true,\"req\":\"^0.1.36\"},{\"kind\":\"dev\",\"name\":\"num-bigint\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"num-integer\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"num_cpus\",\"req\":\"^1.8\"},{\"kind\":\"dev\",\"name\":\"opt-level\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"ryu\",\"req\":\"^1\"}],\"features\":{}}", "zmij_1.0.21": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\",\"target\":\"cfg(not(miri))\"},{\"name\":\"no-panic\",\"optional\":true,\"req\":\"^0.1.36\"},{\"kind\":\"dev\",\"name\":\"num-bigint\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"num-integer\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"num_cpus\",\"req\":\"^1.8\"},{\"kind\":\"dev\",\"name\":\"opt-level\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"ryu\",\"req\":\"^1\"}],\"features\":{}}", "zoneinfo64_0.2.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"calendrical_calculations\",\"req\":\"^0.2.3\"},{\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"chrono-tz\",\"req\":\"^0.10.4\"},{\"default_features\":false,\"name\":\"icu_locale_core\",\"req\":\"^2.1.0\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.14.0\"},{\"default_features\":false,\"features\":[\"tzdb-bundle-always\",\"std\"],\"kind\":\"dev\",\"name\":\"jiff\",\"req\":\"^0.2.15\"},{\"default_features\":false,\"name\":\"potential_utf\",\"req\":\"^0.1.3\"},{\"default_features\":false,\"name\":\"resb\",\"req\":\"^0.1.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0.220\"}],\"features\":{\"chrono\":[\"dep:chrono\"]}}", diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 15ca924a8b..18c26e6a14 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -616,7 +616,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 1.1.3", + "rustix 1.1.4", "slab", "windows-sys 0.61.2", ] @@ -647,7 +647,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix 1.1.3", + "rustix 1.1.4", ] [[package]] @@ -673,7 +673,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 1.1.3", + "rustix 1.1.4", "signal-hook-registry", "slab", "windows-sys 0.61.2", @@ -1349,6 +1349,15 @@ dependencies = [ "error-code", ] +[[package]] +name = "clru" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197fd99cb113a8d5d9b6376f3aa817f32c1078f2343b714fff7d2ca44fdf67d5" +dependencies = [ + "hashbrown 0.16.1", +] + [[package]] name = "cmake" version = "0.1.57" @@ -2272,16 +2281,20 @@ dependencies = [ name = "codex-git-utils" version = "0.0.0" dependencies = [ + "anyhow", "assert_matches", + "chrono", "codex-exec-server", "codex-protocol", "codex-utils-absolute-path", "futures", + "gix", "once_cell", "pretty_assertions", "regex", "schemars 0.8.22", "serde", + "similar", "tempfile", "thiserror 2.0.18", "tokio", @@ -3979,6 +3992,20 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "dasp_sample" version = "0.11.0" @@ -4615,6 +4642,16 @@ dependencies = [ "once_cell", ] +[[package]] +name = "faster-hex" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" +dependencies = [ + "heapless", + "serde", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -4651,7 +4688,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", - "rustix 1.1.3", + "rustix 1.1.4", "windows-sys 0.59.0", ] @@ -4681,6 +4718,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "filetime" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +dependencies = [ + "cfg-if", + "libc", + "libredox", +] + [[package]] name = "find-crate" version = "0.6.3" @@ -5049,7 +5097,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ - "rustix 1.1.3", + "rustix 1.1.4", "windows-link", ] @@ -5118,6 +5166,861 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "gix" +version = "0.81.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0473c64d9ccbcfb9953a133b47c8b9a335b87ac6c52b983ee4b03d49000b0f3f" +dependencies = [ + "gix-actor", + "gix-archive", + "gix-blame", + "gix-commitgraph", + "gix-config", + "gix-date", + "gix-diff", + "gix-dir", + "gix-discover", + "gix-error", + "gix-features", + "gix-filter", + "gix-fs", + "gix-glob", + "gix-hash", + "gix-hashtable", + "gix-index", + "gix-lock", + "gix-merge", + "gix-negotiate", + "gix-object", + "gix-odb", + "gix-pack", + "gix-path", + "gix-protocol", + "gix-ref", + "gix-refspec", + "gix-revision", + "gix-revwalk", + "gix-sec", + "gix-shallow", + "gix-status", + "gix-submodule", + "gix-tempfile", + "gix-trace", + "gix-traverse", + "gix-url", + "gix-utils", + "gix-validate", + "gix-worktree", + "gix-worktree-state", + "gix-worktree-stream", + "nonempty", + "smallvec", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-actor" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e5e5b518339d5e6718af108fd064d4e9ba33caf728cf487352873d76411df35" +dependencies = [ + "bstr", + "gix-date", + "gix-error", + "winnow", +] + +[[package]] +name = "gix-archive" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "651c99be11aac9b303483193ae50b45eb6e094da4f5ed797019b03948f51aad6" +dependencies = [ + "bstr", + "gix-date", + "gix-error", + "gix-object", + "gix-worktree-stream", +] + +[[package]] +name = "gix-attributes" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c233d6eaa098c0ca5ce03236fd7a96e27f1abe72fad74b46003fbd11fe49563c" +dependencies = [ + "bstr", + "gix-glob", + "gix-path", + "gix-quote", + "gix-trace", + "kstring", + "smallvec", + "thiserror 2.0.18", + "unicode-bom", +] + +[[package]] +name = "gix-bitmap" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7add20f40d060db8c9b1314d499bac6ed7480f33eb113ce3e1cf5d6ff85d989" +dependencies = [ + "gix-error", +] + +[[package]] +name = "gix-blame" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77aaf9f7348f4da3ebfbfbbc35fa0d07155d98377856198dde6f695fd648705" +dependencies = [ + "gix-commitgraph", + "gix-date", + "gix-diff", + "gix-error", + "gix-hash", + "gix-object", + "gix-revwalk", + "gix-trace", + "gix-traverse", + "gix-worktree", + "smallvec", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-chunk" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1096b6608fbe5d27fb4984e20f992b4e76fb8c613f6acb87d07c5831b53a6959" +dependencies = [ + "gix-error", +] + +[[package]] +name = "gix-command" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b849c65a609f50d02f8a2774fe371650b3384a743c79c2a070ce0da49b7fb7da" +dependencies = [ + "bstr", + "gix-path", + "gix-quote", + "gix-trace", + "shell-words", +] + +[[package]] +name = "gix-commitgraph" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3196655fd1443f3c58a48c114aa480be3e4e87b393d7292daaa0d543862eb445" +dependencies = [ + "bstr", + "gix-chunk", + "gix-error", + "gix-hash", + "memmap2", + "nonempty", +] + +[[package]] +name = "gix-config" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08939b4c4ed7a663d0e64be9e1e9bdf23a1fb4fcee1febdf449f12229542e50d" +dependencies = [ + "bstr", + "gix-config-value", + "gix-features", + "gix-glob", + "gix-path", + "gix-ref", + "gix-sec", + "memchr", + "smallvec", + "thiserror 2.0.18", + "unicode-bom", + "winnow", +] + +[[package]] +name = "gix-config-value" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "441a300bc3645a1f45cba495b9175f90f47256ce43f2ee161da0031e3ac77c92" +dependencies = [ + "bitflags 2.10.0", + "bstr", + "gix-path", + "libc", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-date" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39acf819aa9fee65e4838a2eec5cb2506e47ebb89e02a5ab9918196e491571ea" +dependencies = [ + "bstr", + "gix-error", + "itoa", + "jiff", + "smallvec", +] + +[[package]] +name = "gix-diff" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88f3b3475e5d3877d7c30c40827cc2441936ce890efc226e5ba4afe3a7ae33f0" +dependencies = [ + "bstr", + "gix-command", + "gix-filter", + "gix-fs", + "gix-hash", + "gix-object", + "gix-path", + "gix-tempfile", + "gix-trace", + "gix-traverse", + "gix-worktree", + "imara-diff 0.1.8", + "imara-diff 0.2.0", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-dir" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da4604a360988f0ba8efe6f90093ca5a844f4a7f8e1a3dcda501ec44e600ea9" +dependencies = [ + "bstr", + "gix-discover", + "gix-fs", + "gix-ignore", + "gix-index", + "gix-object", + "gix-path", + "gix-pathspec", + "gix-trace", + "gix-utils", + "gix-worktree", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-discover" +version = "0.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c65bd3330fe0cb9d40d875bf862fd5e8ad6fa4164ddbc4842fbeb889c3f0b2c6" +dependencies = [ + "bstr", + "dunce", + "gix-fs", + "gix-path", + "gix-ref", + "gix-sec", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-error" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e86d01da904d4a9265def43bd42a18c5e6dc7000a73af512946ba14579c9fbd" +dependencies = [ + "bstr", +] + +[[package]] +name = "gix-features" +version = "0.46.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "752493cd4b1d5eaaa0138a7493f65c96863fefa990fc021e0e519579e389ab20" +dependencies = [ + "bytes", + "crc32fast", + "gix-path", + "gix-trace", + "gix-utils", + "libc", + "once_cell", + "prodash", + "thiserror 2.0.18", + "walkdir", + "zlib-rs", +] + +[[package]] +name = "gix-filter" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d37598282a6566da6fb52667570c7fe0aedcb122ac886724a9e62a2180523e35" +dependencies = [ + "bstr", + "encoding_rs", + "gix-attributes", + "gix-command", + "gix-hash", + "gix-object", + "gix-packetline", + "gix-path", + "gix-quote", + "gix-trace", + "gix-utils", + "smallvec", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-fs" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a964b4aec683eb0bacb87533defa80805bb4768056371a47ab38b00a2d377b72" +dependencies = [ + "bstr", + "fastrand", + "gix-features", + "gix-path", + "gix-utils", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-glob" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b03e6cd88cc0dc1eafa1fddac0fb719e4e74b6ea58dd016e71125fde4a326bee" +dependencies = [ + "bitflags 2.10.0", + "bstr", + "gix-features", + "gix-path", +] + +[[package]] +name = "gix-hash" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fb896a02d9ab96fa518475a5f30ad3952010f801a8de5840f633f4a6b985dfb" +dependencies = [ + "faster-hex", + "gix-features", + "sha1-checked", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-hashtable" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2664216fc5e89b51e756a4a3ac676315602ce2dac07acf1da959a22038d69b33" +dependencies = [ + "gix-hash", + "hashbrown 0.16.1", + "parking_lot", +] + +[[package]] +name = "gix-ignore" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f915dcf6911e3027537166d34e13f0fe101ed12225178d2ae29cd1272cff26" +dependencies = [ + "bstr", + "gix-glob", + "gix-path", + "gix-trace", + "unicode-bom", +] + +[[package]] +name = "gix-index" +version = "0.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bae54ab14e4e74d5dda60b82ea7afad7c8eb3be68283d6d5f29bd2e6d47fff7" +dependencies = [ + "bitflags 2.10.0", + "bstr", + "filetime", + "fnv", + "gix-bitmap", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-traverse", + "gix-utils", + "gix-validate", + "hashbrown 0.16.1", + "itoa", + "libc", + "memmap2", + "rustix 1.1.4", + "smallvec", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-lock" +version = "21.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054fbd0989700c69dc5aa80bc66944f05df1e15aa7391a9e42aca7366337905f" +dependencies = [ + "gix-tempfile", + "gix-utils", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-merge" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4606747466512d22c2dffc019142e1941238f543987ea51353c938cca80c500" +dependencies = [ + "bstr", + "gix-command", + "gix-diff", + "gix-filter", + "gix-fs", + "gix-hash", + "gix-index", + "gix-object", + "gix-path", + "gix-quote", + "gix-revision", + "gix-revwalk", + "gix-tempfile", + "gix-trace", + "gix-worktree", + "imara-diff 0.1.8", + "nonempty", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-negotiate" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea064c7595eea08fdd01c70748af747d9acc40f727b61f4c8a2145a5c5fc28c" +dependencies = [ + "bitflags 2.10.0", + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-object", + "gix-revwalk", +] + +[[package]] +name = "gix-object" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cafb802bb688a7c1e69ef965612ff5ff859f046bfb616377e4a0ba4c01e43d47" +dependencies = [ + "bstr", + "gix-actor", + "gix-date", + "gix-features", + "gix-hash", + "gix-hashtable", + "gix-path", + "gix-utils", + "gix-validate", + "itoa", + "smallvec", + "thiserror 2.0.18", + "winnow", +] + +[[package]] +name = "gix-odb" +version = "0.78.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24833ae9323b4f7079575fb9f961cf9c414b0afbec428a536ab8e7dd93bc002b" +dependencies = [ + "arc-swap", + "gix-features", + "gix-fs", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-pack", + "gix-path", + "gix-quote", + "parking_lot", + "tempfile", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-pack" +version = "0.68.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3484119cd19859d7d7639413c27e192478fa354d3f4ff5f7e3c041e8040f0f4" +dependencies = [ + "clru", + "gix-chunk", + "gix-error", + "gix-features", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-path", + "memmap2", + "smallvec", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-packetline" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be19313dcdb7dff75a3ce2f99be00878458295bcc3b6c7f0005591597573345c" +dependencies = [ + "bstr", + "faster-hex", + "gix-trace", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-path" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c31d4373bda7fab9eb01822927b55185a378d6e1bf737e0a54c743ad806658" +dependencies = [ + "bstr", + "gix-trace", + "gix-validate", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-pathspec" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89611f13544ca5ebeb68a502673814ef57200df60c24a61c2ce7b96f612f08b" +dependencies = [ + "bitflags 2.10.0", + "bstr", + "gix-attributes", + "gix-config-value", + "gix-glob", + "gix-path", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-protocol" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f38666350736b5877c79f57ddae02bde07a4ce186d889adc391e831cddcbe76" +dependencies = [ + "bstr", + "gix-date", + "gix-features", + "gix-hash", + "gix-ref", + "gix-shallow", + "gix-transport", + "gix-utils", + "maybe-async", + "nonempty", + "thiserror 2.0.18", + "winnow", +] + +[[package]] +name = "gix-quote" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68533db71259c8776dd4e770d2b7b98696213ecdc1f5c9e3507119e274e0c578" +dependencies = [ + "bstr", + "gix-error", + "gix-utils", +] + +[[package]] +name = "gix-ref" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2159978abb99b7027c8579d15211e262ef0ef2594d5cecb3334fbcbdfe2997c" +dependencies = [ + "gix-actor", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-path", + "gix-tempfile", + "gix-utils", + "gix-validate", + "memmap2", + "thiserror 2.0.18", + "winnow", +] + +[[package]] +name = "gix-refspec" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc806ee13f437428f8a1ba4c72ecfaa3f20e14f5f0d4c2bc17d0b33e794aa6ac" +dependencies = [ + "bstr", + "gix-error", + "gix-glob", + "gix-hash", + "gix-revision", + "gix-validate", + "smallvec", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-revision" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c08f1ec5d1e6a524f8ba291c41f0ccaef64e48ed0e8cf790b3461cae45f6d3d" +dependencies = [ + "bitflags 2.10.0", + "bstr", + "gix-commitgraph", + "gix-date", + "gix-error", + "gix-hash", + "gix-object", + "gix-revwalk", + "gix-trace", + "nonempty", +] + +[[package]] +name = "gix-revwalk" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4b2b87772b21ca449249e86d32febadba5cba32b0fcce804ab9cefc6f2111c" +dependencies = [ + "gix-commitgraph", + "gix-date", + "gix-error", + "gix-hash", + "gix-hashtable", + "gix-object", + "smallvec", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-sec" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf82ae037de9c62850ce67beaa92ec8e3e17785ea307cdde7618edc215603b4f" +dependencies = [ + "bitflags 2.10.0", + "gix-path", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "gix-shallow" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf60711c9083b2364b3fac8a352444af76b17201f3682fdebe74fa66d89a772" +dependencies = [ + "bstr", + "gix-hash", + "gix-lock", + "nonempty", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-status" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d6c598e3fdbc352fba1c5ba7e709e69402fafbc44d9295edad2e3c4738996b" +dependencies = [ + "bstr", + "filetime", + "gix-diff", + "gix-dir", + "gix-features", + "gix-filter", + "gix-fs", + "gix-hash", + "gix-index", + "gix-object", + "gix-path", + "gix-pathspec", + "gix-worktree", + "portable-atomic", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-submodule" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5c3929c5e6821f651d35e8420f72fea3cfafe9fc1e928a61e718b462c72a5" +dependencies = [ + "bstr", + "gix-config", + "gix-path", + "gix-pathspec", + "gix-refspec", + "gix-url", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-tempfile" +version = "21.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22227f6b203f511ff451c33c89899e87e4f571fc596b06f68e6e613a6508528" +dependencies = [ + "dashmap", + "gix-fs", + "libc", + "parking_lot", + "tempfile", +] + +[[package]] +name = "gix-trace" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f69a13643b8437d4ca6845e08143e847a36ca82903eed13303475d0ae8b162e0" + +[[package]] +name = "gix-transport" +version = "0.55.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a521e39c6235ce63ed6c001e2dd79818c830b82c3b7b59247ee7b229c39ec9bb" +dependencies = [ + "bstr", + "gix-command", + "gix-features", + "gix-packetline", + "gix-quote", + "gix-sec", + "gix-url", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-traverse" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "963dc2afcdb611092aa587c3f9365e749ac0a0892ff27662dbc75f26c953fbec" +dependencies = [ + "bitflags 2.10.0", + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-revwalk", + "smallvec", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-url" +version = "0.35.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d28e8af3d42581190da884f013caf254d2fd4d6ab102408f08d21bfa11de6c8d" +dependencies = [ + "bstr", + "gix-path", + "percent-encoding", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-utils" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "befcdbdfb1238d2854591f760a48711bed85e72d80a10e8f2f93f656746ef7c5" +dependencies = [ + "bstr", + "fastrand", + "unicode-normalization", +] + +[[package]] +name = "gix-validate" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec1eff98d91941f47766367cba1be746bab662bad761d9891ae6f7882f7840b" +dependencies = [ + "bstr", +] + +[[package]] +name = "gix-worktree" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6bd5830cbc43c9c00918b826467d2afad685b195cb82329cde2b2d116d2c578" +dependencies = [ + "bstr", + "gix-attributes", + "gix-fs", + "gix-glob", + "gix-hash", + "gix-ignore", + "gix-index", + "gix-object", + "gix-path", + "gix-validate", +] + +[[package]] +name = "gix-worktree-state" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644a1681f96e1be43c2a8384337d9d220e7624f50db54beda70997052aebf707" +dependencies = [ + "bstr", + "gix-features", + "gix-filter", + "gix-fs", + "gix-index", + "gix-object", + "gix-path", + "gix-worktree", + "io-close", + "thiserror 2.0.18", +] + +[[package]] +name = "gix-worktree-stream" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e3fb70a1f650a5cec7d5b8d10d6d6fe86daf3cf15bde08ba0c70988a2932c3" +dependencies = [ + "gix-attributes", + "gix-error", + "gix-features", + "gix-filter", + "gix-fs", + "gix-hash", + "gix-object", + "gix-path", + "gix-traverse", + "parking_lot", +] + [[package]] name = "glib" version = "0.21.5" @@ -5231,6 +6134,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -5302,6 +6214,16 @@ dependencies = [ "http 1.4.0", ] +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.4.1" @@ -5877,6 +6799,25 @@ dependencies = [ "quick-error", ] +[[package]] +name = "imara-diff" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17d34b7d42178945f775e84bc4c36dde7c1c6cdfea656d3354d009056f2bb3d2" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "imara-diff" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f01d462f766df78ab820dd06f5eb700233c51f0f4c2e846520eaf4ba6aa5c5c" +dependencies = [ + "hashbrown 0.15.5", + "memchr", +] + [[package]] name = "impl-more" version = "0.1.9" @@ -6023,6 +6964,16 @@ dependencies = [ "rustversion", ] +[[package]] +name = "io-close" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "io_tee" version = "0.1.1" @@ -6130,28 +7081,45 @@ checksum = "84de9d95a6d2547d9b77ee3f25fa0ee32e3c3a6484d47a55adebc0439c077992" [[package]] name = "jiff" -version = "0.2.18" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" dependencies = [ "jiff-static", + "jiff-tzdb-platform", "log", "portable-atomic", "portable-atomic-util", "serde_core", + "windows-sys 0.61.2", ] [[package]] name = "jiff-static" -version = "0.2.18" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" dependencies = [ "proc-macro2", "quote", "syn 2.0.114", ] +[[package]] +name = "jiff-tzdb" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c900ef84826f1338a557697dc8fc601df9ca9af4ac137c7fb61d4c6f2dfd3076" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + [[package]] name = "jni" version = "0.21.1" @@ -6247,6 +7215,15 @@ dependencies = [ "libc", ] +[[package]] +name = "kstring" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" +dependencies = [ + "static_assertions", +] + [[package]] name = "lalrpop" version = "0.19.12" @@ -6426,9 +7403,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" @@ -6615,6 +7592,17 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3eede3bdf92f3b4f9dc04072a9ce5ab557d5ec9038773bf9ffcd5588b3cc05b" +[[package]] +name = "maybe-async" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "mcp_test_support" version = "0.0.0" @@ -6657,6 +7645,15 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.6.5" @@ -6872,6 +7869,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "nonempty" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9737e026353e5cd0736f98eddae28665118eb6f6600902a7f50db585621fecb6" + [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -7864,7 +8867,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.1.3", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -8051,6 +9054,15 @@ dependencies = [ "windows 0.62.2", ] +[[package]] +name = "prodash" +version = "31.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "962200e2d7d551451297d9fdce85138374019ada198e30ea9ede38034e27604c" +dependencies = [ + "parking_lot", +] + [[package]] name = "proptest" version = "1.9.0" @@ -9115,14 +10127,14 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags 2.10.0", "errno", "libc", - "linux-raw-sys 0.11.0", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] @@ -9803,6 +10815,16 @@ dependencies = [ "digest", ] +[[package]] +name = "sha1-checked" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" +dependencies = [ + "digest", + "sha1", +] + [[package]] name = "sha1_smol" version = "1.0.1" @@ -10574,14 +11596,14 @@ checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[package]] name = "tempfile" -version = "3.24.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", "getrandom 0.3.4", "once_cell", - "rustix 1.1.3", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -10644,7 +11666,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ - "rustix 1.1.3", + "rustix 1.1.4", "windows-sys 0.60.2", ] @@ -11499,6 +12521,12 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" +[[package]] +name = "unicode-bom" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" + [[package]] name = "unicode-ident" version = "1.0.22" @@ -11859,7 +12887,7 @@ checksum = "fee64194ccd96bf648f42a65a7e589547096dfa702f7cadef84347b66ad164f9" dependencies = [ "cc", "downcast-rs", - "rustix 1.1.3", + "rustix 1.1.4", "smallvec", "wayland-sys", ] @@ -11871,7 +12899,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e6faa537fbb6c186cb9f1d41f2f811a4120d1b57ec61f50da451a0c5122bec" dependencies = [ "bitflags 2.10.0", - "rustix 1.1.3", + "rustix 1.1.4", "wayland-backend", "wayland-scanner", ] @@ -12037,7 +13065,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" dependencies = [ "env_home", - "rustix 1.1.3", + "rustix 1.1.4", "winsafe", ] @@ -12702,7 +13730,7 @@ dependencies = [ "libc", "log", "os_pipe", - "rustix 1.1.3", + "rustix 1.1.4", "thiserror 2.0.18", "tree_magic_mini", "wayland-backend", @@ -12724,7 +13752,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" dependencies = [ "gethostname", - "rustix 1.1.3", + "rustix 1.1.4", "x11rb-protocol", ] @@ -13038,6 +14066,12 @@ dependencies = [ "zstd 0.13.3", ] +[[package]] +name = "zlib-rs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" + [[package]] name = "zmij" version = "1.0.19" diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index b83674e591..75e54abfba 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -244,6 +244,7 @@ env_logger = "0.11.9" eventsource-stream = "0.2.3" futures = { version = "0.3", default-features = false } gethostname = "1.1.0" +gix = { version = "0.81.0", default-features = false, features = ["sha1"] } glob = "0.3" globset = "0.4" hmac = "0.12.1" diff --git a/codex-rs/git-utils/Cargo.toml b/codex-rs/git-utils/Cargo.toml index 5500546db7..0154cfc032 100644 --- a/codex-rs/git-utils/Cargo.toml +++ b/codex-rs/git-utils/Cargo.toml @@ -9,14 +9,18 @@ readme = "README.md" workspace = true [dependencies] +anyhow = { workspace = true } +chrono = { workspace = true } codex-exec-server = { workspace = true } codex-protocol = { workspace = true } codex-utils-absolute-path = { workspace = true } futures = { workspace = true, features = ["alloc"] } +gix = { workspace = true } once_cell = { workspace = true } regex = "1" schemars = { workspace = true } serde = { workspace = true, features = ["derive"] } +similar = { workspace = true } tempfile = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["macros", "process", "rt", "time"] } diff --git a/codex-rs/git-utils/README.md b/codex-rs/git-utils/README.md index bd24b72b7a..1fd1051e3b 100644 --- a/codex-rs/git-utils/README.md +++ b/codex-rs/git-utils/README.md @@ -1,7 +1,11 @@ # codex-git-utils Helpers for interacting with git, including patch application and worktree -snapshot utilities. +snapshot utilities. The crate also exposes a lightweight baseline API for +internal directories that use git only as a resettable diff mechanism: +`reset_git_repository` replaces `root/.git` with a fresh one-commit baseline, +and `diff_since_latest_init` returns structured file changes plus a unified +diff from that baseline to the current directory contents. ```rust,no_run use std::path::Path; diff --git a/codex-rs/git-utils/src/baseline.rs b/codex-rs/git-utils/src/baseline.rs new file mode 100644 index 0000000000..5239598a2a --- /dev/null +++ b/codex-rs/git-utils/src/baseline.rs @@ -0,0 +1,645 @@ +use anyhow::Context; +use gix::hash::ObjectId; +use gix::objs::Tree; +use gix::objs::tree::Entry; +use gix::objs::tree::EntryKind; +use gix::objs::tree::EntryMode; +use similar::TextDiff; +use std::collections::BTreeMap; +use std::ffi::OsStr; +use std::fs; +use std::path::Path; +use std::path::PathBuf; +use tokio::task; + +const BASELINE_COMMIT_MESSAGE: &str = + "Initialize Codex git baseline\n\nCo-authored-by: Codex "; + +/// File-level change status between a git baseline and the current directory. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GitBaselineChangeStatus { + Added, + Modified, + Deleted, +} + +impl GitBaselineChangeStatus { + /// Returns the short git-style status label for this change. + pub fn label(self) -> &'static str { + match self { + GitBaselineChangeStatus::Added => "A", + GitBaselineChangeStatus::Modified => "M", + GitBaselineChangeStatus::Deleted => "D", + } + } +} + +/// One changed file between a git baseline and the current directory. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GitBaselineChange { + pub status: GitBaselineChangeStatus, + pub path: String, +} + +/// Structured diff from the latest git baseline reset to the current directory. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GitBaselineDiff { + pub changes: Vec, + pub unified_diff: String, +} + +impl GitBaselineDiff { + pub fn has_changes(&self) -> bool { + !self.changes.is_empty() + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct GitBaselineFileEntry { + oid: ObjectId, + mode: EntryMode, +} + +/// Replaces any existing `.git` metadata in `root` with a fresh one-commit baseline. +/// +/// This is intentionally destructive for `root/.git`. It is meant for internal directories where +/// git is used only as a baseline/diff implementation detail, not for user repositories. +pub async fn reset_git_repository(root: &Path) -> anyhow::Result<()> { + let root = root.to_path_buf(); + task::spawn_blocking(move || { + fs::create_dir_all(&root) + .with_context(|| format!("create git baseline root {}", root.display()))?; + remove_git_metadata(&root)?; + let repo = gix::init(&root).with_context(|| format!("init git repo {}", root.display()))?; + commit_current_tree(&repo, BASELINE_COMMIT_MESSAGE)?; + anyhow::Ok(()) + }) + .await? +} + +/// Returns the diff between the latest baseline reset and the current directory contents. +pub async fn diff_since_latest_init(root: &Path) -> anyhow::Result { + let root = root.to_path_buf(); + task::spawn_blocking(move || { + let repo = gix::open(&root).with_context(|| format!("open git repo {}", root.display()))?; + let head_entries = head_file_entries(&repo)?; + let current_entries = current_file_entries(&repo, &root)?; + let changes = diff_entries(&head_entries, ¤t_entries); + let unified_diff = + render_unified_diff(&repo, &root, &head_entries, ¤t_entries, &changes)?; + Ok(GitBaselineDiff { + changes, + unified_diff, + }) + }) + .await? +} + +fn remove_git_metadata(root: &Path) -> anyhow::Result<()> { + let git_path = root.join(".git"); + let metadata = match fs::symlink_metadata(&git_path) { + Ok(metadata) => metadata, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(()), + Err(err) => return Err(err).with_context(|| format!("stat {}", git_path.display())), + }; + + if metadata.file_type().is_dir() && !metadata.file_type().is_symlink() { + fs::remove_dir_all(&git_path).with_context(|| format!("remove {}", git_path.display())) + } else { + fs::remove_file(&git_path).with_context(|| format!("remove {}", git_path.display())) + } +} + +fn commit_current_tree(repo: &gix::Repository, message: &str) -> anyhow::Result<()> { + let root = repo + .workdir() + .context("git baseline repo must have a worktree")?; + let tree_id = write_tree(repo, root)?; + let signature = codex_signature(); + let mut time = gix::date::parse::TimeBuf::default(); + let signature_ref = signature.to_ref(&mut time); + repo.commit_as( + signature_ref, + signature_ref, + "HEAD", + message, + tree_id, + Vec::::new(), + ) + .context("commit git baseline repo")?; + Ok(()) +} + +fn codex_signature() -> gix::actor::Signature { + gix::actor::Signature { + name: "Codex".into(), + email: "noreply@openai.com".into(), + time: gix::date::Time { + seconds: chrono::Utc::now().timestamp(), + offset: 0, + }, + } +} + +fn write_tree(repo: &gix::Repository, dir: &Path) -> anyhow::Result { + let mut entries = Vec::new(); + for entry in fs::read_dir(dir).with_context(|| format!("read {}", dir.display()))? { + let entry = entry?; + let path = entry.path(); + let file_name = entry.file_name(); + if file_name == OsStr::new(".git") { + continue; + } + + let file_type = entry.file_type()?; + if file_type.is_dir() { + let oid = write_tree(repo, &path)?; + let tree = repo + .find_tree(oid) + .with_context(|| format!("load tree {}", path.display()))?; + if tree.decode()?.entries.is_empty() { + continue; + } + entries.push(Entry { + mode: EntryKind::Tree.into(), + filename: os_str_to_bstring(&file_name), + oid, + }); + } else if file_type.is_file() { + let bytes = fs::read(&path).with_context(|| format!("read {}", path.display()))?; + let oid = repo + .write_blob(bytes) + .with_context(|| format!("write blob {}", path.display()))? + .detach(); + entries.push(Entry { + mode: file_mode(&path, EntryKind::Blob)?, + filename: os_str_to_bstring(&file_name), + oid, + }); + } else if file_type.is_symlink() { + let target = + fs::read_link(&path).with_context(|| format!("read symlink {}", path.display()))?; + let oid = repo + .write_blob(path_to_bytes(&target)) + .with_context(|| format!("write symlink blob {}", path.display()))? + .detach(); + entries.push(Entry { + mode: EntryKind::Link.into(), + filename: os_str_to_bstring(&file_name), + oid, + }); + } + } + + entries.sort(); + repo.write_object(&Tree { entries }) + .context("write tree object") + .map(gix::Id::detach) +} + +fn head_file_entries( + repo: &gix::Repository, +) -> anyhow::Result> { + let tree_id = repo.head_tree_id().context("load HEAD tree id")?; + let tree = repo.find_tree(tree_id.detach()).context("load HEAD tree")?; + let mut entries = BTreeMap::new(); + collect_tree_entries(repo, tree, PathBuf::new(), &mut entries)?; + Ok(entries) +} + +fn collect_tree_entries( + repo: &gix::Repository, + tree: gix::Tree<'_>, + prefix: PathBuf, + entries: &mut BTreeMap, +) -> anyhow::Result<()> { + for entry in tree.iter() { + let entry = entry?; + let file_name = bstr_to_path(entry.inner.filename); + let path = prefix.join(file_name); + if entry.inner.mode.is_tree() { + let tree = repo + .find_tree(entry.inner.oid.to_owned()) + .context("load child tree")?; + collect_tree_entries(repo, tree, path, entries)?; + } else { + entries.insert( + path_to_slash_string(&path), + GitBaselineFileEntry { + oid: entry.inner.oid.to_owned(), + mode: entry.inner.mode, + }, + ); + } + } + Ok(()) +} + +fn current_file_entries( + repo: &gix::Repository, + root: &Path, +) -> anyhow::Result> { + let mut entries = BTreeMap::new(); + collect_current_entries(repo, root, root, &mut entries)?; + Ok(entries) +} + +fn collect_current_entries( + repo: &gix::Repository, + root: &Path, + dir: &Path, + entries: &mut BTreeMap, +) -> anyhow::Result<()> { + for entry in fs::read_dir(dir).with_context(|| format!("read {}", dir.display()))? { + let entry = entry?; + let path = entry.path(); + if path.file_name() == Some(OsStr::new(".git")) { + continue; + } + + let file_type = entry.file_type()?; + if file_type.is_dir() { + collect_current_entries(repo, root, &path, entries)?; + } else if file_type.is_file() { + let bytes = fs::read(&path).with_context(|| format!("read {}", path.display()))?; + entries.insert( + relative_slash_path(root, &path)?, + GitBaselineFileEntry { + oid: blob_oid(repo, &bytes)?, + mode: file_mode(&path, EntryKind::Blob)?, + }, + ); + } else if file_type.is_symlink() { + let target = + fs::read_link(&path).with_context(|| format!("read symlink {}", path.display()))?; + entries.insert( + relative_slash_path(root, &path)?, + GitBaselineFileEntry { + oid: blob_oid(repo, &path_to_bytes(&target))?, + mode: EntryKind::Link.into(), + }, + ); + } + } + Ok(()) +} + +fn blob_oid(repo: &gix::Repository, bytes: &[u8]) -> anyhow::Result { + gix::objs::compute_hash(repo.object_hash(), gix::objs::Kind::Blob, bytes) + .context("compute git baseline blob oid") +} + +fn diff_entries( + head: &BTreeMap, + current: &BTreeMap, +) -> Vec { + let mut entries = Vec::new(); + for (path, entry) in current { + match head.get(path) { + None => entries.push(GitBaselineChange { + status: GitBaselineChangeStatus::Added, + path: path.clone(), + }), + Some(head_entry) if head_entry != entry => entries.push(GitBaselineChange { + status: GitBaselineChangeStatus::Modified, + path: path.clone(), + }), + Some(_) => {} + } + } + for path in head.keys() { + if !current.contains_key(path) { + entries.push(GitBaselineChange { + status: GitBaselineChangeStatus::Deleted, + path: path.clone(), + }); + } + } + entries.sort_by(|left, right| left.path.cmp(&right.path)); + entries +} + +fn render_unified_diff( + repo: &gix::Repository, + root: &Path, + head_entries: &BTreeMap, + current_entries: &BTreeMap, + changes: &[GitBaselineChange], +) -> anyhow::Result { + let mut rendered = String::new(); + for change in changes { + rendered.push_str(&render_change_diff( + repo, + root, + head_entries, + current_entries, + change, + )?); + } + Ok(rendered) +} + +fn render_change_diff( + repo: &gix::Repository, + root: &Path, + head_entries: &BTreeMap, + current_entries: &BTreeMap, + change: &GitBaselineChange, +) -> anyhow::Result { + let old_entry = head_entries.get(&change.path); + let new_entry = current_entries.get(&change.path); + let old_bytes = old_entry + .map(|entry| read_head_blob(repo, entry)) + .transpose() + .with_context(|| format!("read HEAD content for {}", change.path))?; + let new_bytes = new_entry + .map(|_| read_current_file_bytes(root, &change.path)) + .transpose() + .with_context(|| format!("read current content for {}", change.path))?; + + let old_text = String::from_utf8_lossy(old_bytes.as_deref().unwrap_or_default()); + let new_text = String::from_utf8_lossy(new_bytes.as_deref().unwrap_or_default()); + let old_header = if old_bytes.is_some() { + format!("a/{}", change.path) + } else { + "/dev/null".to_string() + }; + let new_header = if new_bytes.is_some() { + format!("b/{}", change.path) + } else { + "/dev/null".to_string() + }; + + let mut section = format!("diff --git a/{0} b/{0}\n", change.path); + match (old_entry, new_entry) { + (None, Some(entry)) => { + section.push_str(&format!("new file mode {}\n", mode_label(entry.mode))); + } + (Some(entry), None) => { + section.push_str(&format!("deleted file mode {}\n", mode_label(entry.mode))); + } + (Some(old), Some(new)) if old.mode != new.mode => { + section.push_str(&format!( + "old mode {}\nnew mode {}\n", + mode_label(old.mode), + mode_label(new.mode) + )); + } + (Some(_), Some(_)) => {} + (None, None) => return Ok(String::new()), + } + + let diff = TextDiff::from_lines(&old_text, &new_text) + .unified_diff() + .context_radius(3) + .header(&old_header, &new_header) + .to_string(); + section.push_str(&diff); + if !section.ends_with('\n') { + section.push('\n'); + } + Ok(section) +} + +fn read_head_blob(repo: &gix::Repository, entry: &GitBaselineFileEntry) -> anyhow::Result> { + let mut blob = repo.find_blob(entry.oid)?; + Ok(blob.take_data()) +} + +fn read_current_file_bytes(root: &Path, relative_path: &str) -> anyhow::Result> { + let path = root.join(relative_path); + let metadata = + fs::symlink_metadata(&path).with_context(|| format!("stat {}", path.display()))?; + if metadata.file_type().is_symlink() { + let target = + fs::read_link(&path).with_context(|| format!("read symlink {}", path.display()))?; + Ok(path_to_bytes(&target)) + } else { + fs::read(&path).with_context(|| format!("read {}", path.display())) + } +} + +fn mode_label(mode: EntryMode) -> &'static str { + match mode.kind() { + EntryKind::Blob => "100644", + EntryKind::BlobExecutable => "100755", + EntryKind::Link => "120000", + EntryKind::Tree => "040000", + EntryKind::Commit => "160000", + } +} + +#[cfg(unix)] +fn file_mode(path: &Path, default: EntryKind) -> anyhow::Result { + use std::os::unix::fs::PermissionsExt; + + let mode = fs::metadata(path)?.permissions().mode(); + Ok(if mode & 0o111 == 0 { + default.into() + } else { + EntryKind::BlobExecutable.into() + }) +} + +#[cfg(not(unix))] +fn file_mode(_path: &Path, default: EntryKind) -> anyhow::Result { + Ok(default.into()) +} + +#[cfg(unix)] +fn os_str_to_bstring(value: &OsStr) -> gix::bstr::BString { + use std::os::unix::ffi::OsStrExt; + + value.as_bytes().into() +} + +#[cfg(not(unix))] +fn os_str_to_bstring(value: &OsStr) -> gix::bstr::BString { + value.to_string_lossy().as_bytes().into() +} + +#[cfg(unix)] +fn path_to_bytes(path: &Path) -> Vec { + use std::os::unix::ffi::OsStrExt; + + path.as_os_str().as_bytes().to_vec() +} + +#[cfg(not(unix))] +fn path_to_bytes(path: &Path) -> Vec { + path.to_string_lossy().as_bytes().to_vec() +} + +fn bstr_to_path(value: &gix::bstr::BStr) -> PathBuf { + #[cfg(unix)] + { + use std::os::unix::ffi::OsStrExt; + + PathBuf::from(OsStr::from_bytes(value)) + } + #[cfg(not(unix))] + { + PathBuf::from(value.to_string()) + } +} + +fn relative_slash_path(root: &Path, path: &Path) -> anyhow::Result { + path.strip_prefix(root) + .with_context(|| format!("strip {} from {}", root.display(), path.display())) + .map(path_to_slash_string) +} + +fn path_to_slash_string(path: &Path) -> String { + path.components() + .map(|component| component.as_os_str().to_string_lossy()) + .collect::>() + .join("/") +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + use std::fs; + use tempfile::TempDir; + + #[tokio::test] + async fn reset_creates_fresh_baseline() { + let home = TempDir::new().expect("tempdir"); + let root = home.path().join("repo"); + fs::create_dir_all(&root).expect("create root"); + fs::write(root.join("MEMORY.md"), "baseline").expect("write memory"); + + reset_git_repository(&root).await.expect("reset repo"); + + assert!(root.join(".git").is_dir()); + let diff = diff_since_latest_init(&root).await.expect("diff"); + assert!(!diff.has_changes()); + assert_eq!(diff.unified_diff, ""); + } + + #[tokio::test] + async fn diff_reports_added_modified_and_deleted_files() { + let home = TempDir::new().expect("tempdir"); + let root = home.path().join("repo"); + fs::create_dir_all(root.join("rollout_summaries")).expect("create rollout summaries"); + fs::write(root.join("MEMORY.md"), "old").expect("write memory"); + fs::write( + root.join("rollout_summaries/deleted.md"), + "thread_id: 00000000-0000-4000-8000-000000000001\nimportant stale evidence\n", + ) + .expect("write rollout summary"); + reset_git_repository(&root).await.expect("reset repo"); + + fs::write(root.join("MEMORY.md"), "new").expect("update memory"); + fs::write(root.join("memory_summary.md"), "summary").expect("write summary"); + fs::remove_file(root.join("rollout_summaries/deleted.md")).expect("delete summary"); + + let diff = diff_since_latest_init(&root).await.expect("diff"); + assert_eq!( + diff.changes, + vec![ + GitBaselineChange { + status: GitBaselineChangeStatus::Modified, + path: "MEMORY.md".to_string(), + }, + GitBaselineChange { + status: GitBaselineChangeStatus::Added, + path: "memory_summary.md".to_string(), + }, + GitBaselineChange { + status: GitBaselineChangeStatus::Deleted, + path: "rollout_summaries/deleted.md".to_string(), + }, + ] + ); + assert!( + diff.unified_diff + .contains("diff --git a/MEMORY.md b/MEMORY.md") + ); + assert!(diff.unified_diff.contains("-old")); + assert!(diff.unified_diff.contains("+new")); + assert!( + diff.unified_diff + .contains("diff --git a/memory_summary.md b/memory_summary.md") + ); + assert!(diff.unified_diff.contains("+summary")); + assert!( + diff.unified_diff.contains( + "diff --git a/rollout_summaries/deleted.md b/rollout_summaries/deleted.md" + ) + ); + assert!(diff.unified_diff.contains("deleted file mode 100644")); + assert!( + diff.unified_diff + .contains("-thread_id: 00000000-0000-4000-8000-000000000001") + ); + assert!(diff.unified_diff.contains("-important stale evidence")); + } + + #[tokio::test] + async fn reset_drops_previous_history() { + let home = TempDir::new().expect("tempdir"); + let root = home.path().join("repo"); + fs::create_dir_all(&root).expect("create root"); + fs::write(root.join("MEMORY.md"), "old").expect("write memory"); + reset_git_repository(&root).await.expect("reset repo"); + + fs::write(root.join("MEMORY.md"), "new").expect("update memory"); + reset_git_repository(&root).await.expect("reset repo again"); + + let repo = gix::open(&root).expect("open repo"); + let head = repo.head_id().expect("head").detach(); + let commit = repo.find_commit(head).expect("find head commit"); + assert_eq!(commit.parent_ids().count(), 0); + let diff = diff_since_latest_init(&root).await.expect("diff"); + assert!(!diff.has_changes()); + } + + #[tokio::test] + async fn status_scan_does_not_write_added_file_blobs() { + let home = TempDir::new().expect("tempdir"); + let root = home.path().join("repo"); + fs::create_dir_all(&root).expect("create root"); + reset_git_repository(&root).await.expect("reset repo"); + let added_content = b"new uncommitted memory"; + fs::write(root.join("MEMORY.md"), added_content).expect("write memory"); + + let diff = diff_since_latest_init(&root).await.expect("diff"); + assert!(diff.has_changes()); + + let repo = gix::open(&root).expect("open repo"); + let added_oid = blob_oid(&repo, added_content).expect("compute added oid"); + assert!( + repo.find_blob(added_oid).is_err(), + "status scans should hash current files without writing loose git objects" + ); + } + + #[cfg(unix)] + #[tokio::test] + async fn reports_executable_bit_changes_as_modified() { + use std::os::unix::fs::PermissionsExt; + + let home = TempDir::new().expect("tempdir"); + let root = home.path().join("repo"); + fs::create_dir_all(&root).expect("create root"); + let path = root.join("MEMORY.md"); + fs::write(&path, "same content").expect("write memory"); + reset_git_repository(&root).await.expect("reset repo"); + let mut permissions = fs::metadata(&path).expect("stat memory").permissions(); + permissions.set_mode(permissions.mode() | 0o111); + fs::set_permissions(&path, permissions).expect("chmod memory"); + + let diff = diff_since_latest_init(&root).await.expect("diff"); + assert_eq!( + diff.changes, + vec![GitBaselineChange { + status: GitBaselineChangeStatus::Modified, + path: "MEMORY.md".to_string(), + }] + ); + assert!(diff.unified_diff.contains("old mode 100644")); + assert!(diff.unified_diff.contains("new mode 100755")); + } +} From b06fc8bd0d2cf3c090aaaf90420a41d7f2943954 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Tue, 21 Apr 2026 09:48:31 -0700 Subject: [PATCH 04/16] core: make test-log a dev dependency (#18846) The `test-log` crate is only used by `codex-core` tests, so it does not need to be part of the normal `codex-core` dependency graph. Keeping `test-log` in `dev-dependencies` removes it from normal `codex-core` builds and keeps the production dependency set a little smaller. Verification: - `cargo tree -p codex-core --edges normal --invert test-log` - `cargo check -p codex-core --lib` - `cargo test -p codex-core --lib` --- codex-rs/core/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index 6ef6741fd9..d1cca0ac32 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -104,7 +104,6 @@ sha2 = { workspace = true } shlex = { workspace = true } similar = { workspace = true } tempfile = { workspace = true } -test-log = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = [ "io-std", @@ -166,6 +165,7 @@ opentelemetry_sdk = { workspace = true, features = [ ] } serial_test = { workspace = true } tempfile = { workspace = true } +test-log = { workspace = true } tracing-opentelemetry = { workspace = true } tracing-subscriber = { workspace = true } tracing-test = { workspace = true, features = ["no-env-filter"] } From 48f82ca7c5f9a039b44ccf71765e1b2d4fb190f5 Mon Sep 17 00:00:00 2001 From: Ruslan Nigmatullin Date: Tue, 21 Apr 2026 10:08:42 -0700 Subject: [PATCH 05/16] app-server: define device key v2 protocol (#18428) ## Why Clients need a stable app-server protocol surface for enrolling a local device key, retrieving its public key, and producing a device-bound proof. The protocol reports `protectionClass` explicitly so clients can distinguish hardware-backed keys from an explicitly allowed OS-protected fallback. Signing uses a tagged `DeviceKeySignPayload` enum rather than arbitrary bytes so each signed statement is auditable at the API boundary. ## What changed - Added v2 JSON-RPC methods for `device/key/create`, `device/key/public`, and `device/key/sign`. - Added request/response types for device-key metadata, SPKI public keys, protection classes, and ECDSA signatures. - Added `DeviceKeyProtectionPolicy` with hardware-only default behavior and an explicit `allow_os_protected_nonextractable` option. - Added the initial `remoteControlClientConnection` signing payload variant. - Regenerated JSON Schema and TypeScript fixtures for app-server clients. ## Stack This is PR 1 of 4 in the device-key app-server stack. ## Validation - `just write-app-server-schema` - `cargo test -p codex-app-server-protocol` --- .../schema/json/ClientRequest.json | 280 +++++++++++++ .../schema/json/DynamicToolCallParams.json | 2 +- .../codex_app_server_protocol.schemas.json | 386 +++++++++++++++++- .../codex_app_server_protocol.v2.schemas.json | 380 +++++++++++++++++ .../schema/json/v2/DeviceKeyCreateParams.json | 39 ++ .../json/v2/DeviceKeyCreateResponse.json | 45 ++ .../schema/json/v2/DeviceKeyPublicParams.json | 14 + .../json/v2/DeviceKeyPublicResponse.json | 45 ++ .../schema/json/v2/DeviceKeySignParams.json | 165 ++++++++ .../schema/json/v2/DeviceKeySignResponse.json | 33 ++ .../schema/typescript/ClientRequest.ts | 5 +- .../typescript/v2/DeviceKeyAlgorithm.ts | 8 + .../typescript/v2/DeviceKeyCreateParams.ts | 13 + .../typescript/v2/DeviceKeyCreateResponse.ts | 14 + .../typescript/v2/DeviceKeyProtectionClass.ts | 8 + .../v2/DeviceKeyProtectionPolicy.ts | 8 + .../typescript/v2/DeviceKeyPublicParams.ts | 8 + .../typescript/v2/DeviceKeyPublicResponse.ts | 14 + .../typescript/v2/DeviceKeySignParams.ts | 9 + .../typescript/v2/DeviceKeySignPayload.ts | 54 +++ .../typescript/v2/DeviceKeySignResponse.ts | 18 + .../RemoteControlClientConnectionAudience.ts | 8 + .../RemoteControlClientEnrollmentAudience.ts | 8 + .../schema/typescript/v2/index.ts | 12 + .../src/protocol/common.rs | 12 + .../app-server-protocol/src/protocol/v2.rs | 333 +++++++++++++++ .../app-server/src/codex_message_processor.rs | 5 + 27 files changed, 1921 insertions(+), 5 deletions(-) create mode 100644 codex-rs/app-server-protocol/schema/json/v2/DeviceKeyCreateParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/DeviceKeyCreateResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/DeviceKeyPublicParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/DeviceKeyPublicResponse.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/DeviceKeySignParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/DeviceKeySignResponse.json create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyAlgorithm.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyCreateParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyCreateResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyProtectionClass.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyProtectionPolicy.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyPublicParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyPublicResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeySignParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeySignPayload.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeySignResponse.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/RemoteControlClientConnectionAudience.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/RemoteControlClientEnrollmentAudience.ts diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 43871d74a0..352cf02113 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -521,6 +521,200 @@ } ] }, + "DeviceKeyCreateParams": { + "description": "Create a controller-local device key with a random key id.", + "properties": { + "accountUserId": { + "type": "string" + }, + "clientId": { + "type": "string" + }, + "protectionPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/DeviceKeyProtectionPolicy" + }, + { + "type": "null" + } + ], + "description": "Defaults to `hardware_only` when omitted." + } + }, + "required": [ + "accountUserId", + "clientId" + ], + "type": "object" + }, + "DeviceKeyProtectionPolicy": { + "description": "Protection policy for creating or loading a controller-local device key.", + "enum": [ + "hardware_only", + "allow_os_protected_nonextractable" + ], + "type": "string" + }, + "DeviceKeyPublicParams": { + "description": "Fetch a controller-local device key public key by id.", + "properties": { + "keyId": { + "type": "string" + } + }, + "required": [ + "keyId" + ], + "type": "object" + }, + "DeviceKeySignParams": { + "description": "Sign an accepted structured payload with a controller-local device key.", + "properties": { + "keyId": { + "type": "string" + }, + "payload": { + "$ref": "#/definitions/DeviceKeySignPayload" + } + }, + "required": [ + "keyId", + "payload" + ], + "type": "object" + }, + "DeviceKeySignPayload": { + "description": "Structured payloads accepted by `device/key/sign`.", + "oneOf": [ + { + "description": "Payload bound to one remote-control controller websocket `/client` connection challenge.", + "properties": { + "accountUserId": { + "type": "string" + }, + "audience": { + "$ref": "#/definitions/RemoteControlClientConnectionAudience" + }, + "clientId": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "scopes": { + "description": "Must contain exactly `remote_control_controller_websocket`.", + "items": { + "type": "string" + }, + "type": "array" + }, + "sessionId": { + "description": "Backend-issued websocket session id that this proof authorizes.", + "type": "string" + }, + "targetOrigin": { + "description": "Origin of the backend endpoint that issued the challenge and will verify this proof.", + "type": "string" + }, + "targetPath": { + "description": "Websocket route path that this proof authorizes.", + "type": "string" + }, + "tokenExpiresAt": { + "description": "Remote-control token expiration as Unix seconds.", + "format": "int64", + "type": "integer" + }, + "tokenSha256Base64url": { + "description": "SHA-256 of the controller-scoped remote-control token, encoded as unpadded base64url.", + "type": "string" + }, + "type": { + "enum": [ + "remoteControlClientConnection" + ], + "title": "RemoteControlClientConnectionDeviceKeySignPayloadType", + "type": "string" + } + }, + "required": [ + "accountUserId", + "audience", + "clientId", + "nonce", + "scopes", + "sessionId", + "targetOrigin", + "targetPath", + "tokenExpiresAt", + "tokenSha256Base64url", + "type" + ], + "title": "RemoteControlClientConnectionDeviceKeySignPayload", + "type": "object" + }, + { + "description": "Payload bound to a remote-control client `/client/enroll` ownership challenge.", + "properties": { + "accountUserId": { + "type": "string" + }, + "audience": { + "$ref": "#/definitions/RemoteControlClientEnrollmentAudience" + }, + "challengeExpiresAt": { + "description": "Enrollment challenge expiration as Unix seconds.", + "format": "int64", + "type": "integer" + }, + "challengeId": { + "description": "Backend-issued enrollment challenge id that this proof authorizes.", + "type": "string" + }, + "clientId": { + "type": "string" + }, + "deviceIdentitySha256Base64url": { + "description": "SHA-256 of the requested device identity operation, encoded as unpadded base64url.", + "type": "string" + }, + "nonce": { + "type": "string" + }, + "targetOrigin": { + "description": "Origin of the backend endpoint that issued the challenge and will verify this proof.", + "type": "string" + }, + "targetPath": { + "description": "HTTP route path that this proof authorizes.", + "type": "string" + }, + "type": { + "enum": [ + "remoteControlClientEnrollment" + ], + "title": "RemoteControlClientEnrollmentDeviceKeySignPayloadType", + "type": "string" + } + }, + "required": [ + "accountUserId", + "audience", + "challengeExpiresAt", + "challengeId", + "clientId", + "deviceIdentitySha256Base64url", + "nonce", + "targetOrigin", + "targetPath", + "type" + ], + "title": "RemoteControlClientEnrollmentDeviceKeySignPayload", + "type": "object" + } + ] + }, "DynamicToolSpec": { "properties": { "deferLoading": { @@ -1747,6 +1941,20 @@ } ] }, + "RemoteControlClientConnectionAudience": { + "description": "Audience for a remote-control client connection device-key proof.", + "enum": [ + "remote_control_client_websocket" + ], + "type": "string" + }, + "RemoteControlClientEnrollmentAudience": { + "description": "Audience for a remote-control client enrollment device-key proof.", + "enum": [ + "remote_control_client_enrollment" + ], + "type": "string" + }, "RequestId": { "anyOf": [ { @@ -4360,6 +4568,78 @@ "title": "App/listRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "device/key/create" + ], + "title": "Device/key/createRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/DeviceKeyCreateParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Device/key/createRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "device/key/public" + ], + "title": "Device/key/publicRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/DeviceKeyPublicParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Device/key/publicRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "device/key/sign" + ], + "title": "Device/key/signRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/DeviceKeySignParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Device/key/signRequest", + "type": "object" + }, { "properties": { "id": { diff --git a/codex-rs/app-server-protocol/schema/json/DynamicToolCallParams.json b/codex-rs/app-server-protocol/schema/json/DynamicToolCallParams.json index 0aa8b9c571..991733da66 100644 --- a/codex-rs/app-server-protocol/schema/json/DynamicToolCallParams.json +++ b/codex-rs/app-server-protocol/schema/json/DynamicToolCallParams.json @@ -30,4 +30,4 @@ ], "title": "DynamicToolCallParams", "type": "object" -} +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 2cb5fb84df..630e8d4934 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -737,6 +737,78 @@ "title": "App/listRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "enum": [ + "device/key/create" + ], + "title": "Device/key/createRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/DeviceKeyCreateParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Device/key/createRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "enum": [ + "device/key/public" + ], + "title": "Device/key/publicRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/DeviceKeyPublicParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Device/key/publicRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "enum": [ + "device/key/sign" + ], + "title": "Device/key/signRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/DeviceKeySignParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Device/key/signRequest", + "type": "object" + }, { "properties": { "id": { @@ -7324,6 +7396,300 @@ "title": "DeprecationNoticeNotification", "type": "object" }, + "DeviceKeyAlgorithm": { + "description": "Device-key algorithm reported at enrollment and signing boundaries.", + "enum": [ + "ecdsa_p256_sha256" + ], + "type": "string" + }, + "DeviceKeyCreateParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Create a controller-local device key with a random key id.", + "properties": { + "accountUserId": { + "type": "string" + }, + "clientId": { + "type": "string" + }, + "protectionPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/v2/DeviceKeyProtectionPolicy" + }, + { + "type": "null" + } + ], + "description": "Defaults to `hardware_only` when omitted." + } + }, + "required": [ + "accountUserId", + "clientId" + ], + "title": "DeviceKeyCreateParams", + "type": "object" + }, + "DeviceKeyCreateResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Device-key metadata and public key returned by create/public APIs.", + "properties": { + "algorithm": { + "$ref": "#/definitions/v2/DeviceKeyAlgorithm" + }, + "keyId": { + "type": "string" + }, + "protectionClass": { + "$ref": "#/definitions/v2/DeviceKeyProtectionClass" + }, + "publicKeySpkiDerBase64": { + "description": "SubjectPublicKeyInfo DER encoded as base64.", + "type": "string" + } + }, + "required": [ + "algorithm", + "keyId", + "protectionClass", + "publicKeySpkiDerBase64" + ], + "title": "DeviceKeyCreateResponse", + "type": "object" + }, + "DeviceKeyProtectionClass": { + "description": "Platform protection class for a controller-local device key.", + "enum": [ + "hardware_secure_enclave", + "hardware_tpm", + "os_protected_nonextractable" + ], + "type": "string" + }, + "DeviceKeyProtectionPolicy": { + "description": "Protection policy for creating or loading a controller-local device key.", + "enum": [ + "hardware_only", + "allow_os_protected_nonextractable" + ], + "type": "string" + }, + "DeviceKeyPublicParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Fetch a controller-local device key public key by id.", + "properties": { + "keyId": { + "type": "string" + } + }, + "required": [ + "keyId" + ], + "title": "DeviceKeyPublicParams", + "type": "object" + }, + "DeviceKeyPublicResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Device-key public metadata returned by `device/key/public`.", + "properties": { + "algorithm": { + "$ref": "#/definitions/v2/DeviceKeyAlgorithm" + }, + "keyId": { + "type": "string" + }, + "protectionClass": { + "$ref": "#/definitions/v2/DeviceKeyProtectionClass" + }, + "publicKeySpkiDerBase64": { + "description": "SubjectPublicKeyInfo DER encoded as base64.", + "type": "string" + } + }, + "required": [ + "algorithm", + "keyId", + "protectionClass", + "publicKeySpkiDerBase64" + ], + "title": "DeviceKeyPublicResponse", + "type": "object" + }, + "DeviceKeySignParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Sign an accepted structured payload with a controller-local device key.", + "properties": { + "keyId": { + "type": "string" + }, + "payload": { + "$ref": "#/definitions/v2/DeviceKeySignPayload" + } + }, + "required": [ + "keyId", + "payload" + ], + "title": "DeviceKeySignParams", + "type": "object" + }, + "DeviceKeySignPayload": { + "description": "Structured payloads accepted by `device/key/sign`.", + "oneOf": [ + { + "description": "Payload bound to one remote-control controller websocket `/client` connection challenge.", + "properties": { + "accountUserId": { + "type": "string" + }, + "audience": { + "$ref": "#/definitions/v2/RemoteControlClientConnectionAudience" + }, + "clientId": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "scopes": { + "description": "Must contain exactly `remote_control_controller_websocket`.", + "items": { + "type": "string" + }, + "type": "array" + }, + "sessionId": { + "description": "Backend-issued websocket session id that this proof authorizes.", + "type": "string" + }, + "targetOrigin": { + "description": "Origin of the backend endpoint that issued the challenge and will verify this proof.", + "type": "string" + }, + "targetPath": { + "description": "Websocket route path that this proof authorizes.", + "type": "string" + }, + "tokenExpiresAt": { + "description": "Remote-control token expiration as Unix seconds.", + "format": "int64", + "type": "integer" + }, + "tokenSha256Base64url": { + "description": "SHA-256 of the controller-scoped remote-control token, encoded as unpadded base64url.", + "type": "string" + }, + "type": { + "enum": [ + "remoteControlClientConnection" + ], + "title": "RemoteControlClientConnectionDeviceKeySignPayloadType", + "type": "string" + } + }, + "required": [ + "accountUserId", + "audience", + "clientId", + "nonce", + "scopes", + "sessionId", + "targetOrigin", + "targetPath", + "tokenExpiresAt", + "tokenSha256Base64url", + "type" + ], + "title": "RemoteControlClientConnectionDeviceKeySignPayload", + "type": "object" + }, + { + "description": "Payload bound to a remote-control client `/client/enroll` ownership challenge.", + "properties": { + "accountUserId": { + "type": "string" + }, + "audience": { + "$ref": "#/definitions/v2/RemoteControlClientEnrollmentAudience" + }, + "challengeExpiresAt": { + "description": "Enrollment challenge expiration as Unix seconds.", + "format": "int64", + "type": "integer" + }, + "challengeId": { + "description": "Backend-issued enrollment challenge id that this proof authorizes.", + "type": "string" + }, + "clientId": { + "type": "string" + }, + "deviceIdentitySha256Base64url": { + "description": "SHA-256 of the requested device identity operation, encoded as unpadded base64url.", + "type": "string" + }, + "nonce": { + "type": "string" + }, + "targetOrigin": { + "description": "Origin of the backend endpoint that issued the challenge and will verify this proof.", + "type": "string" + }, + "targetPath": { + "description": "HTTP route path that this proof authorizes.", + "type": "string" + }, + "type": { + "enum": [ + "remoteControlClientEnrollment" + ], + "title": "RemoteControlClientEnrollmentDeviceKeySignPayloadType", + "type": "string" + } + }, + "required": [ + "accountUserId", + "audience", + "challengeExpiresAt", + "challengeId", + "clientId", + "deviceIdentitySha256Base64url", + "nonce", + "targetOrigin", + "targetPath", + "type" + ], + "title": "RemoteControlClientEnrollmentDeviceKeySignPayload", + "type": "object" + } + ] + }, + "DeviceKeySignResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "ASN.1 DER signature returned by `device/key/sign`.", + "properties": { + "algorithm": { + "$ref": "#/definitions/v2/DeviceKeyAlgorithm" + }, + "signatureDerBase64": { + "description": "ECDSA signature DER encoded as base64.", + "type": "string" + }, + "signedPayloadBase64": { + "description": "Exact bytes signed by the device key, encoded as base64. Verifiers must verify this byte string directly and must not reserialize `payload`.", + "type": "string" + } + }, + "required": [ + "algorithm", + "signatureDerBase64", + "signedPayloadBase64" + ], + "title": "DeviceKeySignResponse", + "type": "object" + }, "DynamicToolCallOutputContentItem": { "oneOf": [ { @@ -11642,6 +12008,20 @@ "title": "ReasoningTextDeltaNotification", "type": "object" }, + "RemoteControlClientConnectionAudience": { + "description": "Audience for a remote-control client connection device-key proof.", + "enum": [ + "remote_control_client_websocket" + ], + "type": "string" + }, + "RemoteControlClientEnrollmentAudience": { + "description": "Audience for a remote-control client enrollment device-key proof.", + "enum": [ + "remote_control_client_enrollment" + ], + "type": "string" + }, "RequestId": { "anyOf": [ { @@ -14099,8 +14479,8 @@ }, "namespace": { "type": [ - "null", - "string" + "string", + "null" ] }, "status": { @@ -16612,4 +16992,4 @@ }, "title": "CodexAppServerProtocol", "type": "object" -} +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 4ff918e642..82c990533a 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -1418,6 +1418,78 @@ "title": "App/listRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "device/key/create" + ], + "title": "Device/key/createRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/DeviceKeyCreateParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Device/key/createRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "device/key/public" + ], + "title": "Device/key/publicRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/DeviceKeyPublicParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Device/key/publicRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "device/key/sign" + ], + "title": "Device/key/signRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/DeviceKeySignParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Device/key/signRequest", + "type": "object" + }, { "properties": { "id": { @@ -3935,6 +4007,300 @@ "title": "DeprecationNoticeNotification", "type": "object" }, + "DeviceKeyAlgorithm": { + "description": "Device-key algorithm reported at enrollment and signing boundaries.", + "enum": [ + "ecdsa_p256_sha256" + ], + "type": "string" + }, + "DeviceKeyCreateParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Create a controller-local device key with a random key id.", + "properties": { + "accountUserId": { + "type": "string" + }, + "clientId": { + "type": "string" + }, + "protectionPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/DeviceKeyProtectionPolicy" + }, + { + "type": "null" + } + ], + "description": "Defaults to `hardware_only` when omitted." + } + }, + "required": [ + "accountUserId", + "clientId" + ], + "title": "DeviceKeyCreateParams", + "type": "object" + }, + "DeviceKeyCreateResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Device-key metadata and public key returned by create/public APIs.", + "properties": { + "algorithm": { + "$ref": "#/definitions/DeviceKeyAlgorithm" + }, + "keyId": { + "type": "string" + }, + "protectionClass": { + "$ref": "#/definitions/DeviceKeyProtectionClass" + }, + "publicKeySpkiDerBase64": { + "description": "SubjectPublicKeyInfo DER encoded as base64.", + "type": "string" + } + }, + "required": [ + "algorithm", + "keyId", + "protectionClass", + "publicKeySpkiDerBase64" + ], + "title": "DeviceKeyCreateResponse", + "type": "object" + }, + "DeviceKeyProtectionClass": { + "description": "Platform protection class for a controller-local device key.", + "enum": [ + "hardware_secure_enclave", + "hardware_tpm", + "os_protected_nonextractable" + ], + "type": "string" + }, + "DeviceKeyProtectionPolicy": { + "description": "Protection policy for creating or loading a controller-local device key.", + "enum": [ + "hardware_only", + "allow_os_protected_nonextractable" + ], + "type": "string" + }, + "DeviceKeyPublicParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Fetch a controller-local device key public key by id.", + "properties": { + "keyId": { + "type": "string" + } + }, + "required": [ + "keyId" + ], + "title": "DeviceKeyPublicParams", + "type": "object" + }, + "DeviceKeyPublicResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Device-key public metadata returned by `device/key/public`.", + "properties": { + "algorithm": { + "$ref": "#/definitions/DeviceKeyAlgorithm" + }, + "keyId": { + "type": "string" + }, + "protectionClass": { + "$ref": "#/definitions/DeviceKeyProtectionClass" + }, + "publicKeySpkiDerBase64": { + "description": "SubjectPublicKeyInfo DER encoded as base64.", + "type": "string" + } + }, + "required": [ + "algorithm", + "keyId", + "protectionClass", + "publicKeySpkiDerBase64" + ], + "title": "DeviceKeyPublicResponse", + "type": "object" + }, + "DeviceKeySignParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Sign an accepted structured payload with a controller-local device key.", + "properties": { + "keyId": { + "type": "string" + }, + "payload": { + "$ref": "#/definitions/DeviceKeySignPayload" + } + }, + "required": [ + "keyId", + "payload" + ], + "title": "DeviceKeySignParams", + "type": "object" + }, + "DeviceKeySignPayload": { + "description": "Structured payloads accepted by `device/key/sign`.", + "oneOf": [ + { + "description": "Payload bound to one remote-control controller websocket `/client` connection challenge.", + "properties": { + "accountUserId": { + "type": "string" + }, + "audience": { + "$ref": "#/definitions/RemoteControlClientConnectionAudience" + }, + "clientId": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "scopes": { + "description": "Must contain exactly `remote_control_controller_websocket`.", + "items": { + "type": "string" + }, + "type": "array" + }, + "sessionId": { + "description": "Backend-issued websocket session id that this proof authorizes.", + "type": "string" + }, + "targetOrigin": { + "description": "Origin of the backend endpoint that issued the challenge and will verify this proof.", + "type": "string" + }, + "targetPath": { + "description": "Websocket route path that this proof authorizes.", + "type": "string" + }, + "tokenExpiresAt": { + "description": "Remote-control token expiration as Unix seconds.", + "format": "int64", + "type": "integer" + }, + "tokenSha256Base64url": { + "description": "SHA-256 of the controller-scoped remote-control token, encoded as unpadded base64url.", + "type": "string" + }, + "type": { + "enum": [ + "remoteControlClientConnection" + ], + "title": "RemoteControlClientConnectionDeviceKeySignPayloadType", + "type": "string" + } + }, + "required": [ + "accountUserId", + "audience", + "clientId", + "nonce", + "scopes", + "sessionId", + "targetOrigin", + "targetPath", + "tokenExpiresAt", + "tokenSha256Base64url", + "type" + ], + "title": "RemoteControlClientConnectionDeviceKeySignPayload", + "type": "object" + }, + { + "description": "Payload bound to a remote-control client `/client/enroll` ownership challenge.", + "properties": { + "accountUserId": { + "type": "string" + }, + "audience": { + "$ref": "#/definitions/RemoteControlClientEnrollmentAudience" + }, + "challengeExpiresAt": { + "description": "Enrollment challenge expiration as Unix seconds.", + "format": "int64", + "type": "integer" + }, + "challengeId": { + "description": "Backend-issued enrollment challenge id that this proof authorizes.", + "type": "string" + }, + "clientId": { + "type": "string" + }, + "deviceIdentitySha256Base64url": { + "description": "SHA-256 of the requested device identity operation, encoded as unpadded base64url.", + "type": "string" + }, + "nonce": { + "type": "string" + }, + "targetOrigin": { + "description": "Origin of the backend endpoint that issued the challenge and will verify this proof.", + "type": "string" + }, + "targetPath": { + "description": "HTTP route path that this proof authorizes.", + "type": "string" + }, + "type": { + "enum": [ + "remoteControlClientEnrollment" + ], + "title": "RemoteControlClientEnrollmentDeviceKeySignPayloadType", + "type": "string" + } + }, + "required": [ + "accountUserId", + "audience", + "challengeExpiresAt", + "challengeId", + "clientId", + "deviceIdentitySha256Base64url", + "nonce", + "targetOrigin", + "targetPath", + "type" + ], + "title": "RemoteControlClientEnrollmentDeviceKeySignPayload", + "type": "object" + } + ] + }, + "DeviceKeySignResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "ASN.1 DER signature returned by `device/key/sign`.", + "properties": { + "algorithm": { + "$ref": "#/definitions/DeviceKeyAlgorithm" + }, + "signatureDerBase64": { + "description": "ECDSA signature DER encoded as base64.", + "type": "string" + }, + "signedPayloadBase64": { + "description": "Exact bytes signed by the device key, encoded as base64. Verifiers must verify this byte string directly and must not reserialize `payload`.", + "type": "string" + } + }, + "required": [ + "algorithm", + "signatureDerBase64", + "signedPayloadBase64" + ], + "title": "DeviceKeySignResponse", + "type": "object" + }, "DynamicToolCallOutputContentItem": { "oneOf": [ { @@ -8408,6 +8774,20 @@ "title": "ReasoningTextDeltaNotification", "type": "object" }, + "RemoteControlClientConnectionAudience": { + "description": "Audience for a remote-control client connection device-key proof.", + "enum": [ + "remote_control_client_websocket" + ], + "type": "string" + }, + "RemoteControlClientEnrollmentAudience": { + "description": "Audience for a remote-control client enrollment device-key proof.", + "enum": [ + "remote_control_client_enrollment" + ], + "type": "string" + }, "RequestId": { "anyOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyCreateParams.json b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyCreateParams.json new file mode 100644 index 0000000000..fe2c0f0895 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyCreateParams.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "DeviceKeyProtectionPolicy": { + "description": "Protection policy for creating or loading a controller-local device key.", + "enum": [ + "hardware_only", + "allow_os_protected_nonextractable" + ], + "type": "string" + } + }, + "description": "Create a controller-local device key with a random key id.", + "properties": { + "accountUserId": { + "type": "string" + }, + "clientId": { + "type": "string" + }, + "protectionPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/DeviceKeyProtectionPolicy" + }, + { + "type": "null" + } + ], + "description": "Defaults to `hardware_only` when omitted." + } + }, + "required": [ + "accountUserId", + "clientId" + ], + "title": "DeviceKeyCreateParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyCreateResponse.json b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyCreateResponse.json new file mode 100644 index 0000000000..12072588a9 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyCreateResponse.json @@ -0,0 +1,45 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "DeviceKeyAlgorithm": { + "description": "Device-key algorithm reported at enrollment and signing boundaries.", + "enum": [ + "ecdsa_p256_sha256" + ], + "type": "string" + }, + "DeviceKeyProtectionClass": { + "description": "Platform protection class for a controller-local device key.", + "enum": [ + "hardware_secure_enclave", + "hardware_tpm", + "os_protected_nonextractable" + ], + "type": "string" + } + }, + "description": "Device-key metadata and public key returned by create/public APIs.", + "properties": { + "algorithm": { + "$ref": "#/definitions/DeviceKeyAlgorithm" + }, + "keyId": { + "type": "string" + }, + "protectionClass": { + "$ref": "#/definitions/DeviceKeyProtectionClass" + }, + "publicKeySpkiDerBase64": { + "description": "SubjectPublicKeyInfo DER encoded as base64.", + "type": "string" + } + }, + "required": [ + "algorithm", + "keyId", + "protectionClass", + "publicKeySpkiDerBase64" + ], + "title": "DeviceKeyCreateResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyPublicParams.json b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyPublicParams.json new file mode 100644 index 0000000000..37cc5fbe2c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyPublicParams.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Fetch a controller-local device key public key by id.", + "properties": { + "keyId": { + "type": "string" + } + }, + "required": [ + "keyId" + ], + "title": "DeviceKeyPublicParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyPublicResponse.json b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyPublicResponse.json new file mode 100644 index 0000000000..39f98b7623 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeyPublicResponse.json @@ -0,0 +1,45 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "DeviceKeyAlgorithm": { + "description": "Device-key algorithm reported at enrollment and signing boundaries.", + "enum": [ + "ecdsa_p256_sha256" + ], + "type": "string" + }, + "DeviceKeyProtectionClass": { + "description": "Platform protection class for a controller-local device key.", + "enum": [ + "hardware_secure_enclave", + "hardware_tpm", + "os_protected_nonextractable" + ], + "type": "string" + } + }, + "description": "Device-key public metadata returned by `device/key/public`.", + "properties": { + "algorithm": { + "$ref": "#/definitions/DeviceKeyAlgorithm" + }, + "keyId": { + "type": "string" + }, + "protectionClass": { + "$ref": "#/definitions/DeviceKeyProtectionClass" + }, + "publicKeySpkiDerBase64": { + "description": "SubjectPublicKeyInfo DER encoded as base64.", + "type": "string" + } + }, + "required": [ + "algorithm", + "keyId", + "protectionClass", + "publicKeySpkiDerBase64" + ], + "title": "DeviceKeyPublicResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/DeviceKeySignParams.json b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeySignParams.json new file mode 100644 index 0000000000..054765b297 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeySignParams.json @@ -0,0 +1,165 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "DeviceKeySignPayload": { + "description": "Structured payloads accepted by `device/key/sign`.", + "oneOf": [ + { + "description": "Payload bound to one remote-control controller websocket `/client` connection challenge.", + "properties": { + "accountUserId": { + "type": "string" + }, + "audience": { + "$ref": "#/definitions/RemoteControlClientConnectionAudience" + }, + "clientId": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "scopes": { + "description": "Must contain exactly `remote_control_controller_websocket`.", + "items": { + "type": "string" + }, + "type": "array" + }, + "sessionId": { + "description": "Backend-issued websocket session id that this proof authorizes.", + "type": "string" + }, + "targetOrigin": { + "description": "Origin of the backend endpoint that issued the challenge and will verify this proof.", + "type": "string" + }, + "targetPath": { + "description": "Websocket route path that this proof authorizes.", + "type": "string" + }, + "tokenExpiresAt": { + "description": "Remote-control token expiration as Unix seconds.", + "format": "int64", + "type": "integer" + }, + "tokenSha256Base64url": { + "description": "SHA-256 of the controller-scoped remote-control token, encoded as unpadded base64url.", + "type": "string" + }, + "type": { + "enum": [ + "remoteControlClientConnection" + ], + "title": "RemoteControlClientConnectionDeviceKeySignPayloadType", + "type": "string" + } + }, + "required": [ + "accountUserId", + "audience", + "clientId", + "nonce", + "scopes", + "sessionId", + "targetOrigin", + "targetPath", + "tokenExpiresAt", + "tokenSha256Base64url", + "type" + ], + "title": "RemoteControlClientConnectionDeviceKeySignPayload", + "type": "object" + }, + { + "description": "Payload bound to a remote-control client `/client/enroll` ownership challenge.", + "properties": { + "accountUserId": { + "type": "string" + }, + "audience": { + "$ref": "#/definitions/RemoteControlClientEnrollmentAudience" + }, + "challengeExpiresAt": { + "description": "Enrollment challenge expiration as Unix seconds.", + "format": "int64", + "type": "integer" + }, + "challengeId": { + "description": "Backend-issued enrollment challenge id that this proof authorizes.", + "type": "string" + }, + "clientId": { + "type": "string" + }, + "deviceIdentitySha256Base64url": { + "description": "SHA-256 of the requested device identity operation, encoded as unpadded base64url.", + "type": "string" + }, + "nonce": { + "type": "string" + }, + "targetOrigin": { + "description": "Origin of the backend endpoint that issued the challenge and will verify this proof.", + "type": "string" + }, + "targetPath": { + "description": "HTTP route path that this proof authorizes.", + "type": "string" + }, + "type": { + "enum": [ + "remoteControlClientEnrollment" + ], + "title": "RemoteControlClientEnrollmentDeviceKeySignPayloadType", + "type": "string" + } + }, + "required": [ + "accountUserId", + "audience", + "challengeExpiresAt", + "challengeId", + "clientId", + "deviceIdentitySha256Base64url", + "nonce", + "targetOrigin", + "targetPath", + "type" + ], + "title": "RemoteControlClientEnrollmentDeviceKeySignPayload", + "type": "object" + } + ] + }, + "RemoteControlClientConnectionAudience": { + "description": "Audience for a remote-control client connection device-key proof.", + "enum": [ + "remote_control_client_websocket" + ], + "type": "string" + }, + "RemoteControlClientEnrollmentAudience": { + "description": "Audience for a remote-control client enrollment device-key proof.", + "enum": [ + "remote_control_client_enrollment" + ], + "type": "string" + } + }, + "description": "Sign an accepted structured payload with a controller-local device key.", + "properties": { + "keyId": { + "type": "string" + }, + "payload": { + "$ref": "#/definitions/DeviceKeySignPayload" + } + }, + "required": [ + "keyId", + "payload" + ], + "title": "DeviceKeySignParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/DeviceKeySignResponse.json b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeySignResponse.json new file mode 100644 index 0000000000..83fec90330 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/DeviceKeySignResponse.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "DeviceKeyAlgorithm": { + "description": "Device-key algorithm reported at enrollment and signing boundaries.", + "enum": [ + "ecdsa_p256_sha256" + ], + "type": "string" + } + }, + "description": "ASN.1 DER signature returned by `device/key/sign`.", + "properties": { + "algorithm": { + "$ref": "#/definitions/DeviceKeyAlgorithm" + }, + "signatureDerBase64": { + "description": "ECDSA signature DER encoded as base64.", + "type": "string" + }, + "signedPayloadBase64": { + "description": "Exact bytes signed by the device key, encoded as base64. Verifiers must verify this byte string directly and must not reserialize `payload`.", + "type": "string" + } + }, + "required": [ + "algorithm", + "signatureDerBase64", + "signedPayloadBase64" + ], + "title": "DeviceKeySignResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts index c26ff9161f..fdb934ed53 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts @@ -16,6 +16,9 @@ import type { CommandExecWriteParams } from "./v2/CommandExecWriteParams"; import type { ConfigBatchWriteParams } from "./v2/ConfigBatchWriteParams"; import type { ConfigReadParams } from "./v2/ConfigReadParams"; import type { ConfigValueWriteParams } from "./v2/ConfigValueWriteParams"; +import type { DeviceKeyCreateParams } from "./v2/DeviceKeyCreateParams"; +import type { DeviceKeyPublicParams } from "./v2/DeviceKeyPublicParams"; +import type { DeviceKeySignParams } from "./v2/DeviceKeySignParams"; import type { ExperimentalFeatureEnablementSetParams } from "./v2/ExperimentalFeatureEnablementSetParams"; import type { ExperimentalFeatureListParams } from "./v2/ExperimentalFeatureListParams"; import type { ExternalAgentConfigDetectParams } from "./v2/ExternalAgentConfigDetectParams"; @@ -71,4 +74,4 @@ import type { WindowsSandboxSetupStartParams } from "./v2/WindowsSandboxSetupSta /** * Request from the client to the server. */ -export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/shellCommand", id: RequestId, params: ThreadShellCommandParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "thread/turns/list", id: RequestId, params: ThreadTurnsListParams, } | { "method": "thread/inject_items", id: RequestId, params: ThreadInjectItemsParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "marketplace/add", id: RequestId, params: MarketplaceAddParams, } | { "method": "marketplace/remove", id: RequestId, params: MarketplaceRemoveParams, } | { "method": "plugin/list", id: RequestId, params: PluginListParams, } | { "method": "plugin/read", id: RequestId, params: PluginReadParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "fs/readFile", id: RequestId, params: FsReadFileParams, } | { "method": "fs/writeFile", id: RequestId, params: FsWriteFileParams, } | { "method": "fs/createDirectory", id: RequestId, params: FsCreateDirectoryParams, } | { "method": "fs/getMetadata", id: RequestId, params: FsGetMetadataParams, } | { "method": "fs/readDirectory", id: RequestId, params: FsReadDirectoryParams, } | { "method": "fs/remove", id: RequestId, params: FsRemoveParams, } | { "method": "fs/copy", id: RequestId, params: FsCopyParams, } | { "method": "fs/watch", id: RequestId, params: FsWatchParams, } | { "method": "fs/unwatch", id: RequestId, params: FsUnwatchParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "plugin/uninstall", id: RequestId, params: PluginUninstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "experimentalFeature/enablement/set", id: RequestId, params: ExperimentalFeatureEnablementSetParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "mcpServer/resource/read", id: RequestId, params: McpResourceReadParams, } | { "method": "mcpServer/tool/call", id: RequestId, params: McpServerToolCallParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "account/sendAddCreditsNudgeEmail", id: RequestId, params: SendAddCreditsNudgeEmailParams, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "command/exec/write", id: RequestId, params: CommandExecWriteParams, } | { "method": "command/exec/terminate", id: RequestId, params: CommandExecTerminateParams, } | { "method": "command/exec/resize", id: RequestId, params: CommandExecResizeParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, }; +export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/shellCommand", id: RequestId, params: ThreadShellCommandParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "thread/turns/list", id: RequestId, params: ThreadTurnsListParams, } | { "method": "thread/inject_items", id: RequestId, params: ThreadInjectItemsParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "marketplace/add", id: RequestId, params: MarketplaceAddParams, } | { "method": "marketplace/remove", id: RequestId, params: MarketplaceRemoveParams, } | { "method": "plugin/list", id: RequestId, params: PluginListParams, } | { "method": "plugin/read", id: RequestId, params: PluginReadParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "device/key/create", id: RequestId, params: DeviceKeyCreateParams, } | { "method": "device/key/public", id: RequestId, params: DeviceKeyPublicParams, } | { "method": "device/key/sign", id: RequestId, params: DeviceKeySignParams, } | { "method": "fs/readFile", id: RequestId, params: FsReadFileParams, } | { "method": "fs/writeFile", id: RequestId, params: FsWriteFileParams, } | { "method": "fs/createDirectory", id: RequestId, params: FsCreateDirectoryParams, } | { "method": "fs/getMetadata", id: RequestId, params: FsGetMetadataParams, } | { "method": "fs/readDirectory", id: RequestId, params: FsReadDirectoryParams, } | { "method": "fs/remove", id: RequestId, params: FsRemoveParams, } | { "method": "fs/copy", id: RequestId, params: FsCopyParams, } | { "method": "fs/watch", id: RequestId, params: FsWatchParams, } | { "method": "fs/unwatch", id: RequestId, params: FsUnwatchParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "plugin/uninstall", id: RequestId, params: PluginUninstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "experimentalFeature/enablement/set", id: RequestId, params: ExperimentalFeatureEnablementSetParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "mcpServer/resource/read", id: RequestId, params: McpResourceReadParams, } | { "method": "mcpServer/tool/call", id: RequestId, params: McpServerToolCallParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "account/sendAddCreditsNudgeEmail", id: RequestId, params: SendAddCreditsNudgeEmailParams, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "command/exec/write", id: RequestId, params: CommandExecWriteParams, } | { "method": "command/exec/terminate", id: RequestId, params: CommandExecTerminateParams, } | { "method": "command/exec/resize", id: RequestId, params: CommandExecResizeParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyAlgorithm.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyAlgorithm.ts new file mode 100644 index 0000000000..6809c41eb5 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyAlgorithm.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Device-key algorithm reported at enrollment and signing boundaries. + */ +export type DeviceKeyAlgorithm = "ecdsa_p256_sha256"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyCreateParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyCreateParams.ts new file mode 100644 index 0000000000..7ffd9b5fa3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyCreateParams.ts @@ -0,0 +1,13 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DeviceKeyProtectionPolicy } from "./DeviceKeyProtectionPolicy"; + +/** + * Create a controller-local device key with a random key id. + */ +export type DeviceKeyCreateParams = { +/** + * Defaults to `hardware_only` when omitted. + */ +protectionPolicy?: DeviceKeyProtectionPolicy | null, accountUserId: string, clientId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyCreateResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyCreateResponse.ts new file mode 100644 index 0000000000..6ace37934a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyCreateResponse.ts @@ -0,0 +1,14 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DeviceKeyAlgorithm } from "./DeviceKeyAlgorithm"; +import type { DeviceKeyProtectionClass } from "./DeviceKeyProtectionClass"; + +/** + * Device-key metadata and public key returned by create/public APIs. + */ +export type DeviceKeyCreateResponse = { keyId: string, +/** + * SubjectPublicKeyInfo DER encoded as base64. + */ +publicKeySpkiDerBase64: string, algorithm: DeviceKeyAlgorithm, protectionClass: DeviceKeyProtectionClass, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyProtectionClass.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyProtectionClass.ts new file mode 100644 index 0000000000..ba7ff311ad --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyProtectionClass.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Platform protection class for a controller-local device key. + */ +export type DeviceKeyProtectionClass = "hardware_secure_enclave" | "hardware_tpm" | "os_protected_nonextractable"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyProtectionPolicy.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyProtectionPolicy.ts new file mode 100644 index 0000000000..66fceafb51 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyProtectionPolicy.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Protection policy for creating or loading a controller-local device key. + */ +export type DeviceKeyProtectionPolicy = "hardware_only" | "allow_os_protected_nonextractable"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyPublicParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyPublicParams.ts new file mode 100644 index 0000000000..5a5b77899d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyPublicParams.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Fetch a controller-local device key public key by id. + */ +export type DeviceKeyPublicParams = { keyId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyPublicResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyPublicResponse.ts new file mode 100644 index 0000000000..9967c0936e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeyPublicResponse.ts @@ -0,0 +1,14 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DeviceKeyAlgorithm } from "./DeviceKeyAlgorithm"; +import type { DeviceKeyProtectionClass } from "./DeviceKeyProtectionClass"; + +/** + * Device-key public metadata returned by `device/key/public`. + */ +export type DeviceKeyPublicResponse = { keyId: string, +/** + * SubjectPublicKeyInfo DER encoded as base64. + */ +publicKeySpkiDerBase64: string, algorithm: DeviceKeyAlgorithm, protectionClass: DeviceKeyProtectionClass, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeySignParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeySignParams.ts new file mode 100644 index 0000000000..0886e45d93 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeySignParams.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DeviceKeySignPayload } from "./DeviceKeySignPayload"; + +/** + * Sign an accepted structured payload with a controller-local device key. + */ +export type DeviceKeySignParams = { keyId: string, payload: DeviceKeySignPayload, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeySignPayload.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeySignPayload.ts new file mode 100644 index 0000000000..8596445490 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeySignPayload.ts @@ -0,0 +1,54 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { RemoteControlClientConnectionAudience } from "./RemoteControlClientConnectionAudience"; +import type { RemoteControlClientEnrollmentAudience } from "./RemoteControlClientEnrollmentAudience"; + +/** + * Structured payloads accepted by `device/key/sign`. + */ +export type DeviceKeySignPayload = { "type": "remoteControlClientConnection", nonce: string, audience: RemoteControlClientConnectionAudience, +/** + * Backend-issued websocket session id that this proof authorizes. + */ +sessionId: string, +/** + * Origin of the backend endpoint that issued the challenge and will verify this proof. + */ +targetOrigin: string, +/** + * Websocket route path that this proof authorizes. + */ +targetPath: string, accountUserId: string, clientId: string, +/** + * Remote-control token expiration as Unix seconds. + */ +tokenExpiresAt: number, +/** + * SHA-256 of the controller-scoped remote-control token, encoded as unpadded base64url. + */ +tokenSha256Base64url: string, +/** + * Must contain exactly `remote_control_controller_websocket`. + */ +scopes: Array, } | { "type": "remoteControlClientEnrollment", nonce: string, audience: RemoteControlClientEnrollmentAudience, +/** + * Backend-issued enrollment challenge id that this proof authorizes. + */ +challengeId: string, +/** + * Origin of the backend endpoint that issued the challenge and will verify this proof. + */ +targetOrigin: string, +/** + * HTTP route path that this proof authorizes. + */ +targetPath: string, accountUserId: string, clientId: string, +/** + * SHA-256 of the requested device identity operation, encoded as unpadded base64url. + */ +deviceIdentitySha256Base64url: string, +/** + * Enrollment challenge expiration as Unix seconds. + */ +challengeExpiresAt: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeySignResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeySignResponse.ts new file mode 100644 index 0000000000..cf77fae27f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DeviceKeySignResponse.ts @@ -0,0 +1,18 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DeviceKeyAlgorithm } from "./DeviceKeyAlgorithm"; + +/** + * ASN.1 DER signature returned by `device/key/sign`. + */ +export type DeviceKeySignResponse = { +/** + * ECDSA signature DER encoded as base64. + */ +signatureDerBase64: string, +/** + * Exact bytes signed by the device key, encoded as base64. Verifiers must verify this byte + * string directly and must not reserialize `payload`. + */ +signedPayloadBase64: string, algorithm: DeviceKeyAlgorithm, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/RemoteControlClientConnectionAudience.ts b/codex-rs/app-server-protocol/schema/typescript/v2/RemoteControlClientConnectionAudience.ts new file mode 100644 index 0000000000..e4d41ff4c2 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/RemoteControlClientConnectionAudience.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Audience for a remote-control client connection device-key proof. + */ +export type RemoteControlClientConnectionAudience = "remote_control_client_websocket"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/RemoteControlClientEnrollmentAudience.ts b/codex-rs/app-server-protocol/schema/typescript/v2/RemoteControlClientEnrollmentAudience.ts new file mode 100644 index 0000000000..b65fb3d11b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/RemoteControlClientEnrollmentAudience.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Audience for a remote-control client enrollment device-key proof. + */ +export type RemoteControlClientEnrollmentAudience = "remote_control_client_enrollment"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index e37759112a..7bddd0f9d6 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -74,6 +74,16 @@ export type { ConfigWriteResponse } from "./ConfigWriteResponse"; export type { ContextCompactedNotification } from "./ContextCompactedNotification"; export type { CreditsSnapshot } from "./CreditsSnapshot"; export type { DeprecationNoticeNotification } from "./DeprecationNoticeNotification"; +export type { DeviceKeyAlgorithm } from "./DeviceKeyAlgorithm"; +export type { DeviceKeyCreateParams } from "./DeviceKeyCreateParams"; +export type { DeviceKeyCreateResponse } from "./DeviceKeyCreateResponse"; +export type { DeviceKeyProtectionClass } from "./DeviceKeyProtectionClass"; +export type { DeviceKeyProtectionPolicy } from "./DeviceKeyProtectionPolicy"; +export type { DeviceKeyPublicParams } from "./DeviceKeyPublicParams"; +export type { DeviceKeyPublicResponse } from "./DeviceKeyPublicResponse"; +export type { DeviceKeySignParams } from "./DeviceKeySignParams"; +export type { DeviceKeySignPayload } from "./DeviceKeySignPayload"; +export type { DeviceKeySignResponse } from "./DeviceKeySignResponse"; export type { DynamicToolCallOutputContentItem } from "./DynamicToolCallOutputContentItem"; export type { DynamicToolCallParams } from "./DynamicToolCallParams"; export type { DynamicToolCallResponse } from "./DynamicToolCallResponse"; @@ -259,6 +269,8 @@ export type { ReasoningEffortOption } from "./ReasoningEffortOption"; export type { ReasoningSummaryPartAddedNotification } from "./ReasoningSummaryPartAddedNotification"; export type { ReasoningSummaryTextDeltaNotification } from "./ReasoningSummaryTextDeltaNotification"; export type { ReasoningTextDeltaNotification } from "./ReasoningTextDeltaNotification"; +export type { RemoteControlClientConnectionAudience } from "./RemoteControlClientConnectionAudience"; +export type { RemoteControlClientEnrollmentAudience } from "./RemoteControlClientEnrollmentAudience"; export type { RequestPermissionProfile } from "./RequestPermissionProfile"; export type { ResidencyRequirement } from "./ResidencyRequirement"; export type { ReviewDelivery } from "./ReviewDelivery"; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index aa3ff8331c..4179e18b55 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -360,6 +360,18 @@ client_request_definitions! { params: v2::AppsListParams, response: v2::AppsListResponse, }, + DeviceKeyCreate => "device/key/create" { + params: v2::DeviceKeyCreateParams, + response: v2::DeviceKeyCreateResponse, + }, + DeviceKeyPublic => "device/key/public" { + params: v2::DeviceKeyPublicParams, + response: v2::DeviceKeyPublicResponse, + }, + DeviceKeySign => "device/key/sign" { + params: v2::DeviceKeySignParams, + response: v2::DeviceKeySignResponse, + }, FsReadFile => "fs/readFile" { params: v2::FsReadFileParams, response: v2::FsReadFileResponse, diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 8ac6651ae9..a78db2e0e2 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -2494,6 +2494,164 @@ pub struct FeedbackUploadResponse { pub thread_id: String, } +/// Device-key algorithm reported at enrollment and signing boundaries. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "snake_case")] +#[ts(rename_all = "snake_case", export_to = "v2/")] +pub enum DeviceKeyAlgorithm { + EcdsaP256Sha256, +} + +/// Platform protection class for a controller-local device key. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "snake_case")] +#[ts(rename_all = "snake_case", export_to = "v2/")] +pub enum DeviceKeyProtectionClass { + HardwareSecureEnclave, + HardwareTpm, + OsProtectedNonextractable, +} + +/// Protection policy for creating or loading a controller-local device key. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "snake_case")] +#[ts(rename_all = "snake_case", export_to = "v2/")] +pub enum DeviceKeyProtectionPolicy { + HardwareOnly, + AllowOsProtectedNonextractable, +} + +/// Create a controller-local device key with a random key id. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct DeviceKeyCreateParams { + /// Defaults to `hardware_only` when omitted. + #[ts(optional = nullable)] + pub protection_policy: Option, + pub account_user_id: String, + pub client_id: String, +} + +/// Device-key metadata and public key returned by create/public APIs. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct DeviceKeyCreateResponse { + pub key_id: String, + /// SubjectPublicKeyInfo DER encoded as base64. + pub public_key_spki_der_base64: String, + pub algorithm: DeviceKeyAlgorithm, + pub protection_class: DeviceKeyProtectionClass, +} + +/// Fetch a controller-local device key public key by id. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct DeviceKeyPublicParams { + pub key_id: String, +} + +/// Device-key public metadata returned by `device/key/public`. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct DeviceKeyPublicResponse { + pub key_id: String, + /// SubjectPublicKeyInfo DER encoded as base64. + pub public_key_spki_der_base64: String, + pub algorithm: DeviceKeyAlgorithm, + pub protection_class: DeviceKeyProtectionClass, +} + +/// Audience for a remote-control client connection device-key proof. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "snake_case")] +#[ts(rename_all = "snake_case", export_to = "v2/")] +pub enum RemoteControlClientConnectionAudience { + RemoteControlClientWebsocket, +} + +/// Audience for a remote-control client enrollment device-key proof. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "snake_case")] +#[ts(rename_all = "snake_case", export_to = "v2/")] +pub enum RemoteControlClientEnrollmentAudience { + RemoteControlClientEnrollment, +} + +/// Structured payloads accepted by `device/key/sign`. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(tag = "type", rename_all = "camelCase")] +#[ts(tag = "type", export_to = "v2/")] +pub enum DeviceKeySignPayload { + /// Payload bound to one remote-control controller websocket `/client` connection challenge. + #[serde(rename_all = "camelCase")] + #[ts(rename_all = "camelCase")] + RemoteControlClientConnection { + nonce: String, + audience: RemoteControlClientConnectionAudience, + /// Backend-issued websocket session id that this proof authorizes. + session_id: String, + /// Origin of the backend endpoint that issued the challenge and will verify this proof. + target_origin: String, + /// Websocket route path that this proof authorizes. + target_path: String, + account_user_id: String, + client_id: String, + /// Remote-control token expiration as Unix seconds. + #[ts(type = "number")] + token_expires_at: i64, + /// SHA-256 of the controller-scoped remote-control token, encoded as unpadded base64url. + token_sha256_base64url: String, + /// Must contain exactly `remote_control_controller_websocket`. + scopes: Vec, + }, + /// Payload bound to a remote-control client `/client/enroll` ownership challenge. + #[serde(rename_all = "camelCase")] + #[ts(rename_all = "camelCase")] + RemoteControlClientEnrollment { + nonce: String, + audience: RemoteControlClientEnrollmentAudience, + /// Backend-issued enrollment challenge id that this proof authorizes. + challenge_id: String, + /// Origin of the backend endpoint that issued the challenge and will verify this proof. + target_origin: String, + /// HTTP route path that this proof authorizes. + target_path: String, + account_user_id: String, + client_id: String, + /// SHA-256 of the requested device identity operation, encoded as unpadded base64url. + device_identity_sha256_base64url: String, + /// Enrollment challenge expiration as Unix seconds. + #[ts(type = "number")] + challenge_expires_at: i64, + }, +} + +/// Sign an accepted structured payload with a controller-local device key. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct DeviceKeySignParams { + pub key_id: String, + pub payload: DeviceKeySignPayload, +} + +/// ASN.1 DER signature returned by `device/key/sign`. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct DeviceKeySignResponse { + /// ECDSA signature DER encoded as base64. + pub signature_der_base64: String, + /// Exact bytes signed by the device key, encoded as base64. Verifiers must verify this byte + /// string directly and must not reserialize `payload`. + pub signed_payload_base64: String, + pub algorithm: DeviceKeyAlgorithm, +} + /// Read a file from the host filesystem. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] @@ -7386,6 +7544,181 @@ mod tests { assert_eq!(decoded, params); } + #[test] + fn device_key_create_params_round_trip_uses_protection_policy() { + let params = DeviceKeyCreateParams { + protection_policy: None, + account_user_id: "account-user-1".to_string(), + client_id: "cli_123".to_string(), + }; + + let value = serde_json::to_value(¶ms).expect("serialize device/key/create params"); + assert_eq!( + value, + json!({ + "accountUserId": "account-user-1", + "clientId": "cli_123", + "protectionPolicy": null, + }) + ); + + let decoded = serde_json::from_value::(value) + .expect("deserialize device/key/create params"); + assert_eq!(decoded, params); + + let params = DeviceKeyCreateParams { + protection_policy: Some(DeviceKeyProtectionPolicy::AllowOsProtectedNonextractable), + account_user_id: "account-user-1".to_string(), + client_id: "cli_123".to_string(), + }; + let value = serde_json::to_value(¶ms) + .expect("serialize device/key/create params with protection policy"); + assert_eq!( + value, + json!({ + "accountUserId": "account-user-1", + "clientId": "cli_123", + "protectionPolicy": "allow_os_protected_nonextractable", + }) + ); + } + + #[test] + fn device_key_create_response_round_trips_protection_class() { + let response = DeviceKeyCreateResponse { + key_id: "dk_123".to_string(), + public_key_spki_der_base64: "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE".to_string(), + algorithm: DeviceKeyAlgorithm::EcdsaP256Sha256, + protection_class: DeviceKeyProtectionClass::OsProtectedNonextractable, + }; + + let value = serde_json::to_value(&response).expect("serialize device/key/create response"); + assert_eq!( + value, + json!({ + "keyId": "dk_123", + "publicKeySpkiDerBase64": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE", + "algorithm": "ecdsa_p256_sha256", + "protectionClass": "os_protected_nonextractable", + }) + ); + + let decoded = serde_json::from_value::(value) + .expect("deserialize device/key/create response"); + assert_eq!(decoded, response); + } + + #[test] + fn device_key_sign_params_round_trip_uses_accepted_payload_enum() { + let params = DeviceKeySignParams { + key_id: "dk_123".to_string(), + payload: DeviceKeySignPayload::RemoteControlClientConnection { + nonce: "nonce-1".to_string(), + audience: RemoteControlClientConnectionAudience::RemoteControlClientWebsocket, + session_id: "wssess_123".to_string(), + target_origin: "https://chatgpt.com".to_string(), + target_path: "/api/codex/remote/control/client".to_string(), + account_user_id: "account-user-1".to_string(), + client_id: "cli_123".to_string(), + token_sha256_base64url: "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU".to_string(), + token_expires_at: 1_700_000_000, + scopes: vec!["remote_control_controller_websocket".to_string()], + }, + }; + + let value = serde_json::to_value(¶ms).expect("serialize device/key/sign params"); + assert_eq!( + value, + json!({ + "keyId": "dk_123", + "payload": { + "type": "remoteControlClientConnection", + "nonce": "nonce-1", + "audience": "remote_control_client_websocket", + "sessionId": "wssess_123", + "targetOrigin": "https://chatgpt.com", + "targetPath": "/api/codex/remote/control/client", + "accountUserId": "account-user-1", + "clientId": "cli_123", + "tokenSha256Base64url": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU", + "tokenExpiresAt": 1_700_000_000, + "scopes": ["remote_control_controller_websocket"], + }, + }) + ); + + let decoded = serde_json::from_value::(value) + .expect("deserialize device/key/sign params"); + assert_eq!(decoded, params); + } + + #[test] + fn device_key_sign_params_round_trip_uses_enrollment_payload() { + let params = DeviceKeySignParams { + key_id: "dk_123".to_string(), + payload: DeviceKeySignPayload::RemoteControlClientEnrollment { + nonce: "nonce-1".to_string(), + audience: RemoteControlClientEnrollmentAudience::RemoteControlClientEnrollment, + challenge_id: "rch_123".to_string(), + target_origin: "https://chatgpt.com".to_string(), + target_path: "/wham/remote/control/client/enroll".to_string(), + account_user_id: "account-user-1".to_string(), + client_id: "cli_123".to_string(), + device_identity_sha256_base64url: "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU" + .to_string(), + challenge_expires_at: 1_700_000_000, + }, + }; + + let value = serde_json::to_value(¶ms) + .expect("serialize device/key/sign params with enrollment payload"); + assert_eq!( + value, + json!({ + "keyId": "dk_123", + "payload": { + "type": "remoteControlClientEnrollment", + "nonce": "nonce-1", + "audience": "remote_control_client_enrollment", + "challengeId": "rch_123", + "targetOrigin": "https://chatgpt.com", + "targetPath": "/wham/remote/control/client/enroll", + "accountUserId": "account-user-1", + "clientId": "cli_123", + "deviceIdentitySha256Base64url": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU", + "challengeExpiresAt": 1_700_000_000, + }, + }) + ); + + let decoded = serde_json::from_value::(value) + .expect("deserialize device/key/sign params with enrollment payload"); + assert_eq!(decoded, params); + } + + #[test] + fn device_key_sign_response_returns_signed_payload_bytes() { + let response = DeviceKeySignResponse { + signature_der_base64: "MEUCIQD".to_string(), + signed_payload_base64: "eyJkb21haW4iOiJjb2RleA".to_string(), + algorithm: DeviceKeyAlgorithm::EcdsaP256Sha256, + }; + + let value = serde_json::to_value(&response).expect("serialize device/key/sign response"); + assert_eq!( + value, + json!({ + "signatureDerBase64": "MEUCIQD", + "signedPayloadBase64": "eyJkb21haW4iOiJjb2RleA", + "algorithm": "ecdsa_p256_sha256", + }) + ); + + let decoded = serde_json::from_value::(value) + .expect("deserialize device/key/sign response"); + assert_eq!(decoded, response); + } + #[test] fn fs_create_directory_params_round_trip_with_default_recursive() { let params = FsCreateDirectoryParams { diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 7a2ada0765..f522908be9 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -1186,6 +1186,11 @@ impl CodexMessageProcessor { self.command_exec_terminate(to_connection_request_id(request_id), params) .await; } + ClientRequest::DeviceKeyCreate { .. } + | ClientRequest::DeviceKeyPublic { .. } + | ClientRequest::DeviceKeySign { .. } => { + warn!("Device key request reached CodexMessageProcessor unexpectedly"); + } ClientRequest::ConfigRead { .. } | ClientRequest::ConfigValueWrite { .. } | ClientRequest::ConfigBatchWrite { .. } From 3a9df58d0611e51dc059d2e9175f4b338d8b99a1 Mon Sep 17 00:00:00 2001 From: Rennie Date: Tue, 21 Apr 2026 10:09:46 -0700 Subject: [PATCH 06/16] Propagate thread id in MCP tool metadata (#18093) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - attach the authoritative Codex thread id to MCP tool request `_meta.threadId` for model-initiated tool calls - attach the same thread id for manual `mcpServer/tool/call` requests before invoking the MCP server - cover both metadata helper behavior and the manual app-server MCP path in tests needed because the Rust app-server is the last place that still has authoritative knowledge of “this model-generated MCP tool call belongs to conversation/thread X” before the request leaves Codex and reaches Hoopa. It adds threadId to MCP request metadata in the model-generated tool-call path, using sess.conversation_id, and also does the same for the manual mcpServer/tool/call path. ## Test plan - `cargo test -p codex-core mcp_tool_call_thread_id_meta_is_added_to_request_meta --lib` - `cargo test -p codex-app-server mcp_server_tool_call_returns_tool_result` Paired Hoopa consumer PR: https://github.com/openai/openai/pull/833263 --- .../app-server/src/codex_message_processor.rs | 32 +++++++++++++++++-- .../app-server/tests/suite/v2/mcp_tool.rs | 13 ++++++-- codex-rs/core/src/mcp_tool_call.rs | 27 ++++++++++++++++ codex-rs/core/src/mcp_tool_call_tests.rs | 29 +++++++++++++++++ 4 files changed, 97 insertions(+), 4 deletions(-) diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index f522908be9..a60a079898 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -6046,17 +6046,19 @@ impl CodexMessageProcessor { params: McpServerToolCallParams, ) { let outgoing = Arc::clone(&self.outgoing); - let (_, thread) = match self.load_thread(¶ms.thread_id).await { + let thread_id = params.thread_id.clone(); + let (_, thread) = match self.load_thread(&thread_id).await { Ok(thread) => thread, Err(error) => { self.outgoing.send_error(request_id, error).await; return; } }; + let meta = with_mcp_tool_call_thread_id_meta(params.meta, &thread_id); tokio::spawn(async move { let result = thread - .call_mcp_tool(¶ms.server, ¶ms.tool, params.arguments, params.meta) + .call_mcp_tool(¶ms.server, ¶ms.tool, params.arguments, meta) .await; match result { Ok(result) => { @@ -9896,6 +9898,32 @@ fn thread_store_archive_error(operation: &str, err: ThreadStoreError) -> JSONRPC } } +const MCP_TOOL_THREAD_ID_META_KEY: &str = "threadId"; + +fn with_mcp_tool_call_thread_id_meta( + meta: Option, + thread_id: &str, +) -> Option { + match meta { + Some(serde_json::Value::Object(mut map)) => { + map.insert( + MCP_TOOL_THREAD_ID_META_KEY.to_string(), + serde_json::Value::String(thread_id.to_string()), + ); + Some(serde_json::Value::Object(map)) + } + None => { + let mut map = serde_json::Map::new(); + map.insert( + MCP_TOOL_THREAD_ID_META_KEY.to_string(), + serde_json::Value::String(thread_id.to_string()), + ); + Some(serde_json::Value::Object(map)) + } + other => other, + } +} + fn summary_from_stored_thread( thread: StoredThread, fallback_provider: &str, diff --git a/codex-rs/app-server/tests/suite/v2/mcp_tool.rs b/codex-rs/app-server/tests/suite/v2/mcp_tool.rs index 87556c8364..8a323efcef 100644 --- a/codex-rs/app-server/tests/suite/v2/mcp_tool.rs +++ b/codex-rs/app-server/tests/suite/v2/mcp_tool.rs @@ -83,10 +83,11 @@ url = "{mcp_server_url}/mcp" ) .await??; let ThreadStartResponse { thread, .. } = to_response(thread_start_resp)?; + let thread_id = thread.id.clone(); let tool_call_request_id = mcp .send_mcp_server_tool_call_request(McpServerToolCallParams { - thread_id: thread.id, + thread_id: thread_id.clone(), server: TEST_SERVER_NAME.to_string(), tool: TEST_TOOL_NAME.to_string(), arguments: Some(json!({ @@ -114,6 +115,7 @@ url = "{mcp_server_url}/mcp" response.structured_content, Some(json!({ "echoed": "hello from app", + "threadId": thread_id, })) ); assert_eq!(response.is_error, Some(false)); @@ -203,7 +205,7 @@ impl ServerHandler for ToolAppsMcpServer { async fn call_tool( &self, request: CallToolRequestParams, - _context: RequestContext, + context: RequestContext, ) -> Result { assert_eq!(request.name.as_ref(), TEST_TOOL_NAME); let message = request @@ -212,12 +214,19 @@ impl ServerHandler for ToolAppsMcpServer { .and_then(|arguments| arguments.get("message")) .and_then(|value| value.as_str()) .unwrap_or_default(); + let thread_id = context + .meta + .0 + .get("threadId") + .and_then(|value| value.as_str()) + .unwrap_or_default(); let mut meta = Meta::new(); meta.0.insert("calledBy".to_string(), json!("mcp-app")); let mut result = CallToolResult::structured(json!({ "echoed": message, + "threadId": thread_id, })); result.content = vec![Content::text(format!("echo: {message}"))]; result.meta = Some(meta); diff --git a/codex-rs/core/src/mcp_tool_call.rs b/codex-rs/core/src/mcp_tool_call.rs index c5d4068d27..82bbc7ca4a 100644 --- a/codex-rs/core/src/mcp_tool_call.rs +++ b/codex-rs/core/src/mcp_tool_call.rs @@ -477,6 +477,8 @@ async fn execute_mcp_tool_call( metadata.and_then(|metadata| metadata.openai_file_input_params.as_deref()), ) .await?; + let request_meta = + with_mcp_tool_call_thread_id_meta(request_meta, &sess.conversation_id.to_string()); let request_meta = augment_mcp_tool_request_meta_with_sandbox_state(sess, turn_context, server, request_meta) .await @@ -660,6 +662,7 @@ pub(crate) struct McpToolApprovalMetadata { const MCP_TOOL_CODEX_APPS_META_KEY: &str = "_codex_apps"; const MCP_TOOL_OPENAI_OUTPUT_TEMPLATE_META_KEY: &str = "openai/outputTemplate"; const MCP_TOOL_UI_RESOURCE_URI_META_KEY: &str = "ui/resourceUri"; +const MCP_TOOL_THREAD_ID_META_KEY: &str = "threadId"; fn custom_mcp_tool_approval_mode( turn_context: &TurnContext, @@ -714,6 +717,30 @@ fn build_mcp_tool_call_request_meta( (!request_meta.is_empty()).then_some(serde_json::Value::Object(request_meta)) } +fn with_mcp_tool_call_thread_id_meta( + meta: Option, + thread_id: &str, +) -> Option { + match meta { + Some(serde_json::Value::Object(mut map)) => { + map.insert( + MCP_TOOL_THREAD_ID_META_KEY.to_string(), + serde_json::Value::String(thread_id.to_string()), + ); + Some(serde_json::Value::Object(map)) + } + None => { + let mut map = serde_json::Map::new(); + map.insert( + MCP_TOOL_THREAD_ID_META_KEY.to_string(), + serde_json::Value::String(thread_id.to_string()), + ); + Some(serde_json::Value::Object(map)) + } + other => other, + } +} + #[derive(Clone, Copy)] struct McpToolApprovalPromptOptions { allow_session_remember: bool, diff --git a/codex-rs/core/src/mcp_tool_call_tests.rs b/codex-rs/core/src/mcp_tool_call_tests.rs index 1893ef054c..d20f233951 100644 --- a/codex-rs/core/src/mcp_tool_call_tests.rs +++ b/codex-rs/core/src/mcp_tool_call_tests.rs @@ -651,6 +651,35 @@ async fn codex_apps_tool_call_request_meta_includes_turn_metadata_and_codex_apps ); } +#[test] +fn mcp_tool_call_thread_id_meta_is_added_to_request_meta() { + assert_eq!( + with_mcp_tool_call_thread_id_meta( + Some(serde_json::json!({ + "source": "test-client", + "threadId": "stale-thread", + })), + "thread-live", + ), + Some(serde_json::json!({ + "source": "test-client", + "threadId": "thread-live", + })) + ); + + assert_eq!( + with_mcp_tool_call_thread_id_meta(/*meta*/ None, "thread-live"), + Some(serde_json::json!({ + "threadId": "thread-live", + })) + ); + + assert_eq!( + with_mcp_tool_call_thread_id_meta(Some(serde_json::json!("invalid-meta")), "thread-live"), + Some(serde_json::json!("invalid-meta")) + ); +} + #[test] fn accepted_elicitation_content_converts_to_request_user_input_response() { let response = request_user_input_response_from_elicitation_content(Some(serde_json::json!( From 41652665f52674e3ad4eeb9570b49b073256a14b Mon Sep 17 00:00:00 2001 From: Casey Chow Date: Tue, 21 Apr 2026 13:10:36 -0400 Subject: [PATCH 07/16] [codex] Add tmux-aware OSC 9 notifications (#17836) ## Summary - wrap OSC 9 notifications in tmux's DCS passthrough so terminal notifications make it through tmux - use codex-terminal-detection for OSC 9 auto-selection so tmux sessions inherit the underlying client terminal support - add focused notification backend tests for plain OSC 9 and tmux-wrapped output ## Stack - base PR: #18479 - review order: #18479, then this PR ## Why Tmux does not forward OSC 9 notifications directly; the sequence has to be wrapped in tmux's DCS passthrough envelope. Codex also had local notification heuristics that could miss supported terminals when running under tmux, even though codex-terminal-detection already knows how to attribute tmux sessions to the client terminal. ## Validation - `just fmt` - `cargo test -p codex-tui` *(currently blocked by an unrelated existing compile error in `app-server/src/message_processor.rs:754` referencing `connection_id` out of scope; not caused by this change)* Co-authored-by: Codex --- codex-rs/terminal-detection/src/lib.rs | 8 +- .../terminal-detection/src/terminal_tests.rs | 38 +++++ codex-rs/tui/src/notifications/mod.rs | 131 ++++++++---------- codex-rs/tui/src/notifications/osc9.rs | 100 ++++++++++++- 4 files changed, 192 insertions(+), 85 deletions(-) diff --git a/codex-rs/terminal-detection/src/lib.rs b/codex-rs/terminal-detection/src/lib.rs index 69b6f47459..6474c189a5 100644 --- a/codex-rs/terminal-detection/src/lib.rs +++ b/codex-rs/terminal-detection/src/lib.rs @@ -145,10 +145,10 @@ impl TerminalInfo { /// Creates terminal metadata from a `TERM` capability value. fn from_term(term: String, multiplexer: Option) -> Self { - let name = if term == "dumb" { - TerminalName::Dumb - } else { - TerminalName::Unknown + let name = match term.as_str() { + "dumb" => TerminalName::Dumb, + "wezterm" | "wezterm-mux" => TerminalName::WezTerm, + _ => TerminalName::Unknown, }; Self::new( name, diff --git a/codex-rs/terminal-detection/src/terminal_tests.rs b/codex-rs/terminal-detection/src/terminal_tests.rs index 52974a0c31..ec2a3b1166 100644 --- a/codex-rs/terminal-detection/src/terminal_tests.rs +++ b/codex-rs/terminal-detection/src/terminal_tests.rs @@ -456,6 +456,44 @@ fn detects_wezterm() { "WezTerm", "wezterm_empty_user_agent" ); + + let env = FakeEnvironment::new().with_var("TERM", "wezterm"); + let terminal = detect_terminal_info_from_env(&env); + assert_eq!( + terminal, + terminal_info( + TerminalName::WezTerm, + /*term_program*/ None, + /*version*/ None, + Some("wezterm"), + /*multiplexer*/ None + ), + "wezterm_term_info" + ); + assert_eq!( + terminal.user_agent_token(), + "wezterm", + "wezterm_term_user_agent" + ); + + let env = FakeEnvironment::new().with_var("TERM", "wezterm-mux"); + let terminal = detect_terminal_info_from_env(&env); + assert_eq!( + terminal, + terminal_info( + TerminalName::WezTerm, + /*term_program*/ None, + /*version*/ None, + Some("wezterm-mux"), + /*multiplexer*/ None + ), + "wezterm_mux_term_info" + ); + assert_eq!( + terminal.user_agent_token(), + "wezterm-mux", + "wezterm_mux_term_user_agent" + ); } #[test] diff --git a/codex-rs/tui/src/notifications/mod.rs b/codex-rs/tui/src/notifications/mod.rs index e5e1085995..ee6694b357 100644 --- a/codex-rs/tui/src/notifications/mod.rs +++ b/codex-rs/tui/src/notifications/mod.rs @@ -1,11 +1,13 @@ mod bel; mod osc9; -use std::env; use std::io; use bel::BelBackend; use codex_config::types::NotificationMethod; +use codex_terminal_detection::TerminalInfo; +use codex_terminal_detection::TerminalName; +use codex_terminal_detection::terminal_info; use osc9::Osc9Backend; #[derive(Debug)] @@ -18,13 +20,13 @@ impl DesktopNotificationBackend { pub fn for_method(method: NotificationMethod) -> Self { match method { NotificationMethod::Auto => { - if supports_osc9() { - Self::Osc9(Osc9Backend) + if supports_osc9(&terminal_info()) { + Self::Osc9(Osc9Backend::new()) } else { Self::Bel(BelBackend) } } - NotificationMethod::Osc9 => Self::Osc9(Osc9Backend), + NotificationMethod::Osc9 => Self::Osc9(Osc9Backend::new()), NotificationMethod::Bel => Self::Bel(BelBackend), } } @@ -48,67 +50,33 @@ pub fn detect_backend(method: NotificationMethod) -> DesktopNotificationBackend DesktopNotificationBackend::for_method(method) } -fn supports_osc9() -> bool { - if env::var_os("WT_SESSION").is_some() { - return false; - } - // Prefer TERM_PROGRAM when present, but keep fallbacks for shells/launchers - // that don't set it (e.g., tmux/ssh) to avoid regressing OSC 9 support. - if matches!( - env::var("TERM_PROGRAM").ok().as_deref(), - Some("WezTerm" | "WarpTerminal" | "ghostty") - ) { - return true; - } - // iTerm still provides a strong session signal even when TERM_PROGRAM is missing. - if env::var_os("ITERM_SESSION_ID").is_some() { - return true; - } - // TERM-based hints cover kitty/wezterm setups without TERM_PROGRAM. +fn supports_osc9(terminal: &TerminalInfo) -> bool { matches!( - env::var("TERM").ok().as_deref(), - Some("xterm-kitty" | "wezterm" | "wezterm-mux") + terminal.name, + TerminalName::Ghostty + | TerminalName::Iterm2 + | TerminalName::Kitty + | TerminalName::WarpTerminal + | TerminalName::WezTerm ) } #[cfg(test)] mod tests { use super::detect_backend; + use super::supports_osc9; use codex_config::types::NotificationMethod; - use serial_test::serial; - use std::ffi::OsString; + use codex_terminal_detection::TerminalInfo; + use codex_terminal_detection::TerminalName; + use pretty_assertions::assert_eq; - struct EnvVarGuard { - key: &'static str, - original: Option, - } - - impl EnvVarGuard { - fn set(key: &'static str, value: &str) -> Self { - let original = std::env::var_os(key); - unsafe { - std::env::set_var(key, value); - } - Self { key, original } - } - - fn remove(key: &'static str) -> Self { - let original = std::env::var_os(key); - unsafe { - std::env::remove_var(key); - } - Self { key, original } - } - } - - impl Drop for EnvVarGuard { - fn drop(&mut self) { - unsafe { - match &self.original { - Some(value) => std::env::set_var(self.key, value), - None => std::env::remove_var(self.key), - } - } + fn test_terminal(name: TerminalName) -> TerminalInfo { + TerminalInfo { + name, + term_program: None, + version: None, + term: None, + multiplexer: None, } } @@ -129,28 +97,39 @@ mod tests { } #[test] - #[serial] - fn auto_prefers_bel_without_hints() { - let _term = EnvVarGuard::remove("TERM"); - let _term_program = EnvVarGuard::remove("TERM_PROGRAM"); - let _iterm = EnvVarGuard::remove("ITERM_SESSION_ID"); - let _wt = EnvVarGuard::remove("WT_SESSION"); - assert!(matches!( - detect_backend(NotificationMethod::Auto), - super::DesktopNotificationBackend::Bel(_) - )); + fn supports_osc9_for_supported_terminals() { + for name in [ + TerminalName::Ghostty, + TerminalName::Iterm2, + TerminalName::Kitty, + TerminalName::WarpTerminal, + TerminalName::WezTerm, + ] { + assert!( + supports_osc9(&test_terminal(name)), + "{name:?} should support OSC 9" + ); + } } #[test] - #[serial] - fn auto_uses_osc9_for_iterm() { - let _term = EnvVarGuard::remove("TERM"); - let _term_program = EnvVarGuard::remove("TERM_PROGRAM"); - let _iterm = EnvVarGuard::set("ITERM_SESSION_ID", "abc"); - let _wt = EnvVarGuard::remove("WT_SESSION"); - assert!(matches!( - detect_backend(NotificationMethod::Auto), - super::DesktopNotificationBackend::Osc9(_) - )); + fn supports_osc9_for_unsupported_terminals() { + for name in [ + TerminalName::AppleTerminal, + TerminalName::Alacritty, + TerminalName::Dumb, + TerminalName::GnomeTerminal, + TerminalName::Konsole, + TerminalName::Unknown, + TerminalName::VsCode, + TerminalName::Vte, + TerminalName::WindowsTerminal, + ] { + assert_eq!( + supports_osc9(&test_terminal(name)), + false, + "{name:?} should not support OSC 9" + ); + } } } diff --git a/codex-rs/tui/src/notifications/osc9.rs b/codex-rs/tui/src/notifications/osc9.rs index fd3a8ad96d..bbe48f176f 100644 --- a/codex-rs/tui/src/notifications/osc9.rs +++ b/codex-rs/tui/src/notifications/osc9.rs @@ -2,25 +2,55 @@ use std::fmt; use std::io; use std::io::stdout; +use codex_terminal_detection::Multiplexer; +use codex_terminal_detection::terminal_info; use crossterm::Command; use ratatui::crossterm::execute; -#[derive(Debug, Default)] -pub struct Osc9Backend; +#[derive(Debug)] +pub struct Osc9Backend { + dcs_passthrough: bool, +} + +impl Default for Osc9Backend { + fn default() -> Self { + Self::new() + } +} impl Osc9Backend { + pub fn new() -> Self { + Self { + dcs_passthrough: matches!(terminal_info().multiplexer, Some(Multiplexer::Tmux { .. })), + } + } + pub fn notify(&mut self, message: &str) -> io::Result<()> { - execute!(stdout(), PostNotification(message.to_string())) + execute!( + stdout(), + PostNotification { + message: message.to_string(), + dcs_passthrough: self.dcs_passthrough, + } + ) } } /// Command that emits an OSC 9 desktop notification with a message. #[derive(Debug, Clone)] -pub struct PostNotification(pub String); +pub struct PostNotification { + pub message: String, + pub dcs_passthrough: bool, +} impl Command for PostNotification { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - write!(f, "\x1b]9;{}\x07", self.0) + if self.dcs_passthrough { + let escaped_message = escape_tmux_dcs_passthrough_payload(&self.message); + write!(f, "\x1bPtmux;\x1b\x1b]9;{escaped_message}\x07\x1b\\") + } else { + write!(f, "\x1b]9;{}\x07", self.message) + } } #[cfg(windows)] @@ -35,3 +65,63 @@ impl Command for PostNotification { true } } + +fn escape_tmux_dcs_passthrough_payload(message: &str) -> String { + message.replace('\u{1b}', "\u{1b}\u{1b}") +} + +#[cfg(test)] +mod tests { + use crossterm::Command; + use pretty_assertions::assert_eq; + + use super::PostNotification; + + #[test] + fn post_notification_writes_plain_osc9_sequence() { + let mut ansi = String::new(); + let command = PostNotification { + message: "hello".to_string(), + dcs_passthrough: false, + }; + + command + .write_ansi(&mut ansi) + .expect("OSC 9 command should format"); + + assert_eq!(ansi, "\u{1b}]9;hello\u{7}"); + } + + #[test] + fn post_notification_writes_tmux_dcs_wrapped_osc9_sequence() { + let mut ansi = String::new(); + let command = PostNotification { + message: "done".to_string(), + dcs_passthrough: true, + }; + + command + .write_ansi(&mut ansi) + .expect("OSC 9 command should format"); + + assert_eq!(ansi, "\u{1b}Ptmux;\u{1b}\u{1b}]9;done\u{7}\u{1b}\\"); + } + + #[test] + fn post_notification_escapes_escape_bytes_inside_tmux_payload() { + let mut ansi = String::new(); + let command = PostNotification { + message: "danger\u{1b}[31m".to_string(), + dcs_passthrough: true, + }; + + command + .write_ansi(&mut ansi) + .expect("OSC 9 command should format"); + + assert_eq!( + ansi, + "\u{1b}Ptmux;\u{1b}\u{1b}]9;danger\u{1b}\u{1b}[31m\u{7}\u{1b}\\" + ); + } +} From b7fec54354096e3c1884de67b8f2ffbdf836f63e Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Tue, 21 Apr 2026 10:13:13 -0700 Subject: [PATCH 08/16] Queue follow-up input during user shell commands (#18820) Fixes #17954. ## Why When a manual shell command like `!sleep 10` is running, submitting plain text such as `hi` currently sends that text as a steer for the active shell turn. User shell turns are not steerable like model turns, so the TUI can remain stuck in `Working` after the shell command finishes. ## What Changed - Detect when the only active work is one or more `ExecCommandSource::UserShell` commands. - Queue plain submitted input in that state so it drains after the shell command and shell turn complete. - Preserve `!cmd` submissions during running work so explicit shell commands keep their existing behavior. - Add regression coverage for the `!sleep 10` plus `hi` flow in `chatwidget::tests::exec_flow::user_message_during_user_shell_command_is_queued_not_steered`. ## Verification - Manually confirmed hang before the fix and no hang after the fix --- codex-rs/tui/src/chatwidget.rs | 15 ++++++ .../tui/src/chatwidget/tests/exec_flow.rs | 52 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 860846f281..d226fa3c8a 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -5411,6 +5411,12 @@ impl ChatWidget { let should_submit_now = self.is_session_configured() && !self.is_plan_streaming_in_tui(); if should_submit_now { + if self.only_user_shell_commands_running() + && !user_message.text.starts_with('!') + { + self.queue_user_message(user_message); + return; + } // Submitted is emitted when user submits. // Reset any reasoning header only when we are actually submitting a turn. self.reasoning_buffer.clear(); @@ -7476,6 +7482,15 @@ impl ChatWidget { self.user_turn_pending_start || self.bottom_pane.is_task_running() } + fn only_user_shell_commands_running(&self) -> bool { + self.agent_turn_running + && !self.running_commands.is_empty() + && self + .running_commands + .values() + .all(|command| command.source == ExecCommandSource::UserShell) + } + /// Rebuild and update the bottom-pane pending-input preview. fn refresh_pending_input_preview(&mut self) { let queued_messages: Vec = self diff --git a/codex-rs/tui/src/chatwidget/tests/exec_flow.rs b/codex-rs/tui/src/chatwidget/tests/exec_flow.rs index 9f181d3d21..04a5fa44ba 100644 --- a/codex-rs/tui/src/chatwidget/tests/exec_flow.rs +++ b/codex-rs/tui/src/chatwidget/tests/exec_flow.rs @@ -1036,6 +1036,58 @@ async fn bang_shell_enter_while_task_running_submits_run_user_shell_command() { assert_matches!(rx.try_recv(), Err(TryRecvError::Empty)); } +#[tokio::test] +async fn user_message_during_user_shell_command_is_queued_not_steered() { + let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await; + chat.thread_id = Some(ThreadId::new()); + chat.handle_codex_event(Event { + id: "turn-start".into(), + msg: EventMsg::TurnStarted(TurnStartedEvent { + turn_id: "turn-1".to_string(), + started_at: None, + model_context_window: None, + collaboration_mode_kind: ModeKind::Default, + }), + }); + let begin = begin_exec_with_source( + &mut chat, + "user-shell-sleep", + "sleep 10", + ExecCommandSource::UserShell, + ); + + assert!(chat.only_user_shell_commands_running()); + chat.bottom_pane + .set_composer_text("hi".to_string(), Vec::new(), Vec::new()); + chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)); + + assert_matches!(op_rx.try_recv(), Err(TryRecvError::Empty)); + assert_eq!(chat.queued_user_message_texts(), vec!["hi".to_string()]); + + end_exec(&mut chat, begin, "", "", /*exit_code*/ 0); + chat.handle_codex_event(Event { + id: "turn-complete".into(), + msg: EventMsg::TurnComplete(TurnCompleteEvent { + turn_id: "turn-1".to_string(), + last_agent_message: Some("done".to_string()), + completed_at: None, + duration_ms: None, + }), + }); + + match next_submit_op(&mut op_rx) { + Op::UserTurn { items, .. } => assert_eq!( + items, + vec![UserInput::Text { + text: "hi".to_string(), + text_elements: Vec::new(), + }] + ), + other => panic!("expected queued user message after shell completion, got {other:?}"), + } + assert!(chat.queued_user_messages.is_empty()); +} + #[tokio::test] async fn disabled_slash_command_while_task_running_snapshot() { // Build a chat widget and simulate an active task From 10e1659d4fcd8960315ceab9da138f94320b4986 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Tue, 21 Apr 2026 18:15:37 +0100 Subject: [PATCH 09/16] Stabilize debug clear memories integration test (#18858) ## Why `debug_clear_memories_resets_state_and_removes_memory_dir` can be flaky because the test drops its `sqlx::SqlitePool` immediately before invoking `codex debug clear-memories`. Dropping the pool does not wait for all SQLite connections to close, so the CLI can race with still-open test connections. ## What changed - Await `pool.close()` before spawning `codex debug clear-memories`. - Close the reopened verification pool before the temp `CODEX_HOME` is torn down. ## Verification - `cargo test -p codex-cli --test debug_clear_memories debug_clear_memories_resets_state_and_removes_memory_dir` --- codex-rs/cli/tests/debug_clear_memories.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codex-rs/cli/tests/debug_clear_memories.rs b/codex-rs/cli/tests/debug_clear_memories.rs index a24f7ebdde..9d5e114dbf 100644 --- a/codex-rs/cli/tests/debug_clear_memories.rs +++ b/codex-rs/cli/tests/debug_clear_memories.rs @@ -105,7 +105,7 @@ INSERT INTO jobs ( let memory_root = codex_home.path().join("memories"); std::fs::create_dir_all(&memory_root)?; std::fs::write(memory_root.join("memory_summary.md"), "stale memory")?; - drop(pool); + pool.close().await; let mut cmd = codex_command(codex_home.path())?; cmd.args(["debug", "clear-memories"]) @@ -127,6 +127,7 @@ INSERT INTO jobs ( assert_eq!(memory_jobs_count, 0); assert!(memory_root.exists()); assert_eq!(std::fs::read_dir(memory_root)?.count(), 0); + pool.close().await; Ok(()) } From 4ed722ab8d2fd195c652a7cbfcc31844318cd4ea Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Tue, 21 Apr 2026 10:16:51 -0700 Subject: [PATCH 10/16] Move TUI app tests to modules they cover (#18799) ## Summary The TUI app refactor in #18753 moved the old `app.rs` tests into a single `app/tests.rs` file. That kept the split mechanically simple, but it left several focused unit tests far from the modules they exercise. This PR is a follow-up that moves tests next to the code they cover. It also adds `tui/src/app/test_support.rs` for shared fixture construction. This is just a mechanical refactoring (no functional changes) and does not affect any production code. --- codex-rs/tui/src/app.rs | 4 +- codex-rs/tui/src/app/background_requests.rs | 145 +++++ codex-rs/tui/src/app/config_persistence.rs | 173 ++++++ codex-rs/tui/src/app/platform_actions.rs | 35 ++ codex-rs/tui/src/app/session_lifecycle.rs | 58 ++ codex-rs/tui/src/app/side.rs | 51 ++ codex-rs/tui/src/app/startup_prompts.rs | 28 + codex-rs/tui/src/app/test_support.rs | 89 +++ codex-rs/tui/src/app/tests.rs | 626 -------------------- codex-rs/tui/src/app/thread_events.rs | 274 +++++++++ 10 files changed, 855 insertions(+), 628 deletions(-) create mode 100644 codex-rs/tui/src/app/test_support.rs diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 80e8f4a0f1..f2dfd5fa2b 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -200,8 +200,6 @@ mod thread_session_state; use self::agent_navigation::AgentNavigationDirection; use self::agent_navigation::AgentNavigationState; use self::app_server_requests::PendingAppServerRequests; -#[cfg(test)] -use self::background_requests::*; use self::loaded_threads::find_loaded_subagent_threads_for_primary; use self::pending_interactive_replay::PendingInteractiveReplayState; use self::platform_actions::*; @@ -1152,5 +1150,7 @@ impl Drop for App { } } +#[cfg(test)] +pub(super) mod test_support; #[cfg(test)] mod tests; diff --git a/codex-rs/tui/src/app/background_requests.rs b/codex-rs/tui/src/app/background_requests.rs index 9fc741ff5c..80879dbd79 100644 --- a/codex-rs/tui/src/app/background_requests.rs +++ b/codex-rs/tui/src/app/background_requests.rs @@ -637,3 +637,148 @@ pub(super) fn mcp_inventory_maps_from_statuses(statuses: Vec) - (tools, resources, resource_templates, auth_statuses) } + +#[cfg(test)] +mod tests { + use super::*; + use codex_app_server_protocol::PluginMarketplaceEntry; + use codex_protocol::mcp::Tool; + use codex_utils_absolute_path::AbsolutePathBuf; + use pretty_assertions::assert_eq; + + fn test_absolute_path(path: &str) -> AbsolutePathBuf { + AbsolutePathBuf::try_from(PathBuf::from(path)).expect("absolute test path") + } + + #[test] + fn hide_cli_only_plugin_marketplaces_removes_openai_bundled() { + let mut response = PluginListResponse { + marketplaces: vec![ + PluginMarketplaceEntry { + name: "openai-bundled".to_string(), + path: Some(test_absolute_path("/marketplaces/openai-bundled")), + interface: None, + plugins: Vec::new(), + }, + PluginMarketplaceEntry { + name: "openai-curated".to_string(), + path: Some(test_absolute_path("/marketplaces/openai-curated")), + interface: None, + plugins: Vec::new(), + }, + ], + marketplace_load_errors: Vec::new(), + featured_plugin_ids: Vec::new(), + }; + + hide_cli_only_plugin_marketplaces(&mut response); + + assert_eq!( + response.marketplaces, + vec![PluginMarketplaceEntry { + name: "openai-curated".to_string(), + path: Some(test_absolute_path("/marketplaces/openai-curated")), + interface: None, + plugins: Vec::new(), + }] + ); + } + + #[test] + fn mcp_inventory_maps_prefix_tool_names_by_server() { + let statuses = vec![ + McpServerStatus { + name: "docs".to_string(), + tools: HashMap::from([( + "list".to_string(), + Tool { + description: None, + name: "list".to_string(), + title: None, + input_schema: serde_json::json!({"type": "object"}), + output_schema: None, + annotations: None, + icons: None, + meta: None, + }, + )]), + resources: Vec::new(), + resource_templates: Vec::new(), + auth_status: codex_app_server_protocol::McpAuthStatus::Unsupported, + }, + McpServerStatus { + name: "disabled".to_string(), + tools: HashMap::new(), + resources: Vec::new(), + resource_templates: Vec::new(), + auth_status: codex_app_server_protocol::McpAuthStatus::Unsupported, + }, + ]; + + let (tools, resources, resource_templates, auth_statuses) = + mcp_inventory_maps_from_statuses(statuses); + let mut resource_names = resources.keys().cloned().collect::>(); + resource_names.sort(); + let mut template_names = resource_templates.keys().cloned().collect::>(); + template_names.sort(); + + assert_eq!( + tools.keys().cloned().collect::>(), + vec!["mcp__docs__list".to_string()] + ); + assert_eq!(resource_names, vec!["disabled", "docs"]); + assert_eq!(template_names, vec!["disabled", "docs"]); + assert_eq!( + auth_statuses.get("disabled"), + Some(&McpAuthStatus::Unsupported) + ); + } + + #[test] + fn build_feedback_upload_params_includes_thread_id_and_rollout_path() { + let thread_id = ThreadId::new(); + let rollout_path = PathBuf::from("/tmp/rollout.jsonl"); + + let params = build_feedback_upload_params( + Some(thread_id), + Some(rollout_path.clone()), + FeedbackCategory::SafetyCheck, + Some("needs follow-up".to_string()), + Some("turn-123".to_string()), + /*include_logs*/ true, + ); + + assert_eq!(params.classification, "safety_check"); + assert_eq!(params.reason, Some("needs follow-up".to_string())); + assert_eq!(params.thread_id, Some(thread_id.to_string())); + assert_eq!( + params + .tags + .as_ref() + .and_then(|tags| tags.get("turn_id")) + .map(String::as_str), + Some("turn-123") + ); + assert_eq!(params.include_logs, true); + assert_eq!(params.extra_log_files, Some(vec![rollout_path])); + } + + #[test] + fn build_feedback_upload_params_omits_rollout_path_without_logs() { + let params = build_feedback_upload_params( + /*origin_thread_id*/ None, + Some(PathBuf::from("/tmp/rollout.jsonl")), + FeedbackCategory::GoodResult, + /*reason*/ None, + /*turn_id*/ None, + /*include_logs*/ false, + ); + + assert_eq!(params.classification, "good_result"); + assert_eq!(params.reason, None); + assert_eq!(params.thread_id, None); + assert_eq!(params.tags, None); + assert_eq!(params.include_logs, false); + assert_eq!(params.extra_log_files, None); + } +} diff --git a/codex-rs/tui/src/app/config_persistence.rs b/codex-rs/tui/src/app/config_persistence.rs index c909a37c1f..edfde7ceeb 100644 --- a/codex-rs/tui/src/app/config_persistence.rs +++ b/codex-rs/tui/src/app/config_persistence.rs @@ -537,3 +537,176 @@ impl App { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::app::test_support::app_enabled_in_effective_config; + use crate::app::test_support::make_test_app; + use crate::test_support::PathBufExt; + use codex_protocol::protocol::Event; + use codex_protocol::protocol::EventMsg; + use codex_protocol::protocol::SessionConfiguredEvent; + use pretty_assertions::assert_eq; + use tempfile::tempdir; + + #[tokio::test] + async fn update_reasoning_effort_updates_collaboration_mode() { + let mut app = make_test_app().await; + app.chat_widget + .set_reasoning_effort(Some(ReasoningEffortConfig::Medium)); + + app.on_update_reasoning_effort(Some(ReasoningEffortConfig::High)); + + assert_eq!( + app.chat_widget.current_reasoning_effort(), + Some(ReasoningEffortConfig::High) + ); + assert_eq!( + app.config.model_reasoning_effort, + Some(ReasoningEffortConfig::High) + ); + } + + #[tokio::test] + async fn refresh_in_memory_config_from_disk_loads_latest_apps_state() -> Result<()> { + let mut app = make_test_app().await; + let codex_home = tempdir()?; + app.config.codex_home = codex_home.path().to_path_buf().abs(); + let app_id = "unit_test_refresh_in_memory_config_connector".to_string(); + + assert_eq!(app_enabled_in_effective_config(&app.config, &app_id), None); + + ConfigEditsBuilder::new(&app.config.codex_home) + .with_edits([ + ConfigEdit::SetPath { + segments: vec!["apps".to_string(), app_id.clone(), "enabled".to_string()], + value: false.into(), + }, + ConfigEdit::SetPath { + segments: vec![ + "apps".to_string(), + app_id.clone(), + "disabled_reason".to_string(), + ], + value: "user".into(), + }, + ]) + .apply() + .await + .expect("persist app toggle"); + + assert_eq!(app_enabled_in_effective_config(&app.config, &app_id), None); + + app.refresh_in_memory_config_from_disk().await?; + + assert_eq!( + app_enabled_in_effective_config(&app.config, &app_id), + Some(false) + ); + Ok(()) + } + + #[tokio::test] + async fn refresh_in_memory_config_from_disk_best_effort_keeps_current_config_on_error() + -> Result<()> { + let mut app = make_test_app().await; + let codex_home = tempdir()?; + app.config.codex_home = codex_home.path().to_path_buf().abs(); + std::fs::write(codex_home.path().join("config.toml"), "[broken")?; + let original_config = app.config.clone(); + + app.refresh_in_memory_config_from_disk_best_effort("starting a new thread") + .await; + + assert_eq!(app.config, original_config); + Ok(()) + } + + #[tokio::test] + async fn refresh_in_memory_config_from_disk_uses_active_chat_widget_cwd() -> Result<()> { + let mut app = make_test_app().await; + let original_cwd = app.config.cwd.clone(); + let next_cwd_tmp = tempdir()?; + let next_cwd = next_cwd_tmp.path().to_path_buf(); + + app.chat_widget.handle_codex_event(Event { + id: String::new(), + msg: EventMsg::SessionConfigured(SessionConfiguredEvent { + session_id: ThreadId::new(), + forked_from_id: None, + thread_name: None, + model: "gpt-test".to_string(), + model_provider_id: "test-provider".to_string(), + service_tier: None, + approval_policy: AskForApproval::Never, + approvals_reviewer: ApprovalsReviewer::User, + sandbox_policy: SandboxPolicy::new_read_only_policy(), + cwd: next_cwd.clone().abs(), + reasoning_effort: None, + history_log_id: 0, + history_entry_count: 0, + initial_messages: None, + network_proxy: None, + rollout_path: Some(PathBuf::new()), + }), + }); + + assert_eq!(app.chat_widget.config_ref().cwd.to_path_buf(), next_cwd); + assert_eq!(app.config.cwd, original_cwd); + + app.refresh_in_memory_config_from_disk().await?; + + assert_eq!(app.config.cwd, app.chat_widget.config_ref().cwd); + Ok(()) + } + + #[tokio::test] + async fn rebuild_config_for_resume_or_fallback_uses_current_config_on_same_cwd_error() + -> Result<()> { + let mut app = make_test_app().await; + let codex_home = tempdir()?; + app.config.codex_home = codex_home.path().to_path_buf().abs(); + std::fs::write(codex_home.path().join("config.toml"), "[broken")?; + let current_config = app.config.clone(); + let current_cwd = current_config.cwd.clone(); + + let resume_config = app + .rebuild_config_for_resume_or_fallback(¤t_cwd, current_cwd.to_path_buf()) + .await?; + + assert_eq!(resume_config, current_config); + Ok(()) + } + + #[tokio::test] + async fn rebuild_config_for_resume_or_fallback_errors_when_cwd_changes() -> Result<()> { + let mut app = make_test_app().await; + let codex_home = tempdir()?; + app.config.codex_home = codex_home.path().to_path_buf().abs(); + std::fs::write(codex_home.path().join("config.toml"), "[broken")?; + let current_cwd = app.config.cwd.clone(); + let next_cwd_tmp = tempdir()?; + let next_cwd = next_cwd_tmp.path().to_path_buf(); + + let result = app + .rebuild_config_for_resume_or_fallback(¤t_cwd, next_cwd) + .await; + + assert!(result.is_err()); + Ok(()) + } + + #[tokio::test] + async fn sync_tui_theme_selection_updates_chat_widget_config_copy() { + let mut app = make_test_app().await; + + app.sync_tui_theme_selection("dracula".to_string()); + + assert_eq!(app.config.tui_theme.as_deref(), Some("dracula")); + assert_eq!( + app.chat_widget.config_ref().tui_theme.as_deref(), + Some("dracula") + ); + } +} diff --git a/codex-rs/tui/src/app/platform_actions.rs b/codex-rs/tui/src/app/platform_actions.rs index 36559feb94..b8e2888352 100644 --- a/codex-rs/tui/src/app/platform_actions.rs +++ b/codex-rs/tui/src/app/platform_actions.rs @@ -59,3 +59,38 @@ pub(super) fn side_return_shortcut_matches(key_event: KeyEvent) -> bool { _ => false, } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn side_return_shortcuts_match_esc_and_ctrl_c() { + assert!(side_return_shortcut_matches(KeyEvent::new( + KeyCode::Esc, + KeyModifiers::NONE, + ))); + assert!(side_return_shortcut_matches(KeyEvent::new_with_kind( + KeyCode::Esc, + KeyModifiers::NONE, + KeyEventKind::Repeat, + ))); + assert!(side_return_shortcut_matches(KeyEvent::new( + KeyCode::Char('c'), + KeyModifiers::CONTROL, + ))); + assert!(side_return_shortcut_matches(KeyEvent::new( + KeyCode::Char('C'), + KeyModifiers::CONTROL, + ))); + assert!(!side_return_shortcut_matches(KeyEvent::new( + KeyCode::Char('d'), + KeyModifiers::CONTROL, + ))); + assert!(!side_return_shortcut_matches(KeyEvent::new_with_kind( + KeyCode::Esc, + KeyModifiers::NONE, + KeyEventKind::Release, + ))); + } +} diff --git a/codex-rs/tui/src/app/session_lifecycle.rs b/codex-rs/tui/src/app/session_lifecycle.rs index 0551d32538..89d5e1d43e 100644 --- a/codex-rs/tui/src/app/session_lifecycle.rs +++ b/codex-rs/tui/src/app/session_lifecycle.rs @@ -730,3 +730,61 @@ impl App { Ok(AppRunControl::Continue) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn terminal_thread_read_error_detection_matches_not_loaded_errors() { + let err = color_eyre::eyre::eyre!( + "thread/read failed during TUI session lookup: thread/read failed: thread not loaded: thr_123" + ); + + assert!(App::is_terminal_thread_read_error(&err)); + } + + #[test] + fn terminal_thread_read_error_detection_ignores_transient_failures() { + let err = color_eyre::eyre::eyre!( + "thread/read failed during TUI session lookup: thread/read transport error: broken pipe" + ); + + assert!(!App::is_terminal_thread_read_error(&err)); + } + + #[test] + fn closed_state_for_thread_read_error_preserves_live_state_without_cache_on_transient_error() { + let err = color_eyre::eyre::eyre!( + "thread/read failed during TUI session lookup: thread/read transport error: broken pipe" + ); + + assert!(!App::closed_state_for_thread_read_error( + &err, /*existing_is_closed*/ None + )); + } + + #[test] + fn closed_state_for_thread_read_error_marks_terminal_uncached_threads_closed() { + let err = color_eyre::eyre::eyre!( + "thread/read failed during TUI session lookup: thread/read failed: thread not loaded: thr_123" + ); + + assert!(App::closed_state_for_thread_read_error( + &err, /*existing_is_closed*/ None + )); + } + + #[test] + fn include_turns_fallback_detection_handles_unmaterialized_and_ephemeral_threads() { + let unmaterialized = color_eyre::eyre::eyre!( + "thread/read failed during TUI session lookup: thread/read failed: thread thr_123 is not materialized yet; includeTurns is unavailable before first user message" + ); + let ephemeral = color_eyre::eyre::eyre!( + "thread/read failed during TUI session lookup: thread/read failed: ephemeral threads do not support includeTurns" + ); + + assert!(App::can_fallback_from_include_turns_error(&unmaterialized)); + assert!(App::can_fallback_from_include_turns_error(&ephemeral)); + } +} diff --git a/codex-rs/tui/src/app/side.rs b/codex-rs/tui/src/app/side.rs index e951233c5d..4ca3785ebf 100644 --- a/codex-rs/tui/src/app/side.rs +++ b/codex-rs/tui/src/app/side.rs @@ -97,6 +97,57 @@ impl SideParentStatus { } } +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn side_boundary_prompt_marks_inherited_history_reference_only() { + let item = App::side_boundary_prompt_item(); + let ResponseItem::Message { role, content, .. } = item else { + panic!("expected hidden side boundary prompt to be a user message"); + }; + assert_eq!(role, "user"); + let [ContentItem::InputText { text }] = content.as_slice() else { + panic!("expected hidden side boundary prompt text"); + }; + assert!(text.contains("Side conversation boundary.")); + assert!(text.contains("Everything before this boundary is inherited history")); + assert!(text.contains("It is not your current task.")); + assert!(text.contains("Only messages submitted after this boundary are active")); + assert!(text.contains("Do not continue, execute, or complete")); + assert!(text.contains("separate from the main thread")); + assert!( + text.contains("External tools may be available according to this thread's current") + ); + assert!(text.contains("Any tool calls or outputs visible before this boundary happened")); + assert!(text.contains("Do not modify files")); + } + + #[test] + fn side_start_error_message_explains_missing_first_prompt() { + let err = color_eyre::eyre::eyre!( + "thread/fork failed during TUI bootstrap: thread/fork failed: no rollout found for thread id 019da1a1-bed9-7a43-88a2-b49d43915021" + ); + + assert_eq!( + App::side_start_error_message(&err), + "'/side' is unavailable until the current conversation has started. Send a message first, then try /side again." + ); + } + + #[test] + fn side_start_error_message_uses_generic_start_wording() { + let err = color_eyre::eyre::eyre!("transport disconnected"); + + assert_eq!( + App::side_start_error_message(&err), + "Failed to start side conversation: transport disconnected" + ); + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(super) enum SideParentStatusChange { Set(SideParentStatus), diff --git a/codex-rs/tui/src/app/startup_prompts.rs b/codex-rs/tui/src/app/startup_prompts.rs index 23c116ee5f..c5bc541a03 100644 --- a/codex-rs/tui/src/app/startup_prompts.rs +++ b/codex-rs/tui/src/app/startup_prompts.rs @@ -324,3 +324,31 @@ pub(super) fn normalize_harness_overrides_for_cwd( overrides.additional_writable_roots = normalized; Ok(overrides) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_support::PathBufExt; + use pretty_assertions::assert_eq; + use std::path::PathBuf; + use tempfile::tempdir; + + #[test] + fn normalize_harness_overrides_resolves_relative_add_dirs() -> Result<()> { + let temp_dir = tempdir()?; + let base_cwd = temp_dir.path().join("base").abs(); + std::fs::create_dir_all(base_cwd.as_path())?; + + let overrides = ConfigOverrides { + additional_writable_roots: vec![PathBuf::from("rel")], + ..Default::default() + }; + let normalized = normalize_harness_overrides_for_cwd(overrides, &base_cwd)?; + + assert_eq!( + normalized.additional_writable_roots, + vec![base_cwd.join("rel").into_path_buf()] + ); + Ok(()) + } +} diff --git a/codex-rs/tui/src/app/test_support.rs b/codex-rs/tui/src/app/test_support.rs new file mode 100644 index 0000000000..8b2dfe8d47 --- /dev/null +++ b/codex-rs/tui/src/app/test_support.rs @@ -0,0 +1,89 @@ +//! Shared App fixtures for app submodule unit tests. +//! +//! This module keeps heavyweight `App` construction and config-inspection helpers available to +//! focused sibling test modules without making `app/tests.rs` the only practical place to test +//! app-owned behavior. + +use super::*; +use crate::chatwidget::tests::make_chatwidget_manual_with_sender; + +pub(super) async fn make_test_app() -> App { + let (chat_widget, app_event_tx, _rx, _op_rx) = make_chatwidget_manual_with_sender().await; + let config = chat_widget.config_ref().clone(); + let file_search = FileSearchManager::new(config.cwd.to_path_buf(), app_event_tx.clone()); + let model = crate::legacy_core::test_support::get_model_offline(config.model.as_deref()); + let session_telemetry = test_session_telemetry(&config, model.as_str()); + + App { + model_catalog: chat_widget.model_catalog(), + session_telemetry, + app_event_tx, + chat_widget, + config, + active_profile: None, + cli_kv_overrides: Vec::new(), + harness_overrides: ConfigOverrides::default(), + runtime_approval_policy_override: None, + runtime_sandbox_policy_override: None, + file_search, + transcript_cells: Vec::new(), + overlay: None, + deferred_history_lines: Vec::new(), + has_emitted_history_lines: false, + enhanced_keys_supported: false, + commit_anim_running: Arc::new(AtomicBool::new(false)), + status_line_invalid_items_warned: Arc::new(AtomicBool::new(false)), + terminal_title_invalid_items_warned: Arc::new(AtomicBool::new(false)), + backtrack: BacktrackState::default(), + backtrack_render_pending: false, + feedback: codex_feedback::CodexFeedback::new(), + feedback_audience: FeedbackAudience::External, + environment_manager: Arc::new(EnvironmentManager::new(/*exec_server_url*/ None)), + remote_app_server_url: None, + remote_app_server_auth_token: None, + pending_update_action: None, + pending_shutdown_exit_thread_id: None, + windows_sandbox: WindowsSandboxState::default(), + thread_event_channels: HashMap::new(), + thread_event_listener_tasks: HashMap::new(), + agent_navigation: AgentNavigationState::default(), + side_threads: HashMap::new(), + active_thread_id: None, + active_thread_rx: None, + primary_thread_id: None, + last_subagent_backfill_attempt: None, + primary_session_configured: None, + pending_primary_events: VecDeque::new(), + pending_app_server_requests: PendingAppServerRequests::default(), + pending_plugin_enabled_writes: HashMap::new(), + } +} + +fn test_session_telemetry(config: &Config, model: &str) -> SessionTelemetry { + let model_info = crate::legacy_core::test_support::construct_model_info_offline(model, config); + SessionTelemetry::new( + ThreadId::new(), + model, + model_info.slug.as_str(), + /*account_id*/ None, + /*account_email*/ None, + /*auth_mode*/ None, + "test_originator".to_string(), + /*log_user_prompts*/ false, + "test".to_string(), + SessionSource::Cli, + ) +} + +pub(super) fn app_enabled_in_effective_config(config: &Config, app_id: &str) -> Option { + config + .config_layer_stack + .effective_config() + .as_table() + .and_then(|table| table.get("apps")) + .and_then(TomlValue::as_table) + .and_then(|apps| apps.get(app_id)) + .and_then(TomlValue::as_table) + .and_then(|app| app.get("enabled")) + .and_then(TomlValue::as_bool) +} diff --git a/codex-rs/tui/src/app/tests.rs b/codex-rs/tui/src/app/tests.rs index 5ed786ef05..d34a49ec55 100644 --- a/codex-rs/tui/src/app/tests.rs +++ b/codex-rs/tui/src/app/tests.rs @@ -28,16 +28,6 @@ use codex_app_server_protocol::AdditionalPermissionProfile; use codex_app_server_protocol::AgentMessageDeltaNotification; use codex_app_server_protocol::CommandExecutionRequestApprovalParams; use codex_app_server_protocol::ConfigWarningNotification; -use codex_app_server_protocol::HookCompletedNotification; -use codex_app_server_protocol::HookEventName as AppServerHookEventName; -use codex_app_server_protocol::HookExecutionMode as AppServerHookExecutionMode; -use codex_app_server_protocol::HookHandlerType as AppServerHookHandlerType; -use codex_app_server_protocol::HookOutputEntry as AppServerHookOutputEntry; -use codex_app_server_protocol::HookOutputEntryKind as AppServerHookOutputEntryKind; -use codex_app_server_protocol::HookRunStatus as AppServerHookRunStatus; -use codex_app_server_protocol::HookRunSummary as AppServerHookRunSummary; -use codex_app_server_protocol::HookScope as AppServerHookScope; -use codex_app_server_protocol::HookStartedNotification; use codex_app_server_protocol::JSONRPCErrorError; use codex_app_server_protocol::McpServerStartupState; use codex_app_server_protocol::McpServerStatusUpdatedNotification; @@ -47,7 +37,6 @@ use codex_app_server_protocol::NetworkPolicyAmendment as AppServerNetworkPolicyA use codex_app_server_protocol::NetworkPolicyRuleAction as AppServerNetworkPolicyRuleAction; use codex_app_server_protocol::NonSteerableTurnKind as AppServerNonSteerableTurnKind; use codex_app_server_protocol::PermissionsRequestApprovalParams; -use codex_app_server_protocol::PluginMarketplaceEntry; use codex_app_server_protocol::RequestId as AppServerRequestId; use codex_app_server_protocol::ServerNotification; use codex_app_server_protocol::ServerRequest; @@ -72,14 +61,12 @@ use codex_protocol::config_types::CollaborationMode; use codex_protocol::config_types::CollaborationModeMask; use codex_protocol::config_types::ModeKind; use codex_protocol::config_types::Settings; -use codex_protocol::mcp::Tool; use codex_protocol::models::FileSystemPermissions; use codex_protocol::models::NetworkPermissions; use codex_protocol::models::PermissionProfile; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::Event; use codex_protocol::protocol::EventMsg; -use codex_protocol::protocol::McpAuthStatus; use codex_protocol::protocol::NetworkApprovalContext; use codex_protocol::protocol::NetworkApprovalProtocol; use codex_protocol::protocol::RolloutItem; @@ -114,109 +101,6 @@ fn test_absolute_path(path: &str) -> AbsolutePathBuf { AbsolutePathBuf::try_from(PathBuf::from(path)).expect("absolute test path") } -#[test] -fn hide_cli_only_plugin_marketplaces_removes_openai_bundled() { - let mut response = PluginListResponse { - marketplaces: vec![ - PluginMarketplaceEntry { - name: "openai-bundled".to_string(), - path: Some(test_absolute_path("/marketplaces/openai-bundled")), - interface: None, - plugins: Vec::new(), - }, - PluginMarketplaceEntry { - name: "openai-curated".to_string(), - path: Some(test_absolute_path("/marketplaces/openai-curated")), - interface: None, - plugins: Vec::new(), - }, - ], - marketplace_load_errors: Vec::new(), - featured_plugin_ids: Vec::new(), - }; - - hide_cli_only_plugin_marketplaces(&mut response); - - assert_eq!( - response.marketplaces, - vec![PluginMarketplaceEntry { - name: "openai-curated".to_string(), - path: Some(test_absolute_path("/marketplaces/openai-curated")), - interface: None, - plugins: Vec::new(), - }] - ); -} - -#[test] -fn normalize_harness_overrides_resolves_relative_add_dirs() -> Result<()> { - let temp_dir = tempdir()?; - let base_cwd = temp_dir.path().join("base").abs(); - std::fs::create_dir_all(base_cwd.as_path())?; - - let overrides = ConfigOverrides { - additional_writable_roots: vec![PathBuf::from("rel")], - ..Default::default() - }; - let normalized = normalize_harness_overrides_for_cwd(overrides, &base_cwd)?; - - assert_eq!( - normalized.additional_writable_roots, - vec![base_cwd.join("rel").into_path_buf()] - ); - Ok(()) -} - -#[test] -fn mcp_inventory_maps_prefix_tool_names_by_server() { - let statuses = vec![ - McpServerStatus { - name: "docs".to_string(), - tools: HashMap::from([( - "list".to_string(), - Tool { - description: None, - name: "list".to_string(), - title: None, - input_schema: serde_json::json!({"type": "object"}), - output_schema: None, - annotations: None, - icons: None, - meta: None, - }, - )]), - resources: Vec::new(), - resource_templates: Vec::new(), - auth_status: codex_app_server_protocol::McpAuthStatus::Unsupported, - }, - McpServerStatus { - name: "disabled".to_string(), - tools: HashMap::new(), - resources: Vec::new(), - resource_templates: Vec::new(), - auth_status: codex_app_server_protocol::McpAuthStatus::Unsupported, - }, - ]; - - let (tools, resources, resource_templates, auth_statuses) = - mcp_inventory_maps_from_statuses(statuses); - let mut resource_names = resources.keys().cloned().collect::>(); - resource_names.sort(); - let mut template_names = resource_templates.keys().cloned().collect::>(); - template_names.sort(); - - assert_eq!( - tools.keys().cloned().collect::>(), - vec!["mcp__docs__list".to_string()] - ); - assert_eq!(resource_names, vec!["disabled", "docs"]); - assert_eq!(template_names, vec!["disabled", "docs"]); - assert_eq!( - auth_statuses.get("disabled"), - Some(&McpAuthStatus::Unsupported) - ); -} - #[tokio::test] async fn handle_mcp_inventory_result_clears_committed_loading_cell() { let mut app = make_test_app().await; @@ -1438,59 +1322,6 @@ async fn open_agent_picker_marks_terminal_read_errors_closed() -> Result<()> { Ok(()) } -#[test] -fn terminal_thread_read_error_detection_matches_not_loaded_errors() { - let err = color_eyre::eyre::eyre!( - "thread/read failed during TUI session lookup: thread/read failed: thread not loaded: thr_123" - ); - - assert!(App::is_terminal_thread_read_error(&err)); -} - -#[test] -fn terminal_thread_read_error_detection_ignores_transient_failures() { - let err = color_eyre::eyre::eyre!( - "thread/read failed during TUI session lookup: thread/read transport error: broken pipe" - ); - - assert!(!App::is_terminal_thread_read_error(&err)); -} - -#[test] -fn closed_state_for_thread_read_error_preserves_live_state_without_cache_on_transient_error() { - let err = color_eyre::eyre::eyre!( - "thread/read failed during TUI session lookup: thread/read transport error: broken pipe" - ); - - assert!(!App::closed_state_for_thread_read_error( - &err, /*existing_is_closed*/ None - )); -} - -#[test] -fn closed_state_for_thread_read_error_marks_terminal_uncached_threads_closed() { - let err = color_eyre::eyre::eyre!( - "thread/read failed during TUI session lookup: thread/read failed: thread not loaded: thr_123" - ); - - assert!(App::closed_state_for_thread_read_error( - &err, /*existing_is_closed*/ None - )); -} - -#[test] -fn include_turns_fallback_detection_handles_unmaterialized_and_ephemeral_threads() { - let unmaterialized = color_eyre::eyre::eyre!( - "thread/read failed during TUI session lookup: thread/read failed: thread thr_123 is not materialized yet; includeTurns is unavailable before first user message" - ); - let ephemeral = color_eyre::eyre::eyre!( - "thread/read failed during TUI session lookup: thread/read failed: ephemeral threads do not support includeTurns" - ); - - assert!(App::can_fallback_from_include_turns_error(&unmaterialized)); - assert!(App::can_fallback_from_include_turns_error(&ephemeral)); -} - #[tokio::test] async fn open_agent_picker_marks_loaded_threads_open() -> Result<()> { let mut app = make_test_app().await; @@ -3080,57 +2911,6 @@ async fn side_fork_config_is_ephemeral_and_appends_developer_guardrails() { assert!(app.transcript_cells.is_empty()); } -#[test] -fn side_boundary_prompt_marks_inherited_history_reference_only() { - let item = App::side_boundary_prompt_item(); - let codex_protocol::models::ResponseItem::Message { role, content, .. } = item else { - panic!("expected hidden side boundary prompt to be a user message"); - }; - assert_eq!(role, "user"); - let [codex_protocol::models::ContentItem::InputText { text }] = content.as_slice() else { - panic!("expected hidden side boundary prompt text"); - }; - assert!(text.contains("Side conversation boundary.")); - assert!(text.contains("Everything before this boundary is inherited history")); - assert!(text.contains("It is not your current task.")); - assert!(text.contains("Only messages submitted after this boundary are active")); - assert!(text.contains("Do not continue, execute, or complete")); - assert!(text.contains("separate from the main thread")); - assert!(text.contains("External tools may be available according to this thread's current")); - assert!(text.contains("Any tool calls or outputs visible before this boundary happened")); - assert!(text.contains("Do not modify files")); -} - -#[test] -fn side_return_shortcuts_match_esc_and_ctrl_c() { - assert!(side_return_shortcut_matches(KeyEvent::new( - KeyCode::Esc, - KeyModifiers::NONE, - ))); - assert!(side_return_shortcut_matches(KeyEvent::new_with_kind( - KeyCode::Esc, - KeyModifiers::NONE, - KeyEventKind::Repeat, - ))); - assert!(side_return_shortcut_matches(KeyEvent::new( - KeyCode::Char('c'), - KeyModifiers::CONTROL, - ))); - assert!(side_return_shortcut_matches(KeyEvent::new( - KeyCode::Char('C'), - KeyModifiers::CONTROL, - ))); - assert!(!side_return_shortcut_matches(KeyEvent::new( - KeyCode::Char('d'), - KeyModifiers::CONTROL, - ))); - assert!(!side_return_shortcut_matches(KeyEvent::new_with_kind( - KeyCode::Esc, - KeyModifiers::NONE, - KeyEventKind::Release, - ))); -} - #[tokio::test] async fn side_start_block_message_tracks_open_side_conversation() { let mut app = make_test_app().await; @@ -3281,28 +3061,6 @@ async fn side_parent_status_prioritizes_input_over_approval() -> Result<()> { Ok(()) } -#[test] -fn side_start_error_message_explains_missing_first_prompt() { - let err = color_eyre::eyre::eyre!( - "thread/fork failed during TUI bootstrap: thread/fork failed: no rollout found for thread id 019da1a1-bed9-7a43-88a2-b49d43915021" - ); - - assert_eq!( - App::side_start_error_message(&err), - "'/side' is unavailable until the current conversation has started. Send a message first, then try /side again." - ); -} - -#[test] -fn side_start_error_message_uses_generic_start_wording() { - let err = color_eyre::eyre::eyre!("transport disconnected"); - - assert_eq!( - App::side_start_error_message(&err), - "Failed to start side conversation: transport disconnected" - ); -} - #[tokio::test] async fn side_thread_snapshot_hides_forked_parent_transcript() { let parent_thread_id = ThreadId::new(); @@ -3991,61 +3749,6 @@ fn token_usage_notification( }) } -fn hook_started_notification(thread_id: ThreadId, turn_id: &str) -> ServerNotification { - ServerNotification::HookStarted(HookStartedNotification { - thread_id: thread_id.to_string(), - turn_id: Some(turn_id.to_string()), - run: AppServerHookRunSummary { - id: "user-prompt-submit:0:/tmp/hooks.json".to_string(), - event_name: AppServerHookEventName::UserPromptSubmit, - handler_type: AppServerHookHandlerType::Command, - execution_mode: AppServerHookExecutionMode::Sync, - scope: AppServerHookScope::Turn, - source_path: test_path_buf("/tmp/hooks.json").abs(), - source: codex_app_server_protocol::HookSource::User, - display_order: 0, - status: AppServerHookRunStatus::Running, - status_message: Some("checking go-workflow input policy".to_string()), - started_at: 1, - completed_at: None, - duration_ms: None, - entries: Vec::new(), - }, - }) -} - -fn hook_completed_notification(thread_id: ThreadId, turn_id: &str) -> ServerNotification { - ServerNotification::HookCompleted(HookCompletedNotification { - thread_id: thread_id.to_string(), - turn_id: Some(turn_id.to_string()), - run: AppServerHookRunSummary { - id: "user-prompt-submit:0:/tmp/hooks.json".to_string(), - event_name: AppServerHookEventName::UserPromptSubmit, - handler_type: AppServerHookHandlerType::Command, - execution_mode: AppServerHookExecutionMode::Sync, - scope: AppServerHookScope::Turn, - source_path: test_path_buf("/tmp/hooks.json").abs(), - source: codex_app_server_protocol::HookSource::User, - display_order: 0, - status: AppServerHookRunStatus::Stopped, - status_message: Some("checking go-workflow input policy".to_string()), - started_at: 1, - completed_at: Some(11), - duration_ms: Some(10), - entries: vec![ - AppServerHookOutputEntry { - kind: AppServerHookOutputEntryKind::Warning, - text: "go-workflow must start from PlanMode".to_string(), - }, - AppServerHookOutputEntry { - kind: AppServerHookOutputEntryKind::Stop, - text: "prompt blocked".to_string(), - }, - ], - }, - }) -} - fn agent_message_delta_notification( thread_id: ThreadId, turn_id: &str, @@ -4098,162 +3801,6 @@ fn request_user_input_request(thread_id: ThreadId, turn_id: &str, item_id: &str) } } -#[test] -fn thread_event_store_tracks_active_turn_lifecycle() { - let mut store = ThreadEventStore::new(/*capacity*/ 8); - assert_eq!(store.active_turn_id(), None); - - let thread_id = ThreadId::new(); - store.push_notification(turn_started_notification(thread_id, "turn-1")); - assert_eq!(store.active_turn_id(), Some("turn-1")); - - store.push_notification(turn_completed_notification( - thread_id, - "turn-2", - TurnStatus::Completed, - )); - assert_eq!(store.active_turn_id(), Some("turn-1")); - - store.push_notification(turn_completed_notification( - thread_id, - "turn-1", - TurnStatus::Interrupted, - )); - assert_eq!(store.active_turn_id(), None); -} - -#[test] -fn thread_event_store_restores_active_turn_from_snapshot_turns() { - let thread_id = ThreadId::new(); - let session = test_thread_session(thread_id, test_path_buf("/tmp/project")); - let turns = vec![ - test_turn("turn-1", TurnStatus::Completed, Vec::new()), - test_turn("turn-2", TurnStatus::InProgress, Vec::new()), - ]; - - let store = - ThreadEventStore::new_with_session(/*capacity*/ 8, session.clone(), turns.clone()); - assert_eq!(store.active_turn_id(), Some("turn-2")); - - let mut refreshed_store = ThreadEventStore::new(/*capacity*/ 8); - refreshed_store.set_session(session, turns); - assert_eq!(refreshed_store.active_turn_id(), Some("turn-2")); -} - -#[test] -fn thread_event_store_clear_active_turn_id_resets_cached_turn() { - let mut store = ThreadEventStore::new(/*capacity*/ 8); - let thread_id = ThreadId::new(); - store.push_notification(turn_started_notification(thread_id, "turn-1")); - - store.clear_active_turn_id(); - - assert_eq!(store.active_turn_id(), None); -} - -#[test] -fn thread_event_store_rebase_preserves_resolved_request_state() { - let thread_id = ThreadId::new(); - let mut store = ThreadEventStore::new(/*capacity*/ 8); - store.push_request(exec_approval_request( - thread_id, - "turn-approval", - "call-approval", - /*approval_id*/ None, - )); - store.push_notification(ServerNotification::ServerRequestResolved( - codex_app_server_protocol::ServerRequestResolvedNotification { - request_id: AppServerRequestId::Integer(1), - thread_id: thread_id.to_string(), - }, - )); - - store.rebase_buffer_after_session_refresh(); - - let snapshot = store.snapshot(); - assert!(snapshot.events.is_empty()); - assert_eq!(store.has_pending_thread_approvals(), false); -} - -#[test] -fn thread_event_store_rebase_preserves_hook_notifications() { - let thread_id = ThreadId::new(); - let mut store = ThreadEventStore::new(/*capacity*/ 8); - store.push_notification(hook_started_notification(thread_id, "turn-hook")); - store.push_notification(hook_completed_notification(thread_id, "turn-hook")); - - store.rebase_buffer_after_session_refresh(); - - let snapshot = store.snapshot(); - let hook_notifications = snapshot - .events - .into_iter() - .map(|event| match event { - ThreadBufferedEvent::Notification(notification) => { - serde_json::to_value(notification).expect("hook notification should serialize") - } - other => panic!("expected buffered hook notification, saw: {other:?}"), - }) - .collect::>(); - assert_eq!( - hook_notifications, - vec![ - serde_json::to_value(hook_started_notification(thread_id, "turn-hook")) - .expect("hook notification should serialize"), - serde_json::to_value(hook_completed_notification(thread_id, "turn-hook")) - .expect("hook notification should serialize"), - ] - ); -} - -#[test] -fn build_feedback_upload_params_includes_thread_id_and_rollout_path() { - let thread_id = ThreadId::new(); - let rollout_path = PathBuf::from("/tmp/rollout.jsonl"); - - let params = build_feedback_upload_params( - Some(thread_id), - Some(rollout_path.clone()), - FeedbackCategory::SafetyCheck, - Some("needs follow-up".to_string()), - Some("turn-123".to_string()), - /*include_logs*/ true, - ); - - assert_eq!(params.classification, "safety_check"); - assert_eq!(params.reason, Some("needs follow-up".to_string())); - assert_eq!(params.thread_id, Some(thread_id.to_string())); - assert_eq!( - params - .tags - .as_ref() - .and_then(|tags| tags.get("turn_id")) - .map(String::as_str), - Some("turn-123") - ); - assert_eq!(params.include_logs, true); - assert_eq!(params.extra_log_files, Some(vec![rollout_path])); -} - -#[test] -fn build_feedback_upload_params_omits_rollout_path_without_logs() { - let params = build_feedback_upload_params( - /*origin_thread_id*/ None, - Some(PathBuf::from("/tmp/rollout.jsonl")), - FeedbackCategory::GoodResult, - /*reason*/ None, - /*turn_id*/ None, - /*include_logs*/ false, - ); - - assert_eq!(params.classification, "good_result"); - assert_eq!(params.reason, None); - assert_eq!(params.thread_id, None); - assert_eq!(params.tags, None); - assert_eq!(params.include_logs, false); - assert_eq!(params.extra_log_files, None); -} - #[tokio::test] async fn feedback_submission_without_thread_emits_error_history_cell() { let (mut app, mut app_event_rx, _op_rx) = make_test_app_with_channels().await; @@ -4383,19 +3930,6 @@ fn test_session_telemetry(config: &Config, model: &str) -> SessionTelemetry { ) } -fn app_enabled_in_effective_config(config: &Config, app_id: &str) -> Option { - config - .config_layer_stack - .effective_config() - .as_table() - .and_then(|table| table.get("apps")) - .and_then(TomlValue::as_table) - .and_then(|apps| apps.get(app_id)) - .and_then(TomlValue::as_table) - .and_then(|app| app.get("enabled")) - .and_then(TomlValue::as_bool) -} - #[test] fn active_turn_not_steerable_turn_error_extracts_structured_server_error() { let turn_error = AppServerTurnError { @@ -4457,166 +3991,6 @@ fn active_turn_steer_race_extracts_actual_turn_id_from_mismatch() { ); } -#[tokio::test] -async fn update_reasoning_effort_updates_collaboration_mode() { - let mut app = make_test_app().await; - app.chat_widget - .set_reasoning_effort(Some(ReasoningEffortConfig::Medium)); - - app.on_update_reasoning_effort(Some(ReasoningEffortConfig::High)); - - assert_eq!( - app.chat_widget.current_reasoning_effort(), - Some(ReasoningEffortConfig::High) - ); - assert_eq!( - app.config.model_reasoning_effort, - Some(ReasoningEffortConfig::High) - ); -} - -#[tokio::test] -async fn refresh_in_memory_config_from_disk_loads_latest_apps_state() -> Result<()> { - let mut app = make_test_app().await; - let codex_home = tempdir()?; - app.config.codex_home = codex_home.path().to_path_buf().abs(); - let app_id = "unit_test_refresh_in_memory_config_connector".to_string(); - - assert_eq!(app_enabled_in_effective_config(&app.config, &app_id), None); - - ConfigEditsBuilder::new(&app.config.codex_home) - .with_edits([ - ConfigEdit::SetPath { - segments: vec!["apps".to_string(), app_id.clone(), "enabled".to_string()], - value: false.into(), - }, - ConfigEdit::SetPath { - segments: vec![ - "apps".to_string(), - app_id.clone(), - "disabled_reason".to_string(), - ], - value: "user".into(), - }, - ]) - .apply() - .await - .expect("persist app toggle"); - - assert_eq!(app_enabled_in_effective_config(&app.config, &app_id), None); - - app.refresh_in_memory_config_from_disk().await?; - - assert_eq!( - app_enabled_in_effective_config(&app.config, &app_id), - Some(false) - ); - Ok(()) -} - -#[tokio::test] -async fn refresh_in_memory_config_from_disk_best_effort_keeps_current_config_on_error() -> Result<()> -{ - let mut app = make_test_app().await; - let codex_home = tempdir()?; - app.config.codex_home = codex_home.path().to_path_buf().abs(); - std::fs::write(codex_home.path().join("config.toml"), "[broken")?; - let original_config = app.config.clone(); - - app.refresh_in_memory_config_from_disk_best_effort("starting a new thread") - .await; - - assert_eq!(app.config, original_config); - Ok(()) -} - -#[tokio::test] -async fn refresh_in_memory_config_from_disk_uses_active_chat_widget_cwd() -> Result<()> { - let mut app = make_test_app().await; - let original_cwd = app.config.cwd.clone(); - let next_cwd_tmp = tempdir()?; - let next_cwd = next_cwd_tmp.path().to_path_buf(); - - app.chat_widget.handle_codex_event(Event { - id: String::new(), - msg: EventMsg::SessionConfigured(SessionConfiguredEvent { - session_id: ThreadId::new(), - forked_from_id: None, - thread_name: None, - model: "gpt-test".to_string(), - model_provider_id: "test-provider".to_string(), - service_tier: None, - approval_policy: AskForApproval::Never, - approvals_reviewer: ApprovalsReviewer::User, - sandbox_policy: SandboxPolicy::new_read_only_policy(), - cwd: next_cwd.clone().abs(), - reasoning_effort: None, - history_log_id: 0, - history_entry_count: 0, - initial_messages: None, - network_proxy: None, - rollout_path: Some(PathBuf::new()), - }), - }); - - assert_eq!(app.chat_widget.config_ref().cwd.to_path_buf(), next_cwd); - assert_eq!(app.config.cwd, original_cwd); - - app.refresh_in_memory_config_from_disk().await?; - - assert_eq!(app.config.cwd, app.chat_widget.config_ref().cwd); - Ok(()) -} - -#[tokio::test] -async fn rebuild_config_for_resume_or_fallback_uses_current_config_on_same_cwd_error() -> Result<()> -{ - let mut app = make_test_app().await; - let codex_home = tempdir()?; - app.config.codex_home = codex_home.path().to_path_buf().abs(); - std::fs::write(codex_home.path().join("config.toml"), "[broken")?; - let current_config = app.config.clone(); - let current_cwd = current_config.cwd.clone(); - - let resume_config = app - .rebuild_config_for_resume_or_fallback(¤t_cwd, current_cwd.to_path_buf()) - .await?; - - assert_eq!(resume_config, current_config); - Ok(()) -} - -#[tokio::test] -async fn rebuild_config_for_resume_or_fallback_errors_when_cwd_changes() -> Result<()> { - let mut app = make_test_app().await; - let codex_home = tempdir()?; - app.config.codex_home = codex_home.path().to_path_buf().abs(); - std::fs::write(codex_home.path().join("config.toml"), "[broken")?; - let current_cwd = app.config.cwd.clone(); - let next_cwd_tmp = tempdir()?; - let next_cwd = next_cwd_tmp.path().to_path_buf(); - - let result = app - .rebuild_config_for_resume_or_fallback(¤t_cwd, next_cwd) - .await; - - assert!(result.is_err()); - Ok(()) -} - -#[tokio::test] -async fn sync_tui_theme_selection_updates_chat_widget_config_copy() { - let mut app = make_test_app().await; - - app.sync_tui_theme_selection("dracula".to_string()); - - assert_eq!(app.config.tui_theme.as_deref(), Some("dracula")); - assert_eq!( - app.chat_widget.config_ref().tui_theme.as_deref(), - Some("dracula") - ); -} - #[tokio::test] async fn fresh_session_config_uses_current_service_tier() { let mut app = make_test_app().await; diff --git a/codex-rs/tui/src/app/thread_events.rs b/codex-rs/tui/src/app/thread_events.rs index 6bbeca04e4..89cef2332c 100644 --- a/codex-rs/tui/src/app/thread_events.rs +++ b/codex-rs/tui/src/app/thread_events.rs @@ -264,3 +264,277 @@ impl ThreadEventChannel { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_support::PathBufExt; + use crate::test_support::test_path_buf; + use codex_app_server_protocol::CommandExecutionRequestApprovalParams; + use codex_app_server_protocol::HookCompletedNotification; + use codex_app_server_protocol::HookEventName as AppServerHookEventName; + use codex_app_server_protocol::HookExecutionMode as AppServerHookExecutionMode; + use codex_app_server_protocol::HookHandlerType as AppServerHookHandlerType; + use codex_app_server_protocol::HookOutputEntry as AppServerHookOutputEntry; + use codex_app_server_protocol::HookOutputEntryKind as AppServerHookOutputEntryKind; + use codex_app_server_protocol::HookRunStatus as AppServerHookRunStatus; + use codex_app_server_protocol::HookRunSummary as AppServerHookRunSummary; + use codex_app_server_protocol::HookScope as AppServerHookScope; + use codex_app_server_protocol::HookStartedNotification; + use codex_app_server_protocol::RequestId as AppServerRequestId; + use codex_app_server_protocol::TurnCompletedNotification; + use codex_app_server_protocol::TurnStartedNotification; + use codex_config::types::ApprovalsReviewer; + use codex_protocol::protocol::AskForApproval; + use codex_protocol::protocol::SandboxPolicy; + use pretty_assertions::assert_eq; + use std::path::PathBuf; + + fn test_thread_session(thread_id: ThreadId, cwd: PathBuf) -> ThreadSessionState { + ThreadSessionState { + thread_id, + forked_from_id: None, + fork_parent_title: None, + thread_name: None, + model: "gpt-test".to_string(), + model_provider_id: "test-provider".to_string(), + service_tier: None, + approval_policy: AskForApproval::Never, + approvals_reviewer: ApprovalsReviewer::User, + sandbox_policy: SandboxPolicy::new_read_only_policy(), + cwd: cwd.abs(), + instruction_source_paths: Vec::new(), + reasoning_effort: None, + history_log_id: 0, + history_entry_count: 0, + network_proxy: None, + rollout_path: Some(PathBuf::new()), + } + } + + fn test_turn(turn_id: &str, status: TurnStatus, items: Vec) -> Turn { + Turn { + id: turn_id.to_string(), + items, + status, + error: None, + started_at: None, + completed_at: None, + duration_ms: None, + } + } + + fn turn_started_notification(thread_id: ThreadId, turn_id: &str) -> ServerNotification { + ServerNotification::TurnStarted(TurnStartedNotification { + thread_id: thread_id.to_string(), + turn: Turn { + started_at: Some(0), + ..test_turn(turn_id, TurnStatus::InProgress, Vec::new()) + }, + }) + } + + fn turn_completed_notification( + thread_id: ThreadId, + turn_id: &str, + status: TurnStatus, + ) -> ServerNotification { + ServerNotification::TurnCompleted(TurnCompletedNotification { + thread_id: thread_id.to_string(), + turn: Turn { + completed_at: Some(0), + duration_ms: Some(1), + ..test_turn(turn_id, status, Vec::new()) + }, + }) + } + + fn hook_started_notification(thread_id: ThreadId, turn_id: &str) -> ServerNotification { + ServerNotification::HookStarted(HookStartedNotification { + thread_id: thread_id.to_string(), + turn_id: Some(turn_id.to_string()), + run: AppServerHookRunSummary { + id: "user-prompt-submit:0:/tmp/hooks.json".to_string(), + event_name: AppServerHookEventName::UserPromptSubmit, + handler_type: AppServerHookHandlerType::Command, + execution_mode: AppServerHookExecutionMode::Sync, + scope: AppServerHookScope::Turn, + source_path: test_path_buf("/tmp/hooks.json").abs(), + source: codex_app_server_protocol::HookSource::User, + display_order: 0, + status: AppServerHookRunStatus::Running, + status_message: Some("checking go-workflow input policy".to_string()), + started_at: 1, + completed_at: None, + duration_ms: None, + entries: Vec::new(), + }, + }) + } + + fn hook_completed_notification(thread_id: ThreadId, turn_id: &str) -> ServerNotification { + ServerNotification::HookCompleted(HookCompletedNotification { + thread_id: thread_id.to_string(), + turn_id: Some(turn_id.to_string()), + run: AppServerHookRunSummary { + id: "user-prompt-submit:0:/tmp/hooks.json".to_string(), + event_name: AppServerHookEventName::UserPromptSubmit, + handler_type: AppServerHookHandlerType::Command, + execution_mode: AppServerHookExecutionMode::Sync, + scope: AppServerHookScope::Turn, + source_path: test_path_buf("/tmp/hooks.json").abs(), + source: codex_app_server_protocol::HookSource::User, + display_order: 0, + status: AppServerHookRunStatus::Stopped, + status_message: Some("checking go-workflow input policy".to_string()), + started_at: 1, + completed_at: Some(11), + duration_ms: Some(10), + entries: vec![ + AppServerHookOutputEntry { + kind: AppServerHookOutputEntryKind::Warning, + text: "go-workflow must start from PlanMode".to_string(), + }, + AppServerHookOutputEntry { + kind: AppServerHookOutputEntryKind::Stop, + text: "prompt blocked".to_string(), + }, + ], + }, + }) + } + + fn exec_approval_request( + thread_id: ThreadId, + turn_id: &str, + item_id: &str, + approval_id: Option<&str>, + ) -> ServerRequest { + ServerRequest::CommandExecutionRequestApproval { + request_id: AppServerRequestId::Integer(1), + params: CommandExecutionRequestApprovalParams { + thread_id: thread_id.to_string(), + turn_id: turn_id.to_string(), + item_id: item_id.to_string(), + approval_id: approval_id.map(str::to_string), + reason: Some("needs approval".to_string()), + network_approval_context: None, + command: Some("echo hello".to_string()), + cwd: Some(test_path_buf("/tmp/project").abs()), + command_actions: None, + additional_permissions: None, + proposed_execpolicy_amendment: None, + proposed_network_policy_amendments: None, + available_decisions: None, + }, + } + } + + #[test] + fn thread_event_store_tracks_active_turn_lifecycle() { + let mut store = ThreadEventStore::new(/*capacity*/ 8); + assert_eq!(store.active_turn_id(), None); + + let thread_id = ThreadId::new(); + store.push_notification(turn_started_notification(thread_id, "turn-1")); + assert_eq!(store.active_turn_id(), Some("turn-1")); + + store.push_notification(turn_completed_notification( + thread_id, + "turn-2", + TurnStatus::Completed, + )); + assert_eq!(store.active_turn_id(), Some("turn-1")); + + store.push_notification(turn_completed_notification( + thread_id, + "turn-1", + TurnStatus::Interrupted, + )); + assert_eq!(store.active_turn_id(), None); + } + + #[test] + fn thread_event_store_restores_active_turn_from_snapshot_turns() { + let thread_id = ThreadId::new(); + let session = test_thread_session(thread_id, test_path_buf("/tmp/project")); + let turns = vec![ + test_turn("turn-1", TurnStatus::Completed, Vec::new()), + test_turn("turn-2", TurnStatus::InProgress, Vec::new()), + ]; + + let store = + ThreadEventStore::new_with_session(/*capacity*/ 8, session.clone(), turns.clone()); + assert_eq!(store.active_turn_id(), Some("turn-2")); + + let mut refreshed_store = ThreadEventStore::new(/*capacity*/ 8); + refreshed_store.set_session(session, turns); + assert_eq!(refreshed_store.active_turn_id(), Some("turn-2")); + } + + #[test] + fn thread_event_store_clear_active_turn_id_resets_cached_turn() { + let mut store = ThreadEventStore::new(/*capacity*/ 8); + let thread_id = ThreadId::new(); + store.push_notification(turn_started_notification(thread_id, "turn-1")); + + store.clear_active_turn_id(); + + assert_eq!(store.active_turn_id(), None); + } + + #[test] + fn thread_event_store_rebase_preserves_resolved_request_state() { + let thread_id = ThreadId::new(); + let mut store = ThreadEventStore::new(/*capacity*/ 8); + store.push_request(exec_approval_request( + thread_id, + "turn-approval", + "call-approval", + /*approval_id*/ None, + )); + store.push_notification(ServerNotification::ServerRequestResolved( + codex_app_server_protocol::ServerRequestResolvedNotification { + request_id: AppServerRequestId::Integer(1), + thread_id: thread_id.to_string(), + }, + )); + + store.rebase_buffer_after_session_refresh(); + + let snapshot = store.snapshot(); + assert!(snapshot.events.is_empty()); + assert_eq!(store.has_pending_thread_approvals(), false); + } + + #[test] + fn thread_event_store_rebase_preserves_hook_notifications() { + let thread_id = ThreadId::new(); + let mut store = ThreadEventStore::new(/*capacity*/ 8); + store.push_notification(hook_started_notification(thread_id, "turn-hook")); + store.push_notification(hook_completed_notification(thread_id, "turn-hook")); + + store.rebase_buffer_after_session_refresh(); + + let snapshot = store.snapshot(); + let hook_notifications = snapshot + .events + .into_iter() + .map(|event| match event { + ThreadBufferedEvent::Notification(notification) => { + serde_json::to_value(notification).expect("hook notification should serialize") + } + other => panic!("expected buffered hook notification, saw: {other:?}"), + }) + .collect::>(); + assert_eq!( + hook_notifications, + vec![ + serde_json::to_value(hook_started_notification(thread_id, "turn-hook")) + .expect("hook notification should serialize"), + serde_json::to_value(hook_completed_notification(thread_id, "turn-hook")) + .expect("hook notification should serialize"), + ] + ); + } +} From 5fe767e8e1398213f77e45e7507be244c5035e6d Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Tue, 21 Apr 2026 10:22:26 -0700 Subject: [PATCH 11/16] Refactor app-server config loading into ConfigManager (#18442) Localize app-server configuration loading in one place. --- codex-rs/Cargo.lock | 2 + codex-rs/app-server/Cargo.toml | 2 + .../app-server/src/codex_message_processor.rs | 397 ++++-------------- codex-rs/app-server/src/config_api.rs | 173 ++------ codex-rs/app-server/src/config_manager.rs | 326 ++++++++++++++ .../src/config_manager_service.rs} | 225 +++------- .../src/config_manager_service_tests.rs} | 37 +- codex-rs/app-server/src/lib.rs | 2 + codex-rs/app-server/src/message_processor.rs | 21 +- .../suite/v2/experimental_feature_list.rs | 4 + codex-rs/core/src/config/mod.rs | 15 +- codex-rs/core/src/plugins/manager.rs | 28 +- 12 files changed, 585 insertions(+), 647 deletions(-) create mode 100644 codex-rs/app-server/src/config_manager.rs rename codex-rs/{core/src/config/service.rs => app-server/src/config_manager_service.rs} (74%) rename codex-rs/{core/src/config/service_tests.rs => app-server/src/config_manager_service_tests.rs} (95%) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 18c26e6a14..de1db07093 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1508,11 +1508,13 @@ dependencies = [ "sha2", "shlex", "tempfile", + "thiserror 2.0.18", "time", "tokio", "tokio-tungstenite", "tokio-util", "toml 0.9.11+spec-1.1.0", + "toml_edit 0.24.0+spec-1.1.0", "tracing", "tracing-opentelemetry", "tracing-subscriber", diff --git a/codex-rs/app-server/Cargo.toml b/codex-rs/app-server/Cargo.toml index f354539640..ea49058e6c 100644 --- a/codex-rs/app-server/Cargo.toml +++ b/codex-rs/app-server/Cargo.toml @@ -72,8 +72,10 @@ serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } sha2 = { workspace = true } tempfile = { workspace = true } +thiserror = { workspace = true } time = { workspace = true } toml = { workspace = true } +toml_edit = { workspace = true } tokio = { workspace = true, features = [ "io-std", "macros", diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index a60a079898..3b92d4199f 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -2,7 +2,7 @@ use crate::bespoke_event_handling::apply_bespoke_event_handling; use crate::bespoke_event_handling::maybe_emit_hook_prompt_item_completed; use crate::command_exec::CommandExecManager; use crate::command_exec::StartCommandExecParams; -use crate::config_api::apply_runtime_feature_enablement; +use crate::config_manager::ConfigManager; use crate::error_code::INPUT_TOO_LARGE_ERROR_CODE; use crate::error_code::INTERNAL_ERROR_CODE; use crate::error_code::INVALID_PARAMS_ERROR_CODE; @@ -212,8 +212,6 @@ use codex_arg0::Arg0DispatchPaths; use codex_backend_client::AddCreditsNudgeCreditType as BackendAddCreditsNudgeCreditType; use codex_backend_client::Client as BackendClient; use codex_chatgpt::connectors; -use codex_cloud_requirements::cloud_requirements_loader; -use codex_config::ThreadConfigLoader; use codex_config::types::McpServerTransportConfig; use codex_core::CodexThread; use codex_core::ForkSnapshot; @@ -231,9 +229,6 @@ use codex_core::config::edit::ConfigEdit; use codex_core::config::edit::ConfigEditsBuilder; use codex_core::config_loader::CloudRequirementsLoadError; use codex_core::config_loader::CloudRequirementsLoadErrorCode; -use codex_core::config_loader::CloudRequirementsLoader; -use codex_core::config_loader::LoaderOverrides; -use codex_core::config_loader::load_config_layers_state; use codex_core::config_loader::project_trust_key; use codex_core::exec::ExecCapturePolicy; use codex_core::exec::ExecExpiration; @@ -280,7 +275,6 @@ use codex_login::ServerOptions as LoginServerOptions; use codex_login::ShutdownHandle; use codex_login::auth::login_with_chatgpt_auth_tokens; use codex_login::complete_device_code_login; -use codex_login::default_client::set_default_client_residency_requirement; use codex_login::login_with_api_key; use codex_login::request_device_code; use codex_login::run_login_server; @@ -348,16 +342,13 @@ use codex_thread_store::ThreadStore; use codex_thread_store::ThreadStoreError; use codex_thread_store::UpdateThreadMetadataParams as StoreUpdateThreadMetadataParams; use codex_utils_absolute_path::AbsolutePathBuf; -use codex_utils_json_to_toml::json_to_toml; use codex_utils_pty::DEFAULT_OUTPUT_BYTES_CAP; -use std::collections::BTreeMap; use std::collections::HashMap; use std::collections::HashSet; use std::io::Error as IoError; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; -use std::sync::RwLock; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; use std::time::Duration; @@ -478,10 +469,7 @@ pub(crate) struct CodexMessageProcessor { arg0_paths: Arg0DispatchPaths, config: Arc, thread_store: Arc, - cli_overrides: Arc>>, - runtime_feature_enablement: Arc>>, - cloud_requirements: Arc>, - thread_config_loader: Arc, + config_manager: ConfigManager, active_login: Arc>>, pending_thread_unloads: Arc>>, thread_state_manager: ThreadStateManager, @@ -638,11 +626,10 @@ pub(crate) struct CodexMessageProcessorArgs { pub(crate) outgoing: Arc, pub(crate) analytics_events_client: AnalyticsEventsClient, pub(crate) arg0_paths: Arg0DispatchPaths, + /// Startup config used as the process baseline. Fresh effective config loads + /// go through `config_manager`. pub(crate) config: Arc, - pub(crate) cli_overrides: Arc>>, - pub(crate) runtime_feature_enablement: Arc>>, - pub(crate) cloud_requirements: Arc>, - pub(crate) thread_config_loader: Arc, + pub(crate) config_manager: ConfigManager, pub(crate) feedback: CodexFeedback, pub(crate) log_db: Option, } @@ -727,10 +714,7 @@ impl CodexMessageProcessor { analytics_events_client, arg0_paths, config, - cli_overrides, - runtime_feature_enablement, - cloud_requirements, - thread_config_loader, + config_manager, feedback, log_db, } = args; @@ -742,10 +726,7 @@ impl CodexMessageProcessor { arg0_paths, thread_store: configured_thread_store(&config), config, - cli_overrides, - runtime_feature_enablement, - cloud_requirements, - thread_config_loader, + config_manager, active_login: Arc::new(Mutex::new(None)), pending_thread_unloads: Arc::new(Mutex::new(HashSet::new())), thread_state_manager: ThreadStateManager::new(), @@ -763,44 +744,14 @@ impl CodexMessageProcessor { &self, fallback_cwd: Option, ) -> Result { - let cloud_requirements = self.current_cloud_requirements(); - let mut config = codex_core::config::ConfigBuilder::default() - .cli_overrides(self.current_cli_overrides()) - .fallback_cwd(fallback_cwd) - .cloud_requirements(cloud_requirements) - .build() + self.config_manager + .load_latest_config(fallback_cwd) .await .map_err(|err| JSONRPCErrorError { code: INTERNAL_ERROR_CODE, message: format!("failed to reload config: {err}"), data: None, - })?; - apply_runtime_feature_enablement(&mut config, &self.current_runtime_feature_enablement()); - config.codex_self_exe = self.arg0_paths.codex_self_exe.clone(); - config.codex_linux_sandbox_exe = self.arg0_paths.codex_linux_sandbox_exe.clone(); - config.main_execve_wrapper_exe = self.arg0_paths.main_execve_wrapper_exe.clone(); - Ok(config) - } - - fn current_cloud_requirements(&self) -> CloudRequirementsLoader { - self.cloud_requirements - .read() - .map(|guard| guard.clone()) - .unwrap_or_default() - } - - fn current_cli_overrides(&self) -> Vec<(String, TomlValue)> { - self.cli_overrides - .read() - .map(|guard| guard.clone()) - .unwrap_or_default() - } - - fn current_runtime_feature_enablement(&self) -> BTreeMap { - self.runtime_feature_enablement - .read() - .map(|guard| guard.clone()) - .unwrap_or_default() + }) } /// If a client sends `developer_instructions: null` during a mode switch, @@ -1424,10 +1375,8 @@ impl CodexMessageProcessor { let outgoing_clone = self.outgoing.clone(); let active_login = self.active_login.clone(); let auth_manager = self.auth_manager.clone(); - let cloud_requirements = self.cloud_requirements.clone(); + let config_manager = self.config_manager.clone(); let chatgpt_base_url = self.config.chatgpt_base_url.clone(); - let codex_home = self.config.codex_home.to_path_buf(); - let cli_overrides = self.current_cli_overrides(); let auth_url = server.auth_url.clone(); tokio::spawn(async move { let (success, error_msg) = match tokio::time::timeout( @@ -1457,17 +1406,13 @@ impl CodexMessageProcessor { if success { auth_manager.reload(); - replace_cloud_requirements_loader( - cloud_requirements.as_ref(), + config_manager.replace_cloud_requirements_loader( auth_manager.clone(), chatgpt_base_url, - codex_home, ); - sync_default_client_residency_requirement( - &cli_overrides, - cloud_requirements.as_ref(), - ) - .await; + config_manager + .sync_default_client_residency_requirement() + .await; // Notify clients with the actual current auth mode. let auth = auth_manager.auth_cached(); @@ -1541,10 +1486,8 @@ impl CodexMessageProcessor { let outgoing_clone = self.outgoing.clone(); let active_login = self.active_login.clone(); let auth_manager = self.auth_manager.clone(); - let cloud_requirements = self.cloud_requirements.clone(); + let config_manager = self.config_manager.clone(); let chatgpt_base_url = self.config.chatgpt_base_url.clone(); - let codex_home = self.config.codex_home.to_path_buf(); - let cli_overrides = self.current_cli_overrides(); tokio::spawn(async move { let (success, error_msg) = tokio::select! { _ = cancel.cancelled() => { @@ -1571,17 +1514,13 @@ impl CodexMessageProcessor { if success { auth_manager.reload(); - replace_cloud_requirements_loader( - cloud_requirements.as_ref(), + config_manager.replace_cloud_requirements_loader( auth_manager.clone(), chatgpt_base_url, - codex_home, ); - sync_default_client_residency_requirement( - &cli_overrides, - cloud_requirements.as_ref(), - ) - .await; + config_manager + .sync_default_client_residency_requirement() + .await; let auth = auth_manager.auth_cached(); let payload_v2 = AccountUpdatedNotification { @@ -1711,14 +1650,12 @@ impl CodexMessageProcessor { return; } self.auth_manager.reload(); - replace_cloud_requirements_loader( - self.cloud_requirements.as_ref(), + self.config_manager.replace_cloud_requirements_loader( self.auth_manager.clone(), self.config.chatgpt_base_url.clone(), - self.config.codex_home.to_path_buf(), ); - let cli_overrides = self.current_cli_overrides(); - sync_default_client_residency_requirement(&cli_overrides, self.cloud_requirements.as_ref()) + self.config_manager + .sync_default_client_residency_requirement() .await; self.outgoing @@ -2429,8 +2366,6 @@ impl CodexMessageProcessor { personality, ); typesafe_overrides.ephemeral = ephemeral; - let cloud_requirements = self.current_cloud_requirements(); - let cli_overrides = self.current_cli_overrides(); let listener_task_context = ListenerTaskContext { thread_manager: Arc::clone(&self.thread_manager), thread_state_manager: self.thread_state_manager.clone(), @@ -2443,15 +2378,11 @@ impl CodexMessageProcessor { codex_home: self.config.codex_home.to_path_buf(), }; let request_trace = request_context.request_trace(); - let runtime_feature_enablement = self.current_runtime_feature_enablement(); - let thread_config_loader = Arc::clone(&self.thread_config_loader); + let config_manager = self.config_manager.clone(); let thread_start_task = async move { Self::thread_start_task( listener_task_context, - cli_overrides, - runtime_feature_enablement, - cloud_requirements, - thread_config_loader, + config_manager, request_id, app_server_client_name, app_server_client_version, @@ -2525,10 +2456,7 @@ impl CodexMessageProcessor { #[allow(clippy::too_many_arguments)] async fn thread_start_task( listener_task_context: ListenerTaskContext, - cli_overrides: Vec<(String, TomlValue)>, - runtime_feature_enablement: BTreeMap, - cloud_requirements: CloudRequirementsLoader, - thread_config_loader: Arc, + config_manager: ConfigManager, request_id: ConnectionRequestId, app_server_client_name: Option, app_server_client_version: Option, @@ -2542,16 +2470,9 @@ impl CodexMessageProcessor { request_trace: Option, ) { let requested_cwd = typesafe_overrides.cwd.clone(); - let mut config = match derive_config_from_params( - &cli_overrides, - config_overrides.clone(), - typesafe_overrides.clone(), - Arc::clone(&thread_config_loader), - &cloud_requirements, - &listener_task_context.codex_home, - &runtime_feature_enablement, - ) - .await + let mut config = match config_manager + .load_with_overrides(config_overrides.clone(), typesafe_overrides.clone()) + .await { Ok(config) => config, Err(err) => { @@ -2590,6 +2511,7 @@ impl CodexMessageProcessor { let trust_target = resolve_root_git_project_for_trust(LOCAL_FS.as_ref(), &config.cwd) .await .unwrap_or_else(|| config.cwd.clone()); + let current_cli_overrides = config_manager.current_cli_overrides(); let cli_overrides_with_trust; let cli_overrides_for_reload = if let Err(err) = codex_core::config::set_project_trust_level( @@ -2611,7 +2533,7 @@ impl CodexMessageProcessor { project_trust_key(trust_target.as_path()), TomlValue::Table(project), ); - cli_overrides_with_trust = cli_overrides + cli_overrides_with_trust = current_cli_overrides .iter() .cloned() .chain(std::iter::once(( @@ -2621,19 +2543,17 @@ impl CodexMessageProcessor { .collect::>(); cli_overrides_with_trust.as_slice() } else { - &cli_overrides + current_cli_overrides.as_slice() }; - config = match derive_config_from_params( - cli_overrides_for_reload, - config_overrides, - typesafe_overrides, - thread_config_loader, - &cloud_requirements, - &listener_task_context.codex_home, - &runtime_feature_enablement, - ) - .await + config = match config_manager + .load_with_cli_overrides( + cli_overrides_for_reload, + config_overrides, + typesafe_overrides, + /*fallback_cwd*/ None, + ) + .await { Ok(config) => config, Err(err) => { @@ -4417,19 +4337,10 @@ impl CodexMessageProcessor { .await; // Derive a Config using the same logic as new conversation, honoring overrides if provided. - let cloud_requirements = self.current_cloud_requirements(); - let cli_overrides = self.current_cli_overrides(); - let runtime_feature_enablement = self.current_runtime_feature_enablement(); - let config = match derive_config_for_cwd( - &cli_overrides, - request_overrides, - typesafe_overrides, - history_cwd, - &cloud_requirements, - &self.config.codex_home, - &runtime_feature_enablement, - ) - .await + let config = match self + .config_manager + .load_for_cwd(request_overrides, typesafe_overrides, history_cwd) + .await { Ok(config) => config, Err(err) => { @@ -4997,19 +4908,10 @@ impl CodexMessageProcessor { ); typesafe_overrides.ephemeral = ephemeral.then_some(true); // Derive a Config using the same logic as new conversation, honoring overrides if provided. - let cloud_requirements = self.current_cloud_requirements(); - let cli_overrides = self.current_cli_overrides(); - let runtime_feature_enablement = self.current_runtime_feature_enablement(); - let config = match derive_config_for_cwd( - &cli_overrides, - request_overrides, - typesafe_overrides, - history_cwd, - &cloud_requirements, - &self.config.codex_home, - &runtime_feature_enablement, - ) - .await + let config = match self + .config_manager + .load_for_cwd(request_overrides, typesafe_overrides, history_cwd) + .await { Ok(config) => config, Err(err) => { @@ -6584,8 +6486,6 @@ impl CodexMessageProcessor { return; } }; - let cli_overrides = self.current_cli_overrides(); - let host_name = codex_config::host_name(); let mut data = Vec::new(); for cwd in cwds { let extra_roots = extra_roots_by_cwd @@ -6606,17 +6506,10 @@ impl CodexMessageProcessor { continue; } }; - let config_layer_stack = match load_config_layers_state( - LOCAL_FS.as_ref(), - &self.config.codex_home, - Some(cwd_abs.clone()), - &cli_overrides, - LoaderOverrides::default(), - CloudRequirementsLoader::default(), - self.thread_config_loader.as_ref(), - host_name.as_deref(), - ) - .await + let config_layer_stack = match self + .config_manager + .load_config_layers_for_cwd(cwd_abs.clone()) + .await { Ok(config_layer_stack) => config_layer_stack, Err(err) => { @@ -8772,30 +8665,25 @@ impl CodexMessageProcessor { WindowsSandboxSetupMode::Unelevated => CoreWindowsSandboxSetupMode::Unelevated, }; let config = Arc::clone(&self.config); - let cloud_requirements = self.current_cloud_requirements(); + let config_manager = self.config_manager.clone(); let command_cwd = params .cwd .map(PathBuf::from) .unwrap_or_else(|| config.cwd.to_path_buf()); - let cli_overrides = self.current_cli_overrides(); - let runtime_feature_enablement = self.current_runtime_feature_enablement(); let outgoing = Arc::clone(&self.outgoing); let connection_id = request_id.connection_id; tokio::spawn(async move { - let derived_config = derive_config_for_cwd( - &cli_overrides, - /*request_overrides*/ None, - ConfigOverrides { - cwd: Some(command_cwd.clone()), - ..Default::default() - }, - Some(command_cwd.clone()), - &cloud_requirements, - &config.codex_home, - &runtime_feature_enablement, - ) - .await; + let derived_config = config_manager + .load_for_cwd( + /*request_overrides*/ None, + ConfigOverrides { + cwd: Some(command_cwd.clone()), + ..Default::default() + }, + Some(command_cwd.clone()), + ) + .await; let setup_result = match derived_config { Ok(config) => { let setup_request = WindowsSandboxSetupRequest { @@ -9525,116 +9413,6 @@ fn validate_dynamic_tools(tools: &[ApiDynamicToolSpec]) -> Result<(), String> { Ok(()) } -fn replace_cloud_requirements_loader( - cloud_requirements: &RwLock, - auth_manager: Arc, - chatgpt_base_url: String, - codex_home: PathBuf, -) { - let loader = cloud_requirements_loader(auth_manager, chatgpt_base_url, codex_home); - if let Ok(mut guard) = cloud_requirements.write() { - *guard = loader; - } else { - warn!("failed to update cloud requirements loader"); - } -} - -async fn sync_default_client_residency_requirement( - cli_overrides: &[(String, TomlValue)], - cloud_requirements: &RwLock, -) { - let loader = cloud_requirements - .read() - .map(|guard| guard.clone()) - .unwrap_or_default(); - match codex_core::config::ConfigBuilder::default() - .cli_overrides(cli_overrides.to_vec()) - .cloud_requirements(loader) - .build() - .await - { - Ok(config) => set_default_client_residency_requirement(config.enforce_residency.value()), - Err(err) => warn!( - error = %err, - "failed to sync default client residency requirement after auth refresh" - ), - } -} - -/// Derive the effective [`Config`] by layering three override sources. -/// -/// Precedence (lowest to highest): -/// - `cli_overrides`: process-wide startup `--config` flags. -/// - `request_overrides`: per-request dotted-path overrides (`params.config`), converted JSON->TOML. -/// - `typesafe_overrides`: Request objects such as `NewThreadParams` and -/// `ThreadStartParams` support a limited set of _explicit_ config overrides, so -/// `typesafe_overrides` is a `ConfigOverrides` derived from the respective request object. -/// Because the overrides are defined explicitly in the `*Params`, this takes priority over -/// the more general "bag of config options" provided by `cli_overrides` and `request_overrides`. -async fn derive_config_from_params( - cli_overrides: &[(String, TomlValue)], - request_overrides: Option>, - typesafe_overrides: ConfigOverrides, - thread_config_loader: Arc, - cloud_requirements: &CloudRequirementsLoader, - codex_home: &Path, - runtime_feature_enablement: &BTreeMap, -) -> std::io::Result { - let merged_cli_overrides = cli_overrides - .iter() - .cloned() - .chain( - request_overrides - .unwrap_or_default() - .into_iter() - .map(|(k, v)| (k, json_to_toml(v))), - ) - .collect::>(); - - let mut config = codex_core::config::ConfigBuilder::default() - .codex_home(codex_home.to_path_buf()) - .cli_overrides(merged_cli_overrides) - .harness_overrides(typesafe_overrides) - .cloud_requirements(cloud_requirements.clone()) - .thread_config_loader(thread_config_loader) - .build() - .await?; - apply_runtime_feature_enablement(&mut config, runtime_feature_enablement); - Ok(config) -} - -async fn derive_config_for_cwd( - cli_overrides: &[(String, TomlValue)], - request_overrides: Option>, - typesafe_overrides: ConfigOverrides, - cwd: Option, - cloud_requirements: &CloudRequirementsLoader, - codex_home: &Path, - runtime_feature_enablement: &BTreeMap, -) -> std::io::Result { - let merged_cli_overrides = cli_overrides - .iter() - .cloned() - .chain( - request_overrides - .unwrap_or_default() - .into_iter() - .map(|(k, v)| (k, json_to_toml(v))), - ) - .collect::>(); - - let mut config = codex_core::config::ConfigBuilder::default() - .codex_home(codex_home.to_path_buf()) - .cli_overrides(merged_cli_overrides) - .harness_overrides(typesafe_overrides) - .fallback_cwd(cwd) - .cloud_requirements(cloud_requirements.clone()) - .build() - .await?; - apply_runtime_feature_enablement(&mut config, runtime_feature_enablement); - Ok(config) -} - async fn read_history_cwd_from_state_db( config: &Config, thread_id: Option, @@ -10556,6 +10334,8 @@ mod tests { use codex_config::SessionThreadConfig; use codex_config::StaticThreadConfigLoader; use codex_config::ThreadConfigSource; + use codex_core::config_loader::CloudRequirementsLoader; + use codex_core::config_loader::LoaderOverrides; use codex_model_provider_info::ModelProviderInfo; use codex_model_provider_info::WireApi; use codex_protocol::ThreadId; @@ -10569,8 +10349,10 @@ mod tests { use codex_utils_absolute_path::test_support::test_path_buf; use pretty_assertions::assert_eq; use serde_json::json; + use std::collections::BTreeMap; use std::path::PathBuf; use std::sync::Arc; + use std::sync::RwLock; use tempfile::TempDir; #[test] @@ -10844,21 +10626,13 @@ mod tests { requires_openai_auth: false, supports_websockets: true, }; - let config = derive_config_from_params( - &[], - Some(HashMap::from([ - ("model_provider".to_string(), json!("request")), - ("features.plugins".to_string(), json!(true)), - ( - "model_providers.session".to_string(), - json!({ - "name": "request", - "base_url": "http://127.0.0.1:9999/api/codex", - "wire_api": "responses", - }), - ), - ])), - ConfigOverrides::default(), + let config_manager = ConfigManager::new( + temp_dir.path().to_path_buf(), + Arc::new(RwLock::new(Vec::new())), + Arc::new(RwLock::new(BTreeMap::new())), + LoaderOverrides::default(), + Arc::new(RwLock::new(CloudRequirementsLoader::default())), + Arg0DispatchPaths::default(), Arc::new(StaticThreadConfigLoader::new(vec![ ThreadConfigSource::Session(SessionThreadConfig { model_provider: Some("session".to_string()), @@ -10869,11 +10643,24 @@ mod tests { features: BTreeMap::from([("plugins".to_string(), false)]), }), ])), - &CloudRequirementsLoader::default(), - temp_dir.path(), - &BTreeMap::new(), - ) - .await?; + ); + let config = config_manager + .load_with_overrides( + Some(HashMap::from([ + ("model_provider".to_string(), json!("request")), + ("features.plugins".to_string(), json!(true)), + ( + "model_providers.session".to_string(), + json!({ + "name": "request", + "base_url": "http://127.0.0.1:9999/api/codex", + "wire_api": "responses", + }), + ), + ])), + ConfigOverrides::default(), + ) + .await?; assert_eq!(config.model_provider_id, "session"); assert_eq!(config.model_provider, session_provider); diff --git a/codex-rs/app-server/src/config_api.rs b/codex-rs/app-server/src/config_api.rs index c57d3893c1..ffccd6b1b2 100644 --- a/codex-rs/app-server/src/config_api.rs +++ b/codex-rs/app-server/src/config_api.rs @@ -1,3 +1,5 @@ +use crate::config_manager::ConfigManager; +use crate::config_manager_service::ConfigManagerError; use crate::error_code::INTERNAL_ERROR_CODE; use crate::error_code::INVALID_REQUEST_ERROR_CODE; use async_trait::async_trait; @@ -19,11 +21,7 @@ use codex_app_server_protocol::NetworkUnixSocketPermission; use codex_app_server_protocol::SandboxMode; use codex_core::ThreadManager; use codex_core::config::Config; -use codex_core::config::ConfigService; -use codex_core::config::ConfigServiceError; -use codex_core::config_loader::CloudRequirementsLoader; use codex_core::config_loader::ConfigRequirementsToml; -use codex_core::config_loader::LoaderOverrides; use codex_core::config_loader::ResidencyRequirement as CoreResidencyRequirement; use codex_core::config_loader::SandboxModeRequirement as CoreSandboxModeRequirement; use codex_core::plugins::PluginId; @@ -34,12 +32,8 @@ use codex_features::feature_for_key; use codex_protocol::config_types::WebSearchMode; use codex_protocol::protocol::Op; use serde_json::json; -use std::collections::BTreeMap; -use std::collections::BTreeSet; use std::path::PathBuf; use std::sync::Arc; -use std::sync::RwLock; -use toml::Value as TomlValue; use tracing::warn; const SUPPORTED_EXPERIMENTAL_FEATURE_ENABLEMENT: &[&str] = &[ @@ -72,86 +66,36 @@ impl UserConfigReloader for ThreadManager { #[derive(Clone)] pub(crate) struct ConfigApi { - codex_home: PathBuf, - cli_overrides: Arc>>, - runtime_feature_enablement: Arc>>, - loader_overrides: LoaderOverrides, - cloud_requirements: Arc>, + config_manager: ConfigManager, user_config_reloader: Arc, analytics_events_client: AnalyticsEventsClient, } impl ConfigApi { pub(crate) fn new( - codex_home: PathBuf, - cli_overrides: Arc>>, - runtime_feature_enablement: Arc>>, - loader_overrides: LoaderOverrides, - cloud_requirements: Arc>, + config_manager: ConfigManager, user_config_reloader: Arc, analytics_events_client: AnalyticsEventsClient, ) -> Self { Self { - codex_home, - cli_overrides, - runtime_feature_enablement, - loader_overrides, - cloud_requirements, + config_manager, user_config_reloader, analytics_events_client, } } - fn config_service(&self) -> ConfigService { - ConfigService::new( - self.codex_home.clone(), - self.current_cli_overrides(), - self.loader_overrides.clone(), - self.current_cloud_requirements(), - codex_config::host_name(), - ) - } - - fn current_cli_overrides(&self) -> Vec<(String, TomlValue)> { - self.cli_overrides - .read() - .map(|guard| guard.clone()) - .unwrap_or_default() - } - - fn current_runtime_feature_enablement(&self) -> BTreeMap { - self.runtime_feature_enablement - .read() - .map(|guard| guard.clone()) - .unwrap_or_default() - } - - fn current_cloud_requirements(&self) -> CloudRequirementsLoader { - self.cloud_requirements - .read() - .map(|guard| guard.clone()) - .unwrap_or_default() - } - pub(crate) async fn load_latest_config( &self, fallback_cwd: Option, ) -> Result { - let mut config = codex_core::config::ConfigBuilder::default() - .codex_home(self.codex_home.clone()) - .cli_overrides(self.current_cli_overrides()) - .loader_overrides(self.loader_overrides.clone()) - .fallback_cwd(fallback_cwd) - .cloud_requirements(self.current_cloud_requirements()) - .build() + self.config_manager + .load_latest_config(fallback_cwd) .await .map_err(|err| JSONRPCErrorError { code: INTERNAL_ERROR_CODE, message: format!("failed to resolve feature override precedence: {err}"), data: None, - })?; - apply_runtime_feature_enablement(&mut config, &self.current_runtime_feature_enablement()); - Ok(config) + }) } pub(crate) async fn read( @@ -159,11 +103,7 @@ impl ConfigApi { params: ConfigReadParams, ) -> Result { let fallback_cwd = params.cwd.as_ref().map(PathBuf::from); - let mut response = self - .config_service() - .read(params) - .await - .map_err(map_error)?; + let mut response = self.config_manager.read(params).await.map_err(map_error)?; let config = self.load_latest_config(fallback_cwd).await?; for feature_key in SUPPORTED_EXPERIMENTAL_FEATURE_ENABLEMENT { let Some(feature) = feature_for_key(feature_key) else { @@ -191,7 +131,7 @@ impl ConfigApi { &self, ) -> Result { let requirements = self - .config_service() + .config_manager .read_requirements() .await .map_err(map_error)? @@ -207,7 +147,7 @@ impl ConfigApi { let pending_changes = collect_plugin_enabled_candidates([(¶ms.key_path, ¶ms.value)].into_iter()); let response = self - .config_service() + .config_manager .write_value(params) .await .map_err(map_error)?; @@ -227,7 +167,7 @@ impl ConfigApi { .map(|edit| (&edit.key_path, &edit.value)), ); let response = self - .config_service() + .config_manager .batch_write(params) .await .map_err(map_error)?; @@ -278,21 +218,17 @@ impl ConfigApi { return Ok(ExperimentalFeatureEnablementSetResponse { enablement }); } - { - let mut runtime_feature_enablement = - self.runtime_feature_enablement - .write() - .map_err(|_| JSONRPCErrorError { - code: INTERNAL_ERROR_CODE, - message: "failed to update feature enablement".to_string(), - data: None, - })?; - runtime_feature_enablement.extend( + self.config_manager + .extend_runtime_feature_enablement( enablement .iter() .map(|(name, enabled)| (name.clone(), *enabled)), - ); - } + ) + .map_err(|_| JSONRPCErrorError { + code: INTERNAL_ERROR_CODE, + message: "failed to update feature enablement".to_string(), + data: None, + })?; self.load_latest_config(/*fallback_cwd*/ None).await?; self.user_config_reloader.reload_user_config().await; @@ -309,7 +245,8 @@ impl ConfigApi { continue; }; let metadata = - installed_plugin_telemetry_metadata(self.codex_home.as_path(), &plugin_id).await; + installed_plugin_telemetry_metadata(self.config_manager.codex_home(), &plugin_id) + .await; if enabled { self.analytics_events_client.track_plugin_enabled(metadata); } else { @@ -319,49 +256,6 @@ impl ConfigApi { } } -pub(crate) fn protected_feature_keys( - config_layer_stack: &codex_core::config_loader::ConfigLayerStack, -) -> BTreeSet { - let mut protected_features = config_layer_stack - .effective_config() - .get("features") - .and_then(toml::Value::as_table) - .map(|features| features.keys().cloned().collect::>()) - .unwrap_or_default(); - - if let Some(feature_requirements) = config_layer_stack - .requirements_toml() - .feature_requirements - .as_ref() - { - protected_features.extend(feature_requirements.entries.keys().cloned()); - } - - protected_features -} - -pub(crate) fn apply_runtime_feature_enablement( - config: &mut Config, - runtime_feature_enablement: &BTreeMap, -) { - let protected_features = protected_feature_keys(&config.config_layer_stack); - for (name, enabled) in runtime_feature_enablement { - if protected_features.contains(name) { - continue; - } - let Some(feature) = feature_for_key(name) else { - continue; - }; - if let Err(err) = config.features.set_enabled(feature, *enabled) { - warn!( - feature = name, - error = %err, - "failed to apply runtime feature enablement" - ); - } - } -} - fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigRequirements { ConfigRequirements { allowed_approval_policies: requirements.allowed_approval_policies.map(|policies| { @@ -495,7 +389,7 @@ fn map_network_unix_socket_permission_to_api( } } -fn map_error(err: ConfigServiceError) -> JSONRPCErrorError { +fn map_error(err: ConfigManagerError) -> JSONRPCErrorError { if let Some(code) = err.write_error_code() { return config_write_error(code, err.to_string()); } @@ -520,7 +414,11 @@ fn config_write_error(code: ConfigWriteErrorCode, message: impl Into) -> #[cfg(test)] mod tests { use super::*; + use crate::config_manager::apply_runtime_feature_enablement; use codex_analytics::AnalyticsEventsClient; + use codex_arg0::Arg0DispatchPaths; + use codex_core::config_loader::CloudRequirementsLoader; + use codex_core::config_loader::LoaderOverrides; use codex_core::config_loader::NetworkDomainPermissionToml as CoreNetworkDomainPermissionToml; use codex_core::config_loader::NetworkDomainPermissionsToml as CoreNetworkDomainPermissionsToml; use codex_core::config_loader::NetworkRequirementsToml as CoreNetworkRequirementsToml; @@ -533,9 +431,12 @@ mod tests { use codex_protocol::protocol::AskForApproval as CoreAskForApproval; use pretty_assertions::assert_eq; use serde_json::json; + use std::collections::BTreeMap; + use std::sync::RwLock; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; use tempfile::TempDir; + use toml::Value as TomlValue; #[derive(Default)] struct RecordingUserConfigReloader { @@ -830,11 +731,15 @@ mod tests { ); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("test")); let config_api = ConfigApi::new( - codex_home.path().to_path_buf(), - Arc::new(RwLock::new(Vec::new())), - Arc::new(RwLock::new(BTreeMap::new())), - LoaderOverrides::default(), - Arc::new(RwLock::new(CloudRequirementsLoader::default())), + ConfigManager::new( + codex_home.path().to_path_buf(), + Arc::new(RwLock::new(Vec::new())), + Arc::new(RwLock::new(BTreeMap::new())), + LoaderOverrides::default(), + Arc::new(RwLock::new(CloudRequirementsLoader::default())), + Arg0DispatchPaths::default(), + Arc::new(codex_config::NoopThreadConfigLoader), + ), reloader.clone(), AnalyticsEventsClient::new( auth_manager, diff --git a/codex-rs/app-server/src/config_manager.rs b/codex-rs/app-server/src/config_manager.rs new file mode 100644 index 0000000000..67e0072f47 --- /dev/null +++ b/codex-rs/app-server/src/config_manager.rs @@ -0,0 +1,326 @@ +use codex_arg0::Arg0DispatchPaths; +use codex_cloud_requirements::cloud_requirements_loader; +use codex_config::ThreadConfigLoader; +use codex_core::config::Config; +use codex_core::config::ConfigOverrides; +use codex_core::config_loader::CloudRequirementsLoader; +use codex_core::config_loader::ConfigLayerStack; +use codex_core::config_loader::LoaderOverrides; +use codex_core::config_loader::load_config_layers_state; +use codex_exec_server::LOCAL_FS; +use codex_features::feature_for_key; +use codex_login::AuthManager; +use codex_login::default_client::set_default_client_residency_requirement; +use codex_utils_absolute_path::AbsolutePathBuf; +use codex_utils_json_to_toml::json_to_toml; +use std::collections::BTreeMap; +use std::collections::BTreeSet; +use std::collections::HashMap; +use std::path::Path; +use std::path::PathBuf; +use std::sync::Arc; +use std::sync::RwLock; +use toml::Value as TomlValue; +use tracing::warn; + +/// Shared app-server entry point for loading effective Codex configuration. +#[derive(Clone)] +pub(crate) struct ConfigManager { + codex_home: PathBuf, + cli_overrides: Arc>>, + runtime_feature_enablement: Arc>>, + loader_overrides: LoaderOverrides, + cloud_requirements: Arc>, + arg0_paths: Arg0DispatchPaths, + thread_config_loader: Arc, + host_name: Option, +} + +impl ConfigManager { + pub(crate) fn new( + codex_home: PathBuf, + cli_overrides: Arc>>, + runtime_feature_enablement: Arc>>, + loader_overrides: LoaderOverrides, + cloud_requirements: Arc>, + arg0_paths: Arg0DispatchPaths, + thread_config_loader: Arc, + ) -> Self { + Self::new_with_host_name( + codex_home, + cli_overrides, + runtime_feature_enablement, + loader_overrides, + cloud_requirements, + arg0_paths, + thread_config_loader, + codex_config::host_name(), + ) + } + + #[allow(clippy::too_many_arguments)] + fn new_with_host_name( + codex_home: PathBuf, + cli_overrides: Arc>>, + runtime_feature_enablement: Arc>>, + loader_overrides: LoaderOverrides, + cloud_requirements: Arc>, + arg0_paths: Arg0DispatchPaths, + thread_config_loader: Arc, + host_name: Option, + ) -> Self { + Self { + codex_home, + cli_overrides, + runtime_feature_enablement, + loader_overrides, + cloud_requirements, + arg0_paths, + thread_config_loader, + host_name, + } + } + + pub(crate) fn codex_home(&self) -> &Path { + self.codex_home.as_path() + } + + pub(crate) fn current_cli_overrides(&self) -> Vec<(String, TomlValue)> { + self.cli_overrides + .read() + .map(|guard| guard.clone()) + .unwrap_or_default() + } + + pub(crate) fn current_cloud_requirements(&self) -> CloudRequirementsLoader { + self.cloud_requirements + .read() + .map(|guard| guard.clone()) + .unwrap_or_default() + } + + pub(crate) fn extend_runtime_feature_enablement(&self, enablement: I) -> Result<(), ()> + where + I: IntoIterator, + { + let mut runtime_feature_enablement = + self.runtime_feature_enablement.write().map_err(|_| ())?; + runtime_feature_enablement.extend(enablement); + Ok(()) + } + + pub(crate) fn replace_cloud_requirements_loader( + &self, + auth_manager: Arc, + chatgpt_base_url: String, + ) { + let loader = + cloud_requirements_loader(auth_manager, chatgpt_base_url, self.codex_home.clone()); + if let Ok(mut guard) = self.cloud_requirements.write() { + *guard = loader; + } else { + warn!("failed to update cloud requirements loader"); + } + } + + pub(crate) async fn sync_default_client_residency_requirement(&self) { + match self.load_latest_config(/*fallback_cwd*/ None).await { + Ok(config) => { + set_default_client_residency_requirement(config.enforce_residency.value()); + } + Err(err) => warn!( + error = %err, + "failed to sync default client residency requirement after auth refresh" + ), + } + } + + pub(crate) async fn load_latest_config( + &self, + fallback_cwd: Option, + ) -> std::io::Result { + self.load_with_cli_overrides( + &self.current_cli_overrides(), + /*request_overrides*/ None, + ConfigOverrides::default(), + fallback_cwd, + ) + .await + } + + pub(crate) async fn load_with_overrides( + &self, + request_overrides: Option>, + typesafe_overrides: ConfigOverrides, + ) -> std::io::Result { + self.load_with_cli_overrides( + &self.current_cli_overrides(), + request_overrides, + typesafe_overrides, + /*fallback_cwd*/ None, + ) + .await + } + + pub(crate) async fn load_for_cwd( + &self, + request_overrides: Option>, + typesafe_overrides: ConfigOverrides, + cwd: Option, + ) -> std::io::Result { + self.load_with_cli_overrides( + &self.current_cli_overrides(), + request_overrides, + typesafe_overrides, + cwd, + ) + .await + } + + pub(crate) async fn load_with_cli_overrides( + &self, + cli_overrides: &[(String, TomlValue)], + request_overrides: Option>, + typesafe_overrides: ConfigOverrides, + fallback_cwd: Option, + ) -> std::io::Result { + let merged_cli_overrides = cli_overrides + .iter() + .cloned() + .chain( + request_overrides + .unwrap_or_default() + .into_iter() + .map(|(key, value)| (key, json_to_toml(value))), + ) + .collect::>(); + + let mut config = codex_core::config::ConfigBuilder::default() + .codex_home(self.codex_home.clone()) + .cli_overrides(merged_cli_overrides) + .loader_overrides(self.loader_overrides.clone()) + .harness_overrides(typesafe_overrides) + .fallback_cwd(fallback_cwd) + .cloud_requirements(self.current_cloud_requirements()) + .thread_config_loader(Arc::clone(&self.thread_config_loader)) + .host_name(self.host_name.clone()) + .build() + .await?; + self.apply_runtime_feature_enablement(&mut config); + self.apply_arg0_paths(&mut config); + Ok(config) + } + + pub(crate) async fn load_config_layers_for_cwd( + &self, + cwd: AbsolutePathBuf, + ) -> std::io::Result { + self.load_config_layers(Some(cwd)).await + } + + pub(crate) async fn load_config_layers( + &self, + cwd: Option, + ) -> std::io::Result { + load_config_layers_state( + LOCAL_FS.as_ref(), + &self.codex_home, + cwd, + &self.current_cli_overrides(), + self.loader_overrides.clone(), + self.current_cloud_requirements(), + self.thread_config_loader.as_ref(), + self.host_name.as_deref(), + ) + .await + } + + fn apply_runtime_feature_enablement(&self, config: &mut Config) { + apply_runtime_feature_enablement(config, &self.current_runtime_feature_enablement()); + } + + fn current_runtime_feature_enablement(&self) -> BTreeMap { + self.runtime_feature_enablement + .read() + .map(|guard| guard.clone()) + .unwrap_or_default() + } + + fn apply_arg0_paths(&self, config: &mut Config) { + config.codex_self_exe = self.arg0_paths.codex_self_exe.clone(); + config.codex_linux_sandbox_exe = self.arg0_paths.codex_linux_sandbox_exe.clone(); + config.main_execve_wrapper_exe = self.arg0_paths.main_execve_wrapper_exe.clone(); + } + + #[cfg(test)] + pub(crate) fn new_for_tests( + codex_home: PathBuf, + cli_overrides: Vec<(String, TomlValue)>, + loader_overrides: LoaderOverrides, + cloud_requirements: CloudRequirementsLoader, + host_name: Option, + ) -> Self { + Self::new_with_host_name( + codex_home, + Arc::new(RwLock::new(cli_overrides)), + Arc::new(RwLock::new(BTreeMap::new())), + loader_overrides, + Arc::new(RwLock::new(cloud_requirements)), + Arg0DispatchPaths::default(), + Arc::new(codex_config::NoopThreadConfigLoader), + host_name, + ) + } + + #[cfg(test)] + pub(crate) fn without_managed_config_for_tests(codex_home: PathBuf) -> Self { + Self::new_for_tests( + codex_home, + Vec::new(), + LoaderOverrides::without_managed_config_for_tests(), + CloudRequirementsLoader::default(), + /*host_name*/ None, + ) + } +} + +pub(crate) fn protected_feature_keys(config_layer_stack: &ConfigLayerStack) -> BTreeSet { + let mut protected_features = config_layer_stack + .effective_config() + .get("features") + .and_then(toml::Value::as_table) + .map(|features| features.keys().cloned().collect::>()) + .unwrap_or_default(); + + if let Some(feature_requirements) = config_layer_stack + .requirements_toml() + .feature_requirements + .as_ref() + { + protected_features.extend(feature_requirements.entries.keys().cloned()); + } + + protected_features +} + +pub(crate) fn apply_runtime_feature_enablement( + config: &mut Config, + runtime_feature_enablement: &BTreeMap, +) { + let protected_features = protected_feature_keys(&config.config_layer_stack); + for (name, enabled) in runtime_feature_enablement { + if protected_features.contains(name) { + continue; + } + let Some(feature) = feature_for_key(name) else { + continue; + }; + if let Err(err) = config.features.set_enabled(feature, *enabled) { + warn!( + feature = name, + error = %err, + "failed to apply runtime feature enablement" + ); + } + } +} diff --git a/codex-rs/core/src/config/service.rs b/codex-rs/app-server/src/config_manager_service.rs similarity index 74% rename from codex-rs/core/src/config/service.rs rename to codex-rs/app-server/src/config_manager_service.rs index e9546ef4db..0104429a4b 100644 --- a/codex-rs/core/src/config/service.rs +++ b/codex-rs/app-server/src/config_manager_service.rs @@ -1,20 +1,4 @@ -use super::deserialize_config_toml_with_base; -use crate::config::edit::ConfigEdit; -use crate::config::edit::ConfigEditsBuilder; -use crate::config::managed_features::validate_explicit_feature_settings_in_config_toml; -use crate::config::managed_features::validate_feature_requirements_in_config_toml; -use crate::config_loader::CloudRequirementsLoader; -use crate::config_loader::ConfigLayerEntry; -use crate::config_loader::ConfigLayerStack; -use crate::config_loader::ConfigLayerStackOrdering; -use crate::config_loader::ConfigRequirementsToml; -use crate::config_loader::LoaderOverrides; -use crate::config_loader::load_config_layers_state; -use crate::config_loader::merge_toml_values; -use crate::path_utils; -use crate::path_utils::SymlinkWritePaths; -use crate::path_utils::resolve_symlink_write_paths; -use crate::path_utils::write_atomically; +use crate::config_manager::ConfigManager; use codex_app_server_protocol::Config as ApiConfig; use codex_app_server_protocol::ConfigBatchWriteParams; use codex_app_server_protocol::ConfigLayerMetadata; @@ -29,7 +13,19 @@ use codex_app_server_protocol::OverriddenMetadata; use codex_app_server_protocol::WriteStatus; use codex_config::CONFIG_TOML_FILE; use codex_config::config_toml::ConfigToml; -use codex_exec_server::LOCAL_FS; +use codex_core::config::deserialize_config_toml_with_base; +use codex_core::config::edit::ConfigEdit; +use codex_core::config::edit::ConfigEditsBuilder; +use codex_core::config::validate_feature_requirements_for_config_toml; +use codex_core::config_loader::ConfigLayerEntry; +use codex_core::config_loader::ConfigLayerStack; +use codex_core::config_loader::ConfigLayerStackOrdering; +use codex_core::config_loader::ConfigRequirementsToml; +use codex_core::config_loader::merge_toml_values; +use codex_core::path_utils; +use codex_core::path_utils::SymlinkWritePaths; +use codex_core::path_utils::resolve_symlink_write_paths; +use codex_core::path_utils::write_atomically; use codex_utils_absolute_path::AbsolutePathBuf; use serde_json::Value as JsonValue; use std::borrow::Cow; @@ -41,7 +37,7 @@ use toml::Value as TomlValue; use toml_edit::Item as TomlItem; #[derive(Debug, Error)] -pub enum ConfigServiceError { +pub(crate) enum ConfigManagerError { #[error("{message}")] Write { code: ConfigWriteErrorCode, @@ -77,7 +73,7 @@ pub enum ConfigServiceError { }, } -impl ConfigServiceError { +impl ConfigManagerError { fn write(code: ConfigWriteErrorCode, message: impl Into) -> Self { Self::Write { code, @@ -101,7 +97,7 @@ impl ConfigServiceError { Self::Anyhow { context, source } } - pub fn write_error_code(&self) -> Option { + pub(crate) fn write_error_code(&self) -> Option { match self { Self::Write { code, .. } => Some(code.clone()), _ => None, @@ -109,78 +105,22 @@ impl ConfigServiceError { } } -#[derive(Clone)] -pub struct ConfigService { - codex_home: PathBuf, - cli_overrides: Vec<(String, TomlValue)>, - loader_overrides: LoaderOverrides, - cloud_requirements: CloudRequirementsLoader, - host_name: Option, -} - -impl ConfigService { - pub fn new( - codex_home: PathBuf, - cli_overrides: Vec<(String, TomlValue)>, - loader_overrides: LoaderOverrides, - cloud_requirements: CloudRequirementsLoader, - host_name: Option, - ) -> Self { - Self { - codex_home, - cli_overrides, - loader_overrides, - cloud_requirements, - host_name, - } - } - - pub fn new_with_defaults(codex_home: PathBuf) -> Self { - Self { - codex_home, - cli_overrides: Vec::new(), - loader_overrides: LoaderOverrides::default(), - cloud_requirements: CloudRequirementsLoader::default(), - host_name: codex_config::host_name(), - } - } - - #[cfg(test)] - pub(crate) fn without_managed_config_for_tests(codex_home: PathBuf) -> Self { - Self::new( - codex_home, - Vec::new(), - LoaderOverrides::without_managed_config_for_tests(), - CloudRequirementsLoader::default(), - /*host_name*/ None, - ) - } - - pub async fn read( +impl ConfigManager { + pub(crate) async fn read( &self, params: ConfigReadParams, - ) -> Result { + ) -> Result { let layers = match params.cwd.as_deref() { Some(cwd) => { let cwd = AbsolutePathBuf::try_from(PathBuf::from(cwd)).map_err(|err| { - ConfigServiceError::io("failed to resolve config cwd to an absolute path", err) + ConfigManagerError::io("failed to resolve config cwd to an absolute path", err) })?; - crate::config::ConfigBuilder::default() - .codex_home(self.codex_home.clone()) - .cli_overrides(self.cli_overrides.clone()) - .loader_overrides(self.loader_overrides.clone()) - .fallback_cwd(Some(cwd.to_path_buf())) - .cloud_requirements(self.cloud_requirements.clone()) - .host_name(self.host_name.clone()) - .build() - .await - .map_err(|err| { - ConfigServiceError::io("failed to read configuration layers", err) - })? - .config_layer_stack + self.load_config_layers(Some(cwd)).await.map_err(|err| { + ConfigManagerError::io("failed to read configuration layers", err) + })? } None => self.load_thread_agnostic_config().await.map_err(|err| { - ConfigServiceError::io("failed to read configuration layers", err) + ConfigManagerError::io("failed to read configuration layers", err) })?, }; @@ -188,12 +128,12 @@ impl ConfigService { let effective_config_toml: ConfigToml = effective .try_into() - .map_err(|err| ConfigServiceError::toml("invalid configuration", err))?; + .map_err(|err| ConfigManagerError::toml("invalid configuration", err))?; let json_value = serde_json::to_value(&effective_config_toml) - .map_err(|err| ConfigServiceError::json("failed to serialize configuration", err))?; + .map_err(|err| ConfigManagerError::json("failed to serialize configuration", err))?; let config: ApiConfig = serde_json::from_value(json_value) - .map_err(|err| ConfigServiceError::json("failed to deserialize configuration", err))?; + .map_err(|err| ConfigManagerError::json("failed to deserialize configuration", err))?; Ok(ConfigReadResponse { config, @@ -211,13 +151,13 @@ impl ConfigService { }) } - pub async fn read_requirements( + pub(crate) async fn read_requirements( &self, - ) -> Result, ConfigServiceError> { + ) -> Result, ConfigManagerError> { let layers = self .load_thread_agnostic_config() .await - .map_err(|err| ConfigServiceError::io("failed to read configuration layers", err))?; + .map_err(|err| ConfigManagerError::io("failed to read configuration layers", err))?; let requirements = layers.requirements_toml().clone(); if requirements.is_empty() { @@ -227,19 +167,19 @@ impl ConfigService { } } - pub async fn write_value( + pub(crate) async fn write_value( &self, params: ConfigValueWriteParams, - ) -> Result { + ) -> Result { let edits = vec![(params.key_path, params.value, params.merge_strategy)]; self.apply_edits(params.file_path, params.expected_version, edits) .await } - pub async fn batch_write( + pub(crate) async fn batch_write( &self, params: ConfigBatchWriteParams, - ) -> Result { + ) -> Result { let edits = params .edits .into_iter() @@ -250,37 +190,22 @@ impl ConfigService { .await } - pub async fn load_user_saved_config( - &self, - ) -> Result { - let layers = self - .load_thread_agnostic_config() - .await - .map_err(|err| ConfigServiceError::io("failed to load configuration", err))?; - - let toml_value = layers.effective_config(); - let cfg: ConfigToml = toml_value - .try_into() - .map_err(|err| ConfigServiceError::toml("failed to parse config.toml", err))?; - Ok(cfg.into()) - } - async fn apply_edits( &self, file_path: Option, expected_version: Option, edits: Vec<(String, JsonValue, MergeStrategy)>, - ) -> Result { + ) -> Result { let allowed_path = - AbsolutePathBuf::resolve_path_against_base(CONFIG_TOML_FILE, &self.codex_home); + AbsolutePathBuf::resolve_path_against_base(CONFIG_TOML_FILE, self.codex_home()); let provided_path = match file_path { Some(path) => AbsolutePathBuf::from_absolute_path(PathBuf::from(path)) - .map_err(|err| ConfigServiceError::io("failed to resolve user config path", err))?, + .map_err(|err| ConfigManagerError::io("failed to resolve user config path", err))?, None => allowed_path.clone(), }; if !paths_match(&allowed_path, &provided_path) { - return Err(ConfigServiceError::write( + return Err(ConfigManagerError::write( ConfigWriteErrorCode::ConfigLayerReadonly, "Only writes to the user config are allowed", )); @@ -289,7 +214,7 @@ impl ConfigService { let layers = self .load_thread_agnostic_config() .await - .map_err(|err| ConfigServiceError::io("failed to load configuration", err))?; + .map_err(|err| ConfigManagerError::io("failed to load configuration", err))?; let user_layer = match layers.get_user_layer() { Some(layer) => Cow::Borrowed(layer), None => Cow::Owned(create_empty_user_layer(&allowed_path).await?), @@ -298,7 +223,7 @@ impl ConfigService { if let Some(expected) = expected_version.as_deref() && expected != user_layer.version { - return Err(ConfigServiceError::write( + return Err(ConfigManagerError::write( ConfigWriteErrorCode::ConfigVersionConflict, "Configuration was modified since last read. Fetch latest version and retry.", )); @@ -310,20 +235,20 @@ impl ConfigService { for (key_path, value, strategy) in edits.into_iter() { let segments = parse_key_path(&key_path).map_err(|message| { - ConfigServiceError::write(ConfigWriteErrorCode::ConfigValidationError, message) + ConfigManagerError::write(ConfigWriteErrorCode::ConfigValidationError, message) })?; let original_value = value_at_path(&user_config, &segments).cloned(); let parsed_value = parse_value(value).map_err(|message| { - ConfigServiceError::write(ConfigWriteErrorCode::ConfigValidationError, message) + ConfigManagerError::write(ConfigWriteErrorCode::ConfigValidationError, message) })?; apply_merge(&mut user_config, &segments, parsed_value.as_ref(), strategy).map_err( |err| match err { - MergeError::PathNotFound => ConfigServiceError::write( + MergeError::PathNotFound => ConfigManagerError::write( ConfigWriteErrorCode::ConfigPathNotFound, "Path not found", ), - MergeError::Validation(message) => ConfigServiceError::write( + MergeError::Validation(message) => ConfigManagerError::write( ConfigWriteErrorCode::ConfigValidationError, message, ), @@ -336,7 +261,7 @@ impl ConfigService { Some(value) => ConfigEdit::SetPath { segments: segments.clone(), value: toml_value_to_item(&value).map_err(|err| { - ConfigServiceError::anyhow("failed to build config edits", err) + ConfigManagerError::anyhow("failed to build config edits", err) })?, }, None => ConfigEdit::ClearPath { @@ -350,56 +275,45 @@ impl ConfigService { } validate_config(&user_config).map_err(|err| { - ConfigServiceError::write( + ConfigManagerError::write( ConfigWriteErrorCode::ConfigValidationError, format!("Invalid configuration: {err}"), ) })?; let user_config_toml = - deserialize_config_toml_with_base(user_config.clone(), &self.codex_home).map_err( + deserialize_config_toml_with_base(user_config.clone(), self.codex_home()).map_err( |err| { - ConfigServiceError::write( + ConfigManagerError::write( ConfigWriteErrorCode::ConfigValidationError, format!("Invalid configuration: {err}"), ) }, )?; - validate_explicit_feature_settings_in_config_toml( + validate_feature_requirements_for_config_toml( &user_config_toml, layers.requirements().feature_requirements.as_ref(), ) .map_err(|err| { - ConfigServiceError::write( + ConfigManagerError::write( ConfigWriteErrorCode::ConfigValidationError, format!("Invalid configuration: {err}"), ) })?; - validate_feature_requirements_in_config_toml( - &user_config_toml, - layers.requirements().feature_requirements.as_ref(), - ) - .map_err(|err| { - ConfigServiceError::write( - ConfigWriteErrorCode::ConfigValidationError, - format!("Invalid configuration: {err}"), - ) - })?; - let updated_layers = layers.with_user_config(&provided_path, user_config.clone()); let effective = updated_layers.effective_config(); validate_config(&effective).map_err(|err| { - ConfigServiceError::write( + ConfigManagerError::write( ConfigWriteErrorCode::ConfigValidationError, format!("Invalid configuration: {err}"), ) })?; if !config_edits.is_empty() { - ConfigEditsBuilder::new(&self.codex_home) + ConfigEditsBuilder::new(self.codex_home()) .with_edits(config_edits) .apply() .await - .map_err(|err| ConfigServiceError::anyhow("failed to persist config.toml", err))?; + .map_err(|err| ConfigManagerError::anyhow("failed to persist config.toml", err))?; } let overridden = first_overridden_edit(&updated_layers, &effective, &parsed_segments); @@ -413,7 +327,7 @@ impl ConfigService { version: updated_layers .get_user_layer() .ok_or_else(|| { - ConfigServiceError::write( + ConfigManagerError::write( ConfigWriteErrorCode::UserLayerNotFound, "user layer not found in updated layers", ) @@ -429,40 +343,29 @@ impl ConfigService { /// include any in-repo .codex/ folders because there is no cwd/project root /// associated with this query. async fn load_thread_agnostic_config(&self) -> std::io::Result { - let cwd: Option = None; - load_config_layers_state( - LOCAL_FS.as_ref(), - &self.codex_home, - cwd, - &self.cli_overrides, - self.loader_overrides.clone(), - self.cloud_requirements.clone(), - &codex_config::NoopThreadConfigLoader, - self.host_name.as_deref(), - ) - .await + self.load_config_layers(/*cwd*/ None).await } } async fn create_empty_user_layer( config_toml: &AbsolutePathBuf, -) -> Result { +) -> Result { let SymlinkWritePaths { read_path, write_path, } = resolve_symlink_write_paths(config_toml.as_path()) - .map_err(|err| ConfigServiceError::io("failed to resolve user config path", err))?; + .map_err(|err| ConfigManagerError::io("failed to resolve user config path", err))?; let toml_value = match read_path { Some(path) => match tokio::fs::read_to_string(&path).await { Ok(contents) => toml::from_str(&contents).map_err(|e| { - ConfigServiceError::toml("failed to parse existing user config.toml", e) + ConfigManagerError::toml("failed to parse existing user config.toml", e) })?, Err(err) if err.kind() == std::io::ErrorKind::NotFound => { write_empty_user_config(write_path.clone()).await?; TomlValue::Table(toml::map::Map::new()) } Err(err) => { - return Err(ConfigServiceError::io( + return Err(ConfigManagerError::io( "failed to read user config.toml", err, )); @@ -481,11 +384,11 @@ async fn create_empty_user_layer( )) } -async fn write_empty_user_config(write_path: PathBuf) -> Result<(), ConfigServiceError> { +async fn write_empty_user_config(write_path: PathBuf) -> Result<(), ConfigManagerError> { task::spawn_blocking(move || write_atomically(&write_path, "")) .await - .map_err(|err| ConfigServiceError::anyhow("config persistence task panicked", err.into()))? - .map_err(|err| ConfigServiceError::io("failed to create empty user config.toml", err)) + .map_err(|err| ConfigManagerError::anyhow("config persistence task panicked", err.into()))? + .map_err(|err| ConfigManagerError::io("failed to create empty user config.toml", err)) } fn parse_value(value: JsonValue) -> Result, String> { @@ -746,5 +649,5 @@ fn find_effective_layer( } #[cfg(test)] -#[path = "service_tests.rs"] +#[path = "config_manager_service_tests.rs"] mod tests; diff --git a/codex-rs/core/src/config/service_tests.rs b/codex-rs/app-server/src/config_manager_service_tests.rs similarity index 95% rename from codex-rs/core/src/config/service_tests.rs rename to codex-rs/app-server/src/config_manager_service_tests.rs index ceb801c456..a871d8e43f 100644 --- a/codex-rs/core/src/config/service_tests.rs +++ b/codex-rs/app-server/src/config_manager_service_tests.rs @@ -4,6 +4,9 @@ use codex_app_server_protocol::AppConfig; use codex_app_server_protocol::AppToolApproval; use codex_app_server_protocol::AppsConfig; use codex_app_server_protocol::AskForApproval; +use codex_core::config_loader::CloudRequirementsLoader; +use codex_core::config_loader::FeatureRequirementsToml; +use codex_core::config_loader::LoaderOverrides; use codex_utils_absolute_path::AbsolutePathBuf; use pretty_assertions::assert_eq; use std::collections::BTreeMap; @@ -74,7 +77,7 @@ unified_exec = true "#; std::fs::write(tmp.path().join(CONFIG_TOML_FILE), original)?; - let service = ConfigService::without_managed_config_for_tests(tmp.path().to_path_buf()); + let service = ConfigManager::without_managed_config_for_tests(tmp.path().to_path_buf()); service .write_value(ConfigValueWriteParams { file_path: Some(tmp.path().join(CONFIG_TOML_FILE).display().to_string()), @@ -108,7 +111,7 @@ async fn write_value_supports_nested_app_paths() -> Result<()> { let tmp = tempdir().expect("tempdir"); std::fs::write(tmp.path().join(CONFIG_TOML_FILE), "")?; - let service = ConfigService::without_managed_config_for_tests(tmp.path().to_path_buf()); + let service = ConfigManager::without_managed_config_for_tests(tmp.path().to_path_buf()); service .write_value(ConfigValueWriteParams { file_path: Some(tmp.path().join(CONFIG_TOML_FILE).display().to_string()), @@ -172,7 +175,7 @@ async fn write_value_supports_custom_mcp_server_default_tool_approval_mode() -> "[mcp_servers.docs]\ncommand = \"docs-server\"\n", )?; - let service = ConfigService::without_managed_config_for_tests(tmp.path().to_path_buf()); + let service = ConfigManager::without_managed_config_for_tests(tmp.path().to_path_buf()); service .write_value(ConfigValueWriteParams { file_path: Some(tmp.path().join(CONFIG_TOML_FILE).display().to_string()), @@ -218,7 +221,7 @@ async fn read_includes_origins_and_layers() { std::fs::write(&managed_path, "approval_policy = \"never\"").unwrap(); let managed_file = AbsolutePathBuf::try_from(managed_path.clone()).expect("managed file"); - let service = ConfigService::new( + let service = ConfigManager::new_for_tests( tmp.path().to_path_buf(), vec![], LoaderOverrides::with_managed_config_path_for_tests(managed_path.clone()), @@ -297,7 +300,7 @@ writable_roots = ["~/code"] ), ); - let service = ConfigService::new( + let service = ConfigManager::new_for_tests( tmp.path().to_path_buf(), vec![], loader_overrides, @@ -338,7 +341,7 @@ async fn write_value_reports_override() { std::fs::write(&managed_path, "approval_policy = \"never\"").unwrap(); let managed_file = AbsolutePathBuf::try_from(managed_path.clone()).expect("managed file"); - let service = ConfigService::new( + let service = ConfigManager::new_for_tests( tmp.path().to_path_buf(), vec![], LoaderOverrides::with_managed_config_path_for_tests(managed_path.clone()), @@ -388,7 +391,7 @@ async fn version_conflict_rejected() { let user_path = tmp.path().join(CONFIG_TOML_FILE); std::fs::write(&user_path, "model = \"user\"").unwrap(); - let service = ConfigService::without_managed_config_for_tests(tmp.path().to_path_buf()); + let service = ConfigManager::without_managed_config_for_tests(tmp.path().to_path_buf()); let error = service .write_value(ConfigValueWriteParams { file_path: Some(tmp.path().join(CONFIG_TOML_FILE).display().to_string()), @@ -411,7 +414,7 @@ async fn write_value_defaults_to_user_config_path() { let tmp = tempdir().expect("tempdir"); std::fs::write(tmp.path().join(CONFIG_TOML_FILE), "").unwrap(); - let service = ConfigService::without_managed_config_for_tests(tmp.path().to_path_buf()); + let service = ConfigManager::without_managed_config_for_tests(tmp.path().to_path_buf()); service .write_value(ConfigValueWriteParams { file_path: None, @@ -438,7 +441,7 @@ async fn invalid_user_value_rejected_even_if_overridden_by_managed() { let managed_path = tmp.path().join("managed_config.toml"); std::fs::write(&managed_path, "approval_policy = \"never\"").unwrap(); - let service = ConfigService::new( + let service = ConfigManager::new_for_tests( tmp.path().to_path_buf(), vec![], LoaderOverrides::with_managed_config_path_for_tests(managed_path.clone()), @@ -471,7 +474,7 @@ async fn reserved_builtin_provider_override_rejected() { let tmp = tempdir().expect("tempdir"); std::fs::write(tmp.path().join(CONFIG_TOML_FILE), "model = \"user\"\n").unwrap(); - let service = ConfigService::without_managed_config_for_tests(tmp.path().to_path_buf()); + let service = ConfigManager::without_managed_config_for_tests(tmp.path().to_path_buf()); let error = service .write_value(ConfigValueWriteParams { file_path: Some(tmp.path().join(CONFIG_TOML_FILE).display().to_string()), @@ -499,13 +502,13 @@ async fn write_value_rejects_feature_requirement_conflict() { let tmp = tempdir().expect("tempdir"); std::fs::write(tmp.path().join(CONFIG_TOML_FILE), "").unwrap(); - let service = ConfigService::new( + let service = ConfigManager::new_for_tests( tmp.path().to_path_buf(), vec![], LoaderOverrides::without_managed_config_for_tests(), CloudRequirementsLoader::new(async { Ok(Some(ConfigRequirementsToml { - feature_requirements: Some(crate::config_loader::FeatureRequirementsToml { + feature_requirements: Some(FeatureRequirementsToml { entries: BTreeMap::from([("personality".to_string(), true)]), }), ..Default::default() @@ -546,13 +549,13 @@ async fn write_value_rejects_profile_feature_requirement_conflict() { let tmp = tempdir().expect("tempdir"); std::fs::write(tmp.path().join(CONFIG_TOML_FILE), "").unwrap(); - let service = ConfigService::new( + let service = ConfigManager::new_for_tests( tmp.path().to_path_buf(), vec![], LoaderOverrides::without_managed_config_for_tests(), CloudRequirementsLoader::new(async { Ok(Some(ConfigRequirementsToml { - feature_requirements: Some(crate::config_loader::FeatureRequirementsToml { + feature_requirements: Some(FeatureRequirementsToml { entries: BTreeMap::from([("personality".to_string(), true)]), }), ..Default::default() @@ -604,7 +607,7 @@ async fn read_reports_managed_overrides_user_and_session_flags() { TomlValue::String("session".to_string()), )]; - let service = ConfigService::new( + let service = ConfigManager::new_for_tests( tmp.path().to_path_buf(), cli_overrides, LoaderOverrides::with_managed_config_path_for_tests(managed_path.clone()), @@ -658,7 +661,7 @@ async fn write_value_reports_managed_override() { std::fs::write(&managed_path, "approval_policy = \"never\"").unwrap(); let managed_file = AbsolutePathBuf::try_from(managed_path.clone()).expect("managed file"); - let service = ConfigService::new( + let service = ConfigManager::new_for_tests( tmp.path().to_path_buf(), vec![], LoaderOverrides::with_managed_config_path_for_tests(managed_path.clone()), @@ -714,7 +717,7 @@ alpha = "a" std::fs::write(&path, base)?; - let service = ConfigService::without_managed_config_for_tests(tmp.path().to_path_buf()); + let service = ConfigManager::without_managed_config_for_tests(tmp.path().to_path_buf()); service .write_value(ConfigValueWriteParams { file_path: Some(path.display().to_string()), diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index 0eac54df2a..d4573b267a 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -72,6 +72,8 @@ mod codex_message_processor; mod command_exec; mod config; mod config_api; +mod config_manager; +mod config_manager_service; mod dynamic_tools; mod error_code; mod external_agent_config_api; diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index 2032eb61cd..53d9f2df4c 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -10,6 +10,7 @@ use std::sync::atomic::Ordering; use crate::codex_message_processor::CodexMessageProcessor; use crate::codex_message_processor::CodexMessageProcessorArgs; use crate::config_api::ConfigApi; +use crate::config_manager::ConfigManager; use crate::error_code::INVALID_REQUEST_ERROR_CODE; use crate::external_agent_config_api::ExternalAgentConfigApi; use crate::fs_api::FsApi; @@ -294,6 +295,15 @@ impl MessageProcessor { let cli_overrides = Arc::new(RwLock::new(cli_overrides)); let runtime_feature_enablement = Arc::new(RwLock::new(BTreeMap::new())); let cloud_requirements = Arc::new(RwLock::new(cloud_requirements)); + let config_manager = ConfigManager::new( + config.codex_home.to_path_buf(), + cli_overrides, + runtime_feature_enablement, + loader_overrides, + cloud_requirements, + arg0_paths.clone(), + thread_config_loader, + ); let codex_message_processor = CodexMessageProcessor::new(CodexMessageProcessorArgs { auth_manager: auth_manager.clone(), thread_manager: Arc::clone(&thread_manager), @@ -301,10 +311,7 @@ impl MessageProcessor { analytics_events_client: analytics_events_client.clone(), arg0_paths, config: Arc::clone(&config), - cli_overrides: cli_overrides.clone(), - runtime_feature_enablement: runtime_feature_enablement.clone(), - cloud_requirements: cloud_requirements.clone(), - thread_config_loader, + config_manager: config_manager.clone(), feedback, log_db, }); @@ -314,11 +321,7 @@ impl MessageProcessor { .plugins_manager() .maybe_start_plugin_startup_tasks_for_config(&config, auth_manager.clone()); let config_api = ConfigApi::new( - config.codex_home.to_path_buf(), - cli_overrides, - runtime_feature_enablement, - loader_overrides, - cloud_requirements, + config_manager, thread_manager.clone(), analytics_events_client.clone(), ); diff --git a/codex-rs/app-server/tests/suite/v2/experimental_feature_list.rs b/codex-rs/app-server/tests/suite/v2/experimental_feature_list.rs index 07bdd0bde1..3f7e6989f8 100644 --- a/codex-rs/app-server/tests/suite/v2/experimental_feature_list.rs +++ b/codex-rs/app-server/tests/suite/v2/experimental_feature_list.rs @@ -15,6 +15,7 @@ use codex_app_server_protocol::JSONRPCError; use codex_app_server_protocol::JSONRPCResponse; use codex_app_server_protocol::RequestId; use codex_core::config::ConfigBuilder; +use codex_core::config_loader::LoaderOverrides; use codex_features::FEATURES; use codex_features::Stage; use pretty_assertions::assert_eq; @@ -32,6 +33,9 @@ async fn experimental_feature_list_returns_feature_metadata_with_stage() -> Resu let config = ConfigBuilder::default() .codex_home(codex_home.path().to_path_buf()) .fallback_cwd(Some(codex_home.path().to_path_buf())) + .loader_overrides(LoaderOverrides::with_managed_config_path_for_tests( + codex_home.path().join("managed_config.toml"), + )) .build() .await?; let mut mcp = McpProcess::new(codex_home.path()).await?; diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index a6240fdcb3..7ae710f83c 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -7,6 +7,7 @@ use crate::config_loader::ConfigLayerStackOrdering; use crate::config_loader::ConfigRequirements; use crate::config_loader::ConfigRequirementsToml; use crate::config_loader::ConstrainedWithSource; +use crate::config_loader::FeatureRequirementsToml; use crate::config_loader::LoaderOverrides; use crate::config_loader::McpServerIdentity; use crate::config_loader::McpServerRequirement; @@ -108,7 +109,6 @@ mod network_proxy_spec; mod permissions; #[cfg(test)] mod schema; -pub(crate) mod service; pub use codex_config::Constrained; pub use codex_config::ConstraintError; pub use codex_config::ConstraintResult; @@ -118,8 +118,6 @@ pub use managed_features::ManagedFeatures; pub use network_proxy_spec::NetworkProxySpec; pub use network_proxy_spec::StartedNetworkProxy; pub(crate) use permissions::resolve_permission_profile; -pub use service::ConfigService; -pub use service::ConfigServiceError; pub use codex_git_utils::GhostSnapshotConfig; @@ -947,7 +945,7 @@ pub async fn load_config_as_toml_with_cli_and_loader_overrides( Ok(cfg) } -pub(crate) fn deserialize_config_toml_with_base( +pub fn deserialize_config_toml_with_base( root_value: TomlValue, config_base_dir: &Path, ) -> std::io::Result { @@ -959,6 +957,15 @@ pub(crate) fn deserialize_config_toml_with_base( .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e)) } +/// Validate user-visible feature settings against managed feature requirements. +pub fn validate_feature_requirements_for_config_toml( + cfg: &ConfigToml, + feature_requirements: Option<&Sourced>, +) -> std::io::Result<()> { + managed_features::validate_explicit_feature_settings_in_config_toml(cfg, feature_requirements)?; + managed_features::validate_feature_requirements_in_config_toml(cfg, feature_requirements) +} + fn load_catalog_json(path: &AbsolutePathBuf) -> std::io::Result { let file_contents = std::fs::read_to_string(path)?; let catalog = serde_json::from_str::(&file_contents).map_err(|err| { diff --git a/codex-rs/core/src/plugins/manager.rs b/codex-rs/core/src/plugins/manager.rs index 3698b4b191..efa91edc7c 100644 --- a/codex-rs/core/src/plugins/manager.rs +++ b/codex-rs/core/src/plugins/manager.rs @@ -6,14 +6,10 @@ use super::startup_sync::start_startup_remote_plugin_sync_once; use super::sync_openai_plugins_repo; use crate::SkillMetadata; use crate::config::Config; -use crate::config::ConfigService; -use crate::config::ConfigServiceError; use crate::config::edit::ConfigEdit; use crate::config::edit::ConfigEditsBuilder; use crate::config_loader::ConfigLayerStack; use codex_analytics::AnalyticsEventsClient; -use codex_app_server_protocol::ConfigValueWriteParams; -use codex_app_server_protocol::MergeStrategy; use codex_config::types::PluginConfig; use codex_core_plugins::loader::configured_curated_plugin_ids_from_codex_home; use codex_core_plugins::loader::installed_plugin_telemetry_metadata; @@ -61,7 +57,6 @@ use codex_plugin::PluginIdError; use codex_plugin::prompt_safe_plugin_description; use codex_protocol::protocol::Product; use codex_utils_absolute_path::AbsolutePathBuf; -use serde_json::json; use std::collections::HashMap; use std::collections::HashSet; use std::path::PathBuf; @@ -609,18 +604,17 @@ impl PluginsManager { .await .map_err(PluginInstallError::join)??; - ConfigService::new_with_defaults(self.codex_home.clone()) - .write_value(ConfigValueWriteParams { - key_path: format!("plugins.{}", result.plugin_id.as_key()), - value: json!({ - "enabled": true, - }), - merge_strategy: MergeStrategy::Replace, - file_path: None, - expected_version: None, - }) + ConfigEditsBuilder::new(&self.codex_home) + .with_edits([ConfigEdit::SetPath { + segments: vec![ + "plugins".to_string(), + result.plugin_id.as_key(), + "enabled".to_string(), + ], + value: value(true), + }]) + .apply() .await - .map(|_| ()) .map_err(PluginInstallError::from)?; let analytics_events_client = match self.analytics_events_client.read() { @@ -1538,7 +1532,7 @@ pub enum PluginInstallError { Store(#[from] PluginStoreError), #[error("{0}")] - Config(#[from] ConfigServiceError), + Config(#[from] anyhow::Error), #[error("failed to join plugin install task: {0}")] Join(#[from] tokio::task::JoinError), From 2a226096f60450bb9978d02e030ce29283f00c55 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Tue, 21 Apr 2026 10:22:36 -0700 Subject: [PATCH 12/16] Split DeveloperInstructions into individual fragments. (#18813) Split DeveloperInstructions into individual fragments. --- .codex/skills/code-review-context/SKILL.md | 1 + codex-rs/core-skills/src/lib.rs | 4 +- codex-rs/core-skills/src/render.rs | 63 +- codex-rs/core/src/apps/mod.rs | 3 +- codex-rs/core/src/apps/render.rs | 17 +- .../context/approved_command_prefix_saved.rs | 24 + .../core/src/context/apps_instructions.rs | 30 + .../context/available_plugins_instructions.rs | 58 ++ .../context/available_skills_instructions.rs | 56 ++ .../collaboration_mode_instructions.rs | 32 + .../core/src/context/environment_context.rs | 2 +- codex-rs/core/src/context/fragment.rs | 30 +- .../guardian_followup_review_reminder.rs | 21 + .../src/context/hook_additional_context.rs | 22 + .../context/image_generation_instructions.rs | 30 + codex-rs/core/src/context/mod.rs | 38 +- .../src/context/model_switch_instructions.rs | 27 + .../core/src/context/network_rule_saved.rs | 35 + .../src/context/permissions_instructions.rs | 316 +++++++ .../context/permissions_instructions_tests.rs | 412 +++++++++ .../context/personality_spec_instructions.rs | 25 + .../core/src/context/plugin_instructions.rs | 22 + .../permissions/approval_policy/never.md | 0 .../permissions/approval_policy/on_failure.md | 0 .../permissions/approval_policy/on_request.md | 0 .../on_request_rule_request_permission.md | 0 .../approval_policy/unless_trusted.md | 0 .../sandbox_mode/danger_full_access.md | 0 .../permissions/sandbox_mode/read_only.md | 0 .../sandbox_mode/workspace_write.md | 0 .../context}/prompts/realtime/realtime_end.md | 0 .../prompts/realtime/realtime_start.md | 0 .../src/context/realtime_end_instructions.rs | 32 + .../context/realtime_start_instructions.rs | 18 + .../realtime_start_with_instructions.rs | 26 + .../core/src/context/skill_instructions.rs | 2 +- .../src/context/spawn_agent_instructions.rs | 14 + .../core/src/context/subagent_notification.rs | 2 +- codex-rs/core/src/context/turn_aborted.rs | 2 +- .../core/src/context/user_instructions.rs | 2 +- .../core/src/context/user_shell_command.rs | 2 +- codex-rs/core/src/context_manager/updates.rs | 63 +- codex-rs/core/src/guardian/review_session.rs | 14 +- codex-rs/core/src/hook_runtime.rs | 6 +- codex-rs/core/src/lib.rs | 4 +- codex-rs/core/src/plugins/injection.rs | 7 +- codex-rs/core/src/plugins/mod.rs | 1 - codex-rs/core/src/plugins/render.rs | 42 +- codex-rs/core/src/session/mod.rs | 69 +- codex-rs/core/src/session/tests.rs | 19 +- codex-rs/core/src/skills.rs | 2 +- codex-rs/core/src/stream_events_utils.rs | 14 +- .../tools/handlers/multi_agents_v2/spawn.rs | 22 +- .../core/tests/suite/permissions_messages.rs | 15 +- codex-rs/protocol/src/models.rs | 822 ------------------ 55 files changed, 1410 insertions(+), 1058 deletions(-) create mode 100644 codex-rs/core/src/context/approved_command_prefix_saved.rs create mode 100644 codex-rs/core/src/context/apps_instructions.rs create mode 100644 codex-rs/core/src/context/available_plugins_instructions.rs create mode 100644 codex-rs/core/src/context/available_skills_instructions.rs create mode 100644 codex-rs/core/src/context/collaboration_mode_instructions.rs create mode 100644 codex-rs/core/src/context/guardian_followup_review_reminder.rs create mode 100644 codex-rs/core/src/context/hook_additional_context.rs create mode 100644 codex-rs/core/src/context/image_generation_instructions.rs create mode 100644 codex-rs/core/src/context/model_switch_instructions.rs create mode 100644 codex-rs/core/src/context/network_rule_saved.rs create mode 100644 codex-rs/core/src/context/permissions_instructions.rs create mode 100644 codex-rs/core/src/context/permissions_instructions_tests.rs create mode 100644 codex-rs/core/src/context/personality_spec_instructions.rs create mode 100644 codex-rs/core/src/context/plugin_instructions.rs rename codex-rs/{protocol/src => core/src/context}/prompts/permissions/approval_policy/never.md (100%) rename codex-rs/{protocol/src => core/src/context}/prompts/permissions/approval_policy/on_failure.md (100%) rename codex-rs/{protocol/src => core/src/context}/prompts/permissions/approval_policy/on_request.md (100%) rename codex-rs/{protocol/src => core/src/context}/prompts/permissions/approval_policy/on_request_rule_request_permission.md (100%) rename codex-rs/{protocol/src => core/src/context}/prompts/permissions/approval_policy/unless_trusted.md (100%) rename codex-rs/{protocol/src => core/src/context}/prompts/permissions/sandbox_mode/danger_full_access.md (100%) rename codex-rs/{protocol/src => core/src/context}/prompts/permissions/sandbox_mode/read_only.md (100%) rename codex-rs/{protocol/src => core/src/context}/prompts/permissions/sandbox_mode/workspace_write.md (100%) rename codex-rs/{protocol/src => core/src/context}/prompts/realtime/realtime_end.md (100%) rename codex-rs/{protocol/src => core/src/context}/prompts/realtime/realtime_start.md (100%) create mode 100644 codex-rs/core/src/context/realtime_end_instructions.rs create mode 100644 codex-rs/core/src/context/realtime_start_instructions.rs create mode 100644 codex-rs/core/src/context/realtime_start_with_instructions.rs create mode 100644 codex-rs/core/src/context/spawn_agent_instructions.rs diff --git a/.codex/skills/code-review-context/SKILL.md b/.codex/skills/code-review-context/SKILL.md index 3d33192ec5..7faf3d7cd2 100644 --- a/.codex/skills/code-review-context/SKILL.md +++ b/.codex/skills/code-review-context/SKILL.md @@ -10,3 +10,4 @@ Codex maintains a context (history of messages) that is sent to the model in inf 3. No unbounded items - everything injected in the model context must have a bounded size and a hard cap. 4. No items larger than 10K tokens. 5. Highlight new individual items that can cross >1k tokens as P0. These need an additional manual review. +6. All injected fragments must be defined as structs in `core/context` and implement ContextualUserFragment trait \ No newline at end of file diff --git a/codex-rs/core-skills/src/lib.rs b/codex-rs/core-skills/src/lib.rs index 6d3ac7a4fe..06ced0d5d4 100644 --- a/codex-rs/core-skills/src/lib.rs +++ b/codex-rs/core-skills/src/lib.rs @@ -22,8 +22,8 @@ pub use model::SkillLoadOutcome; pub use model::SkillMetadata; pub use model::SkillPolicy; pub use model::filter_skill_load_outcome_for_product; -pub use render::RenderedSkillsSection; +pub use render::AvailableSkills; pub use render::SkillMetadataBudget; pub use render::SkillRenderReport; +pub use render::build_available_skills; pub use render::default_skill_metadata_budget; -pub use render::render_skills_section; diff --git a/codex-rs/core-skills/src/render.rs b/codex-rs/core-skills/src/render.rs index ad674fbffa..6bcd5e4e05 100644 --- a/codex-rs/core-skills/src/render.rs +++ b/codex-rs/core-skills/src/render.rs @@ -3,8 +3,6 @@ use codex_otel::SessionTelemetry; use codex_otel::THREAD_SKILLS_ENABLED_TOTAL_METRIC; use codex_otel::THREAD_SKILLS_KEPT_TOTAL_METRIC; use codex_otel::THREAD_SKILLS_TRUNCATED_METRIC; -use codex_protocol::protocol::SKILLS_INSTRUCTIONS_CLOSE_TAG; -use codex_protocol::protocol::SKILLS_INSTRUCTIONS_OPEN_TAG; use codex_protocol::protocol::SkillScope; use codex_utils_output_truncation::approx_token_count; @@ -48,8 +46,8 @@ pub enum SkillRenderSideEffects<'a> { } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct RenderedSkillsSection { - pub text: String, +pub struct AvailableSkills { + pub skill_lines: Vec, pub report: SkillRenderReport, pub emit_warning: bool, } @@ -71,11 +69,11 @@ pub fn default_skill_metadata_budget(context_window: Option) -> SkillMetada )) } -pub fn render_skills_section( +pub fn build_available_skills( skills: &[SkillMetadata], budget: SkillMetadataBudget, side_effects: SkillRenderSideEffects<'_>, -) -> Option { +) -> Option { if skills.is_empty() { let _ = record_skill_render_side_effects( side_effects, @@ -93,39 +91,8 @@ pub fn render_skills_section( report.included_count, report.omitted_count > 0, ); - let mut lines: Vec = Vec::new(); - lines.push("## Skills".to_string()); - lines.push("A skill is a set of local instructions to follow that is stored in a `SKILL.md` file. Below is the list of skills that can be used. Each entry includes a name, description, and file path so you can open the source for full instructions when using a specific skill.".to_string()); - lines.push("### Available skills".to_string()); - if !skill_lines.is_empty() { - lines.extend(skill_lines); - } - - lines.push("### How to use skills".to_string()); - lines.push( - r###"- Discovery: The list above is the skills available in this session (name + description + file path). Skill bodies live on disk at the listed paths. -- Trigger rules: If the user names a skill (with `$SkillName` or plain text) OR the task clearly matches a skill's description shown above, you must use that skill for that turn. Multiple mentions mean use them all. Do not carry skills across turns unless re-mentioned. -- Missing/blocked: If a named skill isn't in the list or the path can't be read, say so briefly and continue with the best fallback. -- How to use a skill (progressive disclosure): - 1) After deciding to use a skill, open its `SKILL.md`. Read only enough to follow the workflow. - 2) When `SKILL.md` references relative paths (e.g., `scripts/foo.py`), resolve them relative to the skill directory listed above first, and only consider other paths if needed. - 3) If `SKILL.md` points to extra folders such as `references/`, load only the specific files needed for the request; don't bulk-load everything. - 4) If `scripts/` exist, prefer running or patching them instead of retyping large code blocks. - 5) If `assets/` or templates exist, reuse them instead of recreating from scratch. -- Coordination and sequencing: - - If multiple skills apply, choose the minimal set that covers the request and state the order you'll use them. - - Announce which skill(s) you're using and why (one short line). If you skip an obvious skill, say why. -- Context hygiene: - - Keep context small: summarize long sections instead of pasting them; only load extra files when needed. - - Avoid deep reference-chasing: prefer opening only files directly linked from `SKILL.md` unless you're blocked. - - When variants exist (frameworks, providers, domains), pick only the relevant reference file(s) and note that choice. -- Safety and fallback: If a skill can't be applied cleanly (missing files, unclear instructions), state the issue, pick the next-best approach, and continue."### - .to_string(), - ); - - let body = lines.join("\n"); - Some(RenderedSkillsSection { - text: format!("{SKILLS_INSTRUCTIONS_OPEN_TAG}\n{body}\n{SKILLS_INSTRUCTIONS_CLOSE_TAG}"), + Some(AvailableSkills { + skill_lines, report, emit_warning, }) @@ -274,7 +241,7 @@ mod tests { .cost(&format!("{}\n", render_skill_line(&admin))); let budget = SkillMetadataBudget::Characters(system_cost + admin_cost); - let rendered = render_skills_section( + let rendered = build_available_skills( &[system, user, repo, admin], budget, SkillRenderSideEffects::None, @@ -284,10 +251,11 @@ mod tests { assert_eq!(rendered.report.included_count, 2); assert_eq!(rendered.report.omitted_count, 2); assert!(!rendered.emit_warning); - assert!(rendered.text.contains("- system-skill:")); - assert!(rendered.text.contains("- admin-skill:")); - assert!(!rendered.text.contains("- repo-skill:")); - assert!(!rendered.text.contains("- user-skill:")); + let rendered_text = rendered.skill_lines.join("\n"); + assert!(rendered_text.contains("- system-skill:")); + assert!(rendered_text.contains("- admin-skill:")); + assert!(!rendered_text.contains("- repo-skill:")); + assert!(!rendered_text.contains("- user-skill:")); } #[test] @@ -300,13 +268,14 @@ mod tests { let budget = SkillMetadataBudget::Characters(repo_cost); let rendered = - render_skills_section(&[oversized, repo], budget, SkillRenderSideEffects::None) + build_available_skills(&[oversized, repo], budget, SkillRenderSideEffects::None) .expect("skills render"); assert_eq!(rendered.report.included_count, 1); assert_eq!(rendered.report.omitted_count, 1); assert!(!rendered.emit_warning); - assert!(!rendered.text.contains("- oversized-system-skill:")); - assert!(rendered.text.contains("- repo-skill:")); + let rendered_text = rendered.skill_lines.join("\n"); + assert!(!rendered_text.contains("- oversized-system-skill:")); + assert!(rendered_text.contains("- repo-skill:")); } } diff --git a/codex-rs/core/src/apps/mod.rs b/codex-rs/core/src/apps/mod.rs index dcb5c4c20a..5a58d22204 100644 --- a/codex-rs/core/src/apps/mod.rs +++ b/codex-rs/core/src/apps/mod.rs @@ -1,3 +1,2 @@ +#[cfg(test)] mod render; - -pub(crate) use render::render_apps_section; diff --git a/codex-rs/core/src/apps/render.rs b/codex-rs/core/src/apps/render.rs index fe23a09f8d..3793231e10 100644 --- a/codex-rs/core/src/apps/render.rs +++ b/codex-rs/core/src/apps/render.rs @@ -1,22 +1,11 @@ +use crate::context::AppsInstructions; +use crate::context::ContextualUserFragment; use codex_app_server_protocol::AppInfo; -use codex_mcp::CODEX_APPS_MCP_SERVER_NAME; use codex_protocol::protocol::APPS_INSTRUCTIONS_CLOSE_TAG; use codex_protocol::protocol::APPS_INSTRUCTIONS_OPEN_TAG; pub(crate) fn render_apps_section(connectors: &[AppInfo]) -> Option { - if !connectors - .iter() - .any(|connector| connector.is_accessible && connector.is_enabled) - { - return None; - } - - let body = format!( - "## Apps (Connectors)\nApps (Connectors) can be explicitly triggered in user messages in the format `[$app-name](app://{{connector_id}})`. Apps can also be implicitly triggered as long as the context suggests usage of available apps.\nAn app is equivalent to a set of MCP tools within the `{CODEX_APPS_MCP_SERVER_NAME}` MCP.\nAn installed app's MCP tools are either provided to you already, or can be lazy-loaded through the `tool_search` tool. If `tool_search` is available, the apps that are searchable by `tools_search` will be listed by it.\nDo not additionally call list_mcp_resources or list_mcp_resource_templates for apps." - ); - Some(format!( - "{APPS_INSTRUCTIONS_OPEN_TAG}\n{body}\n{APPS_INSTRUCTIONS_CLOSE_TAG}" - )) + AppsInstructions::from_connectors(connectors).map(|instructions| instructions.render()) } #[cfg(test)] diff --git a/codex-rs/core/src/context/approved_command_prefix_saved.rs b/codex-rs/core/src/context/approved_command_prefix_saved.rs new file mode 100644 index 0000000000..3aac17712a --- /dev/null +++ b/codex-rs/core/src/context/approved_command_prefix_saved.rs @@ -0,0 +1,24 @@ +use super::ContextualUserFragment; + +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct ApprovedCommandPrefixSaved { + prefixes: String, +} + +impl ApprovedCommandPrefixSaved { + pub(crate) fn new(prefixes: impl Into) -> Self { + Self { + prefixes: prefixes.into(), + } + } +} + +impl ContextualUserFragment for ApprovedCommandPrefixSaved { + const ROLE: &'static str = "developer"; + const START_MARKER: &'static str = ""; + const END_MARKER: &'static str = ""; + + fn body(&self) -> String { + format!("Approved command prefix saved:\n{}", self.prefixes) + } +} diff --git a/codex-rs/core/src/context/apps_instructions.rs b/codex-rs/core/src/context/apps_instructions.rs new file mode 100644 index 0000000000..55865b429a --- /dev/null +++ b/codex-rs/core/src/context/apps_instructions.rs @@ -0,0 +1,30 @@ +use codex_app_server_protocol::AppInfo; +use codex_mcp::CODEX_APPS_MCP_SERVER_NAME; +use codex_protocol::protocol::APPS_INSTRUCTIONS_CLOSE_TAG; +use codex_protocol::protocol::APPS_INSTRUCTIONS_OPEN_TAG; + +use super::ContextualUserFragment; + +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct AppsInstructions; + +impl AppsInstructions { + pub(crate) fn from_connectors(connectors: &[AppInfo]) -> Option { + connectors + .iter() + .any(|connector| connector.is_accessible && connector.is_enabled) + .then_some(Self) + } +} + +impl ContextualUserFragment for AppsInstructions { + const ROLE: &'static str = "developer"; + const START_MARKER: &'static str = APPS_INSTRUCTIONS_OPEN_TAG; + const END_MARKER: &'static str = APPS_INSTRUCTIONS_CLOSE_TAG; + + fn body(&self) -> String { + format!( + "\n## Apps (Connectors)\nApps (Connectors) can be explicitly triggered in user messages in the format `[$app-name](app://{{connector_id}})`. Apps can also be implicitly triggered as long as the context suggests usage of available apps.\nAn app is equivalent to a set of MCP tools within the `{CODEX_APPS_MCP_SERVER_NAME}` MCP.\nAn installed app's MCP tools are either provided to you already, or can be lazy-loaded through the `tool_search` tool. If `tool_search` is available, the apps that are searchable by `tools_search` will be listed by it.\nDo not additionally call list_mcp_resources or list_mcp_resource_templates for apps.\n" + ) + } +} diff --git a/codex-rs/core/src/context/available_plugins_instructions.rs b/codex-rs/core/src/context/available_plugins_instructions.rs new file mode 100644 index 0000000000..52f9c8df48 --- /dev/null +++ b/codex-rs/core/src/context/available_plugins_instructions.rs @@ -0,0 +1,58 @@ +use codex_plugin::PluginCapabilitySummary; +use codex_protocol::protocol::PLUGINS_INSTRUCTIONS_CLOSE_TAG; +use codex_protocol::protocol::PLUGINS_INSTRUCTIONS_OPEN_TAG; + +use super::ContextualUserFragment; + +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct AvailablePluginsInstructions { + plugins: Vec, +} + +impl AvailablePluginsInstructions { + pub(crate) fn from_plugins(plugins: &[PluginCapabilitySummary]) -> Option { + if plugins.is_empty() { + return None; + } + + Some(Self { + plugins: plugins.to_vec(), + }) + } +} + +impl ContextualUserFragment for AvailablePluginsInstructions { + const ROLE: &'static str = "developer"; + const START_MARKER: &'static str = PLUGINS_INSTRUCTIONS_OPEN_TAG; + const END_MARKER: &'static str = PLUGINS_INSTRUCTIONS_CLOSE_TAG; + + fn body(&self) -> String { + let mut lines = vec![ + "## Plugins".to_string(), + "A plugin is a local bundle of skills, MCP servers, and apps. Below is the list of plugins that are enabled and available in this session.".to_string(), + "### Available plugins".to_string(), + ]; + + lines.extend( + self.plugins + .iter() + .map(|plugin| match plugin.description.as_deref() { + Some(description) => format!("- `{}`: {description}", plugin.display_name), + None => format!("- `{}`", plugin.display_name), + }), + ); + + lines.push("### How to use plugins".to_string()); + lines.push( + r###"- Discovery: The list above is the plugins available in this session. +- Skill naming: If a plugin contributes skills, those skill entries are prefixed with `plugin_name:` in the Skills list. +- Trigger rules: If the user explicitly names a plugin, prefer capabilities associated with that plugin for that turn. +- Relationship to capabilities: Plugins are not invoked directly. Use their underlying skills, MCP tools, and app tools to help solve the task. +- Preference: When a relevant plugin is available, prefer using capabilities associated with that plugin over standalone capabilities that provide similar functionality. +- Missing/blocked: If the user requests a plugin that is not listed above, or the plugin does not have relevant callable capabilities for the task, say so briefly and continue with the best fallback."### + .to_string(), + ); + + format!("\n{}\n", lines.join("\n")) + } +} diff --git a/codex-rs/core/src/context/available_skills_instructions.rs b/codex-rs/core/src/context/available_skills_instructions.rs new file mode 100644 index 0000000000..aba4b20135 --- /dev/null +++ b/codex-rs/core/src/context/available_skills_instructions.rs @@ -0,0 +1,56 @@ +use codex_core_skills::AvailableSkills; +use codex_protocol::protocol::SKILLS_INSTRUCTIONS_CLOSE_TAG; +use codex_protocol::protocol::SKILLS_INSTRUCTIONS_OPEN_TAG; + +use super::ContextualUserFragment; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct AvailableSkillsInstructions { + skill_lines: Vec, +} + +impl From for AvailableSkillsInstructions { + fn from(available_skills: AvailableSkills) -> Self { + Self { + skill_lines: available_skills.skill_lines, + } + } +} + +impl ContextualUserFragment for AvailableSkillsInstructions { + const ROLE: &'static str = "developer"; + const START_MARKER: &'static str = SKILLS_INSTRUCTIONS_OPEN_TAG; + const END_MARKER: &'static str = SKILLS_INSTRUCTIONS_CLOSE_TAG; + + fn body(&self) -> String { + let mut lines: Vec = Vec::new(); + lines.push("## Skills".to_string()); + lines.push("A skill is a set of local instructions to follow that is stored in a `SKILL.md` file. Below is the list of skills that can be used. Each entry includes a name, description, and file path so you can open the source for full instructions when using a specific skill.".to_string()); + lines.push("### Available skills".to_string()); + lines.extend(self.skill_lines.iter().cloned()); + + lines.push("### How to use skills".to_string()); + lines.push( + r###"- Discovery: The list above is the skills available in this session (name + description + file path). Skill bodies live on disk at the listed paths. +- Trigger rules: If the user names a skill (with `$SkillName` or plain text) OR the task clearly matches a skill's description shown above, you must use that skill for that turn. Multiple mentions mean use them all. Do not carry skills across turns unless re-mentioned. +- Missing/blocked: If a named skill isn't in the list or the path can't be read, say so briefly and continue with the best fallback. +- How to use a skill (progressive disclosure): + 1) After deciding to use a skill, open its `SKILL.md`. Read only enough to follow the workflow. + 2) When `SKILL.md` references relative paths (e.g., `scripts/foo.py`), resolve them relative to the skill directory listed above first, and only consider other paths if needed. + 3) If `SKILL.md` points to extra folders such as `references/`, load only the specific files needed for the request; don't bulk-load everything. + 4) If `scripts/` exist, prefer running or patching them instead of retyping large code blocks. + 5) If `assets/` or templates exist, reuse them instead of recreating from scratch. +- Coordination and sequencing: + - If multiple skills apply, choose the minimal set that covers the request and state the order you'll use them. + - Announce which skill(s) you're using and why (one short line). If you skip an obvious skill, say why. +- Context hygiene: + - Keep context small: summarize long sections instead of pasting them; only load extra files when needed. + - Avoid deep reference-chasing: prefer opening only files directly linked from `SKILL.md` unless you're blocked. + - When variants exist (frameworks, providers, domains), pick only the relevant reference file(s) and note that choice. +- Safety and fallback: If a skill can't be applied cleanly (missing files, unclear instructions), state the issue, pick the next-best approach, and continue."### + .to_string(), + ); + + format!("\n{}\n", lines.join("\n")) + } +} diff --git a/codex-rs/core/src/context/collaboration_mode_instructions.rs b/codex-rs/core/src/context/collaboration_mode_instructions.rs new file mode 100644 index 0000000000..dcf3bac1de --- /dev/null +++ b/codex-rs/core/src/context/collaboration_mode_instructions.rs @@ -0,0 +1,32 @@ +use super::ContextualUserFragment; +use codex_protocol::config_types::CollaborationMode; +use codex_protocol::protocol::COLLABORATION_MODE_CLOSE_TAG; +use codex_protocol::protocol::COLLABORATION_MODE_OPEN_TAG; + +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct CollaborationModeInstructions { + instructions: String, +} + +impl CollaborationModeInstructions { + pub(crate) fn from_collaboration_mode(collaboration_mode: &CollaborationMode) -> Option { + collaboration_mode + .settings + .developer_instructions + .as_ref() + .filter(|instructions| !instructions.is_empty()) + .map(|instructions| Self { + instructions: instructions.clone(), + }) + } +} + +impl ContextualUserFragment for CollaborationModeInstructions { + const ROLE: &'static str = "developer"; + const START_MARKER: &'static str = COLLABORATION_MODE_OPEN_TAG; + const END_MARKER: &'static str = COLLABORATION_MODE_CLOSE_TAG; + + fn body(&self) -> String { + self.instructions.clone() + } +} diff --git a/codex-rs/core/src/context/environment_context.rs b/codex-rs/core/src/context/environment_context.rs index c676bfb9d9..c4e77624f8 100644 --- a/codex-rs/core/src/context/environment_context.rs +++ b/codex-rs/core/src/context/environment_context.rs @@ -200,7 +200,7 @@ impl ContextualUserFragment for EnvironmentContext { lines.extend(subagents.lines().map(|line| format!(" {line}"))); lines.push(" ".to_string()); } - format!("\n{}", lines.join("\n")) + format!("\n{}\n", lines.join("\n")) } } diff --git a/codex-rs/core/src/context/fragment.rs b/codex-rs/core/src/context/fragment.rs index e0e6d03303..34f4a7c367 100644 --- a/codex-rs/core/src/context/fragment.rs +++ b/codex-rs/core/src/context/fragment.rs @@ -28,13 +28,16 @@ impl FragmentRegistration for FragmentRegistrationPro } } -/// Context payload that is injected as a user-authored message fragment. +/// Context payload that is injected as a message fragment. /// -/// Implementations own the response role, start/end markers used to recognize -/// the fragment, and provide the fragment body appended directly after the -/// start marker. The default helpers wrap that body and convert it into the -/// response item shape expected by model input assembly. -pub(crate) trait ContextualUserFragment { +/// Implementations own the response role and provide the exact fragment body. +/// Marked fragments also provide start/end markers used to recognize injected +/// context later. `render()` concatenates markers and body without adding +/// separators, so implementations should include any whitespace they need +/// between tags in `body()`. Unmarked fragments should leave both markers empty, +/// in which case the default helpers render only the body and never match +/// arbitrary text. +pub trait ContextualUserFragment { const ROLE: &'static str; const START_MARKER: &'static str; const END_MARKER: &'static str; @@ -45,6 +48,10 @@ pub(crate) trait ContextualUserFragment { where Self: Sized, { + if Self::START_MARKER.is_empty() || Self::END_MARKER.is_empty() { + return false; + } + let trimmed = text.trim_start(); let starts_with_marker = trimmed .get(..Self::START_MARKER.len()) @@ -57,12 +64,11 @@ pub(crate) trait ContextualUserFragment { } fn render(&self) -> String { - format!( - "{}{}\n{}", - Self::START_MARKER, - self.body(), - Self::END_MARKER - ) + if Self::START_MARKER.is_empty() && Self::END_MARKER.is_empty() { + return self.body(); + } + + format!("{}{}{}", Self::START_MARKER, self.body(), Self::END_MARKER) } fn into(self) -> ResponseItem diff --git a/codex-rs/core/src/context/guardian_followup_review_reminder.rs b/codex-rs/core/src/context/guardian_followup_review_reminder.rs new file mode 100644 index 0000000000..972355ba95 --- /dev/null +++ b/codex-rs/core/src/context/guardian_followup_review_reminder.rs @@ -0,0 +1,21 @@ +use super::ContextualUserFragment; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct GuardianFollowupReviewReminder; + +impl ContextualUserFragment for GuardianFollowupReviewReminder { + const ROLE: &'static str = "developer"; + const START_MARKER: &'static str = ""; + const END_MARKER: &'static str = ""; + + fn body(&self) -> String { + concat!( + "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, set outcome to \"allow\" unless the policy explicitly disallows user ", + "overwrites in such cases." + ) + .to_string() + } +} diff --git a/codex-rs/core/src/context/hook_additional_context.rs b/codex-rs/core/src/context/hook_additional_context.rs new file mode 100644 index 0000000000..01c95a6524 --- /dev/null +++ b/codex-rs/core/src/context/hook_additional_context.rs @@ -0,0 +1,22 @@ +use super::ContextualUserFragment; + +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct HookAdditionalContext { + text: String, +} + +impl HookAdditionalContext { + pub(crate) fn new(text: impl Into) -> Self { + Self { text: text.into() } + } +} + +impl ContextualUserFragment for HookAdditionalContext { + const ROLE: &'static str = "developer"; + const START_MARKER: &'static str = ""; + const END_MARKER: &'static str = ""; + + fn body(&self) -> String { + self.text.clone() + } +} diff --git a/codex-rs/core/src/context/image_generation_instructions.rs b/codex-rs/core/src/context/image_generation_instructions.rs new file mode 100644 index 0000000000..01abee9096 --- /dev/null +++ b/codex-rs/core/src/context/image_generation_instructions.rs @@ -0,0 +1,30 @@ +use super::ContextualUserFragment; +use std::fmt::Display; + +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct ImageGenerationInstructions { + image_output_dir: String, + image_output_path: String, +} + +impl ImageGenerationInstructions { + pub(crate) fn new(image_output_dir: impl Display, image_output_path: impl Display) -> Self { + Self { + image_output_dir: image_output_dir.to_string(), + image_output_path: image_output_path.to_string(), + } + } +} + +impl ContextualUserFragment for ImageGenerationInstructions { + const ROLE: &'static str = "developer"; + const START_MARKER: &'static str = ""; + const END_MARKER: &'static str = ""; + + fn body(&self) -> String { + format!( + "Generated images are saved to {} as {} by default.\nIf 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.", + self.image_output_dir, self.image_output_path + ) + } +} diff --git a/codex-rs/core/src/context/mod.rs b/codex-rs/core/src/context/mod.rs index 94c5849f9d..bbc5bcacf1 100644 --- a/codex-rs/core/src/context/mod.rs +++ b/codex-rs/core/src/context/mod.rs @@ -1,20 +1,56 @@ +//! Context fragments injected into model input. + +mod approved_command_prefix_saved; +mod apps_instructions; +mod available_plugins_instructions; +mod available_skills_instructions; +mod collaboration_mode_instructions; mod contextual_user_message; mod environment_context; mod fragment; +mod guardian_followup_review_reminder; +mod hook_additional_context; +mod image_generation_instructions; +mod model_switch_instructions; +mod network_rule_saved; +mod permissions_instructions; +mod personality_spec_instructions; +mod plugin_instructions; +mod realtime_end_instructions; +mod realtime_start_instructions; +mod realtime_start_with_instructions; mod skill_instructions; +mod spawn_agent_instructions; mod subagent_notification; mod turn_aborted; mod user_instructions; mod user_shell_command; +pub(crate) use approved_command_prefix_saved::ApprovedCommandPrefixSaved; +pub(crate) use apps_instructions::AppsInstructions; +pub(crate) use available_plugins_instructions::AvailablePluginsInstructions; +pub(crate) use available_skills_instructions::AvailableSkillsInstructions; +pub(crate) use collaboration_mode_instructions::CollaborationModeInstructions; pub(crate) use contextual_user_message::is_contextual_user_fragment; pub(crate) use contextual_user_message::is_memory_excluded_contextual_user_fragment; pub(crate) use contextual_user_message::parse_visible_hook_prompt_message; pub(crate) use environment_context::EnvironmentContext; -pub(crate) use fragment::ContextualUserFragment; +pub use fragment::ContextualUserFragment; pub(crate) use fragment::FragmentRegistration; pub(crate) use fragment::FragmentRegistrationProxy; +pub(crate) use guardian_followup_review_reminder::GuardianFollowupReviewReminder; +pub(crate) use hook_additional_context::HookAdditionalContext; +pub(crate) use image_generation_instructions::ImageGenerationInstructions; +pub(crate) use model_switch_instructions::ModelSwitchInstructions; +pub(crate) use network_rule_saved::NetworkRuleSaved; +pub use permissions_instructions::PermissionsInstructions; +pub(crate) use personality_spec_instructions::PersonalitySpecInstructions; +pub(crate) use plugin_instructions::PluginInstructions; +pub(crate) use realtime_end_instructions::RealtimeEndInstructions; +pub(crate) use realtime_start_instructions::RealtimeStartInstructions; +pub(crate) use realtime_start_with_instructions::RealtimeStartWithInstructions; pub(crate) use skill_instructions::SkillInstructions; +pub(crate) use spawn_agent_instructions::SpawnAgentInstructions; pub(crate) use subagent_notification::SubagentNotification; pub(crate) use turn_aborted::TurnAborted; pub(crate) use user_instructions::UserInstructions; diff --git a/codex-rs/core/src/context/model_switch_instructions.rs b/codex-rs/core/src/context/model_switch_instructions.rs new file mode 100644 index 0000000000..0b75afb61a --- /dev/null +++ b/codex-rs/core/src/context/model_switch_instructions.rs @@ -0,0 +1,27 @@ +use super::ContextualUserFragment; + +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct ModelSwitchInstructions { + model_instructions: String, +} + +impl ModelSwitchInstructions { + pub(crate) fn new(model_instructions: impl Into) -> Self { + Self { + model_instructions: model_instructions.into(), + } + } +} + +impl ContextualUserFragment for ModelSwitchInstructions { + const ROLE: &'static str = "developer"; + const START_MARKER: &'static str = ""; + const END_MARKER: &'static str = ""; + + fn body(&self) -> String { + format!( + "\nThe user was previously using a different model. Please continue the conversation according to the following instructions:\n\n{}\n", + self.model_instructions + ) + } +} diff --git a/codex-rs/core/src/context/network_rule_saved.rs b/codex-rs/core/src/context/network_rule_saved.rs new file mode 100644 index 0000000000..8162e6bda2 --- /dev/null +++ b/codex-rs/core/src/context/network_rule_saved.rs @@ -0,0 +1,35 @@ +use super::ContextualUserFragment; +use codex_protocol::approvals::NetworkPolicyAmendment; +use codex_protocol::approvals::NetworkPolicyRuleAction; + +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct NetworkRuleSaved { + action: NetworkPolicyRuleAction, + host: String, +} + +impl NetworkRuleSaved { + pub(crate) fn new(amendment: &NetworkPolicyAmendment) -> Self { + Self { + action: amendment.action, + host: amendment.host.clone(), + } + } +} + +impl ContextualUserFragment for NetworkRuleSaved { + const ROLE: &'static str = "developer"; + const START_MARKER: &'static str = ""; + const END_MARKER: &'static str = ""; + + fn body(&self) -> String { + let (action, list_name) = match self.action { + NetworkPolicyRuleAction::Allow => ("Allowed", "allowlist"), + NetworkPolicyRuleAction::Deny => ("Denied", "denylist"), + }; + format!( + "{action} network rule saved in execpolicy ({list_name}): {}", + self.host + ) + } +} diff --git a/codex-rs/core/src/context/permissions_instructions.rs b/codex-rs/core/src/context/permissions_instructions.rs new file mode 100644 index 0000000000..db420f7464 --- /dev/null +++ b/codex-rs/core/src/context/permissions_instructions.rs @@ -0,0 +1,316 @@ +use super::ContextualUserFragment; +use codex_execpolicy::Policy; +use codex_protocol::config_types::ApprovalsReviewer; +use codex_protocol::config_types::SandboxMode; +use codex_protocol::models::format_allow_prefixes; +use codex_protocol::protocol::AskForApproval; +use codex_protocol::protocol::GranularApprovalConfig; +use codex_protocol::protocol::NetworkAccess; +use codex_protocol::protocol::SandboxPolicy; +use codex_protocol::protocol::WritableRoot; +use codex_utils_template::Template; +use std::path::Path; +use std::sync::LazyLock; + +const APPROVAL_POLICY_NEVER: &str = include_str!("prompts/permissions/approval_policy/never.md"); +const APPROVAL_POLICY_UNLESS_TRUSTED: &str = + include_str!("prompts/permissions/approval_policy/unless_trusted.md"); +const APPROVAL_POLICY_ON_FAILURE: &str = + include_str!("prompts/permissions/approval_policy/on_failure.md"); +const APPROVAL_POLICY_ON_REQUEST_RULE: &str = + include_str!("prompts/permissions/approval_policy/on_request.md"); +const APPROVAL_POLICY_ON_REQUEST_RULE_REQUEST_PERMISSION: &str = + include_str!("prompts/permissions/approval_policy/on_request_rule_request_permission.md"); +const AUTO_REVIEW_APPROVAL_SUFFIX: &str = "`approvals_reviewer` is `auto_review`: Sandbox escalations with require_escalated will be reviewed for compliance with the policy. If a rejection happens, you should proceed only with a materially safer alternative, or inform the user of the risk and send a final message to ask for approval."; + +const SANDBOX_MODE_DANGER_FULL_ACCESS: &str = + include_str!("prompts/permissions/sandbox_mode/danger_full_access.md"); +const SANDBOX_MODE_WORKSPACE_WRITE: &str = + include_str!("prompts/permissions/sandbox_mode/workspace_write.md"); +const SANDBOX_MODE_READ_ONLY: &str = include_str!("prompts/permissions/sandbox_mode/read_only.md"); + +static SANDBOX_MODE_DANGER_FULL_ACCESS_TEMPLATE: LazyLock