Add hooks for interrupted turns (#40511)

## What changed

- Add an `Interrupt` hook event that runs for an active top-level turn before its
  interrupted abort event is emitted.
- Flush the turn transcript before invoking the hook and provide the session,
  turn, transcript, working directory, model, and permission mode in its input.
- Support command and MCP handlers, including asynchronous commands, with a
  one-second default timeout and a three-second maximum.
- Expose the event through hook configuration, managed requirements, app-server
  notifications, generated schemas, analytics, and the TUI hook views.

## Testing

- Cover handler discovery, timeout normalization, output parsing, protocol
  compatibility, TUI rendering, and interrupt execution ordering.

GitOrigin-RevId: 163fa7c098d94ac2775f6d137f8e916f8ea9b6eb
This commit is contained in:
Andrei Eternal
2026-08-25 01:27:18 +00:00
committed by copyberry
parent 9b2ef38f54
commit cbfd999db7
49 changed files with 1393 additions and 48 deletions
@@ -589,6 +589,9 @@ pub struct ManagedHooksRequirements {
#[serde(rename = "Stop")]
#[ts(rename = "Stop")]
pub stop: Vec<ConfiguredHookMatcherGroup>,
#[serde(rename = "Interrupt", default)]
#[ts(rename = "Interrupt")]
pub interrupt: Vec<ConfiguredHookMatcherGroup>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
@@ -17,7 +17,7 @@ use serde::Serialize;
v2_enum_from_core!(
pub enum HookEventName from CoreHookEventName {
PreToolUse, PermissionRequest, PostToolUse, PreCompact, PostCompact, SessionStart, SessionEnd, UserPromptSubmit, SubagentStart, SubagentStop, Stop
PreToolUse, PermissionRequest, PostToolUse, PreCompact, PostCompact, SessionStart, SessionEnd, UserPromptSubmit, SubagentStart, SubagentStop, Stop, Interrupt
}
);
@@ -72,6 +72,30 @@ fn test_absolute_path() -> AbsolutePathBuf {
absolute_path("readable")
}
#[test]
fn managed_hooks_requirements_default_interrupt_to_empty() {
let value = json!({
"managedDir": null,
"windowsManagedDir": null,
"PreToolUse": [],
"PermissionRequest": [],
"PostToolUse": [],
"PreCompact": [],
"PostCompact": [],
"SessionStart": [],
"SessionEnd": [],
"UserPromptSubmit": [],
"SubagentStart": [],
"SubagentStop": [],
"Stop": []
});
let parsed: ManagedHooksRequirements =
serde_json::from_value(value).expect("deserialize managed hooks requirements");
assert_eq!(parsed.interrupt, Vec::new());
}
#[test]
fn external_agent_config_detect_response_defaults_connectors_for_older_servers() {
let response = serde_json::from_value::<ExternalAgentConfigDetectResponse>(json!({