mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Add persisted hook enablement state (#19840)
## Why After `hooks/list` exposes the hook inventory, clients need a way to persist user hook preferences, make those changes effective in already-open sessions, and distinguish user-controllable hooks from managed requirements without adding another bespoke app-server write API. ## What - Extends `hooks/list` entries with effective `enabled` state. - Persists user-level hook state under `hooks.state.<hook-id>` so the model can grow beyond a single boolean over time. - Uses the existing `config/batchWrite` path for hook state updates instead of introducing a dedicated hook write RPC. - Refreshes live session hook engines after config writes so already-open threads observe updated enablement without a restart. ## Stack 1. openai/codex#19705 2. openai/codex#19778 3. This PR - openai/codex#19840 4. openai/codex#19882 ## Reviewer Notes The generated schema files account for much of the raw diff. The core behavior is in: - `hooks/src/config_rules.rs`, which resolves per-hook user state from the config layer stack. - `hooks/src/engine/discovery.rs`, which projects effective enablement into `hooks/list` from source-derived managedness. - `config/src/hook_config.rs`, which defines the new `hooks.state` representation. - `core/src/session/mod.rs`, which rebuilds live hook state after user config reloads. --------- Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -4,7 +4,7 @@ use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::HookEventsToml;
|
||||
use crate::HooksToml;
|
||||
use crate::permissions_toml::PermissionsToml;
|
||||
use crate::profile_toml::ConfigProfile;
|
||||
use crate::types::AnalyticsConfigToml;
|
||||
@@ -343,8 +343,8 @@ pub struct ConfigToml {
|
||||
/// User-level skill config entries keyed by SKILL.md path.
|
||||
pub skills: Option<SkillsConfig>,
|
||||
|
||||
/// Lifecycle hooks configured inline in TOML.
|
||||
pub hooks: Option<HookEventsToml>,
|
||||
/// Lifecycle hooks configured inline in TOML plus user-level overrides.
|
||||
pub hooks: Option<HooksToml>,
|
||||
|
||||
/// User-level plugin config entries keyed by plugin name.
|
||||
#[serde(default)]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -12,6 +13,20 @@ pub struct HooksFile {
|
||||
pub hooks: HookEventsToml,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct HooksToml {
|
||||
#[serde(flatten)]
|
||||
pub events: HookEventsToml,
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub state: BTreeMap<String, HookStateToml>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct HookStateToml {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub enabled: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct HookEventsToml {
|
||||
#[serde(rename = "PreToolUse", default)]
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use super::HookEventsToml;
|
||||
use super::HookHandlerConfig;
|
||||
use super::HooksFile;
|
||||
use super::HooksToml;
|
||||
use super::ManagedHooksRequirementsToml;
|
||||
use super::MatcherGroup;
|
||||
|
||||
@@ -81,6 +84,48 @@ statusMessage = "checking"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hooks_toml_deserializes_inline_events_and_state_map() {
|
||||
let parsed: HooksToml = toml::from_str(
|
||||
r#"
|
||||
[state."/tmp/hooks.json:pre_tool_use:0:0"]
|
||||
enabled = false
|
||||
|
||||
[[PreToolUse]]
|
||||
matcher = "^Bash$"
|
||||
|
||||
[[PreToolUse.hooks]]
|
||||
type = "command"
|
||||
command = "python3 /tmp/pre.py"
|
||||
"#,
|
||||
)
|
||||
.expect("hooks TOML should deserialize");
|
||||
|
||||
assert_eq!(
|
||||
parsed,
|
||||
HooksToml {
|
||||
events: HookEventsToml {
|
||||
pre_tool_use: vec![MatcherGroup {
|
||||
matcher: Some("^Bash$".to_string()),
|
||||
hooks: vec![HookHandlerConfig::Command {
|
||||
command: "python3 /tmp/pre.py".to_string(),
|
||||
timeout_sec: None,
|
||||
r#async: false,
|
||||
status_message: None,
|
||||
}],
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
state: BTreeMap::from([(
|
||||
"/tmp/hooks.json:pre_tool_use:0:0".to_string(),
|
||||
super::HookStateToml {
|
||||
enabled: Some(false),
|
||||
},
|
||||
)]),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_hooks_requirements_flatten_hook_events() {
|
||||
let parsed: ManagedHooksRequirementsToml = toml::from_str(
|
||||
|
||||
@@ -73,7 +73,9 @@ pub use diagnostics::io_error_from_config_error;
|
||||
pub use fingerprint::version_for_toml;
|
||||
pub use hook_config::HookEventsToml;
|
||||
pub use hook_config::HookHandlerConfig;
|
||||
pub use hook_config::HookStateToml;
|
||||
pub use hook_config::HooksFile;
|
||||
pub use hook_config::HooksToml;
|
||||
pub use hook_config::ManagedHooksRequirementsToml;
|
||||
pub use hook_config::MatcherGroup;
|
||||
pub use host_name::host_name;
|
||||
|
||||
Reference in New Issue
Block a user