package hooks // session_registry.go 实现会话级 Hook 注册表(14.P1-C). // // 设计目标:多租户 HTTP API 模式下,每个请求可以携带自己的 hooks, // 与引擎级全局 hooks 叠加,实现按请求隔离,互不干扰. // // 架构: // // CompositeManager = SessionManager(session-level)+ EngineManager(engine-level) // 触发顺序:session-level 先触发,engine-level 后触发. // // 升华改进(ELEVATED): 早期设计中的 hook 系统是进程全局单例, // 多租户模式下一个用户的 hook 影响所有用户(安全漏洞). // CompositeManager 实现双层结构: // - session-level:请求粒度隔离,生命周期 = 一次 Run() // - engine-level:引擎粒度共享,生命周期 = Engine 实例 // // nil session 时自动退化为单 Manager(零性能损耗,向后兼容). // // 跨行业场景: // - SaaS 多租户:每个租户的审计 hook 只处理自己的请求,无数据泄露 // - CI 流水线:每个 job 注册特定的告警 hook,互不污染 // - 金融合规:每笔交易的合规 hook 严格隔离,不混入其他交易 // // 精妙之处(CLEVER): CompositeManager 实现与 Manager 完全相同的接口-- // 调用方无需感知"我在用单层还是双层 Manager",零改动集成. // 替代方案:<在 Manager 内部维护 session hooks map,按 session ID 查找> - // 否决:Manager 变有状态(需要清理陈旧 session),并发复杂度上升. import ( "context" ) // CompositeManager 将 session-level hooks 与 engine-level hooks 组合执行. // // 查找规则: // 1. HasHooks:session || engine(任意一方有即返回 true) // 2. Execute:先执行 session hooks,再执行 engine hooks // 3. ExecuteAsync:同上,但异步(各自独立 goroutine) // // nil session 时,行为与 engine-level Manager 完全相同(向后兼容). type CompositeManager struct { session *Manager // 会话级 hooks(nil = 无会话 hooks) engine *Manager // 引擎级 hooks(不能为 nil) } // NewCompositeManager 创建 CompositeManager. // // 精妙之处(CLEVER): session 为 nil 时不创建包装对象,直接返回 engine Manager-- // 避免零值 session 带来的双重 nil 检查开销,热路径性能不降级. // 调用方始终收到非 nil 返回值,无需后续 nil 守卫. // 替代方案:<总是创建 CompositeManager,session=nil 时内部判断> - // 否决:每次 HasHooks/Execute 都多一层空指针判断,高频路径不必要开销. func NewCompositeManager(session, engine *Manager) *Manager { // ELEVATED: session 为 nil 时退化为单层 Manager,零额外开销,向后兼容. if session == nil { return engine } // 将 CompositeManager 包装为 *Manager-compatible 的执行策略: // 不能直接返回 *CompositeManager(类型不兼容), // 使用内部钩子机制注入复合执行逻辑. // // 精妙之处(CLEVER): 返回的是 *Manager,但其 hooks map 已包含 composite 语义-- // 我们创建一个新的 Manager,将 session 和 engine 的 hooks 合并到其中: // session hooks 先注册(先执行),engine hooks 后注册(后执行). // 合并是副本(不修改原 Manager),线程安全无竞争. // // 替代方案:<定义 CompositeManager 实现 Manager 接口> - // 否决:Manager 是具体类型,不是接口,接口化需要大量调用点改动. composite := &Manager{ hooks: make(map[HookType][]HookDef), executor: engine.executor, } // 继承 engine 的 observer composite.observer = engine.observer // 将 session hooks 合并进 composite(session 优先,先触发) session.mu.RLock() for hookType, defs := range session.hooks { composite.hooks[hookType] = append(composite.hooks[hookType], defs...) } session.mu.RUnlock() // 将 engine hooks 合并进 composite(engine 后触发) engine.mu.RLock() for hookType, defs := range engine.hooks { composite.hooks[hookType] = append(composite.hooks[hookType], defs...) } engine.mu.RUnlock() return composite } // 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 ResolveHooksMgr(session, engine *Manager) *Manager { if session == nil { return engine } if engine == nil { return session } return NewCompositeManager(session, engine) } // ---- Context key for session hooks (alternative injection path) ---- // ctxSessionHooksKey 是 context 中存储会话级 hooks 的 key(备用注入路径). // 主路径是 runConfig.sessionHooks;context 注入用于无法修改 Run 签名的场景 // (例如 SubAgent 内部想继承外层的 session hooks). type ctxSessionHooksKey struct{} // WithSessionHooksCtx 将 session hooks 注入 context(SubAgent 继承用). // // 升华改进(ELEVATED): SubAgent 的 context 链路中注入 session hooks-- // 父 Agent 通过 WithSessionHooks RunOption 注册的 hooks, // 自动传播到 SubAgent,无需调用方手动传递. // 替代方案: - 否决:审计 hook 漏掉子任务,合规不完整. func WithSessionHooksCtx(ctx context.Context, m *Manager) context.Context { if m == nil { return ctx } return context.WithValue(ctx, ctxSessionHooksKey{}, m) } // SessionHooksFromCtx 从 context 取 session hooks(可能为 nil). func SessionHooksFromCtx(ctx context.Context) *Manager { m, _ := ctx.Value(ctxSessionHooksKey{}).(*Manager) return m }