@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
@@ -0,0 +1,490 @@
1
+ """
2
+ Kite 环境检查器(版本锁定)
3
+
4
+ 负责检查和准备 Python 运行环境:
5
+ 1. 检查 Python 版本是否与 python_version.json 一致
6
+ 2. 检查虚拟环境是否存在
7
+ 3. 检查依赖库是否与 dependencies_lock.json 一致
8
+ 4. 维护状态记录文件 ~/.kite/env_status.json
9
+
10
+ 状态记录机制:
11
+ - 成功时:记录 status = "success",下次启动跳过检查
12
+ - 失败时:记录 status = "failed" + 结构化错误信息,下次启动进行完整诊断
13
+ """
14
+
15
+ import hashlib
16
+ import json
17
+ import os
18
+ import subprocess
19
+ import sys
20
+ from datetime import datetime, timezone
21
+ from pathlib import Path
22
+ from typing import Dict, Optional, Tuple
23
+
24
+
25
+ def get_kite_data_path() -> Path:
26
+ """获取 KITE_DATA 路径"""
27
+ return Path.home() / ".kite"
28
+
29
+
30
+ def get_venv_path() -> Path:
31
+ """获取虚拟环境路径"""
32
+ return get_kite_data_path() / "venv"
33
+
34
+
35
+ def get_status_file_path() -> Path:
36
+ """获取状态记录文件路径"""
37
+ return get_kite_data_path() / "env_status.json"
38
+
39
+
40
+ def get_requirements_path() -> Path:
41
+ """获取 requirements.txt 路径(已废弃,使用 dependencies_lock.json)"""
42
+ return Path(__file__).parent.parent / "requirements.txt"
43
+
44
+
45
+ def get_dependencies_lock_path() -> Path:
46
+ """获取依赖锁定文件路径"""
47
+ return Path(__file__).parent.parent / "dependencies_lock.json"
48
+
49
+
50
+ def get_python_version_file_path() -> Path:
51
+ """获取 Python 版本文件路径"""
52
+ return Path(__file__).parent.parent / "python_version.json"
53
+
54
+
55
+ def load_dependencies_lock() -> Optional[Dict]:
56
+ """加载依赖锁定文件"""
57
+ lock_file = get_dependencies_lock_path()
58
+
59
+ if not lock_file.exists():
60
+ return None
61
+
62
+ try:
63
+ with open(lock_file, "r", encoding="utf-8") as f:
64
+ return json.load(f)
65
+ except Exception as e:
66
+ print(f"[环境检查] 读取依赖锁定文件失败: {e}")
67
+ return None
68
+
69
+
70
+ def get_required_python_version() -> Optional[str]:
71
+ """获取要求的 Python 版本"""
72
+ version_file = get_python_version_file_path()
73
+
74
+ if not version_file.exists():
75
+ return None
76
+
77
+ try:
78
+ with open(version_file, "r", encoding="utf-8") as f:
79
+ data = json.load(f)
80
+ return data.get("version")
81
+ except Exception as e:
82
+ print(f"[环境检查] 读取 Python 版本文件失败: {e}")
83
+ return None
84
+
85
+
86
+ def calculate_requirements_hash() -> str:
87
+ """计算 dependencies_lock.json 的 hash"""
88
+ lock_file = get_dependencies_lock_path()
89
+
90
+ if not lock_file.exists():
91
+ return ""
92
+
93
+ with open(lock_file, "rb") as f:
94
+ return hashlib.sha256(f.read()).hexdigest()[:16]
95
+
96
+
97
+ def load_status() -> Optional[Dict]:
98
+ """加载状态记录"""
99
+ status_file = get_status_file_path()
100
+
101
+ if not status_file.exists():
102
+ return None
103
+
104
+ try:
105
+ with open(status_file, "r", encoding="utf-8") as f:
106
+ return json.load(f)
107
+ except Exception as e:
108
+ print(f"[环境检查] 读取状态文件失败: {e}")
109
+ return None
110
+
111
+
112
+ def save_status(status_data: Dict):
113
+ """保存状态记录"""
114
+ status_file = get_status_file_path()
115
+ status_file.parent.mkdir(parents=True, exist_ok=True)
116
+
117
+ with open(status_file, "w", encoding="utf-8") as f:
118
+ json.dump(status_data, f, indent=2, ensure_ascii=False)
119
+
120
+
121
+ def check_python_version() -> Tuple[bool, str, str]:
122
+ """
123
+ 检查 Python 版本是否与要求一致
124
+
125
+ Returns:
126
+ (is_match, current_version, required_version)
127
+ """
128
+ current_version = sys.version_info
129
+ current_version_str = f"{current_version.major}.{current_version.minor}.{current_version.micro}"
130
+
131
+ required_version = get_required_python_version()
132
+
133
+ if not required_version:
134
+ # 没有版本要求文件,降级为 3.8+ 检查
135
+ if current_version.major == 3 and current_version.minor >= 8:
136
+ return True, current_version_str, "3.8+"
137
+ else:
138
+ return False, current_version_str, "3.8+"
139
+
140
+ # 精确版本匹配
141
+ if current_version_str == required_version:
142
+ return True, current_version_str, required_version
143
+ else:
144
+ return False, current_version_str, required_version
145
+
146
+
147
+ def check_venv_exists() -> Tuple[bool, str]:
148
+ """检查虚拟环境是否存在"""
149
+ venv_path = get_venv_path()
150
+
151
+ if not venv_path.exists():
152
+ return False, "虚拟环境目录不存在"
153
+
154
+ # 检查关键文件
155
+ if os.name == "nt": # Windows
156
+ python_exe = venv_path / "Scripts" / "python.exe"
157
+ pip_exe = venv_path / "Scripts" / "pip.exe"
158
+ else: # Linux / macOS
159
+ python_exe = venv_path / "bin" / "python"
160
+ pip_exe = venv_path / "bin" / "pip"
161
+
162
+ if not python_exe.exists():
163
+ return False, "虚拟环境 python 不存在"
164
+
165
+ if not pip_exe.exists():
166
+ return False, "虚拟环境 pip 不存在"
167
+
168
+ return True, "虚拟环境正常"
169
+
170
+
171
+ def create_venv() -> bool:
172
+ """创建虚拟环境"""
173
+ venv_path = get_venv_path()
174
+
175
+ print(f"[环境检查] 正在创建虚拟环境: {venv_path}")
176
+
177
+ try:
178
+ venv_path.parent.mkdir(parents=True, exist_ok=True)
179
+
180
+ subprocess.run(
181
+ [sys.executable, "-m", "venv", str(venv_path)],
182
+ check=True,
183
+ capture_output=True,
184
+ text=True
185
+ )
186
+
187
+ print(f"[环境检查] 虚拟环境创建成功")
188
+ return True
189
+
190
+ except subprocess.CalledProcessError as e:
191
+ print(f"[环境检查] 虚拟环境创建失败: {e.stderr}")
192
+ return False
193
+
194
+
195
+ def get_venv_pip() -> Path:
196
+ """获取虚拟环境的 pip 路径"""
197
+ venv_path = get_venv_path()
198
+
199
+ if os.name == "nt": # Windows
200
+ return venv_path / "Scripts" / "pip.exe"
201
+ else: # Linux / macOS
202
+ return venv_path / "bin" / "pip"
203
+
204
+
205
+ def check_dependencies_installed() -> Tuple[bool, str, Dict]:
206
+ """
207
+ 检查依赖是否与 dependencies_lock.json 一致
208
+
209
+ Returns:
210
+ (all_ok, message, details)
211
+ """
212
+ lock_data = load_dependencies_lock()
213
+
214
+ if not lock_data:
215
+ return True, "无依赖锁定文件", {}
216
+
217
+ dependencies = lock_data.get("dependencies", {})
218
+
219
+ if not dependencies:
220
+ return True, "无依赖要求", {}
221
+
222
+ pip_path = get_venv_pip()
223
+
224
+ if not pip_path.exists():
225
+ return False, "pip 不存在", {}
226
+
227
+ # 检查每个依赖(使用虚拟环境的 pip)
228
+ missing = []
229
+ version_mismatch = []
230
+
231
+ for package_name, package_info in dependencies.items():
232
+ required_version = package_info.get("version")
233
+
234
+ # 使用虚拟环境的 pip show 检查包
235
+ try:
236
+ result = subprocess.run(
237
+ [str(pip_path), "show", package_name],
238
+ capture_output=True,
239
+ text=True,
240
+ timeout=10
241
+ )
242
+
243
+ if result.returncode != 0:
244
+ # 包不存在
245
+ missing.append({
246
+ "package": package_name,
247
+ "required": required_version
248
+ })
249
+ else:
250
+ # 解析 pip show 输出获取版本
251
+ installed_version = None
252
+ for line in result.stdout.split('\n'):
253
+ if line.startswith('Version:'):
254
+ installed_version = line.split(':', 1)[1].strip()
255
+ break
256
+
257
+ if installed_version and installed_version != required_version:
258
+ version_mismatch.append({
259
+ "package": package_name,
260
+ "required": required_version,
261
+ "installed": installed_version
262
+ })
263
+
264
+ except subprocess.TimeoutExpired:
265
+ # 超时视为缺失
266
+ missing.append({
267
+ "package": package_name,
268
+ "required": required_version
269
+ })
270
+ except Exception:
271
+ # 其他错误视为缺失
272
+ missing.append({
273
+ "package": package_name,
274
+ "required": required_version
275
+ })
276
+
277
+ if missing or version_mismatch:
278
+ details = {
279
+ "missing": missing,
280
+ "version_mismatch": version_mismatch
281
+ }
282
+
283
+ msg_parts = []
284
+ if missing:
285
+ msg_parts.append(f"{len(missing)} 个缺失")
286
+ if version_mismatch:
287
+ msg_parts.append(f"{len(version_mismatch)} 个版本不匹配")
288
+
289
+ return False, ", ".join(msg_parts), details
290
+
291
+ return True, "依赖正常", {}
292
+
293
+
294
+ def install_dependencies() -> bool:
295
+ """安装依赖(根据 dependencies_lock.json)"""
296
+ lock_data = load_dependencies_lock()
297
+
298
+ if not lock_data:
299
+ print("[环境检查] 无依赖锁定文件,跳过依赖安装")
300
+ return True
301
+
302
+ dependencies = lock_data.get("dependencies", {})
303
+
304
+ if not dependencies:
305
+ print("[环境检查] 无依赖要求,跳过依赖安装")
306
+ return True
307
+
308
+ pip_path = get_venv_pip()
309
+
310
+ if not pip_path.exists():
311
+ print(f"[环境检查] pip 不存在: {pip_path}")
312
+ return False
313
+
314
+ print(f"[环境检查] 正在安装 {len(dependencies)} 个依赖...")
315
+
316
+ # 构建安装列表
317
+ install_list = [
318
+ f"{package}=={info['version']}"
319
+ for package, info in dependencies.items()
320
+ ]
321
+
322
+ try:
323
+ subprocess.run(
324
+ [str(pip_path), "install"] + install_list,
325
+ check=True,
326
+ capture_output=False, # 显示安装进度
327
+ text=True
328
+ )
329
+
330
+ print("[环境检查] 依赖安装完成")
331
+ return True
332
+
333
+ except subprocess.CalledProcessError as e:
334
+ print(f"[环境检查] 依赖安装失败")
335
+ return False
336
+
337
+
338
+ def run_full_check() -> Tuple[bool, Dict]:
339
+ """运行完整的环境检查"""
340
+ print("[环境检查] 开始完整诊断...")
341
+
342
+ # 1. 检查 Python 版本
343
+ py_ok, py_version, required_version = check_python_version()
344
+ if not py_ok:
345
+ error_data = {
346
+ "type": "wrong_version",
347
+ "message": f"Python 版本不匹配: {py_version}(需要 {required_version})",
348
+ "details": {
349
+ "current": py_version,
350
+ "required": required_version
351
+ }
352
+ }
353
+ return False, error_data
354
+
355
+ print(f"[环境检查] ✓ Python 版本: {py_version}")
356
+
357
+ # 2. 检查虚拟环境
358
+ venv_ok, venv_msg = check_venv_exists()
359
+ if not venv_ok:
360
+ print(f"[环境检查] ✗ 虚拟环境: {venv_msg}")
361
+
362
+ # 尝试创建虚拟环境
363
+ if not create_venv():
364
+ error_data = {
365
+ "type": "venv_failed",
366
+ "message": "虚拟环境创建失败",
367
+ "details": {"reason": venv_msg}
368
+ }
369
+ return False, error_data
370
+
371
+ venv_ok, venv_msg = check_venv_exists()
372
+
373
+ print(f"[环境检查] ✓ 虚拟环境: {venv_msg}")
374
+
375
+ # 3. 检查依赖
376
+ deps_ok, deps_msg, deps_details = check_dependencies_installed()
377
+ if not deps_ok:
378
+ print(f"[环境检查] ✗ 依赖库: {deps_msg}")
379
+
380
+ if deps_details:
381
+ if deps_details.get("missing"):
382
+ print(f" 缺失: {len(deps_details['missing'])} 个")
383
+ if deps_details.get("version_mismatch"):
384
+ print(f" 版本不匹配: {len(deps_details['version_mismatch'])} 个")
385
+
386
+ # 尝试安装依赖
387
+ if not install_dependencies():
388
+ error_data = {
389
+ "type": "missing_dependencies",
390
+ "message": "依赖安装失败",
391
+ "details": deps_details
392
+ }
393
+ return False, error_data
394
+
395
+ deps_ok, deps_msg, deps_details = check_dependencies_installed()
396
+
397
+ print(f"[环境检查] ✓ 依赖库: {deps_msg}")
398
+
399
+ print("[环境检查] 完整诊断通过")
400
+ return True, {}
401
+
402
+
403
+ def should_run_full_check(status: Optional[Dict]) -> Tuple[bool, str]:
404
+ """判断是否需要运行完整检查"""
405
+
406
+ # 1. 状态文件不存在
407
+ if status is None:
408
+ return True, "首次运行"
409
+
410
+ # 2. 上次检查失败
411
+ if status.get("status") != "success":
412
+ return True, "上次检查失败"
413
+
414
+ # 3. dependencies_lock.json hash 变化
415
+ current_hash = calculate_requirements_hash()
416
+ saved_hash = status.get("dependencies", {}).get("lock_hash", "")
417
+
418
+ if current_hash != saved_hash:
419
+ return True, "依赖锁定文件已更新"
420
+
421
+ # 4. 虚拟环境目录被删除
422
+ venv_ok, _ = check_venv_exists()
423
+ if not venv_ok:
424
+ return True, "虚拟环境不存在"
425
+
426
+ return False, "环境正常"
427
+
428
+
429
+ def check_environment() -> bool:
430
+ """
431
+ 检查环境是否就绪
432
+
433
+ Returns:
434
+ True if ready, False otherwise
435
+ """
436
+ # 1. 加载状态
437
+ status = load_status()
438
+
439
+ # 2. 判断是否需要完整检查
440
+ need_check, reason = should_run_full_check(status)
441
+
442
+ if not need_check:
443
+ print(f"[环境检查] 跳过检查({reason})")
444
+ return True
445
+
446
+ print(f"[环境检查] 需要完整检查({reason})")
447
+
448
+ # 3. 运行完整检查
449
+ success, error_data = run_full_check()
450
+
451
+ # 4. 保存状态
452
+ py_ok, py_version, required_version = check_python_version()
453
+ venv_path = get_venv_path()
454
+
455
+ status_data = {
456
+ "version": "1.0",
457
+ "last_check": datetime.now(timezone.utc).isoformat(),
458
+ "status": "success" if success else "failed",
459
+ "python": {
460
+ "path": str(venv_path / ("Scripts/python.exe" if os.name == "nt" else "bin/python")),
461
+ "version": py_version,
462
+ "required_version": required_version,
463
+ "system_python": sys.executable
464
+ },
465
+ "venv": {
466
+ "path": str(venv_path),
467
+ "exists": venv_path.exists(),
468
+ "valid": success
469
+ },
470
+ "dependencies": {
471
+ "installed": success,
472
+ "lock_hash": calculate_requirements_hash()
473
+ },
474
+ "error": error_data if not success else None
475
+ }
476
+
477
+ save_status(status_data)
478
+
479
+ if success:
480
+ print("[环境检查] 环境就绪")
481
+ else:
482
+ print(f"[环境检查] 环境检查失败: {error_data.get('message')}")
483
+
484
+ return success
485
+
486
+
487
+ if __name__ == "__main__":
488
+ # 可以单独运行此脚本进行环境检查
489
+ success = check_environment()
490
+ sys.exit(0 if success else 1)
@@ -0,0 +1,128 @@
1
+ {
2
+ "scan_time": "2026-03-08T16:23:35.649703+00:00",
3
+ "python_version": "3.13.12",
4
+ "dependencies": {
5
+ "agentcp": {
6
+ "version": "0.1.47",
7
+ "used_by": [
8
+ "extensions"
9
+ ]
10
+ },
11
+ "aiofiles": {
12
+ "version": "25.1.0",
13
+ "used_by": [
14
+ "extensions"
15
+ ]
16
+ },
17
+ "aiohttp": {
18
+ "version": "3.13.3",
19
+ "used_by": [
20
+ "extensions"
21
+ ]
22
+ },
23
+ "certifi": {
24
+ "version": "2025.4.26",
25
+ "used_by": [
26
+ "extensions"
27
+ ]
28
+ },
29
+ "cryptography": {
30
+ "version": "44.0.3",
31
+ "used_by": [
32
+ "extensions"
33
+ ]
34
+ },
35
+ "python-dotenv": {
36
+ "version": "1.1.0",
37
+ "used_by": [
38
+ "launcher",
39
+ "extensions"
40
+ ]
41
+ },
42
+ "fastapi": {
43
+ "version": "0.124.4",
44
+ "used_by": [
45
+ "kernel",
46
+ "extensions"
47
+ ]
48
+ },
49
+ "flask": {
50
+ "version": "3.0.0",
51
+ "used_by": [
52
+ "extensions"
53
+ ]
54
+ },
55
+ "httpx": {
56
+ "version": "0.28.1",
57
+ "used_by": [
58
+ "extensions"
59
+ ]
60
+ },
61
+ "json5": {
62
+ "version": "0.13.0",
63
+ "used_by": [
64
+ "extensions"
65
+ ]
66
+ },
67
+ "openai": {
68
+ "version": "1.78.1",
69
+ "used_by": [
70
+ "extensions"
71
+ ]
72
+ },
73
+ "psutil": {
74
+ "version": "7.2.2",
75
+ "used_by": [
76
+ "extensions"
77
+ ]
78
+ },
79
+ "pydantic": {
80
+ "version": "2.11.4",
81
+ "used_by": [
82
+ "extensions"
83
+ ]
84
+ },
85
+ "requests": {
86
+ "version": "2.32.3",
87
+ "used_by": [
88
+ "extensions"
89
+ ]
90
+ },
91
+ "starlette": {
92
+ "version": "0.50.0",
93
+ "used_by": [
94
+ "kernel",
95
+ "extensions"
96
+ ]
97
+ },
98
+ "uvicorn": {
99
+ "version": "0.38.0",
100
+ "used_by": [
101
+ "kernel",
102
+ "extensions"
103
+ ]
104
+ },
105
+ "websocket-client": {
106
+ "version": "1.8.0",
107
+ "used_by": [
108
+ "extensions"
109
+ ]
110
+ },
111
+ "websockets": {
112
+ "version": "15.0.1",
113
+ "used_by": [
114
+ "launcher",
115
+ "extensions"
116
+ ]
117
+ },
118
+ "pyyaml": {
119
+ "version": "6.0.3",
120
+ "used_by": [
121
+ "kernel",
122
+ "launcher",
123
+ "extensions",
124
+ "kite_cli"
125
+ ]
126
+ }
127
+ }
128
+ }
@@ -89,7 +89,7 @@ class AssistantServer:
89
89
  """Single WebSocket session: connect, subscribe, register, ready, receive loop."""
90
90
  url = f"ws://127.0.0.1:{self.kernel_port}/ws?token={self.token}&id=assistant"
91
91
  print(f"[assistant] Connecting to Kernel (port {self.kernel_port})")
92
- async with websockets.connect(url, open_timeout=3, ping_interval=20, ping_timeout=20, close_timeout=10) as ws:
92
+ async with websockets.connect(url, open_timeout=3, ping_interval=None, close_timeout=10) as ws:
93
93
  self._ws = ws
94
94
  elapsed = time.monotonic() - self.boot_t0 if self.boot_t0 else 0
95
95
  elapsed_str = f" ({elapsed:.1f}s)" if elapsed else ""
@@ -119,17 +119,18 @@ class AssistantServer:
119
119
 
120
120
  # Step 3: Publish module.ready (every reconnect)
121
121
  if not self._shutting_down:
122
+ startup_time = time.monotonic() - self.boot_t0 if self.boot_t0 else 0
122
123
  await self._rpc_call(ws, "event.publish", {
123
124
  "event_id": str(uuid.uuid4()),
124
125
  "event": "module.ready",
125
126
  "data": {
126
127
  "module_id": "assistant",
127
128
  "graceful_shutdown": True,
129
+ "startup_time": startup_time,
128
130
  },
129
131
  })
130
- elapsed = time.monotonic() - self.boot_t0 if self.boot_t0 else 0
131
- elapsed_str = f" ({elapsed:.1f}s)" if elapsed else ""
132
- print(f"[assistant] module.ready published{elapsed_str}")
132
+ elapsed_str = self._fmt_elapsed(self.boot_t0)
133
+ print(f"[assistant] module.ready published ({elapsed_str})")
133
134
 
134
135
  # Receive loop
135
136
  # CRITICAL: RPC 死锁防范
@@ -138,9 +139,6 @@ class AssistantServer:
138
139
  # - 如果接收循环被 await handler 阻塞,出站响应永远收不到 → 超时死锁
139
140
  # - 事件通知和 RPC 响应可以同步处理(它们不会反向调用 rpc_call)
140
141
 
141
- # Start heartbeat loop
142
- heartbeat_task = asyncio.create_task(self._heartbeat_loop(ws))
143
-
144
142
  async for raw in ws:
145
143
  try:
146
144
  msg = json.loads(raw)
@@ -157,6 +155,11 @@ class AssistantServer:
157
155
  event_type = params.get("event", "")
158
156
  data = params.get("data", {})
159
157
 
158
+ # Handle system.ping event
159
+ if event_type == "system.ping":
160
+ await self._handle_ping_event(data)
161
+ continue
162
+
160
163
  # Layer 1: 处理订阅的事件
161
164
  if event_type == "module.shutdown":
162
165
  target = data.get("module_id", "")
@@ -223,16 +226,29 @@ class AssistantServer:
223
226
  msg["params"] = params
224
227
  await ws.send(json.dumps(msg))
225
228
 
226
- async def _heartbeat_loop(self, ws):
227
- """Send registry.heartbeat every 30 seconds to prevent TTL expiration."""
228
- while True:
229
- try:
230
- await asyncio.sleep(30)
231
- if not self._shutting_down:
232
- await self._rpc_call(ws, "registry.heartbeat", {"module_id": "assistant"})
233
- except Exception as e:
234
- print(f"[assistant] Heartbeat error: {e}")
235
- break
229
+ async def _handle_ping_event(self, data: dict):
230
+ """Handle system.ping event and reply with system.pong."""
231
+ t1 = data.get("ping_time")
232
+ t2 = time.time()
233
+
234
+ await self._rpc_call(self._ws, "event.publish", {
235
+ "event_id": str(uuid.uuid4()),
236
+ "event": "system.pong",
237
+ "data": {
238
+ "module_id": "assistant",
239
+ "ping_time": t1,
240
+ "pong_time": t2,
241
+ },
242
+ })
243
+
244
+ def _fmt_elapsed(self, t0: float) -> str:
245
+ """Format elapsed time since t0."""
246
+ d = time.monotonic() - t0 if t0 else 0
247
+ if d < 1:
248
+ return f"{d * 1000:.0f}ms"
249
+ if d < 10:
250
+ return f"{d:.1f}s"
251
+ return f"{d:.0f}s"
236
252
 
237
253
  async def _publish_event(self, event: dict):
238
254
  """Publish an event via JSON-RPC event.publish."""