feat(prax): introspection standard compliance + monitor Phase 1 polling fallback

Co-Authored-By: @prax <prax@aipass>
This commit is contained in:
AIOSAI
2026-03-18 15:29:41 -07:00
co-authored by @prax
parent c96d1a353f
commit 194c471af9
20 changed files with 467 additions and 182 deletions
+7
View File
@@ -13,6 +13,13 @@
"subject": "Seedgo log_structure standard update for hierarchical placement",
"date_closed": "2026-03-09",
"location": "prax"
},
{
"plan_id": "FPLAN-0039",
"type": "FPLAN",
"subject": "Dashboard Pipeline — Full Lifecycle",
"date_closed": "2026-03-18",
"location": "prax"
}
]
}
+9 -9
View File
@@ -85,16 +85,16 @@ prax/
│ ├── prax.py # Entry point (CLI)
│ ├── modules/
│ │ ├── logger.py # SystemLogger (public API)
│ │ ├── monitor_module.py # Mission Control
│ │ ├── agent_status_module.py # Agent status overview
│ │ ├── monitor.py # Mission Control
│ │ ├── agent_status.py # Agent status overview
│ │ ├── dashboard.py # System dashboard
│ │ ├── discover_module.py # Module discovery
│ │ ├── init_module.py # Logging system initialization
│ │ ├── log_audit_module.py # Log file audit
│ │ ├── run_module.py # Continuous logging mode
│ │ ├── shutdown_module.py # Logging system shutdown
│ │ ├── status_module.py # System status / STATUS sync
│ │ └── terminal_module.py # Terminal output toggle
│ │ ├── discover.py # Module discovery
│ │ ├── init_prax.py # Logging system initialization
│ │ ├── log_audit.py # Log file audit
│ │ ├── run.py # Continuous logging mode
│ │ ├── shutdown.py # Logging system shutdown
│ │ ├── status.py # System status / STATUS sync
│ │ └── terminal.py # Terminal output toggle
│ └── handlers/
│ ├── central/ # Central file reader
│ ├── config/ # Configuration loading
+20 -3
View File
@@ -74,18 +74,35 @@ def get_system_logs_dir() -> Path:
def get_module_logs_dir(module_name: str) -> Path:
"""Get the branch-root logs directory for a module.
Returns ECOSYSTEM_ROOT / module_name / "logs", creating it if needed.
Checks ECOSYSTEM_ROOT (src/aipass/) first, then SRC_ROOT (src/) for
branches that live outside the aipass namespace (e.g., commons).
This is the primary local log directory resolver for the two-tier
model (system_logs/ for central aggregation + branch-root logs/
for local debugging).
Args:
module_name: Module name (e.g., "flow", "prax", "trigger")
module_name: Module name (e.g., "flow", "prax", "commons")
Returns:
Path to the module's branch-root logs directory
"""
logs_dir = ECOSYSTEM_ROOT / module_name / "logs"
# Standard: src/aipass/{module}/logs
branch_dir = ECOSYSTEM_ROOT / module_name
if branch_dir.exists():
logs_dir = branch_dir / "logs"
logs_dir.mkdir(parents=True, exist_ok=True)
return logs_dir
# Fallback: src/{module}/logs for branches outside aipass namespace
src_root = ECOSYSTEM_ROOT.parent
alt_branch_dir = src_root / module_name
if alt_branch_dir.exists():
logs_dir = alt_branch_dir / "logs"
logs_dir.mkdir(parents=True, exist_ok=True)
return logs_dir
# Default: create under ECOSYSTEM_ROOT (original behavior for new branches)
logs_dir = branch_dir / "logs"
logs_dir.mkdir(parents=True, exist_ok=True)
return logs_dir
@@ -78,17 +78,20 @@ def get_calling_module_path() -> Optional[str]:
del frame
_AIPASS_PKG_ROOT = Path(__file__).resolve().parents[4] # logging/ → handlers/ → apps/ → prax/ → aipass/
_SRC_ROOT = _AIPASS_PKG_ROOT.parent # aipass/ → src/ (contains branches outside aipass namespace)
def detect_branch_from_path(module_path: str) -> Optional[str]:
"""Detect branch name from module file path
The package structure is: .../src/aipass/{module}/apps/...
Resolves relative to the aipass package root found via __file__.
Also handles branches outside src/aipass/ (e.g., src/commons/).
Examples:
.../src/aipass/flow/apps/module.py → "flow"
.../src/aipass/prax/apps/module.py → "prax"
.../src/aipass/drone/apps/branch.py → "drone"
.../src/commons/apps/module.py → "commons"
Returns:
Module/branch name (e.g., "flow") or None
@@ -96,8 +99,10 @@ def detect_branch_from_path(module_path: str) -> Optional[str]:
if not module_path:
return None
path = Path(module_path).resolve()
# Primary: src/aipass/{branch}/...
try:
path = Path(module_path).resolve()
relative = path.relative_to(_AIPASS_PKG_ROOT)
# relative is like: flow/apps/module.py → parts[0] = "flow"
if len(relative.parts) >= 2:
@@ -107,4 +112,14 @@ def detect_branch_from_path(module_path: str) -> Optional[str]:
except ValueError:
pass
# Fallback: src/{branch}/... for branches outside src/aipass/ (e.g., commons)
try:
relative = path.relative_to(_SRC_ROOT)
if len(relative.parts) >= 2 and relative.parts[0] != "aipass":
branch = relative.parts[0]
json_handler.log_operation("introspection_resolved", {"module_path": module_path, "branch": branch, "outside_aipass": True})
return branch
except ValueError:
pass
return None
@@ -104,7 +104,7 @@ def setup_individual_logger(module_name: str) -> logging.Logger:
if branch_path:
branch_name = Path(branch_path).name
else:
branch_name = "prax" # Fallback to prax if no branch detected
branch_name = "unknown_branch" # Visible fallback — never silently route to prax
# Create formatter (shared by all handlers)
formatter = logging.Formatter(
@@ -20,7 +20,7 @@ Flow:
5. Include branch attribution in all events
Thread Safety:
- Designed to run in monitor_module.py's file watcher thread
- Designed to run in monitor.py's file watcher thread
- Uses MonitoringQueue's thread-safe enqueue
- Watchdog observers run in their own threads
@@ -14,7 +14,7 @@ Watchdog FileSystemEventHandler that processes filesystem events and:
2. Fires trigger events for cross-module integration
3. Parses Claude Code JSONL sessions for agent activity tracking
Extracted from monitor_module.py to maintain 3-layer architecture:
Extracted from monitor.py to maintain 3-layer architecture:
module (orchestration) -> handler (implementation)
"""
@@ -493,15 +493,16 @@ class LogFileWatcher(FileSystemEventHandler):
logger.info(f"Error replaying {log_file}: {e}")
def start_log_watcher(event_queue: MonitoringQueue) -> Any:
def start_log_watcher(event_queue: MonitoringQueue, use_polling: bool = False) -> Any:
"""
Start watching log files and pushing events to queue.
Args:
event_queue: MonitoringQueue instance for event delivery
use_polling: If True, use PollingObserver instead of native inotify
Returns:
Observer instance (caller must keep alive and call .stop() on shutdown)
Observer instance
"""
global _log_observer
@@ -513,18 +514,28 @@ def start_log_watcher(event_queue: MonitoringQueue) -> Any:
# Create watcher instance
watcher = LogFileWatcher(event_queue)
# Soft start: seek to end of all logs, only show NEW activity
# Replay recent lines for startup context (before seeking to EOF)
watcher.replay_recent(num_lines=2)
# Soft start: seek to end of all logs, only show NEW activity after this
watcher.initialize_positions()
# Create and start observer
observer = WatchdogObserver()
# Create observer — polling fallback when inotify unavailable
if use_polling:
from watchdog.observers.polling import PollingObserver
observer = PollingObserver(timeout=1)
logger.info("Log watcher using polling observer (1s interval)")
else:
observer = WatchdogObserver()
observer.schedule(watcher, str(get_system_logs_dir()), recursive=False)
observer.start()
_log_observer = observer
json_handler.log_operation("log_watcher_started", {"log_dir": str(get_system_logs_dir())})
logger.info(f"Log watcher started, monitoring: {get_system_logs_dir()}")
mode = "polling" if use_polling else "inotify"
json_handler.log_operation("log_watcher_started", {"log_dir": str(get_system_logs_dir()), "mode": mode})
logger.info(f"Log watcher started ({mode}), monitoring: {get_system_logs_dir()}")
return observer
@@ -1,5 +1,5 @@
# =================== AIPass ====================
# Name: agent_status_module.py
# Name: agent_status.py
# Description: PRAX Agent Status Push Command
# Version: 0.1.0
# Created: 2026-02-25
@@ -73,10 +73,6 @@ def handle_command(command: str, args: List[str]) -> bool:
if command != 'agent-status-push':
return False
if not args:
print_introspection()
return True
from aipass.prax.apps.handlers.dashboard.agent_status_writer import (
build_agent_status_section,
push_agent_status_dashboard,
+6 -2
View File
@@ -12,7 +12,7 @@ Dashboard Section Utilities
Provides utilities for services to update their sections in branch
DASHBOARD.local.json files. Each service manages only its own section.
Run directly or via: python3 apps/modules/dashboard.py
Run via: drone @prax dashboard
"""
__all__ = [
@@ -162,7 +162,7 @@ def print_introspection():
for section in DASHBOARD_TEMPLATE["sections"]:
console.print(f" - {section}")
console.print()
console.print("[dim]Run 'python3 dashboard.py --help' for usage[/dim]")
console.print("[dim]Run 'drone @prax dashboard --help' for usage[/dim]")
console.print()
@@ -465,6 +465,10 @@ def handle_command(command: str, args: List[str]) -> bool:
print_introspection()
return True
if args[0] in ('--help', '-h', 'help'):
print_help()
return True
subcmd = args[0]
json_handler.log_operation("dashboard_command", {"subcommand": subcmd})
@@ -1,5 +1,5 @@
# =================== AIPass ====================
# Name: discover_module.py
# Name: discover.py
# Description: PRAX Discover Command
# Version: 1.0.0
# Created: 2025-11-15
@@ -15,28 +15,33 @@ Implements the 'discover' command using handle_command interface.
import sys
from typing import List
from aipass.cli.apps.modules import console
from aipass.cli.apps.modules import console, error
from aipass.prax.apps.handlers.discovery.scanner import discover_python_modules
from aipass.prax.apps.handlers.json import json_handler
def print_help():
"""Display module help and connected handlers"""
"""Drone-compliant help output - command syntax and examples"""
console.print()
console.print("[bold cyan]PRAX Discover Module[/bold cyan]")
console.print()
console.print("[yellow]Connected Handlers:[/yellow]")
console.print("[bold cyan]PRAX Discover - Scan Python Modules[/bold cyan]")
console.print()
console.print(" [cyan]prax/modules/[/cyan]")
console.print(" [dim]- logger.py[/dim] (system_logger)")
console.print("[yellow]Purpose:[/yellow]")
console.print(" Scan and register Python modules in the AIPass ecosystem")
console.print()
console.print(" [cyan]prax/handlers/discovery/[/cyan]")
console.print(" [dim]- scanner.py[/dim] (discover_python_modules)")
console.print("[yellow]Usage:[/yellow]")
console.print(" drone @prax discover scan")
console.print()
console.print("[dim]Run 'python3 discover_module.py --help' for usage[/dim]")
console.print("[yellow]Subcommands:[/yellow]")
console.print(" scan Discover and register Python modules")
console.print()
console.print("[yellow]Examples:[/yellow]")
console.print()
console.print(" [dim]# Discover Python modules[/dim]")
console.print(" $ drone @prax discover scan")
console.print()
@@ -58,24 +63,39 @@ def handle_command(command: str, args: List[str]) -> bool:
print_introspection()
return True
json_handler.log_operation("discover_command_executed", {"command": command})
console.print("🔍 Discovering Python modules...")
modules = discover_python_modules()
console.print(f"✅ Discovered {len(modules)} modules")
if args[0] in ('--help', '-h', 'help'):
print_help()
return True
if args[0] == 'scan':
json_handler.log_operation("discover_command_executed", {"command": command})
console.print("🔍 Discovering Python modules...")
modules = discover_python_modules()
console.print(f"✅ Discovered {len(modules)} modules")
return True
error(f"Unknown discover subcommand: {args[0]}")
print_help()
return True
def print_introspection():
"""Display module introspection info."""
"""Display module introspection - shows connected handlers"""
console.print()
console.print("discover_module Module")
console.print("Implements the 'discover' command to scan and register Python modules")
console.print("[bold cyan]PRAX Discover Module[/bold cyan]")
console.print()
console.print("Connected Handlers:")
console.print(" modules/")
console.print(" - logger.py (system_logger — auto-routing logger for module log files)")
console.print(" handlers/discovery/")
console.print(" - scanner.py (discover_python_modules — scans filesystem for Python modules)")
console.print("[yellow]Connected Handlers:[/yellow]")
console.print()
console.print(" [cyan]prax/modules/[/cyan]")
console.print(" [dim]- logger.py[/dim] (system_logger)")
console.print()
console.print(" [cyan]prax/handlers/discovery/[/cyan]")
console.print(" [dim]- scanner.py[/dim] (discover_python_modules)")
console.print()
console.print("[dim]Run 'drone @prax discover --help' for usage[/dim]")
console.print()
@@ -1,5 +1,5 @@
# =================== AIPass ====================
# Name: init_module.py
# Name: init_prax.py
# Description: PRAX Init Command
# Version: 1.0.0
# Created: 2025-11-15
@@ -32,7 +32,7 @@ def print_introspection():
console.print(" [dim]- logger.py[/dim] (initialize_logging_system, system_logger)")
console.print()
console.print("[dim]Run 'python3 init_module.py --help' for usage[/dim]")
console.print("[dim]Run 'drone @prax init --help' for usage[/dim]")
console.print()
@@ -46,13 +46,18 @@ def print_help():
console.print(" Initialize the PRAX logging system for monitoring AIPass operations")
console.print()
console.print("[yellow]Usage Examples:[/yellow]")
console.print("[yellow]Usage:[/yellow]")
console.print(" drone @prax init start")
console.print()
console.print("[yellow]Subcommands:[/yellow]")
console.print(" start Initialize the PRAX logging system")
console.print()
console.print("[yellow]Examples:[/yellow]")
console.print()
console.print(" [dim]# Initialize logging system[/dim]")
console.print(" $ prax init")
console.print()
console.print(" [dim]# Standalone execution[/dim]")
console.print(" $ python3 init_module.py")
console.print(" $ drone @prax init start")
console.print()
@@ -74,17 +79,26 @@ def handle_command(command: str, args: List[str]) -> bool:
print_introspection()
return True
try:
json_handler.log_operation("init_command_executed", {"command": command})
console.print("🚀 Initializing PRAX logging system...")
initialize_logging_system()
console.print("✅ PRAX logging system initialized")
if args[0] in ('--help', '-h', 'help'):
print_help()
return True
except Exception as e:
logger.error(f"Error in init command: {e}")
error(str(e))
return True
if args[0] == 'start':
try:
json_handler.log_operation("init_command_executed", {"command": command})
console.print("🚀 Initializing PRAX logging system...")
initialize_logging_system()
console.print("✅ PRAX logging system initialized")
return True
except Exception as e:
logger.error(f"Error in init command: {e}")
error(str(e))
return True
error(f"Unknown init subcommand: {args[0]}")
print_help()
return True
if __name__ == "__main__":
@@ -1,5 +1,5 @@
# =================== AIPass ====================
# Name: log_audit_module.py
# Name: log_audit.py
# Description: PRAX Log Audit Command
# Version: 0.1.0
# Created: 2026-02-26
@@ -37,7 +37,7 @@ def print_introspection():
console.print(" [dim]- log_watchdog.py (scan_log_files, enforce_log_limits, log_health_summary)[/dim]")
console.print()
console.print("[dim]Run 'drone @prax log-audit' for status, 'drone @prax log-audit enforce' to truncate[/dim]")
console.print("[dim]Run 'drone @prax log-audit --help' for usage[/dim]")
console.print()
@@ -51,10 +51,15 @@ def print_help():
console.print(" Scan system_logs/ for oversized files and enforce rotation limits")
console.print()
console.print("[yellow]Subcommands:[/yellow]")
console.print()
console.print(" [cyan]audit[/cyan] Show log health summary + any oversized files")
console.print(" [cyan]enforce[/cyan] Truncate all oversized files to 1000 lines")
console.print()
console.print("[yellow]Usage:[/yellow]")
console.print()
console.print(" [dim]# Show log health summary + any oversized files[/dim]")
console.print(" $ drone @prax log-audit")
console.print(" $ drone @prax log-audit audit")
console.print()
console.print(" [dim]# Truncate all oversized files to 1000 lines[/dim]")
console.print(" $ drone @prax log-audit enforce")
@@ -108,15 +113,25 @@ def handle_command(command: str, args: List[str]) -> bool:
print_introspection()
return True
if args[0] in ('--help', '-h', 'help'):
print_help()
return True
from aipass.prax.apps.handlers.logging.log_watchdog import (
scan_log_files,
enforce_log_limits,
log_health_summary,
)
json_handler.log_operation("log_audit_executed", {"mode": args[0] if args else "audit"})
subcmd = args[0]
json_handler.log_operation("log_audit_executed", {"mode": subcmd})
if args and args[0] == 'enforce':
if subcmd == 'audit':
files = scan_log_files()
summary = log_health_summary()
_display_audit(files, summary)
return True
elif subcmd == 'enforce':
console.print("\n[bold cyan]Enforcing log limits...[/bold cyan]")
actions = enforce_log_limits()
@@ -134,12 +149,10 @@ def handle_command(command: str, args: List[str]) -> bool:
console.print()
logger.info("[log-audit] Enforced limits on %d files", len(actions))
return True
# Default: audit mode
files = scan_log_files()
summary = log_health_summary()
_display_audit(files, summary)
return True
else:
error(f"Unknown log-audit subcommand: {subcmd}")
print_help()
return True
if __name__ == "__main__":
@@ -1,5 +1,5 @@
# =================== AIPass ====================
# Name: monitor_module.py
# Name: monitor.py
# Description: Unified Monitoring Module
# Version: 0.3.0
# Created: 2025-11-23
@@ -22,9 +22,9 @@ Purpose:
Enables multi-agent workflow visibility and coordination.
Usage:
prax monitor # Monitor all branches (quiet mode)
prax monitor all # Explicit all-branches monitoring
prax monitor seed,cli # Monitor specific branches
drone @prax monitor # Show introspection
drone @prax monitor run # Monitor all branches
drone @prax monitor run seed,cli # Monitor specific branches
Interactive Commands:
help # Show available commands
@@ -50,6 +50,7 @@ import sys
import argparse
import threading
import time
from datetime import datetime
from pathlib import Path
from typing import List, Optional
@@ -68,6 +69,7 @@ from aipass.prax.apps.handlers.monitoring import (
MonitoringQueue, # event_queue.py
ModuleTracker, # module_tracker.py
)
from aipass.prax.apps.handlers.monitoring.event_queue import MonitoringEvent
# NOTE: FileSystemEventHandler implementation lives in:
# aipass.prax.apps.handlers.monitoring.filesystem_handler.MonitoringFileHandler
# It handles trigger events for file_created/file_deleted/file_modified/file_moved
@@ -202,10 +204,29 @@ def handle_command(command: str, args: List[str]) -> bool:
if command != 'monitor':
return False
# Introspection gate — bare command shows module info
if not args:
print_introspection()
return True
# Help intercept
if args[0] in ('--help', '-h', 'help'):
print_help()
return True
# Subcommand routing
subcmd = args[0]
if subcmd == 'run':
return _run_monitor(args[1:])
# Unknown subcommand
error(f"Unknown monitor subcommand: {subcmd}")
print_help()
return True
def _run_monitor(args: List[str]) -> bool:
"""Launch Mission Control live monitoring."""
global _monitoring_active, _event_queue, _module_tracker
global _display_thread, _file_watcher_thread, _log_watcher_thread
@@ -304,6 +325,47 @@ def _display_worker():
print_event(event.event_type, event.branch, event.message, event.level, pid=branch_pid)
def _get_watch_directories(repo_root: Path) -> list[tuple[Path, bool]]:
"""Get targeted directories to watch instead of entire repo root.
Returns (path, recursive) tuples. Watches apps/ recursively for code,
branch roots non-recursively for STATUS/README, and .trinity/ for identity.
Some branches (backup, memory) have 10,000+ dirs in data stores.
Watching only apps/ keeps inotify count under ~800.
"""
dirs: list[tuple[Path, bool]] = []
# Load branch paths from registry
registry_path = repo_root / "AIPASS_REGISTRY.json"
if registry_path.exists():
try:
data = _json.loads(registry_path.read_text(encoding="utf-8"))
for branch in data.get("branches", []):
branch_path = repo_root / branch.get("path", "")
if not branch_path.exists():
continue
# apps/ recursive — source code changes
apps_dir = branch_path / "apps"
if apps_dir.exists():
dirs.append((apps_dir, True))
# Branch root non-recursive — STATUS.local.md, README.md
dirs.append((branch_path, False))
# .trinity/ non-recursive — identity files
trinity_dir = branch_path / ".trinity"
if trinity_dir.exists():
dirs.append((trinity_dir, False))
except (ValueError, OSError) as e:
logger.warning(f"[monitor] Failed to read registry: {e}")
# Watch Claude Code project sessions for agent activity tracking
claude_projects = Path.home() / ".claude" / "projects"
if claude_projects.exists():
dirs.append((claude_projects, True))
return dirs
def _file_watcher_worker():
"""File watcher thread - watches filesystem changes and pushes to queue"""
global _monitoring_active, _event_queue
@@ -311,24 +373,64 @@ def _file_watcher_worker():
from watchdog.observers import Observer
from aipass.prax.apps.handlers.monitoring.filesystem_handler import MonitoringFileHandler
# Files whose modification indicates a command is running (python3 direct calls)
# Maps filename -> command description. Used to emit command separators from file events.
COMMAND_INDICATOR_FILES = {
'standards_audit_log.json': 'seed audit',
'standards_checklist_log.json': 'seed checklist',
}
# Create observer and start watching
# Watch from repo root (covers all modules)
from aipass.prax.apps.handlers.config.load import _find_repo_root
observer = Observer()
handler = MonitoringFileHandler(
event_queue=_event_queue,
command_indicator_files=COMMAND_INDICATOR_FILES,
)
watch_dir = _find_repo_root()
observer.schedule(handler, str(watch_dir), recursive=True)
observer.start()
from aipass.prax.apps.handlers.config.load import _find_repo_root
repo_root = _find_repo_root()
watch_dirs = _get_watch_directories(repo_root)
if not watch_dirs:
logger.error("[monitor] No watch directories found — file watcher disabled")
if _event_queue:
_event_queue.enqueue(MonitoringEvent(
priority=2, event_type='log', branch='PRAX',
action='warning', level='warning', timestamp=datetime.now(),
message="File watcher: no watch directories found — file events disabled"
))
return
observer = Observer()
for watch_dir, recursive in watch_dirs:
observer.schedule(handler, str(watch_dir), recursive=recursive)
logger.info(f"[monitor] File watcher: {len(watch_dirs)} watches scheduled")
try:
observer.start()
except OSError as e:
logger.warning(f"[monitor] inotify unavailable: {e} — switching to polling")
if _event_queue:
_event_queue.enqueue(MonitoringEvent(
priority=2, event_type='log', branch='PRAX',
action='warning', level='warning', timestamp=datetime.now(),
message="File watcher: inotify unavailable — using polling fallback (slower)"
))
# Fallback to PollingObserver
try:
from watchdog.observers.polling import PollingObserver
observer = PollingObserver(timeout=2)
for watch_dir, recursive in watch_dirs:
observer.schedule(handler, str(watch_dir), recursive=recursive)
observer.start()
logger.info("[monitor] File watcher: polling fallback active")
except Exception as e2:
logger.error(f"[monitor] Polling fallback also failed: {e2}")
if _event_queue:
_event_queue.enqueue(MonitoringEvent(
priority=1, event_type='log', branch='PRAX',
action='error', level='error', timestamp=datetime.now(),
message="File watcher: completely unavailable — no file events"
))
return
try:
while _monitoring_active:
@@ -344,12 +446,31 @@ def _log_watcher_worker():
from aipass.prax.apps.handlers.monitoring.log_watcher import start_log_watcher, stop_log_watcher
# Start the proper log watcher (has command detection, branch detection, message parsing)
# Guard against None - should never happen since we initialize before starting threads
if _event_queue is None:
logger.error("[monitor] Event queue not initialized for log watcher")
return
_observer = start_log_watcher(_event_queue)
try:
start_log_watcher(_event_queue)
except OSError as e:
logger.warning(f"[monitor] Log watcher inotify failed: {e} — switching to polling")
if _event_queue:
_event_queue.enqueue(MonitoringEvent(
priority=2, event_type='log', branch='PRAX',
action='warning', level='warning', timestamp=datetime.now(),
message="Log watcher: inotify unavailable — using polling fallback (slower)"
))
try:
start_log_watcher(_event_queue, use_polling=True)
except Exception as e2:
logger.error(f"[monitor] Log watcher polling fallback failed: {e2}")
if _event_queue:
_event_queue.enqueue(MonitoringEvent(
priority=1, event_type='log', branch='PRAX',
action='error', level='error', timestamp=datetime.now(),
message="Log watcher: completely unavailable — no log events"
))
return
try:
while _monitoring_active:
@@ -383,7 +504,7 @@ def _interactive_loop():
if not user_input:
continue
cmd, cmd_args = parse_command(user_input)
cmd, _cmd_args = parse_command(user_input)
if not cmd:
continue
@@ -460,7 +581,7 @@ def print_introspection():
console.print(" [green]STATUS: Active - watches *.log files for new entries[/green]")
console.print()
console.print("[dim]Run 'python3 monitor_module.py --help' for usage[/dim]")
console.print("[dim]Run 'drone @prax monitor --help' for usage[/dim]")
console.print()
@@ -476,15 +597,21 @@ def print_help():
console.print("[yellow]Commands:[/yellow]")
console.print()
console.print(" [cyan]monitor[/cyan]")
console.print(" Start monitoring all branches (quiet mode)")
console.print(" [cyan]drone @prax monitor[/cyan]")
console.print(" Show module introspection")
console.print()
console.print(" [cyan]monitor all[/cyan]")
console.print(" [cyan]drone @prax monitor run[/cyan]")
console.print(" Start monitoring all branches")
console.print()
console.print(" [cyan]drone @prax monitor run all[/cyan]")
console.print(" Explicit all-branches monitoring")
console.print()
console.print(" [cyan]monitor [branches][/cyan]")
console.print(" [cyan]drone @prax monitor run [branches][/cyan]")
console.print(" Monitor specific branches (comma-separated)")
console.print(" Example: monitor seed,cli,flow")
console.print(" Example: drone @prax monitor run seed,cli,flow")
console.print()
console.print(" [cyan]drone @prax monitor --help[/cyan]")
console.print(" Show this help")
console.print()
console.print("[yellow]Interactive Mode Commands:[/yellow]")
@@ -498,13 +625,10 @@ def print_help():
console.print("[yellow]Examples:[/yellow]")
console.print()
console.print(" [dim]# Monitor all branches[/dim]")
console.print(" $ prax monitor")
console.print(" $ drone @prax monitor run")
console.print()
console.print(" [dim]# Monitor specific branches[/dim]")
console.print(" $ prax monitor seed,cli,flow")
console.print()
console.print(" [dim]# Standalone execution[/dim]")
console.print(" $ python3 monitor_module.py")
console.print(" $ drone @prax monitor run seed,cli,flow")
console.print()
@@ -1,5 +1,5 @@
# =================== AIPass ====================
# Name: run_module.py
# Name: run.py
# Description: PRAX Run Command
# Version: 1.0.0
# Created: 2025-11-15
@@ -16,23 +16,32 @@ import sys
from typing import List
from aipass.prax.apps.modules.logger import start_continuous_logging
from aipass.cli.apps.modules import console
from aipass.cli.apps.modules import console, error
from aipass.prax.apps.handlers.json import json_handler
def print_help():
"""Display module help and connected handlers"""
"""Drone-compliant help output - command syntax and examples"""
console.print()
console.print("[bold cyan]PRAX Run Module[/bold cyan]")
console.print()
console.print("[yellow]Connected Handlers:[/yellow]")
console.print("[bold cyan]PRAX Run - Continuous Logging Mode[/bold cyan]")
console.print()
console.print(" [cyan]prax/modules/[/cyan]")
console.print(" [dim]- logger.py[/dim] (start_continuous_logging, system_logger)")
console.print("[yellow]Purpose:[/yellow]")
console.print(" Start PRAX continuous logging mode for monitoring AIPass operations")
console.print()
console.print("[dim]Run 'python3 run_module.py --help' for usage[/dim]")
console.print("[yellow]Usage:[/yellow]")
console.print(" drone @prax run start")
console.print()
console.print("[yellow]Subcommands:[/yellow]")
console.print(" start Start continuous logging mode")
console.print()
console.print("[yellow]Examples:[/yellow]")
console.print()
console.print(" [dim]# Start continuous logging[/dim]")
console.print(" $ drone @prax run start")
console.print()
@@ -54,22 +63,34 @@ def handle_command(command: str, args: List[str]) -> bool:
print_introspection()
return True
json_handler.log_operation("run_command_executed", {"command": command})
console.print("🚀 Starting PRAX continuous logging mode...")
start_continuous_logging()
if args[0] in ('--help', '-h', 'help'):
print_help()
return True
if args[0] == 'start':
json_handler.log_operation("run_command_executed", {"command": command})
console.print("🚀 Starting PRAX continuous logging mode...")
start_continuous_logging()
return True
error(f"Unknown run subcommand: {args[0]}")
print_help()
return True
def print_introspection():
"""Display module introspection info."""
"""Display module introspection - shows connected handlers"""
console.print()
console.print("run_module Module")
console.print("Implements the 'run' command to start PRAX continuous logging mode")
console.print("[bold cyan]PRAX Run Module[/bold cyan]")
console.print()
console.print("Connected Handlers:")
console.print(" modules/")
console.print(" - logger.py (start_continuous_logging — starts background logging loop)")
console.print(" - logger.py (system_logger — auto-routing logger for module log files)")
console.print("[yellow]Connected Handlers:[/yellow]")
console.print()
console.print(" [cyan]prax/modules/[/cyan]")
console.print(" [dim]- logger.py[/dim] (start_continuous_logging, system_logger)")
console.print()
console.print("[dim]Run 'drone @prax run --help' for usage[/dim]")
console.print()
@@ -1,5 +1,5 @@
# =================== AIPass ====================
# Name: shutdown_module.py
# Name: shutdown.py
# Description: PRAX Shutdown Command
# Version: 1.0.0
# Created: 2025-11-15
@@ -16,23 +16,32 @@ import sys
from typing import List
from aipass.prax.apps.modules.logger import shutdown_logging_system
from aipass.cli.apps.modules import console
from aipass.cli.apps.modules import console, error
from aipass.prax.apps.handlers.json import json_handler
def print_help():
"""Display module help and connected handlers"""
"""Drone-compliant help output - command syntax and examples"""
console.print()
console.print("[bold cyan]PRAX Shutdown Module[/bold cyan]")
console.print()
console.print("[yellow]Connected Handlers:[/yellow]")
console.print("[bold cyan]PRAX Shutdown - Stop Logging System[/bold cyan]")
console.print()
console.print(" [cyan]prax/modules/[/cyan]")
console.print(" [dim]- logger.py[/dim] (shutdown_logging_system, system_logger)")
console.print("[yellow]Purpose:[/yellow]")
console.print(" Cleanly stop the PRAX logging system")
console.print()
console.print("[dim]Run 'python3 shutdown_module.py --help' for usage[/dim]")
console.print("[yellow]Usage:[/yellow]")
console.print(" drone @prax shutdown now")
console.print()
console.print("[yellow]Subcommands:[/yellow]")
console.print(" now Shut down the PRAX logging system")
console.print()
console.print("[yellow]Examples:[/yellow]")
console.print()
console.print(" [dim]# Shut down logging system[/dim]")
console.print(" $ drone @prax shutdown now")
console.print()
@@ -54,23 +63,35 @@ def handle_command(command: str, args: List[str]) -> bool:
print_introspection()
return True
json_handler.log_operation("shutdown_command_executed", {"command": command})
console.print("🛑 Shutting down PRAX logging system...")
shutdown_logging_system()
console.print("✅ PRAX logging system shutdown complete")
if args[0] in ('--help', '-h', 'help'):
print_help()
return True
if args[0] == 'now':
json_handler.log_operation("shutdown_command_executed", {"command": command})
console.print("🛑 Shutting down PRAX logging system...")
shutdown_logging_system()
console.print("✅ PRAX logging system shutdown complete")
return True
error(f"Unknown shutdown subcommand: {args[0]}")
print_help()
return True
def print_introspection():
"""Display module introspection info."""
"""Display module introspection - shows connected handlers"""
console.print()
console.print("shutdown_module Module")
console.print("Implements the 'shutdown' command to cleanly stop PRAX logging system")
console.print("[bold cyan]PRAX Shutdown Module[/bold cyan]")
console.print()
console.print("Connected Handlers:")
console.print(" modules/")
console.print(" - logger.py (shutdown_logging_system — stops watcher, restores logger, logs shutdown)")
console.print(" - logger.py (system_logger — auto-routing logger for module log files)")
console.print("[yellow]Connected Handlers:[/yellow]")
console.print()
console.print(" [cyan]prax/modules/[/cyan]")
console.print(" [dim]- logger.py[/dim] (shutdown_logging_system, system_logger)")
console.print()
console.print("[dim]Run 'drone @prax shutdown --help' for usage[/dim]")
console.print()
@@ -1,5 +1,5 @@
# =================== AIPass ====================
# Name: status_module.py
# Name: status.py
# Description: PRAX Status Command
# Version: 1.1.0
# Created: 2025-11-15
@@ -45,10 +45,6 @@ def handle_command(command: str, args: List[str]) -> bool:
if command != 'status':
return False
if not args:
print_introspection()
return True
json_handler.log_operation("status_checked", {"subcommand": args[0] if args else "default"})
# --- sub-command routing ------------------------------------------------
@@ -110,7 +106,7 @@ def _handle_sync() -> bool:
def print_introspection():
"""Display module introspection info."""
console.print()
console.print("status_module Module")
console.print("status Module")
console.print("Implements the 'status' command to display PRAX system status dashboard")
console.print()
console.print("Connected Handlers:")
@@ -1,5 +1,5 @@
# =================== AIPass ====================
# Name: terminal_module.py
# Name: terminal.py
# Description: PRAX Terminal Command
# Version: 1.1.0
# Created: 2025-11-15
@@ -36,7 +36,7 @@ def print_introspection():
console.print(" [dim]- logger.py[/dim] (enable_terminal_output, disable_terminal_output, system_logger)")
console.print()
console.print("[dim]Run 'python3 terminal_module.py --help' for usage[/dim]")
console.print("[dim]Run 'drone @prax terminal --help' for usage[/dim]")
console.print()
@@ -58,13 +58,10 @@ def print_help():
console.print("[yellow]Usage Examples:[/yellow]")
console.print()
console.print(" [dim]# Enable terminal output[/dim]")
console.print(" $ prax terminal enable")
console.print(" $ drone @prax terminal enable")
console.print()
console.print(" [dim]# Disable terminal output[/dim]")
console.print(" $ prax terminal disable")
console.print()
console.print(" [dim]# Standalone execution[/dim]")
console.print(" $ python3 terminal_module.py enable")
console.print(" $ drone @prax terminal disable")
console.print()
console.print("[dim]Commands: enable, disable[/dim]")
@@ -89,6 +86,10 @@ def handle_command(command: str, args: List[str]) -> bool:
print_introspection()
return True
if args[0] in ('--help', '-h', 'help'):
print_help()
return True
try:
if args[0] not in ['enable', 'disable']:
print_help()
+47 -7
View File
@@ -96,7 +96,37 @@ def print_introspection():
console.print(f" [cyan]•[/cyan] {module_name}")
console.print()
console.print("[dim]Run 'prax --help' for usage information[/dim]")
console.print("[dim]Run 'drone @prax --help' for usage information[/dim]")
console.print()
def print_help():
"""Rich-formatted top-level help for PRAX"""
console.print()
console.print("[bold cyan]PRAX - System-Wide Logging Infrastructure[/bold cyan]")
console.print()
console.print("[yellow]Commands:[/yellow]")
console.print(" [cyan]monitor[/cyan] Mission Control - unified real-time monitoring")
console.print(" [cyan]init[/cyan] Initialize PRAX logging system")
console.print(" [cyan]status[/cyan] Show PRAX system status")
console.print(" [cyan]run[/cyan] Start continuous logging mode")
console.print(" [cyan]shutdown[/cyan] Shutdown PRAX logging system")
console.print(" [cyan]discover[/cyan] Discover Python modules in ecosystem")
console.print(" [cyan]terminal[/cyan] Enable/disable terminal output")
console.print()
console.print("[yellow]Flags:[/yellow]")
console.print(" [cyan]--help, -h[/cyan] Show help information")
console.print(" [cyan]--version, -V[/cyan] Show version")
console.print(" [cyan]--verbose, -v[/cyan] Verbose output")
console.print()
console.print("[yellow]Examples:[/yellow]")
console.print(" $ drone @prax monitor")
console.print(" $ drone @prax monitor run")
console.print(" $ drone @prax status")
console.print(" $ drone @prax terminal enable")
console.print()
@@ -135,6 +165,7 @@ def main():
parser = argparse.ArgumentParser(
description='PRAX - System-Wide Logging Infrastructure',
formatter_class=argparse.RawDescriptionHelpFormatter,
add_help=False, # We route --help to modules when a command is given
epilog="""
Available Commands:
monitor Mission Control - unified real-time monitoring
@@ -146,10 +177,10 @@ Available Commands:
terminal Enable/disable terminal output (requires: enable|disable)
Examples:
prax monitor
prax init
prax status
prax terminal enable
drone @prax monitor
drone @prax init
drone @prax status
drone @prax terminal enable
"""
)
@@ -163,14 +194,19 @@ Examples:
nargs='*',
help='Arguments for the command')
parser.add_argument('--help', '-h', action='store_true', dest='show_help',
help='Show help information')
parser.add_argument('--version', '-V', action='version', version='PRAX v2.0.0')
parser.add_argument('--verbose', '-v', action='store_true', help='Verbose output')
parsed_args, remaining = parser.parse_known_args()
# If no command provided, show introspection display
# If no command provided, show introspection or top-level help
if not parsed_args.command:
print_introspection()
if parsed_args.show_help:
print_help()
else:
print_introspection()
return 0
# Discover command modules
@@ -184,6 +220,10 @@ Examples:
# so subcommand handlers like dashboard can receive --all, --branch, etc.
all_args = parsed_args.args + remaining
# Pass --help through to module handler (e.g. drone @prax monitor --help)
if parsed_args.show_help:
all_args = ['--help'] + all_args
# Route command to appropriate handler
if route_command(parsed_args.command, all_args, handlers):
return 0
-15
View File
@@ -1,15 +0,0 @@
# dev.local.md - PRAX
```
Branch: /home/coder/workspace/AIPass/src/aipass/prax
Created: 2026-03-07
```
## Issues
-
---
## Todos
-