// Package hooks 是进程内钩子总线。决策按 block/deny、ask、modify、allow 取更强的一条。 // 没有表态的处理函数不改变结果。 package hooks import "sync" type Event string const ( SessionStart Event = "SessionStart" UserPromptSubmit Event = "UserPromptSubmit" PreToolUse Event = "PreToolUse" PermissionRequest Event = "PermissionRequest" PostToolUse Event = "PostToolUse" PostToolUseFailure Event = "PostToolUseFailure" PostToolBatch Event = "PostToolBatch" Stop Event = "Stop" SubagentStart Event = "SubagentStart" SubagentStop Event = "SubagentStop" PreCompact Event = "PreCompact" SessionEnd Event = "SessionEnd" ) type Decision string const ( Allow Decision = "allow" Deny Decision = "deny" Ask Decision = "ask" Modify Decision = "modify" Block Decision = "block" ) // Call 是一次事件。工具字段只在工具事件上有意义。 type Call struct { Event Event Tool string Args string Result string IsError bool Turn int LastText string Continuations int ToolCalls int } // Outcome 是一个处理函数的表态。空 Decision 表示不插手。 type Outcome struct { Decision Decision Reason string Args string Notice string Replacement string } type Handler func(Call) Outcome type Bus struct { mu sync.Mutex hs []Handler } func New() *Bus { return &Bus{} } func (b *Bus) On(h Handler) { if b == nil || h == nil { return } b.mu.Lock() b.hs = append(b.hs, h) b.mu.Unlock() } // Emit 按优先级合并表态。deny 与 block 同级,先登记的保留。 func (b *Bus) Emit(c Call) Outcome { if b == nil { return Outcome{} } b.mu.Lock() hs := append([]Handler(nil), b.hs...) b.mu.Unlock() var merged Outcome for _, h := range hs { merged = prefer(merged, h(c)) } return merged } func prefer(cur, next Outcome) Outcome { if rank(next.Decision) > rank(cur.Decision) { return next } return cur } func rank(d Decision) int { switch d { case Block, Deny: return 4 case Ask: return 3 case Modify: return 2 case Allow: return 1 default: return 0 } } // AdaptBefore 把「返回错误即拒绝」收成 PreToolUse 处理函数。 func AdaptBefore(fn func(name, args string) error) Handler { return func(c Call) Outcome { if c.Event != PreToolUse || fn == nil { return Outcome{} } if err := fn(c.Tool, c.Args); err != nil { return Outcome{Decision: Deny, Reason: err.Error()} } return Outcome{Decision: Allow} } } // AdaptAfter 把工具结果观察收成 PostToolUse 或失败事件。它不改写结果。 func AdaptAfter(fn func(name, args, result string, isError bool)) Handler { return func(c Call) Outcome { if fn == nil { return Outcome{} } switch c.Event { case PostToolUse, PostToolUseFailure: fn(c.Tool, c.Args, c.Result, c.IsError) } return Outcome{} } }