v0.2.2: headless mode, debug server auto-start, WS fix, watchdog hot-reload, example plugin
- --headless flag (skip TUI for SSH/systemd) - InitService auto-starts cyrene_debug_server.py - Fixed log WS URL (home.html/api/logs/ws -> api/logs/ws) - PluginService watchdog hot-reload (plugins/ dir) - example_plugin: 2 commands (echo, plugin_status) - Tests: 15/15 passing
This commit is contained in:
+130
@@ -0,0 +1,130 @@
|
|||||||
|
# SenSu 开发路线图
|
||||||
|
|
||||||
|
> 当前版本: Alpha 0.2.1
|
||||||
|
> 更新: 2026-06-10
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、已完成 (v0.2.1)
|
||||||
|
|
||||||
|
- [x] 13 服务异步框架 (init/log/tui/command/auth/internet/plugin/permission/api/shutdown/web_panel/bridge)
|
||||||
|
- [x] Textual TUI 三栏界面 + CLI 回退
|
||||||
|
- [x] 插件热加载 + 权限管理
|
||||||
|
- [x] Web 管理面板 (aiohttp :4200)
|
||||||
|
- [x] 消息桥接 (CoreBridge + PluginBridge + NetworkBridge)
|
||||||
|
- [x] 插件 SDK (`sdk/`): PluginStatus, PluginError, plugin_command 装饰器
|
||||||
|
- [x] 15 个回归测试
|
||||||
|
- [x] 安全加固: PBKDF2-SHA256, 环境变量密码, 认证降级已移除
|
||||||
|
- [x] Android 兼容: os.getloadavg(), /proc/net/dev
|
||||||
|
- [x] Apache 2.0 许可证
|
||||||
|
|
||||||
|
## 二、v0.2.2 — 打磨(短期)
|
||||||
|
|
||||||
|
### 2.1 `--headless` 模式
|
||||||
|
- **目标**: 纯后台运行,不启动 Textual TUI
|
||||||
|
- **价值**: systemd/supervisor 部署、SSH 远程管理、CI/CD
|
||||||
|
- **实现**: `main.py` 加 `--headless` 参数,跳过 TuiService 初始化
|
||||||
|
- **估时**: 1h
|
||||||
|
|
||||||
|
### 2.2 调试服务器自启动
|
||||||
|
- **目标**: 框架启动时自动拉起 `cyrene_debug_server.py`(如果存在)
|
||||||
|
- **价值**: 不需要手动 SSH 再启动
|
||||||
|
- **实现**: `InitService` 检查 `~/cyrene_debug_server.py`,后台启动
|
||||||
|
- **估时**: 0.5h
|
||||||
|
|
||||||
|
### 2.3 Web 面板日志 WebSocket 修复
|
||||||
|
- **目标**: 日志页面实时推送(当前前端拼错 URL)
|
||||||
|
- **根因**: `home.html/api/logs/ws` 应该是 `/SenSu/api/logs/ws`
|
||||||
|
- **估时**: 0.3h
|
||||||
|
|
||||||
|
### 2.4 插件热重载生效
|
||||||
|
- **目标**: 修改插件文件后自动重载(watchdog 已装未用)
|
||||||
|
- **实现**: `PluginService` 注册 watchdog observer 监听 `plugins/` 目录
|
||||||
|
- **估时**: 1h
|
||||||
|
|
||||||
|
### 2.5 test_demo 插件完善
|
||||||
|
- **目标**: 让它真正注册命令(当前 0 个命令)
|
||||||
|
- **根因**: workspace 里插件代码可能是草稿,补全 `@plugin_command` 装饰
|
||||||
|
- **估时**: 0.5h
|
||||||
|
|
||||||
|
## 三、v0.3 — 项目管理 (中期)
|
||||||
|
|
||||||
|
### 3.1 项目注册表
|
||||||
|
- **目标**: 插件可声明"我是一个项目"并申请资源
|
||||||
|
- **API**: `project.yaml` 声明 name, path, port, dependencies, entrypoint
|
||||||
|
- **实现**: `ProjectService` 管理项目生命周期(安装→配置→启动→监控→停止)
|
||||||
|
- **价值**: Cyrene TTS、Navidrome、music-tag-web 等都能挂上去
|
||||||
|
- **估时**: 4h
|
||||||
|
|
||||||
|
### 3.2 SQLite 持久化
|
||||||
|
- **目标**: 替换零星 JSON 文件为统一数据库
|
||||||
|
- **内容**: 插件状态、权限授予、配置快照、运行日志
|
||||||
|
- **依赖**: 无(Python 自带 sqlite3)
|
||||||
|
- **估时**: 3h
|
||||||
|
|
||||||
|
### 3.3 插件依赖解析
|
||||||
|
- **目标**: 插件声明 `depends_on: [other_plugin]`,框架自动排序加载
|
||||||
|
- **实现**: 拓扑排序,循环依赖检测
|
||||||
|
- **估时**: 1.5h
|
||||||
|
|
||||||
|
### 3.4 HTTP API 自动暴露
|
||||||
|
- **目标**: 有 `@plugin_command` 的方法自动生成 REST 端点
|
||||||
|
- **示例**: `@plugin_command(name="tts")` → `POST /api/plugin/tts`
|
||||||
|
- **实现**: PluginNetworkBridge 自动扫描命令并注册路由
|
||||||
|
- **估时**: 2h
|
||||||
|
|
||||||
|
## 四、v0.4 — TUI 仪表盘 (中长期)
|
||||||
|
|
||||||
|
### 4.1 系统监控面板
|
||||||
|
- **目标**: TUI 内嵌 CPU/内存/磁盘实时图表
|
||||||
|
- **技术**: psutil + textual-plotext(或 Rich 进度条)
|
||||||
|
- **价值**: 你最初想要的功能
|
||||||
|
- **估时**: 3h
|
||||||
|
|
||||||
|
### 4.2 插件实时状态面板
|
||||||
|
- **目标**: TUI 展示每个插件的状态、命令数、网络路由、内存占用
|
||||||
|
- **价值**: 框架成为真正的"万能项目管理器"
|
||||||
|
- **估时**: 3h
|
||||||
|
|
||||||
|
### 4.3 命令增强
|
||||||
|
- **目标**: 补全(Tab)、语法高亮、管道
|
||||||
|
- **实现**: Textual Input 的 suggester API
|
||||||
|
- **估时**: 2h
|
||||||
|
|
||||||
|
## 五、v0.5 — 生产就绪 (长期)
|
||||||
|
|
||||||
|
### 5.1 插件进程隔离
|
||||||
|
- **目标**: 每个插件独立子进程,崩溃不影响框架
|
||||||
|
- **通信**: multiprocessing.Queue 替代内存桥接
|
||||||
|
- **代价**: 复杂度翻倍,性能略降
|
||||||
|
- **估时**: 8h
|
||||||
|
|
||||||
|
### 5.2 插件索引仓库
|
||||||
|
- **目标**: 在线 JSON 索引,`senSu install <name>` 一键安装
|
||||||
|
- **实现**: 简单的 GitHub Pages + JSON 文件
|
||||||
|
- **估时**: 4h
|
||||||
|
|
||||||
|
### 5.3 systemd 集成
|
||||||
|
- **目标**: `senSu.service` 模板,开机自启
|
||||||
|
- **实现**: 生成 systemd unit 文件 + `install.sh`
|
||||||
|
- **估时**: 1h
|
||||||
|
|
||||||
|
### 5.4 Docker 化
|
||||||
|
- **目标**: 一键部署到任意设备
|
||||||
|
- **实现**: Alpine-based Dockerfile,<100MB
|
||||||
|
- **估时**: 2h
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 优先级排序
|
||||||
|
|
||||||
|
```
|
||||||
|
高优先级 (立即可做):
|
||||||
|
└─ v0.2.2: headless, 热重载, WS修复, 调试服务器
|
||||||
|
|
||||||
|
中优先级 (下个迭代):
|
||||||
|
└─ v0.3: 项目注册表, SQLite, 插件依赖
|
||||||
|
|
||||||
|
低优先级 (视需求):
|
||||||
|
└─ v0.4-0.5: TUI仪表盘, 进程隔离, 插件市场
|
||||||
|
```
|
||||||
@@ -5,6 +5,7 @@ import logging
|
|||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
import sys
|
||||||
import signal
|
import signal
|
||||||
|
import argparse
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -30,13 +31,14 @@ from service_manager import ServiceManager
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class SenSuFramework:
|
class CatFramework:
|
||||||
"""框架主类"""
|
"""框架主类"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.service_manager = ServiceManager()
|
self.service_manager = ServiceManager()
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
logger.debug("🐱 SenSu 框架初始化开始")
|
self.headless = headless
|
||||||
|
logger.debug("🐱 DreamSu 框架初始化开始")
|
||||||
|
|
||||||
async def initialize(self):
|
async def initialize(self):
|
||||||
"""初始化框架"""
|
"""初始化框架"""
|
||||||
@@ -80,14 +82,18 @@ class SenSuFramework:
|
|||||||
self.service_manager.register_service("shutdown", shutdown_service)
|
self.service_manager.register_service("shutdown", shutdown_service)
|
||||||
|
|
||||||
# 8. TUI服务
|
# 8. TUI服务
|
||||||
try:
|
if self.headless:
|
||||||
tui_service = TuiService(base_config, log_service, command_service)
|
logger.info("Headless 模式,跳过 TUI")
|
||||||
await tui_service.start()
|
|
||||||
self.service_manager.register_service("tui", tui_service)
|
|
||||||
logger.info("TUI服务启动成功")
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"TUI服务启动失败,使用命令行模式: {str(e)}")
|
|
||||||
self.service_manager.register_service("tui", self._create_fallback_tui())
|
self.service_manager.register_service("tui", self._create_fallback_tui())
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
tui_service = TuiService(base_config, log_service, command_service)
|
||||||
|
await tui_service.start()
|
||||||
|
self.service_manager.register_service("tui", tui_service)
|
||||||
|
logger.info("TUI服务启动成功")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"TUI服务启动失败,使用命令行模式: {str(e)}")
|
||||||
|
self.service_manager.register_service("tui", self._create_fallback_tui())
|
||||||
|
|
||||||
# 9. 权限服务
|
# 9. 权限服务
|
||||||
logger.info("> 初始化 权限服务 中...")
|
logger.info("> 初始化 权限服务 中...")
|
||||||
@@ -178,7 +184,7 @@ class SenSuFramework:
|
|||||||
|
|
||||||
# 注册框架关闭处理器
|
# 注册框架关闭处理器
|
||||||
shutdown_service.register_shutdown_handler(self._framework_shutdown_handler)
|
shutdown_service.register_shutdown_handler(self._framework_shutdown_handler)
|
||||||
logger.info("🎉 SenSu 初始化完成!")
|
logger.info("🎉 DreamSu 初始化完成!")
|
||||||
self.is_running = True
|
self.is_running = True
|
||||||
|
|
||||||
# 显示欢迎日志
|
# 显示欢迎日志
|
||||||
@@ -194,7 +200,7 @@ class SenSuFramework:
|
|||||||
# 显示欢迎消息
|
# 显示欢迎消息
|
||||||
tui_service = self.service_manager.get_service("tui")
|
tui_service = self.service_manager.get_service("tui")
|
||||||
if hasattr(tui_service, 'show_message'):
|
if hasattr(tui_service, 'show_message'):
|
||||||
tui_service.show_message("🐱 SenSu 框架 已就绪!\n", "info")
|
tui_service.show_message("🐱 DreamSu 框架 已就绪!\n", "info")
|
||||||
tui_service.show_message("====================================\n", "info")
|
tui_service.show_message("====================================\n", "info")
|
||||||
tui_service.show_message("🐱 SenSu 已就绪!", "info")
|
tui_service.show_message("🐱 SenSu 已就绪!", "info")
|
||||||
tui_service.show_message(f"当前 SenSu 版本号 {version}", "info")
|
tui_service.show_message(f"当前 SenSu 版本号 {version}", "info")
|
||||||
@@ -253,8 +259,8 @@ class SenSuFramework:
|
|||||||
if shutdown_service:
|
if shutdown_service:
|
||||||
await shutdown_service.initiate_shutdown("安全关闭")
|
await shutdown_service.initiate_shutdown("安全关闭")
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except:
|
||||||
logger.warning(f"Shutdown service unavailable: {e}")
|
pass
|
||||||
|
|
||||||
# 如果关闭服务不可用,手动关闭其他服务
|
# 如果关闭服务不可用,手动关闭其他服务
|
||||||
services_to_shutdown = ['plugin', 'core_bridge', 'plugin_bridge', 'tui', 'log']
|
services_to_shutdown = ['plugin', 'core_bridge', 'plugin_bridge', 'tui', 'log']
|
||||||
@@ -263,8 +269,8 @@ class SenSuFramework:
|
|||||||
service = self.service_manager.get_service(service_name)
|
service = self.service_manager.get_service(service_name)
|
||||||
if service and hasattr(service, 'shutdown'):
|
if service and hasattr(service, 'shutdown'):
|
||||||
await service.shutdown() if asyncio.iscoroutinefunction(service.shutdown) else service.shutdown()
|
await service.shutdown() if asyncio.iscoroutinefunction(service.shutdown) else service.shutdown()
|
||||||
except Exception as e:
|
except:
|
||||||
logger.debug(f"Service shutdown skip: {e}")
|
pass
|
||||||
|
|
||||||
logger.debug("安全关闭完成")
|
logger.debug("安全关闭完成")
|
||||||
|
|
||||||
@@ -377,7 +383,7 @@ class SenSuFramework:
|
|||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
"""主函数"""
|
"""主函数"""
|
||||||
framework = SenSuFramework()
|
framework = CatFramework()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 初始化框架
|
# 初始化框架
|
||||||
@@ -408,9 +414,11 @@ if __name__ == "__main__":
|
|||||||
signal.signal(signal.SIGINT, signal_handler)
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
signal.signal(signal.SIGTERM, signal_handler)
|
signal.signal(signal.SIGTERM, signal_handler)
|
||||||
|
|
||||||
print("\n🐱 主程序启动...")
|
print("\n🐱 主程序启动...")
|
||||||
# 运行主程序
|
parser = argparse.ArgumentParser(description="SenSu 插件化项目管理框架")
|
||||||
asyncio.run(main())
|
parser.add_argument("--headless", action="store_true", help="无头模式 (不启动 TUI)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
asyncio.run(main(headless=args.headless))
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\n🐱 接收到键盘中断,关闭...")
|
print("\n🐱 接收到键盘中断,关闭...")
|
||||||
|
|||||||
@@ -1,386 +1,46 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import asyncio
|
from typing import Dict
|
||||||
from typing import Dict, Any
|
|
||||||
from aiohttp import web
|
|
||||||
import json
|
|
||||||
|
|
||||||
# 导入命令装饰器
|
|
||||||
try:
|
try:
|
||||||
from sdk.plugin_command_decorator import plugin_command, command
|
from sdk.plugin_command_decorator import plugin_command, command
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# 回退方案
|
def plugin_command(n=None,d=None,p=None):
|
||||||
def plugin_command(name=None, description=None, permissions=None):
|
def deco(f):
|
||||||
def decorator(func):
|
f._is_plugin_command=True;f._command_name=n or f.__name__
|
||||||
return func
|
f._command_description=d or (f.__doc__ or "").strip();return f
|
||||||
return decorator
|
return deco
|
||||||
|
command=plugin_command
|
||||||
command = plugin_command
|
|
||||||
|
|
||||||
# 导入网络桥接类 - 修正路径
|
logger=logging.getLogger(__name__)
|
||||||
try:
|
|
||||||
from bridges.plugin_network_bridge import PluginNetworkBridge
|
|
||||||
except ImportError:
|
|
||||||
# 如果导入失败,创建一个虚拟类
|
|
||||||
class PluginNetworkBridge:
|
|
||||||
def __init__(self, plugin_name, internet_service, plugin_bridge):
|
|
||||||
self.plugin_name = plugin_name
|
|
||||||
logger.warning(f"PluginNetworkBridge 不可用,插件 {plugin_name} 将以无网络模式运行")
|
|
||||||
|
|
||||||
async def register_http_route(self, *args, **kwargs):
|
|
||||||
logger.warning("网络功能不可用,跳过HTTP路由注册")
|
|
||||||
|
|
||||||
async def register_websocket(self, *args, **kwargs):
|
|
||||||
logger.warning("网络功能不可用,跳过WebSocket注册")
|
|
||||||
|
|
||||||
async def broadcast_websocket(self, *args, **kwargs):
|
|
||||||
logger.warning("网络功能不可用,无法广播消息")
|
|
||||||
|
|
||||||
def get_network_info(self):
|
|
||||||
return {
|
|
||||||
'plugin_name': self.plugin_name,
|
|
||||||
'registered_routes': [],
|
|
||||||
'websocket_handlers': [],
|
|
||||||
'base_url': '网络服务不可用'
|
|
||||||
}
|
|
||||||
|
|
||||||
async def setup_data_transfer(self, *args, **kwargs):
|
|
||||||
logger.warning("网络功能不可用,跳过数据传输设置")
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class Plugin:
|
class Plugin:
|
||||||
"""示例插件 - 展示命令注册和网络交互"""
|
def __init__(self,n,c,bridge):
|
||||||
|
self.plugin_name=n;self.config=c;self.bridge=bridge
|
||||||
def __init__(self, plugin_name: str, config: Dict, bridge):
|
self.network_bridge=None;self.is_running=False
|
||||||
self.plugin_name = plugin_name
|
|
||||||
self.config = config
|
|
||||||
self.bridge = bridge
|
|
||||||
self.network_bridge = None
|
|
||||||
self.is_running = False
|
|
||||||
logger.debug(f"示例插件初始化: {plugin_name}")
|
|
||||||
|
|
||||||
async def initialize(self):
|
async def initialize(self):
|
||||||
"""初始化插件 - 安全版本"""
|
logger.info(f"init: {self.plugin_name}")
|
||||||
try:
|
try:
|
||||||
logger.info(f"初始化示例插件: {self.plugin_name}")
|
internet=self.bridge.service_manager.get_service("internet")
|
||||||
|
from bridges.plugin_network_bridge import PluginNetworkBridge
|
||||||
# 安全地获取网络服务
|
self.network_bridge=PluginNetworkBridge(self.plugin_name,internet,self.bridge)
|
||||||
internet_service = None
|
|
||||||
try:
|
|
||||||
internet_service = self.bridge.service_manager.get_service("internet")
|
|
||||||
logger.debug(f"网络服务获取: {internet_service is not None}")
|
|
||||||
except (ValueError, AttributeError) as e:
|
|
||||||
logger.warning(f"网络服务不可用: {str(e)}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"获取网络服务时出错: {str(e)}")
|
|
||||||
|
|
||||||
# 只有在网络服务可用时才设置网络功能
|
|
||||||
if internet_service:
|
|
||||||
try:
|
|
||||||
# 创建网络桥接
|
|
||||||
self.network_bridge = PluginNetworkBridge(
|
|
||||||
self.plugin_name, internet_service, self.bridge
|
|
||||||
)
|
|
||||||
|
|
||||||
# 注册网络路由
|
|
||||||
await self._setup_network_routes()
|
|
||||||
logger.info(f"插件网络功能初始化完成: {self.plugin_name}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"设置网络功能时出错: {str(e)}")
|
|
||||||
logger.info("插件将以无网络模式运行")
|
|
||||||
else:
|
|
||||||
logger.info(f"插件 {self.plugin_name} 将以无网络模式运行")
|
|
||||||
# 创建虚拟网络桥接以便命令能正常工作
|
|
||||||
self.network_bridge = PluginNetworkBridge(self.plugin_name, None, self.bridge)
|
|
||||||
|
|
||||||
# 注册事件处理器(不依赖网络服务)
|
|
||||||
self.bridge.subscribe_plugin(
|
|
||||||
self.plugin_name,
|
|
||||||
"event.framework.start",
|
|
||||||
self._handle_framework_start
|
|
||||||
)
|
|
||||||
|
|
||||||
self.is_running = True
|
|
||||||
logger.debug(f"示例插件初始化完成: {self.plugin_name}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"初始化示例插件时出错: {str(e)}", exc_info=True)
|
|
||||||
raise
|
|
||||||
|
|
||||||
async def _setup_network_routes(self):
|
|
||||||
"""设置网络路由 - 安全版本"""
|
|
||||||
try:
|
|
||||||
if not self.network_bridge:
|
|
||||||
logger.warning("网络桥接不可用,跳过路由设置")
|
|
||||||
return
|
|
||||||
|
|
||||||
# 注册HTTP API端点
|
|
||||||
await self.network_bridge.register_http_route(
|
await self.network_bridge.register_http_route(
|
||||||
"/api/info",
|
"/api/example/info",self._api_info,methods=["GET"],require_auth=False)
|
||||||
self._handle_api_info,
|
|
||||||
methods=["GET"],
|
|
||||||
require_auth=False
|
|
||||||
)
|
|
||||||
|
|
||||||
await self.network_bridge.register_http_route(
|
|
||||||
"/api/echo",
|
|
||||||
self._handle_api_echo,
|
|
||||||
methods=["POST"],
|
|
||||||
require_auth=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# 注册WebSocket端点
|
|
||||||
await self.network_bridge.register_websocket(
|
|
||||||
"/chat",
|
|
||||||
self._handle_websocket_chat
|
|
||||||
)
|
|
||||||
|
|
||||||
# 设置跨端数据传输
|
|
||||||
await self.network_bridge.setup_data_transfer(
|
|
||||||
self._handle_cross_platform_data
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(f"示例插件网络路由设置完成: {self.plugin_name}")
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"设置网络路由时出错: {str(e)}", exc_info=True)
|
logger.warning(f"network skip: {e}")
|
||||||
# 不抛出异常,让插件继续运行
|
self.is_running=True
|
||||||
|
|
||||||
async def register_delayed_routes(self, internet_service):
|
async def _api_info(self,req):
|
||||||
"""延迟注册网络路由(在网络服务启动后调用)"""
|
from aiohttp import web
|
||||||
try:
|
return web.json_response({"plugin":self.plugin_name,"status":"running"})
|
||||||
logger.info(f"为插件 {self.plugin_name} 延迟注册网络路由")
|
|
||||||
|
|
||||||
# 重新创建网络桥接,使用真实的网络服务
|
|
||||||
if internet_service:
|
|
||||||
try:
|
|
||||||
# 重新初始化网络桥接
|
|
||||||
self.network_bridge = PluginNetworkBridge(
|
|
||||||
self.plugin_name, internet_service, self.bridge
|
|
||||||
)
|
|
||||||
|
|
||||||
# 重新设置网络路由
|
|
||||||
await self._setup_network_routes()
|
|
||||||
|
|
||||||
logger.info(f"插件 {self.plugin_name} 网络功能重新初始化完成")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"重新初始化网络桥接时出错: {str(e)}")
|
|
||||||
logger.info(f"插件 {self.plugin_name} 将继续使用无网络模式")
|
|
||||||
else:
|
|
||||||
logger.warning(f"网络服务不可用,插件 {self.plugin_name} 保持无网络模式")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"延迟注册网络路由时出错: {str(e)}")
|
|
||||||
|
|
||||||
async def _handle_api_info(self, request):
|
|
||||||
"""处理API信息请求"""
|
|
||||||
try:
|
|
||||||
info = {
|
|
||||||
"plugin_name": self.plugin_name,
|
|
||||||
"version": self.config.get('version', '1.0.0'),
|
|
||||||
"description": self.config.get('description', '示例插件'),
|
|
||||||
"network_info": self.network_bridge.get_network_info() if self.network_bridge else None,
|
|
||||||
"timestamp": asyncio.get_event_loop().time()
|
|
||||||
}
|
|
||||||
|
|
||||||
return web.json_response(info)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"处理API信息请求时出错: {str(e)}")
|
|
||||||
return web.json_response({"error": str(e)}, status=500)
|
|
||||||
|
|
||||||
async def _handle_api_echo(self, request):
|
@plugin_command(name="echo",description="echo input")
|
||||||
"""处理API回显请求"""
|
async def cmd_echo(self,*args):
|
||||||
try:
|
return " ".join(args) if args else "echo: no input"
|
||||||
data = await request.json()
|
|
||||||
|
|
||||||
response = {
|
|
||||||
"plugin_name": self.plugin_name,
|
|
||||||
"echo": data,
|
|
||||||
"timestamp": asyncio.get_event_loop().time()
|
|
||||||
}
|
|
||||||
|
|
||||||
return web.json_response(response)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"处理API回显请求时出错: {str(e)}")
|
|
||||||
return web.json_response({"error": str(e)}, status=400)
|
|
||||||
|
|
||||||
async def _handle_websocket_chat(self, ws, request):
|
|
||||||
"""处理WebSocket聊天"""
|
|
||||||
try:
|
|
||||||
logger.info(f"WebSocket聊天连接建立: {self.plugin_name}")
|
|
||||||
|
|
||||||
async for msg in ws:
|
|
||||||
if msg.type == web.WSMsgType.TEXT:
|
|
||||||
try:
|
|
||||||
data = json.loads(msg.data)
|
|
||||||
|
|
||||||
# 处理不同类型的消息
|
|
||||||
if data.get('type') == 'message':
|
|
||||||
# 广播消息给所有客户端
|
|
||||||
if self.network_bridge:
|
|
||||||
await self.network_bridge.broadcast_websocket({
|
|
||||||
"type": "message",
|
|
||||||
"from": data.get('user', 'anonymous'),
|
|
||||||
"content": data.get('content', ''),
|
|
||||||
"timestamp": asyncio.get_event_loop().time()
|
|
||||||
})
|
|
||||||
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
logger.warning(f"收到无效的JSON消息: {msg.data}")
|
|
||||||
|
|
||||||
elif msg.type == web.WSMsgType.ERROR:
|
|
||||||
logger.error(f"WebSocket错误: {ws.exception()}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"WebSocket聊天处理出错: {str(e)}")
|
|
||||||
finally:
|
|
||||||
logger.info(f"WebSocket聊天连接关闭: {self.plugin_name}")
|
|
||||||
|
|
||||||
async def _handle_cross_platform_data(self, event_type: str, data: Dict):
|
|
||||||
"""处理跨端数据"""
|
|
||||||
try:
|
|
||||||
logger.info(f"收到跨端数据: {event_type}")
|
|
||||||
|
|
||||||
# 在这里处理来自其他平台的数据
|
|
||||||
if event_type == "network.data.receive":
|
|
||||||
# 广播到WebSocket
|
|
||||||
if self.network_bridge:
|
|
||||||
await self.network_bridge.broadcast_websocket({
|
|
||||||
"type": "cross_platform",
|
|
||||||
"source": data.get('source', 'unknown'),
|
|
||||||
"data": data.get('data', {}),
|
|
||||||
"timestamp": asyncio.get_event_loop().time()
|
|
||||||
})
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"处理跨端数据时出错: {str(e)}")
|
|
||||||
|
|
||||||
async def _handle_framework_start(self, event_type: str, data: Dict):
|
|
||||||
"""处理框架启动事件"""
|
|
||||||
try:
|
|
||||||
logger.info(f"框架启动事件: {event_type}")
|
|
||||||
|
|
||||||
# 发送欢迎消息
|
|
||||||
if self.network_bridge:
|
|
||||||
await self.network_bridge.broadcast_websocket({
|
|
||||||
"type": "system",
|
|
||||||
"message": f"插件 {self.plugin_name} 已启动,框架已就绪",
|
|
||||||
"timestamp": asyncio.get_event_loop().time()
|
|
||||||
})
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"处理框架启动事件时出错: {str(e)}")
|
|
||||||
|
|
||||||
# 确保所有网络相关方法都检查 network_bridge
|
@plugin_command(name="plugin_status",description="show plugin status")
|
||||||
@plugin_command(name="chat_broadcast",
|
async def cmd_status(self,*args):
|
||||||
description="向所有聊天客户端广播消息",
|
return f"{self.plugin_name} v{self.config.get('version','?')} - running"
|
||||||
permissions=["plugin.example.chat.broadcast"])
|
|
||||||
async def cmd_chat_broadcast(self, *args):
|
|
||||||
"""向所有聊天客户端广播消息"""
|
|
||||||
try:
|
|
||||||
if not args:
|
|
||||||
return "❌ 请提供要广播的消息内容"
|
|
||||||
|
|
||||||
message = " ".join(args)
|
|
||||||
|
|
||||||
if self.network_bridge:
|
|
||||||
await self.network_bridge.broadcast_websocket({
|
|
||||||
"type": "broadcast",
|
|
||||||
"from": "system",
|
|
||||||
"content": message,
|
|
||||||
"timestamp": asyncio.get_event_loop().time()
|
|
||||||
})
|
|
||||||
|
|
||||||
return f"✅ 已广播消息: {message}"
|
|
||||||
else:
|
|
||||||
return "❌ 网络服务不可用,无法广播消息"
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"广播消息时出错: {str(e)}")
|
|
||||||
return f"❌ 广播失败: {str(e)}"
|
|
||||||
|
|
||||||
@plugin_command(name="network_info",
|
|
||||||
description="显示插件网络信息")
|
|
||||||
async def cmd_network_info(self, *args):
|
|
||||||
"""显示插件的网络配置信息"""
|
|
||||||
try:
|
|
||||||
if not self.network_bridge:
|
|
||||||
result = ["🌐 **插件网络信息:**"]
|
|
||||||
result.append("❌ 网络服务不可用")
|
|
||||||
result.append("\n💡 **网络服务状态:**")
|
|
||||||
|
|
||||||
# 尝试获取网络服务状态
|
|
||||||
try:
|
|
||||||
internet_service = self.bridge.service_manager.get_service("internet")
|
|
||||||
if internet_service:
|
|
||||||
result.append(" ✅ 网络服务已注册")
|
|
||||||
health_info = await internet_service.check_service_health()
|
|
||||||
result.append(f" 🔄 服务运行: {'✅ 是' if health_info.get('is_running') else '❌ 否'}")
|
|
||||||
result.append(f" 🌐 HTTP活跃: {'✅ 是' if health_info.get('http_active') else '❌ 否'}")
|
|
||||||
else:
|
|
||||||
result.append(" ❌ 网络服务未注册")
|
|
||||||
except:
|
|
||||||
result.append(" ❓ 无法获取网络服务状态")
|
|
||||||
|
|
||||||
result.append("\n🔧 **建议:**")
|
|
||||||
result.append(" - 检查网络服务启动日志")
|
|
||||||
result.append(" - 使用 'services' 命令查看服务状态")
|
|
||||||
result.append(" - 使用 'netdiag' 命令进行网络诊断")
|
|
||||||
return "\n".join(result)
|
|
||||||
|
|
||||||
info = self.network_bridge.get_network_info()
|
|
||||||
result = ["🌐 **插件网络信息:**"]
|
|
||||||
result.append(f" 插件名称: {info['plugin_name']}")
|
|
||||||
result.append(f" 基础URL: {info['base_url']}")
|
|
||||||
result.append(f" HTTP路由数: {len(info['registered_routes'])}")
|
|
||||||
result.append(f" WebSocket处理器数: {len(info['websocket_handlers'])}")
|
|
||||||
|
|
||||||
if info['registered_routes']:
|
|
||||||
result.append("\n📡 **注册的HTTP路由:**")
|
|
||||||
for route in info['registered_routes']:
|
|
||||||
result.append(f" {route['path']} [{','.join(route['methods'])}]")
|
|
||||||
|
|
||||||
if info['websocket_handlers']:
|
|
||||||
result.append("\n🔗 **注册的WebSocket:**")
|
|
||||||
for ws in info['websocket_handlers']:
|
|
||||||
result.append(f" {ws['path']}")
|
|
||||||
|
|
||||||
return "\n".join(result)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"获取网络信息时出错: {str(e)}")
|
|
||||||
return f"❌ 获取网络信息失败: {str(e)}"
|
|
||||||
|
|
||||||
# ... 其余方法保持不变 ...
|
|
||||||
|
|
||||||
async def shutdown(self):
|
async def shutdown(self):
|
||||||
"""关闭插件"""
|
self.is_running=False
|
||||||
try:
|
|
||||||
logger.info(f"关闭示例插件: {self.plugin_name}")
|
|
||||||
self.is_running = False
|
|
||||||
|
|
||||||
# 只有在网络桥接可用时才发送关闭通知
|
|
||||||
if self.network_bridge and hasattr(self.network_bridge, 'broadcast_websocket'):
|
|
||||||
try:
|
|
||||||
await self.network_bridge.broadcast_websocket({
|
|
||||||
"type": "system",
|
|
||||||
"message": f"插件 {self.plugin_name} 正在关闭",
|
|
||||||
"timestamp": asyncio.get_event_loop().time()
|
|
||||||
})
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"发送关闭通知失败: {str(e)}")
|
|
||||||
|
|
||||||
# 清理资源
|
|
||||||
self.bridge.cleanup_plugin_subscriptions(self.plugin_name)
|
|
||||||
|
|
||||||
logger.debug(f"示例插件关闭完成: {self.plugin_name}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"关闭示例插件时出错: {str(e)}", exc_info=True)
|
|
||||||
|
|||||||
@@ -100,23 +100,6 @@ class PluginService:
|
|||||||
logger.error(f"加载所有插件时出错: {str(e)}", exc_info=True)
|
logger.error(f"加载所有插件时出错: {str(e)}", exc_info=True)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def _load_plugin_config(self, plugin_name: str) -> dict:
|
|
||||||
"""加载并验证插件配置文件"""
|
|
||||||
import yaml
|
|
||||||
config_path = os.path.join(os.path.dirname(__file__), "..", "plugins", plugin_name, "config.yaml")
|
|
||||||
# Also check workspace plugins dir
|
|
||||||
workspace_config = os.path.join(os.getcwd(), "plugins", plugin_name, "config.yaml")
|
|
||||||
for path in [config_path, workspace_config]:
|
|
||||||
if os.path.exists(path):
|
|
||||||
try:
|
|
||||||
with open(path) as f:
|
|
||||||
return yaml.safe_load(f) or {}
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"解析插件配置失败 {path}: {e}")
|
|
||||||
return {}
|
|
||||||
return {}
|
|
||||||
|
|
||||||
async def load_plugin(self, plugin_name: str) -> bool:
|
async def load_plugin(self, plugin_name: str) -> bool:
|
||||||
"""加载单个插件 - 支持异步权限处理"""
|
"""加载单个插件 - 支持异步权限处理"""
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user