Add configurable gating for the sleep tool (#41243)

## What changed

- Add a stable `sleep_tool` feature that can enable or disable registration of the built-in sleep tool independently of the clock tool.
- Support structured `features.sleep_tool` configuration with `model_driven` and `always_on` modes. The default `model_driven` mode preserves the existing model and `current_time_reminder` behavior, while `always_on` registers sleep whenever the feature is enabled.
- Preserve nested sleep-tool configuration across CLI overrides, config merges, and feature toggles, and expose the settings in the generated config schema.

## Testing

- Cover feature-map overrides, both selection modes, legacy clock settings, config merging and editing, and invalid mode rejection.

GitOrigin-RevId: 18eadc582f8a4445958c29d063e5be495276703e
This commit is contained in:
rka-oai authored and copyberry committed 2026-08-28 02:09:07 +00:00
1 parent 41d3dc56a0
commit ec9620c231
12 files changed
+313 -14

No files matched your search

+4 -1
View File
@@ -67,7 +67,10 @@ pub fn is_structured_feature_path<S: AsRef<str>>(path: &[S]) -> bool {
};
features.as_ref() == "features"
&& matches!(feature.as_ref(), "multi_agent_v2" | "network_proxy")
&& matches!(
feature.as_ref(),
"multi_agent_v2" | "network_proxy" | "sleep_tool"
)
}
fn merge_toml_values_at_path(base: &mut TomlValue, overlay: &TomlValue, path: &mut Vec<String>) {
+32
View File
@@ -255,6 +255,38 @@ fn multi_agent_v2_cli_overrides_preserve_boolean_and_nested_configuration() {
}
}
#[test]
fn sleep_tool_overrides_preserve_disabled_state_and_mode() {
for feature_path in ["features", "profiles.work.features"] {
let disabled = (
format!("{feature_path}.sleep_tool"),
TomlValue::Boolean(false),
);
let mode = (
format!("{feature_path}.sleep_tool.mode"),
TomlValue::String("always_on".to_string()),
);
let expected = parse_toml(&format!(
"[{feature_path}.sleep_tool]\nenabled = false\nmode = \"always_on\"\n",
));
for overrides in [vec![disabled.clone(), mode.clone()], vec![mode, disabled]] {
assert_eq!(crate::build_cli_overrides_layer(&overrides), expected);
}
let boolean_layer = parse_toml(&format!("[{feature_path}]\nsleep_tool = false\n"));
let table_layer = parse_toml(&format!(
"[{feature_path}.sleep_tool]\nmode = \"always_on\"\n",
));
for (mut base, overlay) in [
(boolean_layer.clone(), table_layer.clone()),
(table_layer, boolean_layer),
] {
merge_toml_values(&mut base, &overlay);
assert_eq!(base, expected);
}
}
}
#[test]
fn network_proxy_feature_overrides_preserve_credential_broker_configuration() {
let enabled = (
+9
View File
@@ -122,6 +122,15 @@ pub fn features_schema(schema_gen: &mut SchemaGenerator) -> Schema {
);
continue;
}
if feature.id == codex_features::Feature::SleepTool {
validation.properties.insert(
feature.key.to_string(),
schema_gen.subschema_for::<codex_features::FeatureToml<
codex_features::SleepToolConfigToml,
>>(),
);
continue;
}
if feature.id == codex_features::Feature::AppsMcpPathOverride {
validation.properties.insert(
feature.key.to_string(),