mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
## Summary Support the existing hooks schema in inline TOML so hooks can be configured from both `config.toml` and enterprise-managed `requirements.toml` without requiring a separate `hooks.json` payload. This gives enterprise admins a way to ship managed hook policy through the existing requirements channel while still leaving script delivery to MDM or other device-management tooling, and it keeps `hooks.json` working unchanged for existing users. This also lays the groundwork for follow-on managed filtering work such as #15937, while continuing to respect project trust gating from #14718. It does **not** implement `allow_managed_hooks_only` itself. NOTE: yes, it's a bit unfortunate that the toml isn't formatted as closely as normal to our default styling. This is because we're trying to stay compatible with the spec for plugins/hooks that we'll need to support & the main usecase here is embedding into requirements.toml ## What changed - moved the shared hook serde model out of `codex-rs/hooks` into `codex-rs/config` so the same schema can power `hooks.json`, inline `config.toml` hooks, and managed `requirements.toml` hooks - added `hooks` support to both `ConfigToml` and `ConfigRequirementsToml`, including requirements-side `managed_dir` / `windows_managed_dir` - treated requirements-managed hooks as one constrained value via `Constrained`, so managed hook policy is merged atomically and cannot drift across requirement sources - updated hook discovery to load requirements-managed hooks first, then per-layer `hooks.json`, then per-layer inline TOML hooks, with a warning when a single layer defines both representations - threaded managed hook metadata through discovered handlers and exposed requirements hooks in app-server responses, generated schemas, and `/debug-config` - added hook/config coverage in `codex-rs/config`, `codex-rs/hooks`, `codex-rs/core/src/config_loader/tests.rs`, and `codex-rs/core/tests/suite/hooks.rs` ## Testing - `cargo test -p codex-config` - `cargo test -p codex-hooks` - `cargo test -p codex-app-server config_api` ## Documentation Companion updates are needed in the developers website repo for: - the hooks guide - the config reference, sample, basic, and advanced pages - the enterprise managed configuration guide --------- Co-authored-by: Michael Bolin <mbolin@openai.com>
120 lines
3.1 KiB
Rust
120 lines
3.1 KiB
Rust
use pretty_assertions::assert_eq;
|
|
|
|
use super::HookEventsToml;
|
|
use super::HookHandlerConfig;
|
|
use super::HooksFile;
|
|
use super::ManagedHooksRequirementsToml;
|
|
use super::MatcherGroup;
|
|
|
|
#[test]
|
|
fn hooks_file_deserializes_existing_json_shape() {
|
|
let parsed: HooksFile = serde_json::from_str(
|
|
r#"{
|
|
"hooks": {
|
|
"PreToolUse": [
|
|
{
|
|
"matcher": "^Bash$",
|
|
"hooks": [
|
|
{
|
|
"type": "command",
|
|
"command": "python3 /tmp/pre.py",
|
|
"timeout": 10,
|
|
"statusMessage": "checking"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}"#,
|
|
)
|
|
.expect("hooks.json should deserialize");
|
|
|
|
assert_eq!(
|
|
parsed,
|
|
HooksFile {
|
|
hooks: HookEventsToml {
|
|
pre_tool_use: vec![MatcherGroup {
|
|
matcher: Some("^Bash$".to_string()),
|
|
hooks: vec![HookHandlerConfig::Command {
|
|
command: "python3 /tmp/pre.py".to_string(),
|
|
timeout_sec: Some(10),
|
|
r#async: false,
|
|
status_message: Some("checking".to_string()),
|
|
}],
|
|
}],
|
|
..Default::default()
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn hook_events_deserialize_from_toml_arrays_of_tables() {
|
|
let parsed: HookEventsToml = toml::from_str(
|
|
r#"
|
|
[[PreToolUse]]
|
|
matcher = "^Bash$"
|
|
|
|
[[PreToolUse.hooks]]
|
|
type = "command"
|
|
command = "python3 /tmp/pre.py"
|
|
timeout = 10
|
|
statusMessage = "checking"
|
|
"#,
|
|
)
|
|
.expect("hook events TOML should deserialize");
|
|
|
|
assert_eq!(
|
|
parsed,
|
|
HookEventsToml {
|
|
pre_tool_use: vec![MatcherGroup {
|
|
matcher: Some("^Bash$".to_string()),
|
|
hooks: vec![HookHandlerConfig::Command {
|
|
command: "python3 /tmp/pre.py".to_string(),
|
|
timeout_sec: Some(10),
|
|
r#async: false,
|
|
status_message: Some("checking".to_string()),
|
|
}],
|
|
}],
|
|
..Default::default()
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn managed_hooks_requirements_flatten_hook_events() {
|
|
let parsed: ManagedHooksRequirementsToml = toml::from_str(
|
|
r#"
|
|
managed_dir = "/enterprise/place"
|
|
|
|
[[PreToolUse]]
|
|
matcher = "^Bash$"
|
|
|
|
[[PreToolUse.hooks]]
|
|
type = "command"
|
|
command = "python3 /enterprise/place/pre.py"
|
|
"#,
|
|
)
|
|
.expect("requirements hooks TOML should deserialize");
|
|
|
|
assert_eq!(
|
|
parsed,
|
|
ManagedHooksRequirementsToml {
|
|
managed_dir: Some(std::path::PathBuf::from("/enterprise/place")),
|
|
windows_managed_dir: None,
|
|
hooks: HookEventsToml {
|
|
pre_tool_use: vec![MatcherGroup {
|
|
matcher: Some("^Bash$".to_string()),
|
|
hooks: vec![HookHandlerConfig::Command {
|
|
command: "python3 /enterprise/place/pre.py".to_string(),
|
|
timeout_sec: None,
|
|
r#async: false,
|
|
status_message: None,
|
|
}],
|
|
}],
|
|
..Default::default()
|
|
},
|
|
}
|
|
);
|
|
}
|