diff --git a/src/aipass/trigger/README.md b/src/aipass/trigger/README.md index 0af665f6..5722b82f 100644 --- a/src/aipass/trigger/README.md +++ b/src/aipass/trigger/README.md @@ -2,7 +2,7 @@ **Purpose:** Event bus for AIPass. Branches fire events, registered handlers react. Decouples producers from consumers — the module that detects a condition doesn't need to know what should happen next. **Module:** `aipass.trigger` -**Last Updated:** 2026-03-08 +**Last Updated:** 2026-03-17 ## Commands / Usage @@ -115,4 +115,4 @@ trigger/ --- -*Last Updated: 2026-03-08* +*Last Updated: 2026-03-17* diff --git a/src/aipass/trigger/apps/config.py b/src/aipass/trigger/apps/config.py index a1b7b2c8..ce71e1e4 100644 --- a/src/aipass/trigger/apps/config.py +++ b/src/aipass/trigger/apps/config.py @@ -20,3 +20,17 @@ TRIGGER_ROOT = Path(__file__).resolve().parents[1] # AIPass package root: .../aipass/ AIPASS_PKG_ROOT = TRIGGER_ROOT.parent + + +def print_introspection(): + """Display module introspection info.""" + try: + from aipass.cli.apps.modules.display import console + except ImportError: + from rich.console import Console + console = Console() + + console.print() + console.print("config Module") + console.print("Path constants — TRIGGER_ROOT and AIPASS_PKG_ROOT used by all trigger modules") + console.print() diff --git a/src/aipass/trigger/apps/handlers/error_registry.py b/src/aipass/trigger/apps/handlers/error_registry.py index aa183aef..60f2c129 100644 --- a/src/aipass/trigger/apps/handlers/error_registry.py +++ b/src/aipass/trigger/apps/handlers/error_registry.py @@ -46,6 +46,7 @@ from typing import Any, Dict, List, Optional from aipass.prax.apps.modules.logger import get_direct_logger from aipass.trigger.apps.config import TRIGGER_ROOT +from aipass.trigger.apps.handlers.json import json_handler logger = get_direct_logger() REGISTRY_FILE = TRIGGER_ROOT / "trigger_json" / "error_registry.json" @@ -590,6 +591,7 @@ def report( _save_registry(registry) result = dict(entry) result["is_new"] = False + json_handler.log_operation("error_registered", {"fingerprint": fingerprint[:12], "count": entry["count"]}) return result else: # New error - create entry @@ -616,6 +618,7 @@ def report( _save_registry(registry) result = dict(entry_dict) result["is_new"] = True + json_handler.log_operation("error_registered", {"fingerprint": fingerprint[:12], "count": 1}) return result except Exception: diff --git a/src/aipass/trigger/apps/handlers/error_reporter.py b/src/aipass/trigger/apps/handlers/error_reporter.py index 441f50d6..2ab01f77 100644 --- a/src/aipass/trigger/apps/handlers/error_reporter.py +++ b/src/aipass/trigger/apps/handlers/error_reporter.py @@ -25,6 +25,7 @@ from aipass.prax import logger from aipass.trigger.apps.handlers.error_registry import ( report as _registry_report, ) +from aipass.trigger.apps.handlers.json import json_handler def send_source_fix_email(entry: dict) -> bool: @@ -168,4 +169,5 @@ def report_error( except Exception: pass + json_handler.log_operation("error_reported", {"branch": component, "error_type": error_type}) return result diff --git a/src/aipass/trigger/apps/handlers/events/bulletin_created.py b/src/aipass/trigger/apps/handlers/events/bulletin_created.py index da867b73..b0d13176 100644 --- a/src/aipass/trigger/apps/handlers/events/bulletin_created.py +++ b/src/aipass/trigger/apps/handlers/events/bulletin_created.py @@ -25,6 +25,7 @@ import json from datetime import datetime from pathlib import Path from typing import Any, Dict, List +from aipass.trigger.apps.handlers.json import json_handler def _find_repo_root() -> Path: """Walk up from this file to find the repo root (contains AIPASS_REGISTRY.json).""" @@ -229,5 +230,7 @@ def handle_bulletin_created( # (the newly created bulletin should already be saved there) _propagate_bulletins_to_branches() + json_handler.log_operation("bulletin_event", {"success": True}) + except Exception: pass diff --git a/src/aipass/trigger/apps/handlers/events/cli.py b/src/aipass/trigger/apps/handlers/events/cli.py index 34191e65..a8fe6cc9 100644 --- a/src/aipass/trigger/apps/handlers/events/cli.py +++ b/src/aipass/trigger/apps/handlers/events/cli.py @@ -8,9 +8,12 @@ """CLI Event Handler - Handle CLI display events""" +from aipass.trigger.apps.handlers.json import json_handler + def handle_cli_header_displayed(**kwargs): """Handle cli_header_displayed event - logs when CLI displays headers""" # Handlers cannot use logger or print - event is already logged by core.py # This handler exists to demonstrate event registration works + json_handler.log_operation("cli_event", {"success": True}) pass diff --git a/src/aipass/trigger/apps/handlers/events/error_detected.py b/src/aipass/trigger/apps/handlers/events/error_detected.py index 91cd3167..1ad243a9 100644 --- a/src/aipass/trigger/apps/handlers/events/error_detected.py +++ b/src/aipass/trigger/apps/handlers/events/error_detected.py @@ -39,6 +39,7 @@ from datetime import datetime from pathlib import Path from typing import Any, Callable, Dict, List, Optional from aipass.trigger.apps.config import TRIGGER_ROOT +from aipass.trigger.apps.handlers.json import json_handler def _find_repo_root() -> Path: """Walk up from this file to find the repo root (contains AIPASS_REGISTRY.json).""" @@ -68,6 +69,18 @@ try: except ImportError: _REGISTRY_DISPATCH_AVAILABLE = False + def circuit_breaker_allows() -> bool: + return True + + def circuit_breaker_record_error() -> None: + pass + + def registry_should_dispatch(fingerprint: str) -> bool: + return True + + def registry_record_dispatch(fingerprint: str) -> None: + pass + # Legacy rate limiting (kept for backward compat when registry unavailable) _dispatch_timestamps: Dict[str, List[float]] = {} MAX_DISPATCHES_PER_WINDOW = 3 @@ -531,6 +544,8 @@ def handle_error_detected( from_branch='@trigger' ) + json_handler.log_operation("dispatch_sent", {"recipient": recipient}) + # Record dispatch for tracking if _REGISTRY_DISPATCH_AVAILABLE and fingerprint: # Medic v2: per-fingerprint dispatch tracking + circuit breaker diff --git a/src/aipass/trigger/apps/handlers/events/error_logged.py b/src/aipass/trigger/apps/handlers/events/error_logged.py index 15a8a2b8..97bea020 100644 --- a/src/aipass/trigger/apps/handlers/events/error_logged.py +++ b/src/aipass/trigger/apps/handlers/events/error_logged.py @@ -33,6 +33,7 @@ from datetime import datetime from pathlib import Path from typing import Any, Dict, List from aipass.trigger.apps.config import TRIGGER_ROOT +from aipass.trigger.apps.handlers.json import json_handler def _find_repo_root() -> Path: """Walk up from this file to find the repo root (contains AIPASS_REGISTRY.json).""" @@ -314,5 +315,7 @@ def handle_error_logged( # Record dispatch for rate limiting _record_dispatch(recipient) + json_handler.log_operation("error_logged_event", {"success": True}) + except Exception: return diff --git a/src/aipass/trigger/apps/handlers/events/memory.py b/src/aipass/trigger/apps/handlers/events/memory.py index c42f0f5c..3985047b 100644 --- a/src/aipass/trigger/apps/handlers/events/memory.py +++ b/src/aipass/trigger/apps/handlers/events/memory.py @@ -13,6 +13,8 @@ Placeholder for future memory event handling. from pathlib import Path +from aipass.trigger.apps.handlers.json import json_handler + def handle_memory_saved(**kwargs): @@ -26,4 +28,5 @@ def handle_memory_saved(**kwargs): # Future: Check line count and trigger rollover # if lines > 600: # trigger_rollover(branch) + json_handler.log_operation("memory_event", {"success": True}) pass diff --git a/src/aipass/trigger/apps/handlers/events/memory_template_updated.py b/src/aipass/trigger/apps/handlers/events/memory_template_updated.py index bc7863b8..8ecab567 100644 --- a/src/aipass/trigger/apps/handlers/events/memory_template_updated.py +++ b/src/aipass/trigger/apps/handlers/events/memory_template_updated.py @@ -21,6 +21,8 @@ Event data expected: from pathlib import Path from typing import Any +from aipass.trigger.apps.handlers.json import json_handler + # Path resolution not needed - this handler delegates to Memory Bank's pusher @@ -36,4 +38,5 @@ def handle_memory_template_updated(**kwargs: Any) -> None: **kwargs: Event data (template_name, updated_by, timestamp, etc.) """ # memory_bank integration (optional, requires memory_bank package) + json_handler.log_operation("memory_template_event", {"success": True}) pass diff --git a/src/aipass/trigger/apps/handlers/events/memory_threshold_exceeded.py b/src/aipass/trigger/apps/handlers/events/memory_threshold_exceeded.py index 8ccf044a..95e76864 100644 --- a/src/aipass/trigger/apps/handlers/events/memory_threshold_exceeded.py +++ b/src/aipass/trigger/apps/handlers/events/memory_threshold_exceeded.py @@ -28,6 +28,8 @@ from datetime import datetime from pathlib import Path from typing import Any +from aipass.trigger.apps.handlers.json import json_handler + # Path resolution not needed - this handler uses only event data passed in kwargs @@ -156,5 +158,7 @@ def handle_memory_threshold_exceeded( deliver_email_to_branch(target_branch, email_data) + json_handler.log_operation("memory_threshold_event", {"success": True}) + except Exception: pass diff --git a/src/aipass/trigger/apps/handlers/events/plan_file.py b/src/aipass/trigger/apps/handlers/events/plan_file.py index 2be90682..eea52872 100644 --- a/src/aipass/trigger/apps/handlers/events/plan_file.py +++ b/src/aipass/trigger/apps/handlers/events/plan_file.py @@ -27,6 +27,7 @@ from datetime import datetime, timezone from pathlib import Path from typing import Optional from aipass.trigger.apps.config import TRIGGER_ROOT, AIPASS_PKG_ROOT +from aipass.trigger.apps.handlers.json import json_handler def _find_repo_root() -> Path: """Walk up from this file to find the repo root (contains AIPASS_REGISTRY.json).""" @@ -133,6 +134,8 @@ def handle_plan_file_created(path: str, **kwargs): _save_registry(registry) + json_handler.log_operation("plan_event", {"success": True}) + except Exception as e: _log_error(f"handle_plan_file_created failed for {path}: {e}") diff --git a/src/aipass/trigger/apps/handlers/events/registry.py b/src/aipass/trigger/apps/handlers/events/registry.py index d85f5956..38f3221a 100644 --- a/src/aipass/trigger/apps/handlers/events/registry.py +++ b/src/aipass/trigger/apps/handlers/events/registry.py @@ -10,6 +10,8 @@ from pathlib import Path +from aipass.trigger.apps.handlers.json import json_handler + def setup_handlers(): @@ -66,3 +68,5 @@ def setup_handlers(): trigger.on('bulletin_created', handle_bulletin_created) trigger.on('memory_threshold_exceeded', handle_memory_threshold_exceeded) trigger.on('memory_template_updated', handle_memory_template_updated) + + json_handler.log_operation("handlers_registered", {"success": True}) diff --git a/src/aipass/trigger/apps/handlers/events/startup.py b/src/aipass/trigger/apps/handlers/events/startup.py index cfb6aa27..fd6d87ed 100644 --- a/src/aipass/trigger/apps/handlers/events/startup.py +++ b/src/aipass/trigger/apps/handlers/events/startup.py @@ -24,6 +24,7 @@ from pathlib import Path from datetime import datetime, timedelta from typing import Any, Callable, Dict, List, Optional, Set from aipass.trigger.apps.config import TRIGGER_ROOT +from aipass.trigger.apps.handlers.json import json_handler SYSTEM_LOGS_DIR = TRIGGER_ROOT.parent / "system_logs" TRIGGER_DATA_FILE = TRIGGER_ROOT / "trigger_json" / "trigger_data.json" @@ -333,6 +334,8 @@ def _run_error_catchup(fire_event: Optional[Callable[..., None]] = None) -> None _save_trigger_data(data) + json_handler.log_operation("startup_catchup", {"errors_found": len(errors)}) + except Exception: return @@ -340,15 +343,14 @@ def _run_error_catchup(fire_event: Optional[Callable[..., None]] = None) -> None def _run_memory_bank_check() -> None: """Run Memory Bank rollover check if available. - Lazy-imports Memory Bank watcher to avoid hard dependency. + Uses memory's public modules API to avoid cross-branch handler guard. Silent failure - handlers cannot use logger or print. """ try: - import importlib - mod = importlib.import_module('MEMORY_BANK.apps.handlers.monitor.memory_watcher') - mod.check_and_rollover() + from aipass.memory.apps.modules.rollover import check_and_rollover + check_and_rollover() except ImportError: - return # Memory Bank not available + return # Memory not available except Exception: return diff --git a/src/aipass/trigger/apps/handlers/events/warning_logged.py b/src/aipass/trigger/apps/handlers/events/warning_logged.py index e407dc11..026ab0fd 100644 --- a/src/aipass/trigger/apps/handlers/events/warning_logged.py +++ b/src/aipass/trigger/apps/handlers/events/warning_logged.py @@ -25,6 +25,7 @@ Event data expected: from pathlib import Path from typing import Any +from aipass.trigger.apps.handlers.json import json_handler @@ -65,3 +66,4 @@ def handle_warning_logged( # # Suppress unused variable warnings - all params are part of event contract _ = (branch, message, error_hash, timestamp, log_file, module_name, level, kwargs) + json_handler.log_operation("warning_logged_event", {"success": True}) diff --git a/src/aipass/trigger/apps/handlers/log_watcher.py b/src/aipass/trigger/apps/handlers/log_watcher.py index 99a54b05..e0e9a2c5 100644 --- a/src/aipass/trigger/apps/handlers/log_watcher.py +++ b/src/aipass/trigger/apps/handlers/log_watcher.py @@ -30,6 +30,7 @@ from datetime import datetime, timedelta from pathlib import Path from typing import Any, Dict, Set, Optional, Callable from aipass.trigger.apps.config import TRIGGER_ROOT, AIPASS_PKG_ROOT +from aipass.trigger.apps.handlers.json import json_handler from aipass.prax.apps.modules.logger import get_direct_logger @@ -76,6 +77,9 @@ try: except ImportError: _REGISTRY_AVAILABLE = False + def registry_report(error_type: str, message: str, component: str, log_path: str = "", severity: str = "medium") -> dict: + return {"is_new": False, "count": 0} + # Try to import watchdog try: from watchdog.observers import Observer as WatchdogObserver @@ -530,6 +534,7 @@ class BranchLogWatcher(WatchdogFileSystemEventHandler if WATCHDOG_AVAILABLE else last_seen=result.get('last_seen', ''), count=error_count, ) + json_handler.log_operation("error_detected_in_log", {"branch": branch, "log_path": log_path}) else: logger.warning( "Cannot fire error_detected event: _fire_event callback not set " @@ -560,6 +565,7 @@ class BranchLogWatcher(WatchdogFileSystemEventHandler if WATCHDOG_AVAILABLE else error_hash=error_hash, timestamp=parsed['timestamp'] ) + json_handler.log_operation("error_detected_in_log", {"branch": branch, "log_path": log_path}) else: logger.warning( "Cannot fire error_detected event: _fire_event callback not set " diff --git a/src/aipass/trigger/apps/handlers/medic_state.py b/src/aipass/trigger/apps/handlers/medic_state.py index 25585c9f..a9be0a8b 100644 --- a/src/aipass/trigger/apps/handlers/medic_state.py +++ b/src/aipass/trigger/apps/handlers/medic_state.py @@ -22,6 +22,7 @@ from pathlib import Path from typing import Any, Dict, List from aipass.trigger.apps.config import TRIGGER_ROOT +from aipass.trigger.apps.handlers.json import json_handler TRIGGER_CONFIG_FILE = TRIGGER_ROOT / "trigger_json" / "trigger_config.json" MEDIC_SUPPRESSED_LOG = TRIGGER_ROOT / "logs" / "medic_suppressed.log" @@ -91,6 +92,7 @@ def set_enabled(enabled: bool) -> bool: data['timestamp'] = datetime.now().strftime("%Y-%m-%d") if write_config(data): + json_handler.log_operation("state_persisted", {"key": "medic_enabled", "value": enabled}) return True return False diff --git a/src/aipass/trigger/apps/handlers/watchers/log_watcher.py b/src/aipass/trigger/apps/handlers/watchers/log_watcher.py index 2bec6c58..2481743f 100644 --- a/src/aipass/trigger/apps/handlers/watchers/log_watcher.py +++ b/src/aipass/trigger/apps/handlers/watchers/log_watcher.py @@ -35,6 +35,7 @@ from typing import Dict, Any # System logs directory (package-relative via config) from aipass.trigger.apps.config import TRIGGER_ROOT +from aipass.trigger.apps.handlers.json import json_handler SYSTEM_LOGS_DIR = TRIGGER_ROOT.parent.parent / "system_logs" # Try to import watchdog @@ -257,8 +258,10 @@ class LogFileWatcher(WatchdogFileSystemEventHandler if WATCHDOG_AVAILABLE else o if level == 'error': trigger.fire('error_logged', **event_data) + json_handler.log_operation("system_log_event", {"level": level, "module": module_name}) elif level == 'warning': trigger.fire('warning_logged', **event_data) + json_handler.log_operation("system_log_event", {"level": level, "module": module_name}) except Exception: pass diff --git a/src/aipass/trigger/apps/modules/branch_log_events.py b/src/aipass/trigger/apps/modules/branch_log_events.py index 72e3e085..873f86b7 100644 --- a/src/aipass/trigger/apps/modules/branch_log_events.py +++ b/src/aipass/trigger/apps/modules/branch_log_events.py @@ -22,6 +22,7 @@ from pathlib import Path from aipass.prax.apps.modules.logger import system_logger as logger +from aipass.trigger.apps.handlers.json import json_handler from aipass.trigger.apps.modules.core import trigger from aipass.trigger.apps.handlers.log_watcher import ( @@ -148,7 +149,7 @@ def handle_command(command: str, args: list) -> bool: # Handle module-name routing (drone @trigger branch_log_events ) if command == "branch_log_events": if not args: - print_help() + print_introspection() return True subcommand = args[0] remaining = args[1:] @@ -184,6 +185,7 @@ def handle_command(command: str, args: list) -> bool: reset_hashes() console.print("✅ Error deduplication hashes cleared") + json_handler.log_operation("watcher_command", {"command": command}) return True diff --git a/src/aipass/trigger/apps/modules/core.py b/src/aipass/trigger/apps/modules/core.py index c017b701..b972483a 100644 --- a/src/aipass/trigger/apps/modules/core.py +++ b/src/aipass/trigger/apps/modules/core.py @@ -18,6 +18,7 @@ from pathlib import Path from aipass.prax.apps.modules.logger import system_logger as logger +from aipass.trigger.apps.handlers.json import json_handler from typing import Callable @@ -189,7 +190,10 @@ def handle_command(command: str, args: list) -> bool: # Handle module-name routing (drone @trigger core ) if command == "core": - if not args or args[0] in ['--help', '-h', 'help']: + if not args: + print_introspection() + return True + if args[0] in ['--help', '-h', 'help']: _print_help(console) return True return handle_command(args[0], args[1:]) @@ -216,6 +220,7 @@ def handle_command(command: str, args: list) -> bool: logger.warning(f"[TRIGGER] Ignoring unparseable arg: {arg}") Trigger.fire(event_name, **data) console.print(f"[green]Fired event:[/green] {event_name}") + json_handler.log_operation("event_fired", {"event": event_name}) if data: for k, v in data.items(): console.print(f" [dim]{k}={v}[/dim]") diff --git a/src/aipass/trigger/apps/modules/errors.py b/src/aipass/trigger/apps/modules/errors.py index e757acc3..88eb6b5c 100644 --- a/src/aipass/trigger/apps/modules/errors.py +++ b/src/aipass/trigger/apps/modules/errors.py @@ -26,6 +26,7 @@ from typing import Optional from aipass.prax.apps.modules.logger import system_logger as logger +from aipass.trigger.apps.handlers.json import json_handler from aipass.trigger.apps.handlers.error_registry import ( query, get_entry, update_status, clear_resolved, get_stats, get_circuit_breaker_status, circuit_breaker_reset, @@ -153,7 +154,8 @@ def handle_command(command: str, args: list) -> bool: return False if not args: - return _cmd_list(console, []) + print_introspection() + return True if args[0] in ['--help', '-h', 'help']: print_help() return True @@ -168,7 +170,9 @@ def handle_command(command: str, args: list) -> bool: } if sub in routes: - return routes[sub](console, rest) + result = routes[sub](console, rest) + json_handler.log_operation("error_command", {"subcommand": sub}) + return result error(f"Unknown subcommand: {sub}", suggestion="Run 'drone @trigger errors help' for available commands") return True diff --git a/src/aipass/trigger/apps/modules/log_events.py b/src/aipass/trigger/apps/modules/log_events.py index d6e6d8d2..cbac5c13 100644 --- a/src/aipass/trigger/apps/modules/log_events.py +++ b/src/aipass/trigger/apps/modules/log_events.py @@ -21,6 +21,7 @@ from pathlib import Path from aipass.prax.apps.modules.logger import system_logger as logger +from aipass.trigger.apps.handlers.json import json_handler # Import handler functions from aipass.trigger.apps.handlers.watchers.log_watcher import ( @@ -129,7 +130,10 @@ def handle_command(command: str, args: list) -> bool: # Handle module-name routing (drone @trigger log_events ) if command == "log_events": - if not args or args[0] in ['--help', '-h', 'help']: + if not args: + print_introspection() + return True + if args[0] in ['--help', '-h', 'help']: print_help() return True return handle_command(args[0], args[1:]) @@ -156,6 +160,7 @@ def handle_command(command: str, args: list) -> bool: console.print(f" Active: {info['active']}") console.print(f" Log dir: {info['log_dir']}") + json_handler.log_operation("log_watcher_command", {"command": command}) return True diff --git a/src/aipass/trigger/apps/modules/medic.py b/src/aipass/trigger/apps/modules/medic.py index 036c5e8b..1ca0600f 100644 --- a/src/aipass/trigger/apps/modules/medic.py +++ b/src/aipass/trigger/apps/modules/medic.py @@ -24,6 +24,7 @@ from pathlib import Path from aipass.prax.apps.modules.logger import system_logger as logger +from aipass.trigger.apps.handlers.json import json_handler from aipass.trigger.apps.handlers.medic_state import ( is_enabled, @@ -187,7 +188,7 @@ def handle_command(command: str, args: list) -> bool: # Handle module-name routing (drone @trigger medic ) if command == "medic": if not args: - print_help() + print_introspection() return True if args[0] in ['--help', '-h', 'help']: print_help() @@ -305,6 +306,7 @@ def handle_command(command: str, args: list) -> bool: if not enabled: console.print(" [dim]All error dispatch suppressed. Errors logged to medic_suppressed.log[/dim]") + json_handler.log_operation("medic_toggled", {"command": command}) return True