@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,228 @@
1
+ /**
2
+ * Kernel Client 使用示例
3
+ *
4
+ * 在主页面(index.html)中集成 KernelClient
5
+ */
6
+
7
+ // 全局 Kernel 客户端实例
8
+ let kernelClient = null;
9
+
10
+ /**
11
+ * 初始化 Kernel 客户端
12
+ */
13
+ async function initKernelClient() {
14
+ const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
15
+ kernelClient = new KernelClient(`${proto}//${location.host}/ws/relay`);
16
+
17
+ // 检查是否已配对
18
+ const token = localStorage.getItem('frontend_token');
19
+
20
+ if (!token) {
21
+ // 未配对,跳转到配对页面
22
+ window.location.href = '/pairing.html';
23
+ return;
24
+ }
25
+
26
+ try {
27
+ // 连接
28
+ const result = await kernelClient.connect();
29
+ console.log('[Kernel] Connected:', result);
30
+
31
+ // 订阅事件
32
+ await kernelClient.subscribe([
33
+ 'module.starting',
34
+ 'module.started',
35
+ 'module.ready',
36
+ 'module.stopped',
37
+ 'module.exiting',
38
+ 'module.shutdown',
39
+ 'module.crashed',
40
+ 'system.ready',
41
+ 'registry.updated',
42
+ 'module.registered',
43
+ 'module.unregistered'
44
+ ]);
45
+
46
+ // 监听模块状态变化
47
+ kernelClient.on('module.started', (data) => {
48
+ console.log('[Kernel] Module started:', data.module_id);
49
+ updateModuleStatus(data.module_id, 'running');
50
+ });
51
+
52
+ kernelClient.on('module.stopped', (data) => {
53
+ console.log('[Kernel] Module stopped:', data.module_id);
54
+ updateModuleStatus(data.module_id, 'stopped');
55
+ });
56
+
57
+ kernelClient.on('module.crashed', (data) => {
58
+ console.log('[Kernel] Module crashed:', data.module_id);
59
+ updateModuleStatus(data.module_id, 'crashed');
60
+ });
61
+
62
+ kernelClient.on('registry.updated', (data) => {
63
+ console.log('[Kernel] Registry updated:', data);
64
+ // 触发缓存失效事件
65
+ window.dispatchEvent(new CustomEvent('registryCacheInvalidate', {
66
+ detail: data
67
+ }));
68
+ });
69
+
70
+ // 更新连接状态指示器
71
+ updateConnectionStatus(true);
72
+
73
+ // 触发连接成功事件
74
+ window.dispatchEvent(new CustomEvent('kernelClientReady'));
75
+
76
+ } catch (err) {
77
+ console.error('[Kernel] Connection failed:', err);
78
+
79
+ // 检查是否为永久性错误
80
+ if (kernelClient.fatalError) {
81
+ // 永久性错误(权限、配置等),显示友好提示,不跳转
82
+ showFatalError(err.message);
83
+ updateConnectionStatus(false);
84
+ } else if (err.message.includes('Invalid') || err.message.includes('expired')) {
85
+ // Token 无效或过期,跳转到配对页面
86
+ window.location.href = '/pairing.html';
87
+ } else {
88
+ // 临时性错误(网络问题等),显示重连状态
89
+ updateConnectionStatus(false);
90
+ }
91
+ }
92
+ }
93
+
94
+ /**
95
+ * 显示永久性错误提示
96
+ */
97
+ function showFatalError(message) {
98
+ // 更新连接状态为错误
99
+ updateConnectionStatus(false);
100
+
101
+ // 显示友好的错误提示
102
+ const errorDiv = document.createElement('div');
103
+ errorDiv.style.cssText = `
104
+ position: fixed;
105
+ top: 20px;
106
+ left: 50%;
107
+ transform: translateX(-50%);
108
+ background: #ff4444;
109
+ color: white;
110
+ padding: 15px 25px;
111
+ border-radius: 8px;
112
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
113
+ z-index: 10000;
114
+ max-width: 600px;
115
+ text-align: center;
116
+ `;
117
+
118
+ // 根据错误类型显示不同的提示
119
+ let friendlyMessage = '连接失败';
120
+ let suggestion = '请联系管理员';
121
+
122
+ if (message.includes('Permission denied') || message.includes('not in relay_modules whitelist')) {
123
+ friendlyMessage = '权限不足';
124
+ suggestion = '此模块未被授权连接到 Kernel。请检查 launcher/module.md 中的 relay.modules 配置。';
125
+ } else if (message.includes('Token limit reached')) {
126
+ friendlyMessage = '连接数已达上限';
127
+ suggestion = '请释放一些不用的连接,或增加 relay.token_limits 配置。';
128
+ } else if (message.includes('module_id must start with')) {
129
+ friendlyMessage = '配置错误';
130
+ suggestion = '模块 ID 格式不正确。请检查 relay_config.json5 中的 base_module_id 配置。';
131
+ }
132
+
133
+ errorDiv.innerHTML = `
134
+ <div style="font-size: 18px; font-weight: bold; margin-bottom: 10px;">
135
+ ⚠️ ${friendlyMessage}
136
+ </div>
137
+ <div style="font-size: 14px; opacity: 0.9; margin-bottom: 10px;">
138
+ ${suggestion}
139
+ </div>
140
+ <div style="font-size: 12px; opacity: 0.7; margin-top: 10px; padding-top: 10px; border-top: 1px solid rgba(255,255,255,0.3);">
141
+ 技术详情: ${message}
142
+ </div>
143
+ `;
144
+
145
+ document.body.appendChild(errorDiv);
146
+
147
+ // 5秒后淡出
148
+ setTimeout(() => {
149
+ errorDiv.style.transition = 'opacity 0.5s';
150
+ errorDiv.style.opacity = '0';
151
+ setTimeout(() => errorDiv.remove(), 500);
152
+ }, 10000);
153
+ }
154
+
155
+ /**
156
+ * 更新连接状态指示器
157
+ */
158
+ function updateConnectionStatus(connected) {
159
+ const indicators = [
160
+ document.getElementById('ws-indicator'),
161
+ document.getElementById('ws-indicator-modules')
162
+ ];
163
+
164
+ indicators.forEach(indicator => {
165
+ if (indicator) {
166
+ if (connected) {
167
+ indicator.textContent = '● 已连线';
168
+ indicator.style.color = 'var(--success)';
169
+ } else {
170
+ indicator.textContent = '○ 未连线';
171
+ indicator.style.color = 'var(--gray-400)';
172
+ }
173
+ }
174
+ });
175
+ }
176
+
177
+ /**
178
+ * 更新模块状态
179
+ */
180
+ function updateModuleStatus(moduleName, status) {
181
+ // 这里调用现有的模块状态更新逻辑
182
+ if (typeof _onModuleStatusChange === 'function') {
183
+ _onModuleStatusChange(moduleName, status);
184
+ }
185
+ }
186
+
187
+ /**
188
+ * 调用 RPC 示例
189
+ */
190
+ async function exampleRpcCalls() {
191
+ if (!kernelClient) {
192
+ console.error('[Kernel] Client not initialized');
193
+ return;
194
+ }
195
+
196
+ try {
197
+ // 获取模块列表
198
+ const modules = await kernelClient.call('launcher.list_modules');
199
+ console.log('[Kernel] Modules:', modules);
200
+
201
+ // 启动模块
202
+ await kernelClient.call('launcher.start_module', { name: 'watchdog' });
203
+ console.log('[Kernel] Module started');
204
+
205
+ // 停止模块
206
+ await kernelClient.call('launcher.stop_module', { name: 'watchdog' });
207
+ console.log('[Kernel] Module stopped');
208
+
209
+ } catch (err) {
210
+ console.error('[Kernel] RPC error:', err);
211
+ }
212
+ }
213
+
214
+ /**
215
+ * 页面加载时初始化
216
+ */
217
+ document.addEventListener('DOMContentLoaded', () => {
218
+ initKernelClient();
219
+ });
220
+
221
+ /**
222
+ * 页面关闭时断开连接
223
+ */
224
+ window.addEventListener('beforeunload', () => {
225
+ if (kernelClient) {
226
+ kernelClient.disconnect();
227
+ }
228
+ });
@@ -0,0 +1,396 @@
1
+ /**
2
+ * Kernel WebSocket 客户端
3
+ *
4
+ * 功能:
5
+ * - 配对/认证
6
+ * - 自动重连
7
+ * - RPC 调用
8
+ * - 事件订阅
9
+ * - Token 续期
10
+ */
11
+
12
+ class KernelClient {
13
+ constructor(url) {
14
+ this.url = url;
15
+ this.ws = null;
16
+ this.moduleId = null;
17
+ this.role = null;
18
+ this.connected = false;
19
+ this.authenticated = false;
20
+
21
+ // RPC 请求管理
22
+ this.rpcId = 0;
23
+ this.pendingRpcs = new Map(); // id -> {resolve, reject, timeout}
24
+
25
+ // 事件监听器
26
+ this.eventListeners = new Map(); // event -> [callbacks]
27
+
28
+ // 重连配置
29
+ this.reconnectDelay = 1000; // 1秒
30
+ this.maxReconnectDelay = 10000; // 10秒
31
+ this.reconnectAttempts = 0;
32
+ this.reconnectTimer = null;
33
+ this.fatalError = false; // 是否遇到永久性错误(不应重试)
34
+
35
+ // 心跳配置
36
+ this.heartbeatInterval = 20000; // 20秒
37
+ this.heartbeatTimer = null;
38
+
39
+ // Token 管理
40
+ this.token = localStorage.getItem('frontend_token');
41
+ this.sessionToken = sessionStorage.getItem('session_token');
42
+
43
+ // 生成 session_token(如果没有)
44
+ if (!this.sessionToken) {
45
+ this.sessionToken = 'sess_' + this._randomString(6);
46
+ sessionStorage.setItem('session_token', this.sessionToken);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * 配对(首次使用)
52
+ */
53
+ async pair(code) {
54
+ return new Promise((resolve, reject) => {
55
+ this._connect((ws) => {
56
+ // 发送配对消息
57
+ ws.send(JSON.stringify({
58
+ type: 'pair',
59
+ code: code
60
+ }));
61
+
62
+ // 等待响应
63
+ const handler = (event) => {
64
+ const msg = JSON.parse(event.data);
65
+
66
+ if (msg.type === 'paired') {
67
+ // 配对成功
68
+ this.token = msg.token;
69
+ this.sessionToken = msg.session_token;
70
+ this.moduleId = msg.module_id;
71
+ this.role = msg.role;
72
+
73
+ // 保存 token
74
+ localStorage.setItem('frontend_token', this.token);
75
+ sessionStorage.setItem('session_token', this.sessionToken);
76
+
77
+ this.authenticated = true;
78
+ ws.removeEventListener('message', handler);
79
+ resolve({
80
+ moduleId: this.moduleId,
81
+ role: this.role
82
+ });
83
+ } else if (msg.type === 'error') {
84
+ // 配对失败
85
+ ws.removeEventListener('message', handler);
86
+ reject(new Error(msg.message));
87
+ }
88
+ };
89
+
90
+ ws.addEventListener('message', handler);
91
+ }, reject);
92
+ });
93
+ }
94
+
95
+ /**
96
+ * 连接(已有 token)
97
+ */
98
+ async connect() {
99
+ if (!this.token) {
100
+ throw new Error('No token found, please pair first');
101
+ }
102
+
103
+ return new Promise((resolve, reject) => {
104
+ this._connect((ws) => {
105
+ // 发送认证消息
106
+ ws.send(JSON.stringify({
107
+ type: 'auth',
108
+ token: this.token,
109
+ session_token: this.sessionToken
110
+ }));
111
+
112
+ // 等待响应
113
+ const handler = (event) => {
114
+ const msg = JSON.parse(event.data);
115
+
116
+ if (msg.type === 'authenticated' || msg.type === 'reconnected') {
117
+ // 认证成功
118
+ this.moduleId = msg.module_id;
119
+ this.role = msg.role;
120
+ this.authenticated = true;
121
+
122
+ ws.removeEventListener('message', handler);
123
+ resolve({
124
+ moduleId: this.moduleId,
125
+ role: this.role,
126
+ reconnected: msg.type === 'reconnected'
127
+ });
128
+ } else if (msg.type === 'error') {
129
+ // 认证失败
130
+ ws.removeEventListener('message', handler);
131
+
132
+ // 检查是否为永久性错误
133
+ if (msg.fatal) {
134
+ this.fatalError = true;
135
+ console.error('[KernelClient] Fatal error, will not retry:', msg.message);
136
+ }
137
+
138
+ // 如果是 token 无效,清除本地 token
139
+ if (msg.message.includes('Invalid') || msg.message.includes('expired')) {
140
+ localStorage.removeItem('frontend_token');
141
+ this.token = null;
142
+ }
143
+
144
+ reject(new Error(msg.message));
145
+ }
146
+ };
147
+
148
+ ws.addEventListener('message', handler);
149
+ }, reject);
150
+ });
151
+ }
152
+
153
+ /**
154
+ * 调用 RPC
155
+ */
156
+ async call(method, params = {}, timeout = 30000) {
157
+ if (!this.authenticated) {
158
+ throw new Error('Not authenticated');
159
+ }
160
+
161
+ return new Promise((resolve, reject) => {
162
+ const id = `rpc-${++this.rpcId}`;
163
+
164
+ // 发送 RPC 请求
165
+ this.ws.send(JSON.stringify({
166
+ jsonrpc: '2.0',
167
+ id: id,
168
+ method: method,
169
+ params: params
170
+ }));
171
+
172
+ // 设置超时
173
+ const timer = setTimeout(() => {
174
+ this.pendingRpcs.delete(id);
175
+ reject(new Error(`RPC timeout: ${method}`));
176
+ }, timeout);
177
+
178
+ // 保存 pending RPC
179
+ this.pendingRpcs.set(id, { resolve, reject, timeout: timer });
180
+ });
181
+ }
182
+
183
+ /**
184
+ * 订阅事件
185
+ */
186
+ async subscribe(events) {
187
+ return this.call('event.subscribe', { events });
188
+ }
189
+
190
+ /**
191
+ * 监听事件
192
+ */
193
+ on(event, callback) {
194
+ if (!this.eventListeners.has(event)) {
195
+ this.eventListeners.set(event, []);
196
+ }
197
+ this.eventListeners.get(event).push(callback);
198
+ }
199
+
200
+ /**
201
+ * 取消监听事件
202
+ */
203
+ off(event, callback) {
204
+ if (!this.eventListeners.has(event)) return;
205
+
206
+ const listeners = this.eventListeners.get(event);
207
+ const index = listeners.indexOf(callback);
208
+ if (index > -1) {
209
+ listeners.splice(index, 1);
210
+ }
211
+ }
212
+
213
+ /**
214
+ * 断开连接
215
+ */
216
+ disconnect() {
217
+ if (this.reconnectTimer) {
218
+ clearTimeout(this.reconnectTimer);
219
+ this.reconnectTimer = null;
220
+ }
221
+
222
+ this._stopHeartbeat();
223
+
224
+ if (this.ws) {
225
+ // 发送 disconnect 消息
226
+ try {
227
+ this.ws.send(JSON.stringify({ type: 'disconnect' }));
228
+ } catch (e) {
229
+ // Ignore
230
+ }
231
+
232
+ this.ws.close();
233
+ this.ws = null;
234
+ }
235
+
236
+ this.connected = false;
237
+ this.authenticated = false;
238
+ }
239
+
240
+ /**
241
+ * 启动心跳
242
+ */
243
+ _startHeartbeat() {
244
+ this._stopHeartbeat();
245
+ this.heartbeatTimer = setInterval(() => {
246
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
247
+ try {
248
+ // 发送 ping 消息
249
+ this.ws.send(JSON.stringify({ type: 'ping' }));
250
+ } catch (e) {
251
+ console.error('[KernelClient] Heartbeat failed:', e);
252
+ }
253
+ }
254
+ }, this.heartbeatInterval);
255
+ }
256
+
257
+ /**
258
+ * 停止心跳
259
+ */
260
+ _stopHeartbeat() {
261
+ if (this.heartbeatTimer) {
262
+ clearInterval(this.heartbeatTimer);
263
+ this.heartbeatTimer = null;
264
+ }
265
+ }
266
+
267
+ /**
268
+ * 内部:建立 WebSocket 连接
269
+ */
270
+ _connect(onOpen, onError) {
271
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
272
+ onOpen(this.ws);
273
+ return;
274
+ }
275
+
276
+ const ws = new WebSocket(this.url);
277
+
278
+ ws.onopen = () => {
279
+ console.log('[KernelClient] WebSocket connected');
280
+ this.ws = ws;
281
+ this.connected = true;
282
+ this.reconnectAttempts = 0;
283
+ this.reconnectDelay = 1000;
284
+ this._startHeartbeat();
285
+ onOpen(ws);
286
+ };
287
+
288
+ ws.onerror = (error) => {
289
+ console.error('[KernelClient] WebSocket error:', error);
290
+ if (onError) onError(error);
291
+ };
292
+
293
+ ws.onclose = () => {
294
+ console.log('[KernelClient] WebSocket closed');
295
+ this.connected = false;
296
+ this.authenticated = false;
297
+ this.ws = null;
298
+ this._stopHeartbeat();
299
+
300
+ // 自动重连(如果有 token)
301
+ if (this.token) {
302
+ this._scheduleReconnect();
303
+ }
304
+ };
305
+
306
+ ws.onmessage = (event) => {
307
+ this._handleMessage(event.data);
308
+ };
309
+ }
310
+
311
+ /**
312
+ * 内部:处理消息
313
+ */
314
+ _handleMessage(raw) {
315
+ try {
316
+ const msg = JSON.parse(raw);
317
+
318
+ // RPC 响应
319
+ if (msg.id && this.pendingRpcs.has(msg.id)) {
320
+ const { resolve, reject, timeout } = this.pendingRpcs.get(msg.id);
321
+ clearTimeout(timeout);
322
+ this.pendingRpcs.delete(msg.id);
323
+
324
+ if (msg.error) {
325
+ reject(new Error(msg.error.message || 'RPC error'));
326
+ } else {
327
+ resolve(msg.result);
328
+ }
329
+ return;
330
+ }
331
+
332
+ // 事件通知
333
+ if (msg.method === 'event' || !msg.id) {
334
+ const params = msg.params || {};
335
+ const event = params.event;
336
+ const data = params.data || {};
337
+
338
+ if (event && this.eventListeners.has(event)) {
339
+ const listeners = this.eventListeners.get(event);
340
+ listeners.forEach(callback => {
341
+ try {
342
+ callback(data);
343
+ } catch (e) {
344
+ console.error('[KernelClient] Event listener error:', e);
345
+ }
346
+ });
347
+ }
348
+ }
349
+ } catch (e) {
350
+ console.error('[KernelClient] Message parse error:', e);
351
+ }
352
+ }
353
+
354
+ /**
355
+ * 内部:安排重连
356
+ */
357
+ _scheduleReconnect() {
358
+ // 如果遇到永久性错误,不再重连
359
+ if (this.fatalError) {
360
+ console.error('[KernelClient] Fatal error detected, reconnection disabled');
361
+ return;
362
+ }
363
+
364
+ if (this.reconnectTimer) return;
365
+
366
+ this.reconnectAttempts++;
367
+ const delay = Math.min(
368
+ this.reconnectDelay * Math.pow(1.5, this.reconnectAttempts - 1),
369
+ this.maxReconnectDelay
370
+ );
371
+
372
+ console.log(`[KernelClient] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
373
+
374
+ this.reconnectTimer = setTimeout(() => {
375
+ this.reconnectTimer = null;
376
+ this.connect().catch(err => {
377
+ console.error('[KernelClient] Reconnect failed:', err);
378
+ });
379
+ }, delay);
380
+ }
381
+
382
+ /**
383
+ * 内部:生成随机字符串
384
+ */
385
+ _randomString(length) {
386
+ const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
387
+ let result = '';
388
+ for (let i = 0; i < length; i++) {
389
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
390
+ }
391
+ return result;
392
+ }
393
+ }
394
+
395
+ // 导出
396
+ window.KernelClient = KernelClient;