@agentunion/kite 1.4.0 → 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 (235) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/cli.js +44 -5
  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/server.py +33 -17
  7. package/extensions/channels/acp_channel/server.py +33 -17
  8. package/extensions/services/backup/entry.py +23 -16
  9. package/extensions/services/evol/auth_manager.py +443 -0
  10. package/extensions/services/evol/config.yaml +149 -0
  11. package/extensions/services/evol/config_loader.py +117 -0
  12. package/extensions/services/evol/entry.py +406 -0
  13. package/extensions/services/evol/evol_api.py +173 -0
  14. package/extensions/services/evol/evol_config.json5 +29 -0
  15. package/extensions/services/evol/migrate_tokens.py +122 -0
  16. package/extensions/services/evol/module.md +32 -0
  17. package/extensions/services/evol/pairing.py +250 -0
  18. package/extensions/services/evol/pairing_codes.jsonl +1 -0
  19. package/extensions/services/evol/relay.py +682 -0
  20. package/extensions/services/evol/relay_config.json5 +67 -0
  21. package/extensions/services/evol/routes/__init__.py +1 -0
  22. package/extensions/services/evol/routes/routes_management_ws.py +127 -0
  23. package/extensions/services/evol/routes/routes_rpc.py +89 -0
  24. package/extensions/services/evol/routes/routes_test.py +61 -0
  25. package/extensions/services/evol/server.py +875 -0
  26. package/extensions/services/evol/static/css/style.css +1200 -0
  27. package/extensions/services/evol/static/index.html +781 -0
  28. package/extensions/services/evol/static/index_evol.html +14 -0
  29. package/extensions/services/evol/static/js/app.js +6304 -0
  30. package/extensions/services/evol/static/js/auth.js +326 -0
  31. package/extensions/services/evol/static/js/dialog.js +285 -0
  32. package/extensions/services/evol/static/js/evol-app-fixed.js +50 -0
  33. package/extensions/services/evol/static/js/evol-app.js +1949 -0
  34. package/extensions/services/evol/static/js/evol-app.js.bak +1800 -0
  35. package/extensions/services/evol/static/js/kernel-client-example.js +228 -0
  36. package/extensions/services/evol/static/js/kernel-client.js +396 -0
  37. package/extensions/services/evol/static/js/main.js +141 -0
  38. package/extensions/services/evol/static/js/registry-tests.js +585 -0
  39. package/extensions/services/evol/static/js/stats.js +217 -0
  40. package/extensions/services/evol/static/js/token-manager.js +175 -0
  41. package/extensions/services/evol/static/pairing.html +248 -0
  42. package/extensions/services/evol/static/test_registry.html +262 -0
  43. package/extensions/services/evol/static/test_relay.html +462 -0
  44. package/extensions/services/evol/stats_manager.py +240 -0
  45. package/extensions/services/model_service/entry.py +23 -1
  46. package/extensions/services/proxy/.claude/settings.local.json +13 -0
  47. package/extensions/services/proxy/CHANGELOG_20260308.md +258 -0
  48. package/extensions/services/proxy/_fix_prints.py +133 -0
  49. package/extensions/services/proxy/_fix_prints2.py +87 -0
  50. package/extensions/services/proxy/agentcp/LICENCE +178 -0
  51. package/extensions/services/proxy/agentcp/README copy.md +85 -0
  52. package/extensions/services/proxy/agentcp/README.md +260 -0
  53. package/extensions/services/proxy/agentcp/__init__.py +16 -0
  54. package/extensions/services/proxy/agentcp/agent.py +4 -0
  55. package/extensions/services/proxy/agentcp/agentcp.py +2494 -0
  56. package/extensions/services/proxy/agentcp/agentprofile.json +89 -0
  57. package/extensions/services/proxy/agentcp/ap/__init__.py +16 -0
  58. package/extensions/services/proxy/agentcp/ap/ap_client.py +316 -0
  59. package/extensions/services/proxy/agentcp/assets/images/wechat_qr.png +0 -0
  60. package/extensions/services/proxy/agentcp/backup/metrics.json +31 -0
  61. package/extensions/services/proxy/agentcp/base/__init__.py +20 -0
  62. package/extensions/services/proxy/agentcp/base/auth_client.py +257 -0
  63. package/extensions/services/proxy/agentcp/base/client.py +112 -0
  64. package/extensions/services/proxy/agentcp/base/env.py +34 -0
  65. package/extensions/services/proxy/agentcp/base/html_util.py +336 -0
  66. package/extensions/services/proxy/agentcp/base/log.py +98 -0
  67. package/extensions/services/proxy/agentcp/ca/__init__.py +17 -0
  68. package/extensions/services/proxy/agentcp/ca/ca_client.py +414 -0
  69. package/extensions/services/proxy/agentcp/ca/ca_root.py +74 -0
  70. package/extensions/services/proxy/agentcp/context/__init__.py +20 -0
  71. package/extensions/services/proxy/agentcp/context/context.py +73 -0
  72. package/extensions/services/proxy/agentcp/context/exceptions.py +114 -0
  73. package/extensions/services/proxy/agentcp/create_profile.py +125 -0
  74. package/extensions/services/proxy/agentcp/create_profile_weather.py +125 -0
  75. package/extensions/services/proxy/agentcp/db/__init__.py +15 -0
  76. package/extensions/services/proxy/agentcp/db/db_mananger.py +550 -0
  77. package/extensions/services/proxy/agentcp/docs/UDP_HEARTBEAT_FIX_REPORT.md +265 -0
  78. package/extensions/services/proxy/agentcp/docs/heartbeat_issue_analysis.md +291 -0
  79. package/extensions/services/proxy/agentcp/file/__init__.py +16 -0
  80. package/extensions/services/proxy/agentcp/file/file_client.py +141 -0
  81. package/extensions/services/proxy/agentcp/file/wss_binary_message.py +137 -0
  82. package/extensions/services/proxy/agentcp/hcp.py +299 -0
  83. package/extensions/services/proxy/agentcp/heartbeat/__init__.py +16 -0
  84. package/extensions/services/proxy/agentcp/heartbeat/heartbeat_client.py +360 -0
  85. package/extensions/services/proxy/agentcp/improved_scheduler.py +498 -0
  86. package/extensions/services/proxy/agentcp/llm_agent_utils.py +249 -0
  87. package/extensions/services/proxy/agentcp/llm_server.py +172 -0
  88. package/extensions/services/proxy/agentcp/mermaid.py +210 -0
  89. package/extensions/services/proxy/agentcp/message.py +149 -0
  90. package/extensions/services/proxy/agentcp/metrics.py +256 -0
  91. package/extensions/services/proxy/agentcp/monitoring/__init__.py +20 -0
  92. package/extensions/services/proxy/agentcp/monitoring/global_monitor.py +27 -0
  93. package/extensions/services/proxy/agentcp/monitoring/metrics_store.py +325 -0
  94. package/extensions/services/proxy/agentcp/monitoring/monitoring_service.py +269 -0
  95. package/extensions/services/proxy/agentcp/monitoring/sliding_window.py +222 -0
  96. package/extensions/services/proxy/agentcp/monitoring/standalone_reader.py +224 -0
  97. package/extensions/services/proxy/agentcp/msg/__init__.py +21 -0
  98. package/extensions/services/proxy/agentcp/msg/connection_manager.py +456 -0
  99. package/extensions/services/proxy/agentcp/msg/message_client.py +2058 -0
  100. package/extensions/services/proxy/agentcp/msg/message_serialize.py +263 -0
  101. package/extensions/services/proxy/agentcp/msg/open_ai_message.py +88 -0
  102. package/extensions/services/proxy/agentcp/msg/session_manager.py +1062 -0
  103. package/extensions/services/proxy/agentcp/msg/stream_client.py +267 -0
  104. package/extensions/services/proxy/agentcp/msg/websocket_file_receiver.py +89 -0
  105. package/extensions/services/proxy/agentcp/msg/ws_logger.py +685 -0
  106. package/extensions/services/proxy/agentcp/msg/wss_binary_message.py +137 -0
  107. package/extensions/services/proxy/agentcp/requirements.txt +7 -0
  108. package/extensions/services/proxy/agentcp/samples/agent_graph/README.md +37 -0
  109. package/extensions/services/proxy/agentcp/samples/agent_graph/agentprofile.json +89 -0
  110. package/extensions/services/proxy/agentcp/samples/agent_graph/create_profile.py +138 -0
  111. package/extensions/services/proxy/agentcp/samples/agent_graph/main.py +164 -0
  112. package/extensions/services/proxy/agentcp/samples/agent_use/create_profile.py +123 -0
  113. package/extensions/services/proxy/agentcp/samples/agent_use/llm/create_profile.py +129 -0
  114. package/extensions/services/proxy/agentcp/samples/agent_use/llm/env.json +5 -0
  115. package/extensions/services/proxy/agentcp/samples/agent_use/llm/main.py +146 -0
  116. package/extensions/services/proxy/agentcp/samples/agent_use/main.py +123 -0
  117. package/extensions/services/proxy/agentcp/samples/agent_use/readme.md +379 -0
  118. package/extensions/services/proxy/agentcp/samples/agent_use/search/create_profile.py +129 -0
  119. package/extensions/services/proxy/agentcp/samples/agent_use/search/main.py +28 -0
  120. package/extensions/services/proxy/agentcp/samples/agent_use/tool/create_profile.py +129 -0
  121. package/extensions/services/proxy/agentcp/samples/agent_use/tool/main.py +20 -0
  122. package/extensions/services/proxy/agentcp/samples/ali_amap/README.md +97 -0
  123. package/extensions/services/proxy/agentcp/samples/ali_amap/amap_agent.py +88 -0
  124. package/extensions/services/proxy/agentcp/samples/ali_amap/create_profile.py +125 -0
  125. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/powershell.py +228 -0
  126. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/software.py +63 -0
  127. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/tools.py +36 -0
  128. package/extensions/services/proxy/agentcp/samples/compute_agent/browser_user.py +41 -0
  129. package/extensions/services/proxy/agentcp/samples/deepseek/README.md +79 -0
  130. package/extensions/services/proxy/agentcp/samples/deepseek/create_profile.py +126 -0
  131. package/extensions/services/proxy/agentcp/samples/deepseek/deepseek.py +42 -0
  132. package/extensions/services/proxy/agentcp/samples/dify_chat/README.md +78 -0
  133. package/extensions/services/proxy/agentcp/samples/dify_chat/create_profile.py +126 -0
  134. package/extensions/services/proxy/agentcp/samples/dify_chat/dify_chat.py +47 -0
  135. package/extensions/services/proxy/agentcp/samples/dify_workflow/README.md +78 -0
  136. package/extensions/services/proxy/agentcp/samples/dify_workflow/create_profile.py +126 -0
  137. package/extensions/services/proxy/agentcp/samples/dify_workflow/dify_workflow.py +46 -0
  138. package/extensions/services/proxy/agentcp/samples/executor/README.md +44 -0
  139. package/extensions/services/proxy/agentcp/samples/executor/agentprofile.json +89 -0
  140. package/extensions/services/proxy/agentcp/samples/executor/create_profile.py +139 -0
  141. package/extensions/services/proxy/agentcp/samples/executor/main.py +160 -0
  142. package/extensions/services/proxy/agentcp/samples/filereader/README.md +45 -0
  143. package/extensions/services/proxy/agentcp/samples/filereader/agentprofile.json +90 -0
  144. package/extensions/services/proxy/agentcp/samples/filereader/create_profile.py +137 -0
  145. package/extensions/services/proxy/agentcp/samples/filereader/main.py +253 -0
  146. package/extensions/services/proxy/agentcp/samples/filewriter/README.md +38 -0
  147. package/extensions/services/proxy/agentcp/samples/filewriter/agentprofile.json +91 -0
  148. package/extensions/services/proxy/agentcp/samples/filewriter/create_profile.py +138 -0
  149. package/extensions/services/proxy/agentcp/samples/filewriter/main.py +289 -0
  150. package/extensions/services/proxy/agentcp/samples/hcp/README.md +85 -0
  151. package/extensions/services/proxy/agentcp/samples/hcp/acp_weather_agent.zip +0 -0
  152. package/extensions/services/proxy/agentcp/samples/hcp/create_profile.py +125 -0
  153. package/extensions/services/proxy/agentcp/samples/hcp/hcp.py +237 -0
  154. package/extensions/services/proxy/agentcp/samples/helloworld/README.md +68 -0
  155. package/extensions/services/proxy/agentcp/samples/helloworld/hello_world.py +40 -0
  156. package/extensions/services/proxy/agentcp/samples/llm_agent/MEADME.md +117 -0
  157. package/extensions/services/proxy/agentcp/samples/llm_agent/create_profile.py +125 -0
  158. package/extensions/services/proxy/agentcp/samples/llm_agent/qwen_agent.py +136 -0
  159. package/extensions/services/proxy/agentcp/samples/local_llm_agent/README.md +90 -0
  160. package/extensions/services/proxy/agentcp/samples/local_llm_agent/create_profile.py +125 -0
  161. package/extensions/services/proxy/agentcp/samples/local_llm_agent/main.py +49 -0
  162. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/README.md +55 -0
  163. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/create_profile.py +125 -0
  164. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/main.py +23 -0
  165. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/README.md +103 -0
  166. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/create_profile.py +125 -0
  167. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/main.py +69 -0
  168. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/README.md +58 -0
  169. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/create_profile.py +125 -0
  170. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/main.py +25 -0
  171. package/extensions/services/proxy/agentcp/samples/qwen3/README.md +71 -0
  172. package/extensions/services/proxy/agentcp/samples/qwen3/create_profile.py +126 -0
  173. package/extensions/services/proxy/agentcp/samples/qwen3/qwen3.py +37 -0
  174. package/extensions/services/proxy/agentcp/samples/qwen3_tools/README.md +133 -0
  175. package/extensions/services/proxy/agentcp/samples/qwen3_tools/create_profile.py +126 -0
  176. package/extensions/services/proxy/agentcp/samples/qwen3_tools/qwen3_tools.py +98 -0
  177. package/extensions/services/proxy/agentcp/samples/search/create_profile_qwen.py +125 -0
  178. package/extensions/services/proxy/agentcp/samples/search/create_profile_search.py +125 -0
  179. package/extensions/services/proxy/agentcp/samples/search/qwen_agent.py +136 -0
  180. package/extensions/services/proxy/agentcp/samples/search/search_agent.py +170 -0
  181. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/README.md +89 -0
  182. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/create_profile.py +125 -0
  183. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/main.py +44 -0
  184. package/extensions/services/proxy/agentcp/utils/__init__.py +15 -0
  185. package/extensions/services/proxy/agentcp/utils/file_util.py +117 -0
  186. package/extensions/services/proxy/agentcp/utils/proxy_bypass.py +99 -0
  187. package/extensions/services/proxy/agentcp/workflow.py +203 -0
  188. package/extensions/services/proxy/console_auth.py +109 -0
  189. package/extensions/services/proxy/evol/__init__.py +1 -0
  190. package/extensions/services/proxy/evol/config.py +37 -0
  191. package/extensions/services/proxy/evol/http/__init__.py +1 -0
  192. package/extensions/services/proxy/evol/http/async_http.py +551 -0
  193. package/extensions/services/proxy/evol/log.py +28 -0
  194. package/extensions/services/proxy/evol/presenter/__init__.py +2 -0
  195. package/extensions/services/proxy/evol/presenter/agentIdPresenter.py +1031 -0
  196. package/extensions/services/proxy/evol/presenter/apikeyPresenter.py +106 -0
  197. package/extensions/services/proxy/evol/presenter/configPresenter.py +1281 -0
  198. package/extensions/services/proxy/evol/presenter/userPresenter.py +477 -0
  199. package/extensions/services/proxy/evol/server/__init__.py +1 -0
  200. package/extensions/services/proxy/evol/server/claude_proxy_async.py +3430 -0
  201. package/extensions/services/proxy/evol/server/openclaw_proxy.py +1861 -0
  202. package/extensions/services/proxy/evol/server/proxy_config.py +15 -0
  203. package/extensions/services/proxy/evol/server/proxy_engine.py +501 -0
  204. package/extensions/services/proxy/evol/version.py +24 -0
  205. package/extensions/services/proxy/logs/websocket.log +260 -0
  206. package/extensions/services/proxy/main.py +240 -0
  207. package/extensions/services/proxy/requirements.txt +13 -0
  208. package/extensions/services/proxy/server.py +271 -0
  209. package/extensions/services/watchdog/entry.py +42 -16
  210. package/extensions/services/watchdog/module.md +1 -0
  211. package/extensions/services/watchdog/monitor.py +34 -4
  212. package/extensions/services/web/module.md +1 -1
  213. package/extensions/services/web/server.py +30 -18
  214. package/extensions/services/web/static/js/token-manager.js +10 -10
  215. package/kernel/entry.py +1 -1
  216. package/kernel/module.md +25 -1
  217. package/kernel/registry_store.py +2 -26
  218. package/kernel/rpc_router.py +36 -10
  219. package/kernel/server.py +106 -17
  220. package/kite_cli/commands/deps_install.py +67 -0
  221. package/kite_cli/commands/env_check.py +45 -0
  222. package/kite_cli/commands/prepare.py +49 -0
  223. package/kite_cli/commands/venv_setup.py +56 -0
  224. package/kite_cli/main.py +29 -1
  225. package/launcher/entry.py +306 -21
  226. package/launcher/module.md +9 -0
  227. package/launcher/module_scanner.py +11 -1
  228. package/main.py +4 -1
  229. package/package.json +8 -1
  230. package/python_version.json +4 -0
  231. package/requirements.txt +38 -0
  232. package/scripts/env-manager.js +328 -0
  233. package/scripts/python-env.js +79 -0
  234. package/scripts/scan_dependencies.py +461 -0
  235. package/scripts/setup-python-env.js +191 -0
package/kite_cli/main.py CHANGED
@@ -6,7 +6,7 @@ from kite_cli import __version__
6
6
  from kite_cli.utils.i18n import t
7
7
 
8
8
 
9
- KNOWN_COMMANDS = ["install", "uninstall", "update", "list", "search", "info", "log", "rollback", "clean", "doctor", "history"]
9
+ KNOWN_COMMANDS = ["install", "uninstall", "update", "list", "search", "info", "log", "rollback", "clean", "doctor", "history", "prepare", "env-check", "venv-setup", "deps-install"]
10
10
 
11
11
 
12
12
  class SilentArgumentParser(argparse.ArgumentParser):
@@ -46,6 +46,10 @@ Kite CLI v{__version__} — 模块安装管理工具
46
46
  rollback 回滚最后一次操作
47
47
  clean 清理临时文件和缓存
48
48
  doctor 检查下载工具状态
49
+ prepare 打包前准备(扫描依赖,生成锁定文件)
50
+ env-check 环境检查(虚拟环境和依赖库)
51
+ venv-setup 创建虚拟环境
52
+ deps-install 安装依赖库
49
53
 
50
54
  全局选项:
51
55
  -h, -H, --help 显示此帮助信息
@@ -218,6 +222,18 @@ def main():
218
222
  # doctor 命令
219
223
  doctor_parser = subparsers.add_parser("doctor", help="检查下载工具状态", add_help=True)
220
224
 
225
+ # prepare 命令
226
+ prepare_parser = subparsers.add_parser("prepare", help="打包前准备(扫描依赖)", add_help=True)
227
+
228
+ # env-check 命令
229
+ env_check_parser = subparsers.add_parser("env-check", help="环境检查(虚拟环境和依赖库)", add_help=True)
230
+
231
+ # venv-setup 命令
232
+ venv_setup_parser = subparsers.add_parser("venv-setup", help="创建虚拟环境", add_help=True)
233
+
234
+ # deps-install 命令
235
+ deps_install_parser = subparsers.add_parser("deps-install", help="安装依赖库", add_help=True)
236
+
221
237
  args, unknown = parser.parse_known_args()
222
238
 
223
239
  # 处理全局 --help / --version
@@ -280,6 +296,18 @@ def main():
280
296
  elif args.command == "doctor":
281
297
  from kite_cli.commands.doctor import run_doctor
282
298
  return run_doctor(args)
299
+ elif args.command == "prepare":
300
+ from kite_cli.commands.prepare import run_prepare
301
+ return run_prepare(args)
302
+ elif args.command == "env-check":
303
+ from kite_cli.commands.env_check import run_env_check
304
+ return run_env_check(args)
305
+ elif args.command == "venv-setup":
306
+ from kite_cli.commands.venv_setup import run_venv_setup
307
+ return run_venv_setup(args)
308
+ elif args.command == "deps-install":
309
+ from kite_cli.commands.deps_install import run_deps_install
310
+ return run_deps_install(args)
283
311
  else:
284
312
  print(f"未知命令: {args.command}")
285
313
  return 1
package/launcher/entry.py CHANGED
@@ -73,16 +73,25 @@ class Launcher:
73
73
  discovery=self._load_discovery(),
74
74
  )
75
75
 
76
+ # Load relay configuration
77
+ relay_config = self._load_relay_config()
78
+ self._relay_modules = relay_config.get("modules", [])
79
+ self._relay_token_limits = relay_config.get("token_limits", {})
80
+
76
81
  self.kernel_port: int = 0
77
82
  self.modules: dict[str, ModuleInfo] = {}
78
83
  self._shutdown_event = asyncio.Event()
79
84
  self._thread_shutdown = threading.Event()
80
85
  self._shutdown_complete = threading.Event() # Set when normal shutdown finishes
81
86
  self._module_tokens: dict[str, str] = {} # module_name -> per-module token
87
+ self._client_tokens: dict[str, str] = {} # virtual module_id -> kernel_token (for relay modules)
82
88
 
83
89
  # Three-layer state model: desired_state per module
84
90
  self._desired_states: dict[str, str] = {} # module_name -> "running" | "stopped"
85
91
 
92
+ # Relay module configuration (already loaded above, don't reinitialize)
93
+ # self._relay_modules and self._relay_token_limits are set in lines 78-79
94
+
86
95
  # Kernel WebSocket client
87
96
  self._ws: object | None = None
88
97
  self._ws_task: asyncio.Task | None = None
@@ -526,10 +535,14 @@ class Launcher:
526
535
  ready = await self._wait_event("module.ready", "kernel", timeout=15)
527
536
  if ready:
528
537
  self._graceful_modules["kernel"] = bool(ready.get("graceful_shutdown"))
529
- print("[launcher] Kernel 已就绪")
538
+ # Use startup_time from module.ready event
539
+ startup_time = ready.get("startup_time", time.monotonic() - t_ws)
540
+ self._ready_times["kernel"] = startup_time
541
+ startup_str = f"{startup_time:.3f}s" if startup_time < 10 else f"{startup_time:.2f}s"
542
+ print(f"[launcher] Kernel 已就绪 ({startup_str})")
530
543
  else:
531
544
  print("\033[91m[launcher] 警告: Kernel 在 15s 内未发送 module.ready\033[0m")
532
- self._ready_times["kernel"] = time.monotonic() - t_ws
545
+ self._ready_times["kernel"] = time.monotonic() - t_ws
533
546
 
534
547
  await asyncio.gather(
535
548
  _scan_and_generate_tokens(),
@@ -630,7 +643,7 @@ class Launcher:
630
643
  launcher_token = self._module_tokens.get("launcher", "")
631
644
  ws_url = f"ws://127.0.0.1:{self.kernel_port}/ws?token={launcher_token}&id=launcher"
632
645
  t_ws_connect = time.monotonic()
633
- async with websockets.connect(ws_url, open_timeout=3, ping_interval=20, ping_timeout=20, close_timeout=10) as ws:
646
+ async with websockets.connect(ws_url, open_timeout=3, ping_interval=None, close_timeout=10) as ws:
634
647
  self._ws = ws
635
648
  _ws_s = time.monotonic() - t_ws_connect
636
649
  print(f"[launcher] 已连接到 Kernel ({self._fmt_elapsed(_ws_s)})")
@@ -664,6 +677,7 @@ class Launcher:
664
677
  "restart_launcher": {"method": "restart_launcher", "description": "重启 Launcher"},
665
678
  "rescan": {"method": "rescan", "description": "重新扫描模块"},
666
679
  "shutdown": {"method": "shutdown", "description": "关闭系统"},
680
+ "request_client_token": {"method": "request_client_token", "description": "为 Web 客户端申请 Kernel Token"},
667
681
  },
668
682
  "module": {
669
683
  "config": {
@@ -692,10 +706,14 @@ class Launcher:
692
706
  print("[launcher] 已注册到 Kernel")
693
707
 
694
708
  # Publish module.ready for Launcher itself (every reconnect)
709
+ startup_time = time.monotonic() - self._t_start
695
710
  await self._publish_event("module.ready", {
696
711
  "module_id": "launcher",
697
712
  "graceful_shutdown": True,
713
+ "startup_time": startup_time,
698
714
  })
715
+ # Record launcher's own startup time
716
+ self._ready_times["launcher"] = startup_time
699
717
 
700
718
  # Signal that connection is ready (after subscription and registration)
701
719
  if self._ws_connected:
@@ -775,6 +793,17 @@ class Launcher:
775
793
  self._rpc_results[rpc_id] = msg
776
794
  waiter.set()
777
795
 
796
+ async def _handle_ping_event(self, data: dict):
797
+ """Handle system.ping event and reply with system.pong."""
798
+ t1 = data.get("ping_time")
799
+ t2 = time.time()
800
+
801
+ await self._publish_event("system.pong", {
802
+ "module_id": "launcher",
803
+ "ping_time": t1,
804
+ "pong_time": t2,
805
+ })
806
+
778
807
  async def _handle_event_notification(self, msg: dict):
779
808
  """Handle an event notification (JSON-RPC 2.0 Notification with method='event')."""
780
809
  params = msg.get("params", {})
@@ -783,6 +812,11 @@ class Launcher:
783
812
  data = params.get("data") if isinstance(params.get("data"), dict) else {}
784
813
  ts = params.get("timestamp", "")
785
814
 
815
+ # Handle system.ping event
816
+ if event == "system.ping":
817
+ await self._handle_ping_event(data)
818
+ return
819
+
786
820
  # Trigger event waiters
787
821
  module_id = data.get("module_id", "")
788
822
  waiter_key = f"{event}:{module_id}"
@@ -794,6 +828,14 @@ class Launcher:
794
828
 
795
829
  # module.exiting also wakes module.ready waiter
796
830
  if event == "module.exiting" and module_id:
831
+ # 处理 token 释放(如果是虚拟模块且标记了 token_revoked)
832
+ token_revoked = data.get("token_revoked", False)
833
+ if token_revoked and module_id in self._client_tokens:
834
+ del self._client_tokens[module_id]
835
+ print(f"[launcher] Token revoked for {module_id}")
836
+ # 记录审计日志
837
+ self._log_token_request("system", module_id, "revoke", True)
838
+
797
839
  ready_key = f"module.ready:{module_id}"
798
840
  ready_waiter = self._event_waiters.get(ready_key)
799
841
  if ready_waiter:
@@ -863,9 +905,11 @@ class Launcher:
863
905
  if step == "code_generated":
864
906
  code = data.get("code", "")
865
907
  expires_in = data.get("expires_in", 300)
908
+ module_id = data.get("module_id", "unknown")
866
909
  if code:
867
910
  print(f"[launcher] {GREEN}配对码: {code}{RESET}")
868
911
  print(f"[launcher] {GREEN}有效期: {expires_in} 秒{RESET}")
912
+ print(f"[launcher] {GREEN}来源模块: {module_id}{RESET}")
869
913
  print(f"[launcher] {GREEN}访问 Web 界面时使用此配对码进行配对{RESET}")
870
914
 
871
915
  elif step == "pairing":
@@ -922,6 +966,8 @@ class Launcher:
922
966
  "get_module_config": self._rpc_get_module_config,
923
967
  "update_module_config": self._rpc_update_module_config,
924
968
  "reset_module_config": self._rpc_reset_module_config,
969
+ "request_client_token": self._rpc_request_client_token,
970
+ "release_client_token": self._rpc_release_client_token,
925
971
  }
926
972
  handler = handlers.get(method)
927
973
  if handler:
@@ -943,10 +989,30 @@ class Launcher:
943
989
 
944
990
  async def _rpc_list_modules(self, params: dict) -> dict:
945
991
  """List all modules and their current status."""
992
+ # Get ping/pong latencies from Kernel
993
+ latencies = {}
994
+ try:
995
+ latencies_resp = await self._rpc_call(self._ws, "kernel.latencies", {}, timeout=2)
996
+ if "result" in latencies_resp:
997
+ latencies = latencies_resp["result"].get("latencies", {})
998
+ except Exception as e:
999
+ print(f"[launcher] Failed to get latencies: {e}")
1000
+
1001
+ current_time = time.time()
946
1002
  result = []
947
1003
  for name, info in self.modules.items():
948
1004
  running = self.process_manager.is_running(name)
949
1005
  rec = self.process_manager.get_record(name)
1006
+
1007
+ # Get latency info for this module
1008
+ latency_info = latencies.get(name, {})
1009
+ ping_status = latency_info.get("status", "never")
1010
+
1011
+ # Calculate uptime (running time in seconds)
1012
+ uptime_seconds = None
1013
+ if running and rec and rec.started_at:
1014
+ uptime_seconds = current_time - rec.started_at
1015
+
950
1016
  result.append({
951
1017
  "name": name,
952
1018
  "display_name": info.display_name,
@@ -956,10 +1022,20 @@ class Launcher:
956
1022
  "runtime": info.runtime,
957
1023
  "preferred_port": info.preferred_port,
958
1024
  "monitor": info.monitor,
1025
+ "display_order": info.display_order,
959
1026
  "desired_state": self._desired_states.get(name, "stopped"),
960
1027
  "actual_state": f"running({rec.pid})" if running and rec else "stopped",
961
1028
  "pid": rec.pid if running and rec else None,
1029
+ "startup_time": self._ready_times.get(name), # Module startup time in seconds
1030
+ "uptime_seconds": uptime_seconds, # Running time in seconds
1031
+ "ping_status": ping_status,
1032
+ "ping_outbound_ms": latency_info.get("outbound"),
1033
+ "ping_inbound_ms": latency_info.get("inbound"),
962
1034
  })
1035
+
1036
+ # Sort by display_order (descending), then by name (ascending)
1037
+ result.sort(key=lambda m: (-m["display_order"], m["name"]))
1038
+
963
1039
  return {"modules": result}
964
1040
 
965
1041
  async def _rpc_start_module(self, params: dict) -> dict:
@@ -1864,8 +1940,11 @@ class Launcher:
1864
1940
  print(f"[launcher] 模块 '{info.name}' 主动退出: {reason} ({elapsed:.2f}s)")
1865
1941
  elif ready:
1866
1942
  self._graceful_modules[info.name] = bool(ready.get("graceful_shutdown"))
1867
- self._ready_times[info.name] = elapsed
1868
- print(f"[launcher] 模块 '{info.name}' 已就绪 ({elapsed:.2f}s)")
1943
+ # Use startup_time from module.ready event (module's self-reported startup time)
1944
+ startup_time = ready.get("startup_time", elapsed)
1945
+ self._ready_times[info.name] = startup_time
1946
+ startup_str = f"{startup_time:.3f}s" if startup_time < 10 else f"{startup_time:.2f}s"
1947
+ print(f"[launcher] 模块 '{info.name}' 已就绪 ({startup_str})")
1869
1948
  else:
1870
1949
  print(f"\033[91m[launcher] 警告: '{info.name}' 在 {timeout}s 内未发送 module.ready\033[0m")
1871
1950
 
@@ -2150,9 +2229,6 @@ class Launcher:
2150
2229
  started_at=self._start_unix,
2151
2230
  )
2152
2231
  running.append(("launcher", launcher_info, launcher_rec))
2153
- # Launcher is ready immediately (ready_time = 0)
2154
- if "launcher" not in self._ready_times:
2155
- self._ready_times["launcher"] = 0.0
2156
2232
 
2157
2233
  for name, info in self.modules.items():
2158
2234
  rec = self.process_manager.get_record(name)
@@ -2223,7 +2299,16 @@ class Launcher:
2223
2299
  for name, info, rec in running_sorted:
2224
2300
  label = info.display_name or name
2225
2301
  ready_t = self._ready_times.get(name)
2226
- time_str = f"{ready_t:.2f}s" if ready_t is not None else "—"
2302
+ # Format startup time with ms/s auto-switch
2303
+ if ready_t is not None:
2304
+ if ready_t < 1:
2305
+ time_str = f"{ready_t * 1000:.0f}ms"
2306
+ elif ready_t < 10:
2307
+ time_str = f"{ready_t:.2f}s"
2308
+ else:
2309
+ time_str = f"{ready_t:.1f}s"
2310
+ else:
2311
+ time_str = "—"
2227
2312
 
2228
2313
  # Calculate elapsed from start
2229
2314
  if ready_t is not None and hasattr(self, '_start_unix'):
@@ -2295,18 +2380,53 @@ class Launcher:
2295
2380
 
2296
2381
  lines.append(f"{G} Kernel WS: ws://127.0.0.1:{self.kernel_port}/ws 实例: {self.instance_id}{R}")
2297
2382
 
2298
- # Query Kernel for web module's api_endpoint via RPC
2299
- web_url = ""
2300
- if self._ws:
2301
- try:
2302
- resp = await self._rpc_call(self._ws, "registry.get", {"path": "web.api_endpoint"}, timeout=3)
2303
- val = resp.get("result", {}).get("value")
2304
- if val and isinstance(val, str):
2305
- web_url = val.replace("://127.0.0.1:", "://localhost:")
2306
- except Exception:
2307
- pass
2308
- if web_url:
2309
- lines.append(f"{B} Web 管理后台: {web_url}{R}")
2383
+ # Check if web or evol modules are running and display their URLs
2384
+ RED = "\033[91m"
2385
+ running_names = {name for name, _, _ in running}
2386
+
2387
+ # Check web module
2388
+ if "web" in running_names:
2389
+ web_url = ""
2390
+ web_error = ""
2391
+ if self._ws:
2392
+ try:
2393
+ resp = await self._rpc_call(self._ws, "registry.get", {"path": "web.api_endpoint"}, timeout=3)
2394
+ val = resp.get("result", {}).get("value")
2395
+ if val and isinstance(val, str):
2396
+ web_url = val.replace("://127.0.0.1:", "://localhost:")
2397
+ else:
2398
+ web_error = "未注册到 Kernel Registry"
2399
+ except Exception as e:
2400
+ web_error = f"查询失败: {str(e)}"
2401
+ else:
2402
+ web_error = "Kernel 连接不可用"
2403
+
2404
+ if web_url:
2405
+ lines.append(f"{B} Web 管理后台: {web_url}{R}")
2406
+ else:
2407
+ lines.append(f"{RED} Web 管理后台: {web_error}{R}")
2408
+
2409
+ # Check evol module
2410
+ if "evol" in running_names:
2411
+ evol_url = ""
2412
+ evol_error = ""
2413
+ if self._ws:
2414
+ try:
2415
+ resp = await self._rpc_call(self._ws, "registry.get", {"path": "evol.api_endpoint"}, timeout=3)
2416
+ val = resp.get("result", {}).get("value")
2417
+ if val and isinstance(val, str):
2418
+ evol_url = val.replace("://127.0.0.1:", "://localhost:")
2419
+ else:
2420
+ evol_error = "未注册到 Kernel Registry"
2421
+ except Exception as e:
2422
+ evol_error = f"查询失败: {str(e)}"
2423
+ else:
2424
+ evol_error = "Kernel 连接不可用"
2425
+
2426
+ if evol_url:
2427
+ lines.append(f"{B} Evol: {evol_url}{R}")
2428
+ else:
2429
+ lines.append(f"{RED} Evol: {evol_error}{R}")
2310
2430
 
2311
2431
  # Instance info
2312
2432
  instances = self.process_manager.get_alive_instances()
@@ -2370,6 +2490,155 @@ class Launcher:
2370
2490
 
2371
2491
  print("\n".join(lines))
2372
2492
 
2493
+ async def _rpc_request_client_token(self, params: dict) -> dict:
2494
+ """为 relay 模块申请虚拟客户端 Token.
2495
+
2496
+ Args:
2497
+ params: {
2498
+ "module_id": str - 虚拟模块 ID (如 "web-client-a3f9e2")
2499
+ "_caller_id": str - 调用者模块 ID (由 Kernel 注入)
2500
+ }
2501
+
2502
+ Returns:
2503
+ {
2504
+ "token": str - Kernel Token (64 字符 hex)
2505
+ "module_id": str - 虚拟模块 ID
2506
+ }
2507
+
2508
+ Raises:
2509
+ PermissionError: 调用者不在白名单中
2510
+ ValueError: module_id 格式不合法或已存在
2511
+ RuntimeError: Token 限额已满
2512
+ """
2513
+ import re
2514
+
2515
+ # 1. 获取调用者 ID
2516
+ caller_id = params.get("_caller_id")
2517
+ if not caller_id:
2518
+ raise PermissionError("Missing _caller_id in params")
2519
+
2520
+ # 2. 权限检查
2521
+ if caller_id not in self._relay_modules:
2522
+ print(f"[launcher] DEBUG: 权限检查失败 - caller_id={caller_id}, relay_modules={self._relay_modules}")
2523
+ raise PermissionError(f"Permission denied: {caller_id} not in relay_modules whitelist")
2524
+
2525
+ # 3. 参数验证
2526
+ module_id = params.get("module_id")
2527
+ if not module_id:
2528
+ raise ValueError("module_id is required")
2529
+
2530
+ # 4. 命名规范验证
2531
+ expected_prefix = f"{caller_id}-client-"
2532
+ if not module_id.startswith(expected_prefix):
2533
+ raise ValueError(f"module_id must start with {expected_prefix}")
2534
+
2535
+ suffix = module_id[len(expected_prefix):]
2536
+ if not re.match(r'^[a-zA-Z0-9_-]+$', suffix):
2537
+ raise ValueError("Invalid module_id suffix")
2538
+
2539
+ # 5. 检查是否已存在(幂等)
2540
+ if module_id in self._client_tokens:
2541
+ print(f"[launcher] Token already exists for {module_id}, returning existing token")
2542
+ return {
2543
+ "token": self._client_tokens[module_id],
2544
+ "module_id": module_id
2545
+ }
2546
+
2547
+ # 6. 检查限额
2548
+ limit = self._relay_token_limits.get(caller_id, 100)
2549
+ current_count = sum(1 for mid in self._client_tokens if mid.startswith(expected_prefix))
2550
+ if current_count >= limit:
2551
+ raise RuntimeError(f"Token limit reached: {current_count}/{limit}")
2552
+
2553
+ # 7. 生成 token
2554
+ token = secrets.token_hex(32)
2555
+
2556
+ # 8. 注册到 Kernel
2557
+ try:
2558
+ result = await self._rpc_call(self._ws, "kernel.register_tokens", {module_id: token})
2559
+ if "error" in result:
2560
+ raise RuntimeError(f"Failed to register token: {result['error'].get('message', '')}")
2561
+ except Exception as e:
2562
+ raise RuntimeError(f"Failed to register token to Kernel: {e}")
2563
+
2564
+ # 9. 保存到本地映射
2565
+ self._client_tokens[module_id] = token
2566
+
2567
+ # 10. 记录审计日志
2568
+ self._log_token_request(caller_id, module_id, "request", True)
2569
+
2570
+ print(f"[launcher] Token generated for {module_id} (caller: {caller_id})")
2571
+
2572
+ return {
2573
+ "token": token,
2574
+ "module_id": module_id
2575
+ }
2576
+
2577
+ async def _rpc_release_client_token(self, params: dict) -> dict:
2578
+ """释放虚拟客户端 Token (可选).
2579
+
2580
+ Args:
2581
+ params: {
2582
+ "module_id": str - 虚拟模块 ID
2583
+ "_caller_id": str - 调用者模块 ID (由 Kernel 注入)
2584
+ }
2585
+
2586
+ Returns:
2587
+ {}
2588
+
2589
+ Raises:
2590
+ PermissionError: 调用者不在白名单中或 module_id 不属于调用者
2591
+ ValueError: module_id 格式不合法
2592
+ """
2593
+ # 1. 获取调用者 ID
2594
+ caller_id = params.get("_caller_id")
2595
+ if not caller_id:
2596
+ raise PermissionError("Missing _caller_id in params")
2597
+
2598
+ # 2. 权限检查
2599
+ if caller_id not in self._relay_modules:
2600
+ print(f"[launcher] DEBUG: 权限检查失败 - caller_id={caller_id}, relay_modules={self._relay_modules}")
2601
+ raise PermissionError(f"Permission denied: {caller_id} not in relay_modules whitelist")
2602
+
2603
+ # 3. 参数验证
2604
+ module_id = params.get("module_id")
2605
+ if not module_id:
2606
+ raise ValueError("module_id is required")
2607
+
2608
+ # 4. 验证所有权
2609
+ expected_prefix = f"{caller_id}-client-"
2610
+ if not module_id.startswith(expected_prefix):
2611
+ raise ValueError(f"module_id does not belong to {caller_id}")
2612
+
2613
+ # 5. 删除 token
2614
+ if module_id in self._client_tokens:
2615
+ del self._client_tokens[module_id]
2616
+ print(f"[launcher] Token released for {module_id} (caller: {caller_id})")
2617
+
2618
+ # 6. 记录审计日志
2619
+ self._log_token_request(caller_id, module_id, "release", True)
2620
+
2621
+ return {}
2622
+
2623
+ def _log_token_request(self, caller_id: str, module_id: str, action: str, success: bool):
2624
+ """记录 Token 申请/释放审计日志."""
2625
+ from datetime import datetime, timezone
2626
+ record = {
2627
+ "timestamp": datetime.now(timezone.utc).isoformat(),
2628
+ "caller_id": caller_id,
2629
+ "module_id": module_id,
2630
+ "action": action,
2631
+ "success": success
2632
+ }
2633
+ try:
2634
+ log_dir = os.path.join(os.environ.get("KITE_MODULE_DATA", ""), "log")
2635
+ os.makedirs(log_dir, exist_ok=True)
2636
+ log_file = os.path.join(log_dir, "token_requests.jsonl")
2637
+ with open(log_file, "a", encoding="utf-8") as f:
2638
+ f.write(json.dumps(record, ensure_ascii=False) + "\n")
2639
+ except Exception as e:
2640
+ print(f"[launcher] 警告: 写入 token 审计日志失败: {e}")
2641
+
2373
2642
  # ── Utilities ──
2374
2643
 
2375
2644
  def _load_discovery(self) -> dict | None:
@@ -2385,6 +2654,22 @@ class Launcher:
2385
2654
  print(f"[launcher] 警告: 读取发现配置失败: {e}")
2386
2655
  return None
2387
2656
 
2657
+ def _load_relay_config(self) -> dict:
2658
+ """Read relay config from launcher's own module.md."""
2659
+ md_path = os.path.join(os.environ["KITE_PROJECT"], "launcher", "module.md")
2660
+ try:
2661
+ with open(md_path, "r", encoding="utf-8") as f:
2662
+ fm = _parse_frontmatter(f.read())
2663
+ relay = fm.get("relay")
2664
+ if isinstance(relay, dict) and relay:
2665
+ print(f"[launcher] Relay 配置已加载: modules={relay.get('modules')}, token_limits={relay.get('token_limits')}")
2666
+ return relay
2667
+ else:
2668
+ print(f"[launcher] 警告: relay 配置为空或格式错误")
2669
+ except Exception as e:
2670
+ print(f"[launcher] 警告: 读取 relay 配置失败: {e}")
2671
+ return {}
2672
+
2388
2673
  def _log_lifecycle(self, event: str, module: str, **extra):
2389
2674
  """Append one JSONL line to lifecycle.jsonl."""
2390
2675
  from datetime import datetime, timezone
@@ -14,6 +14,15 @@ discovery:
14
14
  type: scan_dir
15
15
  path: test_modules
16
16
  enabled: true
17
+ relay:
18
+ # 允许申请动态 Token 的模块白名单
19
+ modules:
20
+ - evol
21
+ - web
22
+ # 每个 relay 模块的 Token 限额
23
+ token_limits:
24
+ evol: 100
25
+ web: 50
17
26
  monitor: true
18
27
  ---
19
28
  # Launcher
@@ -31,6 +31,7 @@ class ModuleInfo:
31
31
  preferred_port: int = 0 # 0 = OS assigns; non-zero = try this port first
32
32
  depends_on: list = field(default_factory=list) # module names this module depends on
33
33
  monitor: bool = True # whether Watchdog should monitor this module
34
+ display_order: int = 0 # 0-100, controls display order in module list (0=default, 100=highest)
34
35
  module_dir: str = "" # absolute path to the module directory
35
36
  launch: LaunchConfig = field(default_factory=LaunchConfig)
36
37
 
@@ -105,7 +106,8 @@ class ModuleScanner:
105
106
  # Note: launcher is not scanned (it's the scanner itself)
106
107
  kernel_dir = os.path.join(project_root, "kernel")
107
108
  if os.path.isdir(kernel_dir):
108
- self._scan_dir(kernel_dir, 0, modules)
109
+ # Kernel is at project root, check it directly
110
+ self._add_module(kernel_dir, modules)
109
111
  self._scan_dir(os.path.join(project_root, "extensions"), 2, modules)
110
112
 
111
113
  # Extra sources from discovery config
@@ -216,6 +218,13 @@ class ModuleScanner:
216
218
  monitor_val = fm.get("monitor", "true")
217
219
  monitor = str(monitor_val).lower() not in ("false", "0", "no")
218
220
 
221
+ # Parse display_order (0-100, default 0)
222
+ try:
223
+ display_order = int(fm.get("display_order", 0))
224
+ display_order = max(0, min(100, display_order)) # Clamp to [0, 100]
225
+ except (ValueError, TypeError):
226
+ display_order = 0
227
+
219
228
  # Parse optional launch config
220
229
  launch_raw = fm.get("launch", {})
221
230
  if not isinstance(launch_raw, dict):
@@ -254,6 +263,7 @@ class ModuleScanner:
254
263
  preferred_port=preferred_port,
255
264
  depends_on=depends_on,
256
265
  monitor=monitor,
266
+ display_order=display_order,
257
267
  module_dir=mod_dir,
258
268
  launch=launch,
259
269
  )
package/main.py CHANGED
@@ -2,12 +2,15 @@
2
2
  Kite development entry point.
3
3
  1. Run code stats
4
4
  2. Start launcher
5
+
6
+ 注意:环境检查已在 Node.js 层(cli.js)完成
5
7
  """
6
8
  import sys
7
9
  from pathlib import Path
8
10
 
9
11
  # Add project root to sys.path
10
- sys.path.insert(0, str(Path(__file__).parent))
12
+ project_root = Path(__file__).parent
13
+ sys.path.insert(0, str(project_root))
11
14
 
12
15
  # 1. Run code stats
13
16
  from launcher.count_lines import run_stats
package/package.json CHANGED
@@ -1,18 +1,25 @@
1
1
  {
2
2
  "name": "@agentunion/kite",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Kite framework launcher — start Kite from anywhere",
5
5
  "bin": {
6
6
  "kite": "./cli.js"
7
7
  },
8
+ "scripts": {
9
+ "postinstall": "node scripts/setup-python-env.js"
10
+ },
8
11
  "files": [
9
12
  "cli.js",
10
13
  "main.py",
14
+ "requirements.txt",
15
+ "python_version.json",
16
+ "dependencies_lock.json",
11
17
  "launcher/**",
12
18
  "kernel/**",
13
19
  "extensions/**",
14
20
  "kite_cli/**",
15
21
  "scripts/**",
22
+ "core/**",
16
23
  "CHANGELOG.md",
17
24
  "!**/__pycache__",
18
25
  "!**/__pycache__/**",
@@ -0,0 +1,4 @@
1
+ {
2
+ "version": "3.13.12",
3
+ "note": "此文件在开发环境打包时生成,记录开发环境的 Python 版本。生产环境必须使用相同版本。"
4
+ }
@@ -0,0 +1,38 @@
1
+ # Kite 框架核心依赖
2
+ # 安装方法:pip install -r requirements.txt
3
+
4
+ # Web 框架
5
+ fastapi>=0.115.0
6
+ uvicorn>=0.32.0
7
+ starlette>=0.41.0
8
+
9
+ # WebSocket
10
+ websockets>=13.0
11
+
12
+ # HTTP 客户端
13
+ httpx>=0.27.0
14
+ aiohttp>=3.10.0
15
+ requests>=2.32.0
16
+
17
+ # 配置解析
18
+ json5>=0.9.25
19
+ pyyaml>=6.0.2
20
+ python-dotenv>=1.0.0
21
+
22
+ # 数据验证
23
+ pydantic>=2.9.0
24
+
25
+ # 异步 IO
26
+ aiofiles>=24.1.0
27
+
28
+ # 进程管理
29
+ psutil>=6.1.0
30
+
31
+ # 加密
32
+ cryptography>=43.0.0
33
+
34
+ # 进度条
35
+ tqdm>=4.66.0
36
+
37
+ # 类型扩展
38
+ typing-extensions>=4.12.0