mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
# What <img width="1792" height="1024" alt="image" src="https://github.com/user-attachments/assets/8f81d232-5813-4994-a61d-e42a05a93a3e" /> `SubagentStop` runs when a thread-spawned subagent turn is about to finish. Thread-spawned subagents use `SubagentStop` instead of the normal root-agent `Stop` hook. Configured handlers match on `agent_type`. Hook input includes the normal stop fields plus: - `agent_id`: the child thread id. - `agent_type`: the resolved subagent type. - `agent_transcript_path`: the child subagent transcript path. - `transcript_path`: the parent thread transcript path. - `last_assistant_message`: the final assistant message from the child turn, when available. - `stop_hook_active`: `true` when the child is already continuing because an earlier stop-like hook blocked completion. `SubagentStop` shares the same completion-control semantics as `Stop`, scoped to the child turn: - No decision allows the child turn to finish. - `decision: "block"` with a non-empty `reason` records that reason as hook feedback and continues the child with that prompt. - `continue: false` stops the child turn. If `stopReason` is present, Codex surfaces it as the stop reason. # Lifecycle Scope Only thread-spawned subagents run `SubagentStop`. Internal/system subagents such as Review, Compact, MemoryConsolidation, and Other do not run normal `Stop` hooks and do not run `SubagentStop`. This avoids exposing synthetic matcher labels for internal implementation paths. # Stack 1. #22782: add `SubagentStart`. 2. This PR: add `SubagentStop`. 3. #22882: add subagent identity to normal hook inputs.
155 lines
4.5 KiB
Rust
155 lines
4.5 KiB
Rust
use super::shared::v2_enum_from_core;
|
|
use codex_protocol::protocol::HookEventName as CoreHookEventName;
|
|
use codex_protocol::protocol::HookExecutionMode as CoreHookExecutionMode;
|
|
use codex_protocol::protocol::HookHandlerType as CoreHookHandlerType;
|
|
use codex_protocol::protocol::HookOutputEntry as CoreHookOutputEntry;
|
|
use codex_protocol::protocol::HookOutputEntryKind as CoreHookOutputEntryKind;
|
|
use codex_protocol::protocol::HookRunStatus as CoreHookRunStatus;
|
|
use codex_protocol::protocol::HookRunSummary as CoreHookRunSummary;
|
|
use codex_protocol::protocol::HookScope as CoreHookScope;
|
|
use codex_protocol::protocol::HookSource as CoreHookSource;
|
|
use codex_protocol::protocol::HookTrustStatus as CoreHookTrustStatus;
|
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
|
use schemars::JsonSchema;
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
use ts_rs::TS;
|
|
|
|
v2_enum_from_core!(
|
|
pub enum HookEventName from CoreHookEventName {
|
|
PreToolUse, PermissionRequest, PostToolUse, PreCompact, PostCompact, SessionStart, UserPromptSubmit, SubagentStart, SubagentStop, Stop
|
|
}
|
|
);
|
|
|
|
v2_enum_from_core!(
|
|
pub enum HookHandlerType from CoreHookHandlerType {
|
|
Command, Prompt, Agent
|
|
}
|
|
);
|
|
|
|
v2_enum_from_core!(
|
|
pub enum HookExecutionMode from CoreHookExecutionMode {
|
|
Sync, Async
|
|
}
|
|
);
|
|
|
|
v2_enum_from_core!(
|
|
pub enum HookScope from CoreHookScope {
|
|
Thread, Turn
|
|
}
|
|
);
|
|
|
|
v2_enum_from_core!(
|
|
pub enum HookSource from CoreHookSource {
|
|
System,
|
|
User,
|
|
Project,
|
|
Mdm,
|
|
SessionFlags,
|
|
Plugin,
|
|
CloudRequirements,
|
|
LegacyManagedConfigFile,
|
|
LegacyManagedConfigMdm,
|
|
Unknown,
|
|
}
|
|
);
|
|
|
|
v2_enum_from_core!(
|
|
pub enum HookTrustStatus from CoreHookTrustStatus {
|
|
Managed, Untrusted, Trusted, Modified
|
|
}
|
|
);
|
|
|
|
fn default_hook_source() -> HookSource {
|
|
HookSource::Unknown
|
|
}
|
|
|
|
v2_enum_from_core!(
|
|
pub enum HookRunStatus from CoreHookRunStatus {
|
|
Running, Completed, Failed, Blocked, Stopped
|
|
}
|
|
);
|
|
|
|
v2_enum_from_core!(
|
|
pub enum HookOutputEntryKind from CoreHookOutputEntryKind {
|
|
Warning, Stop, Feedback, Context, Error
|
|
}
|
|
);
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct HookOutputEntry {
|
|
pub kind: HookOutputEntryKind,
|
|
pub text: String,
|
|
}
|
|
|
|
impl From<CoreHookOutputEntry> for HookOutputEntry {
|
|
fn from(value: CoreHookOutputEntry) -> Self {
|
|
Self {
|
|
kind: value.kind.into(),
|
|
text: value.text,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct HookRunSummary {
|
|
pub id: String,
|
|
pub event_name: HookEventName,
|
|
pub handler_type: HookHandlerType,
|
|
pub execution_mode: HookExecutionMode,
|
|
pub scope: HookScope,
|
|
pub source_path: AbsolutePathBuf,
|
|
#[serde(default = "default_hook_source")]
|
|
pub source: HookSource,
|
|
pub display_order: i64,
|
|
pub status: HookRunStatus,
|
|
pub status_message: Option<String>,
|
|
pub started_at: i64,
|
|
pub completed_at: Option<i64>,
|
|
pub duration_ms: Option<i64>,
|
|
pub entries: Vec<HookOutputEntry>,
|
|
}
|
|
|
|
impl From<CoreHookRunSummary> for HookRunSummary {
|
|
fn from(value: CoreHookRunSummary) -> Self {
|
|
Self {
|
|
id: value.id,
|
|
event_name: value.event_name.into(),
|
|
handler_type: value.handler_type.into(),
|
|
execution_mode: value.execution_mode.into(),
|
|
scope: value.scope.into(),
|
|
source_path: value.source_path,
|
|
source: value.source.into(),
|
|
display_order: value.display_order,
|
|
status: value.status.into(),
|
|
status_message: value.status_message,
|
|
started_at: value.started_at,
|
|
completed_at: value.completed_at,
|
|
duration_ms: value.duration_ms,
|
|
entries: value.entries.into_iter().map(Into::into).collect(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct HookStartedNotification {
|
|
pub thread_id: String,
|
|
pub turn_id: Option<String>,
|
|
pub run: HookRunSummary,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct HookCompletedNotification {
|
|
pub thread_id: String,
|
|
pub turn_id: Option<String>,
|
|
pub run: HookRunSummary,
|
|
}
|