@agentunion/kite 1.3.2 → 1.5.0
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.
- package/CHANGELOG.md +302 -0
- package/cli.js +119 -4
- package/core/dependency_checker.py +250 -0
- package/core/env_checker.py +490 -0
- package/dependencies_lock.json +128 -0
- package/extensions/agents/assistant/entry.py +111 -1
- package/extensions/agents/assistant/server.py +279 -215
- package/extensions/channels/acp_channel/entry.py +111 -1
- package/extensions/channels/acp_channel/module.md +23 -22
- package/extensions/channels/acp_channel/server.py +279 -215
- package/extensions/event_hub_bench/entry.py +107 -1
- package/extensions/services/backup/entry.py +306 -21
- package/extensions/services/backup/module.md +24 -22
- package/extensions/services/evol/auth_manager.py +443 -0
- package/extensions/services/evol/config.yaml +149 -0
- package/extensions/services/evol/config_loader.py +117 -0
- package/extensions/services/evol/entry.py +406 -0
- package/extensions/services/evol/evol_api.py +173 -0
- package/extensions/services/evol/evol_config.json5 +29 -0
- package/extensions/services/evol/migrate_tokens.py +122 -0
- package/extensions/services/evol/module.md +32 -0
- package/extensions/services/evol/pairing.py +250 -0
- package/extensions/services/evol/pairing_codes.jsonl +1 -0
- package/extensions/services/evol/relay.py +682 -0
- package/extensions/services/evol/relay_config.json5 +67 -0
- package/extensions/services/evol/routes/__init__.py +1 -0
- package/extensions/services/evol/routes/routes_management_ws.py +127 -0
- package/extensions/services/evol/routes/routes_rpc.py +89 -0
- package/extensions/services/evol/routes/routes_test.py +61 -0
- package/extensions/services/evol/server.py +875 -0
- package/extensions/services/evol/static/css/style.css +1200 -0
- package/extensions/services/evol/static/index.html +781 -0
- package/extensions/services/evol/static/index_evol.html +14 -0
- package/extensions/services/evol/static/js/app.js +6304 -0
- package/extensions/services/evol/static/js/auth.js +326 -0
- package/extensions/services/evol/static/js/dialog.js +285 -0
- package/extensions/services/evol/static/js/evol-app-fixed.js +50 -0
- package/extensions/services/evol/static/js/evol-app.js +1949 -0
- package/extensions/services/evol/static/js/evol-app.js.bak +1800 -0
- package/extensions/services/evol/static/js/kernel-client-example.js +228 -0
- package/extensions/services/evol/static/js/kernel-client.js +396 -0
- package/extensions/services/evol/static/js/main.js +141 -0
- package/extensions/services/evol/static/js/registry-tests.js +585 -0
- package/extensions/services/evol/static/js/stats.js +217 -0
- package/extensions/services/evol/static/js/token-manager.js +175 -0
- package/extensions/services/evol/static/pairing.html +248 -0
- package/extensions/services/evol/static/test_registry.html +262 -0
- package/extensions/services/evol/static/test_relay.html +462 -0
- package/extensions/services/evol/stats_manager.py +240 -0
- package/extensions/services/model_service/entry.py +167 -19
- package/extensions/services/model_service/module.md +21 -22
- package/extensions/services/proxy/.claude/settings.local.json +13 -0
- package/extensions/services/proxy/CHANGELOG_20260308.md +258 -0
- package/extensions/services/proxy/_fix_prints.py +133 -0
- package/extensions/services/proxy/_fix_prints2.py +87 -0
- package/extensions/services/proxy/agentcp/LICENCE +178 -0
- package/extensions/services/proxy/agentcp/README copy.md +85 -0
- package/extensions/services/proxy/agentcp/README.md +260 -0
- package/extensions/services/proxy/agentcp/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/agent.py +4 -0
- package/extensions/services/proxy/agentcp/agentcp.py +2494 -0
- package/extensions/services/proxy/agentcp/agentprofile.json +89 -0
- package/extensions/services/proxy/agentcp/ap/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/ap/ap_client.py +316 -0
- package/extensions/services/proxy/agentcp/assets/images/wechat_qr.png +0 -0
- package/extensions/services/proxy/agentcp/backup/metrics.json +31 -0
- package/extensions/services/proxy/agentcp/base/__init__.py +20 -0
- package/extensions/services/proxy/agentcp/base/auth_client.py +257 -0
- package/extensions/services/proxy/agentcp/base/client.py +112 -0
- package/extensions/services/proxy/agentcp/base/env.py +34 -0
- package/extensions/services/proxy/agentcp/base/html_util.py +336 -0
- package/extensions/services/proxy/agentcp/base/log.py +98 -0
- package/extensions/services/proxy/agentcp/ca/__init__.py +17 -0
- package/extensions/services/proxy/agentcp/ca/ca_client.py +414 -0
- package/extensions/services/proxy/agentcp/ca/ca_root.py +74 -0
- package/extensions/services/proxy/agentcp/context/__init__.py +20 -0
- package/extensions/services/proxy/agentcp/context/context.py +73 -0
- package/extensions/services/proxy/agentcp/context/exceptions.py +114 -0
- package/extensions/services/proxy/agentcp/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/create_profile_weather.py +125 -0
- package/extensions/services/proxy/agentcp/db/__init__.py +15 -0
- package/extensions/services/proxy/agentcp/db/db_mananger.py +550 -0
- package/extensions/services/proxy/agentcp/docs/UDP_HEARTBEAT_FIX_REPORT.md +265 -0
- package/extensions/services/proxy/agentcp/docs/heartbeat_issue_analysis.md +291 -0
- package/extensions/services/proxy/agentcp/file/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/file/file_client.py +141 -0
- package/extensions/services/proxy/agentcp/file/wss_binary_message.py +137 -0
- package/extensions/services/proxy/agentcp/hcp.py +299 -0
- package/extensions/services/proxy/agentcp/heartbeat/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/heartbeat/heartbeat_client.py +360 -0
- package/extensions/services/proxy/agentcp/improved_scheduler.py +498 -0
- package/extensions/services/proxy/agentcp/llm_agent_utils.py +249 -0
- package/extensions/services/proxy/agentcp/llm_server.py +172 -0
- package/extensions/services/proxy/agentcp/mermaid.py +210 -0
- package/extensions/services/proxy/agentcp/message.py +149 -0
- package/extensions/services/proxy/agentcp/metrics.py +256 -0
- package/extensions/services/proxy/agentcp/monitoring/__init__.py +20 -0
- package/extensions/services/proxy/agentcp/monitoring/global_monitor.py +27 -0
- package/extensions/services/proxy/agentcp/monitoring/metrics_store.py +325 -0
- package/extensions/services/proxy/agentcp/monitoring/monitoring_service.py +269 -0
- package/extensions/services/proxy/agentcp/monitoring/sliding_window.py +222 -0
- package/extensions/services/proxy/agentcp/monitoring/standalone_reader.py +224 -0
- package/extensions/services/proxy/agentcp/msg/__init__.py +21 -0
- package/extensions/services/proxy/agentcp/msg/connection_manager.py +456 -0
- package/extensions/services/proxy/agentcp/msg/message_client.py +2058 -0
- package/extensions/services/proxy/agentcp/msg/message_serialize.py +263 -0
- package/extensions/services/proxy/agentcp/msg/open_ai_message.py +88 -0
- package/extensions/services/proxy/agentcp/msg/session_manager.py +1062 -0
- package/extensions/services/proxy/agentcp/msg/stream_client.py +267 -0
- package/extensions/services/proxy/agentcp/msg/websocket_file_receiver.py +89 -0
- package/extensions/services/proxy/agentcp/msg/ws_logger.py +685 -0
- package/extensions/services/proxy/agentcp/msg/wss_binary_message.py +137 -0
- package/extensions/services/proxy/agentcp/requirements.txt +7 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/README.md +37 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/agentprofile.json +89 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/create_profile.py +138 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/main.py +164 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/create_profile.py +123 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/llm/create_profile.py +129 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/llm/env.json +5 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/llm/main.py +146 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/main.py +123 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/readme.md +379 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/search/create_profile.py +129 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/search/main.py +28 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/tool/create_profile.py +129 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/tool/main.py +20 -0
- package/extensions/services/proxy/agentcp/samples/ali_amap/README.md +97 -0
- package/extensions/services/proxy/agentcp/samples/ali_amap/amap_agent.py +88 -0
- package/extensions/services/proxy/agentcp/samples/ali_amap/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/agent/powershell.py +228 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/agent/software.py +63 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/agent/tools.py +36 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/browser_user.py +41 -0
- package/extensions/services/proxy/agentcp/samples/deepseek/README.md +79 -0
- package/extensions/services/proxy/agentcp/samples/deepseek/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/deepseek/deepseek.py +42 -0
- package/extensions/services/proxy/agentcp/samples/dify_chat/README.md +78 -0
- package/extensions/services/proxy/agentcp/samples/dify_chat/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/dify_chat/dify_chat.py +47 -0
- package/extensions/services/proxy/agentcp/samples/dify_workflow/README.md +78 -0
- package/extensions/services/proxy/agentcp/samples/dify_workflow/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/dify_workflow/dify_workflow.py +46 -0
- package/extensions/services/proxy/agentcp/samples/executor/README.md +44 -0
- package/extensions/services/proxy/agentcp/samples/executor/agentprofile.json +89 -0
- package/extensions/services/proxy/agentcp/samples/executor/create_profile.py +139 -0
- package/extensions/services/proxy/agentcp/samples/executor/main.py +160 -0
- package/extensions/services/proxy/agentcp/samples/filereader/README.md +45 -0
- package/extensions/services/proxy/agentcp/samples/filereader/agentprofile.json +90 -0
- package/extensions/services/proxy/agentcp/samples/filereader/create_profile.py +137 -0
- package/extensions/services/proxy/agentcp/samples/filereader/main.py +253 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/README.md +38 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/agentprofile.json +91 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/create_profile.py +138 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/main.py +289 -0
- package/extensions/services/proxy/agentcp/samples/hcp/README.md +85 -0
- package/extensions/services/proxy/agentcp/samples/hcp/acp_weather_agent.zip +0 -0
- package/extensions/services/proxy/agentcp/samples/hcp/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/hcp/hcp.py +237 -0
- package/extensions/services/proxy/agentcp/samples/helloworld/README.md +68 -0
- package/extensions/services/proxy/agentcp/samples/helloworld/hello_world.py +40 -0
- package/extensions/services/proxy/agentcp/samples/llm_agent/MEADME.md +117 -0
- package/extensions/services/proxy/agentcp/samples/llm_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/llm_agent/qwen_agent.py +136 -0
- package/extensions/services/proxy/agentcp/samples/local_llm_agent/README.md +90 -0
- package/extensions/services/proxy/agentcp/samples/local_llm_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/local_llm_agent/main.py +49 -0
- package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/README.md +55 -0
- package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/main.py +23 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/README.md +103 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/main.py +69 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/README.md +58 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/main.py +25 -0
- package/extensions/services/proxy/agentcp/samples/qwen3/README.md +71 -0
- package/extensions/services/proxy/agentcp/samples/qwen3/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/qwen3/qwen3.py +37 -0
- package/extensions/services/proxy/agentcp/samples/qwen3_tools/README.md +133 -0
- package/extensions/services/proxy/agentcp/samples/qwen3_tools/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/qwen3_tools/qwen3_tools.py +98 -0
- package/extensions/services/proxy/agentcp/samples/search/create_profile_qwen.py +125 -0
- package/extensions/services/proxy/agentcp/samples/search/create_profile_search.py +125 -0
- package/extensions/services/proxy/agentcp/samples/search/qwen_agent.py +136 -0
- package/extensions/services/proxy/agentcp/samples/search/search_agent.py +170 -0
- package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/README.md +89 -0
- package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/main.py +44 -0
- package/extensions/services/proxy/agentcp/utils/__init__.py +15 -0
- package/extensions/services/proxy/agentcp/utils/file_util.py +117 -0
- package/extensions/services/proxy/agentcp/utils/proxy_bypass.py +99 -0
- package/extensions/services/proxy/agentcp/workflow.py +203 -0
- package/extensions/services/proxy/console_auth.py +109 -0
- package/extensions/services/proxy/evol/__init__.py +1 -0
- package/extensions/services/proxy/evol/config.py +37 -0
- package/extensions/services/proxy/evol/http/__init__.py +1 -0
- package/extensions/services/proxy/evol/http/async_http.py +551 -0
- package/extensions/services/proxy/evol/log.py +28 -0
- package/extensions/services/proxy/evol/presenter/__init__.py +2 -0
- package/extensions/services/proxy/evol/presenter/agentIdPresenter.py +1031 -0
- package/extensions/services/proxy/evol/presenter/apikeyPresenter.py +106 -0
- package/extensions/services/proxy/evol/presenter/configPresenter.py +1281 -0
- package/extensions/services/proxy/evol/presenter/userPresenter.py +477 -0
- package/extensions/services/proxy/evol/server/__init__.py +1 -0
- package/extensions/services/proxy/evol/server/claude_proxy_async.py +3430 -0
- package/extensions/services/proxy/evol/server/openclaw_proxy.py +1861 -0
- package/extensions/services/proxy/evol/server/proxy_config.py +15 -0
- package/extensions/services/proxy/evol/server/proxy_engine.py +501 -0
- package/extensions/services/proxy/evol/version.py +24 -0
- package/extensions/services/proxy/logs/websocket.log +260 -0
- package/extensions/services/proxy/main.py +240 -0
- package/extensions/services/proxy/requirements.txt +13 -0
- package/extensions/services/proxy/server.py +271 -0
- package/extensions/services/watchdog/entry.py +215 -26
- package/extensions/services/watchdog/module.md +1 -0
- package/extensions/services/watchdog/monitor.py +178 -38
- package/extensions/services/web/WEBSOCKET_STATUS.md +143 -0
- package/extensions/services/web/config_example.py +35 -0
- package/extensions/services/web/config_loader.py +110 -0
- package/extensions/services/web/entry.py +114 -26
- package/extensions/services/web/module.md +35 -24
- package/extensions/services/web/pairing.py +250 -0
- package/extensions/services/web/pairing_codes.jsonl +16 -0
- package/extensions/services/web/relay.py +643 -0
- package/extensions/services/web/relay_config.json5 +67 -0
- package/extensions/services/web/routes/routes_management_ws.py +127 -0
- package/extensions/services/web/routes/routes_rpc.py +89 -0
- package/extensions/services/web/routes/routes_test.py +61 -0
- package/extensions/services/web/routes/schemas.py +0 -22
- package/extensions/services/web/server.py +434 -99
- package/extensions/services/web/static/css/style.css +67 -28
- package/extensions/services/web/static/index.html +234 -44
- package/extensions/services/web/static/js/app.js +1335 -48
- package/extensions/services/web/static/js/kernel-client-example.js +161 -0
- package/extensions/services/web/static/js/kernel-client.js +383 -0
- package/extensions/services/web/static/js/registry-tests.js +558 -0
- package/extensions/services/web/static/js/token-manager.js +175 -0
- package/extensions/services/web/static/pairing.html +248 -0
- package/extensions/services/web/static/test_registry.html +262 -0
- package/extensions/services/web/web_config.json5 +29 -0
- package/kernel/entry.py +120 -32
- package/kernel/event_hub.py +141 -16
- package/kernel/module.md +60 -33
- package/kernel/registry_store.py +45 -36
- package/kernel/rpc_router.py +152 -59
- package/kernel/server.py +322 -26
- package/kite_cli/__init__.py +3 -0
- package/kite_cli/__main__.py +5 -0
- package/kite_cli/commands/__init__.py +1 -0
- package/kite_cli/commands/clean.py +101 -0
- package/kite_cli/commands/deps_install.py +67 -0
- package/kite_cli/commands/doctor.py +35 -0
- package/kite_cli/commands/env_check.py +45 -0
- package/kite_cli/commands/history.py +111 -0
- package/kite_cli/commands/info.py +96 -0
- package/kite_cli/commands/install.py +313 -0
- package/kite_cli/commands/list.py +143 -0
- package/kite_cli/commands/log.py +81 -0
- package/kite_cli/commands/prepare.py +49 -0
- package/kite_cli/commands/rollback.py +88 -0
- package/kite_cli/commands/search.py +73 -0
- package/kite_cli/commands/uninstall.py +85 -0
- package/kite_cli/commands/update.py +118 -0
- package/kite_cli/commands/venv_setup.py +56 -0
- package/kite_cli/core/__init__.py +1 -0
- package/kite_cli/core/checker.py +142 -0
- package/kite_cli/core/dependency.py +229 -0
- package/kite_cli/core/downloader.py +209 -0
- package/kite_cli/core/install_info.py +40 -0
- package/kite_cli/core/tool_installer.py +397 -0
- package/kite_cli/core/validator.py +78 -0
- package/kite_cli/main.py +317 -0
- package/kite_cli/utils/__init__.py +1 -0
- package/kite_cli/utils/i18n.py +252 -0
- package/kite_cli/utils/interactive.py +63 -0
- package/kite_cli/utils/operation_log.py +77 -0
- package/kite_cli/utils/paths.py +34 -0
- package/kite_cli/utils/version.py +308 -0
- package/launcher/entry.py +1124 -178
- package/launcher/logging_setup.py +104 -0
- package/launcher/module.md +46 -37
- package/launcher/module_scanner.py +11 -1
- package/main.py +4 -1
- package/package.json +9 -1
- package/python_version.json +4 -0
- package/requirements.txt +38 -0
- package/scripts/env-manager.js +328 -0
- package/scripts/plan_manager.py +315 -0
- package/scripts/python-env.js +79 -0
- package/scripts/scan_dependencies.py +461 -0
- package/scripts/setup-python-env.js +191 -0
- package/extensions/services/web/routes/routes_modules.py +0 -249
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Kernel WebSocket 中转服务
|
|
3
|
+
|
|
4
|
+
功能:
|
|
5
|
+
- 接受前端 WebSocket 连接
|
|
6
|
+
- 处理配对流程(通过 WebSocket)
|
|
7
|
+
- 为每个前端创建到 Kernel 的连接
|
|
8
|
+
- 双向转发 JSON-RPC 消息
|
|
9
|
+
- 管理前端模块的生命周期(注册、重连、优雅退出)
|
|
10
|
+
- RPC 权限控制(白名单)
|
|
11
|
+
|
|
12
|
+
零共享代码依赖 - 此文件可以独立拷贝到其他模块使用。
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import json
|
|
17
|
+
import secrets
|
|
18
|
+
import time
|
|
19
|
+
import uuid
|
|
20
|
+
from datetime import datetime, timezone
|
|
21
|
+
from typing import Optional
|
|
22
|
+
|
|
23
|
+
import websockets
|
|
24
|
+
from fastapi import WebSocket, WebSocketDisconnect
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SessionInfo:
|
|
28
|
+
"""前端会话信息"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
session_token: str,
|
|
33
|
+
module_id: str,
|
|
34
|
+
kernel_ws,
|
|
35
|
+
client_ws: WebSocket,
|
|
36
|
+
frontend_token: str,
|
|
37
|
+
role: str,
|
|
38
|
+
):
|
|
39
|
+
self.session_token = session_token
|
|
40
|
+
self.module_id = module_id
|
|
41
|
+
self.kernel_ws = kernel_ws
|
|
42
|
+
self.client_ws: Optional[WebSocket] = client_ws
|
|
43
|
+
self.frontend_token = frontend_token
|
|
44
|
+
self.role = role
|
|
45
|
+
self.created_at = time.time()
|
|
46
|
+
self.last_active = time.time()
|
|
47
|
+
self.status = "active" # active | waiting_reconnect
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class KernelRelay:
|
|
51
|
+
"""Kernel WebSocket 中转服务"""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
kernel_host: str,
|
|
56
|
+
kernel_port: int,
|
|
57
|
+
kernel_token: str,
|
|
58
|
+
base_module_id: str,
|
|
59
|
+
reconnect_timeout: int,
|
|
60
|
+
permissions: dict,
|
|
61
|
+
pairing_manager,
|
|
62
|
+
web_server, # 新增:web server 实例,用于发送事件
|
|
63
|
+
):
|
|
64
|
+
"""
|
|
65
|
+
初始化中转服务。
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
kernel_host: Kernel 主机地址
|
|
69
|
+
kernel_port: Kernel 端口
|
|
70
|
+
kernel_token: Kernel 认证 token
|
|
71
|
+
base_module_id: 基础模块 ID(实际 module_id 会加上 session 后缀)
|
|
72
|
+
reconnect_timeout: 重连超时时间(秒)
|
|
73
|
+
permissions: 权限配置(角色 → RPC 白名单)
|
|
74
|
+
pairing_manager: 配对管理器实例
|
|
75
|
+
web_server: web server 实例
|
|
76
|
+
"""
|
|
77
|
+
self.kernel_host = kernel_host
|
|
78
|
+
self.kernel_port = kernel_port
|
|
79
|
+
self.kernel_token = kernel_token
|
|
80
|
+
self.base_module_id = base_module_id
|
|
81
|
+
self.reconnect_timeout = reconnect_timeout
|
|
82
|
+
self.permissions = permissions
|
|
83
|
+
self.pairing_manager = pairing_manager
|
|
84
|
+
self.web_server = web_server
|
|
85
|
+
|
|
86
|
+
# session_token → SessionInfo
|
|
87
|
+
self.sessions: dict[str, SessionInfo] = {}
|
|
88
|
+
|
|
89
|
+
# 待重连的 session(超时清理)
|
|
90
|
+
self.reconnect_timers: dict[str, asyncio.Task] = {}
|
|
91
|
+
|
|
92
|
+
async def handle_client(self, client_ws: WebSocket):
|
|
93
|
+
"""
|
|
94
|
+
处理客户端连接。
|
|
95
|
+
|
|
96
|
+
流程:
|
|
97
|
+
1. 接受 WebSocket 连接
|
|
98
|
+
2. 接收认证/配对/请求配对码消息
|
|
99
|
+
3. 验证身份
|
|
100
|
+
4. 创建或恢复 Kernel 连接
|
|
101
|
+
5. 双向转发消息
|
|
102
|
+
"""
|
|
103
|
+
await client_ws.accept()
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
# 接收认证/配对/请求配对码消息
|
|
107
|
+
raw = await client_ws.receive_text()
|
|
108
|
+
msg = json.loads(raw)
|
|
109
|
+
msg_type = msg.get("type")
|
|
110
|
+
|
|
111
|
+
if msg_type == "request_code":
|
|
112
|
+
# 请求配对码
|
|
113
|
+
await self._handle_request_code(client_ws, msg)
|
|
114
|
+
elif msg_type == "pair":
|
|
115
|
+
# 配对流程
|
|
116
|
+
await self._handle_pair(client_ws, msg)
|
|
117
|
+
elif msg_type == "auth":
|
|
118
|
+
# 认证流程(已有 token)
|
|
119
|
+
await self._handle_auth(client_ws, msg)
|
|
120
|
+
else:
|
|
121
|
+
await client_ws.send_json({
|
|
122
|
+
"type": "error",
|
|
123
|
+
"message": "Invalid message type, expected 'request_code', 'pair' or 'auth'"
|
|
124
|
+
})
|
|
125
|
+
await client_ws.close(code=4000, reason="Invalid message type")
|
|
126
|
+
|
|
127
|
+
except WebSocketDisconnect:
|
|
128
|
+
pass
|
|
129
|
+
except Exception as e:
|
|
130
|
+
print(f"[relay] Client connection error: {e}")
|
|
131
|
+
try:
|
|
132
|
+
await client_ws.close(code=1011, reason="Internal error")
|
|
133
|
+
except Exception:
|
|
134
|
+
pass
|
|
135
|
+
|
|
136
|
+
async def _handle_request_code(self, client_ws: WebSocket, msg: dict):
|
|
137
|
+
"""处理请求配对码"""
|
|
138
|
+
# 生成配对码
|
|
139
|
+
code = self.pairing_manager.generate_pairing_code(role="admin")
|
|
140
|
+
|
|
141
|
+
# 发送事件给 Launcher(通过 Kernel)
|
|
142
|
+
if self.web_server and self.web_server._ws:
|
|
143
|
+
try:
|
|
144
|
+
await self.web_server._publish_event({
|
|
145
|
+
"event": "pairing.status",
|
|
146
|
+
"data": {
|
|
147
|
+
"step": "code_generated",
|
|
148
|
+
"success": True,
|
|
149
|
+
"code": code,
|
|
150
|
+
"expires_in": 300
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
except Exception as e:
|
|
154
|
+
print(f"[relay] Failed to publish pairing event: {e}")
|
|
155
|
+
|
|
156
|
+
# 返回配对码给前端
|
|
157
|
+
await client_ws.send_json({
|
|
158
|
+
"type": "code_generated",
|
|
159
|
+
"code": code
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
# 关闭连接(前端会重新连接进行配对)
|
|
163
|
+
await client_ws.close()
|
|
164
|
+
|
|
165
|
+
async def _handle_pair(self, client_ws: WebSocket, msg: dict):
|
|
166
|
+
"""处理配对请求"""
|
|
167
|
+
code = msg.get("code")
|
|
168
|
+
if not code:
|
|
169
|
+
await client_ws.send_json({
|
|
170
|
+
"type": "error",
|
|
171
|
+
"message": "Missing pairing code"
|
|
172
|
+
})
|
|
173
|
+
await client_ws.close(code=4000, reason="Missing pairing code")
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
# 验证配对码
|
|
177
|
+
result = self.pairing_manager.pair(code)
|
|
178
|
+
if not result:
|
|
179
|
+
# 发送配对失败事件
|
|
180
|
+
if self.web_server and self.web_server._ws:
|
|
181
|
+
try:
|
|
182
|
+
await self.web_server._publish_event({
|
|
183
|
+
"event": "pairing.status",
|
|
184
|
+
"data": {
|
|
185
|
+
"step": "completed",
|
|
186
|
+
"success": False,
|
|
187
|
+
"reason": "Invalid pairing code"
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
except Exception as e:
|
|
191
|
+
print(f"[relay] Failed to publish pairing failed event: {e}")
|
|
192
|
+
|
|
193
|
+
await client_ws.send_json({
|
|
194
|
+
"type": "error",
|
|
195
|
+
"message": "Invalid pairing code"
|
|
196
|
+
})
|
|
197
|
+
await client_ws.close(code=4001, reason="Invalid pairing code")
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
# 生成 session_token
|
|
201
|
+
session_token = "sess_" + secrets.token_urlsafe(6)[:6]
|
|
202
|
+
|
|
203
|
+
# 创建新连接
|
|
204
|
+
await self._create_new_connection(
|
|
205
|
+
client_ws,
|
|
206
|
+
session_token,
|
|
207
|
+
result["token"],
|
|
208
|
+
result["role"],
|
|
209
|
+
is_pairing=True
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
async def _handle_auth(self, client_ws: WebSocket, msg: dict):
|
|
213
|
+
"""处理认证请求(已有 token)"""
|
|
214
|
+
frontend_token = msg.get("token")
|
|
215
|
+
session_token = msg.get("session_token")
|
|
216
|
+
|
|
217
|
+
if not frontend_token or not session_token:
|
|
218
|
+
await client_ws.send_json({
|
|
219
|
+
"type": "error",
|
|
220
|
+
"message": "Missing token or session_token"
|
|
221
|
+
})
|
|
222
|
+
await client_ws.close(code=4000, reason="Missing credentials")
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
# 验证 frontend_token
|
|
226
|
+
token_info = self.pairing_manager.verify_token(frontend_token)
|
|
227
|
+
if not token_info:
|
|
228
|
+
await client_ws.send_json({
|
|
229
|
+
"type": "error",
|
|
230
|
+
"message": "Invalid or expired token"
|
|
231
|
+
})
|
|
232
|
+
await client_ws.close(code=4001, reason="Invalid token")
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
# 检查是否是重连
|
|
236
|
+
if session_token in self.sessions:
|
|
237
|
+
await self._handle_reconnect(client_ws, session_token, token_info)
|
|
238
|
+
else:
|
|
239
|
+
await self._create_new_connection(
|
|
240
|
+
client_ws,
|
|
241
|
+
session_token,
|
|
242
|
+
frontend_token,
|
|
243
|
+
token_info["role"]
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
async def _create_new_connection(
|
|
247
|
+
self,
|
|
248
|
+
client_ws: WebSocket,
|
|
249
|
+
session_token: str,
|
|
250
|
+
frontend_token: str,
|
|
251
|
+
role: str,
|
|
252
|
+
is_pairing: bool = False
|
|
253
|
+
):
|
|
254
|
+
"""创建新的 Kernel 连接"""
|
|
255
|
+
# 生成 module_id
|
|
256
|
+
suffix = session_token.replace("sess_", "")
|
|
257
|
+
module_id = f"{self.base_module_id}-{suffix}"
|
|
258
|
+
|
|
259
|
+
try:
|
|
260
|
+
# 连接 Kernel
|
|
261
|
+
kernel_ws = await self._connect_kernel(module_id)
|
|
262
|
+
|
|
263
|
+
# 创建 session
|
|
264
|
+
session = SessionInfo(
|
|
265
|
+
session_token=session_token,
|
|
266
|
+
module_id=module_id,
|
|
267
|
+
kernel_ws=kernel_ws,
|
|
268
|
+
client_ws=client_ws,
|
|
269
|
+
frontend_token=frontend_token,
|
|
270
|
+
role=role,
|
|
271
|
+
)
|
|
272
|
+
self.sessions[session_token] = session
|
|
273
|
+
|
|
274
|
+
# 返回认证成功
|
|
275
|
+
await client_ws.send_json({
|
|
276
|
+
"type": "paired" if is_pairing else "authenticated",
|
|
277
|
+
"token": frontend_token,
|
|
278
|
+
"session_token": session_token,
|
|
279
|
+
"module_id": module_id,
|
|
280
|
+
"role": role
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
print(f"[relay] New connection: {module_id} (role: {role})")
|
|
284
|
+
|
|
285
|
+
# 如果是配对,发送配对成功事件给 Launcher
|
|
286
|
+
if is_pairing and self.web_server and self.web_server._ws:
|
|
287
|
+
try:
|
|
288
|
+
await self.web_server._publish_event({
|
|
289
|
+
"event": "pairing.status",
|
|
290
|
+
"data": {
|
|
291
|
+
"step": "completed",
|
|
292
|
+
"success": True,
|
|
293
|
+
"module_id": module_id,
|
|
294
|
+
"role": role
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
except Exception as e:
|
|
298
|
+
print(f"[relay] Failed to publish pairing success event: {e}")
|
|
299
|
+
|
|
300
|
+
# 开始双向转发
|
|
301
|
+
await self._relay_messages(session)
|
|
302
|
+
|
|
303
|
+
except Exception as e:
|
|
304
|
+
print(f"[relay] Failed to create connection: {e}")
|
|
305
|
+
await client_ws.send_json({
|
|
306
|
+
"type": "error",
|
|
307
|
+
"message": f"Failed to connect to Kernel: {str(e)}"
|
|
308
|
+
})
|
|
309
|
+
await client_ws.close(code=1011, reason="Kernel connection failed")
|
|
310
|
+
|
|
311
|
+
async def _handle_reconnect(
|
|
312
|
+
self,
|
|
313
|
+
client_ws: WebSocket,
|
|
314
|
+
session_token: str,
|
|
315
|
+
token_info: dict
|
|
316
|
+
):
|
|
317
|
+
"""处理重连"""
|
|
318
|
+
session = self.sessions[session_token]
|
|
319
|
+
|
|
320
|
+
# 取消超时计时器
|
|
321
|
+
if session_token in self.reconnect_timers:
|
|
322
|
+
self.reconnect_timers[session_token].cancel()
|
|
323
|
+
del self.reconnect_timers[session_token]
|
|
324
|
+
|
|
325
|
+
# 更新 client_ws
|
|
326
|
+
session.client_ws = client_ws
|
|
327
|
+
session.status = "active"
|
|
328
|
+
session.last_active = time.time()
|
|
329
|
+
|
|
330
|
+
# 返回重连成功
|
|
331
|
+
await client_ws.send_json({
|
|
332
|
+
"type": "reconnected",
|
|
333
|
+
"module_id": session.module_id,
|
|
334
|
+
"role": session.role
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
print(f"[relay] Reconnected: {session.module_id}")
|
|
338
|
+
|
|
339
|
+
# 继续双向转发
|
|
340
|
+
await self._relay_messages(session)
|
|
341
|
+
|
|
342
|
+
async def _connect_kernel(self, module_id: str):
|
|
343
|
+
"""连接到 Kernel"""
|
|
344
|
+
url = f"ws://{self.kernel_host}:{self.kernel_port}/ws?token={self.kernel_token}&id={module_id}"
|
|
345
|
+
kernel_ws = await websockets.connect(
|
|
346
|
+
url,
|
|
347
|
+
open_timeout=5,
|
|
348
|
+
ping_interval=20,
|
|
349
|
+
ping_timeout=20,
|
|
350
|
+
close_timeout=10
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
# 订阅事件
|
|
354
|
+
await self._send_to_kernel(kernel_ws, {
|
|
355
|
+
"jsonrpc": "2.0",
|
|
356
|
+
"id": str(uuid.uuid4()),
|
|
357
|
+
"method": "event.subscribe",
|
|
358
|
+
"params": {
|
|
359
|
+
"events": [
|
|
360
|
+
"module.started",
|
|
361
|
+
"module.stopped",
|
|
362
|
+
"module.crashed",
|
|
363
|
+
"module.ready",
|
|
364
|
+
]
|
|
365
|
+
}
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
# 注册模块
|
|
369
|
+
await self._send_to_kernel(kernel_ws, {
|
|
370
|
+
"jsonrpc": "2.0",
|
|
371
|
+
"id": str(uuid.uuid4()),
|
|
372
|
+
"method": "registry.register",
|
|
373
|
+
"params": {
|
|
374
|
+
"module_id": module_id,
|
|
375
|
+
"module_type": "web_client",
|
|
376
|
+
"events_subscribe": [
|
|
377
|
+
"module.started",
|
|
378
|
+
"module.stopped",
|
|
379
|
+
"module.crashed",
|
|
380
|
+
"module.ready",
|
|
381
|
+
]
|
|
382
|
+
}
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
# 发送 module.ready
|
|
386
|
+
await self._send_to_kernel(kernel_ws, {
|
|
387
|
+
"jsonrpc": "2.0",
|
|
388
|
+
"id": str(uuid.uuid4()),
|
|
389
|
+
"method": "event.publish",
|
|
390
|
+
"params": {
|
|
391
|
+
"event_id": str(uuid.uuid4()),
|
|
392
|
+
"event": "module.ready",
|
|
393
|
+
"data": {
|
|
394
|
+
"module_id": module_id,
|
|
395
|
+
"graceful_shutdown": True,
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
return kernel_ws
|
|
401
|
+
|
|
402
|
+
async def _relay_messages(self, session: SessionInfo):
|
|
403
|
+
"""双向转发消息"""
|
|
404
|
+
try:
|
|
405
|
+
# 创建两个任务:client → kernel, kernel → client
|
|
406
|
+
client_to_kernel = asyncio.create_task(
|
|
407
|
+
self._forward_client_to_kernel(session)
|
|
408
|
+
)
|
|
409
|
+
kernel_to_client = asyncio.create_task(
|
|
410
|
+
self._forward_kernel_to_client(session)
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
# 等待任一任务完成(断开)
|
|
414
|
+
done, pending = await asyncio.wait(
|
|
415
|
+
[client_to_kernel, kernel_to_client],
|
|
416
|
+
return_when=asyncio.FIRST_COMPLETED
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
# 取消未完成的任务
|
|
420
|
+
for task in pending:
|
|
421
|
+
task.cancel()
|
|
422
|
+
try:
|
|
423
|
+
await task
|
|
424
|
+
except asyncio.CancelledError:
|
|
425
|
+
pass
|
|
426
|
+
|
|
427
|
+
# 检查已完成任务的异常
|
|
428
|
+
for task in done:
|
|
429
|
+
try:
|
|
430
|
+
task.result()
|
|
431
|
+
except Exception:
|
|
432
|
+
pass # 忽略异常,因为断开连接是正常的
|
|
433
|
+
|
|
434
|
+
except Exception as e:
|
|
435
|
+
print(f"[relay] Relay error: {e}")
|
|
436
|
+
|
|
437
|
+
finally:
|
|
438
|
+
# 客户端断开,启动重连等待
|
|
439
|
+
await self._on_client_disconnect(session)
|
|
440
|
+
|
|
441
|
+
async def _forward_client_to_kernel(self, session: SessionInfo):
|
|
442
|
+
"""转发客户端消息到 Kernel(带权限检查)"""
|
|
443
|
+
while True:
|
|
444
|
+
raw = await session.client_ws.receive_text()
|
|
445
|
+
msg = json.loads(raw)
|
|
446
|
+
|
|
447
|
+
# 处理心跳 ping
|
|
448
|
+
if msg.get("type") == "ping":
|
|
449
|
+
await session.client_ws.send_json({"type": "pong"})
|
|
450
|
+
continue
|
|
451
|
+
|
|
452
|
+
# 检查权限
|
|
453
|
+
if not self._check_permission(session.role, msg):
|
|
454
|
+
method = msg.get("method", "")
|
|
455
|
+
# 红色高亮显示权限错误
|
|
456
|
+
print(f"\033[91m[relay] ✗ 权限被拒绝: {method} (角色: {session.role})\033[0m")
|
|
457
|
+
# 返回权限错误
|
|
458
|
+
if "id" in msg:
|
|
459
|
+
await session.client_ws.send_json({
|
|
460
|
+
"jsonrpc": "2.0",
|
|
461
|
+
"id": msg["id"],
|
|
462
|
+
"error": {
|
|
463
|
+
"code": -32000,
|
|
464
|
+
"message": f"Permission denied: {method} (role: {session.role})"
|
|
465
|
+
}
|
|
466
|
+
})
|
|
467
|
+
continue
|
|
468
|
+
|
|
469
|
+
# 检查是否是 web.* RPC 调用
|
|
470
|
+
method = msg.get("method", "")
|
|
471
|
+
if method.startswith("web.") and "id" in msg:
|
|
472
|
+
print(f"[relay] Intercepted web RPC: {method}")
|
|
473
|
+
# 拦截并处理 web.* RPC 调用
|
|
474
|
+
await self._handle_web_rpc(session, msg)
|
|
475
|
+
continue
|
|
476
|
+
|
|
477
|
+
# 转发到 Kernel
|
|
478
|
+
await self._send_to_kernel(session.kernel_ws, msg)
|
|
479
|
+
|
|
480
|
+
async def _forward_kernel_to_client(self, session: SessionInfo):
|
|
481
|
+
"""转发 Kernel 消息到客户端"""
|
|
482
|
+
async for raw in session.kernel_ws:
|
|
483
|
+
await session.client_ws.send_text(raw)
|
|
484
|
+
|
|
485
|
+
def _check_permission(self, role: str, msg: dict) -> bool:
|
|
486
|
+
"""检查 RPC 权限"""
|
|
487
|
+
method = msg.get("method")
|
|
488
|
+
if not method:
|
|
489
|
+
return True # 非 RPC 请求,放行
|
|
490
|
+
|
|
491
|
+
# 获取角色的权限列表
|
|
492
|
+
allowed = self.permissions.get(role, [])
|
|
493
|
+
|
|
494
|
+
# 检查是否匹配
|
|
495
|
+
for pattern in allowed:
|
|
496
|
+
if pattern.endswith(".*"):
|
|
497
|
+
# 通配符匹配
|
|
498
|
+
prefix = pattern[:-2]
|
|
499
|
+
if method.startswith(prefix + "."):
|
|
500
|
+
return True
|
|
501
|
+
elif pattern == method:
|
|
502
|
+
# 精确匹配
|
|
503
|
+
return True
|
|
504
|
+
|
|
505
|
+
return False
|
|
506
|
+
|
|
507
|
+
async def _on_client_disconnect(self, session: SessionInfo):
|
|
508
|
+
"""客户端断开,启动重连等待"""
|
|
509
|
+
session.status = "waiting_reconnect"
|
|
510
|
+
session.client_ws = None
|
|
511
|
+
|
|
512
|
+
print(f"[relay] Client disconnected: {session.module_id}, waiting {self.reconnect_timeout}s for reconnect")
|
|
513
|
+
|
|
514
|
+
# 启动超时计时器
|
|
515
|
+
timer = asyncio.create_task(self._reconnect_timeout(session))
|
|
516
|
+
self.reconnect_timers[session.session_token] = timer
|
|
517
|
+
|
|
518
|
+
async def _reconnect_timeout(self, session: SessionInfo):
|
|
519
|
+
"""重连超时,执行优雅退出"""
|
|
520
|
+
await asyncio.sleep(self.reconnect_timeout)
|
|
521
|
+
|
|
522
|
+
# 超时后仍未重连,执行优雅退出
|
|
523
|
+
if session.status == "waiting_reconnect":
|
|
524
|
+
await self._graceful_shutdown(session)
|
|
525
|
+
|
|
526
|
+
async def _graceful_shutdown(self, session: SessionInfo):
|
|
527
|
+
"""代表前端执行优雅退出"""
|
|
528
|
+
print(f"[relay] Graceful shutdown: {session.module_id}")
|
|
529
|
+
|
|
530
|
+
try:
|
|
531
|
+
# 发送 module.exiting 事件
|
|
532
|
+
await self._send_to_kernel(session.kernel_ws, {
|
|
533
|
+
"jsonrpc": "2.0",
|
|
534
|
+
"id": str(uuid.uuid4()),
|
|
535
|
+
"method": "event.publish",
|
|
536
|
+
"params": {
|
|
537
|
+
"event_id": str(uuid.uuid4()),
|
|
538
|
+
"event": "module.exiting",
|
|
539
|
+
"data": {
|
|
540
|
+
"module_id": session.module_id,
|
|
541
|
+
"action": "none"
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
# 发送 module.shutdown.ready 事件
|
|
547
|
+
await self._send_to_kernel(session.kernel_ws, {
|
|
548
|
+
"jsonrpc": "2.0",
|
|
549
|
+
"id": str(uuid.uuid4()),
|
|
550
|
+
"method": "event.publish",
|
|
551
|
+
"params": {
|
|
552
|
+
"event_id": str(uuid.uuid4()),
|
|
553
|
+
"event": "module.shutdown.ready",
|
|
554
|
+
"data": {
|
|
555
|
+
"module_id": session.module_id
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
})
|
|
559
|
+
|
|
560
|
+
# 断开 Kernel 连接
|
|
561
|
+
await session.kernel_ws.close()
|
|
562
|
+
|
|
563
|
+
except Exception as e:
|
|
564
|
+
print(f"[relay] Graceful shutdown error: {e}")
|
|
565
|
+
|
|
566
|
+
finally:
|
|
567
|
+
# 删除 session
|
|
568
|
+
if session.session_token in self.sessions:
|
|
569
|
+
del self.sessions[session.session_token]
|
|
570
|
+
if session.session_token in self.reconnect_timers:
|
|
571
|
+
del self.reconnect_timers[session.session_token]
|
|
572
|
+
|
|
573
|
+
async def _send_to_kernel(self, kernel_ws, msg: dict):
|
|
574
|
+
"""发送消息到 Kernel"""
|
|
575
|
+
await kernel_ws.send(json.dumps(msg))
|
|
576
|
+
|
|
577
|
+
async def _handle_web_rpc(self, session: SessionInfo, msg: dict):
|
|
578
|
+
"""处理 web.* RPC 调用"""
|
|
579
|
+
rpc_id = msg.get("id")
|
|
580
|
+
method = msg.get("method", "")
|
|
581
|
+
params = msg.get("params", {})
|
|
582
|
+
|
|
583
|
+
print(f"[relay] Handling web RPC: {method} (id={rpc_id})")
|
|
584
|
+
|
|
585
|
+
# 去掉 web. 前缀
|
|
586
|
+
if method.startswith("web."):
|
|
587
|
+
method = method[4:]
|
|
588
|
+
|
|
589
|
+
try:
|
|
590
|
+
# 调用 web_server 的 RPC 处理器
|
|
591
|
+
if method == "list_tokens":
|
|
592
|
+
print(f"[relay] Calling list_tokens")
|
|
593
|
+
result = await self.web_server._rpc_list_tokens()
|
|
594
|
+
elif method == "revoke_token":
|
|
595
|
+
print(f"[relay] Calling revoke_token with params: {params}")
|
|
596
|
+
result = await self.web_server._rpc_revoke_token(params)
|
|
597
|
+
else:
|
|
598
|
+
raise ValueError(f"Unknown method: web.{method}")
|
|
599
|
+
|
|
600
|
+
print(f"[relay] RPC success: {method}, result keys: {list(result.keys()) if isinstance(result, dict) else type(result)}")
|
|
601
|
+
|
|
602
|
+
# 返回结果
|
|
603
|
+
await session.client_ws.send_json({
|
|
604
|
+
"jsonrpc": "2.0",
|
|
605
|
+
"id": rpc_id,
|
|
606
|
+
"result": result
|
|
607
|
+
})
|
|
608
|
+
except Exception as e:
|
|
609
|
+
# 红色高亮显示 RPC 错误
|
|
610
|
+
print(f"\033[91m[relay] ✗ RPC 错误: {method}, 错误: {e}\033[0m")
|
|
611
|
+
# 返回错误
|
|
612
|
+
await session.client_ws.send_json({
|
|
613
|
+
"jsonrpc": "2.0",
|
|
614
|
+
"id": rpc_id,
|
|
615
|
+
"error": {
|
|
616
|
+
"code": -32603,
|
|
617
|
+
"message": str(e)
|
|
618
|
+
}
|
|
619
|
+
})
|
|
620
|
+
|
|
621
|
+
async def close_all_sessions(self):
|
|
622
|
+
"""优雅关闭所有会话(用于 shutdown)"""
|
|
623
|
+
print(f"[relay] Closing {len(self.sessions)} active sessions...")
|
|
624
|
+
|
|
625
|
+
# 取消所有重连定时器
|
|
626
|
+
for timer in self.reconnect_timers.values():
|
|
627
|
+
timer.cancel()
|
|
628
|
+
self.reconnect_timers.clear()
|
|
629
|
+
|
|
630
|
+
# 关闭所有会话
|
|
631
|
+
for session in list(self.sessions.values()):
|
|
632
|
+
try:
|
|
633
|
+
# 关闭客户端连接
|
|
634
|
+
if session.client_ws:
|
|
635
|
+
await session.client_ws.close(code=1001, reason="Server shutting down")
|
|
636
|
+
# 关闭 Kernel 连接
|
|
637
|
+
if session.kernel_ws:
|
|
638
|
+
await session.kernel_ws.close()
|
|
639
|
+
except Exception as e:
|
|
640
|
+
print(f"[relay] Error closing session {session.session_token}: {e}")
|
|
641
|
+
|
|
642
|
+
self.sessions.clear()
|
|
643
|
+
print(f"[relay] All sessions closed")
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
// 中转服务配置
|
|
3
|
+
relay: {
|
|
4
|
+
// 基础模块 ID(实际 module_id 会加上 session 后缀)
|
|
5
|
+
base_module_id: "web-client",
|
|
6
|
+
|
|
7
|
+
// 重连超时时间(秒)
|
|
8
|
+
// 前端刷新页面或短暂断网时,中转服务保持 Kernel 连接的时长
|
|
9
|
+
reconnect_timeout: 6,
|
|
10
|
+
|
|
11
|
+
// WebSocket 端点路径
|
|
12
|
+
endpoint: "/ws/relay"
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
// 认证配置
|
|
16
|
+
auth: {
|
|
17
|
+
// frontend_token 有效期(秒,30天)
|
|
18
|
+
token_expiry: 2592000,
|
|
19
|
+
|
|
20
|
+
// token 续期阈值(秒,7天)
|
|
21
|
+
// 当 token 剩余有效期小于此值时,自动续期
|
|
22
|
+
token_renew_threshold: 604800,
|
|
23
|
+
|
|
24
|
+
// 配对码长度
|
|
25
|
+
pairing_code_length: 6,
|
|
26
|
+
|
|
27
|
+
// 配对码文件路径(相对于模块数据目录)
|
|
28
|
+
pairing_code_file: "pairing_codes.jsonl"
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
// 权限配置(基于角色的 RPC 白名单)
|
|
32
|
+
permissions: {
|
|
33
|
+
// 管理员角色 - 完全权限
|
|
34
|
+
admin: [
|
|
35
|
+
"event.*", // 事件订阅(必需)
|
|
36
|
+
"registry.*", // 注册表查询(必需)
|
|
37
|
+
"launcher.*", // 所有 launcher RPC
|
|
38
|
+
"kernel.health", // Kernel 健康检查
|
|
39
|
+
"kernel.stats", // Kernel 统计信息
|
|
40
|
+
"*.health", // 所有模块的健康检查
|
|
41
|
+
"*.status", // 所有模块的状态查询
|
|
42
|
+
"watchdog.*", // Watchdog 相关
|
|
43
|
+
"backup.*", // Backup 相关
|
|
44
|
+
"model_service.*", // Model Service 相关
|
|
45
|
+
"web.*" // Web 管理相关(token 管理等)
|
|
46
|
+
],
|
|
47
|
+
|
|
48
|
+
// 操作员角色 - 模块管理权限
|
|
49
|
+
operator: [
|
|
50
|
+
"event.*", // 事件订阅(必需)
|
|
51
|
+
"launcher.list_modules",
|
|
52
|
+
"launcher.start_module",
|
|
53
|
+
"launcher.stop_module",
|
|
54
|
+
"launcher.restart_module",
|
|
55
|
+
"*.health",
|
|
56
|
+
"*.status"
|
|
57
|
+
],
|
|
58
|
+
|
|
59
|
+
// 查看者角色 - 只读权限
|
|
60
|
+
viewer: [
|
|
61
|
+
"event.*", // 事件订阅(必需)
|
|
62
|
+
"launcher.list_modules",
|
|
63
|
+
"*.health",
|
|
64
|
+
"*.status"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
}
|