package hooks // Hook 执行后端 -- 可插拔的 hook 执行方式. // // 升华改进(ELEVATED): 早期只有 shell 命令一种执行方式(CLI 思维). // 但引擎有三种消费模式: // - CLI 模式:用户写 bash 脚本 → ShellHandler // - SDK 模式:Go 代码嵌入 → CallbackHandler(Go 函数回调) // - API 模式:远程调用 → WebhookHandler(HTTP POST,待实现) // // HookHandler 接口让 Manager.Execute 不关心后端-- // shell/callback/webhook 对 Manager 来说都是 Execute(ctx, env) → HookResult. // // 跨行业场景: // - 仓储 SDK 嵌入:CallbackHandler 直接调 WMS API 查库存,不走 shell // - 金融 API 模式:WebhookHandler 发审批请求到合规系统 // - 编程 CLI:ShellHandler 跑 lint/test 脚本 // // 替代方案:<原方案只有 shell command,SDK 用户想做 hook 也得写脚本> import ( "context" "time" "git.flytoex.net/yuanwei/flyto-agent/pkg/execenv" ) // ============================================================ // HookHandler - hook 执行后端接口 // ============================================================ // 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 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 } // HookHandlerFunc 是函数类型的 HookHandler 适配器. // 精妙之处(CLEVER): http.HandlerFunc 模式-- // 任何签名匹配的函数自动满足接口,不需要包装 struct. type HookHandlerFunc func(ctx context.Context, hookType HookType, env map[string]string) *HookResult // ExecuteHook 实现 HookHandler 接口. func (f HookHandlerFunc) ExecuteHook(ctx context.Context, hookType HookType, env map[string]string) *HookResult { return f(ctx, hookType, env) } // ============================================================ // ShellHandler - shell 命令执行后端 // ============================================================ // ShellHandler 通过 shell 执行 hook 命令. // 走 runShellHook (M1 方案 β 之后), 让它满足 HookHandler 接口. // // 精妙之处(CLEVER): 共享 runShellHook 函数, 不自己重写 shell 执行逻辑. // 唯一差别是 handler 自己持一个 execenv.Executor, 而不是从 Manager 借. // 这让 ShellHandler 可以独立构造 (SDK 用户在 Manager 之外单独用), 同时 // 仍然遵守严格 DI 契约. type ShellHandler struct { executor execenv.Executor command string timeout time.Duration } // NewShellHandler 从 HookDef 创建 shell 后端. // // executor 参数是**必填**, nil 会 panic. 严格 DI 契约 (M1 方案 β) 不做 // fallback. SDK 用户直接传 execenv.DefaultExecutor{}. func NewShellHandler(def HookDef, executor execenv.Executor) *ShellHandler { if executor == nil { panic("hooks.NewShellHandler: executor is required (M1 strict DI, no fallback)") } return &ShellHandler{ executor: executor, command: def.Command, timeout: def.EffectiveTimeout(), } } // ExecuteHook 通过 shell 执行命令. func (h *ShellHandler) ExecuteHook(ctx context.Context, hookType HookType, env map[string]string) *HookResult { def := HookDef{ Command: h.command, Timeout: int(h.timeout.Seconds()), } return runShellHook(ctx, h.executor, def, env) } // ============================================================ // CallbackHandler - Go 函数回调后端 // ============================================================ // 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"> type CallbackHandler struct { fn func(ctx context.Context, hookType HookType, env map[string]string) *HookResult timeout time.Duration } // NewCallbackHandler 创建 Go 回调后端. func NewCallbackHandler(fn func(ctx context.Context, hookType HookType, env map[string]string) *HookResult) *CallbackHandler { return &CallbackHandler{ fn: fn, timeout: time.Duration(DefaultTimeout) * time.Second, } } // NewCallbackHandlerWithTimeout 创建带超时的 Go 回调后端. func NewCallbackHandlerWithTimeout(fn func(ctx context.Context, hookType HookType, env map[string]string) *HookResult, timeout time.Duration) *CallbackHandler { return &CallbackHandler{ fn: fn, timeout: timeout, } } // ExecuteHook 执行 Go 回调函数. func (h *CallbackHandler) ExecuteHook(ctx context.Context, hookType HookType, env map[string]string) *HookResult { // 设置超时保护--Go 回调也可能阻塞(如调外部 API) timeout := h.timeout if timeout <= 0 { timeout = time.Duration(DefaultTimeout) * time.Second } execCtx, cancel := context.WithTimeout(ctx, timeout) defer cancel() start := time.Now() // 执行回调 result := h.fn(execCtx, hookType, env) if result == nil { result = &HookResult{ExitCode: 0} } result.Duration = time.Since(start) // 检查超时 if execCtx.Err() == context.DeadlineExceeded { result.Error = context.DeadlineExceeded result.ExitCode = -1 } return result }