Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions crates/cli/src/agents/claude/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use serde_json::{Value, json};

use crate::agents::CodingAgent;
use crate::error::CliError;
use crate::hooks::{generated_hooks, transparent_hook_forward_command};
use crate::hooks::{generated_policy_hooks, transparent_hook_forward_commands};
use crate::process::{PreparedAgentLaunch, insert_after_host};

pub(crate) fn prepare(
Expand Down Expand Up @@ -61,15 +61,15 @@ pub(crate) fn prepare(
}))
.map_err(|error| CliError::Launch(error.to_string()))?,
)?;
let hook_command = transparent_hook_forward_command(
let hook_commands = transparent_hook_forward_commands(
&transparent_hook_executable(),
CodingAgent::ClaudeCode,
gateway_url,
)
.map_err(CliError::Launch)?;
write_hooks(
&root.join("hooks/hooks.json"),
generated_hooks(CodingAgent::ClaudeCode, &hook_command),
generated_policy_hooks(CodingAgent::ClaudeCode, &hook_commands),
)?;
let settings_path = root.join("settings.json");
let settings = settings_overlay(&launch.argv, launch.host_index, gateway_url)?;
Expand Down
73 changes: 47 additions & 26 deletions crates/cli/src/agents/codex/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use toml_edit::{DocumentMut, InlineTable, Item, Table, Value as TomlValue, value

use crate::agents::CodingAgent;
use crate::configuration::{BOOTSTRAP_CLIENT_TOKEN_HEADER, BootstrapChallengeKey, RELAY_PLUGIN_ID};
#[cfg(test)]
use crate::hooks::generated_hooks;
#[cfg(test)]
use crate::hooks::merge_hooks;
Expand Down Expand Up @@ -109,11 +110,11 @@ pub(crate) fn install_codex_with_generation(

pub(crate) fn install_codex_with_trust<F>(
gateway_url: &str,
expected_command: &str,
expected_commands: &crate::hooks::GeneratedHookCommands,
trust_hooks: F,
) -> Result<ExitCode, String>
where
F: FnOnce(&Path, &Path, &str) -> Result<(), String>,
F: FnOnce(&Path, &Path, &crate::hooks::GeneratedHookCommands) -> Result<(), String>,
{
let home = home_dir()?;
let codex_dir = codex_home_dir()?;
Expand All @@ -125,7 +126,7 @@ where
let snapshots = codex_install_snapshots(&config_path, &hooks_path)?;
let install_result = remove_legacy_codex_hooks(&hooks_path)
.and_then(|()| install_codex_config(&config_path, gateway_url))
.and_then(|()| trust_hooks(&home, &config_path, expected_command));
.and_then(|()| trust_hooks(&home, &config_path, expected_commands));
if let Err(error) = install_result {
return match restore_codex_install_snapshots(&snapshots) {
Ok(()) => Err(error),
Expand Down Expand Up @@ -257,19 +258,19 @@ pub(crate) fn codex_hook_trust_report_with_generation(
pub(crate) fn codex_hook_trust_report_with_client(
client: &mut dyn CodexHooksClient,
cwd: &Path,
expected_command: &str,
expected_commands: &crate::hooks::GeneratedHookCommands,
) -> Result<CodexHookTrustReport, String> {
let hooks = relay_codex_hooks(client, cwd, expected_command)?;
let hooks = relay_codex_hooks(client, cwd, expected_commands)?;
Ok(codex_hook_trust_report_for(&hooks))
}

pub(crate) fn auto_trust_codex_hooks(
client: &mut dyn CodexHooksClient,
cwd: &Path,
config_path: &Path,
expected_command: &str,
expected_commands: &crate::hooks::GeneratedHookCommands,
) -> Result<(), String> {
let hooks = relay_codex_hooks(client, cwd, expected_command)?;
let hooks = relay_codex_hooks(client, cwd, expected_commands)?;
let before = codex_hook_trust_report_for(&hooks);
if !before.missing_required.is_empty() || !before.duplicate_required.is_empty() {
return Err(format!(
Expand All @@ -280,7 +281,7 @@ pub(crate) fn auto_trust_codex_hooks(
}
let state = snapshot_hook_trust_state(config_path, &hooks)?;
let trust_result = client.trust_hooks(&hooks).and_then(|()| {
let verified_hooks = relay_codex_hooks(client, cwd, expected_command)?;
let verified_hooks = relay_codex_hooks(client, cwd, expected_commands)?;
let verified = codex_hook_trust_report_for(&verified_hooks);
let unverified_targets = hooks
.iter()
Expand Down Expand Up @@ -308,7 +309,7 @@ pub(crate) fn auto_trust_codex_hooks(
return restore_hook_trust_after_failure(
client,
cwd,
expected_command,
expected_commands,
&hooks,
&state,
error,
Expand All @@ -320,21 +321,24 @@ pub(crate) fn auto_trust_codex_hooks(
fn relay_codex_hooks(
client: &mut dyn CodexHooksClient,
cwd: &Path,
expected_command: &str,
expected_commands: &crate::hooks::GeneratedHookCommands,
) -> Result<Vec<CodexHookMetadata>, String> {
let hooks = relay_codex_plugin_hooks(client, cwd)?
.into_iter()
.filter(|hook| hook.command.as_deref() == Some(expected_command))
.filter(|hook| {
hook.command.as_deref()
== expected_codex_hook_command(expected_commands, &hook.event_name)
Comment thread
willkill07 marked this conversation as resolved.
Resolved
})
.collect::<Vec<_>>();
validate_loaded_hook_sources(&hooks, expected_command)?;
validate_loaded_hook_sources(&hooks, expected_commands)?;
Ok(hooks)
}

fn validate_loaded_hook_sources(
hooks: &[CodexHookMetadata],
expected_command: &str,
expected_commands: &crate::hooks::GeneratedHookCommands,
) -> Result<(), String> {
let expected = generated_hooks(CodingAgent::Codex, expected_command);
let expected = crate::hooks::generated_policy_hooks(CodingAgent::Codex, expected_commands);
let sources = hooks
.iter()
.map(|hook| hook.source_path.as_str())
Expand Down Expand Up @@ -538,7 +542,7 @@ fn verify_restored_hook_trust(
fn restore_hook_trust_after_failure(
client: &mut dyn CodexHooksClient,
cwd: &Path,
expected_command: &str,
expected_commands: &crate::hooks::GeneratedHookCommands,
before: &[CodexHookMetadata],
state: &[(String, Option<Value>)],
original_error: String,
Expand All @@ -548,7 +552,7 @@ fn restore_hook_trust_after_failure(
"{original_error}; additionally failed to restore Codex hook trust: {rollback_error}"
));
}
let restored = relay_codex_hooks(client, cwd, expected_command).map_err(|rollback_error| {
let restored = relay_codex_hooks(client, cwd, expected_commands).map_err(|rollback_error| {
format!(
"{original_error}; additionally failed to verify restored Codex hook trust: {rollback_error}"
)
Expand All @@ -569,14 +573,16 @@ fn restore_hook_trust_after_failure(
}

#[cfg(test)]
pub(crate) fn expected_plugin_hook_command(plugin_hooks_path: &Path) -> Result<String, String> {
pub(crate) fn expected_plugin_hook_command(
plugin_hooks_path: &Path,
) -> Result<crate::hooks::GeneratedHookCommands, String> {
expected_plugin_hook_command_with_token(plugin_hooks_path, None)
}

fn expected_plugin_hook_command_with_token(
plugin_hooks_path: &Path,
generation_token: Option<&str>,
) -> Result<String, String> {
) -> Result<crate::hooks::GeneratedHookCommands, String> {
let relay = current_exe()?;
let relay = relay.canonicalize().unwrap_or(relay);
let relay = portable_executable_path(relay);
Expand Down Expand Up @@ -614,9 +620,12 @@ fn plugin_generation_file(plugin_hooks_path: &Path) -> Result<PathBuf, String> {
}
}

fn validate_plugin_hooks(path: &Path, expected_command: &str) -> Result<(), String> {
fn validate_plugin_hooks(
path: &Path,
expected_commands: &crate::hooks::GeneratedHookCommands,
) -> Result<(), String> {
let actual = read_json_object(path)?;
let expected = generated_hooks(CodingAgent::Codex, expected_command);
let expected = crate::hooks::generated_policy_hooks(CodingAgent::Codex, expected_commands);
if actual == expected {
Ok(())
} else {
Expand Down Expand Up @@ -674,14 +683,26 @@ fn is_generated_codex_hook_event(event: &str) -> bool {
.any(|expected| normalize_hook_event(expected) == normalized)
}

fn normalize_hook_event(event: &str) -> String {
pub(crate) fn normalize_hook_event(event: &str) -> String {
event
.chars()
.filter(|character| character.is_ascii_alphanumeric())
.flat_map(char::to_lowercase)
.collect()
}

pub(crate) fn expected_codex_hook_command<'a>(
commands: &'a crate::hooks::GeneratedHookCommands,
event: &str,
) -> Option<&'a str> {
let normalized = normalize_hook_event(event);
CodingAgent::Codex
.hook_events()
.iter()
.find(|expected| normalize_hook_event(expected) == normalized)
.map(|event| commands.for_event(event))
}

fn codex_install_snapshots(
config_path: &Path,
hooks_path: &Path,
Expand Down Expand Up @@ -1635,7 +1656,7 @@ pub(crate) fn codex_hooks_installed_with_generation(
generation_token: Option<&str>,
) -> Result<bool, String> {
let value = read_json_object(path)?;
let generated = generated_hooks(
let generated = crate::hooks::generated_policy_hooks(
CodingAgent::Codex,
&expected_plugin_hook_command_with_token(path, generation_token)?,
);
Expand All @@ -1660,8 +1681,8 @@ pub(crate) fn codex_plugin_hook_command(
relay: &Path,
generation: &Path,
generation_token: &str,
) -> Result<String, String> {
crate::hooks::persistent_hook_forward_command(
) -> Result<crate::hooks::GeneratedHookCommands, String> {
crate::hooks::persistent_hook_forward_commands(
relay,
CodingAgent::Codex,
generation,
Expand All @@ -1675,8 +1696,8 @@ pub(crate) fn codex_plugin_hook_command_for_platform(
generation: &Path,
generation_token: &str,
windows: bool,
) -> String {
crate::hooks::persistent_hook_forward_command_for_platform(
) -> crate::hooks::GeneratedHookCommands {
crate::hooks::persistent_hook_forward_commands_for_platform(
relay,
CodingAgent::Codex,
generation,
Expand Down
6 changes: 3 additions & 3 deletions crates/cli/src/agents/codex/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use serde_json::Value;
use crate::agents::CodingAgent;
use crate::configuration::{RELAY_PLUGIN_ID, RELAY_SOURCE_PLUGIN_ID};
use crate::error::CliError;
use crate::hooks::{generated_hooks, transparent_hook_forward_command};
use crate::hooks::{generated_policy_hooks, transparent_hook_forward_commands};
use crate::process::{PreparedAgentLaunch, insert_after_host};

pub(crate) fn prepare(launch: &mut PreparedAgentLaunch, gateway_url: &str) -> Result<(), CliError> {
Expand All @@ -26,13 +26,13 @@ pub(crate) fn prepare(launch: &mut PreparedAgentLaunch, gateway_url: &str) -> Re
or pass `--openai-base-url` to an upstream that needs no key."
);
}
let hook_command = transparent_hook_forward_command(
let hook_commands = transparent_hook_forward_commands(
&transparent_hook_executable(),
CodingAgent::Codex,
gateway_url,
)
.map_err(CliError::Launch)?;
let hook_groups = generated_hooks(CodingAgent::Codex, &hook_command);
let hook_groups = generated_policy_hooks(CodingAgent::Codex, &hook_commands);
let mut args = vec![
"--config".to_string(),
"features.hooks=true".to_string(),
Expand Down
Loading
Loading