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:
@@ -116,13 +116,13 @@ pub(crate) async fn run_pending_session_start_hooks(
|
||||
permission_mode: hook_permission_mode(turn_context),
|
||||
source: session_start_source,
|
||||
};
|
||||
let preview_runs = sess.hooks().preview_session_start(&request);
|
||||
let hooks = sess.hooks();
|
||||
let preview_runs = hooks.preview_session_start(&request);
|
||||
run_context_injecting_hook(
|
||||
sess,
|
||||
turn_context,
|
||||
preview_runs,
|
||||
sess.hooks()
|
||||
.run_session_start(request, Some(turn_context.sub_id.clone())),
|
||||
hooks.run_session_start(request, Some(turn_context.sub_id.clone())),
|
||||
)
|
||||
.await
|
||||
.record_additional_contexts(sess, turn_context)
|
||||
@@ -153,14 +153,15 @@ pub(crate) async fn run_pre_tool_use_hooks(
|
||||
tool_use_id,
|
||||
tool_input: tool_input.clone(),
|
||||
};
|
||||
let preview_runs = sess.hooks().preview_pre_tool_use(&request);
|
||||
let hooks = sess.hooks();
|
||||
let preview_runs = hooks.preview_pre_tool_use(&request);
|
||||
emit_hook_started_events(sess, turn_context, preview_runs).await;
|
||||
|
||||
let PreToolUseOutcome {
|
||||
hook_events,
|
||||
should_block,
|
||||
block_reason,
|
||||
} = sess.hooks().run_pre_tool_use(request).await;
|
||||
} = hooks.run_pre_tool_use(request).await;
|
||||
emit_hook_completed_events(sess, turn_context, hook_events).await;
|
||||
|
||||
if should_block {
|
||||
@@ -202,13 +203,14 @@ pub(crate) async fn run_permission_request_hooks(
|
||||
run_id_suffix: run_id_suffix.to_string(),
|
||||
tool_input: payload.tool_input,
|
||||
};
|
||||
let preview_runs = sess.hooks().preview_permission_request(&request);
|
||||
let hooks = sess.hooks();
|
||||
let preview_runs = hooks.preview_permission_request(&request);
|
||||
emit_hook_started_events(sess, turn_context, preview_runs).await;
|
||||
|
||||
let PermissionRequestOutcome {
|
||||
hook_events,
|
||||
decision,
|
||||
} = sess.hooks().run_permission_request(request).await;
|
||||
} = hooks.run_permission_request(request).await;
|
||||
emit_hook_completed_events(sess, turn_context, hook_events).await;
|
||||
|
||||
decision
|
||||
@@ -242,10 +244,11 @@ pub(crate) async fn run_post_tool_use_hooks(
|
||||
tool_input,
|
||||
tool_response,
|
||||
};
|
||||
let preview_runs = sess.hooks().preview_post_tool_use(&request);
|
||||
let hooks = sess.hooks();
|
||||
let preview_runs = hooks.preview_post_tool_use(&request);
|
||||
emit_hook_started_events(sess, turn_context, preview_runs).await;
|
||||
|
||||
let outcome = sess.hooks().run_post_tool_use(request).await;
|
||||
let outcome = hooks.run_post_tool_use(request).await;
|
||||
emit_hook_completed_events(sess, turn_context, outcome.hook_events.clone()).await;
|
||||
outcome
|
||||
}
|
||||
@@ -264,12 +267,13 @@ pub(crate) async fn run_user_prompt_submit_hooks(
|
||||
permission_mode: hook_permission_mode(turn_context),
|
||||
prompt,
|
||||
};
|
||||
let preview_runs = sess.hooks().preview_user_prompt_submit(&request);
|
||||
let hooks = sess.hooks();
|
||||
let preview_runs = hooks.preview_user_prompt_submit(&request);
|
||||
run_context_injecting_hook(
|
||||
sess,
|
||||
turn_context,
|
||||
preview_runs,
|
||||
sess.hooks().run_user_prompt_submit(request),
|
||||
hooks.run_user_prompt_submit(request),
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -474,6 +478,7 @@ fn hook_run_metric_tags(run: &HookRunSummary) -> [(&'static str, &'static str);
|
||||
HookSource::Mdm => "mdm",
|
||||
HookSource::SessionFlags => "session_flags",
|
||||
HookSource::Plugin => "plugin",
|
||||
HookSource::CloudRequirements => "cloud_requirements",
|
||||
HookSource::LegacyManagedConfigFile => "legacy_managed_config_file",
|
||||
HookSource::LegacyManagedConfigMdm => "legacy_managed_config_mdm",
|
||||
HookSource::Unknown => "unknown",
|
||||
@@ -605,6 +610,18 @@ mod tests {
|
||||
("status", "blocked"),
|
||||
]
|
||||
);
|
||||
|
||||
let cloud_requirements =
|
||||
sample_hook_run(HookRunStatus::Blocked, HookSource::CloudRequirements);
|
||||
|
||||
assert_eq!(
|
||||
hook_run_metric_tags(&cloud_requirements),
|
||||
[
|
||||
("hook_name", "Stop"),
|
||||
("source", "cloud_requirements"),
|
||||
("status", "blocked"),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user