-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhooks.py
More file actions
121 lines (98 loc) · 4.64 KB
/
Copy pathhooks.py
File metadata and controls
121 lines (98 loc) · 4.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# -*- coding: utf-8 -*-
"""Central DietCode hook wiring — single registration surface for production."""
from __future__ import annotations
import json
import logging
from typing import Any, Callable
from plugins.dietcode.lib.agent.features import is_governance_enabled, is_joyzoning_enabled
from plugins.dietcode.lib.runtime.hook_registry import HOOK_CHAINS, load_hook_chain
logger = logging.getLogger(__name__)
_ON_SESSION_START: tuple[Callable[..., Any], ...] = ()
_ON_SESSION_END: tuple[Callable[..., Any], ...] = ()
_POST_TOOL_CALL: tuple[Callable[..., Any], ...] = ()
_PRE_TOOL_CALL: tuple[Callable[..., Any], ...] = ()
_TRANSFORM_TOOL_RESULT: tuple[Callable[..., Any], ...] = ()
def _ensure_handlers() -> None:
global _ON_SESSION_START, _ON_SESSION_END, _POST_TOOL_CALL, _PRE_TOOL_CALL, _TRANSFORM_TOOL_RESULT
if _ON_SESSION_START:
return
_ON_SESSION_START = load_hook_chain("on_session_start")
_ON_SESSION_END = load_hook_chain("on_session_end")
_POST_TOOL_CALL = load_hook_chain("post_tool_call")
_PRE_TOOL_CALL = load_hook_chain("pre_tool_call")
_TRANSFORM_TOOL_RESULT = load_hook_chain("transform_tool_result")
def _run_all(hook_name: str, handlers: tuple[Callable[..., Any], ...]) -> None:
def _wrapped(**kwargs: Any) -> None:
_ensure_handlers()
for handler in handlers:
try:
handler(**kwargs)
except Exception as exc:
logger.warning("DietCode hook %s (%s) failed: %s", hook_name, handler.__name__, exc)
_wrapped.__name__ = f"dietcode_{hook_name}"
return _wrapped
def _run_pre_tool_call(handlers: tuple[Callable[..., Any], ...]) -> Callable[..., Any]:
def _wrapped(**kwargs: Any) -> dict[str, str] | None:
_ensure_handlers()
for handler in handlers:
try:
result = handler(**kwargs)
except Exception as exc:
logger.warning("DietCode pre_tool_call (%s) failed: %s", handler.__name__, exc)
if is_joyzoning_enabled():
from plugins.dietcode.lib.agent.joyzoning.convergence_gate import block_dict
return block_dict(f"Convergence gate unavailable: {exc}")
continue
if isinstance(result, dict) and result.get("action") == "block":
return result
return None
_wrapped.__name__ = "dietcode_pre_tool_call"
return _wrapped
def _governance_unavailable_payload(exc: Exception) -> str:
return json.dumps(
{
"success": False,
"error": "[GOVERNANCE FAULT] Governance enforcement unavailable.",
"detail": str(exc),
"recovery_plan": (
"Retry once after fixing the underlying error. If this persists, "
"set joyzoning.governance.enabled: false or disable the DietCode plugin."
),
},
ensure_ascii=False,
)
def _run_transform(handlers: tuple[Callable[..., Any], ...]) -> Callable[..., Any]:
def _wrapped(**kwargs: Any) -> str | None:
_ensure_handlers()
for handler in handlers:
try:
result = handler(**kwargs)
except Exception as exc:
logger.warning("DietCode transform_tool_result (%s) failed: %s", handler.__name__, exc)
if is_governance_enabled():
return _governance_unavailable_payload(exc)
continue
if isinstance(result, str) and result.strip():
try:
from plugins.dietcode.lib.runtime.audit_hooks import capture_governance_transform_result
capture_governance_transform_result(result)
except Exception:
pass
return result
return None
_wrapped.__name__ = "dietcode_transform_tool_result"
return _wrapped
def register_all_hooks(ctx) -> None:
"""Register consolidated hooks (one callback per hook name — no duplicate firing)."""
_ensure_handlers()
ctx.register_hook("on_session_start", _run_all("on_session_start", _ON_SESSION_START))
ctx.register_hook("on_session_end", _run_all("on_session_end", _ON_SESSION_END))
ctx.register_hook("post_tool_call", _run_all("post_tool_call", _POST_TOOL_CALL))
ctx.register_hook("pre_tool_call", _run_pre_tool_call(_PRE_TOOL_CALL))
ctx.register_hook("transform_tool_result", _run_transform(_TRANSFORM_TOOL_RESULT))
def hook_chain_summary() -> dict[str, list[str]]:
"""Return declared hook chains for doctor/audit surfaces."""
return {
hook_name: [f"{module}:{attr}" for module, attr in specs]
for hook_name, specs in HOOK_CHAINS.items()
}