mirror of
https://github.com/openai/codex.git
synced 2026-05-15 08:42:34 +00:00
# Why
Managed hook configs need a shared cross-platform shape without making
the existing `command` field polymorphic. The common case is still one
command string, with Windows needing a different entrypoint only when
the runtime is actually Windows.
Keeping `command` as the portable/default path and adding an optional
Windows override keeps the config easier to read, preserves the existing
scalar shape for non-Windows users, and avoids forcing every caller into
a `{ unix, windows }` object when only one platform needs special
handling.
# What
- Add optional `command_windows` / `commandWindows` alongside the
existing hook `command` field.
- Resolve `command_windows` only on Windows during hook discovery; other
platforms continue to use `command` unchanged.
- Keep trust hashing aligned to the effective command selected for the
current runtime.
# Docs
The Codex hooks/config reference should document `command_windows` as
the Windows-only override for command hooks.
102 lines
3.7 KiB
Rust
102 lines
3.7 KiB
Rust
use codex_plugin::PluginHookSource;
|
|
use codex_protocol::protocol::HookEventName;
|
|
|
|
/// Minimal declaration metadata for one bundled plugin hook handler.
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct PluginHookDeclaration {
|
|
pub key: String,
|
|
pub event_name: HookEventName,
|
|
}
|
|
|
|
/// Return the hook handlers declared by plugin bundles without projecting live runtime state.
|
|
pub fn plugin_hook_declarations(hook_sources: &[PluginHookSource]) -> Vec<PluginHookDeclaration> {
|
|
let mut declarations = Vec::new();
|
|
|
|
for source in hook_sources {
|
|
let key_source = plugin_hook_key_source(
|
|
source.plugin_id.as_key().as_str(),
|
|
source.source_relative_path.as_str(),
|
|
);
|
|
for (event_name, groups) in source.hooks.clone().into_matcher_groups() {
|
|
for (group_index, group) in groups.iter().enumerate() {
|
|
for (handler_index, _) in group.hooks.iter().enumerate() {
|
|
declarations.push(PluginHookDeclaration {
|
|
key: crate::hook_key(&key_source, event_name, group_index, handler_index),
|
|
event_name,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
declarations
|
|
}
|
|
|
|
pub(crate) fn plugin_hook_key_source(plugin_id: &str, source_relative_path: &str) -> String {
|
|
format!("{plugin_id}:{source_relative_path}")
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use codex_config::HookEventsToml;
|
|
use codex_config::HookHandlerConfig;
|
|
use codex_config::MatcherGroup;
|
|
use codex_plugin::PluginId;
|
|
use codex_utils_absolute_path::test_support::PathBufExt;
|
|
use codex_utils_absolute_path::test_support::test_path_buf;
|
|
use pretty_assertions::assert_eq;
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn lists_declared_plugin_handlers_with_persisted_hook_keys() {
|
|
let plugin_root = test_path_buf("/tmp/plugin").abs();
|
|
let source_path = plugin_root.join("hooks/hooks.json");
|
|
let declarations = plugin_hook_declarations(&[PluginHookSource {
|
|
plugin_id: PluginId::parse("demo@test").expect("plugin id"),
|
|
plugin_root: plugin_root.clone(),
|
|
plugin_data_root: plugin_root.join("data"),
|
|
source_path,
|
|
source_relative_path: "hooks/hooks.json".to_string(),
|
|
hooks: HookEventsToml {
|
|
pre_tool_use: vec![MatcherGroup {
|
|
matcher: None,
|
|
hooks: vec![
|
|
HookHandlerConfig::Prompt {},
|
|
HookHandlerConfig::Command {
|
|
command: "echo hi".to_string(),
|
|
command_windows: None,
|
|
timeout_sec: None,
|
|
r#async: false,
|
|
status_message: None,
|
|
},
|
|
],
|
|
}],
|
|
session_start: vec![MatcherGroup {
|
|
matcher: None,
|
|
hooks: vec![HookHandlerConfig::Agent {}],
|
|
}],
|
|
..Default::default()
|
|
},
|
|
}]);
|
|
|
|
assert_eq!(
|
|
declarations,
|
|
vec![
|
|
PluginHookDeclaration {
|
|
key: "demo@test:hooks/hooks.json:pre_tool_use:0:0".to_string(),
|
|
event_name: HookEventName::PreToolUse,
|
|
},
|
|
PluginHookDeclaration {
|
|
key: "demo@test:hooks/hooks.json:pre_tool_use:0:1".to_string(),
|
|
event_name: HookEventName::PreToolUse,
|
|
},
|
|
PluginHookDeclaration {
|
|
key: "demo@test:hooks/hooks.json:session_start:0:0".to_string(),
|
|
event_name: HookEventName::SessionStart,
|
|
},
|
|
]
|
|
);
|
|
}
|
|
}
|