重构事件总线和插件运行时,优化消息处理逻辑,新增 IPC 传输字典转换功能,改进组件管理协议

This commit is contained in:
DrSmoothl
2026-03-14 01:39:59 +08:00
parent 84212e8e95
commit 2c330e3902
7 changed files with 197 additions and 56 deletions

View File

@@ -1,12 +1,33 @@
from typing import Any, Dict, List, Optional
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Protocol
from src.common.logger import get_logger
logger = get_logger("plugin_runtime.integration")
if TYPE_CHECKING:
from src.plugin_runtime.host.component_registry import RegisteredComponent
from src.plugin_runtime.host.supervisor import PluginSupervisor
class _RuntimeComponentManagerProtocol(Protocol):
@property
def supervisors(self) -> List["PluginSupervisor"]: ...
def _get_supervisor_for_plugin(self, plugin_id: str) -> Optional["PluginSupervisor"]: ...
def _resolve_component_toggle_target(
self, name: str, component_type: str
) -> tuple[Optional["RegisteredComponent"], Optional[str]]: ...
def _find_duplicate_plugin_ids(self, plugin_dirs: List[str]) -> Dict[str, List[str]]: ...
def _iter_plugin_dirs(self) -> Iterable[str]: ...
class RuntimeComponentCapabilityMixin:
async def _cap_component_get_all_plugins(self, plugin_id: str, capability: str, args: Dict[str, Any]) -> Any:
async def _cap_component_get_all_plugins(
self: _RuntimeComponentManagerProtocol, plugin_id: str, capability: str, args: Dict[str, Any]
) -> Any:
result: Dict[str, Any] = {}
for sv in self.supervisors:
for pid, reg in sv._registered_plugins.items():
@@ -34,7 +55,9 @@ class RuntimeComponentCapabilityMixin:
}
return {"success": True, "plugins": result}
async def _cap_component_get_plugin_info(self, plugin_id: str, capability: str, args: Dict[str, Any]) -> Any:
async def _cap_component_get_plugin_info(
self: _RuntimeComponentManagerProtocol, plugin_id: str, capability: str, args: Dict[str, Any]
) -> Any:
plugin_name: str = args.get("plugin_name", plugin_id)
try:
sv = self._get_supervisor_for_plugin(plugin_name)
@@ -54,20 +77,26 @@ class RuntimeComponentCapabilityMixin:
}
return {"success": False, "error": f"未找到插件: {plugin_name}"}
async def _cap_component_list_loaded_plugins(self, plugin_id: str, capability: str, args: Dict[str, Any]) -> Any:
async def _cap_component_list_loaded_plugins(
self: _RuntimeComponentManagerProtocol, plugin_id: str, capability: str, args: Dict[str, Any]
) -> Any:
plugins: List[str] = []
for sv in self.supervisors:
plugins.extend(sv._registered_plugins.keys())
return {"success": True, "plugins": plugins}
async def _cap_component_list_registered_plugins(self, plugin_id: str, capability: str, args: Dict[str, Any]) -> Any:
async def _cap_component_list_registered_plugins(
self: _RuntimeComponentManagerProtocol, plugin_id: str, capability: str, args: Dict[str, Any]
) -> Any:
plugins: List[str] = []
for sv in self.supervisors:
plugins.extend(sv._registered_plugins.keys())
return {"success": True, "plugins": plugins}
def _resolve_component_toggle_target(self, name: str, component_type: str) -> tuple[Optional[Any], Optional[str]]:
short_name_matches: List[Any] = []
def _resolve_component_toggle_target(
self: _RuntimeComponentManagerProtocol, name: str, component_type: str
) -> tuple[Optional["RegisteredComponent"], Optional[str]]:
short_name_matches: List["RegisteredComponent"] = []
for sv in self.supervisors:
comp = sv.component_registry.get_component(name)
if comp is not None and comp.component_type == component_type:
@@ -85,7 +114,9 @@ class RuntimeComponentCapabilityMixin:
return None, f"组件名不唯一: {name} ({component_type}),请使用完整名 plugin_id.component_name"
return None, f"未找到组件: {name} ({component_type})"
async def _cap_component_enable(self, plugin_id: str, capability: str, args: Dict[str, Any]) -> Any:
async def _cap_component_enable(
self: _RuntimeComponentManagerProtocol, plugin_id: str, capability: str, args: Dict[str, Any]
) -> Any:
name: str = args.get("name", "")
component_type: str = args.get("component_type", "")
scope: str = args.get("scope", "global")
@@ -102,7 +133,9 @@ class RuntimeComponentCapabilityMixin:
comp.enabled = True
return {"success": True}
async def _cap_component_disable(self, plugin_id: str, capability: str, args: Dict[str, Any]) -> Any:
async def _cap_component_disable(
self: _RuntimeComponentManagerProtocol, plugin_id: str, capability: str, args: Dict[str, Any]
) -> Any:
name: str = args.get("name", "")
component_type: str = args.get("component_type", "")
scope: str = args.get("scope", "global")
@@ -119,7 +152,9 @@ class RuntimeComponentCapabilityMixin:
comp.enabled = False
return {"success": True}
async def _cap_component_load_plugin(self, plugin_id: str, capability: str, args: Dict[str, Any]) -> Any:
async def _cap_component_load_plugin(
self: _RuntimeComponentManagerProtocol, plugin_id: str, capability: str, args: Dict[str, Any]
) -> Any:
plugin_name: str = args.get("plugin_name", "")
if not plugin_name:
return {"success": False, "error": "缺少必要参数 plugin_name"}
@@ -162,10 +197,14 @@ class RuntimeComponentCapabilityMixin:
return {"success": False, "error": f"未找到插件: {plugin_name}"}
async def _cap_component_unload_plugin(self, plugin_id: str, capability: str, args: Dict[str, Any]) -> Any:
async def _cap_component_unload_plugin(
self: _RuntimeComponentManagerProtocol, plugin_id: str, capability: str, args: Dict[str, Any]
) -> Any:
return {"success": False, "error": "新运行时不支持单独卸载插件,请使用 reload"}
async def _cap_component_reload_plugin(self, plugin_id: str, capability: str, args: Dict[str, Any]) -> Any:
async def _cap_component_reload_plugin(
self: _RuntimeComponentManagerProtocol, plugin_id: str, capability: str, args: Dict[str, Any]
) -> Any:
plugin_name: str = args.get("plugin_name", "")
if not plugin_name:
return {"success": False, "error": "缺少必要参数 plugin_name"}

View File

@@ -1,7 +1,8 @@
from typing import Any, Dict
from src.config.config import global_config
from src.common.logger import get_logger
from src.config.config import global_config
from src.llm_models.payload_content.tool_option import ToolCall
logger = get_logger("plugin_runtime.integration")
@@ -196,9 +197,12 @@ class RuntimeCoreCapabilityMixin:
serialized_tool_calls = None
if tool_calls:
serialized_tool_calls = [
{"id": tc.id, "function": {"name": tc.function.name, "arguments": tc.function.arguments}}
for tc in tool_calls
if hasattr(tc, "function")
{
"id": tool_call.call_id,
"function": {"name": tool_call.func_name, "arguments": tool_call.args or {}},
}
for tool_call in tool_calls
if isinstance(tool_call, ToolCall)
]
return {
"success": success,

View File

@@ -1,7 +1,7 @@
import random
from pathlib import Path
from typing import Any, Dict, List, Optional
import random
import time
from src.chat.message_receive.chat_manager import BotChatSession, chat_manager
@@ -260,8 +260,8 @@ class RuntimeDataCapabilityMixin:
return {"success": False, "error": str(e)}
@staticmethod
def _serialize_messages(messages: list) -> List[Dict[str, Any]]:
result: List[Dict[str, Any]] = []
def _serialize_messages(messages: list) -> List[Any]:
result: List[Any] = []
for msg in messages:
if hasattr(msg, "model_dump"):
result.append(msg.model_dump())

View File

@@ -90,23 +90,38 @@ class EventDispatcher:
should_continue = True
modified_message: Optional[Dict[str, Any]] = None
intercept_handlers: List[RegisteredComponent] = []
async_handlers: List[RegisteredComponent] = []
for handler in handlers:
intercept = handler.metadata.get("intercept_message", False)
if handler.metadata.get("intercept_message", False):
intercept_handlers.append(handler)
else:
async_handlers.append(handler)
for handler in intercept_handlers:
args = {
"event_type": event_type,
"message": modified_message or message,
**(extra_args or {}),
}
if intercept:
# 阻塞执行
result = await self._invoke_handler(invoke_fn, handler, args, event_type)
if result and not result.continue_processing:
should_continue = False
if result and result.modified_message:
modified_message = result.modified_message
else:
result = await self._invoke_handler(invoke_fn, handler, args, event_type)
if result and not result.continue_processing:
should_continue = False
break
if result and result.modified_message:
modified_message = result.modified_message
if should_continue:
final_message = modified_message or message
for handler in async_handlers:
async_message = final_message.copy() if isinstance(final_message, dict) else final_message
args = {
"event_type": event_type,
"message": async_message,
**(extra_args or {}),
}
# 非阻塞:保持实例级强引用,防止 task 被 GC 回收
task = asyncio.create_task(self._invoke_handler(invoke_fn, handler, args, event_type))
self._background_tasks.add(task)

View File

@@ -7,12 +7,12 @@
4. 提供统一的能力实现注册接口,使插件可以调用主程序功能
"""
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Tuple
from pathlib import Path
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Sequence, Tuple
import asyncio
import json
import os
from pathlib import Path
from src.common.logger import get_logger
from src.config.config import config_manager, global_config
@@ -219,12 +219,11 @@ class PluginRuntimeManager(
if not self._started:
return
tasks = [
if tasks := [
self.notify_plugin_config_updated(plugin_id)
for sv in self.supervisors
for plugin_id in list(sv._registered_plugins.keys())
]
if tasks:
]:
await asyncio.gather(*tasks, return_exceptions=True)
# ─── 事件桥接 ──────────────────────────────────────────────
@@ -393,7 +392,7 @@ class PluginRuntimeManager(
for supervisor in self.supervisors:
yield from getattr(supervisor, "_plugin_dirs", [])
async def _handle_plugin_file_changes(self, changes: List[FileChange]) -> None:
async def _handle_plugin_file_changes(self, changes: Sequence[FileChange]) -> None:
if not self._started or not changes:
return
@@ -405,7 +404,7 @@ class PluginRuntimeManager(
return
reload_supervisors: List[Any] = []
config_updates: Dict[str, set[str]] = {}
config_updates: Dict[int, set[str]] = {}
changed_paths = [change.path.resolve() for change in changes]
for supervisor in self.supervisors: