@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
@@ -26,7 +26,113 @@ import uvicorn
26
26
 
27
27
 
28
28
  # ── Module configuration ──
29
- MODULE_NAME = "web"
29
+
30
+ def _load_module_config() -> dict:
31
+ """Load module configuration from module.md frontmatter.
32
+
33
+ Returns:
34
+ Dict with keys: name, preferred_port, advertise_ip
35
+
36
+ Raises:
37
+ SystemExit: If module.md is invalid or name is non-compliant
38
+ """
39
+ _this_dir = os.path.dirname(os.path.abspath(__file__))
40
+ module_md = os.path.join(_this_dir, "module.md")
41
+
42
+ # Calculate relative path for error messages
43
+ project_root = os.environ.get("KITE_PROJECT", "")
44
+ if project_root and _this_dir.startswith(project_root):
45
+ rel_path = os.path.relpath(_this_dir, project_root)
46
+ else:
47
+ rel_path = _this_dir
48
+
49
+ # Default values (will be overridden if valid config exists)
50
+ result = {
51
+ "name": "",
52
+ "preferred_port": 0,
53
+ "advertise_ip": "0.0.0.0"
54
+ }
55
+
56
+ # Check if module.md exists
57
+ if not os.path.exists(module_md):
58
+ print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
59
+ print(f" Path: {rel_path}/module.md")
60
+ print(f" Reason: File not found")
61
+ sys.exit(1)
62
+
63
+ try:
64
+ with open(module_md, encoding="utf-8") as f:
65
+ text = f.read()
66
+
67
+ # Extract YAML frontmatter (between --- markers)
68
+ import re
69
+ m = re.match(r'^---\s*\n(.*?)\n---', text, re.DOTALL)
70
+ if not m:
71
+ print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
72
+ print(f" Path: {rel_path}/module.md")
73
+ print(f" Reason: Missing YAML frontmatter")
74
+ sys.exit(1)
75
+
76
+ # Parse YAML frontmatter
77
+ try:
78
+ import yaml
79
+ fm = yaml.safe_load(m.group(1)) or {}
80
+ except ImportError:
81
+ print(f"[{rel_path}] ERROR: PyYAML not installed, cannot parse module.md")
82
+ sys.exit(1)
83
+ except Exception as e:
84
+ print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
85
+ print(f" Path: {rel_path}/module.md")
86
+ print(f" Reason: YAML parse error: {e}")
87
+ sys.exit(1)
88
+
89
+ # Validate 'name' field (required)
90
+ if "name" not in fm:
91
+ print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
92
+ print(f" Path: {rel_path}/module.md")
93
+ print(f" Reason: Missing 'name' field")
94
+ sys.exit(1)
95
+
96
+ raw_name = str(fm["name"]).strip()
97
+
98
+ if not raw_name:
99
+ print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
100
+ print(f" Path: {rel_path}/module.md")
101
+ print(f" Reason: Empty module name")
102
+ sys.exit(1)
103
+
104
+ # Validate name characters
105
+ sanitized = re.sub(r'[^a-zA-Z0-9_\-]', '', raw_name)
106
+
107
+ if sanitized != raw_name:
108
+ invalid_chars = ''.join(sorted(set(c for c in raw_name if c not in sanitized)))
109
+ print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
110
+ print(f" Path: {rel_path}/module.md")
111
+ print(f" Reason: Invalid characters in name '{raw_name}': {repr(invalid_chars)}")
112
+ sys.exit(1)
113
+
114
+ result["name"] = sanitized
115
+
116
+ # Extract optional fields
117
+ if "preferred_port" in fm:
118
+ try:
119
+ result["preferred_port"] = int(fm["preferred_port"])
120
+ except (ValueError, TypeError):
121
+ pass
122
+
123
+ if "advertise_ip" in fm:
124
+ result["advertise_ip"] = str(fm["advertise_ip"])
125
+
126
+ except SystemExit:
127
+ raise # Re-raise exit to prevent catching by outer except
128
+ except Exception as e:
129
+ print(f"[{rel_path}] ERROR: Failed to read module.md: {e}")
130
+ sys.exit(1)
131
+
132
+ return result
133
+
134
+ _module_config = _load_module_config()
135
+ MODULE_NAME = _module_config["name"]
30
136
 
31
137
 
32
138
  class _SafeWriter:
@@ -251,27 +357,6 @@ def _fmt_elapsed(t0: float) -> str:
251
357
  return f"{d:.0f}s"
252
358
 
253
359
 
254
- def _read_module_md() -> dict:
255
- """Read preferred_port and advertise_ip from own module.md."""
256
- md_path = os.path.join(_this_dir, "module.md")
257
- result = {"preferred_port": 0, "advertise_ip": "0.0.0.0"}
258
- try:
259
- with open(md_path, "r", encoding="utf-8") as f:
260
- text = f.read()
261
- m = re.match(r'^---\s*\n(.*?)\n---', text, re.DOTALL)
262
- if m:
263
- try:
264
- import yaml
265
- fm = yaml.safe_load(m.group(1)) or {}
266
- except ImportError:
267
- fm = {}
268
- result["preferred_port"] = int(fm.get("preferred_port", 0))
269
- result["advertise_ip"] = fm.get("advertise_ip", "0.0.0.0")
270
- except Exception:
271
- pass
272
- return result
273
-
274
-
275
360
  def _bind_port(preferred: int, host: str, max_attempts: int = 10) -> int | None:
276
361
  """
277
362
  Try to bind to preferred port, then port+1, port+2, ... up to max_attempts.
@@ -351,10 +436,9 @@ def main():
351
436
 
352
437
  print(f"[web] Token received ({len(token)} chars), kernel port: {kernel_port} ({_fmt_elapsed(_t0)})")
353
438
 
354
- # Read preferred_port from module.md
355
- md_cfg = _read_module_md()
356
- host = md_cfg["advertise_ip"]
357
- port = _bind_port(md_cfg["preferred_port"], host)
439
+ # Use cached module config (already loaded at module level)
440
+ host = _module_config["advertise_ip"]
441
+ port = _bind_port(_module_config["preferred_port"], host)
358
442
 
359
443
  # If port binding failed after 10 attempts, exit gracefully
360
444
  if port is None:
@@ -385,6 +469,10 @@ def main():
385
469
  _print_crash_summary(type(e), e.__traceback__)
386
470
  sys.exit(1)
387
471
 
472
+ # Check if server requested exit with non-zero code
473
+ if server._exit_code != 0:
474
+ sys.exit(server._exit_code)
475
+
388
476
 
389
477
  if __name__ == "__main__":
390
478
  main()
@@ -1,24 +1,35 @@
1
- ---
2
- name: web
3
- display_name: Web Management
4
- version: "1.0"
5
- type: service
6
- state: enabled
7
- runtime: python
8
- entry: entry.py
9
- preferred_port: 18766
10
- advertise_ip: 0.0.0.0
11
- events:
12
- - web.test
13
- subscriptions:
14
- - module.started
15
- - module.stopped
16
- - module.shutdown
17
- ---
18
-
19
- # Web Management(Web 管理界面)
20
-
21
- Web 管理界面模块,提供系统管理和监控的 Web UI。
22
-
23
- - 管理界面 — 提供系统配置和状态监控的 Web UI
24
- - 事件通知 — 通过 Event Hub 发布管理操作事件
1
+ ---
2
+ name: web
3
+ display_name: Web Management
4
+ version: '1.0'
5
+ type: service
6
+ state: manual
7
+ runtime: python
8
+ entry: entry.py
9
+ preferred_port: 18766
10
+ advertise_ip: 0.0.0.0
11
+ events:
12
+ - web.test
13
+ subscriptions:
14
+ - module.started
15
+ - module.stopped
16
+ - module.shutdown
17
+
18
+ # 业务配置列表
19
+ businesses:
20
+ - name: web_server
21
+ type: http_service
22
+ description: Web 管理界面和 API 服务
23
+ config_file: web_config.json5
24
+
25
+ - name: relay_service
26
+ type: kernel_relay
27
+ description: Kernel WebSocket 中转服务
28
+ config_file: relay_config.json5
29
+ ---
30
+ # Web Management(Web 管理界面)
31
+
32
+ Web 管理界面模块,提供系统管理和监控的 Web UI。
33
+
34
+ - 管理界面 — 提供系统配置和状态监控的 Web UI
35
+ - 事件通知 — 通过 Event Hub 发布管理操作事件
@@ -0,0 +1,250 @@
1
+ """
2
+ 配对码管理模块
3
+
4
+ 负责配对码的生成、验证、使用和 frontend_token 的管理。
5
+
6
+ 零共享代码依赖 - 此文件可以独立拷贝到其他模块使用。
7
+ """
8
+
9
+ import json
10
+ import os
11
+ import secrets
12
+ import string
13
+ import time
14
+ from datetime import datetime, timezone
15
+ from typing import Optional
16
+
17
+
18
+ class PairingManager:
19
+ """配对码管理器"""
20
+
21
+ def __init__(self, pairing_file: str, code_length: int = 6, token_expiry: int = 2592000):
22
+ """
23
+ 初始化配对码管理器。
24
+
25
+ Args:
26
+ pairing_file: 配对码文件路径(JSONL 格式)
27
+ code_length: 配对码长度(默认 6)
28
+ token_expiry: frontend_token 有效期(秒,默认 30 天)
29
+ """
30
+ self.pairing_file = pairing_file
31
+ self.code_length = code_length
32
+ self.token_expiry = token_expiry
33
+
34
+ # 临时配对码(未持久化)
35
+ self.pending_codes = {} # {code: {"role": "admin", "created_at": timestamp}}
36
+
37
+ # 确保文件存在
38
+ if not os.path.exists(pairing_file):
39
+ os.makedirs(os.path.dirname(pairing_file), exist_ok=True)
40
+
41
+ def generate_pairing_code(self, role: str = "admin") -> str:
42
+ """
43
+ 生成临时配对码(不持久化)。
44
+
45
+ Args:
46
+ role: 用户角色
47
+
48
+ Returns:
49
+ 配对码
50
+ """
51
+ code = self._generate_code()
52
+ self.pending_codes[code] = {
53
+ "role": role,
54
+ "created_at": time.time()
55
+ }
56
+
57
+ # 清理过期的配对码(超过 5 分钟)
58
+ self._cleanup_expired_codes()
59
+
60
+ return code
61
+
62
+ def _cleanup_expired_codes(self):
63
+ """清理过期的临时配对码"""
64
+ now = time.time()
65
+ expired = [
66
+ code for code, info in self.pending_codes.items()
67
+ if now - info["created_at"] > 300 # 5 分钟
68
+ ]
69
+ for code in expired:
70
+ del self.pending_codes[code]
71
+
72
+ def _ensure_active_code(self):
73
+ """确保有 active 状态的配对码,如果没有则生成(已废弃)"""
74
+ pass
75
+
76
+ def _print_pairing_code(self, code: str, is_new: bool):
77
+ """在控制台绿色高亮显示配对码(已废弃,改为通过事件发送给 Launcher)"""
78
+ pass
79
+
80
+ def _create_initial_code(self):
81
+ """创建初始配对码(已废弃,由 _ensure_active_code 替代)"""
82
+ pass
83
+
84
+ def _generate_code(self) -> str:
85
+ """生成随机配对码"""
86
+ chars = string.ascii_uppercase + string.digits
87
+ return ''.join(secrets.choice(chars) for _ in range(self.code_length))
88
+
89
+ def _generate_token(self) -> str:
90
+ """生成 frontend_token"""
91
+ return "tok_" + secrets.token_urlsafe(32)
92
+
93
+ def _read_codes(self) -> list[dict]:
94
+ """读取所有配对码记录"""
95
+ if not os.path.exists(self.pairing_file):
96
+ return []
97
+
98
+ codes = []
99
+ with open(self.pairing_file, "r", encoding="utf-8") as f:
100
+ for line in f:
101
+ line = line.strip()
102
+ if line:
103
+ try:
104
+ codes.append(json.loads(line))
105
+ except json.JSONDecodeError:
106
+ pass
107
+ return codes
108
+
109
+ def _write_code(self, code_record: dict):
110
+ """追加配对码记录"""
111
+ with open(self.pairing_file, "a", encoding="utf-8") as f:
112
+ f.write(json.dumps(code_record, ensure_ascii=False) + "\n")
113
+
114
+ def verify_code(self, code: str) -> Optional[dict]:
115
+ """
116
+ 验证配对码(包括临时配对码和持久化配对码)。
117
+
118
+ Args:
119
+ code: 配对码
120
+
121
+ Returns:
122
+ 如果有效,返回配对码信息(包含 role);否则返回 None
123
+ """
124
+ # 先检查临时配对码
125
+ if code in self.pending_codes:
126
+ info = self.pending_codes[code]
127
+ # 检查是否过期(5 分钟)
128
+ if time.time() - info["created_at"] < 300:
129
+ return {"code": code, "role": info["role"], "status": "pending"}
130
+
131
+ # 再检查持久化配对码(向后兼容)
132
+ codes = self._read_codes()
133
+ for record in reversed(codes):
134
+ if record.get("code") == code and record.get("status") == "active":
135
+ return record
136
+
137
+ return None
138
+
139
+ def pair(self, code: str) -> Optional[dict]:
140
+ """
141
+ 使用配对码进行配对。
142
+
143
+ Args:
144
+ code: 配对码
145
+
146
+ Returns:
147
+ 如果成功,返回 {"token": "...", "role": "..."};否则返回 None
148
+ """
149
+ # 验证配对码
150
+ code_info = self.verify_code(code)
151
+ if not code_info:
152
+ return None
153
+
154
+ # 生成 frontend_token
155
+ token = self._generate_token()
156
+ role = code_info.get("role", "viewer")
157
+
158
+ # 持久化保存 token
159
+ used_record = {
160
+ "code": code,
161
+ "role": role,
162
+ "status": "used",
163
+ "token": token,
164
+ "paired_at": datetime.now(timezone.utc).isoformat(),
165
+ "expires_at": datetime.fromtimestamp(
166
+ time.time() + self.token_expiry, tz=timezone.utc
167
+ ).isoformat()
168
+ }
169
+ self._write_code(used_record)
170
+
171
+ # 从临时配对码中删除
172
+ if code in self.pending_codes:
173
+ del self.pending_codes[code]
174
+
175
+ return {
176
+ "token": token,
177
+ "role": role,
178
+ "expires_at": used_record["expires_at"]
179
+ }
180
+
181
+ def verify_token(self, token: str) -> Optional[dict]:
182
+ """
183
+ 验证 frontend_token。
184
+
185
+ Args:
186
+ token: frontend_token
187
+
188
+ Returns:
189
+ 如果有效,返回 {"role": "...", "expires_at": "..."};否则返回 None
190
+ """
191
+ codes = self._read_codes()
192
+
193
+ # 检查 token 是否被吊销
194
+ for record in reversed(codes):
195
+ if record.get("token") == token and record.get("status") == "revoked":
196
+ return None # Token 已被吊销
197
+
198
+ # 查找 token 对应的记录
199
+ for record in reversed(codes):
200
+ if record.get("token") == token and record.get("status") == "used":
201
+ # 检查是否过期
202
+ expires_at = record.get("expires_at")
203
+ if expires_at:
204
+ expire_time = datetime.fromisoformat(expires_at)
205
+ if datetime.now(timezone.utc) < expire_time:
206
+ return {
207
+ "role": record.get("role", "viewer"),
208
+ "expires_at": expires_at
209
+ }
210
+
211
+ return None
212
+
213
+ def renew_token(self, old_token: str) -> Optional[str]:
214
+ """
215
+ 续期 frontend_token。
216
+
217
+ Args:
218
+ old_token: 旧的 frontend_token
219
+
220
+ Returns:
221
+ 如果成功,返回新的 token;否则返回 None
222
+ """
223
+ # 验证旧 token
224
+ token_info = self.verify_token(old_token)
225
+ if not token_info:
226
+ return None
227
+
228
+ # 生成新 token
229
+ new_token = self._generate_token()
230
+ role = token_info["role"]
231
+
232
+ # 写入新 token 记录
233
+ new_record = {
234
+ "token": new_token,
235
+ "role": role,
236
+ "status": "used",
237
+ "renewed_from": old_token,
238
+ "paired_at": datetime.now(timezone.utc).isoformat(),
239
+ "expires_at": datetime.fromtimestamp(
240
+ time.time() + self.token_expiry, tz=timezone.utc
241
+ ).isoformat()
242
+ }
243
+ self._write_code(new_record)
244
+
245
+ return new_token
246
+
247
+ def get_active_codes(self) -> list[dict]:
248
+ """获取所有 active 状态的配对码"""
249
+ codes = self._read_codes()
250
+ return [c for c in codes if c.get("status") == "active"]
@@ -0,0 +1,16 @@
1
+ {"code": "Z02ZRG", "role": "admin", "status": "active", "created_at": "2026-03-05T15:47:25.324158+00:00"}
2
+ {"code": "6LL9JO", "role": "admin", "status": "used", "token": "tok_bRHd1ci_m5ziNGhGrza-OVUFqiu5dukqVUnZP3DB5rM", "paired_at": "2026-03-05T16:15:24.338472+00:00", "expires_at": "2026-04-04T16:15:24.338483+00:00"}
3
+ {"code": "1KEKFO", "role": "admin", "status": "used", "token": "tok_mQRErFLfPTOd5iuMM0tR3HlsL-ZwRjaoqAOsrd3f7o0", "paired_at": "2026-03-05T16:19:32.214747+00:00", "expires_at": "2026-04-04T16:19:32.214758+00:00"}
4
+ {"token": "tok_bRHd1ci_m5ziNGhGrza-OVUFqiu5dukqVUnZP3DB5rM", "status": "revoked", "revoked_at": "2026-03-05T17:16:47.003028+00:00"}
5
+ {"token": "tok_mQRErFLfPTOd5iuMM0tR3HlsL-ZwRjaoqAOsrd3f7o0", "status": "revoked", "revoked_at": "2026-03-05T17:16:51.276194+00:00"}
6
+ {"code": "62DLAO", "role": "admin", "status": "used", "token": "tok_xUecpGG2v64ET3OIvLvmMZx73AhwMH3GvbuFVYeWThQ", "paired_at": "2026-03-05T17:19:27.498121+00:00", "expires_at": "2026-04-04T17:19:27.498131+00:00"}
7
+ {"token": "tok_xUecpGG2v64ET3OIvLvmMZx73AhwMH3GvbuFVYeWThQ", "status": "revoked", "revoked_at": "2026-03-05T17:19:52.246926+00:00"}
8
+ {"code": "B2KC73", "role": "admin", "status": "used", "token": "tok_mtdU9v_mqwtx5Vzq4NpWL7X8oN_te9G3mKYKbyJ3KJQ", "paired_at": "2026-03-05T17:20:02.715650+00:00", "expires_at": "2026-04-04T17:20:02.715660+00:00"}
9
+ {"token": "tok_mtdU9v_mqwtx5Vzq4NpWL7X8oN_te9G3mKYKbyJ3KJQ", "status": "revoked", "revoked_at": "2026-03-05T17:24:19.299991+00:00"}
10
+ {"code": "RR46UW", "role": "admin", "status": "used", "token": "tok_okmAev1nyxMTpZr5NeIYR5i0GOoQBiBsyu-7O5Zt-bY", "paired_at": "2026-03-05T17:24:42.072484+00:00", "expires_at": "2026-04-04T17:24:42.072494+00:00"}
11
+ {"token": "tok_okmAev1nyxMTpZr5NeIYR5i0GOoQBiBsyu-7O5Zt-bY", "status": "revoked", "revoked_at": "2026-03-05T17:24:46.132417+00:00"}
12
+ {"code": "HUBWSE", "role": "admin", "status": "used", "token": "tok_ycxZ6hbiHg1xmuTbUX7yivvy__YMVYIn6cKvtuDPrM0", "paired_at": "2026-03-05T17:37:23.438551+00:00", "expires_at": "2026-04-04T17:37:23.438560+00:00"}
13
+ {"token": "tok_ycxZ6hbiHg1xmuTbUX7yivvy__YMVYIn6cKvtuDPrM0", "status": "revoked", "revoked_at": "2026-03-05T17:37:43.801272+00:00"}
14
+ {"code": "15Q4QJ", "role": "admin", "status": "used", "token": "tok_byVOicuvkitvDTQRuh05XQpmtAdyUineSddRCi5MHbs", "paired_at": "2026-03-05T17:38:50.616922+00:00", "expires_at": "2026-04-04T17:38:50.616932+00:00"}
15
+ {"token": "tok_byVOicuvkitvDTQRuh05XQpmtAdyUineSddRCi5MHbs", "status": "revoked", "revoked_at": "2026-03-06T07:55:02.015491+00:00"}
16
+ {"code": "7N5Q43", "role": "admin", "status": "used", "token": "tok_J8zYOS5OKcHkEyOzIFRD_sHbq61ngWqyoxlVQrDdhyA", "paired_at": "2026-03-06T07:55:19.446442+00:00", "expires_at": "2026-04-05T07:55:19.446452+00:00"}