@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,875 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Evol HTTP Server
|
|
3
|
+
Evol account management with full Kite module management UI.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import time
|
|
11
|
+
import uuid
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
import websockets
|
|
16
|
+
from fastapi import FastAPI, WebSocket, Request
|
|
17
|
+
from fastapi.staticfiles import StaticFiles
|
|
18
|
+
from fastapi.responses import FileResponse, JSONResponse
|
|
19
|
+
|
|
20
|
+
from extensions.services.evol.evol_api import EvolAPI
|
|
21
|
+
from extensions.services.evol.auth_manager import AuthManager
|
|
22
|
+
from extensions.services.evol.stats_manager import StatsManager
|
|
23
|
+
from extensions.services.evol.routes.routes_rpc import router as rpc_router, set_evol_server
|
|
24
|
+
from extensions.services.evol.routes.routes_management_ws import router as management_ws_router, broadcast_event
|
|
25
|
+
from extensions.services.evol.routes.routes_test import router as test_router
|
|
26
|
+
from extensions.services.evol.config_loader import load_business_configs
|
|
27
|
+
from extensions.services.evol.pairing import PairingManager
|
|
28
|
+
from extensions.services.evol.relay import KernelRelay
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _fmt_elapsed(t0: float) -> str:
|
|
32
|
+
"""Format elapsed time since t0."""
|
|
33
|
+
d = time.monotonic() - t0
|
|
34
|
+
if d < 1:
|
|
35
|
+
return f"{d * 1000:.0f}ms"
|
|
36
|
+
if d < 10:
|
|
37
|
+
return f"{d:.1f}s"
|
|
38
|
+
return f"{d:.0f}s"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
logger = logging.getLogger(__name__)
|
|
42
|
+
|
|
43
|
+
# System broadcast events
|
|
44
|
+
SYSTEM_BROADCAST_EVENTS = {
|
|
45
|
+
"module.ready", "module.registered", "module.started", "module.stopped",
|
|
46
|
+
"module.crashed", "module.exiting", "module.offline",
|
|
47
|
+
"module.shutdown.ack", "module.shutdown.ready",
|
|
48
|
+
"system.ready", "registry.updated",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class EvolServer:
|
|
53
|
+
def __init__(self, token: str, kernel_port: int, host: str, port: int, boot_t0: float):
|
|
54
|
+
self.token = token
|
|
55
|
+
self.kernel_port = kernel_port
|
|
56
|
+
self.host = host
|
|
57
|
+
self.port = port
|
|
58
|
+
self.boot_t0 = boot_t0
|
|
59
|
+
self._ws_task: asyncio.Task | None = None
|
|
60
|
+
self._test_task: asyncio.Task | None = None
|
|
61
|
+
self._ws: object | None = None
|
|
62
|
+
self._shutting_down = False
|
|
63
|
+
self._exit_code = 0
|
|
64
|
+
self._uvicorn_server = None
|
|
65
|
+
self._start_time = time.time()
|
|
66
|
+
self._rpc_futures = {}
|
|
67
|
+
|
|
68
|
+
# 用户信息缓存(10秒有效期)
|
|
69
|
+
self._user_info_cache = {} # {evol_token: {"data": ..., "timestamp": ...}}
|
|
70
|
+
self._cache_ttl = 10 # 缓存有效期(秒)
|
|
71
|
+
|
|
72
|
+
# Evol business managers
|
|
73
|
+
data_dir = os.environ.get("KITE_DATA", os.path.expanduser("~/.kite/data"))
|
|
74
|
+
evol_data_dir = os.path.join(data_dir, "evol")
|
|
75
|
+
os.makedirs(evol_data_dir, exist_ok=True)
|
|
76
|
+
|
|
77
|
+
self.evol_api = EvolAPI()
|
|
78
|
+
self.auth_manager = AuthManager(evol_data_dir)
|
|
79
|
+
self.stats_manager = StatsManager(evol_data_dir, self.evol_api, self.auth_manager)
|
|
80
|
+
|
|
81
|
+
self.app = self._create_app()
|
|
82
|
+
|
|
83
|
+
def _create_app(self) -> FastAPI:
|
|
84
|
+
app = FastAPI(title="Kite Evol Module", docs_url="/docs", redoc_url=None)
|
|
85
|
+
server = self
|
|
86
|
+
|
|
87
|
+
@app.on_event("startup")
|
|
88
|
+
async def _startup():
|
|
89
|
+
# Start stats collection
|
|
90
|
+
await server.stats_manager.start()
|
|
91
|
+
|
|
92
|
+
# Load business configurations
|
|
93
|
+
module_dir = Path(__file__).parent
|
|
94
|
+
try:
|
|
95
|
+
business_configs = load_business_configs(str(module_dir))
|
|
96
|
+
except Exception as e:
|
|
97
|
+
logger.error(f"Failed to load business configs: {e}")
|
|
98
|
+
business_configs = {}
|
|
99
|
+
|
|
100
|
+
# Get relay service config
|
|
101
|
+
relay_business = business_configs.get('relay_service')
|
|
102
|
+
if relay_business:
|
|
103
|
+
try:
|
|
104
|
+
relay_config = relay_business['config']
|
|
105
|
+
|
|
106
|
+
# Initialize pairing manager
|
|
107
|
+
auth_config = relay_config['auth']
|
|
108
|
+
pairing_file = module_dir / auth_config['pairing_code_file']
|
|
109
|
+
pairing_manager = PairingManager(
|
|
110
|
+
pairing_file=str(pairing_file),
|
|
111
|
+
code_length=auth_config['pairing_code_length'],
|
|
112
|
+
token_expiry=auth_config['token_expiry']
|
|
113
|
+
)
|
|
114
|
+
app.state.pairing_manager = pairing_manager
|
|
115
|
+
logger.info("Pairing manager initialized")
|
|
116
|
+
|
|
117
|
+
# Initialize relay service
|
|
118
|
+
relay_service = KernelRelay(
|
|
119
|
+
kernel_host="127.0.0.1",
|
|
120
|
+
kernel_port=server.kernel_port,
|
|
121
|
+
kernel_token=server.token,
|
|
122
|
+
base_module_id=relay_config['relay']['base_module_id'],
|
|
123
|
+
reconnect_timeout=relay_config['relay']['reconnect_timeout'],
|
|
124
|
+
permissions=relay_config['permissions'],
|
|
125
|
+
pairing_manager=pairing_manager,
|
|
126
|
+
evol_server=server
|
|
127
|
+
)
|
|
128
|
+
app.state.relay_service = relay_service
|
|
129
|
+
logger.info("Relay service initialized")
|
|
130
|
+
except KeyError as e:
|
|
131
|
+
logger.error(f"Missing required config field for relay_service: {e}")
|
|
132
|
+
logger.warning("Relay service disabled due to config error")
|
|
133
|
+
except Exception as e:
|
|
134
|
+
logger.error(f"Failed to initialize relay_service: {e}", exc_info=True)
|
|
135
|
+
logger.warning("Relay service disabled due to initialization error")
|
|
136
|
+
else:
|
|
137
|
+
logger.info("Relay service not configured (no 'relay_service' in businesses)")
|
|
138
|
+
|
|
139
|
+
# Start background tasks
|
|
140
|
+
if server.kernel_port:
|
|
141
|
+
server._ws_task = asyncio.create_task(server._ws_loop())
|
|
142
|
+
server._test_task = asyncio.create_task(server._test_event_loop())
|
|
143
|
+
|
|
144
|
+
@app.on_event("shutdown")
|
|
145
|
+
async def _shutdown():
|
|
146
|
+
await server.stats_manager.stop()
|
|
147
|
+
if server._ws_task:
|
|
148
|
+
server._ws_task.cancel()
|
|
149
|
+
if server._test_task:
|
|
150
|
+
server._test_task.cancel()
|
|
151
|
+
if server._ws:
|
|
152
|
+
await server._ws.close()
|
|
153
|
+
print("[evol] Shutdown complete")
|
|
154
|
+
|
|
155
|
+
# Health and status endpoints
|
|
156
|
+
@app.get("/health")
|
|
157
|
+
async def health():
|
|
158
|
+
return {
|
|
159
|
+
"status": "healthy",
|
|
160
|
+
"details": {
|
|
161
|
+
"kernel_connected": server._ws is not None,
|
|
162
|
+
"uptime_seconds": round(time.time() - server._start_time),
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@app.get("/status")
|
|
167
|
+
async def status():
|
|
168
|
+
return {
|
|
169
|
+
"module": "evol",
|
|
170
|
+
"status": "running",
|
|
171
|
+
"kernel_connected": server._ws is not None,
|
|
172
|
+
"uptime_seconds": round(time.time() - server._start_time),
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
# Evol API routes
|
|
176
|
+
@app.post("/api/send_sms")
|
|
177
|
+
async def send_sms(request: Request):
|
|
178
|
+
data = await request.json()
|
|
179
|
+
phone = data.get("phone", "")
|
|
180
|
+
result = await server.evol_api.send_sms(phone)
|
|
181
|
+
return JSONResponse(result)
|
|
182
|
+
|
|
183
|
+
@app.post("/api/verify_sms")
|
|
184
|
+
async def verify_sms(request: Request):
|
|
185
|
+
data = await request.json()
|
|
186
|
+
phone = data.get("phone", "")
|
|
187
|
+
code = data.get("code", "")
|
|
188
|
+
device_info = data.get("deviceInfo", {})
|
|
189
|
+
|
|
190
|
+
result = await server.evol_api.verify_sms(phone, code)
|
|
191
|
+
if not result.get("success"):
|
|
192
|
+
return JSONResponse(result)
|
|
193
|
+
|
|
194
|
+
evol_data = result["data"]
|
|
195
|
+
evol_token = evol_data.get("token", "")
|
|
196
|
+
server.auth_manager.save_evol_token(phone, evol_token, evol_data)
|
|
197
|
+
|
|
198
|
+
# 生成 Kite Token
|
|
199
|
+
kite_token = server.auth_manager.generate_kite_token(device_info)
|
|
200
|
+
|
|
201
|
+
# 绑定 Kite Token 到手机号
|
|
202
|
+
server.auth_manager.bind_kite_token_to_phone(kite_token, phone)
|
|
203
|
+
|
|
204
|
+
return JSONResponse({
|
|
205
|
+
"success": True,
|
|
206
|
+
"kiteToken": kite_token,
|
|
207
|
+
"data": {
|
|
208
|
+
"userInfo": evol_data.get("userInfo", {}),
|
|
209
|
+
"apiKey": evol_data.get("apiKey", ""),
|
|
210
|
+
"credits": evol_data.get("credits", 0)
|
|
211
|
+
}
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
@app.post("/api/get_user_info")
|
|
215
|
+
async def get_user_info(request: Request):
|
|
216
|
+
data = await request.json()
|
|
217
|
+
kite_token = data.get("kiteToken", "")
|
|
218
|
+
|
|
219
|
+
if not server.auth_manager.verify_kite_token(kite_token):
|
|
220
|
+
return JSONResponse({
|
|
221
|
+
"success": False,
|
|
222
|
+
"msg": "Kite Token 无效或已过期",
|
|
223
|
+
"code": "INVALID_TOKEN"
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
# 根据 Kite Token 获取绑定的手机号
|
|
227
|
+
phone = server.auth_manager.get_phone_by_kite_token(kite_token)
|
|
228
|
+
if not phone:
|
|
229
|
+
return JSONResponse({
|
|
230
|
+
"success": False,
|
|
231
|
+
"msg": "未登录 Evol,请先登录",
|
|
232
|
+
"code": "NOT_LOGGED_IN"
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
# 根据手机号获取 Evol Token
|
|
236
|
+
evol_record = server.auth_manager.get_evol_token(phone)
|
|
237
|
+
if not evol_record:
|
|
238
|
+
return JSONResponse({
|
|
239
|
+
"success": False,
|
|
240
|
+
"msg": "Evol Token 已过期,请重新登录",
|
|
241
|
+
"code": "EVOL_TOKEN_EXPIRED"
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
# 更新 Evol Token 使用时间(超过 1 天才记录)
|
|
245
|
+
server.auth_manager.update_evol_token_usage(phone)
|
|
246
|
+
|
|
247
|
+
evol_token = evol_record["token"]
|
|
248
|
+
|
|
249
|
+
# 检查缓存
|
|
250
|
+
now = time.time()
|
|
251
|
+
if evol_token in server._user_info_cache:
|
|
252
|
+
cached = server._user_info_cache[evol_token]
|
|
253
|
+
if now - cached["timestamp"] < server._cache_ttl:
|
|
254
|
+
logger.info(f"Using cached user info (age: {now - cached['timestamp']:.1f}s)")
|
|
255
|
+
return JSONResponse(cached["data"])
|
|
256
|
+
|
|
257
|
+
# 缓存未命中或已过期,从云端获取
|
|
258
|
+
result = await server.evol_api.get_user_info(evol_token)
|
|
259
|
+
if not result.get("success"):
|
|
260
|
+
return JSONResponse(result)
|
|
261
|
+
|
|
262
|
+
# 手动触发账户信息采集
|
|
263
|
+
await server.stats_manager.collect_manual()
|
|
264
|
+
|
|
265
|
+
# 统一返回格式,与 verify_sms 保持一致
|
|
266
|
+
user_data = result["data"]
|
|
267
|
+
response_data = {
|
|
268
|
+
"success": True,
|
|
269
|
+
"data": user_data # 返回完整数据
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
# 更新缓存
|
|
273
|
+
server._user_info_cache[evol_token] = {
|
|
274
|
+
"data": response_data,
|
|
275
|
+
"timestamp": now
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return JSONResponse(response_data)
|
|
279
|
+
|
|
280
|
+
@app.post("/api/logout")
|
|
281
|
+
async def logout(request: Request):
|
|
282
|
+
data = await request.json()
|
|
283
|
+
kite_token = data.get("kiteToken", "")
|
|
284
|
+
|
|
285
|
+
if not server.auth_manager.verify_kite_token(kite_token):
|
|
286
|
+
return JSONResponse({"success": False, "msg": "Kite Token 无效"})
|
|
287
|
+
|
|
288
|
+
# 获取绑定的手机号
|
|
289
|
+
phone = server.auth_manager.get_phone_by_kite_token(kite_token)
|
|
290
|
+
|
|
291
|
+
# 吊销 Evol Token(如果已绑定手机号)
|
|
292
|
+
if phone:
|
|
293
|
+
server.auth_manager.revoke_evol_token(phone)
|
|
294
|
+
|
|
295
|
+
# 吊销 Kite Token(解除绑定)
|
|
296
|
+
server.auth_manager.revoke_kite_token(kite_token)
|
|
297
|
+
|
|
298
|
+
return JSONResponse({"success": True, "msg": "已退出登录"})
|
|
299
|
+
|
|
300
|
+
@app.post("/api/get_credits_stats")
|
|
301
|
+
async def get_credits_stats(request: Request):
|
|
302
|
+
data = await request.json()
|
|
303
|
+
kite_token = data.get("kiteToken", "")
|
|
304
|
+
period = data.get("period", "day")
|
|
305
|
+
date = data.get("date")
|
|
306
|
+
|
|
307
|
+
if not server.auth_manager.verify_kite_token(kite_token):
|
|
308
|
+
return JSONResponse({
|
|
309
|
+
"success": False,
|
|
310
|
+
"msg": "Kite Token 无效或已过期",
|
|
311
|
+
"code": "INVALID_TOKEN"
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
result = server.stats_manager.get_stats(period, date)
|
|
315
|
+
return JSONResponse(result)
|
|
316
|
+
|
|
317
|
+
# Mount module management routes
|
|
318
|
+
app.include_router(rpc_router, prefix="/api")
|
|
319
|
+
app.include_router(test_router, prefix="/api")
|
|
320
|
+
app.include_router(management_ws_router) # /ws/management
|
|
321
|
+
|
|
322
|
+
# Relay WebSocket endpoint
|
|
323
|
+
@app.websocket("/ws/relay")
|
|
324
|
+
async def relay_endpoint(ws: WebSocket):
|
|
325
|
+
relay_service = getattr(app.state, 'relay_service', None)
|
|
326
|
+
if relay_service:
|
|
327
|
+
await relay_service.handle_client(ws)
|
|
328
|
+
else:
|
|
329
|
+
await ws.close(code=1011, reason="Relay service not initialized")
|
|
330
|
+
|
|
331
|
+
# Set evol server reference for RPC forwarding
|
|
332
|
+
set_evol_server(server)
|
|
333
|
+
|
|
334
|
+
# Serve frontend static files
|
|
335
|
+
static_dir = Path(__file__).parent / "static"
|
|
336
|
+
if static_dir.exists():
|
|
337
|
+
@app.get("/")
|
|
338
|
+
async def serve_index():
|
|
339
|
+
index_path = static_dir / "index.html"
|
|
340
|
+
if index_path.exists():
|
|
341
|
+
return FileResponse(index_path)
|
|
342
|
+
return {"message": "Kite Evol Module"}
|
|
343
|
+
|
|
344
|
+
@app.get("/pairing.html")
|
|
345
|
+
async def serve_pairing():
|
|
346
|
+
pairing_path = static_dir / "pairing.html"
|
|
347
|
+
if pairing_path.exists():
|
|
348
|
+
return FileResponse(pairing_path)
|
|
349
|
+
return JSONResponse({"error": "Not found"}, status_code=404)
|
|
350
|
+
|
|
351
|
+
@app.get("/test_registry.html")
|
|
352
|
+
async def serve_test_registry():
|
|
353
|
+
test_path = static_dir / "test_registry.html"
|
|
354
|
+
if test_path.exists():
|
|
355
|
+
return FileResponse(test_path)
|
|
356
|
+
return JSONResponse({"error": "Not found"}, status_code=404)
|
|
357
|
+
|
|
358
|
+
@app.get("/test_relay.html")
|
|
359
|
+
async def serve_test_relay():
|
|
360
|
+
test_path = static_dir / "test_relay.html"
|
|
361
|
+
if test_path.exists():
|
|
362
|
+
return FileResponse(test_path)
|
|
363
|
+
return JSONResponse({"error": "Not found"}, status_code=404)
|
|
364
|
+
|
|
365
|
+
app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
|
|
366
|
+
|
|
367
|
+
@app.get("/js/{file_path:path}")
|
|
368
|
+
async def serve_js(file_path: str):
|
|
369
|
+
file = static_dir / "js" / file_path
|
|
370
|
+
if file.exists() and file.is_file():
|
|
371
|
+
return FileResponse(file)
|
|
372
|
+
return JSONResponse({"error": "Not found"}, status_code=404)
|
|
373
|
+
|
|
374
|
+
@app.get("/css/{file_path:path}")
|
|
375
|
+
async def serve_css(file_path: str):
|
|
376
|
+
file = static_dir / "css" / file_path
|
|
377
|
+
if file.exists() and file.is_file():
|
|
378
|
+
return FileResponse(file)
|
|
379
|
+
return JSONResponse({"error": "Not found"}, status_code=404)
|
|
380
|
+
|
|
381
|
+
return app
|
|
382
|
+
|
|
383
|
+
# ── Kernel WebSocket client ──
|
|
384
|
+
|
|
385
|
+
async def _ws_loop(self):
|
|
386
|
+
retry_delay = 0.3
|
|
387
|
+
max_delay = 5.0
|
|
388
|
+
max_retries = 10
|
|
389
|
+
attempt = 0
|
|
390
|
+
while not self._shutting_down:
|
|
391
|
+
try:
|
|
392
|
+
await self._ws_connect()
|
|
393
|
+
retry_delay = 0.3
|
|
394
|
+
attempt = 0
|
|
395
|
+
except asyncio.CancelledError:
|
|
396
|
+
print(f"[evol] WS loop cancelled")
|
|
397
|
+
return
|
|
398
|
+
except Exception as e:
|
|
399
|
+
attempt += 1
|
|
400
|
+
if hasattr(e, 'rcvd') and e.rcvd is not None:
|
|
401
|
+
code = e.rcvd.code if hasattr(e.rcvd, 'code') else 0
|
|
402
|
+
if code in (4001, 4003):
|
|
403
|
+
print(f"[evol] Kernel 认证失败 (code {code}),退出")
|
|
404
|
+
self._exit_code = 1
|
|
405
|
+
self._shutting_down = True
|
|
406
|
+
if self._uvicorn_server:
|
|
407
|
+
self._uvicorn_server.should_exit = True
|
|
408
|
+
return
|
|
409
|
+
if attempt >= max_retries:
|
|
410
|
+
print(f"[evol] Kernel 重连失败 {max_retries} 次,退出")
|
|
411
|
+
self._exit_code = 1
|
|
412
|
+
self._shutting_down = True
|
|
413
|
+
if self._uvicorn_server:
|
|
414
|
+
self._uvicorn_server.should_exit = True
|
|
415
|
+
return
|
|
416
|
+
if self._shutting_down:
|
|
417
|
+
return
|
|
418
|
+
print(f"[evol] Kernel connection error: {e}, retrying in {retry_delay:.1f}s ({attempt}/{max_retries})")
|
|
419
|
+
self._ws = None
|
|
420
|
+
if self._shutting_down:
|
|
421
|
+
return
|
|
422
|
+
await asyncio.sleep(retry_delay)
|
|
423
|
+
retry_delay = min(retry_delay * 2, max_delay)
|
|
424
|
+
|
|
425
|
+
async def _ws_receiver(self, ws):
|
|
426
|
+
"""WebSocket 接收循环(后台任务)"""
|
|
427
|
+
try:
|
|
428
|
+
async for raw in ws:
|
|
429
|
+
try:
|
|
430
|
+
msg = json.loads(raw)
|
|
431
|
+
except (json.JSONDecodeError, TypeError):
|
|
432
|
+
continue
|
|
433
|
+
|
|
434
|
+
try:
|
|
435
|
+
has_method = "method" in msg
|
|
436
|
+
has_id = "id" in msg
|
|
437
|
+
has_result_or_error = "result" in msg or "error" in msg
|
|
438
|
+
|
|
439
|
+
if has_method and not has_id:
|
|
440
|
+
await self._handle_event_notification(msg)
|
|
441
|
+
elif has_method and has_id:
|
|
442
|
+
asyncio.create_task(self._handle_rpc_request(ws, msg))
|
|
443
|
+
elif has_id and has_result_or_error:
|
|
444
|
+
rpc_id = msg.get("id")
|
|
445
|
+
print(f"[evol] DEBUG: Received RPC response for id={rpc_id}, pending={rpc_id in self._rpc_futures}")
|
|
446
|
+
if rpc_id in self._rpc_futures:
|
|
447
|
+
self._rpc_futures[rpc_id].set_result(msg)
|
|
448
|
+
except Exception as e:
|
|
449
|
+
print(f"[evol] 消息处理异常(已忽略): {e}")
|
|
450
|
+
except Exception as e:
|
|
451
|
+
print(f"[evol] Receive loop exited with exception: {e}")
|
|
452
|
+
finally:
|
|
453
|
+
print(f"[evol] Receive loop ended")
|
|
454
|
+
|
|
455
|
+
async def _ws_connect(self):
|
|
456
|
+
url = f"ws://127.0.0.1:{self.kernel_port}/ws?token={self.token}&id=evol"
|
|
457
|
+
print(f"[evol] WS connecting to Kernel")
|
|
458
|
+
try:
|
|
459
|
+
async with websockets.connect(url, open_timeout=5, ping_interval=None, close_timeout=10) as ws:
|
|
460
|
+
self._ws = ws
|
|
461
|
+
elapsed = time.monotonic() - self.boot_t0 if self.boot_t0 else 0
|
|
462
|
+
elapsed_str = f" ({elapsed:.1f}s)" if elapsed else ""
|
|
463
|
+
print(f"[evol] Connected to Kernel{elapsed_str}")
|
|
464
|
+
|
|
465
|
+
# 启动接收循环(后台任务)
|
|
466
|
+
receiver_task = asyncio.create_task(self._ws_receiver(ws))
|
|
467
|
+
print(f"[evol] Receiver task started")
|
|
468
|
+
|
|
469
|
+
try:
|
|
470
|
+
# Subscribe to events
|
|
471
|
+
await self._rpc_call("event.subscribe", {
|
|
472
|
+
"events": [
|
|
473
|
+
"module.started",
|
|
474
|
+
"module.stopped",
|
|
475
|
+
"module.crashed",
|
|
476
|
+
"module.ready",
|
|
477
|
+
"module.exiting",
|
|
478
|
+
"module.shutdown",
|
|
479
|
+
"module.shutdown.ack",
|
|
480
|
+
"module.shutdown.ready",
|
|
481
|
+
],
|
|
482
|
+
})
|
|
483
|
+
|
|
484
|
+
# Register to Kernel
|
|
485
|
+
await self._rpc_call("registry.register", {
|
|
486
|
+
"module_id": "evol",
|
|
487
|
+
"module_type": "service",
|
|
488
|
+
"api_endpoint": f"http://127.0.0.1:{self.port}",
|
|
489
|
+
"health_endpoint": "/health",
|
|
490
|
+
"tools": {
|
|
491
|
+
"rpc": {
|
|
492
|
+
"module": {
|
|
493
|
+
"health": {"method": "health", "description": "健康检查"},
|
|
494
|
+
"status": {"method": "status", "description": "状态查询"}
|
|
495
|
+
},
|
|
496
|
+
"evol": {
|
|
497
|
+
"list_tokens": {"method": "list_tokens", "description": "列出所有令牌"},
|
|
498
|
+
"list_kite_tokens": {"method": "list_kite_tokens", "description": "列出 Kite 令牌"},
|
|
499
|
+
"list_evol_tokens": {"method": "list_evol_tokens", "description": "列出 Evol 令牌"},
|
|
500
|
+
"revoke_token": {"method": "revoke_token", "description": "撤销令牌"}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
"events_publish": {
|
|
505
|
+
"evol": {
|
|
506
|
+
"test": {"description": "Test event from evol module"},
|
|
507
|
+
"started": {"description": "Evol UI started with access URL"},
|
|
508
|
+
}
|
|
509
|
+
},
|
|
510
|
+
"events_subscribe": [
|
|
511
|
+
"module.started",
|
|
512
|
+
"module.stopped",
|
|
513
|
+
"module.crashed",
|
|
514
|
+
"module.ready",
|
|
515
|
+
"module.exiting",
|
|
516
|
+
"module.shutdown",
|
|
517
|
+
"module.shutdown.ack",
|
|
518
|
+
"module.shutdown.ready",
|
|
519
|
+
],
|
|
520
|
+
})
|
|
521
|
+
print(f"[evol] Registered to Kernel{elapsed_str}")
|
|
522
|
+
|
|
523
|
+
# Send module.ready
|
|
524
|
+
if not self._shutting_down:
|
|
525
|
+
startup_time = time.monotonic() - self.boot_t0 if self.boot_t0 else 0
|
|
526
|
+
await self._rpc_call("event.publish", {
|
|
527
|
+
"event_id": str(uuid.uuid4()),
|
|
528
|
+
"event": "module.ready",
|
|
529
|
+
"data": {
|
|
530
|
+
"module_id": "evol",
|
|
531
|
+
"graceful_shutdown": True,
|
|
532
|
+
"startup_time": startup_time,
|
|
533
|
+
},
|
|
534
|
+
})
|
|
535
|
+
elapsed_str = _fmt_elapsed(self.boot_t0)
|
|
536
|
+
print(f"[evol] module.ready published ({elapsed_str})")
|
|
537
|
+
|
|
538
|
+
# Publish evol.started event
|
|
539
|
+
display_host = "localhost" if self.host == "0.0.0.0" else self.host
|
|
540
|
+
access_url = f"http://{display_host}:{self.port}"
|
|
541
|
+
await self._publish_event({
|
|
542
|
+
"event": "evol.started",
|
|
543
|
+
"data": {
|
|
544
|
+
"module_id": "evol",
|
|
545
|
+
"url": access_url,
|
|
546
|
+
"host": self.host,
|
|
547
|
+
"port": self.port,
|
|
548
|
+
},
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
# 等待接收循环结束(连接断开)
|
|
552
|
+
await receiver_task
|
|
553
|
+
except Exception as e:
|
|
554
|
+
# 取消接收任务
|
|
555
|
+
receiver_task.cancel()
|
|
556
|
+
try:
|
|
557
|
+
await receiver_task
|
|
558
|
+
except asyncio.CancelledError:
|
|
559
|
+
pass
|
|
560
|
+
raise
|
|
561
|
+
except Exception as e:
|
|
562
|
+
print(f"[evol] WebSocket connection error: {e}")
|
|
563
|
+
raise
|
|
564
|
+
finally:
|
|
565
|
+
print(f"[evol] WebSocket connection closed")
|
|
566
|
+
self._ws = None
|
|
567
|
+
|
|
568
|
+
async def _rpc_call(self, method: str, params: dict = None, timeout: float = 10.0) -> dict:
|
|
569
|
+
"""Send a JSON-RPC 2.0 request and await the response."""
|
|
570
|
+
if not self._ws:
|
|
571
|
+
raise RuntimeError("WebSocket not connected")
|
|
572
|
+
|
|
573
|
+
rpc_id = str(uuid.uuid4())
|
|
574
|
+
msg = {"jsonrpc": "2.0", "id": rpc_id, "method": method}
|
|
575
|
+
if params:
|
|
576
|
+
msg["params"] = params
|
|
577
|
+
|
|
578
|
+
# Create future for response
|
|
579
|
+
response_future = asyncio.Future()
|
|
580
|
+
self._rpc_futures[rpc_id] = response_future
|
|
581
|
+
|
|
582
|
+
# Send request
|
|
583
|
+
try:
|
|
584
|
+
print(f"[evol] DEBUG: Sending RPC request id={rpc_id}, method={method}")
|
|
585
|
+
await self._ws.send(json.dumps(msg))
|
|
586
|
+
except Exception as e:
|
|
587
|
+
self._rpc_futures.pop(rpc_id, None)
|
|
588
|
+
raise RuntimeError(f"Failed to send RPC request: {e}")
|
|
589
|
+
|
|
590
|
+
# Wait for response
|
|
591
|
+
try:
|
|
592
|
+
response = await asyncio.wait_for(response_future, timeout=timeout)
|
|
593
|
+
except asyncio.TimeoutError:
|
|
594
|
+
self._rpc_futures.pop(rpc_id, None)
|
|
595
|
+
raise RuntimeError(f"RPC timeout waiting for {method}")
|
|
596
|
+
finally:
|
|
597
|
+
self._rpc_futures.pop(rpc_id, None)
|
|
598
|
+
|
|
599
|
+
# Check for error
|
|
600
|
+
if "error" in response:
|
|
601
|
+
error = response["error"]
|
|
602
|
+
error_msg = error.get("message", "Unknown error")
|
|
603
|
+
error_code = error.get("code", -1)
|
|
604
|
+
raise RuntimeError(f"RPC error: {error_msg} (code: {error_code})")
|
|
605
|
+
|
|
606
|
+
return response.get("result", {})
|
|
607
|
+
|
|
608
|
+
async def _handle_ping_event(self, data: dict):
|
|
609
|
+
"""Handle system.ping event and reply with system.pong."""
|
|
610
|
+
import time
|
|
611
|
+
t1 = data.get("ping_time")
|
|
612
|
+
t2 = time.time()
|
|
613
|
+
|
|
614
|
+
await self._publish_event({
|
|
615
|
+
"event": "system.pong",
|
|
616
|
+
"data": {
|
|
617
|
+
"module_id": "evol",
|
|
618
|
+
"ping_time": t1,
|
|
619
|
+
"pong_time": t2,
|
|
620
|
+
},
|
|
621
|
+
})
|
|
622
|
+
|
|
623
|
+
async def _handle_event_notification(self, msg: dict):
|
|
624
|
+
params = msg.get("params", {})
|
|
625
|
+
event_type = params.get("event", "")
|
|
626
|
+
data = params.get("data", {})
|
|
627
|
+
|
|
628
|
+
# Handle system.ping event
|
|
629
|
+
if event_type == "system.ping":
|
|
630
|
+
await self._handle_ping_event(data)
|
|
631
|
+
return
|
|
632
|
+
|
|
633
|
+
print(f"[evol] Event received: {event_type}, data: {data}")
|
|
634
|
+
|
|
635
|
+
if event_type == "module.shutdown":
|
|
636
|
+
target = data.get("module_id", "")
|
|
637
|
+
reason = data.get("reason", "")
|
|
638
|
+
if target == "evol" or not target or reason == "launcher_lost":
|
|
639
|
+
await self._handle_shutdown()
|
|
640
|
+
return
|
|
641
|
+
|
|
642
|
+
# Forward module status events to management WebSocket clients
|
|
643
|
+
if event_type in (
|
|
644
|
+
"module.started", "module.stopped", "module.crashed",
|
|
645
|
+
"module.ready", "module.exiting",
|
|
646
|
+
"module.shutdown.ack", "module.shutdown.ready",
|
|
647
|
+
):
|
|
648
|
+
await broadcast_event(event_type, data)
|
|
649
|
+
return
|
|
650
|
+
|
|
651
|
+
if event_type in SYSTEM_BROADCAST_EVENTS:
|
|
652
|
+
return
|
|
653
|
+
|
|
654
|
+
if os.environ.get("KITE_ENV") == "development":
|
|
655
|
+
print(f"[evol] Debug: Unhandled event: {event_type}")
|
|
656
|
+
|
|
657
|
+
async def _handle_rpc_request(self, ws, msg: dict):
|
|
658
|
+
rpc_id = msg.get("id", "")
|
|
659
|
+
method = msg.get("method", "")
|
|
660
|
+
params = msg.get("params", {})
|
|
661
|
+
|
|
662
|
+
if method.startswith("evol."):
|
|
663
|
+
method = method[5:]
|
|
664
|
+
|
|
665
|
+
handlers = {
|
|
666
|
+
"health": lambda p: self._rpc_health(),
|
|
667
|
+
"status": lambda p: self._rpc_status(),
|
|
668
|
+
"list_tokens": lambda p: self._rpc_list_tokens(),
|
|
669
|
+
"list_kite_tokens": lambda p: self._rpc_list_kite_tokens(),
|
|
670
|
+
"list_evol_tokens": lambda p: self._rpc_list_evol_tokens(),
|
|
671
|
+
"revoke_token": lambda p: self._rpc_revoke_token(p),
|
|
672
|
+
"subscribe_events": lambda p: self._rpc_subscribe_events(p),
|
|
673
|
+
}
|
|
674
|
+
handler = handlers.get(method)
|
|
675
|
+
if handler:
|
|
676
|
+
try:
|
|
677
|
+
result = await handler(params)
|
|
678
|
+
await ws.send(json.dumps({"jsonrpc": "2.0", "id": rpc_id, "result": result}))
|
|
679
|
+
except Exception as e:
|
|
680
|
+
await ws.send(json.dumps({
|
|
681
|
+
"jsonrpc": "2.0", "id": rpc_id,
|
|
682
|
+
"error": {"code": -32603, "message": str(e)},
|
|
683
|
+
}))
|
|
684
|
+
else:
|
|
685
|
+
await ws.send(json.dumps({
|
|
686
|
+
"jsonrpc": "2.0", "id": rpc_id,
|
|
687
|
+
"error": {"code": -32601, "message": f"Method not found: {method}"},
|
|
688
|
+
}))
|
|
689
|
+
|
|
690
|
+
async def _rpc_health(self) -> dict:
|
|
691
|
+
return {
|
|
692
|
+
"status": "healthy",
|
|
693
|
+
"details": {
|
|
694
|
+
"uptime_seconds": round(time.time() - self._start_time),
|
|
695
|
+
},
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
async def _rpc_status(self) -> dict:
|
|
699
|
+
return {
|
|
700
|
+
"module": "evol",
|
|
701
|
+
"status": "running",
|
|
702
|
+
"uptime_seconds": round(time.time() - self._start_time),
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
async def _rpc_list_tokens(self) -> dict:
|
|
706
|
+
"""列出所有 Kite Token(从 AuthManager 读取)"""
|
|
707
|
+
latest_tokens = self.auth_manager._get_latest_tokens()
|
|
708
|
+
now = time.time()
|
|
709
|
+
|
|
710
|
+
tokens = []
|
|
711
|
+
for token, info in latest_tokens.items():
|
|
712
|
+
# 只返回有效且未过期的 token
|
|
713
|
+
if info.get("isValid", True) and now <= info.get("expiresAt", 0):
|
|
714
|
+
tokens.append({
|
|
715
|
+
"token": token,
|
|
716
|
+
"deviceId": info.get("deviceId", "unknown"),
|
|
717
|
+
"deviceName": info.get("deviceName", "Unknown Device"),
|
|
718
|
+
"phone": info.get("phone"), # 添加绑定的手机号
|
|
719
|
+
"createdAt": info.get("createdAt_human", ""),
|
|
720
|
+
"lastUsedAt": info.get("lastUsedAt_human", ""),
|
|
721
|
+
"expiresAt": info.get("expiresAt_human", ""),
|
|
722
|
+
})
|
|
723
|
+
|
|
724
|
+
return {"tokens": tokens}
|
|
725
|
+
|
|
726
|
+
async def _rpc_list_kite_tokens(self) -> dict:
|
|
727
|
+
"""列出所有 Kite Token(前端配对令牌)"""
|
|
728
|
+
return await self._rpc_list_tokens()
|
|
729
|
+
|
|
730
|
+
async def _rpc_list_evol_tokens(self) -> dict:
|
|
731
|
+
"""列出所有 Evol Token(Evol 云端令牌)"""
|
|
732
|
+
evol_records = self.auth_manager.list_all_evol_tokens()
|
|
733
|
+
|
|
734
|
+
if not evol_records:
|
|
735
|
+
return {"tokens": []}
|
|
736
|
+
|
|
737
|
+
tokens = []
|
|
738
|
+
for evol_record in evol_records:
|
|
739
|
+
# 提取用户信息
|
|
740
|
+
user_info = evol_record.get("userInfo", {})
|
|
741
|
+
account_info = evol_record.get("accountInfo", {})
|
|
742
|
+
|
|
743
|
+
tokens.append({
|
|
744
|
+
"token": evol_record.get("token", ""),
|
|
745
|
+
"phone": evol_record.get("phone", ""),
|
|
746
|
+
"nickName": user_info.get("nickName", ""),
|
|
747
|
+
"userName": user_info.get("userName", ""),
|
|
748
|
+
"credits": account_info.get("credits", 0),
|
|
749
|
+
"creditsLimit": account_info.get("creditsLimit", 0),
|
|
750
|
+
"vipType": account_info.get("vipType", 0),
|
|
751
|
+
"vipTypeName": account_info.get("vipTypeName", "Unknown"),
|
|
752
|
+
"vipExpireTime": account_info.get("vipExpireTime", ""),
|
|
753
|
+
"vipRemainingDays": account_info.get("vipRemainingDays", 0),
|
|
754
|
+
"obtainedAt": evol_record.get("obtainedAt_human", ""),
|
|
755
|
+
"lastUsedAt": evol_record.get("lastUsedAt_human", ""),
|
|
756
|
+
"expiresAt": evol_record.get("expiresAt_human", ""),
|
|
757
|
+
})
|
|
758
|
+
|
|
759
|
+
return {"tokens": tokens}
|
|
760
|
+
|
|
761
|
+
async def _rpc_revoke_token(self, params: dict) -> dict:
|
|
762
|
+
"""吊销 Kite Token(使用 AuthManager)"""
|
|
763
|
+
token = params.get("token")
|
|
764
|
+
if not token:
|
|
765
|
+
raise ValueError("Missing token parameter")
|
|
766
|
+
|
|
767
|
+
# 验证 token 是否存在
|
|
768
|
+
latest_tokens = self.auth_manager._get_latest_tokens()
|
|
769
|
+
if token not in latest_tokens:
|
|
770
|
+
raise ValueError("Token not found")
|
|
771
|
+
|
|
772
|
+
# 吊销 token
|
|
773
|
+
self.auth_manager.revoke_kite_token(token)
|
|
774
|
+
|
|
775
|
+
return {"success": True, "message": "Token revoked successfully"}
|
|
776
|
+
|
|
777
|
+
async def _rpc_subscribe_events(self, params: dict) -> dict:
|
|
778
|
+
"""动态订阅事件(通过 Kernel)"""
|
|
779
|
+
events = params.get("events", [])
|
|
780
|
+
if not events:
|
|
781
|
+
raise ValueError("Missing events parameter")
|
|
782
|
+
|
|
783
|
+
if not self._ws:
|
|
784
|
+
raise RuntimeError("Not connected to Kernel")
|
|
785
|
+
|
|
786
|
+
# 调用 Kernel 的 event.subscribe
|
|
787
|
+
rpc_id = f"rpc-{uuid.uuid4()}"
|
|
788
|
+
await self._ws.send(json.dumps({
|
|
789
|
+
"jsonrpc": "2.0",
|
|
790
|
+
"id": rpc_id,
|
|
791
|
+
"method": "event.subscribe",
|
|
792
|
+
"params": {"events": events}
|
|
793
|
+
}))
|
|
794
|
+
|
|
795
|
+
print(f"[evol] Subscribed to events: {events}")
|
|
796
|
+
return {"success": True, "events": events}
|
|
797
|
+
|
|
798
|
+
async def _handle_shutdown(self):
|
|
799
|
+
print("[evol] Received module.shutdown")
|
|
800
|
+
self._shutting_down = True
|
|
801
|
+
|
|
802
|
+
await self._publish_event({
|
|
803
|
+
"event": "module.shutdown.ack",
|
|
804
|
+
"data": {"module_id": "evol"},
|
|
805
|
+
})
|
|
806
|
+
|
|
807
|
+
await self._publish_event({
|
|
808
|
+
"event": "module.exiting",
|
|
809
|
+
"data": {
|
|
810
|
+
"module_id": "evol",
|
|
811
|
+
"type": "passive",
|
|
812
|
+
"reason": "shutdown_requested",
|
|
813
|
+
"restart": "auto",
|
|
814
|
+
"action": "none",
|
|
815
|
+
"timeout": 3.0,
|
|
816
|
+
"restart_delay": 0.0,
|
|
817
|
+
},
|
|
818
|
+
})
|
|
819
|
+
|
|
820
|
+
if self._test_task:
|
|
821
|
+
self._test_task.cancel()
|
|
822
|
+
|
|
823
|
+
# Close WebSocket connections
|
|
824
|
+
if hasattr(self.app.state, 'relay_service'):
|
|
825
|
+
await self.app.state.relay_service.close_all_sessions()
|
|
826
|
+
|
|
827
|
+
from extensions.services.evol.routes.routes_management_ws import close_all_clients
|
|
828
|
+
await close_all_clients()
|
|
829
|
+
|
|
830
|
+
await self._publish_event({
|
|
831
|
+
"event": "module.shutdown.ready",
|
|
832
|
+
"data": {"module_id": "evol"},
|
|
833
|
+
})
|
|
834
|
+
|
|
835
|
+
# 等待一小段时间确保事件发送完成
|
|
836
|
+
await asyncio.sleep(0.2)
|
|
837
|
+
|
|
838
|
+
# 关闭 Kernel WebSocket 连接
|
|
839
|
+
if self._ws:
|
|
840
|
+
try:
|
|
841
|
+
await self._ws.close(code=1000, reason="Graceful shutdown")
|
|
842
|
+
print("[evol] Kernel WebSocket closed")
|
|
843
|
+
except Exception as e:
|
|
844
|
+
print(f"[evol] Failed to close Kernel WebSocket: {e}")
|
|
845
|
+
|
|
846
|
+
# 触发 uvicorn 优雅关闭(让 uvicorn 自然退出,不要 sys.exit)
|
|
847
|
+
if self._uvicorn_server:
|
|
848
|
+
print("[evol] Triggering uvicorn graceful shutdown")
|
|
849
|
+
self._uvicorn_server.should_exit = True
|
|
850
|
+
else:
|
|
851
|
+
print("[evol] Warning: uvicorn_server not set")
|
|
852
|
+
|
|
853
|
+
async def _publish_event(self, event: dict):
|
|
854
|
+
if not self._ws:
|
|
855
|
+
return
|
|
856
|
+
try:
|
|
857
|
+
await self._rpc_call("event.publish", {
|
|
858
|
+
"event_id": str(uuid.uuid4()),
|
|
859
|
+
"event": event.get("event", ""),
|
|
860
|
+
"data": event.get("data", {}),
|
|
861
|
+
})
|
|
862
|
+
except Exception as e:
|
|
863
|
+
print(f"[evol] ERROR: Failed to publish event {event.get('event')}: {e}")
|
|
864
|
+
|
|
865
|
+
async def _test_event_loop(self):
|
|
866
|
+
while True:
|
|
867
|
+
await asyncio.sleep(10)
|
|
868
|
+
await self._publish_event({
|
|
869
|
+
"event": "evol.test",
|
|
870
|
+
"data": {
|
|
871
|
+
"message": "test event from evol",
|
|
872
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
873
|
+
},
|
|
874
|
+
})
|
|
875
|
+
print("[evol] test event published")
|