Documentation
¶
Overview ¶
Package hooks implements the hook system: 14 lifecycle event points (PreToolUse / PostToolUse / PreCompact / UserPromptSubmit etc.) the engine fires callbacks at, with three backend modes (shell command / Go callback / planned webhook) so the same HookDef works across CLI / SDK / API consumption models.
Consumer integration surfaces fall into three shapes. For the full taxonomy see `docs/api-reference.md` section "API 消费形态 / API Consumption Patterns".
Synchronous callback — form 3 (the only shape this package exposes):
- HookHandler.ExecuteHook: Manager.Execute calls per registered hook for a given HookType, blocks for *HookResult (Decision allow/deny/ask, Message, etc.)
- Three concrete backends:
- ShellHandler: consumer writes a shell command in HookDef.Command; engine execs, reads stdout/stderr, parses exit code.
- CallbackHandler: consumer provides a Go func (for SDK embedding).
- WebhookHandler: POSTs to a URL (planned).
Package hooks 实现 hook 系统: 14 个生命周期事件点 (PreToolUse / PostToolUse / PreCompact / UserPromptSubmit 等) 引擎触发回调, 支持三种后端 (shell 命令 / Go 回调 / 规划中的 webhook), 让同一个 HookDef 在 CLI / SDK / API 三种消费模式下都工作.
消费者接入面分三种形态. 完整分类见 `docs/api-reference.md` "API 消费形态 / API Consumption Patterns" 章节.
同步回调 (callback) —— 形态三 (本包只暴露这一种):
- HookHandler.ExecuteHook: Manager.Execute 对每个注册 hook 按 HookType 调用, 阻塞等 *HookResult (Decision allow/deny/ask, Message 等)
- 三种具体后端:
- ShellHandler: 消费者在 HookDef.Command 写 shell 命令; 引擎 exec, 读 stdout/stderr, 解析 exit code.
- CallbackHandler: 消费者传 Go 函数 (用于 SDK 嵌入).
- WebhookHandler: POST 到 URL (规划中).
Package hooks 的命令执行器.
走 execenv.Executor 统一门框 (M1 方案 β), 本地 CLI 映射到 DefaultExecutor (零开销包装 os/exec), 云端 SaaS 由 platform sandbox.Backend 接管, 本文件 对两种后端无差别.
历史 ¶
原本是 executor struct + newExecutor 的 OO 封装, 迁移到方案 β 后变成 纯函数 runShellHook: hooks 包不再管进程生命周期, 只负责把 HookDef 翻译 成 execenv.Spec, 剩下交给 Executor. Manager 和 ShellHandler 分别持有 execenv.Executor, 两条调用路径各自传入.
Package hooks 实现 Hook 系统.
Hook 系统的核心实现. Hook 系统允许用户在 Agent 的关键生命周期点注入自定义 shell 命令.
原项目的问题:
- 12+ 种 hook 类型全塞一个 5000 行文件
- 每种 hook 的执行逻辑重复但各有特殊分支
- 同步/异步执行混在一起,错误处理不一致
Go 版本的设计:
- Manager 统一管理所有 hook 的注册和触发
- types.go 定义所有类型和结果结构
- executor.go 封装命令执行(环境注入,超时,输出捕获)
- integration.go 提供与 Engine 的集成辅助函数
- 同步/异步执行有明确的 API 分离
Package hooks 与 Engine 的集成点.
提供构建 hook 环境变量的辅助函数. 为每种 hook 类型提供环境变量注入逻辑, Go 版本抽取为独立的 builder 函数,更清晰也更容易测试.
Package hooks 的类型定义.
定义所有 12 种 hook 类型常量,执行结果结构,环境变量构建器. Hook 系统的类型定义.
Index ¶
- Constants
- func BuildNotificationEnv(message string, level string, projectRoot string) map[string]string
- func BuildPermissionEnv(toolName string, reason string, projectRoot string, hookType HookType) map[string]string
- func BuildPostSamplingEnv(model string, turnCount int, inputTokens, outputTokens int, stopReason string, ...) map[string]string
- func BuildPostToolEnv(toolName string, input any, output string, isError bool, projectRoot string) map[string]string
- func BuildPreSamplingEnv(model string, turnCount int, messageCount int, projectRoot string) map[string]string
- func BuildPreToolEnv(toolName string, input any, projectRoot string) map[string]string
- func BuildSessionEnv(sessionID string, projectRoot string, hookType HookType) map[string]string
- func BuildTaskEnv(taskID string, taskDescription string, projectRoot string, hookType HookType) map[string]string
- func ParsePermissionResponse(results *ExecuteResults) (decision string, reason string)
- func ParsePostCompactHookResponse(results *ExecuteResults) string
- func ParseStopHookResponse(results *ExecuteResults) (shouldStop bool, reason string)
- func ParseToolHookResponse(results *ExecuteResults) (blocked bool, reason string)
- func WithSessionHooksCtx(ctx context.Context, m *Manager) context.Context
- type CallbackHandler
- type CompositeManager
- type Config
- type ExecuteResults
- type HookDef
- type HookHandler
- type HookHandlerFunc
- type HookResult
- type HookType
- type Manager
- func (m *Manager) Count(hookType HookType) int
- func (m *Manager) Disable()
- func (m *Manager) Enable()
- func (m *Manager) Execute(ctx context.Context, hookType HookType, env map[string]string) (*ExecuteResults, error)
- func (m *Manager) ExecuteAll(ctx context.Context, hookType HookType, env map[string]string) (syncResults *ExecuteResults, asyncResults <-chan *ExecuteResults)
- func (m *Manager) ExecuteAsync(hookType HookType, env map[string]string) <-chan *ExecuteResults
- func (m *Manager) HasHooks(hookType HookType) bool
- func (m *Manager) Register(hookType HookType, def HookDef) error
- func (m *Manager) SetObserver(obs flyto.EventObserver)
- func (m *Manager) Unregister(hookType HookType)
- func (m *Manager) UnregisterAllBySource(source string)
- func (m *Manager) UnregisterBySource(hookType HookType, source string)
- type ShellHandler
Constants ¶
const DefaultTimeout = 30
DefaultTimeout 是 hook 命令执行的默认超时时间(秒).
const ExitCodeBlock = 2
ExitCodeBlock 是 "有意阻止" 的退出码. 精妙之处(CLEVER): exit 1 = "hook 自己出错了"(fail-open,不阻止). exit 2 = "hook 有意阻止此操作"(用户明确的安全决策). 这个区分很重要--写错的 hook 脚本(exit 1)不应该让 Agent 瘫痪. 替代方案:<所有非零退出码都 block--一个写错的 hook 就瘫痪整个系统>
Variables ¶
This section is empty.
Functions ¶
func BuildNotificationEnv ¶
BuildNotificationEnv 构建 notification hook 的环境变量.
func BuildPermissionEnv ¶
func BuildPermissionEnv(toolName string, reason string, projectRoot string, hookType HookType) map[string]string
BuildPermissionEnv 构建 permission_request / permission_denied hook 的环境变量.
注入的变量:
- TOOL_NAME: 请求权限的工具名称
- PERMISSION_REASON: 请求权限的原因描述
- HOOK_TYPE: "permission_request" 或 "permission_denied"
func BuildPostSamplingEnv ¶
func BuildPostSamplingEnv(model string, turnCount int, inputTokens, outputTokens int, stopReason string, responsePreview string, projectRoot string) map[string]string
BuildPostSamplingEnv 构建 post_sampling hook 的环境变量.
post_sampling hook 在 API 响应后,工具执行前触发(异步 fire-and-forget),不影响控制流.
注入的变量:
- HOOK_TYPE: "post_sampling"
- MODEL: 使用的模型 ID
- TURN: 当前对话轮次
- INPUT_TOKENS: 本轮输入 token 数(API 返回的精确值)
- OUTPUT_TOKENS: 本轮输出 token 数
- STOP_REASON: 模型停止原因("end_turn"/"tool_use"/"max_tokens"/"stop_sequence")
- RESPONSE_PREVIEW: 助手回复文本的前 500 字节(截断,防止 env 过大)
- PROJECT_ROOT / PLATFORM / ARCH / CWD: 基础环境
精妙之处(CLEVER): RESPONSE_PREVIEW 截断 500 字节而非字符-- 多字节 UTF-8 字符截断在字节边界可能生成无效字符串, 但 hook 脚本通常是文本处理工具,短暂的末尾乱码无实质影响. 若需要完整响应,使用 SDK 模式的 CallbackHandler.
func BuildPostToolEnv ¶
func BuildPostToolEnv(toolName string, input any, output string, isError bool, projectRoot string) map[string]string
BuildPostToolEnv 构建 post_tool_use / post_tool_use_failure hook 的环境变量.
注入的变量(在 pre 的基础上增加):
- TOOL_OUTPUT: 工具输出内容
- TOOL_IS_ERROR: "true" 或 "false"
- HOOK_TYPE: "post_tool_use" 或 "post_tool_use_failure"
func BuildPreSamplingEnv ¶
func BuildPreSamplingEnv(model string, turnCount int, messageCount int, projectRoot string) map[string]string
BuildPreSamplingEnv 构建 pre_sampling hook 的环境变量.
pre_sampling hook 在 API 调用前触发(同步),可通过 exit 2 阻止本轮 API 调用.
注入的变量:
- HOOK_TYPE: "pre_sampling"
- MODEL: 当前使用的模型 ID
- TURN: 当前对话轮次(从 1 开始)
- MESSAGE_COUNT: 消息历史条数(不含当前轮新增)
- PROJECT_ROOT: 项目根目录
- PLATFORM / ARCH / CWD: 基础环境(由 baseEnv 注入)
升华改进(ELEVATED): 只传轻量元数据,不传完整消息列表-- 消息列表可能数百 KB,放进环境变量会触发 "argument list too long" 错误(Linux 默认 ARG_MAX=2MB). 需要完整内容的场景应使用 SDK 模式的 CallbackHandler(直接接收 Go 结构体,无此限制). 替代方案:<将 MESSAGES_JSON 注入 env> - 否决原因:单条上下文 > 128KB 即可能触发 execve 限制.
func BuildPreToolEnv ¶
BuildPreToolEnv 构建 pre_tool_use hook 的环境变量.
注入的变量:
- HOOK_TYPE: "pre_tool_use"
- TOOL_NAME: 工具名称
- TOOL_INPUT: 工具输入参数的 JSON 字符串
- PROJECT_ROOT: 项目根目录
- PLATFORM: 操作系统
func BuildSessionEnv ¶
BuildSessionEnv 构建 session_start / session_end hook 的环境变量.
注入的变量:
- SESSION_ID: 会话 ID
- PROJECT_ROOT: 项目根目录
- HOOK_TYPE: "session_start" 或 "session_end"
func BuildTaskEnv ¶
func BuildTaskEnv(taskID string, taskDescription string, projectRoot string, hookType HookType) map[string]string
BuildTaskEnv 构建 task_created / task_completed hook 的环境变量.
func ParsePermissionResponse ¶
func ParsePermissionResponse(results *ExecuteResults) (decision string, reason string)
ParsePermissionResponse 从 hook 执行结果中解析权限决策.
原项目的 permission_request hook 可以返回 JSON 来自动批准/拒绝:
{"decision": "allow"} → 自动批准
{"decision": "deny"} → 自动拒绝
{"decision": "deny", "reason": "..."} → 带原因拒绝
返回值:
- decision: "allow", "deny", 或 ""(未决定,交给用户)
- reason: 拒绝原因(仅 deny 时有效)
func ParsePostCompactHookResponse ¶
func ParsePostCompactHookResponse(results *ExecuteResults) string
ParsePostCompactHookResponse 提取 post_compact hook 的补充摘要. 精妙之处(CLEVER): hook stdout 作为补充摘要追加到压缩结果. 注入文本带来源标记 [hook: post_compact],让模型和日志能区分来源. 空 stdout 或 hook 失败时返回空字符串(不注入).
func ParseStopHookResponse ¶
func ParseStopHookResponse(results *ExecuteResults) (shouldStop bool, reason string)
ParseStopHookResponse 解析 stop hook 的控制指令. 任何 hook 返回非零退出码 → 应该停止 Agent 循环. 精妙之处(CLEVER): stop hook 的语义与 tool hook 不同-- stop hook 的"失败"就是"应该停"(反直觉但合理: hook 脚本检查某个条件,条件不满足就 exit 1 表示"该停了").
func ParseToolHookResponse ¶
func ParseToolHookResponse(results *ExecuteResults) (blocked bool, reason string)
ParseToolHookResponse 解析 pre/post_tool_use hook 的控制指令.
两种阻止方式:
- Exit code 2 → blocked(shell 模式自然表达)
- JSON {"decision":"block"} → blocked(结构化表达)
返回:
- blocked: 是否阻止工具执行
- reason: 阻止原因(来自 JSON 的 reason 字段,或 stderr)
func WithSessionHooksCtx ¶
WithSessionHooksCtx 将 session hooks 注入 context(SubAgent 继承用).
升华改进(ELEVATED): SubAgent 的 context 链路中注入 session hooks-- 父 Agent 通过 WithSessionHooks RunOption 注册的 hooks, 自动传播到 SubAgent,无需调用方手动传递. 替代方案:<SubAgent 不继承 session hooks> - 否决:审计 hook 漏掉子任务,合规不完整.
Types ¶
type CallbackHandler ¶
type CallbackHandler struct {
// contains filtered or unexported fields
}
CallbackHandler 通过 Go 函数回调执行 hook.
升华改进(ELEVATED): SDK 模式的核心--嵌入 Go 程序中的 Agent 不应该 为了一个 hook 而 fork shell 进程.直接调 Go 函数:
- 零进程开销(shell fork+exec 约 1-5ms,Go 调用 <1μs)
- 类型安全(Go 编译器检查参数类型,shell 是纯字符串)
- 可调试(Go debugger 直接跟进 hook 函数)
用法:
hooksMgr.Register(hooks.HookPreToolUse, hooks.HookDef{
Handler: hooks.NewCallbackHandler(func(ctx context.Context, hookType hooks.HookType, env map[string]string) *hooks.HookResult {
if env["TOOL_NAME"] == "Bash" && strings.Contains(env["TOOL_INPUT"], "rm -rf") {
return &hooks.HookResult{ExitCode: 2} // block
}
return &hooks.HookResult{ExitCode: 0}
}),
})
替代方案:<原方案 SDK 用户也得写 shell 脚本,然后设 Command: "my-hook.sh">
func NewCallbackHandler ¶
func NewCallbackHandler(fn func(ctx context.Context, hookType HookType, env map[string]string) *HookResult) *CallbackHandler
NewCallbackHandler 创建 Go 回调后端.
func NewCallbackHandlerWithTimeout ¶
func NewCallbackHandlerWithTimeout(fn func(ctx context.Context, hookType HookType, env map[string]string) *HookResult, timeout time.Duration) *CallbackHandler
NewCallbackHandlerWithTimeout 创建带超时的 Go 回调后端.
func (*CallbackHandler) ExecuteHook ¶
func (h *CallbackHandler) ExecuteHook(ctx context.Context, hookType HookType, env map[string]string) *HookResult
ExecuteHook 执行 Go 回调函数.
type CompositeManager ¶
type CompositeManager struct {
// contains filtered or unexported fields
}
CompositeManager 将 session-level hooks 与 engine-level hooks 组合执行.
查找规则:
- HasHooks:session || engine(任意一方有即返回 true)
- Execute:先执行 session hooks,再执行 engine hooks
- ExecuteAsync:同上,但异步(各自独立 goroutine)
nil session 时,行为与 engine-level Manager 完全相同(向后兼容).
type ExecuteResults ¶
type ExecuteResults struct {
HookType HookType `json:"hook_type"`
Results []*HookResult `json:"results"`
}
ExecuteResults 是一批 hook 的执行结果.
func (*ExecuteResults) FindJSONOutput ¶
func (er *ExecuteResults) FindJSONOutput(key string) (any, bool)
FindJSONOutput 在所有结果中查找包含指定 key 的 JSON 输出. 返回第一个匹配的值.用于提取 hook 的控制指令.
func (*ExecuteResults) FirstError ¶
func (er *ExecuteResults) FirstError() *HookResult
FirstError 返回第一个失败的 hook 结果(如果有).
func (*ExecuteResults) HasErrors ¶
func (er *ExecuteResults) HasErrors() bool
HasErrors 检查是否有任何 hook 执行失败.
type HookDef ¶
type HookDef struct {
Command string `json:"command,omitempty"` // Shell 命令(CLI 模式)
Handler HookHandler `json:"-"` // Go 回调(SDK 模式,不序列化)
WebhookURL string `json:"webhook_url,omitempty"` // HTTP 回调 URL(API 模式,待实现)
Timeout int `json:"timeout,omitempty"` // 超时秒数,默认 30
Async bool `json:"async,omitempty"` // 是否异步执行(不阻塞主流程)
// Source 标识 hook 的来源(模块 9.3).
//
// 升华改进(ELEVATED): 早期设计在 PluginHookMatcher 里用 pluginRoot/pluginId 区分来源,
// 导致"全局 hook 注册表"和"插件 hook 注册表"必须分开维护,执行时三路合并(gh-29767).
// 我们把来源信息内嵌进 HookDef,单一注册表即可精准移除特定来源的 hooks,
// 不需要维护多个注册表或全量重建.
//
// 约定:
// - ""(空字符串)= 全局 hook(来自 HooksConfig 或 Manager.Register 直接调用)
// - 非空字符串 = 插件名(来自 plugin.Host,值为 plugin.Plugin.Name)
//
// 精妙之处(CLEVER): 空字符串 = 全局,向后兼容--
// 所有现有 HookDef{Command: "..."} 字面量 Source 自动为 ""(全局),
// 零迁移成本.UnregisterBySource("") 等价于原来的 Unregister(移除所有全局 hooks).
//
// 扩展点(预留,未实现):
// HTTP API / SaaS 多租户场景可用 "tenantID:pluginName" 前缀,
// UnregisterAllBySource 按前缀匹配实现租户级隔离.
// C# 移植对应字段:public string Source { get; init; } = ""
Source string `json:"source,omitempty"` // 来源标识(空=全局,非空=插件名)
// PluginDir 是插件的根目录(绝对路径).
// 非空时,executor 将 FLYTO_PLUGIN_ROOT 注入到 hook 子进程环境变量,
// 让 hook 脚本通过 $FLYTO_PLUGIN_ROOT/scripts/foo.py 引用插件内文件.
//
// 升华改进(ELEVATED): 早期设计通过 pluginRoot 参数在调用链中传递,
// 需要修改所有 Build*Env 函数签名(破坏性变更).
// 我们内嵌在 HookDef 里--注册时知道 pluginDir,执行时自然可用,
// 无需修改 Build*Env 或 executor 的公共接口.
// 替代方案:os.Setenv 全局设置(无法区分不同插件的路径).
PluginDir string `json:"plugin_dir,omitempty"` // 插件根目录(注入 FLYTO_PLUGIN_ROOT)
}
HookDef 是单个 hook 的定义. 对应原项目中 settings.json 里每个 hook 条目.
升华改进(ELEVATED): 支持三种执行后端--
- Command(shell):CLI 模式,JSON 配置文件使用
- Handler(Go 回调):SDK 模式,Go 代码注册使用(json:"-" 不序列化)
- WebhookURL(HTTP):API 模式(待实现)
优先级:Handler > Command.如果两者都设置,Handler 胜出. 替代方案:<早期方案只有 Command 一种,SDK 用户也得写 shell 脚本>
func (HookDef) EffectiveTimeout ¶
EffectiveTimeout 返回 hook 定义的有效超时时间. 如果未设置(0),使用默认值 30 秒.
type HookHandler ¶
type HookHandler interface {
// ExecuteHook 执行 hook 并返回结果.
// hookType: hook 类型(pre_tool_use 等)
// env: 环境变量(TOOL_NAME,TOOL_INPUT 等)
ExecuteHook(ctx context.Context, hookType HookType, env map[string]string) *HookResult
}
HookHandler 是 hook 的执行后端. 精妙之处(CLEVER): 单方法接口--Go 惯例,最小抽象. 任何 func(ctx, HookType, env) → HookResult 都可以适配.
Shape: synchronous callback. Manager.Execute calls ExecuteHook per registered hook for a given HookType and blocks for *HookResult. Three concrete backends (ShellHandler / CallbackHandler / WebhookHandler-planned) cover CLI / SDK / API consumption modes.
形态: 同步回调. Manager.Execute 对每个注册 hook 按 HookType 调 ExecuteHook 并阻塞等 *HookResult. 三种具体后端 (ShellHandler / CallbackHandler / WebhookHandler 规划中) 覆盖 CLI / SDK / API 三种消费模式.
type HookHandlerFunc ¶
type HookHandlerFunc func(ctx context.Context, hookType HookType, env map[string]string) *HookResult
HookHandlerFunc 是函数类型的 HookHandler 适配器. 精妙之处(CLEVER): http.HandlerFunc 模式-- 任何签名匹配的函数自动满足接口,不需要包装 struct.
func (HookHandlerFunc) ExecuteHook ¶
func (f HookHandlerFunc) ExecuteHook(ctx context.Context, hookType HookType, env map[string]string) *HookResult
ExecuteHook 实现 HookHandler 接口.
type HookResult ¶
type HookResult struct {
// Command 是执行的原始命令
Command string `json:"command"`
// Stdout 是标准输出内容
Stdout string `json:"stdout"`
// Stderr 是标准错误输出内容
Stderr string `json:"stderr"`
// ExitCode 是进程退出码(0 表示成功)
ExitCode int `json:"exit_code"`
// JSONOutput 是从 stdout 解析出的 JSON 对象(如果 stdout 是有效 JSON).
// 原项目用这个机制让 hook 影响 Agent 行为,
// 例如 permission_request hook 返回 {"decision": "allow"} 可以自动批准.
JSONOutput map[string]any `json:"json_output,omitempty"`
// Duration 是命令执行耗时
Duration time.Duration `json:"duration"`
// Error 是执行过程中的错误(超时,找不到命令等)
Error error `json:"error,omitempty"`
}
HookResult 是单个 hook 命令的执行结果.
func (*HookResult) Success ¶
func (r *HookResult) Success() bool
Success 返回 hook 是否执行成功(退出码为 0 且没有错误).
type HookType ¶
type HookType string
HookType 是 hook 类型枚举. 原项目有 12+ 种 hook 类型,覆盖了工具调用,会话生命周期,权限等场景.
const ( // 工具相关的 hook HookPreToolUse HookType = "pre_tool_use" // 工具执行前触发 HookPostToolUse HookType = "post_tool_use" // 工具执行成功后触发 HookPostToolUseFailure HookType = "post_tool_use_failure" // 工具执行失败后触发 // 会话生命周期 hook HookSessionStart HookType = "session_start" // 会话开始时触发 HookSessionEnd HookType = "session_end" // 会话结束时触发 // 权限相关 hook HookPermission HookType = "permission_request" // 请求权限时触发(可自动批准) HookPermissionDenied HookType = "permission_denied" // 权限被拒绝时触发 // 控制流 hook HookStop HookType = "stop" // Agent 停止时触发 // 通知 hook HookNotification HookType = "notification" // 发送通知时触发 // 配置变更 hook HookConfigChange HookType = "config_change" // 配置变更时触发 // 任务相关 hook HookTaskCreated HookType = "task_created" // 子任务创建时触发 HookTaskCompleted HookType = "task_completed" // 子任务完成时触发 // 采样生命周期 hook(模块 9.2) // // 升华改进(ELEVATED): 早期设计只有 post-sampling,且仅为程序化 API(不暴露在 settings.json), // 用于 memory extraction / auto-dream 等内部副作用(post-sampling hook 返回 void). // 我们新增 pre-sampling,并将两者统一进 settings.json 可配置的 hook 体系. // // pre_sampling: API 调用前触发,同步执行,可阻止(exit 2 → 终止本轮,推送 WarningEvent). // 用途:请求级审计,配额控制,合规拦截,动态路由(SDK 模式 CallbackHandler 切换模型). // // post_sampling: API 响应后,工具执行前触发,异步 fire-and-forget,不影响控制流. // 用途:响应记录,内存提取,分析埋点. // // 精妙之处(CLEVER): pre 同步 / post 异步的非对称设计-- // pre 需要拦截能力(同步返回决策),post 只做副作用(不能阻塞用户等待 AI 响应). // 如果 post 也同步,一个慢 hook 脚本会让每轮 API 响应后都多等 N 秒. // 替代方案:两者都同步(统一简单)- 否决:post-sampling 主路径性能不可接受. HookPreSampling HookType = "pre_sampling" // API 调用前触发(同步,可阻止) HookPostSampling HookType = "post_sampling" // API 响应后触发(异步,fire-and-forget) )
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager 管理所有 hook 的注册和触发.
线程安全:内部用 RWMutex 保护 hook 注册表, 支持并发执行不同类型的 hook.
Executor 依赖 (M1 方案 β 严格 DI) ¶
Manager 持有 execenv.Executor 用于启动 shell hook 子进程. 本地 CLI 场景 由 engine.New 把 cfg.Executor (默认 execenv.DefaultExecutor{}) 注入, 云端 SaaS 场景由 platform 层注入 sandbox.Backend. Manager 自己不知道也不关心 后端是哪个, 只负责把 Class/Spec 透传下去.
强制约定: executor 字段永远 non-nil. NewManager 会校验, nil 直接 panic -- 严格 DI 不做 fallback, 避免历史上的 "mock hooks 绕过 executor 路径" 漏网之鱼.
func NewCompositeManager ¶
NewCompositeManager 创建 CompositeManager.
精妙之处(CLEVER): session 为 nil 时不创建包装对象,直接返回 engine Manager-- 避免零值 session 带来的双重 nil 检查开销,热路径性能不降级. 调用方始终收到非 nil 返回值,无需后续 nil 守卫. 替代方案:<总是创建 CompositeManager,session=nil 时内部判断> - 否决:每次 HasHooks/Execute 都多一层空指针判断,高频路径不必要开销.
func NewManager ¶
NewManager 创建 hook 管理器.
如果 cfg 为 nil,创建一个空的管理器(没有预注册的 hook). 消费层后续可以用 Register 动态添加 hook.
executor 参数是**必填**, nil 会 panic. 严格 DI 契约 (M1 方案 β) 不做 fallback 到 execenv.DefaultExecutor{}: 本地 CLI 场景由 engine.New 注入, 云端 SaaS 场景由 platform 层注入 sandbox.Backend. 绕过 DI 让测试用 nil 会让 "shell hook 在沙盒里真的会走后端" 的合同静默失守.
测试代码直接传 execenv.DefaultExecutor{} 即可 (零开销包装 os/exec, 和 老路径 bit-identical, 详见 core/pkg/execenv bench).
func ResolveHooksMgr ¶
ResolveHooksMgr 为本次 Run 解析有效的 Hook 管理器.
升华改进(ELEVATED): 单一函数封装"session/engine 合并"决策-- 所有触发点(session_start/end, pre/post_tool_use, pre/post_sampling, stop) 都调用此函数,无需在每个触发点重复判断 session hooks 是否存在.
参数:
- session: session-level Manager(来自 runConfig.sessionHooks,可为 nil)
- engine: engine-level Manager(来自 Engine.hooksMgr,可为 nil)
精妙之处(CLEVER): engine=nil 时也安全--返回 nil,调用方的 nil 守卫正常工作. session!=nil && engine=nil 时,直接用 session Manager(稀有但合法场景).
历史包袱(LEGACY): 早期设计中所有触发点直接引用 e.hooksMgr, 替换为 ResolveHooksMgr(runCfg.sessionHooks, e.hooksMgr) 即可升级.
func SessionHooksFromCtx ¶
SessionHooksFromCtx 从 context 取 session hooks(可能为 nil).
func (*Manager) Execute ¶
func (m *Manager) Execute(ctx context.Context, hookType HookType, env map[string]string) (*ExecuteResults, error)
Execute 同步执行指定类型的所有 hook.
按注册顺序依次执行,每个 hook 都会执行(即使前一个失败). 返回所有 hook 的执行结果,调用方可以检查是否有错误.
env 是注入到 shell 命令的环境变量(通过 integration.go 的 Build* 函数构建).
精妙之处(CLEVER): fail-open 策略--单个 hook 失败不阻断其余 hook 的执行. 如果 fail-close,一个写错的 hook 脚本就会让整个 Agent 瘫痪. 结果按执行顺序返回,调用方可以检查每个 hook 的状态. 如果没有注册的 hook,返回空结果(不是错误).
func (*Manager) ExecuteAll ¶
func (m *Manager) ExecuteAll(ctx context.Context, hookType HookType, env map[string]string) (syncResults *ExecuteResults, asyncResults <-chan *ExecuteResults)
ExecuteAll 执行指定类型的所有 hook(包括同步和异步的).
同步 hook 按顺序执行并等待结果; 异步 hook 在后台执行,结果通过 asyncResults channel 返回.
返回值:
- syncResults: 同步 hook 的执行结果
- asyncResults: 异步 hook 的结果 channel(可以 select 或忽略)
func (*Manager) ExecuteAsync ¶
func (m *Manager) ExecuteAsync(hookType HookType, env map[string]string) <-chan *ExecuteResults
ExecuteAsync 异步执行指定类型的所有 hook(不阻塞主流程).
适用于不需要等待结果的 hook,如 notification,session_end 等. 返回一个 channel,异步完成后会收到结果. 调用方可以选择等待结果或忽略.
设计决策:
- 使用 goroutine 执行,不阻塞调用方
- 使用 background context(不受调用方 context 取消影响) 因为异步 hook 通常在会话结束时触发,此时 context 可能已取消
- 设置独立的超时(每个 hook 的 EffectiveTimeout)
func (*Manager) HasHooks ¶
HasHooks 检查指定类型是否有注册的 hook. 用于 Engine 在关键路径上快速判断是否需要触发 hook(避免不必要的环境变量构建).
func (*Manager) Register ¶
Register 注册一个新的 hook 定义到指定类型.
同一个 hook 类型可以注册多个 handler,执行时按注册顺序依次执行. 返回错误如果 hookType 无效.
func (*Manager) SetObserver ¶
func (m *Manager) SetObserver(obs flyto.EventObserver)
SetObserver 设置可观测性接口. 升华改进(ELEVATED): Setter 注入而非构造函数参数-- NewManager 已在多处调用,加参数会破坏所有调用点. Setter 注入让现有代码零改动即可工作. 替代方案:修改 NewManager 签名(破坏所有现有调用点).
func (*Manager) Unregister ¶
Unregister 移除指定类型的所有 hook.
func (*Manager) UnregisterAllBySource ¶
UnregisterAllBySource 移除所有 hook 类型中来自特定来源的 hook.
典型用途:禁用插件时一次性清理该插件在所有 hook 类型上的注册.
升华改进(ELEVATED): 早期实现 的 clearRegisteredPluginHooks() 清除所有插件 hooks 并保留 callback hooks--这是全量操作,无法精准到单个插件. 我们按 Source 精准过滤,支持 N 个插件各自独立 disable,互不干扰. 替代方案:<全量清除所有非全局 hooks 再重建> - 否决:插件 A disable 时不应影响插件 B 的 hooks.
func (*Manager) UnregisterBySource ¶
UnregisterBySource 移除指定 hook 类型中来自特定来源的所有 hook.
source 对应 HookDef.Source 字段(空字符串 = 全局,插件名 = 该插件的 hooks). 若该类型下没有指定来源的 hook,静默返回(幂等操作).
用途:
- 禁用插件时,按类型精准移除该插件的 hooks,不影响全局 hooks
- 调试时临时移除某来源的特定类型 hooks
精妙之处(CLEVER): 过滤而非删除-- 保留所有 Source != source 的 hooks,生成新 slice 替换旧 slice. 若过滤后 slice 为空,用 delete 从 map 中彻底移除(节省内存,HasHooks 返回 false). 替代方案:<遍历删除 index,原地修改> - 否决:slice 原地删除需要 copy 填坑,代码复杂且有 off-by-one 风险.
type ShellHandler ¶
type ShellHandler struct {
// contains filtered or unexported fields
}
ShellHandler 通过 shell 执行 hook 命令. 走 runShellHook (M1 方案 β 之后), 让它满足 HookHandler 接口.
精妙之处(CLEVER): 共享 runShellHook 函数, 不自己重写 shell 执行逻辑. 唯一差别是 handler 自己持一个 execenv.Executor, 而不是从 Manager 借. 这让 ShellHandler 可以独立构造 (SDK 用户在 Manager 之外单独用), 同时 仍然遵守严格 DI 契约.
func NewShellHandler ¶
func NewShellHandler(def HookDef, executor execenv.Executor) *ShellHandler
NewShellHandler 从 HookDef 创建 shell 后端.
executor 参数是**必填**, nil 会 panic. 严格 DI 契约 (M1 方案 β) 不做 fallback. SDK 用户直接传 execenv.DefaultExecutor{}.
func (*ShellHandler) ExecuteHook ¶
func (h *ShellHandler) ExecuteHook(ctx context.Context, hookType HookType, env map[string]string) *HookResult
ExecuteHook 通过 shell 执行命令.