@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.
Files changed (293) hide show
  1. package/CHANGELOG.md +302 -0
  2. package/cli.js +119 -4
  3. package/core/dependency_checker.py +250 -0
  4. package/core/env_checker.py +490 -0
  5. package/dependencies_lock.json +128 -0
  6. package/extensions/agents/assistant/entry.py +111 -1
  7. package/extensions/agents/assistant/server.py +279 -215
  8. package/extensions/channels/acp_channel/entry.py +111 -1
  9. package/extensions/channels/acp_channel/module.md +23 -22
  10. package/extensions/channels/acp_channel/server.py +279 -215
  11. package/extensions/event_hub_bench/entry.py +107 -1
  12. package/extensions/services/backup/entry.py +306 -21
  13. package/extensions/services/backup/module.md +24 -22
  14. package/extensions/services/evol/auth_manager.py +443 -0
  15. package/extensions/services/evol/config.yaml +149 -0
  16. package/extensions/services/evol/config_loader.py +117 -0
  17. package/extensions/services/evol/entry.py +406 -0
  18. package/extensions/services/evol/evol_api.py +173 -0
  19. package/extensions/services/evol/evol_config.json5 +29 -0
  20. package/extensions/services/evol/migrate_tokens.py +122 -0
  21. package/extensions/services/evol/module.md +32 -0
  22. package/extensions/services/evol/pairing.py +250 -0
  23. package/extensions/services/evol/pairing_codes.jsonl +1 -0
  24. package/extensions/services/evol/relay.py +682 -0
  25. package/extensions/services/evol/relay_config.json5 +67 -0
  26. package/extensions/services/evol/routes/__init__.py +1 -0
  27. package/extensions/services/evol/routes/routes_management_ws.py +127 -0
  28. package/extensions/services/evol/routes/routes_rpc.py +89 -0
  29. package/extensions/services/evol/routes/routes_test.py +61 -0
  30. package/extensions/services/evol/server.py +875 -0
  31. package/extensions/services/evol/static/css/style.css +1200 -0
  32. package/extensions/services/evol/static/index.html +781 -0
  33. package/extensions/services/evol/static/index_evol.html +14 -0
  34. package/extensions/services/evol/static/js/app.js +6304 -0
  35. package/extensions/services/evol/static/js/auth.js +326 -0
  36. package/extensions/services/evol/static/js/dialog.js +285 -0
  37. package/extensions/services/evol/static/js/evol-app-fixed.js +50 -0
  38. package/extensions/services/evol/static/js/evol-app.js +1949 -0
  39. package/extensions/services/evol/static/js/evol-app.js.bak +1800 -0
  40. package/extensions/services/evol/static/js/kernel-client-example.js +228 -0
  41. package/extensions/services/evol/static/js/kernel-client.js +396 -0
  42. package/extensions/services/evol/static/js/main.js +141 -0
  43. package/extensions/services/evol/static/js/registry-tests.js +585 -0
  44. package/extensions/services/evol/static/js/stats.js +217 -0
  45. package/extensions/services/evol/static/js/token-manager.js +175 -0
  46. package/extensions/services/evol/static/pairing.html +248 -0
  47. package/extensions/services/evol/static/test_registry.html +262 -0
  48. package/extensions/services/evol/static/test_relay.html +462 -0
  49. package/extensions/services/evol/stats_manager.py +240 -0
  50. package/extensions/services/model_service/entry.py +167 -19
  51. package/extensions/services/model_service/module.md +21 -22
  52. package/extensions/services/proxy/.claude/settings.local.json +13 -0
  53. package/extensions/services/proxy/CHANGELOG_20260308.md +258 -0
  54. package/extensions/services/proxy/_fix_prints.py +133 -0
  55. package/extensions/services/proxy/_fix_prints2.py +87 -0
  56. package/extensions/services/proxy/agentcp/LICENCE +178 -0
  57. package/extensions/services/proxy/agentcp/README copy.md +85 -0
  58. package/extensions/services/proxy/agentcp/README.md +260 -0
  59. package/extensions/services/proxy/agentcp/__init__.py +16 -0
  60. package/extensions/services/proxy/agentcp/agent.py +4 -0
  61. package/extensions/services/proxy/agentcp/agentcp.py +2494 -0
  62. package/extensions/services/proxy/agentcp/agentprofile.json +89 -0
  63. package/extensions/services/proxy/agentcp/ap/__init__.py +16 -0
  64. package/extensions/services/proxy/agentcp/ap/ap_client.py +316 -0
  65. package/extensions/services/proxy/agentcp/assets/images/wechat_qr.png +0 -0
  66. package/extensions/services/proxy/agentcp/backup/metrics.json +31 -0
  67. package/extensions/services/proxy/agentcp/base/__init__.py +20 -0
  68. package/extensions/services/proxy/agentcp/base/auth_client.py +257 -0
  69. package/extensions/services/proxy/agentcp/base/client.py +112 -0
  70. package/extensions/services/proxy/agentcp/base/env.py +34 -0
  71. package/extensions/services/proxy/agentcp/base/html_util.py +336 -0
  72. package/extensions/services/proxy/agentcp/base/log.py +98 -0
  73. package/extensions/services/proxy/agentcp/ca/__init__.py +17 -0
  74. package/extensions/services/proxy/agentcp/ca/ca_client.py +414 -0
  75. package/extensions/services/proxy/agentcp/ca/ca_root.py +74 -0
  76. package/extensions/services/proxy/agentcp/context/__init__.py +20 -0
  77. package/extensions/services/proxy/agentcp/context/context.py +73 -0
  78. package/extensions/services/proxy/agentcp/context/exceptions.py +114 -0
  79. package/extensions/services/proxy/agentcp/create_profile.py +125 -0
  80. package/extensions/services/proxy/agentcp/create_profile_weather.py +125 -0
  81. package/extensions/services/proxy/agentcp/db/__init__.py +15 -0
  82. package/extensions/services/proxy/agentcp/db/db_mananger.py +550 -0
  83. package/extensions/services/proxy/agentcp/docs/UDP_HEARTBEAT_FIX_REPORT.md +265 -0
  84. package/extensions/services/proxy/agentcp/docs/heartbeat_issue_analysis.md +291 -0
  85. package/extensions/services/proxy/agentcp/file/__init__.py +16 -0
  86. package/extensions/services/proxy/agentcp/file/file_client.py +141 -0
  87. package/extensions/services/proxy/agentcp/file/wss_binary_message.py +137 -0
  88. package/extensions/services/proxy/agentcp/hcp.py +299 -0
  89. package/extensions/services/proxy/agentcp/heartbeat/__init__.py +16 -0
  90. package/extensions/services/proxy/agentcp/heartbeat/heartbeat_client.py +360 -0
  91. package/extensions/services/proxy/agentcp/improved_scheduler.py +498 -0
  92. package/extensions/services/proxy/agentcp/llm_agent_utils.py +249 -0
  93. package/extensions/services/proxy/agentcp/llm_server.py +172 -0
  94. package/extensions/services/proxy/agentcp/mermaid.py +210 -0
  95. package/extensions/services/proxy/agentcp/message.py +149 -0
  96. package/extensions/services/proxy/agentcp/metrics.py +256 -0
  97. package/extensions/services/proxy/agentcp/monitoring/__init__.py +20 -0
  98. package/extensions/services/proxy/agentcp/monitoring/global_monitor.py +27 -0
  99. package/extensions/services/proxy/agentcp/monitoring/metrics_store.py +325 -0
  100. package/extensions/services/proxy/agentcp/monitoring/monitoring_service.py +269 -0
  101. package/extensions/services/proxy/agentcp/monitoring/sliding_window.py +222 -0
  102. package/extensions/services/proxy/agentcp/monitoring/standalone_reader.py +224 -0
  103. package/extensions/services/proxy/agentcp/msg/__init__.py +21 -0
  104. package/extensions/services/proxy/agentcp/msg/connection_manager.py +456 -0
  105. package/extensions/services/proxy/agentcp/msg/message_client.py +2058 -0
  106. package/extensions/services/proxy/agentcp/msg/message_serialize.py +263 -0
  107. package/extensions/services/proxy/agentcp/msg/open_ai_message.py +88 -0
  108. package/extensions/services/proxy/agentcp/msg/session_manager.py +1062 -0
  109. package/extensions/services/proxy/agentcp/msg/stream_client.py +267 -0
  110. package/extensions/services/proxy/agentcp/msg/websocket_file_receiver.py +89 -0
  111. package/extensions/services/proxy/agentcp/msg/ws_logger.py +685 -0
  112. package/extensions/services/proxy/agentcp/msg/wss_binary_message.py +137 -0
  113. package/extensions/services/proxy/agentcp/requirements.txt +7 -0
  114. package/extensions/services/proxy/agentcp/samples/agent_graph/README.md +37 -0
  115. package/extensions/services/proxy/agentcp/samples/agent_graph/agentprofile.json +89 -0
  116. package/extensions/services/proxy/agentcp/samples/agent_graph/create_profile.py +138 -0
  117. package/extensions/services/proxy/agentcp/samples/agent_graph/main.py +164 -0
  118. package/extensions/services/proxy/agentcp/samples/agent_use/create_profile.py +123 -0
  119. package/extensions/services/proxy/agentcp/samples/agent_use/llm/create_profile.py +129 -0
  120. package/extensions/services/proxy/agentcp/samples/agent_use/llm/env.json +5 -0
  121. package/extensions/services/proxy/agentcp/samples/agent_use/llm/main.py +146 -0
  122. package/extensions/services/proxy/agentcp/samples/agent_use/main.py +123 -0
  123. package/extensions/services/proxy/agentcp/samples/agent_use/readme.md +379 -0
  124. package/extensions/services/proxy/agentcp/samples/agent_use/search/create_profile.py +129 -0
  125. package/extensions/services/proxy/agentcp/samples/agent_use/search/main.py +28 -0
  126. package/extensions/services/proxy/agentcp/samples/agent_use/tool/create_profile.py +129 -0
  127. package/extensions/services/proxy/agentcp/samples/agent_use/tool/main.py +20 -0
  128. package/extensions/services/proxy/agentcp/samples/ali_amap/README.md +97 -0
  129. package/extensions/services/proxy/agentcp/samples/ali_amap/amap_agent.py +88 -0
  130. package/extensions/services/proxy/agentcp/samples/ali_amap/create_profile.py +125 -0
  131. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/powershell.py +228 -0
  132. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/software.py +63 -0
  133. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/tools.py +36 -0
  134. package/extensions/services/proxy/agentcp/samples/compute_agent/browser_user.py +41 -0
  135. package/extensions/services/proxy/agentcp/samples/deepseek/README.md +79 -0
  136. package/extensions/services/proxy/agentcp/samples/deepseek/create_profile.py +126 -0
  137. package/extensions/services/proxy/agentcp/samples/deepseek/deepseek.py +42 -0
  138. package/extensions/services/proxy/agentcp/samples/dify_chat/README.md +78 -0
  139. package/extensions/services/proxy/agentcp/samples/dify_chat/create_profile.py +126 -0
  140. package/extensions/services/proxy/agentcp/samples/dify_chat/dify_chat.py +47 -0
  141. package/extensions/services/proxy/agentcp/samples/dify_workflow/README.md +78 -0
  142. package/extensions/services/proxy/agentcp/samples/dify_workflow/create_profile.py +126 -0
  143. package/extensions/services/proxy/agentcp/samples/dify_workflow/dify_workflow.py +46 -0
  144. package/extensions/services/proxy/agentcp/samples/executor/README.md +44 -0
  145. package/extensions/services/proxy/agentcp/samples/executor/agentprofile.json +89 -0
  146. package/extensions/services/proxy/agentcp/samples/executor/create_profile.py +139 -0
  147. package/extensions/services/proxy/agentcp/samples/executor/main.py +160 -0
  148. package/extensions/services/proxy/agentcp/samples/filereader/README.md +45 -0
  149. package/extensions/services/proxy/agentcp/samples/filereader/agentprofile.json +90 -0
  150. package/extensions/services/proxy/agentcp/samples/filereader/create_profile.py +137 -0
  151. package/extensions/services/proxy/agentcp/samples/filereader/main.py +253 -0
  152. package/extensions/services/proxy/agentcp/samples/filewriter/README.md +38 -0
  153. package/extensions/services/proxy/agentcp/samples/filewriter/agentprofile.json +91 -0
  154. package/extensions/services/proxy/agentcp/samples/filewriter/create_profile.py +138 -0
  155. package/extensions/services/proxy/agentcp/samples/filewriter/main.py +289 -0
  156. package/extensions/services/proxy/agentcp/samples/hcp/README.md +85 -0
  157. package/extensions/services/proxy/agentcp/samples/hcp/acp_weather_agent.zip +0 -0
  158. package/extensions/services/proxy/agentcp/samples/hcp/create_profile.py +125 -0
  159. package/extensions/services/proxy/agentcp/samples/hcp/hcp.py +237 -0
  160. package/extensions/services/proxy/agentcp/samples/helloworld/README.md +68 -0
  161. package/extensions/services/proxy/agentcp/samples/helloworld/hello_world.py +40 -0
  162. package/extensions/services/proxy/agentcp/samples/llm_agent/MEADME.md +117 -0
  163. package/extensions/services/proxy/agentcp/samples/llm_agent/create_profile.py +125 -0
  164. package/extensions/services/proxy/agentcp/samples/llm_agent/qwen_agent.py +136 -0
  165. package/extensions/services/proxy/agentcp/samples/local_llm_agent/README.md +90 -0
  166. package/extensions/services/proxy/agentcp/samples/local_llm_agent/create_profile.py +125 -0
  167. package/extensions/services/proxy/agentcp/samples/local_llm_agent/main.py +49 -0
  168. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/README.md +55 -0
  169. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/create_profile.py +125 -0
  170. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/main.py +23 -0
  171. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/README.md +103 -0
  172. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/create_profile.py +125 -0
  173. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/main.py +69 -0
  174. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/README.md +58 -0
  175. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/create_profile.py +125 -0
  176. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/main.py +25 -0
  177. package/extensions/services/proxy/agentcp/samples/qwen3/README.md +71 -0
  178. package/extensions/services/proxy/agentcp/samples/qwen3/create_profile.py +126 -0
  179. package/extensions/services/proxy/agentcp/samples/qwen3/qwen3.py +37 -0
  180. package/extensions/services/proxy/agentcp/samples/qwen3_tools/README.md +133 -0
  181. package/extensions/services/proxy/agentcp/samples/qwen3_tools/create_profile.py +126 -0
  182. package/extensions/services/proxy/agentcp/samples/qwen3_tools/qwen3_tools.py +98 -0
  183. package/extensions/services/proxy/agentcp/samples/search/create_profile_qwen.py +125 -0
  184. package/extensions/services/proxy/agentcp/samples/search/create_profile_search.py +125 -0
  185. package/extensions/services/proxy/agentcp/samples/search/qwen_agent.py +136 -0
  186. package/extensions/services/proxy/agentcp/samples/search/search_agent.py +170 -0
  187. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/README.md +89 -0
  188. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/create_profile.py +125 -0
  189. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/main.py +44 -0
  190. package/extensions/services/proxy/agentcp/utils/__init__.py +15 -0
  191. package/extensions/services/proxy/agentcp/utils/file_util.py +117 -0
  192. package/extensions/services/proxy/agentcp/utils/proxy_bypass.py +99 -0
  193. package/extensions/services/proxy/agentcp/workflow.py +203 -0
  194. package/extensions/services/proxy/console_auth.py +109 -0
  195. package/extensions/services/proxy/evol/__init__.py +1 -0
  196. package/extensions/services/proxy/evol/config.py +37 -0
  197. package/extensions/services/proxy/evol/http/__init__.py +1 -0
  198. package/extensions/services/proxy/evol/http/async_http.py +551 -0
  199. package/extensions/services/proxy/evol/log.py +28 -0
  200. package/extensions/services/proxy/evol/presenter/__init__.py +2 -0
  201. package/extensions/services/proxy/evol/presenter/agentIdPresenter.py +1031 -0
  202. package/extensions/services/proxy/evol/presenter/apikeyPresenter.py +106 -0
  203. package/extensions/services/proxy/evol/presenter/configPresenter.py +1281 -0
  204. package/extensions/services/proxy/evol/presenter/userPresenter.py +477 -0
  205. package/extensions/services/proxy/evol/server/__init__.py +1 -0
  206. package/extensions/services/proxy/evol/server/claude_proxy_async.py +3430 -0
  207. package/extensions/services/proxy/evol/server/openclaw_proxy.py +1861 -0
  208. package/extensions/services/proxy/evol/server/proxy_config.py +15 -0
  209. package/extensions/services/proxy/evol/server/proxy_engine.py +501 -0
  210. package/extensions/services/proxy/evol/version.py +24 -0
  211. package/extensions/services/proxy/logs/websocket.log +260 -0
  212. package/extensions/services/proxy/main.py +240 -0
  213. package/extensions/services/proxy/requirements.txt +13 -0
  214. package/extensions/services/proxy/server.py +271 -0
  215. package/extensions/services/watchdog/entry.py +215 -26
  216. package/extensions/services/watchdog/module.md +1 -0
  217. package/extensions/services/watchdog/monitor.py +178 -38
  218. package/extensions/services/web/WEBSOCKET_STATUS.md +143 -0
  219. package/extensions/services/web/config_example.py +35 -0
  220. package/extensions/services/web/config_loader.py +110 -0
  221. package/extensions/services/web/entry.py +114 -26
  222. package/extensions/services/web/module.md +35 -24
  223. package/extensions/services/web/pairing.py +250 -0
  224. package/extensions/services/web/pairing_codes.jsonl +16 -0
  225. package/extensions/services/web/relay.py +643 -0
  226. package/extensions/services/web/relay_config.json5 +67 -0
  227. package/extensions/services/web/routes/routes_management_ws.py +127 -0
  228. package/extensions/services/web/routes/routes_rpc.py +89 -0
  229. package/extensions/services/web/routes/routes_test.py +61 -0
  230. package/extensions/services/web/routes/schemas.py +0 -22
  231. package/extensions/services/web/server.py +434 -99
  232. package/extensions/services/web/static/css/style.css +67 -28
  233. package/extensions/services/web/static/index.html +234 -44
  234. package/extensions/services/web/static/js/app.js +1335 -48
  235. package/extensions/services/web/static/js/kernel-client-example.js +161 -0
  236. package/extensions/services/web/static/js/kernel-client.js +383 -0
  237. package/extensions/services/web/static/js/registry-tests.js +558 -0
  238. package/extensions/services/web/static/js/token-manager.js +175 -0
  239. package/extensions/services/web/static/pairing.html +248 -0
  240. package/extensions/services/web/static/test_registry.html +262 -0
  241. package/extensions/services/web/web_config.json5 +29 -0
  242. package/kernel/entry.py +120 -32
  243. package/kernel/event_hub.py +141 -16
  244. package/kernel/module.md +60 -33
  245. package/kernel/registry_store.py +45 -36
  246. package/kernel/rpc_router.py +152 -59
  247. package/kernel/server.py +322 -26
  248. package/kite_cli/__init__.py +3 -0
  249. package/kite_cli/__main__.py +5 -0
  250. package/kite_cli/commands/__init__.py +1 -0
  251. package/kite_cli/commands/clean.py +101 -0
  252. package/kite_cli/commands/deps_install.py +67 -0
  253. package/kite_cli/commands/doctor.py +35 -0
  254. package/kite_cli/commands/env_check.py +45 -0
  255. package/kite_cli/commands/history.py +111 -0
  256. package/kite_cli/commands/info.py +96 -0
  257. package/kite_cli/commands/install.py +313 -0
  258. package/kite_cli/commands/list.py +143 -0
  259. package/kite_cli/commands/log.py +81 -0
  260. package/kite_cli/commands/prepare.py +49 -0
  261. package/kite_cli/commands/rollback.py +88 -0
  262. package/kite_cli/commands/search.py +73 -0
  263. package/kite_cli/commands/uninstall.py +85 -0
  264. package/kite_cli/commands/update.py +118 -0
  265. package/kite_cli/commands/venv_setup.py +56 -0
  266. package/kite_cli/core/__init__.py +1 -0
  267. package/kite_cli/core/checker.py +142 -0
  268. package/kite_cli/core/dependency.py +229 -0
  269. package/kite_cli/core/downloader.py +209 -0
  270. package/kite_cli/core/install_info.py +40 -0
  271. package/kite_cli/core/tool_installer.py +397 -0
  272. package/kite_cli/core/validator.py +78 -0
  273. package/kite_cli/main.py +317 -0
  274. package/kite_cli/utils/__init__.py +1 -0
  275. package/kite_cli/utils/i18n.py +252 -0
  276. package/kite_cli/utils/interactive.py +63 -0
  277. package/kite_cli/utils/operation_log.py +77 -0
  278. package/kite_cli/utils/paths.py +34 -0
  279. package/kite_cli/utils/version.py +308 -0
  280. package/launcher/entry.py +1124 -178
  281. package/launcher/logging_setup.py +104 -0
  282. package/launcher/module.md +46 -37
  283. package/launcher/module_scanner.py +11 -1
  284. package/main.py +4 -1
  285. package/package.json +9 -1
  286. package/python_version.json +4 -0
  287. package/requirements.txt +38 -0
  288. package/scripts/env-manager.js +328 -0
  289. package/scripts/plan_manager.py +315 -0
  290. package/scripts/python-env.js +79 -0
  291. package/scripts/scan_dependencies.py +461 -0
  292. package/scripts/setup-python-env.js +191 -0
  293. 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")