fix(hook): keep composite hooks backward compatible

Avoid AttributeError regressions when hooks define their own __init__ or when a CompositeHook wraps another composite.

Made-with: Cursor
This commit is contained in:
Xubin Ren 2026-04-08 15:38:41 +00:00 committed by Xubin Ren
parent d88be08bfd
commit 6bf101c79b
3 changed files with 32 additions and 1 deletions

View File

@ -65,6 +65,7 @@ class CompositeHook(AgentHook):
__slots__ = ("_hooks",)
def __init__(self, hooks: list[AgentHook]) -> None:
super().__init__()
self._hooks = list(hooks)
def wants_streaming(self) -> bool:
@ -72,7 +73,7 @@ class CompositeHook(AgentHook):
async def _for_each_hook_safe(self, method_name: str, *args: Any, **kwargs: Any) -> None:
for h in self._hooks:
if h._reraise:
if getattr(h, "_reraise", False):
await getattr(h, method_name)(*args, **kwargs)
continue

View File

@ -27,6 +27,7 @@ class _SubagentHook(AgentHook):
"""Logging-only hook for subagent execution."""
def __init__(self, task_id: str) -> None:
super().__init__()
self._task_id = task_id
async def before_execute_tools(self, context: AgentHookContext) -> None:

View File

@ -232,6 +232,35 @@ async def test_composite_empty_hooks_no_ops():
assert hook.finalize_content(ctx, "test") == "test"
@pytest.mark.asyncio
async def test_composite_supports_legacy_hook_init_without_super():
calls: list[str] = []
class LegacyHook(AgentHook):
def __init__(self, label: str) -> None:
self.label = label
async def before_iteration(self, context: AgentHookContext) -> None:
calls.append(self.label)
hook = CompositeHook([LegacyHook("legacy")])
await hook.before_iteration(_ctx())
assert calls == ["legacy"]
@pytest.mark.asyncio
async def test_composite_can_wrap_another_composite():
calls: list[str] = []
class Inner(AgentHook):
async def before_iteration(self, context: AgentHookContext) -> None:
calls.append("inner")
hook = CompositeHook([CompositeHook([Inner()])])
await hook.before_iteration(_ctx())
assert calls == ["inner"]
# ---------------------------------------------------------------------------
# Integration: AgentLoop with extra hooks
# ---------------------------------------------------------------------------