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

90 lines
3.1 KiB
Python

from __future__ import annotations
import json
import logging
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
logger = logging.getLogger(__name__)
@dataclass
class ChannelPluginManifest:
id: str
name: str
version: str
description: str = ""
author: str = ""
dependencies: list[str] = field(default_factory=list)
entry_point: str | None = None
source_path: str | None = None
target_path: str | None = None
config_schema: dict[str, Any] = field(default_factory=dict)
capabilities: list[str] = field(default_factory=list)
tags: list[str] = field(default_factory=list)
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "ChannelPluginManifest":
return cls(
id=data["id"],
name=data.get("name", data["id"]),
version=data.get("version", "0.0.1"),
description=data.get("description", ""),
author=data.get("author", ""),
dependencies=data.get("dependencies", []),
entry_point=data.get("entry_point"),
source_path=data.get("source_path"),
target_path=data.get("target_path"),
config_schema=data.get("config_schema", {}),
capabilities=data.get("capabilities", []),
tags=data.get("tags", []),
)
def to_dict(self) -> dict[str, Any]:
return {
"id": self.id,
"name": self.name,
"version": self.version,
"description": self.description,
"author": self.author,
"dependencies": self.dependencies,
"entry_point": self.entry_point,
"source_path": self.source_path,
"target_path": self.target_path,
"config_schema": self.config_schema,
"capabilities": self.capabilities,
"tags": self.tags,
}
def load_manifests_from_directory(manifest_dir: str | Path) -> list[ChannelPluginManifest]:
"""从目录加载所有插件清单文件 (.json)。"""
manifest_dir = Path(manifest_dir)
if not manifest_dir.exists():
logger.warning("Manifest directory does not exist: %s", manifest_dir)
return []
manifests = []
for file_path in manifest_dir.glob("*.json"):
try:
data = json.loads(file_path.read_text(encoding="utf-8"))
if isinstance(data, list):
for item in data:
manifests.append(ChannelPluginManifest.from_dict(item))
else:
manifests.append(ChannelPluginManifest.from_dict(data))
except Exception:
logger.exception("Failed to load manifest from %s", file_path)
return manifests
def save_manifest(manifest: ChannelPluginManifest, manifest_dir: str | Path) -> Path:
"""保存插件清单到指定目录。"""
manifest_dir = Path(manifest_dir)
manifest_dir.mkdir(parents=True, exist_ok=True)
file_path = manifest_dir / f"{manifest.id}.json"
file_path.write_text(json.dumps(manifest.to_dict(), indent=2, ensure_ascii=False), encoding="utf-8")
logger.info("Saved manifest to %s", file_path)
return file_path