# Phase 8:插件生态(Plugin Ecosystem) 日期:2026-04-04 状态:待开始 前置依赖:Phase 6(工具系统重构)、Phase 7(Hook 拦截层) Demo参考:claw-code-main — plugins/, PluginLifecycle --- ## 1. 阶段目标 建立完整的**插件生命周期管理系统**,允许第三方开发者通过插件扩展 Jarvis 的功能。 **核心能力**: - 插件安装/卸载/启用/禁用 - 插件沙箱隔离执行 - 插件提供的工具/Hook/命令注册 - 内置插件集 --- ## 2. 插件架构 ### 2.1 插件结构 ``` my-plugin/ ├── manifest.json # 插件清单 ├── SKILL.md # 插件技能文档(可选) ├── README.md # 插件说明 │ ├── tools/ # 插件提供的工具 │ ├── __init__.py │ └── my_tool.py │ ├── hooks/ # 插件提供的 Hook │ ├── __init__.py │ └── my_hook.py │ ├── commands/ # 插件提供的命令 │ ├── __init__.py │ └── my_command.py │ └── static/ # 静态资源 └── icon.png ``` ### 2.2 插件清单 (manifest.json) ```json { "id": "my-plugin", "name": "My Plugin", "version": "1.0.0", "description": "A sample plugin for Jarvis", "author": "Developer Name", "license": "MIT", "homepage": "https://github.com/developer/my-plugin", "compatibility": { "jarvis_version": ">=2.0.0", "api_version": "1.0" }, "capabilities": { "tools": [ { "name": "my_tool", "description": "Does something useful", "parameters": { "type": "object", "properties": { "input": {"type": "string"} }, "required": ["input"] } } ], "hooks": [ { "name": "my_pre_hook", "type": "pre_tool_use", "description": "Custom pre-hook" } ], "commands": [ { "name": "/my-command", "description": "Custom slash command" } ] }, "permissions": { "network": false, "filesystem": "read", "database": false }, "dependencies": {} } ``` --- ## 3. 核心组件 ### 3.1 PluginManifest ```python # backend/app/agents/plugins/manifest.py @dataclass class ToolSpec: """工具规格""" name: str description: str parameters: dict return_schema: dict | None = None @dataclass class HookSpec: """Hook 规格""" name: str type: HookType description: str filter_names: list[str] | None = None @dataclass class CommandSpec: """命令规格""" name: str description: str parameters: list | None = None @dataclass class PluginManifest: """插件清单""" id: str name: str version: str description: str author: str license: str homepage: str | None = None compatibility: dict = field(default_factory=dict) capabilities: dict = field(default_factory=dict) permissions: dict = field(default_factory=dict) dependencies: dict = field(default_factory=dict) @classmethod def from_file(cls, path: Path) -> "PluginManifest": """从文件加载""" with open(path / "manifest.json") as f: data = json.load(f) return cls(**data) def validate(self) -> list[str]: """验证清单有效性""" errors = [] if not self.id: errors.append("Plugin ID is required") if not self.version: errors.append("Plugin version is required") if "tools" in self.capabilities: for tool in self.capabilities["tools"]: if "name" not in tool: errors.append(f"Tool missing name in plugin {self.id}") return errors ``` ### 3.2 PluginManager ```python # backend/app/agents/plugins/manager.py class PluginState(Enum): """插件状态""" INSTALLED = "installed" ENABLED = "enabled" DISABLED = "disabled" ERROR = "error" @dataclass class Plugin: """插件实例""" manifest: PluginManifest path: Path state: PluginState module: Any = None error_message: str | None = None tools: list[ToolManifest] = field(default_factory=list) hooks: list[HookConfig] = field(default_factory=list) commands: list[CommandSpec] = field(default_factory=list) class PluginManager: """插件管理器""" def __init__( self, plugin_dir: Path, tool_registry: ToolRegistry, hook_manager: HookManager ): self.plugin_dir = plugin_dir self.tool_registry = tool_registry self.hook_manager = hook_manager self._plugins: dict[str, Plugin] = {} self._hooks = [] async def install(self, plugin_path: Path | str) -> Plugin: """ 安装插件 流程: 1. 验证插件清单 2. 检查兼容性 3. 复制到插件目录 4. 加载插件模块 """ plugin_path = Path(plugin_path) # 1. 加载清单 manifest = PluginManifest.from_file(plugin_path) # 2. 验证 errors = manifest.validate() if errors: raise PluginValidationError(errors) # 3. 检查版本兼容性 await self._check_compatibility(manifest) # 4. 创建插件目录 target_dir = self.plugin_dir / manifest.id if target_dir.exists(): raise PluginAlreadyExistsError(manifest.id) shutil.copytree(plugin_path, target_dir) # 5. 创建插件实例 plugin = Plugin( manifest=manifest, path=target_dir, state=PluginState.INSTALLED ) # 6. 加载模块 await self._load_plugin(plugin) self._plugins[manifest.id] = plugin return plugin async def uninstall(self, plugin_id: str) -> bool: """卸载插件""" plugin = self._plugins.get(plugin_id) if not plugin: raise PluginNotFoundError(plugin_id) # 禁用插件 if plugin.state == PluginState.ENABLED: await self.disable(plugin_id) # 移除工具和 Hook for tool in plugin.tools: self.tool_registry.unregister(tool.name) for hook in plugin.hooks: self.hook_manager.remove_hook(hook) # 删除插件目录 shutil.rmtree(plugin.path) del self._plugins[plugin_id] return True async def enable(self, plugin_id: str) -> Plugin: """启用插件""" plugin = self._plugins.get(plugin_id) if not plugin: raise PluginNotFoundError(plugin_id) # 注册工具 for tool in plugin.tools: self.tool_registry.register( tool, self._create_tool_executor(plugin, tool.name) ) # 注册 Hook for hook in plugin.hooks: self.hook_manager.add_hook(hook) plugin.state = PluginState.ENABLED return plugin async def disable(self, plugin_id: str) -> Plugin: """禁用插件""" plugin = self._plugins.get(plugin_id) if not plugin: raise PluginNotFoundError(plugin_id) # 注销工具 for tool in plugin.tools: self.tool_registry.unregister(tool.name) # 注销 Hook for hook in plugin.hooks: self.hook_manager.remove_hook(hook) plugin.state = PluginState.DISABLED return plugin async def reload(self, plugin_id: str) -> Plugin: """重新加载插件""" plugin = self._plugins.get(plugin_id) if not plugin: raise PluginNotFoundError(plugin_id) was_enabled = plugin.state == PluginState.ENABLED if was_enabled: await self.disable(plugin_id) await self._load_plugin(plugin) if was_enabled: await self.enable(plugin_id) return plugin async def list_plugins(self) -> list[Plugin]: """列出所有插件""" return list(self._plugins.values()) ``` --- ## 4. 插件隔离 ### 4.1 PluginSandbox ```python # backend/app/agents/plugins/sandbox.py class PluginSandbox: """插件沙箱""" def __init__(self, plugin: Plugin, permissions: dict): self.plugin = plugin self.permissions = permissions self._env = {} async def load_module(self) -> Any: """在沙箱中加载插件模块""" # 设置受限的环境 self._env = { "__name__": f"plugin.{self.plugin.manifest.id}", "__file__": str(self.plugin.path), } if not self.permissions.get("filesystem", False): # 只读文件系统 self._env["__builtins__"] = { k: v for k, v in __builtins__.items() if k in ["print", "len", "range", "str", "int", "float", "list", "dict", "tuple", "set", "bool", "type", "isinstance", "hasattr", "getattr", "setattr", "open"] } # 加载模块 spec = importlib.util.spec_from_file_location( f"plugin_{self.plugin.manifest.id}", self.plugin.path / "__init__.py" ) module = importlib.util.module_from_spec(spec) sys.modules[f"plugin_{self.plugin.manifest.id}"] = module try: spec.loader.exec_module(module) except Exception as e: raise PluginLoadError(str(e)) return module def check_permission(self, permission: str) -> bool: """检查权限""" return self.permissions.get(permission, False) ``` --- ## 5. 插件市场 ### 5.1 PluginMarketplace ```python # backend/app/services/plugin_marketplace.py @dataclass class MarketplacePlugin: """市场中的插件""" manifest: PluginManifest download_count: int rating: float reviews_count: int last_updated: datetime class PluginMarketplace: """插件市场""" def __init__(self, http_client: httpx.AsyncClient): self.http_client = http_client self.cache: dict[str, MarketplacePlugin] = {} self.cache_ttl = timedelta(hours=1) async def search( self, query: str, category: str | None = None, limit: int = 20 ) -> list[MarketplacePlugin]: """搜索插件""" # TODO: 连接到实际的市场 API return [] async def get_plugin( self, plugin_id: str ) -> MarketplacePlugin | None: """获取插件详情""" # 检查缓存 if plugin_id in self.cache: return self.cache[plugin_id] # TODO: 从市场 API 获取 return None async def download_plugin( self, plugin_id: str, target_dir: Path ) -> Path: """下载插件""" # TODO: 实现下载逻辑 pass ``` --- ## 6. 内置插件 ### 6.1 内置插件列表 | 插件 | 描述 | 提供的工具 | |------|------|-----------| | `code-helper` | 代码助手 | `lint`, `format`, `explain_code` | | `git-helper` | Git 助手 | `git_status`, `git_log`, `git_diff` | | `web-helper` | Web 助手 | `fetch_url`, `parse_html` | | `file-organizer` | 文件整理 | `organize_files`, `cleanup_duplicates` | ### 6.2 BuiltinPlugins ```python # backend/app/agents/plugins/builtins.py BUILTIN_PLUGINS = [ { "id": "code-helper", "name": "Code Helper", "version": "1.0.0", "description": "代码辅助工具集", "capabilities": { "tools": [ { "name": "lint", "description": "检查代码风格", "parameters": { "type": "object", "properties": { "code": {"type": "string"}, "language": {"type": "string"} } } }, { "name": "format", "description": "格式化代码", "parameters": { "type": "object", "properties": { "code": {"type": "string"}, "language": {"type": "string"} } } } ] } } ] ``` --- ## 7. API 接口 ```python # backend/app/routers/plugins.py @router.get("/api/plugins") async def list_plugins( current_user: User = Depends(get_current_user) ): """列出用户已安装的插件""" plugins = await plugin_manager.list_plugins() return { "plugins": [ { "id": p.manifest.id, "name": p.manifest.name, "version": p.manifest.version, "state": p.state.value, "description": p.manifest.description } for p in plugins ] } @router.post("/api/plugins/install") async def install_plugin( request: InstallPluginRequest, current_user: User = Depends(get_current_user) ): """安装插件""" plugin = await plugin_manager.install(Path(request.path)) return {"plugin_id": plugin.manifest.id} @router.post("/api/plugins/{plugin_id}/enable") async def enable_plugin( plugin_id: str, current_user: User = Depends(get_current_user) ): """启用插件""" plugin = await plugin_manager.enable(plugin_id) return {"state": plugin.state.value} @router.post("/api/plugins/{plugin_id}/disable") async def disable_plugin( plugin_id: str, current_user: User = Depends(get_current_user) ): """禁用插件""" plugin = await plugin_manager.disable(plugin_id) return {"state": plugin.state.value} @router.delete("/api/plugins/{plugin_id}") async def uninstall_plugin( plugin_id: str, current_user: User = Depends(get_current_user) ): """卸载插件""" await plugin_manager.uninstall(plugin_id) return {"status": "ok"} @router.get("/api/marketplace/plugins") async def search_marketplace( q: str | None = None, category: str | None = None, limit: int = 20 ): """搜索插件市场""" plugins = await marketplace.search(q, category, limit) return {"plugins": plugins} ``` --- ## 8. 文件结构 ``` backend/app/agents/plugins/ ├── __init__.py ├── manifest.py # 插件清单 ├── manager.py # 插件管理器 ├── sandbox.py # 插件沙箱 │ ├── builtins/ # 内置插件 │ ├── __init__.py │ ├── code_helper/ │ ├── git_helper/ │ └── web_helper/ │ ├── marketplace.py # 插件市场 │ └── router.py # API 路由(可选) backend/app/routers/ └── plugins.py # 插件 API 路由 ``` --- ## 9. 验收标准 | 检查点 | 标准 | |--------|------| | 插件安装 | 可以从目录安装插件 | | 插件启用 | 插件的工具和 Hook 正确注册 | | 插件禁用 | 插件的工具和 Hook 正确注销 | | 插件卸载 | 插件目录正确删除 | | 插件隔离 | 插件无法访问未授权资源 | | 市场搜索 | 可以搜索插件市场 | | 内置插件 | 内置插件默认安装 | --- ## 10. Demo 借鉴 | claw-code | Jarvis 对应 | |-----------|------------| | `src/plugins/builtinPlugins.ts` | `plugins/builtins.py` | | `src/plugins/bundled/index.ts` | `plugins/builtins/` | | `PluginLifecycle` | `PluginManager` | | `PluginInstallationManager` | `PluginManager.install` | | `/plugin`, `/reload-plugins` | `/api/plugins/*` |