import asyncio
import concurrent.futures
import inspect
import posixpath
import threading
from contextlib import contextmanager
from pathlib import Path
from typing import (
    Any,
    Callable,
    ContextManager,
    Dict,
    List,
    Optional,
    Tuple,
    Type,
    Union,
)

from watchfiles import watch

from app.schemas.plugin import Plugin as _SchemaPlugin
from app.schemas.plugin import PluginDashboard as _SchemaPluginDashboard
from app.schemas.plugin import PluginInstance, PluginRuntimeStatus
from app.foundation.crypto import RSAUtils
from app.foundation.singleton import Singleton
from app.foundation.version import compare_version
from app.runtime.execution import run_in_threadpool_to_completion
from app.runtime.log import logger
from app.runtime.observability import observe_compat_facade
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.thread import ThreadHelper

settings = RuntimeSettingsCompat()
from app.runtime.events import EventHandlerBinding, eventmanager
from app.runtime.reload import ConfigReloadMixin
from app.runtime.extensions.plugin.loader import PluginLoader
from app.runtime.extensions.plugin.lifecycle import PluginLifecycle
from app.runtime.extensions.plugin.metadata import PluginMetadataMapper
from app.runtime.extensions.plugin.monitor import (
    PluginChangeMonitor,
    PluginMonitorController,
)
from app.runtime.extensions.plugin.projection import PluginProjection
from app.runtime.extensions.plugin.registry import PluginRegistry
from app.runtime.extensions.plugin.storage import get_plugin_storage
from app.runtime.extensions.plugin.system import get_plugin_system
from app.runtime.extensions.plugin.tools import PluginToolCatalog
from app.runtime.extensions.plugin.sync import (
    LocalPluginSyncService,
    PluginSyncService,
)
from app.runtime.extensions.plugin.clone import PluginCloneService
from app.runtime.extensions.plugin.access import PluginAccessPolicy
from app.runtime.extensions.plugin.admission import PluginMutationAdmission
from app.runtime.extensions.plugin.catalog import PluginCatalogFacade
from app.runtime.extensions.plugin.paths import PluginPathResolver
from app.runtime.extensions.plugin.dependency import (
    PluginDependencyClassification,
    PluginDependencyInstallResult,
    PluginDependencyService,
)
from app.runtime.extensions.plugin.storage import PluginConfigStore, PluginInstanceStore
from app.schemas.exception import PluginMutationRejectedError
from app.schemas.types import EventType, SystemConfigKey

LegacyDiagnosticsConfigurator = Callable[..., None]
LegacyImportScanner = Callable[..., None]
LegacyPluginImportPreparer = Callable[..., None]
PluginInstallReporter = Callable[..., None]
SiteAuthLevelProvider = Callable[[], int]
PluginCatalogFactory = Callable[["PluginManager"], Any]


def _ignore_legacy_diagnostics(**_kwargs) -> None:
    """在启动组合根尚未注入兼容服务时保持插件加载可用。"""


def _ignore_plugin_resource_imports(**_kwargs) -> None:
    """未进入应用启动组合时不主动创建进程级宿主资源。"""


def _unavailable_site_auth_level() -> int:
    """站点能力尚未装配时返回未认证等级。"""
    return 0


def _unavailable_plugin_catalog_factory(_manager: "PluginManager") -> Any:
    """在启动组合根尚未装配目录用例时拒绝隐式跨层构造。"""
    raise RuntimeError("插件目录应用服务尚未由启动组合根装配")


_legacy_diagnostics_configurator: LegacyDiagnosticsConfigurator = (
    _ignore_legacy_diagnostics
)
_legacy_import_scanner: LegacyImportScanner = _ignore_legacy_diagnostics
_legacy_plugin_import_preparer: LegacyPluginImportPreparer = (
    _ignore_plugin_resource_imports
)
_plugin_install_reporter: PluginInstallReporter = _ignore_legacy_diagnostics
_site_auth_level_provider: SiteAuthLevelProvider = _unavailable_site_auth_level
_plugin_catalog_factory: PluginCatalogFactory = _unavailable_plugin_catalog_factory


def configure_plugin_legacy_import_services(
    *,
    diagnostics_configurator: LegacyDiagnosticsConfigurator,
    import_scanner: LegacyImportScanner,
) -> None:
    """由启动组合根注入插件旧导入诊断服务，避免扩展层反向依赖兼容层。"""
    global _legacy_diagnostics_configurator, _legacy_import_scanner
    _legacy_diagnostics_configurator = diagnostics_configurator
    _legacy_import_scanner = import_scanner


def configure_plugin_resource_import_preparer(
    preparer: LegacyPluginImportPreparer,
) -> None:
    """注入旧插件导入前的宿主资源准备器。"""
    global _legacy_plugin_import_preparer
    _legacy_plugin_import_preparer = preparer


def configure_plugin_install_reporter(reporter: PluginInstallReporter) -> None:
    """由启动组合根注入插件安装上报器，避免扩展层依赖远程服务。"""
    global _plugin_install_reporter
    _plugin_install_reporter = reporter


def configure_site_auth_level_provider(provider: SiteAuthLevelProvider) -> None:
    """由启动组合根注入站点认证等级，避免扩展运行时依赖应用服务。"""
    global _site_auth_level_provider
    _site_auth_level_provider = provider


def configure_plugin_catalog_factory(factory: PluginCatalogFactory) -> None:
    """由启动组合根注入插件目录应用服务工厂，消除 Runtime 反向依赖。"""
    global _plugin_catalog_factory
    _plugin_catalog_factory = factory


@observe_compat_facade("PluginManager")
class PluginManager(ConfigReloadMixin, metaclass=Singleton):
    """插件管理器"""
    CONFIG_WATCH = {"DEV", "PLUGIN_AUTO_RELOAD", "PLUGIN_LOCAL_REPO_PATHS"}
    AGENT_TOOLS_BUILD_MAX_ATTEMPTS = 3

    def __init__(self):
        """初始化插件注册表、缓存和开发模式监控状态。"""
        self._plugin_registry = PluginRegistry()
        # 旧属性继续引用注册表拥有的可变字典，保持插件和测试的访问身份。
        self._plugins = self._plugin_registry.classes
        self._running_plugins = self._plugin_registry.running
        self._plugin_instance_store = PluginInstanceStore(
            storage=lambda: get_plugin_storage(),
        )
        # 配置Key
        self._config_key: str = "plugin.%s"
        self._plugin_config_store = PluginConfigStore(
            storage=lambda: get_plugin_storage(),
            plugin_exists=lambda pid: bool(self._plugins.get(pid)),
            key_prefix=self._config_key,
        )
        self._plugin_access = PluginAccessPolicy(
            auth_level=lambda: _site_auth_level_provider(),
            verify_keys=RSAUtils.verify_rsa_keys,
            log=logger,
        )
        self._plugin_catalog_view = PluginCatalogFacade(
            classes=lambda: self._plugins,
            running=lambda: self._running_plugins,
            storage=lambda: get_plugin_storage(),
            system=get_plugin_system,
            market_catalog=lambda: self._plugin_catalog(),
            market_loader=lambda market, package_version=None, force=False: (
                self.get_plugins_from_market(market, package_version, force)
            ),
            async_market_loader=lambda market, package_version=None, force=False: (
                self.async_get_plugins_from_market(market, package_version, force)
            ),
            map_plugin=lambda **kwargs: self._process_plugin_info(**kwargs),
            auth_checker=lambda **kwargs: self.__set_and_check_auth_level(**kwargs),
            plugin_attr=lambda pid, attr: self.get_plugin_attr(pid, attr),
            plugin_instance=self.get_plugin_instance,
            plugin_instances=self.get_plugin_instances,
            runtime_status=self._plugin_registry.runtime_status,
            log=logger,
        )
        # 本地插件同步写入运行目录后的短时忽略窗口
        self._recent_local_sync: Dict[str, float] = {}
        self._monitor_suppression_lock = threading.Lock()
        self._suppressed_monitor_plugins: Dict[str, int] = {}
        self._plugin_paths = PluginPathResolver(
            runtime_root=settings.ROOT_PATH / "app" / "plugins",
            running=lambda: self._running_plugins,
            system=get_plugin_system,
            strict_system_version=lambda: not settings.DEV,
            log=logger,
        )
        self._local_plugin_sync = LocalPluginSyncService(
            installed_plugins=lambda: get_plugin_storage().read(
                SystemConfigKey.UserInstalledPlugins
            ) or [],
            candidate=lambda pid: get_plugin_system().local_candidate(pid),
            system=get_plugin_system,
            recent_sync=self._recent_local_sync,
            log=logger,
        )
        self._plugin_monitor = PluginMonitorController(
            runner=self._run_file_watcher,
            log=logger,
        )
        self._plugin_quiesce_lock = threading.RLock()
        self._plugin_quiesce_future: Optional[
            concurrent.futures.Future[bool]
        ] = None
        self._plugin_service_quiesce_future: Optional[
            concurrent.futures.Future[bool]
        ] = None
        self._plugin_mutation_admission = PluginMutationAdmission()
        self._plugin_runtime_closed = False
        self._plugin_dependencies = PluginDependencyService(
            system=get_plugin_system,
            log=logger,
        )
        self._plugin_loader = PluginLoader(
            plugins_root=settings.ROOT_PATH / "app" / "plugins",
            import_preparer=lambda **kwargs: _legacy_plugin_import_preparer(**kwargs),
            import_scanner=lambda **kwargs: _legacy_import_scanner(**kwargs),
            log=logger,
        )
        self._plugin_tool_catalog = PluginToolCatalog(
            max_attempts=self.AGENT_TOOLS_BUILD_MAX_ATTEMPTS
        )
        self._plugin_lifecycle = PluginLifecycle(
            classes=self._plugins,
            running=self._running_plugins,
            load_plugins=lambda pid, installed, check: self._load_runtime_plugins(
                pid,
                installed,
                check,
            ),
            installed_plugins=lambda: get_plugin_storage().read(
                SystemConfigKey.UserInstalledPlugins
            ) or [],
            plugin_config=self.get_plugin_config,
            auth_checker=lambda plugin: self.__set_and_check_auth_level(plugin=plugin),
            clear_modules=lambda pid: self._clear_plugin_modules(pid),
            clear_tools=self.clear_plugin_agent_tools_cache,
            enable_events=eventmanager.enable_event_handler,
            disable_events=eventmanager.disable_event_handler,
            runtime_status_writer=self._plugin_registry.set_runtime_status,
            log=logger,
            event_sender=eventmanager.send_event,
        )
        self._plugin_metadata = PluginMetadataMapper(
            plugin_instance=self._plugin_registry.instance,
            plugin_class=self._plugin_registry.plugin_class,
            annotate_system_version=lambda info: get_plugin_system().annotate_system_version(
                info
            ),
            is_package_compatible=lambda info, version: get_plugin_system().is_package_compatible(
                info,
                version,
            ),
            auth_checker=lambda plugin, source: self.__set_and_check_auth_level(
                plugin,
                source,
            ),
            version_compare=compare_version,
            log=logger,
        )
        self._plugin_sync = PluginSyncService(
            frozen=lambda: get_plugin_system().is_frozen(),
            installed_plugins=lambda: get_plugin_storage().read(
                SystemConfigKey.UserInstalledPlugins
            ) or [],
            online_plugins=lambda: self.get_online_plugins(),
            local_plugins=lambda: self.get_local_repo_plugins(),
            merge_plugins=lambda higher, base, _markets: self.process_plugins_list(
                higher,
                base,
            ),
            plugin_exists=lambda plugin_id, version: self.is_plugin_exists(
                plugin_id,
                version,
            ),
            install=lambda plugin_id, repo_url, force: get_plugin_system().package.install(
                plugin_id=plugin_id,
                repo_url=repo_url,
                force_install=force,
            ),
            report=lambda **kwargs: _plugin_install_reporter(**kwargs),
            log=logger,
        )
        self._plugin_clone = PluginCloneService(
            plugin_class=self._plugin_registry.plugin_class,
            plugin_exists=lambda plugin_id: self.is_plugin_exists(plugin_id),
            source_plugin_id=self.get_plugin_source_id,
            save_instance=self._plugin_instance_store.save,
            delete_instance=self._plugin_instance_store.delete,
            read_config=self.get_plugin_config,
            save_config=lambda plugin_id, config: self.save_plugin_config(
                plugin_id,
                config,
                force=True,
            ),
            delete_config=lambda plugin_id: self.delete_plugin_config(
                plugin_id,
                force=True,
            ),
            reload_plugin=self.reload_plugin,
            remove_plugin=self.remove_plugin,
            log=logger,
        )
        # 事件总线只通过通用解析器访问运行中的插件实例。
        eventmanager.register_handler_instance_resolver(
            "plugins",
            self.resolve_event_handler_instance,
        )
    def resolve_event_handler_instance(
            self,
            owner_class: Type[Any],
    ) -> Optional[EventHandlerBinding]:
        """为插件声明的事件方法解析当前运行实例。"""
        plugin_id = owner_class.__name__
        # 旧测试与部分扩展会替换私有映射来构造隔离运行态，解析器继续尊重该接缝。
        if plugin_id not in self._plugins:
            return None
        plugin = self._running_plugins.get(plugin_id)
        owner_name = plugin_id
        if plugin and callable(getattr(plugin, "get_name", None)):
            owner_name = plugin.get_name()
        return EventHandlerBinding(
            instance=plugin,
            owner_name=owner_name,
            run_sync_in_threadpool=True,
        )

    def init_config(self):
        """按最新系统配置完整重启插件。"""
        try:
            with self.mutation("配置热重载"):
                # 停止已有插件
                self.stop()
                classification = self.classify_plugins()
                self.apply_plugin_dependency_classification(classification)
                for plugin_id in classification.ready:
                    self.start(plugin_id)
        except PluginMutationRejectedError as error:
            logger.warning(str(error))

    def start(self, pid: Optional[str] = None) -> Dict[str, PluginRuntimeStatus]:
        """
        启动加载插件
        :param pid: 插件ID，为空加载所有插件
        """

        try:
            with self.mutation("启动插件"):
                with self._plugin_quiesce_lock:
                    _legacy_diagnostics_configurator(
                        enabled=settings.DEBUG,
                        emitter=logger.warning,
                    )
                    return self._plugin_lifecycle.start(pid)
        except PluginMutationRejectedError as error:
            logger.warning(str(error))
            if pid:
                return {pid: PluginRuntimeStatus.LOAD_FAILED}
            return {}

    def init_plugin(self, plugin_id: str, conf: dict):
        """
        初始化插件
        :param plugin_id: 插件ID
        :param conf: 插件配置
        """
        try:
            with self.mutation("初始化插件配置"):
                with self._plugin_quiesce_lock:
                    self._plugin_lifecycle.initialize(plugin_id, conf)
        except PluginMutationRejectedError as error:
            logger.warning(str(error))

    def clear_plugin_agent_tools_cache(self) -> None:
        """
        清空插件智能体工具注册表缓存。
        """
        self._plugin_tool_catalog.clear()

    def get_plugin_agent_tools_revision(self) -> int:
        """
        获取插件智能体工具注册表版本号。
        """
        return self._plugin_tool_catalog.revision

    @property
    def _plugin_agent_tools_revision(self) -> int:
        """兼容读取旧私有字段，实际版本由独立工具目录持有。"""
        return self._plugin_tool_catalog.revision

    def stop(self, pid: Optional[str] = None) -> None:
        """
        停止插件服务
        :param pid: 插件ID，为空停止所有插件
        """
        try:
            with self.mutation("停止插件"):
                with self._plugin_quiesce_lock:
                    self._plugin_lifecycle.stop(pid)
        except PluginMutationRejectedError as error:
            logger.warning(str(error))

    def mutation(self, operation: str) -> ContextManager[None]:
        """为一个完整插件可变事务取得可跨异步边界传播的准入 lease。"""
        return self._plugin_mutation_admission.hold(operation)

    def reopen_plugins(self) -> bool:
        """为新应用生命周期解除运行时封口，仍活跃的 quiesce owner 禁止复用。"""
        with self._plugin_quiesce_lock:
            futures = (
                self._plugin_quiesce_future,
                self._plugin_service_quiesce_future,
            )
            if any(future is not None and not future.done() for future in futures):
                logger.warning("插件后台服务仍在停止，无法开启新的应用生命周期")
                return False
            if self._plugin_runtime_closed and self._running_plugins:
                logger.warning("上一应用生命周期仍持有插件实例，拒绝解除运行时封口")
                return False
            if not self._plugin_mutation_admission.reopen():
                logger.warning("上一应用生命周期仍有插件可变事务，拒绝解除运行时封口")
                return False
            self._plugin_runtime_closed = False
            return True

    async def quiesce_plugins(self, timeout: float = 240.0) -> bool:
        """封口变更事务并停用插件 handler，超时后保留 Future ownership。"""
        if self._plugin_mutation_admission.is_held():
            logger.warning("插件可变事务不能等待自身收敛，拒绝在事务内执行停机")
            return False
        with self._plugin_quiesce_lock:
            self._plugin_runtime_closed = True
            self._plugin_mutation_admission.seal()
            future = self._plugin_quiesce_future
            if future is None or future.done():
                future = ThreadHelper().submit(self._quiesce_after_mutations)
                self._plugin_quiesce_future = future
        try:
            result = await asyncio.wait_for(
                asyncio.shield(asyncio.wrap_future(future)),
                timeout=max(0.0, timeout),
            )
            return bool(result)
        except asyncio.TimeoutError:
            logger.error(f"插件后台服务未在 {timeout:g} 秒内收敛")
            return False
        except Exception as error:  # noqa: BLE001  Future 异常必须转为生命周期结果
            logger.error(f"插件后台服务停止失败：{error}", exc_info=True)
            return False
        finally:
            if future.done():
                with self._plugin_quiesce_lock:
                    if self._plugin_quiesce_future is future:
                        self._plugin_quiesce_future = None

    async def quiesce_plugin_services(self, timeout: float = 240.0) -> bool:
        """在事件结算后执行旧插件停机 hook，并有界等待同步 owner。"""
        with self._plugin_quiesce_lock:
            prepare_future = self._plugin_quiesce_future
            if prepare_future is not None and not prepare_future.done():
                logger.warning("插件事件入口仍在封口，拒绝提前关闭插件资源")
                return False
            if not self._plugin_runtime_closed:
                logger.warning("插件运行时尚未封口，拒绝关闭插件资源")
                return False
            if self._plugin_mutation_admission.active_count:
                logger.warning("插件可变事务仍在执行，拒绝关闭插件资源")
                return False
            future = self._plugin_service_quiesce_future
            if future is None or future.done():
                future = ThreadHelper().submit(
                    self._plugin_lifecycle.quiesce_services,
                )
                self._plugin_service_quiesce_future = future
        try:
            result = await asyncio.wait_for(
                asyncio.shield(asyncio.wrap_future(future)),
                timeout=max(0.0, timeout),
            )
            return bool(result)
        except asyncio.TimeoutError:
            logger.error(f"插件旧停机 hook 未在 {timeout:g} 秒内收敛")
            return False
        except Exception as error:  # noqa: BLE001  Future 异常必须转为生命周期结果
            logger.error(f"插件旧停机 hook 执行失败：{error}", exc_info=True)
            return False
        finally:
            if future.done():
                with self._plugin_quiesce_lock:
                    if self._plugin_service_quiesce_future is future:
                        self._plugin_service_quiesce_future = None

    def finalize_plugins(self) -> bool:
        """确认 quiesce owner 已结束后禁用 handler 并卸载插件实例。"""
        with self._plugin_quiesce_lock:
            futures = (
                self._plugin_quiesce_future,
                self._plugin_service_quiesce_future,
            )
        if any(future is not None and not future.done() for future in futures):
            logger.warning("插件后台服务仍在停止，拒绝释放运行实例")
            return False
        if self._plugin_mutation_admission.active_count:
            logger.warning("插件可变事务仍在执行，拒绝释放运行实例")
            return False
        return self._plugin_lifecycle.finalize()

    def _quiesce_after_mutations(self) -> bool:
        """等待已获准变更自然结束后，再停用插件事件入口。"""
        self._plugin_mutation_admission.wait_until_idle()
        return self._plugin_lifecycle.quiesce_handlers()

    @staticmethod
    def _load_selective_plugins(pid: Optional[str], installed_plugins: List[str],
                                check_module_func: Callable) -> List[Any]:
        """
        选择性加载插件，只import符合条件的插件
        :param pid: 指定插件ID，为空则加载所有已安装插件
        :param installed_plugins: 已安装插件列表
        :param check_module_func: 模块检查函数
        :return: 插件类列表
        """
        return PluginLoader(
            plugins_root=settings.ROOT_PATH / "app" / "plugins",
            import_preparer=lambda **kwargs: _legacy_plugin_import_preparer(**kwargs),
            import_scanner=lambda **kwargs: _legacy_import_scanner(**kwargs),
            log=logger,
        ).load(pid, installed_plugins, check_module_func)

    def _load_runtime_plugins(
        self,
        pid: Optional[str],
        installed_plugins: List[str],
        check_module_func: Callable[[Any], bool],
    ) -> List[Any]:
        """加载物理插件或虚拟实例，并保留旧选择性加载方法的合同。"""
        if pid:
            instance = self._plugin_instance_store.get(pid)
            if instance:
                return self._plugin_loader.load_instance(instance, check_module_func)
            return self._plugin_loader.load(pid, installed_plugins, check_module_func)

        plugins = self._plugin_loader.load(
            None,
            installed_plugins,
            check_module_func,
        )
        for instance in self._plugin_instance_store.all().values():
            plugins.extend(
                self._plugin_loader.load_instance(instance, check_module_func)
            )
        return plugins

    @property
    def running_plugins(self) -> Dict[str, Any]:
        """
        获取运行态插件列表
        :return: 运行态插件列表
        """
        return self._plugin_registry.running

    @property
    def plugins(self) -> Dict[str, Any]:
        """
        获取插件列表
        :return: 插件列表
        """
        return self._plugin_registry.classes

    def on_config_changed(self):
        """在插件监控配置变化后重建文件监控。"""
        self.reload_monitor()

    def get_reload_name(self) -> str:
        """返回配置重载日志使用的功能名称。"""
        return "插件文件修改监测"

    def start_monitor(self, *, reopen: bool = False) -> None:
        """按当前配置启动监控；新生命周期可显式解除既有封口。"""
        if reopen and not self._plugin_monitor.reopen():
            return
        if (
            not self.is_plugin_settling()
            and (settings.DEV or settings.PLUGIN_AUTO_RELOAD)
        ):
            self._plugin_monitor.start()

    def reload_monitor(self):
        """
        重新加载插件文件修改监测
        """
        self._plugin_monitor.reload(
            enabled=(
                not self.is_plugin_settling()
                and (settings.DEV or settings.PLUGIN_AUTO_RELOAD)
            )
        )

    def stop_monitor(self, timeout: float = 5.0) -> bool:
        """停止插件文件监控，并返回线程是否在预算内真正退出。"""
        return self._plugin_monitor.stop(timeout=timeout)

    def close_monitor(self, timeout: float = 5.0) -> bool:
        """封口当前生命周期的文件监控，并返回线程是否真正退出。"""
        return self._plugin_monitor.close(timeout=timeout)

    def _run_file_watcher(self):
        """
        运行 watchfiles 监视器的主循环。
        """
        PluginChangeMonitor(
            runtime_root=settings.ROOT_PATH / "app" / "plugins",
            local_roots=get_plugin_system().local_repo_paths,
            stop_event=self._plugin_monitor.stop_event,
            recent_sync=self._recent_local_sync,
            federated_change=self._get_federated_plugin_change,
            runtime_plugin=self._get_plugin_id_from_path,
            monitor_suppressed=self.is_plugin_monitor_suppressed,
            local_candidate=self._get_local_plugin_candidate_from_path,
            sync_local=self._sync_local_plugin_if_installed,
            reload_plugin=self.reload_plugin_tree,
            dependency_manifest_status=(
                get_plugin_system().dependency_manifest_status
            ),
            watch=watch,
            log=logger,
        ).run()

    def _get_federated_plugin_change(
        self,
        event_path: Path,
    ) -> Optional[Tuple[str, Optional[dict], bool]]:
        """
        识别运行态 Vue 插件声明目录内的构建产物变化。

        :return: 插件 ID、本地仓库候选和联邦入口是否完整；非联邦目录变化返回 None。
        """
        return self._plugin_paths.federated_change(event_path)

    def _get_plugin_id_from_path(self, event_path: Path) -> Optional[str]:
        """
        根据文件路径解析出插件的ID。
        :param event_path: 被修改文件的 Path 对象。
        :return: 插件ID字符串，如果不是有效插件文件则返回 None。
        """
        return self._plugin_paths.runtime_plugin(event_path)

    def _get_local_plugin_candidate_from_path(self, event_path: Path) -> Optional[dict]:
        """
        根据本地插件仓库路径解析具体插件候选，保留 plugins/plugins.v2 来源差异
        """
        return self._plugin_paths.local_candidate(event_path)

    def _sync_local_plugin_if_installed(self, pid: str, candidate: Optional[dict] = None) -> bool:
        """
        已安装本地插件源码变化时，同步到运行目录
        """
        try:
            with self.mutation("同步本地插件源码"):
                return self._local_plugin_sync.sync(pid, candidate)
        except PluginMutationRejectedError as error:
            logger.warning(str(error))
            return False

    @contextmanager
    def suppress_plugin_monitor(self, plugin_id: str):
        """在插件目录原子更新期间阻止文件监控抢先重载半成品。"""
        with self.mutation("更新插件包"):
            normalized_id = plugin_id.lower()
            with self._monitor_suppression_lock:
                self._suppressed_monitor_plugins[normalized_id] = (
                    self._suppressed_monitor_plugins.get(normalized_id, 0) + 1
                )
            try:
                yield
            finally:
                with self._monitor_suppression_lock:
                    count = self._suppressed_monitor_plugins.get(normalized_id, 0)
                    if count <= 1:
                        self._suppressed_monitor_plugins.pop(normalized_id, None)
                    else:
                        self._suppressed_monitor_plugins[normalized_id] = count - 1

    def is_plugin_monitor_suppressed(self, plugin_id: str) -> bool:
        """判断指定插件是否处于安装或替换写入阶段。"""
        with self._monitor_suppression_lock:
            return self._suppressed_monitor_plugins.get(plugin_id.lower(), 0) > 0

    def remove_plugin(self, plugin_id: str):
        """
        从内存中移除一个插件
        :param plugin_id: 插件ID
        """
        try:
            with self.mutation("移除插件实例"):
                with self._plugin_quiesce_lock:
                    self._plugin_lifecycle.stop(plugin_id)
                    self._plugin_registry.remove(plugin_id)
        except PluginMutationRejectedError as error:
            logger.warning(str(error))

    def reload_plugin(self, plugin_id: str) -> PluginRuntimeStatus:
        """
        将一个插件重新加载到内存
        :param plugin_id: 插件ID
        """
        try:
            with self.mutation("重新加载插件"):
                with self._plugin_quiesce_lock:
                    return self._plugin_lifecycle.reload(
                        plugin_id,
                        EventType.PluginReload,
                    )
        except PluginMutationRejectedError as error:
            logger.warning(str(error))
            return PluginRuntimeStatus.LOAD_FAILED

    def reload_plugin_tree(self, plugin_id: str) -> PluginRuntimeStatus:
        """重载源码插件，并同步刷新所有引用该源码的虚拟实例。"""
        try:
            with self.mutation("重载插件实例树"):
                with self._plugin_quiesce_lock:
                    source_plugin_id = self.get_plugin_source_id(plugin_id)
                    status = self.reload_plugin(source_plugin_id)
                    for instance in self._plugin_instance_store.for_source(source_plugin_id):
                        self.reload_plugin(instance.instance_id)
                    return status
        except PluginMutationRejectedError as error:
            logger.warning(str(error))
            return PluginRuntimeStatus.LOAD_FAILED

    def get_plugin_reload_targets(self, plugin_id: str) -> List[str]:
        """返回源码更新后需要刷新注册信息的源插件及其实例 ID。"""
        source_plugin_id = self.get_plugin_source_id(plugin_id)
        return [
            source_plugin_id,
            *(
                instance.instance_id
                for instance in self._plugin_instance_store.for_source(
                    source_plugin_id
                )
            ),
        ]

    @staticmethod
    def _clear_plugin_modules(plugin_id: Optional[str] = None):
        """
        清除插件及其所有子模块的缓存
        :param plugin_id: 插件ID
        """

        return PluginLoader(
            plugins_root=settings.ROOT_PATH / "app" / "plugins",
            import_preparer=lambda **kwargs: _legacy_plugin_import_preparer(**kwargs),
            import_scanner=lambda **kwargs: _legacy_import_scanner(**kwargs),
            log=logger,
        ).clear_modules(plugin_id)

    def sync(self) -> List[str]:
        """
        安装本地不存在或需要更新的插件
        """

        with self.mutation("同步插件包"):
            return self._plugin_sync.sync()

    @staticmethod
    def install_plugin_missing_dependencies() -> List[str]:
        """
        安装插件中缺失或不兼容的依赖项
        """
        manager = PluginManager()
        with manager.mutation("安装插件依赖"):
            return PluginDependencyService(
                system=get_plugin_system,
                log=logger,
            ).install_missing()

    @staticmethod
    def install_plugin_missing_dependencies_with_status() -> PluginDependencyInstallResult:
        """安装插件缺失依赖并返回缺失项及安装成功状态。"""
        manager = PluginManager()
        with manager.mutation("安装插件依赖"):
            return PluginDependencyService(
                system=get_plugin_system,
                log=logger,
            ).install_missing_with_status()

    @staticmethod
    async def async_install_plugin_missing_dependencies_with_status() -> PluginDependencyInstallResult:
        """在异步启动链中恢复插件依赖并保留取消语义。"""
        manager = PluginManager()
        with manager.mutation("安装插件依赖"):
            return await PluginDependencyService(
                system=get_plugin_system,
                log=logger,
            ).async_install_missing_with_status()

    def classify_plugins(self) -> PluginDependencyClassification:
        """按源码依赖状态分类物理插件，并把结果映射到虚拟实例。"""
        source_classification = PluginDependencyService(
            system=get_plugin_system,
            log=logger,
        ).classify_plugins()
        ready = list(source_classification.ready)
        missing_dependencies = list(source_classification.missing_dependencies)
        missing_source = list(source_classification.missing_source)
        source_ready = set(source_classification.ready)
        source_pending = set(source_classification.missing_dependencies)
        for instance in self._plugin_instance_store.all().values():
            if instance.source_plugin_id in source_ready:
                ready.append(instance.instance_id)
            elif instance.source_plugin_id in source_pending:
                missing_dependencies.append(instance.instance_id)
            else:
                missing_source.append(instance.instance_id)
        return PluginDependencyClassification(
            ready=tuple(ready),
            missing_dependencies=tuple(missing_dependencies),
            missing_source=tuple(missing_source),
        )

    def apply_plugin_dependency_classification(
        self,
        classification: PluginDependencyClassification,
    ) -> None:
        """把源码和依赖分类写入运行状态，已激活插件保持当前结果。"""
        running_ids = set(self._plugin_registry.running_ids())
        for plugin_id in classification.missing_source:
            self._plugin_registry.set_runtime_status(
                plugin_id,
                PluginRuntimeStatus.SOURCE_MISSING,
            )
        for plugin_id in classification.missing_dependencies:
            self._plugin_registry.set_runtime_status(
                plugin_id,
                PluginRuntimeStatus.DEPENDENCY_PENDING,
            )
        for plugin_id in classification.ready:
            current_status = self._plugin_registry.runtime_status(plugin_id)
            if (
                plugin_id in running_ids
                and current_status is not PluginRuntimeStatus.DEPENDENCY_PENDING
            ):
                continue
            self._plugin_registry.set_runtime_status(
                plugin_id,
                PluginRuntimeStatus.READY,
            )

    def set_plugin_settling(self, settling: bool) -> None:
        """更新启动后的插件恢复任务状态。"""
        self._plugin_registry.set_settling(settling)

    def get_plugin_runtime_statuses(self) -> Dict[str, PluginRuntimeStatus]:
        """返回插件运行状态快照。"""
        return self._plugin_registry.runtime_status_snapshot()

    def get_plugin_runtime_generation(self) -> int:
        """返回插件状态变化代次。"""
        return self._plugin_registry.generation

    def is_plugin_settling(self) -> bool:
        """返回插件源码和依赖是否仍在后台恢复。"""
        return self._plugin_registry.settling

    def get_plugin_config(self, pid: str) -> dict:
        """
        获取插件配置
        :param pid: 插件ID
        """
        return self._plugin_config_store.read(pid)

    def get_plugin_instances(self) -> dict[str, PluginInstance]:
        """返回全部有效虚拟插件实例描述。"""
        return self._plugin_instance_store.all()

    def get_plugin_instance(self, plugin_id: str) -> Optional[PluginInstance]:
        """返回指定虚拟插件实例描述，物理插件返回空。"""
        return self._plugin_instance_store.get(plugin_id)

    def get_plugin_source_id(self, plugin_id: str) -> str:
        """解析插件运行身份对应的源码身份，普通插件保持原值。"""
        instance = self._plugin_instance_store.get(plugin_id)
        return instance.source_plugin_id if instance else plugin_id

    def get_plugin_source_instances(self, plugin_id: str) -> List[PluginInstance]:
        """返回直接引用指定源码插件的虚拟实例。"""
        return self._plugin_instance_store.for_source(plugin_id)

    def delete_plugin_instance(self, plugin_id: str) -> bool:
        """删除虚拟实例描述；调用方仍负责停止实例和清理业务数据。"""
        try:
            with self.mutation("删除插件实例描述"):
                return self._plugin_instance_store.delete(plugin_id)
        except PluginMutationRejectedError as error:
            logger.warning(str(error))
            return False

    def save_plugin_config(self, pid: str, conf: dict, force: bool = False) -> bool:
        """
        保存插件配置
        :param pid: 插件ID
        :param conf: 配置
        :param force: 强制保存
        """
        try:
            with self.mutation("保存插件配置"):
                return self._plugin_config_store.write(pid, conf, force)
        except PluginMutationRejectedError as error:
            logger.warning(str(error))
            return False

    async def async_save_plugin_config(
        self, pid: str, conf: dict, force: bool = False
    ) -> bool:
        """
        异步保存插件配置。
        :param pid: 插件ID
        :param conf: 配置
        :param force: 强制保存
        """
        try:
            with self.mutation("保存插件配置"):
                return await self._plugin_config_store.async_write(pid, conf, force)
        except PluginMutationRejectedError as error:
            logger.warning(str(error))
            return False

    def delete_plugin_config(self, pid: str, force: bool = False) -> bool:
        """
        删除插件配置
        :param pid: 插件ID
        :param force: 插件停止后仍允许按插件 ID 删除持久化配置
        """
        try:
            with self.mutation("删除插件配置"):
                return self._plugin_config_store.delete(pid, force)
        except PluginMutationRejectedError as error:
            logger.warning(str(error))
            return False

    def delete_plugin_data(self, pid: str, force: bool = False) -> bool:
        """
        删除插件数据
        :param pid: 插件ID
        :param force: 插件停止后仍允许按插件 ID 删除持久化数据
        """
        try:
            with self.mutation("删除插件数据"):
                return self._plugin_config_store.delete_data(pid, force)
        except PluginMutationRejectedError as error:
            logger.warning(str(error))
            return False

    def get_plugin_state(self, pid: str) -> bool:
        """
        获取插件状态
        :param pid: 插件ID
        """
        plugin = self._plugin_registry.instance(pid)
        return plugin.get_state() if plugin else False

    def _plugin_projection(self) -> PluginProjection:
        """构造绑定当前运行态插件注册表的能力投影服务。"""
        return PluginProjection(
            self._plugin_registry.running,
            logger,
            self.get_plugin_remote_entry,
        )

    def _plugin_catalog(self) -> Any:
        """构造绑定当前市场客户端和插件 DTO 映射器的目录应用服务。"""
        return _plugin_catalog_factory(self)

    def get_plugin_commands(self, pid: Optional[str] = None) -> List[Dict[str, Any]]:
        """
        获取插件命令
        [{
            "cmd": "/xx",
            "event": EventType.xx,
            "desc": "xxxx",
            "data": {},
            "pid": "",
        }]
        """
        return self._plugin_projection().commands(pid)

    def get_plugin_apis(self, pid: Optional[str] = None) -> List[Dict[str, Any]]:
        """
        获取插件API
        [{
            "path": "/xx",
            "endpoint": self.xxx,
            "methods": ["GET", "POST"],
            "summary": "API名称",
            "description": "API说明",
            "allow_anonymous": false
        }]
        """
        return self._plugin_projection().apis(pid)

    def get_plugin_services(self, pid: Optional[str] = None) -> List[Dict[str, Any]]:
        """
        获取插件服务
        [{
            "id": "服务ID",
            "name": "服务名称",
            "trigger": "触发器：cron、interval、date、CronTrigger.from_crontab()",
            "func": self.xxx,
            "kwargs": {} # 定时器参数,
            "func_kwargs": {} # 方法参数
        }]
        """
        return self._plugin_projection().services(pid)

    def get_plugin_modules(self, pid: Optional[str] = None) -> Dict[tuple, Dict[str, Any]]:
        """
        获取插件模块
        {
            plugin_id: {
                method: function
            }
        }
        """
        return self._plugin_projection().modules(pid)

    def get_media_sources(self, pid: Optional[str] = None) -> List[Dict[str, Any]]:
        """获取运行中插件声明的媒体数据源。"""
        return self._plugin_projection().media_sources(pid)

    def get_plugin_actions(self, pid: Optional[str] = None) -> List[Dict[str, Any]]:
        """
        获取插件动作
        [{
            "id": "动作ID",
            "name": "动作名称",
            "func": self.xxx,
            "kwargs": {} # 需要附加传递的参数
        }]
        """
        return self._plugin_projection().actions(pid)

    @staticmethod
    def _copy_plugin_agent_tools(
        tools_info: List[Dict[str, Any]]
    ) -> List[Dict[str, Any]]:
        """
        复制插件智能体工具注册信息，避免调用方修改缓存内容。
        """
        return PluginToolCatalog.copy(tools_info)

    def get_plugin_agent_tools(self, pid: Optional[str] = None) -> List[Dict[str, Any]]:
        """
        获取插件智能体工具
        [{
            "plugin_id": "插件ID",
            "plugin_name": "插件名称",
            "tools": [ToolClass1, ToolClass2, ...]
        }]
        """
        return self._plugin_tool_catalog.get(
            self._running_plugins,
            plugin_id=pid,
            log=logger,
        )

    @staticmethod
    def get_plugin_remote_entry(plugin_id: str, dist_path: str) -> str:
        """
        获取插件的远程入口地址
        :param plugin_id: 插件 ID
        :param dist_path: 插件的分发路径
        :return: 远程入口地址
        """
        dist_path = dist_path.strip("/")
        path = posixpath.join(
            "plugin",
            "file",
            plugin_id.lower(),
            dist_path,
            "remoteEntry.js",
        )
        if not path.startswith("/"):
            path = "/" + path
        return path

    def get_plugin_remotes(self, pid: Optional[str] = None) -> List[Dict[str, Any]]:
        """
        获取插件联邦组件列表
        """
        return self._plugin_projection().remotes(pid)

    def get_plugin_auth_providers(self) -> List[Dict[str, Any]]:
        """
        聚合插件声明的登录认证提供方。

        :return: 插件认证入口列表
        """
        return self._plugin_projection().auth_providers()

    def get_plugin_sidebar_nav(self) -> List[Dict[str, Any]]:
        """
        聚合所有已启用 Vue 插件的侧栏导航项（get_sidebar_nav）。
        """
        return self._plugin_projection().sidebar()

    def get_plugin_dashboard_meta(self) -> List[Dict[str, str]]:
        """
        获取所有插件仪表盘元信息
        """
        return self._plugin_projection().dashboard_metadata()

    def get_plugin_dashboard(self, pid: str, key: str, user_agent: str = None) -> Optional[_SchemaPluginDashboard]:
        """
        获取插件仪表盘
        """
        return self._plugin_projection().dashboard(pid, key, user_agent)

    def get_plugin_attr(self, pid: str, attr: str) -> Any:
        """
        获取插件属性
        :param pid: 插件ID
        :param attr: 属性名
        """
        plugin = self._plugin_registry.instance(pid)
        if not plugin:
            return None
        if not hasattr(plugin, attr):
            return None
        return getattr(plugin, attr)

    def run_plugin_method(self, pid: str, method: str, *args, **kwargs) -> Any:
        """
        运行插件方法
        :param pid: 插件ID
        :param method: 方法名
        :param args: 参数
        :param kwargs: 关键字参数
        """
        plugin = self._plugin_registry.instance(pid)
        if not plugin:
            return None
        if not hasattr(plugin, method):
            return None
        return getattr(plugin, method)(*args, **kwargs)

    async def async_run_plugin_method(self, pid: str, method: str, *args, **kwargs) -> Any:
        """
        异步运行插件方法，同步实现经受控线程入口执行
        :param pid: 插件ID
        :param method: 方法名
        :param args: 参数
        :param kwargs: 关键字参数
        """
        plugin = self._plugin_registry.instance(pid)
        if not plugin:
            return None
        if not hasattr(plugin, method):
            return None
        method_func = getattr(plugin, method)
        if inspect.iscoroutinefunction(method_func):
            return await method_func(*args, **kwargs)
        return await run_in_threadpool_to_completion(method_func, *args, **kwargs)

    def get_plugin_ids(self) -> List[str]:
        """
        获取所有插件ID
        """
        return self._plugin_registry.plugin_ids()

    def get_running_plugin_ids(self) -> List[str]:
        """
        获取所有运行态插件ID
        """
        return self._plugin_registry.running_ids()

    def get_online_plugins(self, force: bool = False) -> List[_SchemaPlugin]:
        """
        获取所有在线插件信息
        """
        return self._plugin_catalog_view.online(force)

    def get_local_plugins(self) -> List[_SchemaPlugin]:
        """
        获取所有本地已下载的插件信息
        """
        return self._plugin_catalog_view.local()

    def get_installed_plugins(self) -> List[_SchemaPlugin]:
        """按安装清单返回插件，即使运行时尚未加载也保留卡片。"""
        return self._plugin_catalog_view.installed()

    def get_local_plugin_version(self, pid: str) -> Optional[str]:
        """
        获取指定已安装插件的本地版本，不触发全部插件的状态、页面和权限计算。

        插件类由运行期动态加载，旧插件可能未声明版本属性，因此缺失时返回 None。
        """
        return self._plugin_catalog_view.local_version(pid)

    def get_local_repo_plugins(self) -> List[_SchemaPlugin]:
        """
        获取本地插件仓库目录中的插件信息
        """
        return self._plugin_catalog_view.local_repository()

    def is_plugin_exists(self, pid: str, version: str = None) -> bool:
        """
        判断插件是否存在，并满足版本要求(有传入version时)
        :param pid: 插件ID
        :param version: 插件版本
        """
        return self._plugin_catalog_view.exists(pid, version)

    def get_plugins_from_market(self, market: str,
                                package_version: Optional[str] = None,
                                force: bool = False) -> Optional[List[_SchemaPlugin]]:
        """
        从指定的市场获取插件信息
        :param market: 市场的 URL 或标识
        :param package_version: 首选插件版本 (如 "v2", "v3")，如果不指定则获取 v1 版本
        :param force: 是否强制刷新（忽略缓存）
        :return: 返回插件的列表，若获取失败返回 []
        """
        return self._plugin_catalog_view.get_from_market(
            market,
            package_version,
            force,
        )

    def process_plugins_list(self, higher_version_plugins: List[_SchemaPlugin],
                             base_version_plugins: List[_SchemaPlugin]) -> List[_SchemaPlugin]:
        """
        处理插件列表：合并、去重、排序、保留最高版本
        :param higher_version_plugins: 高版本插件列表
        :param base_version_plugins: 基础版本插件列表
        :return: 处理后的插件列表
        """
        return self._plugin_catalog_view.merge(
            higher_version_plugins,
            base_version_plugins,
        )

    def _process_plugin_info(self, pid: str, plugin_info: dict, market: str,
                             installed_apps: List[str], add_time: int,
                             package_version: Optional[str] = None) -> Optional[_SchemaPlugin]:
        """
        处理单个插件信息，创建 schemas.Plugin 对象
        :param pid: 插件ID
        :param plugin_info: 插件信息字典
        :param market: 市场URL
        :param installed_apps: 已安装插件列表
        :param add_time: 添加顺序
        :param package_version: 包版本
        :return: 创建的插件对象，如果验证失败返回None
        """
        return self._plugin_metadata.map(
            plugin_id=pid,
            plugin_info=plugin_info,
            market=market,
            installed_plugins=installed_apps,
            add_time=add_time,
            package_version=package_version,
        )

    @staticmethod
    def _normalize_plugin_label(labels: Any) -> Optional[str]:
        """
        规整插件市场标签字段，兼容旧字符串和新列表格式。

        :param labels: 插件市场 package 中的 labels 字段
        :return: 用空格拼接后的标签字符串，无法识别或为空时返回 None
        """
        return PluginMetadataMapper.normalize_label(labels)

    async def async_get_online_plugins(
            self,
            force: bool = False,
            progress_callback: Optional[Callable[..., None]] = None,
    ) -> List[_SchemaPlugin]:
        """
        异步获取所有在线插件信息
        :param force: 是否强制刷新（忽略缓存）
        :param progress_callback: 定时服务进度更新回调
        """
        return await self._plugin_catalog_view.async_online(
            force,
            progress_callback,
        )

    async def async_get_plugins_from_market(self, market: str,
                                            package_version: Optional[str] = None,
                                            force: bool = False) -> Optional[List[_SchemaPlugin]]:
        """
        异步从指定的市场获取插件信息
        :param market: 市场的 URL 或标识
        :param package_version: 首选插件版本 (如 "v2", "v3")，如果不指定则获取 v1 版本
        :param force: 是否强制刷新（忽略缓存）
        :return: 返回插件的列表，若获取失败返回 []
        """
        return await self._plugin_catalog_view.async_get_from_market(
            market,
            package_version,
            force,
        )

    def __set_and_check_auth_level(
        self,
        plugin: Union[_SchemaPlugin, Type[Any]],
        source: Optional[Union[dict, Type[Any]]] = None,
    ) -> bool:
        """
        设置并检查插件的认证级别
        :param plugin: 插件对象或包含 auth_level 属性的对象
        :param source: 可选的字典对象或类对象，可能包含 "level" 或 "auth_level" 键
        :return: 如果插件的认证级别有效且当前环境的认证级别满足要求，返回 True，否则返回 False
        """
        return self._plugin_access.check(plugin, source)

    @staticmethod
    def __get_plugin_private_key(plugin_id: str) -> Optional[str]:
        """
        根据插件标识获取对应的私钥
        :param plugin_id: 插件标识
        :return: 对应的插件私钥，如果未找到则返回 None
        """
        return PluginAccessPolicy.private_key(plugin_id)

    def clone_plugin(self, plugin_id: str, suffix: str, name: str, description: str,
                     version: str = None, icon: str = None) -> Tuple[bool, str]:
        """
        创建插件分身
        :param plugin_id: 原插件ID
        :param suffix: 分身后缀
        :param name: 分身名称
        :param description: 分身描述
        :param version: 自定义版本号
        :param icon: 自定义图标URL
        :return: (是否成功, 错误信息)
        """
        try:
            with self.mutation("创建插件分身"):
                return self._plugin_clone.clone(
                    plugin_id=plugin_id,
                    suffix=suffix,
                    name=name,
                    description=description,
                    version=version,
                    icon=icon,
                )
        except PluginMutationRejectedError as error:
            logger.warning(str(error))
            return False, str(error)

    def _modify_plugin_files(self, plugin_dir: Path, original_id: str, suffix: str,
                             name: str, description: str, version: str = None,
                             icon: str = None) -> Tuple[bool, str]:
        """
        兼容旧内部调用，将分身文件改写委托给包适配器。
        :param plugin_dir: 插件目录
        :param original_id: 原插件ID
        :param suffix: 分身后缀
        :param name: 分身名称
        :param description: 分身描述
        :param version: 自定义版本号
        :param icon: 自定义图标URL
        :return: (是否成功, 错误信息)
        """
        original_plugin_class = self._plugins.get(original_id)
        if not original_plugin_class:
            return False, f"无法获取原插件类 {original_id}"
        return get_plugin_system().package._modify_plugin_files(
            plugin_dir=plugin_dir,
            original_class_name=original_plugin_class.__name__,
            suffix=suffix,
            name=name,
            description=description,
            version=version,
            icon=icon,
        )

    @staticmethod
    def _modify_python_file(file_path: Path, original_class_name: str,
                            clone_class_name: str, name: str, description: str,
                            version: str = None, icon: str = None) -> Tuple[bool, str]:
        """
        兼容旧内部调用，将 Python 文件改写委托给包适配器。
        """
        return get_plugin_system().package._modify_python_file(
            file_path=file_path,
            original_class_name=original_class_name,
            clone_class_name=clone_class_name,
            name=name,
            description=description,
            version=version,
            icon=icon,
        )

    def _modify_federation_files(self, dist_dir: Path, original_class_name: str,
                                 clone_class_name: str) -> Tuple[bool, str]:
        """
        兼容旧内部调用，将联邦文件改写委托给包适配器。
        """
        return get_plugin_system().package._modify_federation_files(
            dist_dir=dist_dir,
            original_class_name=original_class_name,
            clone_class_name=clone_class_name,
        )

    @staticmethod
    def _rename_federation_assets(dist_dir: Path, original_class_name: str, clone_class_name: str):
        """
        兼容旧内部调用，将资源重命名委托给包适配器。
        """
        get_plugin_system().package._rename_federation_assets(
            dist_dir,
            original_class_name,
            clone_class_name,
        )
