mirror of
https://github.com/openai/codex.git
synced 2026-05-18 02:02:30 +00:00
# Why `PreToolUse.additionalContext` became model-visible after #20692, but the hook-output spilling path from #21069 never picked up that newer lane. As a result, oversized `PreToolUse` context could bypass the truncation/spill treatment that already applies to the other hook outputs Codex forwards to the model. # What - Run `PreToolUseOutcome.additional_contexts` through `maybe_spill_texts(...)` - Add an integration test proving a large `PreToolUse.additionalContext` is replaced with a truncated preview plus spill-file pointer, while the full text is preserved on disk.
293 lines
9.9 KiB
Rust
293 lines
9.9 KiB
Rust
pub(crate) mod command_runner;
|
|
pub(crate) mod discovery;
|
|
pub(crate) mod dispatcher;
|
|
pub(crate) mod output_parser;
|
|
pub(crate) mod schema_loader;
|
|
|
|
use crate::events::compact::PostCompactRequest;
|
|
use crate::events::compact::PreCompactOutcome;
|
|
use crate::events::compact::PreCompactRequest;
|
|
use crate::events::compact::StatelessHookOutcome;
|
|
use crate::events::permission_request::PermissionRequestOutcome;
|
|
use crate::events::permission_request::PermissionRequestRequest;
|
|
use crate::events::post_tool_use::PostToolUseOutcome;
|
|
use crate::events::post_tool_use::PostToolUseRequest;
|
|
use crate::events::pre_tool_use::PreToolUseOutcome;
|
|
use crate::events::pre_tool_use::PreToolUseRequest;
|
|
use crate::events::session_start::SessionStartOutcome;
|
|
use crate::events::session_start::SessionStartRequest;
|
|
use crate::events::stop::StopOutcome;
|
|
use crate::events::stop::StopRequest;
|
|
use crate::events::user_prompt_submit::UserPromptSubmitOutcome;
|
|
use crate::events::user_prompt_submit::UserPromptSubmitRequest;
|
|
use crate::output_spill::HookOutputSpiller;
|
|
use codex_config::ConfigLayerStack;
|
|
use codex_plugin::PluginHookSource;
|
|
use codex_protocol::ThreadId;
|
|
use codex_protocol::protocol::HookEventName;
|
|
use codex_protocol::protocol::HookHandlerType;
|
|
use codex_protocol::protocol::HookRunSummary;
|
|
use codex_protocol::protocol::HookSource;
|
|
use codex_protocol::protocol::HookTrustStatus;
|
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
|
use std::collections::HashMap;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub(crate) struct CommandShell {
|
|
pub program: String,
|
|
pub args: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub(crate) struct ConfiguredHandler {
|
|
pub event_name: codex_protocol::protocol::HookEventName,
|
|
pub matcher: Option<String>,
|
|
pub command: String,
|
|
pub timeout_sec: u64,
|
|
pub status_message: Option<String>,
|
|
pub source_path: AbsolutePathBuf,
|
|
pub source: HookSource,
|
|
pub display_order: i64,
|
|
pub env: HashMap<String, String>,
|
|
}
|
|
|
|
impl ConfiguredHandler {
|
|
pub fn run_id(&self) -> String {
|
|
format!(
|
|
"{}:{}:{}",
|
|
self.event_name_label(),
|
|
self.display_order,
|
|
self.source_path.display()
|
|
)
|
|
}
|
|
|
|
fn event_name_label(&self) -> &'static str {
|
|
match self.event_name {
|
|
codex_protocol::protocol::HookEventName::PreToolUse => "pre-tool-use",
|
|
codex_protocol::protocol::HookEventName::PermissionRequest => "permission-request",
|
|
codex_protocol::protocol::HookEventName::PostToolUse => "post-tool-use",
|
|
codex_protocol::protocol::HookEventName::PreCompact => "pre-compact",
|
|
codex_protocol::protocol::HookEventName::PostCompact => "post-compact",
|
|
codex_protocol::protocol::HookEventName::SessionStart => "session-start",
|
|
codex_protocol::protocol::HookEventName::UserPromptSubmit => "user-prompt-submit",
|
|
codex_protocol::protocol::HookEventName::Stop => "stop",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct HookListEntry {
|
|
pub key: String,
|
|
pub event_name: HookEventName,
|
|
pub handler_type: HookHandlerType,
|
|
pub matcher: Option<String>,
|
|
pub command: Option<String>,
|
|
pub timeout_sec: u64,
|
|
pub status_message: Option<String>,
|
|
pub source_path: AbsolutePathBuf,
|
|
pub source: HookSource,
|
|
pub plugin_id: Option<String>,
|
|
pub display_order: i64,
|
|
pub enabled: bool,
|
|
pub is_managed: bool,
|
|
pub current_hash: String,
|
|
pub trust_status: HookTrustStatus,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub(crate) struct ClaudeHooksEngine {
|
|
handlers: Vec<ConfiguredHandler>,
|
|
warnings: Vec<String>,
|
|
shell: CommandShell,
|
|
output_spiller: HookOutputSpiller,
|
|
}
|
|
|
|
impl ClaudeHooksEngine {
|
|
pub(crate) fn new(
|
|
enabled: bool,
|
|
bypass_hook_trust: bool,
|
|
config_layer_stack: Option<&ConfigLayerStack>,
|
|
plugin_hook_sources: Vec<PluginHookSource>,
|
|
plugin_hook_load_warnings: Vec<String>,
|
|
shell: CommandShell,
|
|
) -> Self {
|
|
if !enabled {
|
|
return Self {
|
|
handlers: Vec::new(),
|
|
warnings: Vec::new(),
|
|
shell,
|
|
output_spiller: HookOutputSpiller::new(),
|
|
};
|
|
}
|
|
|
|
let _ = schema_loader::generated_hook_schemas();
|
|
let discovered = discovery::discover_handlers(
|
|
config_layer_stack,
|
|
plugin_hook_sources,
|
|
plugin_hook_load_warnings,
|
|
bypass_hook_trust,
|
|
);
|
|
Self {
|
|
handlers: discovered.handlers,
|
|
warnings: discovered.warnings,
|
|
shell,
|
|
output_spiller: HookOutputSpiller::new(),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn warnings(&self) -> &[String] {
|
|
&self.warnings
|
|
}
|
|
|
|
pub(crate) fn preview_session_start(
|
|
&self,
|
|
request: &SessionStartRequest,
|
|
) -> Vec<HookRunSummary> {
|
|
crate::events::session_start::preview(&self.handlers, request)
|
|
}
|
|
|
|
pub(crate) fn preview_pre_tool_use(&self, request: &PreToolUseRequest) -> Vec<HookRunSummary> {
|
|
crate::events::pre_tool_use::preview(&self.handlers, request)
|
|
}
|
|
|
|
pub(crate) fn preview_permission_request(
|
|
&self,
|
|
request: &PermissionRequestRequest,
|
|
) -> Vec<HookRunSummary> {
|
|
crate::events::permission_request::preview(&self.handlers, request)
|
|
}
|
|
|
|
pub(crate) fn preview_post_tool_use(
|
|
&self,
|
|
request: &PostToolUseRequest,
|
|
) -> Vec<HookRunSummary> {
|
|
crate::events::post_tool_use::preview(&self.handlers, request)
|
|
}
|
|
|
|
pub(crate) async fn run_session_start(
|
|
&self,
|
|
request: SessionStartRequest,
|
|
turn_id: Option<String>,
|
|
) -> SessionStartOutcome {
|
|
let session_id = request.session_id;
|
|
let mut outcome =
|
|
crate::events::session_start::run(&self.handlers, &self.shell, request, turn_id).await;
|
|
outcome.additional_contexts = self
|
|
.maybe_spill_texts(session_id, outcome.additional_contexts)
|
|
.await;
|
|
outcome
|
|
}
|
|
|
|
pub(crate) async fn run_pre_tool_use(&self, request: PreToolUseRequest) -> PreToolUseOutcome {
|
|
let session_id = request.session_id;
|
|
let mut outcome =
|
|
crate::events::pre_tool_use::run(&self.handlers, &self.shell, request).await;
|
|
outcome.additional_contexts = self
|
|
.maybe_spill_texts(session_id, outcome.additional_contexts)
|
|
.await;
|
|
outcome
|
|
}
|
|
|
|
pub(crate) async fn run_permission_request(
|
|
&self,
|
|
request: PermissionRequestRequest,
|
|
) -> PermissionRequestOutcome {
|
|
crate::events::permission_request::run(&self.handlers, &self.shell, request).await
|
|
}
|
|
|
|
pub(crate) async fn run_post_tool_use(
|
|
&self,
|
|
request: PostToolUseRequest,
|
|
) -> PostToolUseOutcome {
|
|
let session_id = request.session_id;
|
|
let mut outcome =
|
|
crate::events::post_tool_use::run(&self.handlers, &self.shell, request).await;
|
|
outcome.additional_contexts = self
|
|
.maybe_spill_texts(session_id, outcome.additional_contexts)
|
|
.await;
|
|
outcome.feedback_message = self
|
|
.maybe_spill_text(session_id, outcome.feedback_message)
|
|
.await;
|
|
outcome
|
|
}
|
|
|
|
pub(crate) fn preview_pre_compact(&self, request: &PreCompactRequest) -> Vec<HookRunSummary> {
|
|
crate::events::compact::preview_pre(&self.handlers, request)
|
|
}
|
|
|
|
pub(crate) async fn run_pre_compact(&self, request: PreCompactRequest) -> PreCompactOutcome {
|
|
crate::events::compact::run_pre(&self.handlers, &self.shell, request).await
|
|
}
|
|
|
|
pub(crate) fn preview_post_compact(&self, request: &PostCompactRequest) -> Vec<HookRunSummary> {
|
|
crate::events::compact::preview_post(&self.handlers, request)
|
|
}
|
|
|
|
pub(crate) async fn run_post_compact(
|
|
&self,
|
|
request: PostCompactRequest,
|
|
) -> StatelessHookOutcome {
|
|
crate::events::compact::run_post(&self.handlers, &self.shell, request).await
|
|
}
|
|
|
|
pub(crate) fn preview_user_prompt_submit(
|
|
&self,
|
|
request: &UserPromptSubmitRequest,
|
|
) -> Vec<HookRunSummary> {
|
|
crate::events::user_prompt_submit::preview(&self.handlers, request)
|
|
}
|
|
|
|
pub(crate) async fn run_user_prompt_submit(
|
|
&self,
|
|
request: UserPromptSubmitRequest,
|
|
) -> UserPromptSubmitOutcome {
|
|
let session_id = request.session_id;
|
|
let mut outcome =
|
|
crate::events::user_prompt_submit::run(&self.handlers, &self.shell, request).await;
|
|
outcome.additional_contexts = self
|
|
.maybe_spill_texts(session_id, outcome.additional_contexts)
|
|
.await;
|
|
outcome
|
|
}
|
|
|
|
pub(crate) fn preview_stop(&self, request: &StopRequest) -> Vec<HookRunSummary> {
|
|
crate::events::stop::preview(&self.handlers, request)
|
|
}
|
|
|
|
pub(crate) async fn run_stop(&self, request: StopRequest) -> StopOutcome {
|
|
let session_id = request.session_id;
|
|
let mut outcome = crate::events::stop::run(&self.handlers, &self.shell, request).await;
|
|
outcome.continuation_fragments = self
|
|
.maybe_spill_prompt_fragments(session_id, outcome.continuation_fragments)
|
|
.await;
|
|
outcome
|
|
}
|
|
|
|
async fn maybe_spill_texts(&self, session_id: ThreadId, texts: Vec<String>) -> Vec<String> {
|
|
self.output_spiller
|
|
.maybe_spill_texts(session_id, texts)
|
|
.await
|
|
}
|
|
|
|
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)]
|
|
#[path = "mod_tests.rs"]
|
|
mod tests;
|