mofox-plugin-dev-toolkit 0.3.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mofox_plugin_dev_toolkit-0.3.3.dist-info/METADATA +730 -0
- mofox_plugin_dev_toolkit-0.3.3.dist-info/RECORD +46 -0
- mofox_plugin_dev_toolkit-0.3.3.dist-info/WHEEL +5 -0
- mofox_plugin_dev_toolkit-0.3.3.dist-info/entry_points.txt +2 -0
- mofox_plugin_dev_toolkit-0.3.3.dist-info/licenses/LICENSE +674 -0
- mofox_plugin_dev_toolkit-0.3.3.dist-info/top_level.txt +1 -0
- mpdt/__init__.py +15 -0
- mpdt/__main__.py +8 -0
- mpdt/cli.py +316 -0
- mpdt/commands/__init__.py +9 -0
- mpdt/commands/check.py +498 -0
- mpdt/commands/dev.py +318 -0
- mpdt/commands/generate.py +448 -0
- mpdt/commands/init.py +686 -0
- mpdt/dev/bridge_plugin/__init__.py +17 -0
- mpdt/dev/bridge_plugin/cleanup_handler.py +65 -0
- mpdt/dev/bridge_plugin/dev_config.py +24 -0
- mpdt/dev/bridge_plugin/file_watcher.py +169 -0
- mpdt/dev/bridge_plugin/plugin.py +219 -0
- mpdt/templates/__init__.py +165 -0
- mpdt/templates/action_template.py +102 -0
- mpdt/templates/adapter_template.py +129 -0
- mpdt/templates/chatter_template.py +103 -0
- mpdt/templates/event_template.py +116 -0
- mpdt/templates/plus_command_template.py +150 -0
- mpdt/templates/prompt_template.py +92 -0
- mpdt/templates/router_template.py +175 -0
- mpdt/templates/tool_template.py +98 -0
- mpdt/utils/__init__.py +10 -0
- mpdt/utils/code_parser.py +401 -0
- mpdt/utils/color_printer.py +99 -0
- mpdt/utils/config_loader.py +171 -0
- mpdt/utils/config_manager.py +297 -0
- mpdt/utils/file_ops.py +207 -0
- mpdt/utils/license_generator.py +980 -0
- mpdt/utils/plugin_parser.py +195 -0
- mpdt/utils/template_engine.py +112 -0
- mpdt/validators/__init__.py +26 -0
- mpdt/validators/auto_fix_validator.py +990 -0
- mpdt/validators/base.py +129 -0
- mpdt/validators/component_validator.py +842 -0
- mpdt/validators/config_validator.py +119 -0
- mpdt/validators/metadata_validator.py +107 -0
- mpdt/validators/structure_validator.py +72 -0
- mpdt/validators/style_validator.py +117 -0
- mpdt/validators/type_validator.py +206 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DevBridge 清理事件处理器
|
|
3
|
+
在程序停止时清理 DevBridge 插件和目标插件
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import shutil
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import ClassVar
|
|
9
|
+
|
|
10
|
+
from src.common.logger import get_logger
|
|
11
|
+
from src.plugin_system.base import BaseEventHandler
|
|
12
|
+
from src.plugin_system.base.component_types import EventType
|
|
13
|
+
|
|
14
|
+
from .dev_config import TARGET_PLUGIN_NAME, TARGET_PLUGIN_PATH
|
|
15
|
+
|
|
16
|
+
logger = get_logger("dev_bridge_cleanup")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CleanupHandler(BaseEventHandler):
|
|
20
|
+
"""清理事件处理器 - 在程序停止时清理插件文件"""
|
|
21
|
+
|
|
22
|
+
handler_name = "dev_bridge_cleanup"
|
|
23
|
+
handler_description = "DevBridge 清理处理器"
|
|
24
|
+
weight = -100 # 负权重,确保最后执行
|
|
25
|
+
init_subscribe: ClassVar[list[EventType | str]] = [EventType.ON_STOP]
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
super().__init__()
|
|
29
|
+
self._target_plugin_name = TARGET_PLUGIN_NAME
|
|
30
|
+
self._target_plugin_path = TARGET_PLUGIN_PATH
|
|
31
|
+
|
|
32
|
+
async def execute(self, kwargs: dict | None) -> tuple[bool, bool, str | None]:
|
|
33
|
+
"""程序停止时执行清理(同步删除)"""
|
|
34
|
+
logger.info("🛑 收到停止事件,准备清理 DevBridge...")
|
|
35
|
+
|
|
36
|
+
self._delete_plugins()
|
|
37
|
+
|
|
38
|
+
return True, True, None
|
|
39
|
+
|
|
40
|
+
def _delete_plugins(self):
|
|
41
|
+
"""同步删除插件目录"""
|
|
42
|
+
plugin_dir = Path(__file__).parent
|
|
43
|
+
# 目标插件在 plugins 目录中的路径
|
|
44
|
+
plugins_dir = plugin_dir.parent # plugins 目录
|
|
45
|
+
source_path = Path(self._target_plugin_path)
|
|
46
|
+
target_plugin_dir = plugins_dir / self._target_plugin_name if self._target_plugin_name else None
|
|
47
|
+
|
|
48
|
+
# 判断目标插件是否本来就在 plugins 目录下
|
|
49
|
+
is_in_plugins_dir = source_path.parent.resolve() == plugins_dir.resolve()
|
|
50
|
+
|
|
51
|
+
# 删除目标开发插件(仅当它是复制进来的时候)
|
|
52
|
+
if not is_in_plugins_dir and target_plugin_dir and target_plugin_dir.exists():
|
|
53
|
+
try:
|
|
54
|
+
shutil.rmtree(target_plugin_dir)
|
|
55
|
+
logger.info(f"🧹 目标插件已清理: {target_plugin_dir}")
|
|
56
|
+
except Exception as e:
|
|
57
|
+
logger.warning(f"⚠️ 清理目标插件失败: {e}")
|
|
58
|
+
|
|
59
|
+
# 删除 DevBridge 自己
|
|
60
|
+
try:
|
|
61
|
+
if plugin_dir.exists():
|
|
62
|
+
shutil.rmtree(plugin_dir)
|
|
63
|
+
print(f"[DevBridge] 🧹 DevBridge 插件已清理: {plugin_dir}")
|
|
64
|
+
except Exception as e:
|
|
65
|
+
print(f"[DevBridge] ⚠️ 清理 DevBridge 插件失败: {e}")
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
开发模式配置文件
|
|
3
|
+
此文件在 mpdt dev 注入时会被修改,用于传递开发插件的配置信息
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# ==================== 开发目标插件配置 ====================
|
|
7
|
+
# 以下常量会在 mpdt dev 注入时被自动修改
|
|
8
|
+
|
|
9
|
+
# 目标插件的绝对路径
|
|
10
|
+
TARGET_PLUGIN_PATH: str = ""
|
|
11
|
+
|
|
12
|
+
# 目标插件名称
|
|
13
|
+
TARGET_PLUGIN_NAME: str = ""
|
|
14
|
+
|
|
15
|
+
# 是否启用文件监控
|
|
16
|
+
ENABLE_FILE_WATCHER: bool = True
|
|
17
|
+
|
|
18
|
+
# 文件监控防抖延迟(秒)
|
|
19
|
+
DEBOUNCE_DELAY: float = 0.3
|
|
20
|
+
|
|
21
|
+
# ==================== 其他配置 ====================
|
|
22
|
+
|
|
23
|
+
# 发现服务器端口
|
|
24
|
+
DISCOVERY_PORT: int = 12318
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""
|
|
2
|
+
文件监控器模块
|
|
3
|
+
负责监控目标插件的文件变化并触发重载
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import time
|
|
8
|
+
from collections.abc import Callable, Coroutine
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
|
|
12
|
+
from watchdog.events import FileSystemEvent, FileSystemEventHandler
|
|
13
|
+
from watchdog.observers import Observer
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
from src.common.logger import get_logger
|
|
20
|
+
logger = get_logger("dev_watcher")
|
|
21
|
+
except ImportError:
|
|
22
|
+
import logging
|
|
23
|
+
logger = logging.getLogger("dev_watcher")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PluginFileHandler(FileSystemEventHandler):
|
|
27
|
+
"""插件文件变化处理器"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
plugin_path: Path,
|
|
32
|
+
callback: Callable[[str], Coroutine[Any, Any, None] | None],
|
|
33
|
+
debounce_delay: float = 0.3
|
|
34
|
+
):
|
|
35
|
+
self.plugin_path = plugin_path
|
|
36
|
+
self.callback = callback
|
|
37
|
+
self.debounce_delay = debounce_delay
|
|
38
|
+
self.last_modified: dict[str, float] = {}
|
|
39
|
+
self._loop: asyncio.AbstractEventLoop | None = None
|
|
40
|
+
|
|
41
|
+
def set_event_loop(self, loop: asyncio.AbstractEventLoop):
|
|
42
|
+
"""设置事件循环"""
|
|
43
|
+
self._loop = loop
|
|
44
|
+
|
|
45
|
+
def on_modified(self, event: FileSystemEvent):
|
|
46
|
+
if event.is_directory:
|
|
47
|
+
return
|
|
48
|
+
src_path = event.src_path
|
|
49
|
+
if isinstance(src_path, bytes):
|
|
50
|
+
src_path = src_path.decode()
|
|
51
|
+
self._handle_change(str(src_path))
|
|
52
|
+
|
|
53
|
+
def on_created(self, event: FileSystemEvent):
|
|
54
|
+
if event.is_directory:
|
|
55
|
+
return
|
|
56
|
+
src_path = event.src_path
|
|
57
|
+
if isinstance(src_path, bytes):
|
|
58
|
+
src_path = src_path.decode()
|
|
59
|
+
self._handle_change(str(src_path))
|
|
60
|
+
|
|
61
|
+
def _handle_change(self, src_path: str):
|
|
62
|
+
"""处理文件变化"""
|
|
63
|
+
# 只监控 Python 文件
|
|
64
|
+
if not src_path.endswith(".py"):
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
# 防抖处理
|
|
68
|
+
now = time.time()
|
|
69
|
+
if src_path in self.last_modified:
|
|
70
|
+
if now - self.last_modified[src_path] < self.debounce_delay:
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
self.last_modified[src_path] = now
|
|
74
|
+
|
|
75
|
+
# 获取相对路径
|
|
76
|
+
try:
|
|
77
|
+
rel_path = Path(src_path).relative_to(self.plugin_path)
|
|
78
|
+
except ValueError:
|
|
79
|
+
rel_path = Path(src_path).name
|
|
80
|
+
|
|
81
|
+
logger.info(f"检测到文件变化: {rel_path}")
|
|
82
|
+
|
|
83
|
+
# 在事件循环中调度回调
|
|
84
|
+
if self._loop and self.callback:
|
|
85
|
+
asyncio.run_coroutine_threadsafe(
|
|
86
|
+
self._async_callback(str(rel_path)),
|
|
87
|
+
self._loop
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
async def _async_callback(self, rel_path: str):
|
|
91
|
+
"""异步回调包装"""
|
|
92
|
+
try:
|
|
93
|
+
result = self.callback(rel_path)
|
|
94
|
+
if asyncio.iscoroutine(result):
|
|
95
|
+
await result
|
|
96
|
+
except Exception as e:
|
|
97
|
+
logger.error(f"文件变化回调执行失败: {e}")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class FileWatcher:
|
|
101
|
+
"""文件监控器"""
|
|
102
|
+
|
|
103
|
+
def __init__(
|
|
104
|
+
self,
|
|
105
|
+
plugin_path: str | Path,
|
|
106
|
+
on_change_callback: Callable[[str], Coroutine[Any, Any, None] | None],
|
|
107
|
+
debounce_delay: float = 0.3
|
|
108
|
+
):
|
|
109
|
+
self.plugin_path = Path(plugin_path)
|
|
110
|
+
self.on_change_callback = on_change_callback
|
|
111
|
+
self.debounce_delay = debounce_delay
|
|
112
|
+
self._observer: Any = None
|
|
113
|
+
self._handler: PluginFileHandler | None = None
|
|
114
|
+
self._running = False
|
|
115
|
+
|
|
116
|
+
def start(self, loop: asyncio.AbstractEventLoop | None = None):
|
|
117
|
+
"""启动文件监控"""
|
|
118
|
+
if self._running:
|
|
119
|
+
logger.warning("文件监控器已在运行")
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
if not self.plugin_path.exists():
|
|
123
|
+
logger.error(f"插件路径不存在: {self.plugin_path}")
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
# 创建处理器
|
|
127
|
+
self._handler = PluginFileHandler(
|
|
128
|
+
self.plugin_path,
|
|
129
|
+
self.on_change_callback,
|
|
130
|
+
self.debounce_delay
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# 设置事件循环
|
|
134
|
+
if loop:
|
|
135
|
+
self._handler.set_event_loop(loop)
|
|
136
|
+
else:
|
|
137
|
+
try:
|
|
138
|
+
self._handler.set_event_loop(asyncio.get_running_loop())
|
|
139
|
+
except RuntimeError:
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
# 创建并启动观察者
|
|
143
|
+
self._observer = Observer()
|
|
144
|
+
self._observer.schedule(
|
|
145
|
+
self._handler,
|
|
146
|
+
str(self.plugin_path),
|
|
147
|
+
recursive=True
|
|
148
|
+
)
|
|
149
|
+
self._observer.start()
|
|
150
|
+
self._running = True
|
|
151
|
+
|
|
152
|
+
logger.info(f"文件监控已启动: {self.plugin_path}")
|
|
153
|
+
|
|
154
|
+
def stop(self):
|
|
155
|
+
"""停止文件监控"""
|
|
156
|
+
if not self._running:
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
if self._observer:
|
|
160
|
+
self._observer.stop()
|
|
161
|
+
self._observer.join(timeout=2)
|
|
162
|
+
self._observer = None
|
|
163
|
+
|
|
164
|
+
self._running = False
|
|
165
|
+
logger.info("文件监控已停止")
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def is_running(self) -> bool:
|
|
169
|
+
return self._running
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DevBridge 插件 - 完整的开发模式插件
|
|
3
|
+
负责文件监控、插件重载等所有开发操作
|
|
4
|
+
配置通过 dev_config.py 中的常量传递(mpdt dev 注入时动态修改)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import ClassVar
|
|
10
|
+
|
|
11
|
+
from src.common.logger import get_logger
|
|
12
|
+
from src.plugin_system import (
|
|
13
|
+
BasePlugin,
|
|
14
|
+
register_plugin,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# 导入配置(由 mpdt dev 注入时修改)
|
|
18
|
+
from .dev_config import (
|
|
19
|
+
DEBOUNCE_DELAY,
|
|
20
|
+
ENABLE_FILE_WATCHER,
|
|
21
|
+
TARGET_PLUGIN_NAME,
|
|
22
|
+
TARGET_PLUGIN_PATH,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
logger = get_logger("dev_bridge")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@register_plugin
|
|
29
|
+
class DevBridgePlugin(BasePlugin):
|
|
30
|
+
"""开发模式桥接插件
|
|
31
|
+
|
|
32
|
+
这是一个完整的开发模式插件,负责:
|
|
33
|
+
1. 监控目标插件的文件变化
|
|
34
|
+
2. 自动重载目标插件
|
|
35
|
+
|
|
36
|
+
配置通过 dev_config.py 传递,mpdt dev 在注入时会修改这些常量。
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
plugin_name = "dev_bridge"
|
|
40
|
+
enable_plugin = True
|
|
41
|
+
config_file_name = "config.toml"
|
|
42
|
+
dependencies: ClassVar = []
|
|
43
|
+
python_dependencies: ClassVar = []
|
|
44
|
+
|
|
45
|
+
def __init__(self, *args, **kwargs):
|
|
46
|
+
super().__init__(*args, **kwargs)
|
|
47
|
+
self._file_watcher = None
|
|
48
|
+
self._target_plugin_name = TARGET_PLUGIN_NAME
|
|
49
|
+
self._target_plugin_path = TARGET_PLUGIN_PATH
|
|
50
|
+
|
|
51
|
+
def get_plugin_components(self) -> list:
|
|
52
|
+
"""注册清理事件处理器"""
|
|
53
|
+
from .cleanup_handler import CleanupHandler
|
|
54
|
+
|
|
55
|
+
return [(CleanupHandler.get_handler_info(), CleanupHandler)]
|
|
56
|
+
|
|
57
|
+
async def on_plugin_loaded(self):
|
|
58
|
+
"""插件加载完成后启动文件监控"""
|
|
59
|
+
from .file_watcher import FileWatcher
|
|
60
|
+
|
|
61
|
+
logger.info("=" * 60)
|
|
62
|
+
logger.info("🚀 DevBridge 开发模式插件已加载")
|
|
63
|
+
logger.info(f"📦 目标插件: {self._target_plugin_name}")
|
|
64
|
+
logger.info(f"📂 目标路径: {self._target_plugin_path}")
|
|
65
|
+
logger.info("=" * 60)
|
|
66
|
+
|
|
67
|
+
# 检查目标插件是否成功加载
|
|
68
|
+
await self._check_target_plugin_loaded()
|
|
69
|
+
|
|
70
|
+
# 启动文件监控
|
|
71
|
+
if ENABLE_FILE_WATCHER and self._target_plugin_path:
|
|
72
|
+
plugin_path = Path(self._target_plugin_path)
|
|
73
|
+
if plugin_path.exists():
|
|
74
|
+
self._file_watcher = FileWatcher(plugin_path, self._on_file_changed, DEBOUNCE_DELAY)
|
|
75
|
+
# 获取当前事件循环并启动监控
|
|
76
|
+
try:
|
|
77
|
+
loop = asyncio.get_running_loop()
|
|
78
|
+
self._file_watcher.start(loop)
|
|
79
|
+
logger.info("👀 文件监控已启动")
|
|
80
|
+
logger.info("📝 修改 Python 文件将自动重载插件")
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logger.error(f"启动文件监控失败: {e}")
|
|
83
|
+
else:
|
|
84
|
+
logger.warning(f"目标插件路径不存在: {plugin_path}")
|
|
85
|
+
else:
|
|
86
|
+
logger.info("文件监控已禁用或未配置目标路径")
|
|
87
|
+
|
|
88
|
+
async def _check_target_plugin_loaded(self):
|
|
89
|
+
"""检查目标插件是否成功加载,未加载则报错提示"""
|
|
90
|
+
if not self._target_plugin_name:
|
|
91
|
+
logger.error("❌ 未配置目标插件名称")
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
from src.plugin_system.apis import plugin_manage_api
|
|
96
|
+
|
|
97
|
+
is_loaded = plugin_manage_api.is_plugin_loaded(self._target_plugin_name)
|
|
98
|
+
is_enabled = plugin_manage_api.is_plugin_enabled(self._target_plugin_name)
|
|
99
|
+
|
|
100
|
+
if not is_loaded:
|
|
101
|
+
logger.error("=" * 60)
|
|
102
|
+
logger.error(f"❌ 目标插件 {self._target_plugin_name} 未加载!")
|
|
103
|
+
logger.error("")
|
|
104
|
+
if not is_enabled:
|
|
105
|
+
logger.error("📋 原因: 插件已被禁用")
|
|
106
|
+
logger.error("")
|
|
107
|
+
logger.error("🔧 解决方案:")
|
|
108
|
+
logger.error(" 1. 检查插件的 config.toml 中 [plugin] enabled = true")
|
|
109
|
+
logger.error(" 2. 或在 plugin.py 中设置 enable_plugin = True")
|
|
110
|
+
logger.error(" 3. 或直接删除 enable_plugin 行(默认启用)")
|
|
111
|
+
else:
|
|
112
|
+
logger.error("📋 原因: 插件加载失败,请检查插件代码是否有错误")
|
|
113
|
+
logger.error("=" * 60)
|
|
114
|
+
else:
|
|
115
|
+
logger.info(f"✅ 目标插件 {self._target_plugin_name} 已成功加载")
|
|
116
|
+
|
|
117
|
+
except ValueError:
|
|
118
|
+
logger.error(f"❌ 目标插件 {self._target_plugin_name} 未注册")
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logger.error(f"❌ 检查目标插件状态时出错: {e}")
|
|
121
|
+
|
|
122
|
+
async def _on_file_changed(self, rel_path: str):
|
|
123
|
+
"""文件变化回调 - 同步文件并重载目标插件"""
|
|
124
|
+
if not self._target_plugin_name:
|
|
125
|
+
logger.warning("未配置目标插件名称,跳过重载")
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
logger.info(f"📝 检测到文件变化: {rel_path}")
|
|
129
|
+
|
|
130
|
+
# 先同步文件到 plugins 目录
|
|
131
|
+
try:
|
|
132
|
+
self._sync_plugin_files()
|
|
133
|
+
logger.info("📦 文件已同步到 plugins 目录")
|
|
134
|
+
except Exception as e:
|
|
135
|
+
logger.error(f"❌ 同步文件失败: {e}")
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
from src.plugin_system.apis import plugin_manage_api
|
|
140
|
+
|
|
141
|
+
plugin_name = self._target_plugin_name
|
|
142
|
+
is_loaded = plugin_manage_api.is_plugin_loaded(plugin_name)
|
|
143
|
+
is_enabled = plugin_manage_api.is_plugin_enabled(plugin_name)
|
|
144
|
+
|
|
145
|
+
if is_loaded:
|
|
146
|
+
# 插件已加载,检查是否被禁用
|
|
147
|
+
if not is_enabled:
|
|
148
|
+
logger.info(f"🔓 插件 {plugin_name} 已禁用,正在启用...")
|
|
149
|
+
await plugin_manage_api.enable_plugin(plugin_name)
|
|
150
|
+
|
|
151
|
+
# 重载插件
|
|
152
|
+
logger.info(f"🔄 正在重载插件: {plugin_name}...")
|
|
153
|
+
success = await plugin_manage_api.reload_plugin(plugin_name)
|
|
154
|
+
if success:
|
|
155
|
+
logger.info(f"✅ 插件 {plugin_name} 重载成功")
|
|
156
|
+
else:
|
|
157
|
+
logger.error(f"❌ 插件 {plugin_name} 重载失败")
|
|
158
|
+
else:
|
|
159
|
+
# 插件未加载,使用 enable_plugin 来加载并启用
|
|
160
|
+
# enable_plugin 会同时处理加载和启用,即使插件之前被禁用
|
|
161
|
+
logger.info(f"📦 插件 {plugin_name} 未加载,正在启用并加载...")
|
|
162
|
+
success = await plugin_manage_api.enable_plugin(plugin_name)
|
|
163
|
+
if success:
|
|
164
|
+
logger.info(f"✅ 插件 {plugin_name} 启用并加载成功")
|
|
165
|
+
else:
|
|
166
|
+
logger.error(f"❌ 插件 {plugin_name} 启用/加载失败")
|
|
167
|
+
|
|
168
|
+
except ValueError as e:
|
|
169
|
+
# 插件未注册,尝试扫描并加载
|
|
170
|
+
logger.warning(f"⚠️ 插件未注册: {e}")
|
|
171
|
+
logger.info("🔍 正在扫描插件目录...")
|
|
172
|
+
try:
|
|
173
|
+
from src.plugin_system.apis import plugin_manage_api
|
|
174
|
+
|
|
175
|
+
plugin_manage_api.rescan_and_register_plugins(load_after_register=True)
|
|
176
|
+
if plugin_manage_api.is_plugin_loaded(self._target_plugin_name):
|
|
177
|
+
logger.info(f"✅ 插件 {self._target_plugin_name} 扫描并加载成功")
|
|
178
|
+
else:
|
|
179
|
+
logger.error(f"❌ 插件 {self._target_plugin_name} 扫描后仍未加载")
|
|
180
|
+
except Exception as scan_e:
|
|
181
|
+
logger.error(f"❌ 扫描插件目录失败: {scan_e}")
|
|
182
|
+
except Exception as e:
|
|
183
|
+
logger.error(f"❌ 操作插件时出错: {e}")
|
|
184
|
+
import traceback
|
|
185
|
+
|
|
186
|
+
traceback.print_exc()
|
|
187
|
+
|
|
188
|
+
def _sync_plugin_files(self):
|
|
189
|
+
"""将源插件目录同步到 plugins 目录"""
|
|
190
|
+
import shutil
|
|
191
|
+
|
|
192
|
+
source_path = Path(self._target_plugin_path)
|
|
193
|
+
# plugins 目录是 dev_bridge 所在目录的父目录
|
|
194
|
+
plugins_dir = Path(__file__).parent.parent
|
|
195
|
+
target_path = plugins_dir / self._target_plugin_name
|
|
196
|
+
|
|
197
|
+
# 如果源插件已经在 plugins 目录下,不需要同步
|
|
198
|
+
if source_path.parent.resolve() == plugins_dir.resolve():
|
|
199
|
+
return
|
|
200
|
+
|
|
201
|
+
if not source_path.exists():
|
|
202
|
+
raise FileNotFoundError(f"源插件目录不存在: {source_path}")
|
|
203
|
+
|
|
204
|
+
# 删除旧的目标目录
|
|
205
|
+
if target_path.exists():
|
|
206
|
+
shutil.rmtree(target_path)
|
|
207
|
+
|
|
208
|
+
# 复制新文件
|
|
209
|
+
shutil.copytree(source_path, target_path)
|
|
210
|
+
|
|
211
|
+
async def on_plugin_unload(self):
|
|
212
|
+
"""插件卸载时停止文件监控"""
|
|
213
|
+
# 停止文件监控
|
|
214
|
+
if self._file_watcher:
|
|
215
|
+
self._file_watcher.stop()
|
|
216
|
+
self._file_watcher = None
|
|
217
|
+
logger.info("文件监控已停止")
|
|
218
|
+
|
|
219
|
+
logger.info("DevBridge 插件已卸载")
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
组件模板索引
|
|
3
|
+
|
|
4
|
+
此模块导出所有组件模板的获取函数。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
|
|
9
|
+
from mpdt.templates.action_template import get_action_template
|
|
10
|
+
from mpdt.templates.adapter_template import get_adapter_template
|
|
11
|
+
from mpdt.templates.chatter_template import get_chatter_template
|
|
12
|
+
from mpdt.templates.event_template import get_event_handler_template
|
|
13
|
+
from mpdt.templates.plus_command_template import get_plus_command_template
|
|
14
|
+
from mpdt.templates.prompt_template import get_prompt_template
|
|
15
|
+
from mpdt.templates.router_template import get_router_template
|
|
16
|
+
from mpdt.templates.tool_template import get_tool_template
|
|
17
|
+
|
|
18
|
+
# 导出所有模板获取函数
|
|
19
|
+
__all__ = [
|
|
20
|
+
"get_action_template",
|
|
21
|
+
"get_tool_template",
|
|
22
|
+
"get_event_handler_template",
|
|
23
|
+
"get_adapter_template",
|
|
24
|
+
"get_prompt_template",
|
|
25
|
+
"get_plus_command_template",
|
|
26
|
+
"get_chatter_template",
|
|
27
|
+
"get_router_template",
|
|
28
|
+
"get_component_template",
|
|
29
|
+
"prepare_component_context",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_component_template(component_type: str) -> str:
|
|
34
|
+
"""
|
|
35
|
+
根据组件类型获取对应的模板
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
component_type: 组件类型 (action, tool, event, adapter, prompt, plus_command, chatter, router)
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
模板字符串
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
ValueError: 不支持的组件类型
|
|
45
|
+
"""
|
|
46
|
+
template_map = {
|
|
47
|
+
"action": get_action_template,
|
|
48
|
+
"tool": get_tool_template,
|
|
49
|
+
"event": get_event_handler_template,
|
|
50
|
+
"adapter": get_adapter_template,
|
|
51
|
+
"prompt": get_prompt_template,
|
|
52
|
+
"plus_command": get_plus_command_template,
|
|
53
|
+
"chatter": get_chatter_template,
|
|
54
|
+
"router": get_router_template,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if component_type not in template_map:
|
|
58
|
+
raise ValueError(
|
|
59
|
+
f"不支持的组件类型: {component_type}. "
|
|
60
|
+
f"支持的类型: {', '.join(template_map.keys())}"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return template_map[component_type]()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def prepare_component_context(
|
|
67
|
+
component_type: str,
|
|
68
|
+
component_name: str,
|
|
69
|
+
plugin_name: str,
|
|
70
|
+
author: str = "",
|
|
71
|
+
description: str = "",
|
|
72
|
+
is_async: bool = False,
|
|
73
|
+
) -> dict[str, str]:
|
|
74
|
+
"""
|
|
75
|
+
准备组件模板上下文
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
component_type: 组件类型 (action, tool, event, adapter, prompt, plus_command,router,chatter)
|
|
79
|
+
component_name: 组件名称 (snake_case)
|
|
80
|
+
plugin_name: 插件名称
|
|
81
|
+
author: 作者
|
|
82
|
+
description: 描述
|
|
83
|
+
is_async: 是否异步
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
模板上下文字典
|
|
87
|
+
"""
|
|
88
|
+
from mpdt.utils.file_ops import to_pascal_case
|
|
89
|
+
|
|
90
|
+
# 转换为 PascalCase 并添加类型后缀
|
|
91
|
+
class_name = to_pascal_case(component_name)
|
|
92
|
+
|
|
93
|
+
# 根据组件类型添加合适的后缀
|
|
94
|
+
suffix_map = {
|
|
95
|
+
"action": "Action",
|
|
96
|
+
"tool": "Tool",
|
|
97
|
+
"event": "EventHandler",
|
|
98
|
+
"adapter": "Adapter",
|
|
99
|
+
"prompt": "Prompt",
|
|
100
|
+
"plus_command": "PlusCommand",
|
|
101
|
+
"chatter": "Chatter",
|
|
102
|
+
"router": "Router",
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
suffix = suffix_map.get(component_type, "")
|
|
106
|
+
if suffix and not class_name.endswith(suffix):
|
|
107
|
+
class_name = f"{class_name}{suffix}"
|
|
108
|
+
|
|
109
|
+
date = datetime.now().strftime("%Y-%m-%d")
|
|
110
|
+
|
|
111
|
+
# 基础上下文
|
|
112
|
+
context = {
|
|
113
|
+
"component_name": component_name,
|
|
114
|
+
"class_name": class_name,
|
|
115
|
+
"plugin_name": plugin_name,
|
|
116
|
+
"author": author,
|
|
117
|
+
"description": description or f"{class_name} 组件",
|
|
118
|
+
"date": date,
|
|
119
|
+
"async_keyword": "async " if is_async else "",
|
|
120
|
+
"await_keyword": "await " if is_async else "",
|
|
121
|
+
"component_type": component_type + "s", # actions, tools, etc.
|
|
122
|
+
"module_name": component_name,
|
|
123
|
+
"method_name": _get_method_name(component_type),
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# 特定组件类型的额外字段
|
|
127
|
+
if component_type == "plus_command":
|
|
128
|
+
context["command_name"] = component_name
|
|
129
|
+
elif component_type == "tool":
|
|
130
|
+
context["tool_name"] = component_name
|
|
131
|
+
elif component_type == "event":
|
|
132
|
+
context["event_type"] = component_name.replace("_handler", "").replace("_event", "")
|
|
133
|
+
elif component_type == "adapter":
|
|
134
|
+
context["adapter_name"] = component_name
|
|
135
|
+
elif component_type == "prompt":
|
|
136
|
+
context["prompt_name"] = component_name
|
|
137
|
+
elif component_type == "chatter":
|
|
138
|
+
context["chatter_name"] = component_name
|
|
139
|
+
elif component_type == "router":
|
|
140
|
+
context["router_name"] = component_name
|
|
141
|
+
|
|
142
|
+
return context
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _get_method_name(component_type: str) -> str:
|
|
146
|
+
"""
|
|
147
|
+
根据组件类型获取主要方法名
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
component_type: 组件类型
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
方法名
|
|
154
|
+
"""
|
|
155
|
+
method_map = {
|
|
156
|
+
"action": "execute",
|
|
157
|
+
"plus_command": "execute",
|
|
158
|
+
"tool": "run",
|
|
159
|
+
"event": "handle",
|
|
160
|
+
"adapter": "connect",
|
|
161
|
+
"prompt": "build",
|
|
162
|
+
"chatter": "chat",
|
|
163
|
+
"router": "route",
|
|
164
|
+
}
|
|
165
|
+
return method_map.get(component_type, "execute")
|