ForcePilot/backend/package/yuxi/channel/plugins/catalog.py
Kris dd1857221c feat(channel/plugins): 新增渠道插件系统完整实现
实现了渠道插件的全生命周期管理能力,包括:
1. 插件清单的加载、解析与存储
2. 依赖校验与拓扑排序安装/卸载
3. 插件发现与安全沙箱执行环境
4. 插件注册中心与目录管理能力
2026-05-21 10:27:58 +08:00

88 lines
3.0 KiB
Python

from __future__ import annotations
import logging
from typing import Any
from .discovery import ChannelPluginDiscovery
from .install import install_channel_plugin
from .manifest import ChannelPluginManifest, load_manifests_from_directory
from .registry import ChannelPluginRegistry
logger = logging.getLogger(__name__)
class ChannelPluginCatalog:
"""渠道插件目录 — 管理可用插件的注册、安装与查询。"""
def __init__(self, manifest_dir: str | None = None):
self._manifest_dir = manifest_dir
self._manifests: dict[str, ChannelPluginManifest] = {}
self._installed: set[str] = set()
def load_manifests(self) -> None:
if not self._manifest_dir:
return
manifests = load_manifests_from_directory(self._manifest_dir)
for manifest in manifests:
self._manifests[manifest.id] = manifest
logger.info("Loaded %d plugin manifests", len(self._manifests))
def get_manifest(self, plugin_id: str) -> ChannelPluginManifest | None:
return self._manifests.get(plugin_id)
def list_manifests(self) -> list[ChannelPluginManifest]:
return list(self._manifests.values())
def list_available(self) -> list[str]:
return list(self._manifests.keys())
def list_installed(self) -> list[str]:
return list(self._installed)
def is_installed(self, plugin_id: str) -> bool:
return plugin_id in self._installed
def install(self, plugin_id: str) -> bool:
manifest = self._manifests.get(plugin_id)
if not manifest:
logger.warning("Cannot install plugin '%s': manifest not found", plugin_id)
return False
try:
install_channel_plugin(manifest)
self._installed.add(plugin_id)
logger.info("Installed channel plugin: %s", plugin_id)
return True
except Exception:
logger.exception("Failed to install plugin '%s'", plugin_id)
return False
def uninstall(self, plugin_id: str) -> bool:
if plugin_id not in self._installed:
return False
self._installed.discard(plugin_id)
manifest = self._manifests.get(plugin_id)
target_dir = manifest.target_path if manifest else None
dependencies = manifest.dependencies if manifest else None
from .install import uninstall_channel_plugin
uninstall_channel_plugin(
plugin_id=plugin_id,
target_dir=target_dir,
dependencies=dependencies,
)
return True
def discover_builtin(self, package_name: str = "yuxi.channels") -> list[Any]:
discovered = ChannelPluginDiscovery.discover_builtin(package_name)
for plugin in discovered:
self._installed.add(getattr(plugin, "id"))
return discovered
def discover_from_path(self, plugin_path: str) -> list[Any]:
discovered = ChannelPluginDiscovery.discover_from_path(plugin_path)
for plugin in discovered:
self._installed.add(getattr(plugin, "id"))
return discovered