Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/agent/capabilities/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def discover_and_import_capabilities():
discovered_modules = []
failed_imports = []

logger.debug(f"Starting dynamic capability discovery in {capabilities_dir}")
logger.debug("Starting dynamic capability discovery")

# TODO: I expect there is a better way to do this,
# this will dynamically import all Python files in the capabilities directory
Expand Down
108 changes: 89 additions & 19 deletions src/agent/plugins/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,13 @@ class PluginManager:
# Hook implementation marker - shared across all plugin instances
hookimpl = pluggy.HookimplMarker("agentup")

def __init__(self):
"""Initialize the plugin manager."""
def __init__(self, config: dict[str, Any] | None = None):
"""Initialize the plugin manager.

Args:
config: Optional configuration dictionary. If not provided,
will be loaded when needed.
"""
self.pm = pluggy.PluginManager("agentup")
self.pm.add_hookspecs(CapabilitySpec)

Expand All @@ -40,6 +45,21 @@ def __init__(self):
# Track plugin hooks for each capability
self.capability_hooks: dict[str, Any] = {}

# Store configuration
self._config = config

@property
def config(self) -> dict[str, Any]:
"""Get configuration, loading if necessary."""
if self._config is None:
try:
from agent.config import load_config
self._config = load_config(configure_logging=False)
except ImportError:
logger.warning("Could not load configuration, using empty config")
self._config = {}
return self._config

def discover_plugins(self) -> None:
"""Discover and load all available plugins."""
logger.debug("Plugin discovery started")
Expand Down Expand Up @@ -105,31 +125,73 @@ def _load_entry_point_plugins(self) -> None:
except Exception as e:
logger.error(f"Error loading entry point plugins: {e}")

def _should_load_filesystem_plugins(self) -> bool:
"""Check if filesystem plugin loading is enabled in development configuration.

Returns:
True if filesystem plugins should be loaded, False otherwise (default).
"""
# Check if development mode is enabled
dev_config = self.config.get("development", {})
if not dev_config.get("enabled", False):
return False

# Check if filesystem plugins specifically enabled
fs_plugins = dev_config.get("filesystem_plugins", {})
if not fs_plugins.get("enabled", False):
return False

# Log security warning
logger.warning(
"SECURITY WARNING: Filesystem plugin loading is enabled. "
"This allows execution of arbitrary code from the filesystem. "
"Only use in trusted development environments!"
)

return True

def _load_installed_plugins(self) -> None:
# TODO: Document this method
"""Load plugins from installed plugins directory."""
installed_dir = Path.home() / ".agentup" / "plugins"
if not installed_dir.exists():
logger.debug("No installed plugins directory found")
"""Load plugins from installed plugins directory only if explicitly enabled in config."""
# Check if filesystem plugin loading is enabled
if not self._should_load_filesystem_plugins():
logger.debug("Filesystem based plugin loading disabled (secure default)")
return

for plugin_dir in installed_dir.iterdir():
if plugin_dir.is_dir():
try:
# Check for plugin.py or __init__.py
if (plugin_dir / "plugin.py").exists():
self._load_installed_plugin(plugin_dir, "plugin.py")
elif (plugin_dir / "__init__.py").exists():
self._load_installed_plugin(plugin_dir, "__init__.py")
except Exception as e:
logger.error(f"Failed to load installed plugin from {plugin_dir}: {e}")
# Get allowed directories from config
dev_config = self.config.get("development", {})
fs_config = dev_config.get("filesystem_plugins", {})
allowed_dirs = fs_config.get("allowed_directories", ["~/.agentup/plugins"])

for dir_path in allowed_dirs:
# Expand user home directory
expanded_path = Path(dir_path).expanduser()

if not expanded_path.exists():
logger.debug(f"Filesystem plugin directory not found: {expanded_path}")
continue

if not expanded_path.is_dir():
logger.warning(f"Filesystem plugin path is not a directory: {expanded_path}")
continue

logger.info(f"Loading filesystem plugins from: {expanded_path}")

for plugin_dir in expanded_path.iterdir():
if plugin_dir.is_dir():
try:
# Check for plugin.py or __init__.py
if (plugin_dir / "plugin.py").exists():
self._load_installed_plugin(plugin_dir, "plugin.py")
elif (plugin_dir / "__init__.py").exists():
self._load_installed_plugin(plugin_dir, "__init__.py")
except Exception as e:
logger.error(f"Failed to load installed plugin from {plugin_dir}: {e}")

def _load_installed_plugin(self, plugin_dir: Path, entry_file: str) -> None:
"""Load a single installed plugin."""
plugin_name = f"installed_{plugin_dir.name}"
plugin_file = plugin_dir / entry_file

# We'll log with capability count after registering

# Similar to local plugin loading
spec = importlib.util.spec_from_file_location(plugin_name, plugin_file)
Expand Down Expand Up @@ -400,7 +462,15 @@ def get_plugin_manager() -> PluginManager:
"""Get the global plugin manager instance."""
global _plugin_manager
if _plugin_manager is None:
_plugin_manager = PluginManager()
# Try to load configuration for the plugin manager
config = None
try:
from agent.config import load_config
config = load_config(configure_logging=False)
except ImportError:
logger.debug("Could not load configuration for plugin manager")

_plugin_manager = PluginManager(config)
_plugin_manager.discover_plugins()
return _plugin_manager

Expand Down
24 changes: 23 additions & 1 deletion src/agent/templates/config/agent_config.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -364,4 +364,26 @@ logging:
uvicorn:
access_log: {{ uvicorn_access_log | default(false) }}
disable_default_handlers: true
use_colors: {{ uvicorn_colors | default(console_colors | default(true)) }}
use_colors: {{ uvicorn_colors | default(console_colors | default(true)) }}

# Development configuration
# WARNING: These features can be security risks and should NEVER be enabled in production
development:
enabled: false # Master switch for ALL development features

# Filesystem plugin loading -
# This allows loading arbitrary Python code from the filesystem, NEVER enable in production
filesystem_plugins:
enabled: false # Default: disabled for security
allowed_directories:
- ~/.agentup/plugins # Directories to scan for plugins
# Future security enhancements:
# require_signature: true # Require cryptographic signatures
# allowed_plugins: [] # Whitelist specific plugin files

# Other development features can be added here
# debug_mode: false
# hot_reload: false
# unsafe_features:
# allow_eval: false
# allow_exec: false
28 changes: 27 additions & 1 deletion src/agent/templates/config/agent_config_full.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -293,4 +293,30 @@ logging:
uvicorn:
access_log: true
disable_default_handlers: true
use_colors: false
use_colors: false

# Development configuration
# WARNING: These features can be security risks and should NEVER be enabled in production
development:
enabled: false # Master switch for ALL development features

# Filesystem plugin loading
# This allows loading arbitrary Python code from the filesystem, NEVER enable in production
filesystem_plugins:
enabled: false # Default: disabled for security
allowed_directories:
- ~/.agentup/plugins # Directories to scan for plugins
# Security enhancements:
# require_signature: true # Require cryptographic signatures
# allowed_plugins: [] # Whitelist specific plugin files
# watch_for_changes: true # Auto-reload on file changes
# log_level: DEBUG # Extra logging for plugin loading

# Other development features
debug_mode: false # Enable debug endpoints and verbose logging
hot_reload: false # Auto-reload on code changes
profiling: false # Enable performance profiling
unsafe_features:
allow_eval: false # Allow eval() in plugins
allow_exec: false # Allow exec() in plugins
allow_shell: false # Allow shell command execution
12 changes: 11 additions & 1 deletion src/agent/templates/config/agent_config_minimal.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,14 @@ logging:
# Uvicorn integration
uvicorn:
access_log: false # Minimal logging
disable_default_handlers: true
disable_default_handlers: true

# Development configuration (disabled by default)
development:
enabled: false # Keep all development features disabled in minimal template

# Filesystem plugins remain disabled for security
filesystem_plugins:
enabled: false
allowed_directories:
- ~/.agentup/plugins
Loading