feat(block-h): plugin-contributable intents for fallback action mapper

This commit is contained in:
Agent Zero
2026-08-23 13:42:30 +02:00
parent d87fc4e55c
commit 49949066d3
+44
View File
@@ -6,9 +6,12 @@ Supports keyword-based intent detection for common CRM operations.
from __future__ import annotations from __future__ import annotations
import logging
import re import re
from typing import Any from typing import Any
logger = logging.getLogger(__name__)
# Precompiled patterns for intent detection # Precompiled patterns for intent detection
_PATTERNS = { _PATTERNS = {
"create_contact": re.compile( "create_contact": re.compile(
@@ -27,6 +30,37 @@ _PATTERNS = {
"help": re.compile(r"\b(help|what can you do|assist)\b", re.IGNORECASE), "help": re.compile(r"\b(help|what can you do|assist)\b", re.IGNORECASE),
} }
# Plugin-contributed intents: pattern -> callable(query, context) -> list[dict] | None.
# Registered via ``register_intent_pattern`` so plugins can extend the fallback
# mapper without touching core code (Block H / HC-A).
_CONTRIBUTED_INTENTS: list[tuple[re.Pattern[str], Any]] = []
def register_intent_pattern(
pattern: str | re.Pattern[str],
handler: Any,
*,
owner: str = "",
) -> None:
"""Register a plugin-contributed intent for the fallback action mapper.
Args:
pattern: Regex (compiled or raw string) matching the user query.
handler: Callable ``(query, context) -> list[dict] | None`` producing
proposed actions when the pattern matches.
owner: Optional plugin name, used by ``unregister_intent_patterns``.
"""
compiled = re.compile(pattern) if isinstance(pattern, str) else pattern
_CONTRIBUTED_INTENTS.append((compiled, handler))
def unregister_intent_patterns(owner: str) -> None:
"""Remove all intents contributed by ``owner`` (plugin deactivation)."""
global _CONTRIBUTED_INTENTS
_CONTRIBUTED_INTENTS = [
entry for entry in _CONTRIBUTED_INTENTS if getattr(entry[1], "owner_tag", None) != owner
]
# Name extraction patterns - using single-quoted strings to avoid escaping issues # Name extraction patterns - using single-quoted strings to avoid escaping issues
_NAME_PATTERNS = [ _NAME_PATTERNS = [
re.compile(r"\b(?:named|called|for)\s+['\"]?([^'\".,]+)['\"]?", re.IGNORECASE), re.compile(r"\b(?:named|called|for)\s+['\"]?([^'\".,]+)['\"]?", re.IGNORECASE),
@@ -147,6 +181,16 @@ def map_query_to_actions(query: str, context: dict[str, Any] | None = None) -> l
} }
) )
# --- Plugin-contributed intents (Block H / HC-A) ---
for pattern, handler in _CONTRIBUTED_INTENTS:
try:
if pattern.search(q):
contributed = handler(query, context)
if contributed:
actions.extend(contributed)
except Exception:
logger.warning("Contributed intent handler failed", exc_info=True)
# --- Generic fallback --- # --- Generic fallback ---
if not actions: if not actions:
if _PATTERNS["help"].search(q): if _PATTERNS["help"].search(q):