Files
JARVIS/development-doc/plan/agent-update/phase-8-plugin-ecosystem.md
WIN-JHFT4D3SIVT\caoxiaozhu a3fe4d24fc feat(agents): Phase 7-10 hook system, plugins, skills, orchestration
Phase 7: Built-in Hooks (audit_log, dangerous_confirmation, security_scan)
Phase 8: Plugin system (PluginManager, PluginSandbox, PluginManifest)
Phase 9: Skills registry (SkillRegistry, local/plugin/MCP loaders)
Phase 10: TeamLeader, RemoteTransport, BackgroundTaskManager
2026-04-04 22:56:27 +08:00

629 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 8插件生态Plugin Ecosystem
日期2026-04-04
状态:待开始
前置依赖Phase 6工具系统重构、Phase 7Hook 拦截层)
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/*` |