diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 240d84fdb4..5f1d1f2093 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -404,6 +404,9 @@ "guardian_approval": { "type": "boolean" }, + "hooks": { + "type": "boolean" + }, "image_detail_original": { "type": "boolean" }, @@ -2372,6 +2375,9 @@ "guardian_approval": { "type": "boolean" }, + "hooks": { + "type": "boolean" + }, "image_detail_original": { "type": "boolean" }, diff --git a/codex-rs/features/src/legacy.rs b/codex-rs/features/src/legacy.rs index 2e3a0b37e7..f93b7aea05 100644 --- a/codex-rs/features/src/legacy.rs +++ b/codex-rs/features/src/legacy.rs @@ -9,6 +9,10 @@ struct Alias { } const ALIASES: &[Alias] = &[ + Alias { + legacy_key: "codex_hooks", + feature: Feature::CodexHooks, + }, Alias { legacy_key: "connectors", feature: Feature::Apps, diff --git a/codex-rs/features/src/lib.rs b/codex-rs/features/src/lib.rs index f811f6aa1e..178fe1a8be 100644 --- a/codex-rs/features/src/lib.rs +++ b/codex-rs/features/src/lib.rs @@ -740,8 +740,12 @@ pub const FEATURES: &[FeatureSpec] = &[ }, FeatureSpec { id: Feature::CodexHooks, - key: "codex_hooks", - stage: Stage::UnderDevelopment, + key: "hooks", + stage: Stage::Experimental { + name: "Hooks", + menu_description: "Run deterministic scripts during the Codex lifecycle", + announcement: "", + }, default_enabled: false, }, FeatureSpec { diff --git a/codex-rs/features/src/tests.rs b/codex-rs/features/src/tests.rs index 18b030f88c..5cebd7c2dc 100644 --- a/codex-rs/features/src/tests.rs +++ b/codex-rs/features/src/tests.rs @@ -120,6 +120,22 @@ fn external_migration_is_experimental_and_disabled_by_default() { assert_eq!(Feature::ExternalMigration.default_enabled(), false); } +#[test] +fn hooks_is_experimental_and_user_toggleable() { + let spec = Feature::CodexHooks.info(); + let stage = spec.stage; + + assert_eq!(spec.key, "hooks"); + assert!(matches!(stage, Stage::Experimental { .. })); + assert_eq!(stage.experimental_menu_name(), Some("Hooks")); + assert_eq!( + stage.experimental_menu_description(), + Some("Run deterministic scripts during the Codex lifecycle") + ); + assert_eq!(stage.experimental_announcement(), None); + assert_eq!(Feature::CodexHooks.default_enabled(), false); +} + #[test] fn request_permissions_is_under_development() { assert_eq!( @@ -247,6 +263,12 @@ fn collab_is_legacy_alias_for_multi_agent() { assert_eq!(feature_for_key("collab"), Some(Feature::Collab)); } +#[test] +fn codex_hooks_is_legacy_alias_for_hooks() { + assert_eq!(feature_for_key("hooks"), Some(Feature::CodexHooks)); + assert_eq!(feature_for_key("codex_hooks"), Some(Feature::CodexHooks)); +} + #[test] fn multi_agent_is_stable_and_enabled_by_default() { assert_eq!(Feature::Collab.stage(), Stage::Stable); diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__experimental_features_popup_from_registry.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__experimental_features_popup_from_registry.snap new file mode 100644 index 0000000000..82399dfc58 --- /dev/null +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__experimental_features_popup_from_registry.snap @@ -0,0 +1,22 @@ +--- +source: tui/src/chatwidget/tests/popups_and_settings.rs +expression: popup +--- + Experimental features + Toggle experimental features. Changes are saved to config.toml. + +› [ ] JavaScript REPL Enable a persistent Node-backed JavaScript REPL for interactive website debugging + and other inline JavaScript execution capabilities. Requires Node >= v22.22.0 + installed. + [ ] Memories Allow Codex to create new memories from conversations and bring relevant memories + into new conversations. + [ ] Hooks Run deterministic scripts during the Codex lifecycle + [ ] External migration Show a startup prompt when Codex detects migratable external agent config for this + machine or project. + [ ] Auto-review When Codex needs approval for higher-risk actions (e.g. sandbox escapes or blocked + network access), route eligible approval requests to a carefully-prompted security + reviewer subagent rather than blocking the agent on your input. This can consume + significantly more tokens because it runs a subagent on every approval request. + [ ] Prevent sleep while running Keep your computer awake while Codex is running a thread. + + Press space to select or enter to save for next conversation diff --git a/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs b/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs index a3792c31f4..781bee6201 100644 --- a/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs +++ b/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs @@ -1682,6 +1682,16 @@ async fn experimental_features_popup_snapshot() { assert_chatwidget_snapshot!("experimental_features_popup", popup); } +#[tokio::test] +async fn experimental_features_popup_from_registry_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; + + chat.open_experimental_popup(); + + let popup = render_bottom_popup(&chat, /*width*/ 120); + assert_chatwidget_snapshot!("experimental_features_popup_from_registry", popup); +} + #[tokio::test] async fn experimental_features_toggle_saves_on_exit() { let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;