@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,462 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>中转服务测试</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
16
+ background: #f5f5f5;
17
+ padding: 20px;
18
+ }
19
+
20
+ .container {
21
+ max-width: 1200px;
22
+ margin: 0 auto;
23
+ }
24
+
25
+ h1 {
26
+ font-size: 24px;
27
+ color: #333;
28
+ margin-bottom: 20px;
29
+ }
30
+
31
+ .panel {
32
+ background: white;
33
+ border-radius: 8px;
34
+ padding: 20px;
35
+ margin-bottom: 20px;
36
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
37
+ }
38
+
39
+ .panel h2 {
40
+ font-size: 18px;
41
+ color: #333;
42
+ margin-bottom: 15px;
43
+ border-bottom: 2px solid #667eea;
44
+ padding-bottom: 10px;
45
+ }
46
+
47
+ .status {
48
+ display: flex;
49
+ align-items: center;
50
+ gap: 10px;
51
+ margin-bottom: 15px;
52
+ }
53
+
54
+ .status-dot {
55
+ width: 12px;
56
+ height: 12px;
57
+ border-radius: 50%;
58
+ background: #ccc;
59
+ }
60
+
61
+ .status-dot.connected {
62
+ background: #27ae60;
63
+ }
64
+
65
+ .status-dot.disconnected {
66
+ background: #e74c3c;
67
+ }
68
+
69
+ .btn {
70
+ padding: 10px 20px;
71
+ border: none;
72
+ border-radius: 4px;
73
+ font-size: 14px;
74
+ cursor: pointer;
75
+ margin-right: 10px;
76
+ margin-bottom: 10px;
77
+ }
78
+
79
+ .btn-primary {
80
+ background: #667eea;
81
+ color: white;
82
+ }
83
+
84
+ .btn-success {
85
+ background: #27ae60;
86
+ color: white;
87
+ }
88
+
89
+ .btn-danger {
90
+ background: #e74c3c;
91
+ color: white;
92
+ }
93
+
94
+ .btn:disabled {
95
+ opacity: 0.5;
96
+ cursor: not-allowed;
97
+ }
98
+
99
+ .log {
100
+ background: #1e1e1e;
101
+ color: #d4d4d4;
102
+ padding: 15px;
103
+ border-radius: 4px;
104
+ font-family: monospace;
105
+ font-size: 12px;
106
+ max-height: 400px;
107
+ overflow-y: auto;
108
+ white-space: pre-wrap;
109
+ line-height: 1.4;
110
+ }
111
+
112
+ .log-entry {
113
+ margin-bottom: 5px;
114
+ }
115
+
116
+ .log-time {
117
+ color: #858585;
118
+ }
119
+
120
+ .log-info {
121
+ color: #4ec9b0;
122
+ }
123
+
124
+ .log-success {
125
+ color: #27ae60;
126
+ }
127
+
128
+ .log-error {
129
+ color: #f48771;
130
+ }
131
+
132
+ .log-event {
133
+ color: #dcdcaa;
134
+ }
135
+
136
+ input {
137
+ padding: 8px 12px;
138
+ border: 1px solid #ddd;
139
+ border-radius: 4px;
140
+ font-size: 14px;
141
+ margin-right: 10px;
142
+ }
143
+
144
+ .info-grid {
145
+ display: grid;
146
+ grid-template-columns: 150px 1fr;
147
+ gap: 10px;
148
+ margin-bottom: 15px;
149
+ }
150
+
151
+ .info-label {
152
+ color: #666;
153
+ font-weight: 500;
154
+ }
155
+
156
+ .info-value {
157
+ color: #333;
158
+ font-family: monospace;
159
+ }
160
+ </style>
161
+ </head>
162
+ <body>
163
+ <div class="container">
164
+ <h1>🔌 中转服务测试</h1>
165
+
166
+ <!-- 连接状态 -->
167
+ <div class="panel">
168
+ <h2>连接状态</h2>
169
+ <div class="status">
170
+ <div class="status-dot" id="status-dot"></div>
171
+ <span id="status-text">未连接</span>
172
+ </div>
173
+ <div class="info-grid" id="connection-info" style="display: none;">
174
+ <div class="info-label">Module ID:</div>
175
+ <div class="info-value" id="module-id">-</div>
176
+ <div class="info-label">Role:</div>
177
+ <div class="info-value" id="role">-</div>
178
+ <div class="info-label">Session Token:</div>
179
+ <div class="info-value" id="session-token">-</div>
180
+ </div>
181
+ <div>
182
+ <button class="btn btn-primary" id="btn-connect">连接(使用已有 Token)</button>
183
+ <button class="btn btn-danger" id="btn-disconnect" disabled>断开</button>
184
+ <button class="btn btn-success" id="btn-request-code">首次配对(自动生成配对码)</button>
185
+ </div>
186
+ </div>
187
+
188
+ <!-- 配对码显示 -->
189
+ <div class="panel" id="pairing-code-panel" style="display: none;">
190
+ <h2>配对码</h2>
191
+ <div style="font-size: 32px; font-weight: bold; color: #667eea; text-align: center; padding: 20px; background: #f5f5f5; border-radius: 8px; font-family: monospace;">
192
+ <span id="pairing-code">------</span>
193
+ </div>
194
+ <div style="margin-top: 10px; text-align: center; color: #666;">
195
+ 有效期:5 分钟
196
+ </div>
197
+ </div>
198
+
199
+ <!-- RPC 测试 -->
200
+ <div class="panel">
201
+ <h2>RPC 调用测试</h2>
202
+ <div>
203
+ <button class="btn btn-primary" id="btn-list-modules" disabled>launcher.list_modules</button>
204
+ <button class="btn btn-primary" id="btn-kernel-health" disabled>kernel.health</button>
205
+ <button class="btn btn-primary" id="btn-kernel-stats" disabled>kernel.stats</button>
206
+ </div>
207
+ </div>
208
+
209
+ <!-- 事件订阅测试 -->
210
+ <div class="panel">
211
+ <h2>事件订阅测试</h2>
212
+ <div style="margin-bottom: 15px;">
213
+ <button class="btn btn-success" id="btn-subscribe-all" disabled>订阅全部事件 (*)</button>
214
+ <button class="btn btn-success" id="btn-subscribe-module" disabled>订阅模块事件 (module.*)</button>
215
+ <button class="btn btn-danger" id="btn-unsubscribe" disabled>取消订阅</button>
216
+ </div>
217
+ <div>
218
+ <strong>当前订阅:</strong> <span id="current-subscription">无</span>
219
+ </div>
220
+ </div>
221
+
222
+ <!-- 日志 -->
223
+ <div class="panel">
224
+ <h2>日志</h2>
225
+ <button class="btn btn-danger" id="btn-clear-log" style="margin-bottom: 10px;">清空日志</button>
226
+ <div class="log" id="log"></div>
227
+ </div>
228
+ </div>
229
+
230
+ <script src="js/kernel-client.js"></script>
231
+ <script>
232
+ let client = null;
233
+ let currentSubscription = null;
234
+
235
+ // 日志函数
236
+ function log(message, type = 'info') {
237
+ const logEl = document.getElementById('log');
238
+ const time = new Date().toLocaleTimeString('zh-CN', { hour12: false, fractionalSecondDigits: 3 });
239
+ const entry = document.createElement('div');
240
+ entry.className = 'log-entry';
241
+ entry.innerHTML = `<span class="log-time">[${time}]</span> <span class="log-${type}">${message}</span>`;
242
+ logEl.insertBefore(entry, logEl.firstChild);
243
+ }
244
+
245
+ // 更新状态
246
+ function updateStatus(connected, info = null) {
247
+ const dot = document.getElementById('status-dot');
248
+ const text = document.getElementById('status-text');
249
+ const infoEl = document.getElementById('connection-info');
250
+
251
+ if (connected) {
252
+ dot.className = 'status-dot connected';
253
+ text.textContent = '已连接';
254
+ infoEl.style.display = 'grid';
255
+ document.getElementById('module-id').textContent = info.moduleId || '-';
256
+ document.getElementById('role').textContent = info.role || '-';
257
+ document.getElementById('session-token').textContent = info.sessionToken || '-';
258
+
259
+ // 启用按钮
260
+ document.getElementById('btn-disconnect').disabled = false;
261
+ document.getElementById('btn-list-modules').disabled = false;
262
+ document.getElementById('btn-kernel-health').disabled = false;
263
+ document.getElementById('btn-kernel-stats').disabled = false;
264
+ document.getElementById('btn-subscribe-all').disabled = false;
265
+ document.getElementById('btn-subscribe-module').disabled = false;
266
+ document.getElementById('btn-unsubscribe').disabled = false;
267
+ } else {
268
+ dot.className = 'status-dot disconnected';
269
+ text.textContent = '未连接';
270
+ infoEl.style.display = 'none';
271
+
272
+ // 禁用按钮
273
+ document.getElementById('btn-disconnect').disabled = true;
274
+ document.getElementById('btn-list-modules').disabled = true;
275
+ document.getElementById('btn-kernel-health').disabled = true;
276
+ document.getElementById('btn-kernel-stats').disabled = true;
277
+ document.getElementById('btn-subscribe-all').disabled = true;
278
+ document.getElementById('btn-subscribe-module').disabled = true;
279
+ document.getElementById('btn-unsubscribe').disabled = true;
280
+ }
281
+ }
282
+
283
+ // 连接
284
+ document.getElementById('btn-connect').addEventListener('click', async () => {
285
+ try {
286
+ log('正在连接...', 'info');
287
+ const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
288
+ client = new KernelClient(`${proto}//${location.host}/ws/relay`);
289
+
290
+ const result = await client.connect();
291
+ log(`连接成功!Module ID: ${result.moduleId}`, 'success');
292
+ updateStatus(true, {
293
+ moduleId: result.moduleId,
294
+ role: result.role,
295
+ sessionToken: client.sessionToken
296
+ });
297
+
298
+ // 监听所有事件
299
+ client.eventListeners.set('*', [(data) => {
300
+ log(`收到事件: ${JSON.stringify(data)}`, 'event');
301
+ }]);
302
+
303
+ } catch (err) {
304
+ log(`连接失败: ${err.message}`, 'error');
305
+ updateStatus(false);
306
+ }
307
+ });
308
+
309
+ // 断开
310
+ document.getElementById('btn-disconnect').addEventListener('click', () => {
311
+ if (client) {
312
+ client.disconnect();
313
+ log('已断开连接', 'info');
314
+ updateStatus(false);
315
+ client = null;
316
+ }
317
+ });
318
+
319
+ // 请求配对码并自动配对
320
+ document.getElementById('btn-request-code').addEventListener('click', async () => {
321
+ try {
322
+ log('正在请求配对码...', 'info');
323
+ const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
324
+ const ws = new WebSocket(`${proto}//${location.host}/ws/relay`);
325
+
326
+ ws.onopen = () => {
327
+ log('WebSocket 已连接,发送请求...', 'info');
328
+ ws.send(JSON.stringify({ type: 'request_code' }));
329
+ };
330
+
331
+ ws.onmessage = async (event) => {
332
+ const msg = JSON.parse(event.data);
333
+ log(`收到消息: ${JSON.stringify(msg)}`, 'info');
334
+
335
+ if (msg.type === 'code_generated') {
336
+ const code = msg.code;
337
+ log(`配对码已生成: ${code}`, 'success');
338
+ document.getElementById('pairing-code').textContent = code;
339
+ document.getElementById('pairing-code-panel').style.display = 'block';
340
+
341
+ // 同时在控制台输出
342
+ console.log('='.repeat(50));
343
+ console.log('配对码:', code);
344
+ console.log('有效期: 5 分钟');
345
+ console.log('='.repeat(50));
346
+
347
+ // 关闭这个 WebSocket
348
+ ws.close();
349
+
350
+ // 自动配对
351
+ log('自动配对中...', 'info');
352
+ client = new KernelClient(`${proto}//${location.host}/ws/relay`);
353
+ const result = await client.pair(code);
354
+ log(`配对成功!Module ID: ${result.moduleId}`, 'success');
355
+ updateStatus(true, {
356
+ moduleId: result.moduleId,
357
+ role: result.role,
358
+ sessionToken: client.sessionToken
359
+ });
360
+
361
+ // 监听所有事件
362
+ client.eventListeners.set('*', [(data) => {
363
+ log(`收到事件: ${JSON.stringify(data)}`, 'event');
364
+ }]);
365
+
366
+ } else if (msg.type === 'error') {
367
+ log(`生成配对码失败: ${msg.message}`, 'error');
368
+ }
369
+ };
370
+
371
+ ws.onerror = (err) => {
372
+ log('WebSocket 错误', 'error');
373
+ console.error(err);
374
+ };
375
+
376
+ } catch (err) {
377
+ log(`配对失败: ${err.message}`, 'error');
378
+ }
379
+ });
380
+
381
+ // RPC 测试
382
+ document.getElementById('btn-list-modules').addEventListener('click', async () => {
383
+ try {
384
+ log('调用 launcher.list_modules...', 'info');
385
+ const result = await client.call('launcher.list_modules', {});
386
+ log(`成功!返回 ${result.modules.length} 个模块`, 'success');
387
+ log(JSON.stringify(result, null, 2), 'info');
388
+ } catch (err) {
389
+ log(`失败: ${err.message}`, 'error');
390
+ }
391
+ });
392
+
393
+ document.getElementById('btn-kernel-health').addEventListener('click', async () => {
394
+ try {
395
+ log('调用 kernel.health...', 'info');
396
+ const result = await client.call('kernel.health', {});
397
+ log(`成功!`, 'success');
398
+ log(JSON.stringify(result, null, 2), 'info');
399
+ } catch (err) {
400
+ log(`失败: ${err.message}`, 'error');
401
+ }
402
+ });
403
+
404
+ document.getElementById('btn-kernel-stats').addEventListener('click', async () => {
405
+ try {
406
+ log('调用 kernel.stats...', 'info');
407
+ const result = await client.call('kernel.stats', {});
408
+ log(`成功!`, 'success');
409
+ log(JSON.stringify(result, null, 2), 'info');
410
+ } catch (err) {
411
+ log(`失败: ${err.message}`, 'error');
412
+ }
413
+ });
414
+
415
+ // 事件订阅测试
416
+ document.getElementById('btn-subscribe-all').addEventListener('click', async () => {
417
+ try {
418
+ log('订阅全部事件 (*)...', 'info');
419
+ await client.call('event.subscribe', { events: ['*'] });
420
+ currentSubscription = '*';
421
+ document.getElementById('current-subscription').textContent = '*';
422
+ log('订阅成功!', 'success');
423
+ } catch (err) {
424
+ log(`订阅失败: ${err.message}`, 'error');
425
+ }
426
+ });
427
+
428
+ document.getElementById('btn-subscribe-module').addEventListener('click', async () => {
429
+ try {
430
+ log('订阅模块事件 (module.*)...', 'info');
431
+ await client.call('event.subscribe', { events: ['module.*'] });
432
+ currentSubscription = 'module.*';
433
+ document.getElementById('current-subscription').textContent = 'module.*';
434
+ log('订阅成功!', 'success');
435
+ } catch (err) {
436
+ log(`订阅失败: ${err.message}`, 'error');
437
+ }
438
+ });
439
+
440
+ document.getElementById('btn-unsubscribe').addEventListener('click', async () => {
441
+ try {
442
+ log('取消订阅...', 'info');
443
+ await client.call('event.subscribe', { events: [] });
444
+ currentSubscription = null;
445
+ document.getElementById('current-subscription').textContent = '无';
446
+ log('取消成功!', 'success');
447
+ } catch (err) {
448
+ log(`取消失败: ${err.message}`, 'error');
449
+ }
450
+ });
451
+
452
+ // 清空日志
453
+ document.getElementById('btn-clear-log').addEventListener('click', () => {
454
+ document.getElementById('log').innerHTML = '';
455
+ });
456
+
457
+ // 页面加载时的提示
458
+ log('中转服务测试页面已加载', 'info');
459
+ log('请先点击"连接"或"配对"按钮', 'info');
460
+ </script>
461
+ </body>
462
+ </html>
@@ -0,0 +1,240 @@
1
+ """
2
+ 积分统计管理模块 - 定时采集、数据分析
3
+
4
+ 数据存储结构:
5
+ - stats/YYYY-MM/YYYY-MM-DD.jsonl - 每日账户信息记录
6
+ - 每 15 分钟自动采集一次
7
+ - 手动访问账户页面时也会触发采集
8
+ """
9
+
10
+ import asyncio
11
+ import json
12
+ import os
13
+ import time
14
+ from datetime import datetime, timezone
15
+ from typing import Optional
16
+
17
+
18
+ class StatsManager:
19
+ """积分统计管理器"""
20
+
21
+ def __init__(self, data_dir: str, evol_api, auth_manager):
22
+ """
23
+ 初始化统计管理器
24
+
25
+ Args:
26
+ data_dir: 数据目录($KITE_DATA/evol)
27
+ evol_api: Evol API 实例
28
+ auth_manager: 认证管理器实例
29
+ """
30
+ self.data_dir = data_dir
31
+ self.evol_api = evol_api
32
+ self.auth_manager = auth_manager
33
+
34
+ self.stats_dir = os.path.join(data_dir, "stats")
35
+ os.makedirs(self.stats_dir, exist_ok=True)
36
+
37
+ self.interval = 15 * 60 # 15 分钟
38
+ self.running = False
39
+ self.task = None
40
+
41
+ def _get_daily_file(self, dt: datetime = None) -> str:
42
+ """获取每日记录文件路径"""
43
+ if dt is None:
44
+ dt = datetime.now(timezone.utc)
45
+
46
+ month_dir = os.path.join(self.stats_dir, dt.strftime("%Y-%m"))
47
+ os.makedirs(month_dir, exist_ok=True)
48
+
49
+ return os.path.join(month_dir, dt.strftime("%Y-%m-%d.jsonl"))
50
+
51
+ async def start(self):
52
+ """启动定时采集任务"""
53
+ if self.running:
54
+ return
55
+
56
+ self.running = True
57
+ self.task = asyncio.create_task(self._collect_loop())
58
+ print("[stats] 积分监控已启动,采集间隔: 15分钟")
59
+
60
+ async def stop(self):
61
+ """停止定时采集任务"""
62
+ self.running = False
63
+ if self.task:
64
+ self.task.cancel()
65
+ try:
66
+ await self.task
67
+ except asyncio.CancelledError:
68
+ pass
69
+
70
+ async def _collect_loop(self):
71
+ """定时采集循环"""
72
+ while self.running:
73
+ try:
74
+ await self._collect_once()
75
+ except Exception as e:
76
+ print(f"[stats] 采集失败: {e}")
77
+
78
+ # Wait for next interval
79
+ await asyncio.sleep(self.interval)
80
+
81
+ async def _collect_once(self):
82
+ """执行一次采集(采集所有已登录用户)"""
83
+ # 获取所有有效的 Evol Token
84
+ evol_records = self.auth_manager.list_all_evol_tokens()
85
+ if not evol_records:
86
+ return
87
+
88
+ # 为每个用户采集积分信息
89
+ for evol_record in evol_records:
90
+ try:
91
+ # Fetch user info
92
+ result = await self.evol_api.get_user_info(evol_record["token"])
93
+ if not result.get("success"):
94
+ continue
95
+
96
+ data = result["data"]
97
+ self._save_account_info(data)
98
+ except Exception as e:
99
+ phone = evol_record.get("phone", "unknown")
100
+ print(f"[stats] 采集用户 {phone} 失败: {e}")
101
+
102
+ def _save_account_info(self, data: dict):
103
+ """保存账户信息到每日文件"""
104
+ now = datetime.now(timezone.utc)
105
+ account_info = data.get("accountInfo", {})
106
+ user_info = data.get("userInfo", {})
107
+ credits_package = account_info.get("creditsPackageSummary", {})
108
+
109
+ record = {
110
+ "timestamp": time.time(),
111
+ "timestamp_human": now.isoformat(),
112
+ "userInfo": {
113
+ "userId": user_info.get("userId"),
114
+ "phone": user_info.get("phone"),
115
+ "nickName": user_info.get("nickName")
116
+ },
117
+ "accountInfo": {
118
+ "credits": account_info.get("credits", 0),
119
+ "creditsLimit": account_info.get("creditsLimit", 0),
120
+ "vipType": account_info.get("vipType", 0),
121
+ "vipTypeName": account_info.get("vipTypeName", "Unknown"),
122
+ "vipExpireTime": account_info.get("vipExpireTime"),
123
+ "vipRemainingDays": account_info.get("vipRemainingDays", 0)
124
+ },
125
+ "creditsPackage": {
126
+ "chatbotCredits": credits_package.get("chatbotCredits", 0),
127
+ "codexCredits": credits_package.get("codexCredits", 0),
128
+ "claudeCodeCredits": credits_package.get("claudeCodeCredits", 0),
129
+ "geminiCredits": credits_package.get("geminiCredits", 0),
130
+ "openclawCredits": credits_package.get("openclawCredits", 0),
131
+ "generalCredits": credits_package.get("generalCredits", 0)
132
+ },
133
+ "apiKey": data.get("apiKey", "")
134
+ }
135
+
136
+ # 追加到每日文件
137
+ daily_file = self._get_daily_file(now)
138
+ with open(daily_file, "a", encoding="utf-8") as f:
139
+ f.write(json.dumps(record, ensure_ascii=False) + "\n")
140
+
141
+ print(f"[stats] 账户信息已保存: credits={record['accountInfo']['credits']}")
142
+
143
+ async def collect_manual(self):
144
+ """手动触发采集(用户访问账户页面时)"""
145
+ try:
146
+ await self._collect_once()
147
+ except Exception as e:
148
+ print(f"[stats] 手动采集失败: {e}")
149
+
150
+ def get_stats(self, period: str = "day", date: Optional[str] = None) -> dict:
151
+ """
152
+ 获取统计数据
153
+
154
+ Args:
155
+ period: 统计周期 (day/week/month)
156
+ date: 日期 (YYYY-MM-DD)
157
+
158
+ Returns:
159
+ 统计数据
160
+ """
161
+ if date is None:
162
+ date = datetime.now(timezone.utc).strftime("%Y-%m-%d")
163
+
164
+ try:
165
+ dt = datetime.strptime(date, "%Y-%m-%d")
166
+ except ValueError:
167
+ return {"success": False, "msg": "Invalid date format"}
168
+
169
+ # 读取对应日期的文件
170
+ daily_file = self._get_daily_file(dt)
171
+
172
+ if not os.path.exists(daily_file):
173
+ return {
174
+ "success": True,
175
+ "data": {
176
+ "period": period,
177
+ "date": date,
178
+ "records": [],
179
+ "summary": {
180
+ "startCredits": 0,
181
+ "endCredits": 0,
182
+ "consumed": 0,
183
+ "recovered": 0
184
+ }
185
+ }
186
+ }
187
+
188
+ # 读取所有记录
189
+ records = []
190
+ try:
191
+ with open(daily_file, "r", encoding="utf-8") as f:
192
+ for line in f:
193
+ line = line.strip()
194
+ if not line:
195
+ continue
196
+ try:
197
+ record = json.loads(line)
198
+ records.append(record)
199
+ except json.JSONDecodeError:
200
+ continue
201
+ except Exception as e:
202
+ return {"success": False, "msg": f"读取文件失败: {str(e)}"}
203
+
204
+ if not records:
205
+ return {
206
+ "success": True,
207
+ "data": {
208
+ "period": period,
209
+ "date": date,
210
+ "records": [],
211
+ "summary": {
212
+ "startCredits": 0,
213
+ "endCredits": 0,
214
+ "consumed": 0,
215
+ "recovered": 0
216
+ }
217
+ }
218
+ }
219
+
220
+ # 计算汇总
221
+ start_credits = records[0]["accountInfo"]["credits"]
222
+ end_credits = records[-1]["accountInfo"]["credits"]
223
+ consumed = max(0, start_credits - end_credits)
224
+ recovered = max(0, end_credits - start_credits)
225
+
226
+ return {
227
+ "success": True,
228
+ "data": {
229
+ "period": period,
230
+ "date": date,
231
+ "records": records,
232
+ "summary": {
233
+ "startCredits": start_credits,
234
+ "endCredits": end_credits,
235
+ "consumed": consumed,
236
+ "recovered": recovered,
237
+ "dataPoints": len(records)
238
+ }
239
+ }
240
+ }