mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Terminate timed-out hook process trees (#37527)
## Why Timing out a command hook can leave descendant processes running after the hook itself is stopped. ## What changed - Run hook commands in a process group on Unix and a job object on Windows. - Terminate the full process tree when hook execution is cancelled or fails, with `taskkill` as a Windows fallback. - Preserve descendants when a hook completes successfully so intentionally detached helpers can continue running. GitOrigin-RevId: 1a18653d34c6f1685ef8c4f8e8546663b276292a
This commit is contained in:
1 parent
2e3a1702c2
commit
dd916428cd
16 files changed
+223
-155
No files matched your search
Generated
+1
@@ -3291,6 +3291,7 @@ dependencies = [
|
|||||||
"codex-protocol",
|
"codex-protocol",
|
||||||
"codex-utils-absolute-path",
|
"codex-utils-absolute-path",
|
||||||
"codex-utils-output-truncation",
|
"codex-utils-output-truncation",
|
||||||
|
"codex-utils-pty",
|
||||||
"futures",
|
"futures",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"regex",
|
"regex",
|
||||||
|
|||||||
@@ -33,3 +33,6 @@ uuid = { workspace = true, features = ["v4"] }
|
|||||||
pretty_assertions = { workspace = true }
|
pretty_assertions = { workspace = true }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "time"] }
|
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "time"] }
|
||||||
|
|
||||||
|
[target.'cfg(any(unix, windows))'.dependencies]
|
||||||
|
codex-utils-pty = { workspace = true }
|
||||||
@@ -4,6 +4,8 @@ use std::process::Stdio;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
use codex_utils_pty::JobObject;
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
@@ -17,9 +19,30 @@ use super::dispatcher::hook_handler_type_label;
|
|||||||
use super::dispatcher::hook_scope_label;
|
use super::dispatcher::hook_scope_label;
|
||||||
use super::dispatcher::hook_source_label;
|
use super::dispatcher::hook_source_label;
|
||||||
use super::dispatcher::scope_for_event;
|
use super::dispatcher::scope_for_event;
|
||||||
|
use crate::output_spill::HookOutputSpiller;
|
||||||
use codex_protocol::protocol::HookExecutionMode;
|
use codex_protocol::protocol::HookExecutionMode;
|
||||||
use codex_protocol::protocol::HookHandlerType;
|
use codex_protocol::protocol::HookHandlerType;
|
||||||
|
|
||||||
|
/// Owns the shell and output spiller shared by a session's command hooks.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct CommandHookRuntime {
|
||||||
|
shell: CommandShell,
|
||||||
|
output_spiller: HookOutputSpiller,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandHookRuntime {
|
||||||
|
pub(crate) fn new(shell: CommandShell) -> Self {
|
||||||
|
Self {
|
||||||
|
shell,
|
||||||
|
output_spiller: HookOutputSpiller::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn output_spiller(&self) -> &HookOutputSpiller {
|
||||||
|
&self.output_spiller
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct CommandRunResult {
|
pub(crate) struct CommandRunResult {
|
||||||
pub started_at: i64,
|
pub started_at: i64,
|
||||||
@@ -48,7 +71,7 @@ pub(crate) struct CommandRunResult {
|
|||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
pub(crate) async fn run_command(
|
pub(crate) async fn run_command(
|
||||||
shell: &CommandShell,
|
runtime: &CommandHookRuntime,
|
||||||
handler: &ConfiguredHandler,
|
handler: &ConfiguredHandler,
|
||||||
configured_order: usize,
|
configured_order: usize,
|
||||||
input_json: &str,
|
input_json: &str,
|
||||||
@@ -57,7 +80,7 @@ pub(crate) async fn run_command(
|
|||||||
let started_at = chrono::Utc::now().timestamp();
|
let started_at = chrono::Utc::now().timestamp();
|
||||||
let started = Instant::now();
|
let started = Instant::now();
|
||||||
|
|
||||||
let mut command = build_command(shell, handler);
|
let mut command = build_command(&runtime.shell, handler);
|
||||||
command
|
command
|
||||||
.current_dir(cwd)
|
.current_dir(cwd)
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
@@ -65,7 +88,27 @@ pub(crate) async fn run_command(
|
|||||||
.stderr(Stdio::piped())
|
.stderr(Stdio::piped())
|
||||||
.kill_on_drop(true);
|
.kill_on_drop(true);
|
||||||
|
|
||||||
let mut child = match command.spawn() {
|
#[cfg(unix)]
|
||||||
|
command.process_group(0);
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let mut process_tree_job = JobObject::create().ok();
|
||||||
|
#[cfg(windows)]
|
||||||
|
let child = match process_tree_job.as_ref() {
|
||||||
|
Some(job) => match job.spawn_contained(&mut command) {
|
||||||
|
Ok(child) => Ok(child),
|
||||||
|
Err(_) => {
|
||||||
|
process_tree_job = None;
|
||||||
|
command.creation_flags(0);
|
||||||
|
command.spawn()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => command.spawn(),
|
||||||
|
};
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let child = command.spawn();
|
||||||
|
|
||||||
|
let mut child = match child {
|
||||||
Ok(child) => child,
|
Ok(child) => child,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return finish_command_run(
|
return finish_command_run(
|
||||||
@@ -82,6 +125,12 @@ pub(crate) async fn run_command(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut process_tree_guard = ProcessTreeGuard {
|
||||||
|
process_id: child.id(),
|
||||||
|
#[cfg(windows)]
|
||||||
|
job: process_tree_job,
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(mut stdin) = child.stdin.take()
|
if let Some(mut stdin) = child.stdin.take()
|
||||||
&& let Err(err) = stdin.write_all(input_json.as_bytes()).await
|
&& let Err(err) = stdin.write_all(input_json.as_bytes()).await
|
||||||
&& err.kind() != ErrorKind::BrokenPipe
|
&& err.kind() != ErrorKind::BrokenPipe
|
||||||
@@ -102,17 +151,25 @@ pub(crate) async fn run_command(
|
|||||||
|
|
||||||
let timeout_duration = Duration::from_secs(handler.timeout_sec);
|
let timeout_duration = Duration::from_secs(handler.timeout_sec);
|
||||||
match timeout(timeout_duration, child.wait_with_output()).await {
|
match timeout(timeout_duration, child.wait_with_output()).await {
|
||||||
Ok(Ok(output)) => finish_command_run(
|
Ok(Ok(output)) => {
|
||||||
started_at,
|
// Successfully completed hooks may intentionally leave detached helpers running.
|
||||||
started,
|
#[cfg(windows)]
|
||||||
CommandRunCompletion {
|
if let Some(job) = process_tree_guard.job.as_ref() {
|
||||||
exit_code: output.status.code(),
|
let _ = job.preserve_descendants();
|
||||||
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
|
}
|
||||||
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
|
process_tree_guard.process_id = None;
|
||||||
error: None,
|
finish_command_run(
|
||||||
outcome: "completed",
|
started_at,
|
||||||
},
|
started,
|
||||||
),
|
CommandRunCompletion {
|
||||||
|
exit_code: output.status.code(),
|
||||||
|
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
|
||||||
|
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
|
||||||
|
error: None,
|
||||||
|
outcome: "completed",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
Ok(Err(err)) => finish_command_run(
|
Ok(Err(err)) => finish_command_run(
|
||||||
started_at,
|
started_at,
|
||||||
started,
|
started,
|
||||||
@@ -138,6 +195,40 @@ pub(crate) async fn run_command(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Needed only until command hooks move to the exec server, which owns process-tree cleanup.
|
||||||
|
struct ProcessTreeGuard {
|
||||||
|
process_id: Option<u32>,
|
||||||
|
#[cfg(windows)]
|
||||||
|
job: Option<JobObject>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for ProcessTreeGuard {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let Some(process_id) = self.process_id else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
let _ = codex_utils_pty::process_group::kill_process_group(process_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
if let Some(job) = self.job.as_ref() {
|
||||||
|
let _ = job.terminate();
|
||||||
|
} else {
|
||||||
|
let _ = std::process::Command::new("taskkill")
|
||||||
|
.args(["/PID", &process_id.to_string(), "/T", "/F"])
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.spawn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct CommandRunCompletion {
|
struct CommandRunCompletion {
|
||||||
exit_code: Option<i32>,
|
exit_code: Option<i32>,
|
||||||
stdout: String,
|
stdout: String,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use codex_utils_absolute_path::AbsolutePathBuf;
|
|||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
use super::CommandHookRuntime;
|
||||||
use super::CommandShell;
|
use super::CommandShell;
|
||||||
use super::ConfiguredHandler;
|
use super::ConfiguredHandler;
|
||||||
use super::run_command;
|
use super::run_command;
|
||||||
@@ -51,7 +52,7 @@ async fn cmd_shell_runs_quoted_hook_command_path() {
|
|||||||
|
|
||||||
for shell in shells {
|
for shell in shells {
|
||||||
let result = run_command(
|
let result = run_command(
|
||||||
&shell,
|
&CommandHookRuntime::new(shell),
|
||||||
&handler,
|
&handler,
|
||||||
/*configured_order*/ 0,
|
/*configured_order*/ 0,
|
||||||
"{}",
|
"{}",
|
||||||
@@ -89,7 +90,7 @@ async fn fast_exiting_hook_preserves_stdout_when_stdin_is_not_consumed() {
|
|||||||
let input_json = format!(r#"{{"padding":"{}"}}"#, "x".repeat(1024 * 1024));
|
let input_json = format!(r#"{{"padding":"{}"}}"#, "x".repeat(1024 * 1024));
|
||||||
|
|
||||||
let result = run_command(
|
let result = run_command(
|
||||||
&shell,
|
&CommandHookRuntime::new(shell),
|
||||||
&handler,
|
&handler,
|
||||||
/*configured_order*/ 0,
|
/*configured_order*/ 0,
|
||||||
&input_json,
|
&input_json,
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ use codex_protocol::protocol::HookRunStatus;
|
|||||||
use codex_protocol::protocol::HookRunSummary;
|
use codex_protocol::protocol::HookRunSummary;
|
||||||
use codex_protocol::protocol::HookScope;
|
use codex_protocol::protocol::HookScope;
|
||||||
|
|
||||||
use super::CommandShell;
|
|
||||||
use super::ConfiguredHandler;
|
use super::ConfiguredHandler;
|
||||||
|
use super::command_runner::CommandHookRuntime;
|
||||||
use super::command_runner::CommandRunResult;
|
use super::command_runner::CommandRunResult;
|
||||||
use super::command_runner::run_command;
|
use super::command_runner::run_command;
|
||||||
use crate::events::common::matches_matcher;
|
use crate::events::common::matches_matcher;
|
||||||
@@ -88,7 +88,7 @@ pub(crate) fn running_summary(handler: &ConfiguredHandler) -> HookRunSummary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn execute_handlers<T>(
|
pub(crate) async fn execute_handlers<T>(
|
||||||
shell: &CommandShell,
|
runtime: &CommandHookRuntime,
|
||||||
handlers: Vec<ConfiguredHandler>,
|
handlers: Vec<ConfiguredHandler>,
|
||||||
input_json: String,
|
input_json: String,
|
||||||
cwd: &Path,
|
cwd: &Path,
|
||||||
@@ -100,7 +100,7 @@ pub(crate) async fn execute_handlers<T>(
|
|||||||
let input_json = input_json.clone();
|
let input_json = input_json.clone();
|
||||||
let turn_id = turn_id.clone();
|
let turn_id = turn_id.clone();
|
||||||
pending.push(async move {
|
pending.push(async move {
|
||||||
let result = run_command(shell, &handler, configured_order, &input_json, cwd).await;
|
let result = run_command(runtime, &handler, configured_order, &input_json, cwd).await;
|
||||||
(configured_order, parse(&handler, result, turn_id))
|
(configured_order, parse(&handler, result, turn_id))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,10 +23,8 @@ use crate::events::stop::StopRequest;
|
|||||||
use crate::events::user_prompt_submit::UserPromptSubmitOutcome;
|
use crate::events::user_prompt_submit::UserPromptSubmitOutcome;
|
||||||
use crate::events::user_prompt_submit::UserPromptSubmitRequest;
|
use crate::events::user_prompt_submit::UserPromptSubmitRequest;
|
||||||
use crate::output_spill::AdditionalContextLimit;
|
use crate::output_spill::AdditionalContextLimit;
|
||||||
use crate::output_spill::HookOutputSpiller;
|
|
||||||
use codex_config::ConfigLayerStack;
|
use codex_config::ConfigLayerStack;
|
||||||
use codex_plugin::PluginHookSource;
|
use codex_plugin::PluginHookSource;
|
||||||
use codex_protocol::ThreadId;
|
|
||||||
use codex_protocol::protocol::HookEventName;
|
use codex_protocol::protocol::HookEventName;
|
||||||
use codex_protocol::protocol::HookHandlerType;
|
use codex_protocol::protocol::HookHandlerType;
|
||||||
use codex_protocol::protocol::HookRunSummary;
|
use codex_protocol::protocol::HookRunSummary;
|
||||||
@@ -36,6 +34,8 @@ use codex_utils_absolute_path::AbsolutePathBuf;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use command_runner::CommandHookRuntime;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct CommandShell {
|
pub(crate) struct CommandShell {
|
||||||
pub program: String,
|
pub program: String,
|
||||||
@@ -107,8 +107,7 @@ pub struct HookListEntry {
|
|||||||
pub(crate) struct ClaudeHooksEngine {
|
pub(crate) struct ClaudeHooksEngine {
|
||||||
handlers: Vec<ConfiguredHandler>,
|
handlers: Vec<ConfiguredHandler>,
|
||||||
warnings: Vec<String>,
|
warnings: Vec<String>,
|
||||||
shell: CommandShell,
|
command_runtime: CommandHookRuntime,
|
||||||
output_spiller: HookOutputSpiller,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClaudeHooksEngine {
|
impl ClaudeHooksEngine {
|
||||||
@@ -118,14 +117,13 @@ impl ClaudeHooksEngine {
|
|||||||
config_layer_stack: Option<&ConfigLayerStack>,
|
config_layer_stack: Option<&ConfigLayerStack>,
|
||||||
plugin_hook_sources: Vec<PluginHookSource>,
|
plugin_hook_sources: Vec<PluginHookSource>,
|
||||||
plugin_hook_load_warnings: Vec<String>,
|
plugin_hook_load_warnings: Vec<String>,
|
||||||
shell: CommandShell,
|
command_runtime: CommandHookRuntime,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
if !enabled {
|
if !enabled {
|
||||||
return Self {
|
return Self {
|
||||||
handlers: Vec::new(),
|
handlers: Vec::new(),
|
||||||
warnings: Vec::new(),
|
warnings: Vec::new(),
|
||||||
shell,
|
command_runtime,
|
||||||
output_spiller: HookOutputSpiller::new(),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,11 +134,11 @@ impl ClaudeHooksEngine {
|
|||||||
plugin_hook_load_warnings,
|
plugin_hook_load_warnings,
|
||||||
bypass_hook_trust,
|
bypass_hook_trust,
|
||||||
);
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
handlers: discovered.handlers,
|
handlers: discovered.handlers,
|
||||||
warnings: discovered.warnings,
|
warnings: discovered.warnings,
|
||||||
shell,
|
command_runtime,
|
||||||
output_spiller: HookOutputSpiller::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,26 +187,19 @@ impl ClaudeHooksEngine {
|
|||||||
request: SessionStartRequest,
|
request: SessionStartRequest,
|
||||||
turn_id: Option<String>,
|
turn_id: Option<String>,
|
||||||
) -> SessionStartOutcome {
|
) -> SessionStartOutcome {
|
||||||
crate::events::session_start::run(
|
crate::events::session_start::run(&self.handlers, &self.command_runtime, request, turn_id)
|
||||||
&self.handlers,
|
.await
|
||||||
&self.shell,
|
|
||||||
&self.output_spiller,
|
|
||||||
request,
|
|
||||||
turn_id,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn run_pre_tool_use(&self, request: PreToolUseRequest) -> PreToolUseOutcome {
|
pub(crate) async fn run_pre_tool_use(&self, request: PreToolUseRequest) -> PreToolUseOutcome {
|
||||||
crate::events::pre_tool_use::run(&self.handlers, &self.shell, &self.output_spiller, request)
|
crate::events::pre_tool_use::run(&self.handlers, &self.command_runtime, request).await
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn run_permission_request(
|
pub(crate) async fn run_permission_request(
|
||||||
&self,
|
&self,
|
||||||
request: PermissionRequestRequest,
|
request: PermissionRequestRequest,
|
||||||
) -> PermissionRequestOutcome {
|
) -> PermissionRequestOutcome {
|
||||||
crate::events::permission_request::run(&self.handlers, &self.shell, request).await
|
crate::events::permission_request::run(&self.handlers, &self.command_runtime, request).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn run_post_tool_use(
|
pub(crate) async fn run_post_tool_use(
|
||||||
@@ -216,16 +207,16 @@ impl ClaudeHooksEngine {
|
|||||||
request: PostToolUseRequest,
|
request: PostToolUseRequest,
|
||||||
) -> PostToolUseOutcome {
|
) -> PostToolUseOutcome {
|
||||||
let session_id = request.session_id;
|
let session_id = request.session_id;
|
||||||
let mut outcome = crate::events::post_tool_use::run(
|
let mut outcome =
|
||||||
&self.handlers,
|
crate::events::post_tool_use::run(&self.handlers, &self.command_runtime, request).await;
|
||||||
&self.shell,
|
if let Some(feedback_message) = outcome.feedback_message.take() {
|
||||||
&self.output_spiller,
|
outcome.feedback_message = Some(
|
||||||
request,
|
self.command_runtime
|
||||||
)
|
.output_spiller()
|
||||||
.await;
|
.maybe_spill_text(session_id, feedback_message)
|
||||||
outcome.feedback_message = self
|
.await,
|
||||||
.maybe_spill_text(session_id, outcome.feedback_message)
|
);
|
||||||
.await;
|
}
|
||||||
outcome
|
outcome
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +225,7 @@ impl ClaudeHooksEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn run_pre_compact(&self, request: PreCompactRequest) -> PreCompactOutcome {
|
pub(crate) async fn run_pre_compact(&self, request: PreCompactRequest) -> PreCompactOutcome {
|
||||||
crate::events::compact::run_pre(&self.handlers, &self.shell, request).await
|
crate::events::compact::run_pre(&self.handlers, &self.command_runtime, request).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn preview_post_compact(&self, request: &PostCompactRequest) -> Vec<HookRunSummary> {
|
pub(crate) fn preview_post_compact(&self, request: &PostCompactRequest) -> Vec<HookRunSummary> {
|
||||||
@@ -245,7 +236,7 @@ impl ClaudeHooksEngine {
|
|||||||
&self,
|
&self,
|
||||||
request: PostCompactRequest,
|
request: PostCompactRequest,
|
||||||
) -> StatelessHookOutcome {
|
) -> StatelessHookOutcome {
|
||||||
crate::events::compact::run_post(&self.handlers, &self.shell, request).await
|
crate::events::compact::run_post(&self.handlers, &self.command_runtime, request).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn preview_user_prompt_submit(
|
pub(crate) fn preview_user_prompt_submit(
|
||||||
@@ -259,13 +250,7 @@ impl ClaudeHooksEngine {
|
|||||||
&self,
|
&self,
|
||||||
request: UserPromptSubmitRequest,
|
request: UserPromptSubmitRequest,
|
||||||
) -> UserPromptSubmitOutcome {
|
) -> UserPromptSubmitOutcome {
|
||||||
crate::events::user_prompt_submit::run(
|
crate::events::user_prompt_submit::run(&self.handlers, &self.command_runtime, request).await
|
||||||
&self.handlers,
|
|
||||||
&self.shell,
|
|
||||||
&self.output_spiller,
|
|
||||||
request,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn preview_stop(&self, request: &StopRequest) -> Vec<HookRunSummary> {
|
pub(crate) fn preview_stop(&self, request: &StopRequest) -> Vec<HookRunSummary> {
|
||||||
@@ -277,34 +262,20 @@ impl ClaudeHooksEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn run_session_end(&self, request: SessionEndRequest) -> SessionEndOutcome {
|
pub(crate) async fn run_session_end(&self, request: SessionEndRequest) -> SessionEndOutcome {
|
||||||
crate::events::session_end::run(&self.handlers, &self.shell, request).await
|
crate::events::session_end::run(&self.handlers, &self.command_runtime, request).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn run_stop(&self, request: StopRequest) -> StopOutcome {
|
pub(crate) async fn run_stop(&self, request: StopRequest) -> StopOutcome {
|
||||||
let session_id = request.session_id;
|
let session_id = request.session_id;
|
||||||
let mut outcome = crate::events::stop::run(&self.handlers, &self.shell, request).await;
|
let mut outcome =
|
||||||
|
crate::events::stop::run(&self.handlers, &self.command_runtime, request).await;
|
||||||
outcome.continuation_fragments = self
|
outcome.continuation_fragments = self
|
||||||
|
.command_runtime
|
||||||
|
.output_spiller()
|
||||||
.maybe_spill_prompt_fragments(session_id, outcome.continuation_fragments)
|
.maybe_spill_prompt_fragments(session_id, outcome.continuation_fragments)
|
||||||
.await;
|
.await;
|
||||||
outcome
|
outcome
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn maybe_spill_text(&self, session_id: ThreadId, text: Option<String>) -> Option<String> {
|
|
||||||
match text {
|
|
||||||
Some(text) => Some(self.output_spiller.maybe_spill_text(session_id, text).await),
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn maybe_spill_prompt_fragments(
|
|
||||||
&self,
|
|
||||||
session_id: ThreadId,
|
|
||||||
fragments: Vec<codex_protocol::items::HookPromptFragment>,
|
|
||||||
) -> Vec<codex_protocol::items::HookPromptFragment> {
|
|
||||||
self.output_spiller
|
|
||||||
.maybe_spill_prompt_fragments(session_id, fragments)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ use pretty_assertions::assert_eq;
|
|||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
|
||||||
use super::ClaudeHooksEngine;
|
use super::ClaudeHooksEngine;
|
||||||
|
use super::CommandHookRuntime;
|
||||||
use super::CommandShell;
|
use super::CommandShell;
|
||||||
use crate::events::pre_tool_use::PreToolUseRequest;
|
use crate::events::pre_tool_use::PreToolUseRequest;
|
||||||
|
|
||||||
@@ -202,10 +203,10 @@ with Path(r"{log_path}").open("a", encoding="utf-8") as handle:
|
|||||||
Some(&config_layer_stack),
|
Some(&config_layer_stack),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
CommandShell {
|
CommandHookRuntime::new(CommandShell {
|
||||||
program: String::new(),
|
program: String::new(),
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
},
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(engine.warnings().is_empty());
|
assert!(engine.warnings().is_empty());
|
||||||
@@ -309,10 +310,10 @@ async fn requirements_managed_hooks_execute_windows_command_override() {
|
|||||||
Some(&config_layer_stack),
|
Some(&config_layer_stack),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
CommandShell {
|
CommandHookRuntime::new(CommandShell {
|
||||||
program: String::new(),
|
program: String::new(),
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
},
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
let outcome = engine
|
let outcome = engine
|
||||||
@@ -389,10 +390,10 @@ fn unknown_requirement_source_hooks_stay_managed() {
|
|||||||
Some(&config_layer_stack),
|
Some(&config_layer_stack),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
CommandShell {
|
CommandHookRuntime::new(CommandShell {
|
||||||
program: String::new(),
|
program: String::new(),
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
},
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(engine.handlers.len(), 1);
|
assert_eq!(engine.handlers.len(), 1);
|
||||||
@@ -472,10 +473,10 @@ fn user_disablement_filters_non_managed_hooks_but_not_managed_hooks() {
|
|||||||
Some(&config_layer_stack),
|
Some(&config_layer_stack),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
CommandShell {
|
CommandHookRuntime::new(CommandShell {
|
||||||
program: String::new(),
|
program: String::new(),
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
},
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(engine.handlers.len(), 1);
|
assert_eq!(engine.handlers.len(), 1);
|
||||||
@@ -538,10 +539,10 @@ fn user_disablement_does_not_filter_managed_layer_hooks() {
|
|||||||
Some(&config_layer_stack),
|
Some(&config_layer_stack),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
CommandShell {
|
CommandHookRuntime::new(CommandShell {
|
||||||
program: String::new(),
|
program: String::new(),
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
},
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(engine.handlers.len(), 1);
|
assert_eq!(engine.handlers.len(), 1);
|
||||||
@@ -700,10 +701,10 @@ fn requirements_managed_hooks_load_when_managed_dir_is_missing() {
|
|||||||
Some(&config_layer_stack),
|
Some(&config_layer_stack),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
CommandShell {
|
CommandHookRuntime::new(CommandShell {
|
||||||
program: String::new(),
|
program: String::new(),
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
},
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(engine.warnings().is_empty());
|
assert!(engine.warnings().is_empty());
|
||||||
@@ -756,10 +757,10 @@ fn allow_managed_hooks_only_false_keeps_unmanaged_hooks() {
|
|||||||
Some(&config_layer_stack),
|
Some(&config_layer_stack),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
CommandShell {
|
CommandHookRuntime::new(CommandShell {
|
||||||
program: String::new(),
|
program: String::new(),
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
},
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(engine.warnings().is_empty());
|
assert!(engine.warnings().is_empty());
|
||||||
@@ -810,10 +811,10 @@ fn allow_managed_hooks_only_in_config_toml_does_not_enable_policy() {
|
|||||||
Some(&config_layer_stack),
|
Some(&config_layer_stack),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
CommandShell {
|
CommandHookRuntime::new(CommandShell {
|
||||||
program: String::new(),
|
program: String::new(),
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
},
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(engine.warnings().is_empty());
|
assert!(engine.warnings().is_empty());
|
||||||
@@ -880,10 +881,10 @@ fn allow_managed_hooks_only_skips_unmanaged_json_and_toml_hooks() {
|
|||||||
Some(&config_layer_stack),
|
Some(&config_layer_stack),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
CommandShell {
|
CommandHookRuntime::new(CommandShell {
|
||||||
program: String::new(),
|
program: String::new(),
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
},
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(engine.handlers.is_empty());
|
assert!(engine.handlers.is_empty());
|
||||||
@@ -919,10 +920,10 @@ fn allow_managed_hooks_only_skips_unmanaged_plugin_hooks() {
|
|||||||
Some(&config_layer_stack),
|
Some(&config_layer_stack),
|
||||||
plugin_hook_sources,
|
plugin_hook_sources,
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
CommandShell {
|
CommandHookRuntime::new(CommandShell {
|
||||||
program: String::new(),
|
program: String::new(),
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
},
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(engine.handlers.is_empty());
|
assert!(engine.handlers.is_empty());
|
||||||
@@ -991,10 +992,10 @@ fn allow_managed_hooks_only_keeps_managed_requirement_and_config_layer_hooks() {
|
|||||||
Some(&config_layer_stack),
|
Some(&config_layer_stack),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
CommandShell {
|
CommandHookRuntime::new(CommandShell {
|
||||||
program: String::new(),
|
program: String::new(),
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
},
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(engine.warnings().is_empty());
|
assert!(engine.warnings().is_empty());
|
||||||
@@ -1101,10 +1102,10 @@ fn discovers_hooks_from_json_and_toml_in_the_same_layer() {
|
|||||||
Some(&config_layer_stack),
|
Some(&config_layer_stack),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
CommandShell {
|
CommandHookRuntime::new(CommandShell {
|
||||||
program: String::new(),
|
program: String::new(),
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
},
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(engine.warnings().iter().any(|warning| {
|
assert!(engine.warnings().iter().any(|warning| {
|
||||||
@@ -1196,10 +1197,10 @@ fn profile_user_layers_load_shared_hooks_json_once() {
|
|||||||
Some(&config_layer_stack),
|
Some(&config_layer_stack),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
CommandShell {
|
CommandHookRuntime::new(CommandShell {
|
||||||
program: String::new(),
|
program: String::new(),
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
},
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(engine.warnings().is_empty());
|
assert!(engine.warnings().is_empty());
|
||||||
@@ -1270,10 +1271,10 @@ fn malformed_hooks_json_is_reported_as_startup_warning() {
|
|||||||
Some(&config_layer_stack),
|
Some(&config_layer_stack),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
CommandShell {
|
CommandHookRuntime::new(CommandShell {
|
||||||
program: String::new(),
|
program: String::new(),
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
},
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(engine.handlers.is_empty());
|
assert!(engine.handlers.is_empty());
|
||||||
@@ -1342,10 +1343,10 @@ print(json.dumps({
|
|||||||
Some(&config_layer_stack),
|
Some(&config_layer_stack),
|
||||||
plugin_hook_sources.clone(),
|
plugin_hook_sources.clone(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
CommandShell {
|
CommandHookRuntime::new(CommandShell {
|
||||||
program: String::new(),
|
program: String::new(),
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
},
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
let preview = engine.preview_pre_tool_use(&PreToolUseRequest {
|
let preview = engine.preview_pre_tool_use(&PreToolUseRequest {
|
||||||
@@ -1462,10 +1463,10 @@ fn plugin_hook_sources_expand_plugin_placeholders() {
|
|||||||
Some(&config_layer_stack),
|
Some(&config_layer_stack),
|
||||||
plugin_hook_sources,
|
plugin_hook_sources,
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
CommandShell {
|
CommandHookRuntime::new(CommandShell {
|
||||||
program: String::new(),
|
program: String::new(),
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
},
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1506,10 +1507,10 @@ fn plugin_hook_load_warnings_are_startup_warnings() {
|
|||||||
/*config_layer_stack*/ None,
|
/*config_layer_stack*/ None,
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
vec!["failed plugin hook".to_string()],
|
vec!["failed plugin hook".to_string()],
|
||||||
CommandShell {
|
CommandHookRuntime::new(CommandShell {
|
||||||
program: String::new(),
|
program: String::new(),
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
},
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(engine.warnings(), &["failed plugin hook".to_string()]);
|
assert_eq!(engine.warnings(), &["failed plugin hook".to_string()]);
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ use codex_protocol::protocol::HookRunSummary;
|
|||||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||||
|
|
||||||
use super::common;
|
use super::common;
|
||||||
use crate::engine::CommandShell;
|
|
||||||
use crate::engine::ConfiguredHandler;
|
use crate::engine::ConfiguredHandler;
|
||||||
|
use crate::engine::command_runner::CommandHookRuntime;
|
||||||
use crate::engine::command_runner::CommandRunResult;
|
use crate::engine::command_runner::CommandRunResult;
|
||||||
use crate::engine::dispatcher;
|
use crate::engine::dispatcher;
|
||||||
use crate::engine::output_parser;
|
use crate::engine::output_parser;
|
||||||
@@ -71,7 +71,7 @@ pub(crate) fn preview_pre(
|
|||||||
|
|
||||||
pub(crate) async fn run_pre(
|
pub(crate) async fn run_pre(
|
||||||
handlers: &[ConfiguredHandler],
|
handlers: &[ConfiguredHandler],
|
||||||
shell: &CommandShell,
|
runtime: &CommandHookRuntime,
|
||||||
request: PreCompactRequest,
|
request: PreCompactRequest,
|
||||||
) -> PreCompactOutcome {
|
) -> PreCompactOutcome {
|
||||||
let matched = dispatcher::select_handlers(
|
let matched = dispatcher::select_handlers(
|
||||||
@@ -103,7 +103,7 @@ pub(crate) async fn run_pre(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let results = dispatcher::execute_handlers(
|
let results = dispatcher::execute_handlers(
|
||||||
shell,
|
runtime,
|
||||||
matched,
|
matched,
|
||||||
input_json,
|
input_json,
|
||||||
request.cwd.as_path(),
|
request.cwd.as_path(),
|
||||||
@@ -153,7 +153,7 @@ pub(crate) fn preview_post(
|
|||||||
|
|
||||||
pub(crate) async fn run_post(
|
pub(crate) async fn run_post(
|
||||||
handlers: &[ConfiguredHandler],
|
handlers: &[ConfiguredHandler],
|
||||||
shell: &CommandShell,
|
runtime: &CommandHookRuntime,
|
||||||
request: PostCompactRequest,
|
request: PostCompactRequest,
|
||||||
) -> StatelessHookOutcome {
|
) -> StatelessHookOutcome {
|
||||||
let matched = dispatcher::select_handlers(
|
let matched = dispatcher::select_handlers(
|
||||||
@@ -185,7 +185,7 @@ pub(crate) async fn run_post(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let results = dispatcher::execute_handlers(
|
let results = dispatcher::execute_handlers(
|
||||||
shell,
|
runtime,
|
||||||
matched,
|
matched,
|
||||||
input_json,
|
input_json,
|
||||||
request.cwd.as_path(),
|
request.cwd.as_path(),
|
||||||
@@ -436,7 +436,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pre_compact_input_includes_lifecycle_metadata() {
|
fn pre_compact_input_includes_lifecycle_metadata() {
|
||||||
let input_json = pre_command_input_json(&pre_request()).expect("serialize command input");
|
let request = pre_request();
|
||||||
|
let input_json = pre_command_input_json(&request).expect("serialize command input");
|
||||||
let input: serde_json::Value =
|
let input: serde_json::Value =
|
||||||
serde_json::from_str(&input_json).expect("parse command input");
|
serde_json::from_str(&input_json).expect("parse command input");
|
||||||
|
|
||||||
@@ -456,7 +457,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn post_compact_input_includes_lifecycle_metadata() {
|
fn post_compact_input_includes_lifecycle_metadata() {
|
||||||
let input_json = post_command_input_json(&post_request()).expect("serialize command input");
|
let request = post_request();
|
||||||
|
let input_json = post_command_input_json(&request).expect("serialize command input");
|
||||||
let input: serde_json::Value =
|
let input: serde_json::Value =
|
||||||
serde_json::from_str(&input_json).expect("parse command input");
|
serde_json::from_str(&input_json).expect("parse command input");
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use super::common;
|
use super::common;
|
||||||
use crate::engine::CommandShell;
|
|
||||||
use crate::engine::ConfiguredHandler;
|
use crate::engine::ConfiguredHandler;
|
||||||
|
use crate::engine::command_runner::CommandHookRuntime;
|
||||||
use crate::engine::command_runner::CommandRunResult;
|
use crate::engine::command_runner::CommandRunResult;
|
||||||
use crate::engine::dispatcher;
|
use crate::engine::dispatcher;
|
||||||
use crate::engine::output_parser;
|
use crate::engine::output_parser;
|
||||||
@@ -86,7 +86,7 @@ pub(crate) fn preview(
|
|||||||
|
|
||||||
pub(crate) async fn run(
|
pub(crate) async fn run(
|
||||||
handlers: &[ConfiguredHandler],
|
handlers: &[ConfiguredHandler],
|
||||||
shell: &CommandShell,
|
runtime: &CommandHookRuntime,
|
||||||
request: PermissionRequestRequest,
|
request: PermissionRequestRequest,
|
||||||
) -> PermissionRequestOutcome {
|
) -> PermissionRequestOutcome {
|
||||||
let matcher_inputs = common::matcher_inputs(&request.tool_name, &request.matcher_aliases);
|
let matcher_inputs = common::matcher_inputs(&request.tool_name, &request.matcher_aliases);
|
||||||
@@ -119,7 +119,7 @@ pub(crate) async fn run(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let results = dispatcher::execute_handlers(
|
let results = dispatcher::execute_handlers(
|
||||||
shell,
|
runtime,
|
||||||
matched,
|
matched,
|
||||||
input_json,
|
input_json,
|
||||||
request.cwd.as_path(),
|
request.cwd.as_path(),
|
||||||
|
|||||||
@@ -11,13 +11,12 @@ use codex_utils_absolute_path::AbsolutePathBuf;
|
|||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use super::common;
|
use super::common;
|
||||||
use crate::engine::CommandShell;
|
|
||||||
use crate::engine::ConfiguredHandler;
|
use crate::engine::ConfiguredHandler;
|
||||||
|
use crate::engine::command_runner::CommandHookRuntime;
|
||||||
use crate::engine::command_runner::CommandRunResult;
|
use crate::engine::command_runner::CommandRunResult;
|
||||||
use crate::engine::dispatcher;
|
use crate::engine::dispatcher;
|
||||||
use crate::engine::output_parser;
|
use crate::engine::output_parser;
|
||||||
use crate::output_spill::AdditionalContext;
|
use crate::output_spill::AdditionalContext;
|
||||||
use crate::output_spill::HookOutputSpiller;
|
|
||||||
use crate::schema::PostToolUseCommandInput;
|
use crate::schema::PostToolUseCommandInput;
|
||||||
use crate::schema::SubagentCommandInputFields;
|
use crate::schema::SubagentCommandInputFields;
|
||||||
|
|
||||||
@@ -71,8 +70,7 @@ pub(crate) fn preview(
|
|||||||
|
|
||||||
pub(crate) async fn run(
|
pub(crate) async fn run(
|
||||||
handlers: &[ConfiguredHandler],
|
handlers: &[ConfiguredHandler],
|
||||||
shell: &CommandShell,
|
runtime: &CommandHookRuntime,
|
||||||
output_spiller: &HookOutputSpiller,
|
|
||||||
request: PostToolUseRequest,
|
request: PostToolUseRequest,
|
||||||
) -> PostToolUseOutcome {
|
) -> PostToolUseOutcome {
|
||||||
let session_id = request.session_id;
|
let session_id = request.session_id;
|
||||||
@@ -105,7 +103,7 @@ pub(crate) async fn run(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let results = dispatcher::execute_handlers(
|
let results = dispatcher::execute_handlers(
|
||||||
shell,
|
runtime,
|
||||||
matched,
|
matched,
|
||||||
input_json,
|
input_json,
|
||||||
request.cwd.as_path(),
|
request.cwd.as_path(),
|
||||||
@@ -119,7 +117,8 @@ pub(crate) async fn run(
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|result| result.data.additional_contexts_for_model.as_slice()),
|
.map(|result| result.data.additional_contexts_for_model.as_slice()),
|
||||||
);
|
);
|
||||||
let additional_contexts = output_spiller
|
let additional_contexts = runtime
|
||||||
|
.output_spiller()
|
||||||
.maybe_spill_additional_contexts(session_id, additional_contexts)
|
.maybe_spill_additional_contexts(session_id, additional_contexts)
|
||||||
.await;
|
.await;
|
||||||
let should_block = results.iter().any(|result| result.data.should_block);
|
let should_block = results.iter().any(|result| result.data.should_block);
|
||||||
|
|||||||
@@ -11,13 +11,12 @@ use codex_utils_absolute_path::AbsolutePathBuf;
|
|||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use super::common;
|
use super::common;
|
||||||
use crate::engine::CommandShell;
|
|
||||||
use crate::engine::ConfiguredHandler;
|
use crate::engine::ConfiguredHandler;
|
||||||
|
use crate::engine::command_runner::CommandHookRuntime;
|
||||||
use crate::engine::command_runner::CommandRunResult;
|
use crate::engine::command_runner::CommandRunResult;
|
||||||
use crate::engine::dispatcher;
|
use crate::engine::dispatcher;
|
||||||
use crate::engine::output_parser;
|
use crate::engine::output_parser;
|
||||||
use crate::output_spill::AdditionalContext;
|
use crate::output_spill::AdditionalContext;
|
||||||
use crate::output_spill::HookOutputSpiller;
|
|
||||||
use crate::schema::PreToolUseCommandInput;
|
use crate::schema::PreToolUseCommandInput;
|
||||||
use crate::schema::SubagentCommandInputFields;
|
use crate::schema::SubagentCommandInputFields;
|
||||||
|
|
||||||
@@ -72,8 +71,7 @@ pub(crate) fn preview(
|
|||||||
|
|
||||||
pub(crate) async fn run(
|
pub(crate) async fn run(
|
||||||
handlers: &[ConfiguredHandler],
|
handlers: &[ConfiguredHandler],
|
||||||
shell: &CommandShell,
|
runtime: &CommandHookRuntime,
|
||||||
output_spiller: &HookOutputSpiller,
|
|
||||||
request: PreToolUseRequest,
|
request: PreToolUseRequest,
|
||||||
) -> PreToolUseOutcome {
|
) -> PreToolUseOutcome {
|
||||||
let session_id = request.session_id;
|
let session_id = request.session_id;
|
||||||
@@ -107,7 +105,7 @@ pub(crate) async fn run(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let results = dispatcher::execute_handlers(
|
let results = dispatcher::execute_handlers(
|
||||||
shell,
|
runtime,
|
||||||
matched,
|
matched,
|
||||||
input_json,
|
input_json,
|
||||||
request.cwd.as_path(),
|
request.cwd.as_path(),
|
||||||
@@ -125,7 +123,8 @@ pub(crate) async fn run(
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|result| result.data.additional_contexts_for_model.as_slice()),
|
.map(|result| result.data.additional_contexts_for_model.as_slice()),
|
||||||
);
|
);
|
||||||
let additional_contexts = output_spiller
|
let additional_contexts = runtime
|
||||||
|
.output_spiller()
|
||||||
.maybe_spill_additional_contexts(session_id, additional_contexts)
|
.maybe_spill_additional_contexts(session_id, additional_contexts)
|
||||||
.await;
|
.await;
|
||||||
let updated_input = if should_block {
|
let updated_input = if should_block {
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ use codex_protocol::protocol::HookRunSummary;
|
|||||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||||
|
|
||||||
use super::common;
|
use super::common;
|
||||||
use crate::engine::CommandShell;
|
|
||||||
use crate::engine::ConfiguredHandler;
|
use crate::engine::ConfiguredHandler;
|
||||||
|
use crate::engine::command_runner::CommandHookRuntime;
|
||||||
use crate::engine::command_runner::CommandRunResult;
|
use crate::engine::command_runner::CommandRunResult;
|
||||||
use crate::engine::dispatcher;
|
use crate::engine::dispatcher;
|
||||||
use crate::schema::NullableString;
|
use crate::schema::NullableString;
|
||||||
@@ -49,7 +49,7 @@ pub(crate) fn preview(handlers: &[ConfiguredHandler]) -> Vec<HookRunSummary> {
|
|||||||
|
|
||||||
pub(crate) async fn run(
|
pub(crate) async fn run(
|
||||||
handlers: &[ConfiguredHandler],
|
handlers: &[ConfiguredHandler],
|
||||||
shell: &CommandShell,
|
runtime: &CommandHookRuntime,
|
||||||
request: SessionEndRequest,
|
request: SessionEndRequest,
|
||||||
) -> SessionEndOutcome {
|
) -> SessionEndOutcome {
|
||||||
let matched = dispatcher::select_handlers(
|
let matched = dispatcher::select_handlers(
|
||||||
@@ -81,7 +81,7 @@ pub(crate) async fn run(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let results = dispatcher::execute_handlers(
|
let results = dispatcher::execute_handlers(
|
||||||
shell,
|
runtime,
|
||||||
matched,
|
matched,
|
||||||
input_json,
|
input_json,
|
||||||
request.cwd.as_path(),
|
request.cwd.as_path(),
|
||||||
|
|||||||
@@ -10,13 +10,12 @@ use codex_protocol::protocol::HookRunSummary;
|
|||||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||||
|
|
||||||
use super::common;
|
use super::common;
|
||||||
use crate::engine::CommandShell;
|
|
||||||
use crate::engine::ConfiguredHandler;
|
use crate::engine::ConfiguredHandler;
|
||||||
|
use crate::engine::command_runner::CommandHookRuntime;
|
||||||
use crate::engine::command_runner::CommandRunResult;
|
use crate::engine::command_runner::CommandRunResult;
|
||||||
use crate::engine::dispatcher;
|
use crate::engine::dispatcher;
|
||||||
use crate::engine::output_parser;
|
use crate::engine::output_parser;
|
||||||
use crate::output_spill::AdditionalContext;
|
use crate::output_spill::AdditionalContext;
|
||||||
use crate::output_spill::HookOutputSpiller;
|
|
||||||
use crate::schema::NullableString;
|
use crate::schema::NullableString;
|
||||||
use crate::schema::SessionStartCommandInput;
|
use crate::schema::SessionStartCommandInput;
|
||||||
use crate::schema::SubagentStartCommandInput;
|
use crate::schema::SubagentStartCommandInput;
|
||||||
@@ -109,8 +108,7 @@ pub(crate) fn preview(
|
|||||||
|
|
||||||
pub(crate) async fn run(
|
pub(crate) async fn run(
|
||||||
handlers: &[ConfiguredHandler],
|
handlers: &[ConfiguredHandler],
|
||||||
shell: &CommandShell,
|
runtime: &CommandHookRuntime,
|
||||||
output_spiller: &HookOutputSpiller,
|
|
||||||
request: SessionStartRequest,
|
request: SessionStartRequest,
|
||||||
turn_id: Option<String>,
|
turn_id: Option<String>,
|
||||||
) -> SessionStartOutcome {
|
) -> SessionStartOutcome {
|
||||||
@@ -185,7 +183,7 @@ pub(crate) async fn run(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let results = dispatcher::execute_handlers(
|
let results = dispatcher::execute_handlers(
|
||||||
shell,
|
runtime,
|
||||||
matched,
|
matched,
|
||||||
input_json,
|
input_json,
|
||||||
request.cwd.as_path(),
|
request.cwd.as_path(),
|
||||||
@@ -203,7 +201,8 @@ pub(crate) async fn run(
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|result| result.data.additional_contexts_for_model.as_slice()),
|
.map(|result| result.data.additional_contexts_for_model.as_slice()),
|
||||||
);
|
);
|
||||||
let additional_contexts = output_spiller
|
let additional_contexts = runtime
|
||||||
|
.output_spiller()
|
||||||
.maybe_spill_additional_contexts(session_id, additional_contexts)
|
.maybe_spill_additional_contexts(session_id, additional_contexts)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ use codex_protocol::protocol::HookRunSummary;
|
|||||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||||
|
|
||||||
use super::common;
|
use super::common;
|
||||||
use crate::engine::CommandShell;
|
|
||||||
use crate::engine::ConfiguredHandler;
|
use crate::engine::ConfiguredHandler;
|
||||||
|
use crate::engine::command_runner::CommandHookRuntime;
|
||||||
use crate::engine::command_runner::CommandRunResult;
|
use crate::engine::command_runner::CommandRunResult;
|
||||||
use crate::engine::dispatcher;
|
use crate::engine::dispatcher;
|
||||||
use crate::engine::output_parser;
|
use crate::engine::output_parser;
|
||||||
@@ -94,7 +94,7 @@ pub(crate) fn preview(
|
|||||||
|
|
||||||
pub(crate) async fn run(
|
pub(crate) async fn run(
|
||||||
handlers: &[ConfiguredHandler],
|
handlers: &[ConfiguredHandler],
|
||||||
shell: &CommandShell,
|
runtime: &CommandHookRuntime,
|
||||||
request: StopRequest,
|
request: StopRequest,
|
||||||
) -> StopOutcome {
|
) -> StopOutcome {
|
||||||
let matched = dispatcher::select_handlers(
|
let matched = dispatcher::select_handlers(
|
||||||
@@ -178,7 +178,7 @@ pub(crate) async fn run(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let results = dispatcher::execute_handlers(
|
let results = dispatcher::execute_handlers(
|
||||||
shell,
|
runtime,
|
||||||
matched,
|
matched,
|
||||||
input_json,
|
input_json,
|
||||||
request.cwd.as_path(),
|
request.cwd.as_path(),
|
||||||
|
|||||||
@@ -10,13 +10,12 @@ use codex_protocol::protocol::HookRunSummary;
|
|||||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||||
|
|
||||||
use super::common;
|
use super::common;
|
||||||
use crate::engine::CommandShell;
|
|
||||||
use crate::engine::ConfiguredHandler;
|
use crate::engine::ConfiguredHandler;
|
||||||
|
use crate::engine::command_runner::CommandHookRuntime;
|
||||||
use crate::engine::command_runner::CommandRunResult;
|
use crate::engine::command_runner::CommandRunResult;
|
||||||
use crate::engine::dispatcher;
|
use crate::engine::dispatcher;
|
||||||
use crate::engine::output_parser;
|
use crate::engine::output_parser;
|
||||||
use crate::output_spill::AdditionalContext;
|
use crate::output_spill::AdditionalContext;
|
||||||
use crate::output_spill::HookOutputSpiller;
|
|
||||||
use crate::schema::NullableString;
|
use crate::schema::NullableString;
|
||||||
use crate::schema::SubagentCommandInputFields;
|
use crate::schema::SubagentCommandInputFields;
|
||||||
use crate::schema::UserPromptSubmitCommandInput;
|
use crate::schema::UserPromptSubmitCommandInput;
|
||||||
@@ -64,8 +63,7 @@ pub(crate) fn preview(
|
|||||||
|
|
||||||
pub(crate) async fn run(
|
pub(crate) async fn run(
|
||||||
handlers: &[ConfiguredHandler],
|
handlers: &[ConfiguredHandler],
|
||||||
shell: &CommandShell,
|
runtime: &CommandHookRuntime,
|
||||||
output_spiller: &HookOutputSpiller,
|
|
||||||
request: UserPromptSubmitRequest,
|
request: UserPromptSubmitRequest,
|
||||||
) -> UserPromptSubmitOutcome {
|
) -> UserPromptSubmitOutcome {
|
||||||
let session_id = request.session_id;
|
let session_id = request.session_id;
|
||||||
@@ -107,7 +105,7 @@ pub(crate) async fn run(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let results = dispatcher::execute_handlers(
|
let results = dispatcher::execute_handlers(
|
||||||
shell,
|
runtime,
|
||||||
matched,
|
matched,
|
||||||
input_json,
|
input_json,
|
||||||
request.cwd.as_path(),
|
request.cwd.as_path(),
|
||||||
@@ -125,7 +123,8 @@ pub(crate) async fn run(
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|result| result.data.additional_contexts_for_model.as_slice()),
|
.map(|result| result.data.additional_contexts_for_model.as_slice()),
|
||||||
);
|
);
|
||||||
let additional_contexts = output_spiller
|
let additional_contexts = runtime
|
||||||
|
.output_spiller()
|
||||||
.maybe_spill_additional_contexts(session_id, additional_contexts)
|
.maybe_spill_additional_contexts(session_id, additional_contexts)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use tokio::process::Command;
|
|||||||
use crate::engine::ClaudeHooksEngine;
|
use crate::engine::ClaudeHooksEngine;
|
||||||
use crate::engine::CommandShell;
|
use crate::engine::CommandShell;
|
||||||
use crate::engine::HookListEntry;
|
use crate::engine::HookListEntry;
|
||||||
|
use crate::engine::command_runner::CommandHookRuntime;
|
||||||
use crate::events::compact::PostCompactRequest;
|
use crate::events::compact::PostCompactRequest;
|
||||||
use crate::events::compact::PreCompactOutcome;
|
use crate::events::compact::PreCompactOutcome;
|
||||||
use crate::events::compact::PreCompactRequest;
|
use crate::events::compact::PreCompactRequest;
|
||||||
@@ -67,16 +68,17 @@ impl Hooks {
|
|||||||
.map(crate::notify_hook)
|
.map(crate::notify_hook)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
|
let command_runtime = CommandHookRuntime::new(CommandShell {
|
||||||
|
program: config.shell_program.unwrap_or_default(),
|
||||||
|
args: config.shell_args,
|
||||||
|
});
|
||||||
let engine = ClaudeHooksEngine::new(
|
let engine = ClaudeHooksEngine::new(
|
||||||
config.feature_enabled,
|
config.feature_enabled,
|
||||||
config.bypass_hook_trust,
|
config.bypass_hook_trust,
|
||||||
config.config_layer_stack.as_ref(),
|
config.config_layer_stack.as_ref(),
|
||||||
config.plugin_hook_sources,
|
config.plugin_hook_sources,
|
||||||
config.plugin_hook_load_warnings,
|
config.plugin_hook_load_warnings,
|
||||||
CommandShell {
|
command_runtime,
|
||||||
program: config.shell_program.unwrap_or_default(),
|
|
||||||
args: config.shell_args,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
Self {
|
Self {
|
||||||
after_agent,
|
after_agent,
|
||||||
|
|||||||
Reference in new issue
Block a user