mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Support asynchronous command hooks (#37533)
## Why Hook configurations can mark command handlers as asynchronous, but Codex previously skipped those handlers outside `SessionEnd`. ## What changed - Run asynchronous command hooks in the background with a per-session concurrency limit, while keeping `SessionEnd` hooks synchronous. - Prevent asynchronous hooks from blocking, stopping, rewriting, or otherwise controlling the operation that launched them. - Deliver warnings and additional context at safe turn boundaries: inject results into an active turn after sampling, or buffer them ahead of the next user prompt when the session is idle. - Preserve in-flight hooks across configuration reloads, scope spilled output to the thread, and abort outstanding work during session shutdown. ## Testing Add unit and integration coverage for background scheduling, concurrency, output parsing, active and idle result delivery, configuration reloads, and shutdown cleanup. GitOrigin-RevId: 8094552e4afe7b47b09a61bb575bb20f4e491d8d
This commit is contained in:
1 parent
61a3dd4387
commit
6f647caa9b
29 files changed
+1514
-429
No files matched your search
@@ -73,7 +73,7 @@ pub(crate) fn running_summary(handler: &ConfiguredHandler) -> HookRunSummary {
|
||||
id: handler.run_id(),
|
||||
event_name: handler.event_name,
|
||||
handler_type: HookHandlerType::Command,
|
||||
execution_mode: HookExecutionMode::Sync,
|
||||
execution_mode: handler.execution_mode,
|
||||
scope: scope_for_event(handler.event_name),
|
||||
source_path: handler.source_path.clone(),
|
||||
source: handler.source,
|
||||
@@ -87,7 +87,7 @@ pub(crate) fn running_summary(handler: &ConfiguredHandler) -> HookRunSummary {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute_handlers<T>(
|
||||
pub(crate) async fn execute_handlers<T: 'static>(
|
||||
runtime: &CommandHookRuntime,
|
||||
handlers: Vec<ConfiguredHandler>,
|
||||
input_json: String,
|
||||
@@ -97,10 +97,20 @@ pub(crate) async fn execute_handlers<T>(
|
||||
) -> Vec<ParsedHandler<T>> {
|
||||
let mut pending = FuturesUnordered::new();
|
||||
for (configured_order, handler) in handlers.into_iter().enumerate() {
|
||||
if handler.execution_mode == HookExecutionMode::Async {
|
||||
runtime.schedule_async_hook(
|
||||
handler,
|
||||
input_json.clone(),
|
||||
cwd.to_path_buf(),
|
||||
turn_id.clone(),
|
||||
parse,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
let input_json = input_json.clone();
|
||||
let turn_id = turn_id.clone();
|
||||
pending.push(async move {
|
||||
let result = run_command(runtime, &handler, configured_order, &input_json, cwd).await;
|
||||
let result = run_command(runtime, &handler, &input_json, cwd).await;
|
||||
(configured_order, parse(&handler, result, turn_id))
|
||||
});
|
||||
}
|
||||
@@ -126,7 +136,7 @@ pub(crate) fn completed_summary(
|
||||
id: handler.run_id(),
|
||||
event_name: handler.event_name,
|
||||
handler_type: HookHandlerType::Command,
|
||||
execution_mode: HookExecutionMode::Sync,
|
||||
execution_mode: handler.execution_mode,
|
||||
scope: scope_for_event(handler.event_name),
|
||||
source_path: handler.source_path.clone(),
|
||||
source: handler.source,
|
||||
@@ -232,6 +242,7 @@ mod tests {
|
||||
) -> ConfiguredHandler {
|
||||
ConfiguredHandler {
|
||||
event_name,
|
||||
execution_mode: codex_protocol::protocol::HookExecutionMode::Sync,
|
||||
matcher: matcher.map(str::to_owned),
|
||||
command: command.to_string(),
|
||||
timeout_sec: 5,
|
||||
|
||||
Reference in new issue
Block a user