feat(plugins): Stage 2.1 — plugin framework and AI tool-use
Introduces a standalone plugin system where every integration lives as an independent Python script in ~/.pyra/plugins/, not hardcoded in core. Plugin framework (src/pyra/plugins/): - base.py: Tool dataclass, PyraPlugin Protocol, BasePlugin helper - loader.py: importlib-based discovery; one bad plugin never crashes pyra - registry.py: singleton aggregating tools, slash commands, system prompts - executor.py: approval gate — scans args, prompts y/N, scans result, logs - install.py: copies bundled_plugins/ to ~/.pyra/plugins/ on install Chat integration: - AI tool-use loop (litellm function calling, up to 10 iterations) - Plugin system prompt additions injected per session - Plugin slash commands merged with static commands CLI additions: - pyra plugin list/install/enable/disable/setup - pyra daemon start/stop/status/restart/install/uninstall (stubs for 2.4) Config: PluginConfig + DaemonConfig added to PyraConfig (backwards-compatible) Bootstrap: ~/.pyra/plugins/ and ~/.pyra/logs/ created on startup Security: tool args and results always injection-scanned; plugin dirs validated with assert_safe_path() before loading (symlink protection) Tests: 37 new tests (loader, registry, executor, plugin isolation security) 161 total, all passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,8 @@ def bootstrap() -> None:
|
||||
ensure_dir(home / "skills" / "powershell", 0o700)
|
||||
ensure_dir(home / "skills" / "python", 0o700)
|
||||
ensure_dir(home / "vault" / "secrets", 0o700)
|
||||
ensure_dir(home / "plugins", 0o700)
|
||||
ensure_dir(home / "logs", 0o700)
|
||||
|
||||
_create_vault_lock(home / "vault" / ".vault_lock")
|
||||
check_vault_lock()
|
||||
|
||||
@@ -17,8 +17,23 @@ class SecurityConfig(BaseModel):
|
||||
log_injections: bool = True
|
||||
|
||||
|
||||
class PluginConfig(BaseModel):
|
||||
enabled: list[str] = Field(default_factory=list)
|
||||
require_approval: bool = True
|
||||
log_executions: bool = True
|
||||
|
||||
|
||||
class DaemonConfig(BaseModel):
|
||||
enabled: bool = False
|
||||
socket_path: str = "~/.pyra/daemon.sock"
|
||||
log_file: str = "~/.pyra/daemon.log"
|
||||
pid_file: str = "~/.pyra/daemon.pid"
|
||||
|
||||
|
||||
class PyraConfig(BaseModel):
|
||||
version: int = 1
|
||||
ai: ProviderConfig
|
||||
memory: MemoryConfig = Field(default_factory=MemoryConfig)
|
||||
security: SecurityConfig = Field(default_factory=SecurityConfig)
|
||||
plugins: PluginConfig = Field(default_factory=PluginConfig)
|
||||
daemon: DaemonConfig = Field(default_factory=DaemonConfig)
|
||||
|
||||
Reference in New Issue
Block a user