@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,498 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 改进的消息调度器
4
+ 支持线程池 + 异步任务池的混合架构
5
+ """
6
+
7
+ import asyncio
8
+ import threading
9
+ import time
10
+ from concurrent.futures import ThreadPoolExecutor
11
+ from typing import Callable, Awaitable, Dict, Any, Optional
12
+ from agentcp.base.log import log_info, log_error, log_exception, log_warning, log_debug
13
+
14
+
15
+ class ImprovedMessageScheduler:
16
+ """
17
+ 改进的消息调度器
18
+
19
+ 架构:
20
+ - 核心线程: core_workers 个 (常驻)
21
+ - 最大线程: max_workers 个 (高峰扩展)
22
+ - 每线程并发异步任务: max_tasks_per_worker 个
23
+
24
+ 优势:
25
+ - 资源高效: 少量线程处理大量异步任务
26
+ - 高并发: 总并发 = max_workers × max_tasks_per_worker
27
+ - 负载均衡: 自动选择负载最低的工作线程
28
+ """
29
+
30
+ def __init__(self,
31
+ core_workers: int = 20,
32
+ max_workers: int = 50,
33
+ max_tasks_per_worker: int = 10):
34
+ """
35
+ 初始化调度器
36
+
37
+ Args:
38
+ core_workers: 核心工作线程数 (常驻)
39
+ max_workers: 最大工作线程数 (高峰时扩展)
40
+ max_tasks_per_worker: 每个工作线程的最大并发异步任务数
41
+ """
42
+ self.core_workers = core_workers
43
+ self.max_workers = max_workers
44
+ self.max_tasks_per_worker = max_tasks_per_worker
45
+
46
+ # 线程池
47
+ self.thread_pool = ThreadPoolExecutor(
48
+ max_workers=max_workers,
49
+ thread_name_prefix="agentcp-worker"
50
+ )
51
+
52
+ # 工作线程状态
53
+ self.worker_loops: Dict[int, asyncio.AbstractEventLoop] = {} # worker_id -> event loop
54
+ self.worker_queues: Dict[int, asyncio.Queue] = {} # worker_id -> message queue
55
+ self.worker_tasks_count: Dict[int, int] = {} # worker_id -> active task count
56
+ self.worker_lock = threading.Lock()
57
+
58
+ # 统计信息
59
+ self.total_messages = 0
60
+ self.total_processed = 0
61
+ self.total_errors = 0
62
+ self.total_rejected = 0 # ✅ P1修复: 添加拒绝计数
63
+ self.active_workers = 0
64
+ self.is_running = True
65
+
66
+ # ✅ 修复: 添加统计锁,保护计数器的线程安全
67
+ self.stats_lock = threading.Lock()
68
+
69
+ # ✅ P1修复: 队列监控配置
70
+ self.queue_warn_threshold = 0.8 # 队列使用率警告阈值
71
+ self.queue_timeout = 5.0 # ✅ 增加: 队列等待超时从2秒增加到5秒
72
+ self.max_submit_retries = 3 # ✅ 新增: 提交失败时的最大重试次数
73
+
74
+ # 初始化核心工作线程
75
+ self._init_core_workers()
76
+
77
+ log_info(f"[Scheduler] 初始化完成: core_workers={core_workers}, "
78
+ f"max_workers={max_workers}, max_tasks_per_worker={max_tasks_per_worker}")
79
+
80
+ def _init_core_workers(self):
81
+ """初始化核心工作线程"""
82
+ for worker_id in range(self.core_workers):
83
+ self._start_worker(worker_id)
84
+
85
+ def _start_worker(self, worker_id: int):
86
+ """
87
+ ✅ P1修复: 启动一个工作线程 (使用Event同步)
88
+
89
+ Args:
90
+ worker_id: 工作线程ID
91
+ """
92
+ # 创建启动就绪事件
93
+ ready_event = threading.Event()
94
+
95
+ # 提交工作线程任务
96
+ self.thread_pool.submit(self._worker_main, worker_id, ready_event)
97
+
98
+ # ✅ 使用Event等待线程就绪 (最多等待5秒)
99
+ if not ready_event.wait(timeout=5.0):
100
+ log_error(f"[Worker-{worker_id}] 启动超时")
101
+ raise RuntimeError(f"Worker-{worker_id} failed to start within 5 seconds")
102
+
103
+ with self.worker_lock:
104
+ self.worker_tasks_count[worker_id] = 0
105
+ self.active_workers += 1
106
+
107
+ log_info(f"[Worker-{worker_id}] 启动成功")
108
+
109
+ def _worker_main(self, worker_id: int, ready_event: threading.Event = None):
110
+ """
111
+ ✅ P1修复: 工作线程主函数
112
+ 运行一个持久的事件循环,处理异步任务
113
+
114
+ Args:
115
+ worker_id: 工作线程ID
116
+ ready_event: 启动就绪事件 (用于同步启动)
117
+ """
118
+ # 创建新的事件循环
119
+ loop = asyncio.new_event_loop()
120
+ asyncio.set_event_loop(loop)
121
+
122
+ # ✅ 增加: 队列大小从1000增加到5000,提高并发容量
123
+ queue = asyncio.Queue(maxsize=5000)
124
+
125
+ # 注册到全局状态
126
+ with self.worker_lock:
127
+ self.worker_loops[worker_id] = loop
128
+ self.worker_queues[worker_id] = queue
129
+
130
+ log_info(f"[Worker-{worker_id}] 事件循环启动, thread={threading.current_thread().name}")
131
+
132
+ # ✅ 通知启动完成
133
+ if ready_event:
134
+ ready_event.set()
135
+
136
+ # 运行事件循环
137
+ try:
138
+ loop.run_until_complete(self._worker_loop(worker_id, queue))
139
+ except Exception as e:
140
+ log_exception(f"[Worker-{worker_id}] 事件循环异常: {e}")
141
+ finally:
142
+ # 清理
143
+ try:
144
+ # 取消所有待处理任务
145
+ pending = asyncio.all_tasks(loop)
146
+ for task in pending:
147
+ task.cancel()
148
+ # 等待取消完成
149
+ loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
150
+ loop.close()
151
+ except Exception as e:
152
+ log_error(f"[Worker-{worker_id}] 清理异常: {e}")
153
+
154
+ # 从全局状态移除
155
+ with self.worker_lock:
156
+ if worker_id in self.worker_loops:
157
+ del self.worker_loops[worker_id]
158
+ if worker_id in self.worker_queues:
159
+ del self.worker_queues[worker_id]
160
+ if worker_id in self.worker_tasks_count:
161
+ del self.worker_tasks_count[worker_id]
162
+ self.active_workers -= 1
163
+
164
+ log_info(f"[Worker-{worker_id}] 已停止")
165
+
166
+ async def _worker_loop(self, worker_id: int, queue: asyncio.Queue):
167
+ """✅ P0-3改进: 工作线程事件循环,优化任务拒绝逻辑
168
+
169
+ 改进要点:
170
+ 1. 移除"放回队列"的逻辑(会导致死锁)
171
+ 2. 任务超限时直接跳过(让 submit_message 重试其他 worker)
172
+ 3. 添加详细的日志
173
+
174
+ Args:
175
+ worker_id: 工作线程ID
176
+ queue: 消息队列
177
+ """
178
+ while self.is_running:
179
+ try:
180
+ # 等待新消息 (超时检查,避免卡死)
181
+ try:
182
+ message_handler, data = await asyncio.wait_for(queue.get(), timeout=1.0)
183
+ except asyncio.TimeoutError:
184
+ continue
185
+
186
+ # ✅ 检查是否超过任务限制
187
+ with self.worker_lock:
188
+ current_tasks = self.worker_tasks_count.get(worker_id, 0)
189
+
190
+ if current_tasks >= self.max_tasks_per_worker:
191
+ # ✅ P0-3改进: 不再尝试放回队列,直接记录拒绝
192
+ with self.stats_lock:
193
+ self.total_rejected += 1
194
+
195
+ message_id = data.get('message_id', 'unknown') if isinstance(data, dict) else 'unknown'
196
+ log_warning(
197
+ f"⚠️ [Worker-{worker_id}] 任务超限 "
198
+ f"({current_tasks}/{self.max_tasks_per_worker}), "
199
+ f"拒绝任务 message_id={message_id[:16] if len(message_id) > 16 else message_id}..."
200
+ )
201
+
202
+ # 短暂等待后继续取下一个任务
203
+ await asyncio.sleep(0.05)
204
+ continue
205
+
206
+ # 增加任务计数
207
+ self.worker_tasks_count[worker_id] = current_tasks + 1
208
+
209
+ # ✅ 关键:创建异步任务(不等待完成)
210
+ asyncio.create_task(
211
+ self._handle_message_wrapper(worker_id, message_handler, data)
212
+ )
213
+
214
+ except asyncio.CancelledError:
215
+ log_info(f"[Worker-{worker_id}] 收到取消信号")
216
+ break
217
+ except Exception as e:
218
+ log_exception(f"[Worker-{worker_id}] 事件循环异常: {e}")
219
+ await asyncio.sleep(0.1)
220
+
221
+ async def _handle_message_wrapper(self,
222
+ worker_id: int,
223
+ message_handler: Callable[[Dict], Awaitable[None]],
224
+ data: Dict[str, Any]):
225
+ """
226
+ 消息处理包装器
227
+ 处理完成后减少任务计数
228
+
229
+ Args:
230
+ worker_id: 工作线程ID
231
+ message_handler: 异步消息处理函数
232
+ data: 消息数据
233
+ """
234
+ try:
235
+ # 调用实际的消息处理函数
236
+ await message_handler(data)
237
+
238
+ # ✅ 修复: 使用锁保护统计计数器
239
+ with self.stats_lock:
240
+ self.total_processed += 1
241
+
242
+ except Exception as e:
243
+ # ✅ 修复: 使用锁保护统计计数器
244
+ with self.stats_lock:
245
+ self.total_errors += 1
246
+ log_exception(f"[Worker-{worker_id}] 消息处理失败: {e}")
247
+ finally:
248
+ # 减少任务计数
249
+ with self.worker_lock:
250
+ current = self.worker_tasks_count.get(worker_id, 0)
251
+ self.worker_tasks_count[worker_id] = max(0, current - 1)
252
+
253
+ def submit_message(self,
254
+ message_handler: Callable[[Dict], Awaitable[None]],
255
+ data: Dict[str, Any],
256
+ raise_on_reject: bool = False) -> bool:
257
+ """✅ P0-3改进: 提交消息到调度器(多候选 worker + 队列监控)
258
+
259
+ 改进要点:
260
+ 1. 选择多个候选 worker 而不是单个
261
+ 2. 依次尝试候选 worker,失败时自动切换
262
+ 3. 检查队列使用率,跳过接近满的 worker
263
+ 4. 优化重试逻辑和等待时间
264
+
265
+ Args:
266
+ message_handler: 异步消息处理函数 async def handler(data)
267
+ data: 消息数据
268
+ raise_on_reject: 如果为True,在任务被拒绝时抛出异常;否则返回False
269
+
270
+ Returns:
271
+ bool: True表示提交成功,False表示被拒绝
272
+
273
+ Raises:
274
+ RuntimeError: 当raise_on_reject=True且任务被拒绝时抛出
275
+ """
276
+ # ✅ 统计计数
277
+ with self.stats_lock:
278
+ self.total_messages += 1
279
+
280
+ # ✅ P0-3改进: 重试机制,每次尝试多个候选 worker
281
+ last_error = None
282
+ for retry_attempt in range(self.max_submit_retries):
283
+ try:
284
+ # ✅ 获取负载最低的 TOP 3 worker
285
+ candidate_workers = self._select_workers_by_load(top_n=3)
286
+
287
+ if not candidate_workers:
288
+ error_msg = "[Scheduler] 没有可用的工作线程"
289
+ log_error(error_msg)
290
+ with self.stats_lock:
291
+ self.total_rejected += 1
292
+ if raise_on_reject:
293
+ raise RuntimeError(error_msg)
294
+ return False
295
+
296
+ # ✅ 依次尝试候选 worker
297
+ submitted = False
298
+ for worker_id in candidate_workers:
299
+ loop = self.worker_loops.get(worker_id)
300
+ queue = self.worker_queues.get(worker_id)
301
+
302
+ if not loop or not queue or loop.is_closed():
303
+ continue
304
+
305
+ # ✅ 检查队列使用率
306
+ queue_size = queue.qsize()
307
+ queue_maxsize = queue.maxsize
308
+ usage_rate = queue_size / queue_maxsize if queue_maxsize > 0 else 0
309
+
310
+ # ✅ 如果队列使用率超过 90%,跳过这个 worker
311
+ if usage_rate >= 0.9:
312
+ log_warning(
313
+ f"⚠️ [Worker-{worker_id}] 队列接近满 "
314
+ f"({queue_size}/{queue_maxsize}, {usage_rate*100:.1f}%), 尝试下一个"
315
+ )
316
+ continue
317
+
318
+ # ✅ 尝试提交
319
+ try:
320
+ future = asyncio.run_coroutine_threadsafe(
321
+ self._put_with_timeout(queue, message_handler, data),
322
+ loop
323
+ )
324
+ future.result(timeout=self.queue_timeout)
325
+
326
+ # ✅ 提交成功
327
+ submitted = True
328
+ log_debug(f"✅ [Scheduler] 消息已提交到 Worker-{worker_id}")
329
+ return True
330
+
331
+ except Exception as e:
332
+ log_debug(f"⚠️ [Worker-{worker_id}] 提交失败: {e}, 尝试下一个")
333
+ continue
334
+
335
+ if submitted:
336
+ return True
337
+
338
+ # ✅ 所有候选 worker 都失败
339
+ last_error = "所有候选 worker 都无法接收任务"
340
+
341
+ except Exception as e:
342
+ last_error = str(e)
343
+
344
+ # ✅ 重试前等待(指数退避)
345
+ if retry_attempt < self.max_submit_retries - 1:
346
+ wait_time = 0.05 * (2 ** retry_attempt) # 指数退避: 0.05s, 0.1s, 0.2s
347
+ log_warning(
348
+ f"⚠️ [Scheduler] 提交失败 (第{retry_attempt + 1}次), "
349
+ f"{wait_time}s 后重试... reason={last_error}"
350
+ )
351
+ time.sleep(wait_time)
352
+
353
+ # ✅ 所有重试都失败
354
+ with self.stats_lock:
355
+ self.total_rejected += 1
356
+
357
+ error_msg = f"[Scheduler] 消息提交最终失败: {last_error}"
358
+ log_error(error_msg)
359
+
360
+ if raise_on_reject:
361
+ raise RuntimeError(error_msg)
362
+
363
+ return False
364
+
365
+ async def _put_with_timeout(self, queue: asyncio.Queue, message_handler, data):
366
+ """
367
+ ✅ P1修复: 带超时的队列put操作
368
+
369
+ Args:
370
+ queue: 目标队列
371
+ message_handler: 消息处理器
372
+ data: 消息数据
373
+ """
374
+ try:
375
+ await asyncio.wait_for(
376
+ queue.put((message_handler, data)),
377
+ timeout=self.queue_timeout
378
+ )
379
+ except asyncio.TimeoutError:
380
+ raise Exception(f"队列已满,等待超时 ({self.queue_timeout}s)")
381
+
382
+ def _select_worker(self) -> Optional[int]:
383
+ """
384
+ 选择负载最低的工作线程(保留向后兼容)
385
+
386
+ Returns:
387
+ worker_id 或 None (如果没有可用worker)
388
+ """
389
+ with self.worker_lock:
390
+ if not self.worker_tasks_count:
391
+ return None
392
+
393
+ # 找到任务数最少的worker
394
+ min_tasks = float('inf')
395
+ selected_worker = None
396
+
397
+ for worker_id, task_count in self.worker_tasks_count.items():
398
+ if task_count < min_tasks:
399
+ min_tasks = task_count
400
+ selected_worker = worker_id
401
+
402
+ # 如果找到空闲worker,直接使用
403
+ if task_count == 0:
404
+ break
405
+
406
+ return selected_worker
407
+
408
+ def _select_workers_by_load(self, top_n: int = 3) -> list:
409
+ """✅ P0-3新增: 选择负载最低的 TOP N worker
410
+
411
+ Args:
412
+ top_n: 返回前 N 个负载最低的 worker
413
+
414
+ Returns:
415
+ worker_id 列表,按负载从低到高排序
416
+ """
417
+ with self.worker_lock:
418
+ if not self.worker_tasks_count:
419
+ return []
420
+
421
+ # 按任务数排序(从少到多)
422
+ sorted_workers = sorted(
423
+ self.worker_tasks_count.items(),
424
+ key=lambda x: x[1] # x[1] 是任务数
425
+ )
426
+
427
+ # 返回前 N 个 worker 的 ID
428
+ return [worker_id for worker_id, _ in sorted_workers[:top_n]]
429
+
430
+ def get_stats(self) -> Dict[str, Any]:
431
+ """
432
+ ✅ 修复: 获取统计信息 (线程安全)
433
+
434
+ Returns:
435
+ 统计数据字典
436
+ """
437
+ # 获取 worker 统计
438
+ with self.worker_lock:
439
+ total_active_tasks = sum(self.worker_tasks_count.values())
440
+ worker_details = dict(self.worker_tasks_count)
441
+ active_workers = self.active_workers
442
+
443
+ # 获取全局统计
444
+ with self.stats_lock:
445
+ total_messages = self.total_messages
446
+ total_processed = self.total_processed
447
+ total_errors = self.total_errors
448
+ total_rejected = self.total_rejected # ✅ P1修复: 添加拒绝统计
449
+
450
+ return {
451
+ 'total_messages': total_messages,
452
+ 'total_processed': total_processed,
453
+ 'total_errors': total_errors,
454
+ 'total_rejected': total_rejected, # ✅ P1修复
455
+ 'active_workers': active_workers,
456
+ 'total_active_tasks': total_active_tasks,
457
+ 'worker_tasks': worker_details,
458
+ 'success_rate': f"{(total_processed / max(1, total_messages)) * 100:.2f}%"
459
+ }
460
+
461
+ def print_stats(self):
462
+ """✅ P1修复: 打印统计信息 (包含拒绝数)"""
463
+ stats = self.get_stats()
464
+ log_info(f"[Scheduler Stats] "
465
+ f"Messages: {stats['total_messages']}, "
466
+ f"Processed: {stats['total_processed']}, "
467
+ f"Errors: {stats['total_errors']}, "
468
+ f"Rejected: {stats['total_rejected']}, "
469
+ f"Active Workers: {stats['active_workers']}, "
470
+ f"Active Tasks: {stats['total_active_tasks']}, "
471
+ f"Success Rate: {stats['success_rate']}")
472
+
473
+ def shutdown(self, wait: bool = True):
474
+ """
475
+ 关闭调度器
476
+
477
+ Args:
478
+ wait: 是否等待所有任务完成
479
+ """
480
+ log_info("[Scheduler] 正在关闭...")
481
+ self.is_running = False
482
+
483
+ if wait:
484
+ # 等待一段时间让任务完成
485
+ max_wait = 10 # 最多等待10秒
486
+ for i in range(max_wait):
487
+ stats = self.get_stats()
488
+ if stats['total_active_tasks'] == 0:
489
+ break
490
+ log_info(f"[Scheduler] 等待任务完成... 剩余 {stats['total_active_tasks']} 个")
491
+ time.sleep(1)
492
+
493
+ # 关闭线程池
494
+ self.thread_pool.shutdown(wait=wait)
495
+
496
+ # 打印最终统计
497
+ self.print_stats()
498
+ log_info("[Scheduler] 已关闭")