@agentunion/kite 1.3.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. package/CHANGELOG.md +302 -0
  2. package/cli.js +119 -4
  3. package/core/dependency_checker.py +250 -0
  4. package/core/env_checker.py +490 -0
  5. package/dependencies_lock.json +128 -0
  6. package/extensions/agents/assistant/entry.py +111 -1
  7. package/extensions/agents/assistant/server.py +279 -215
  8. package/extensions/channels/acp_channel/entry.py +111 -1
  9. package/extensions/channels/acp_channel/module.md +23 -22
  10. package/extensions/channels/acp_channel/server.py +279 -215
  11. package/extensions/event_hub_bench/entry.py +107 -1
  12. package/extensions/services/backup/entry.py +306 -21
  13. package/extensions/services/backup/module.md +24 -22
  14. package/extensions/services/evol/auth_manager.py +443 -0
  15. package/extensions/services/evol/config.yaml +149 -0
  16. package/extensions/services/evol/config_loader.py +117 -0
  17. package/extensions/services/evol/entry.py +406 -0
  18. package/extensions/services/evol/evol_api.py +173 -0
  19. package/extensions/services/evol/evol_config.json5 +29 -0
  20. package/extensions/services/evol/migrate_tokens.py +122 -0
  21. package/extensions/services/evol/module.md +32 -0
  22. package/extensions/services/evol/pairing.py +250 -0
  23. package/extensions/services/evol/pairing_codes.jsonl +1 -0
  24. package/extensions/services/evol/relay.py +682 -0
  25. package/extensions/services/evol/relay_config.json5 +67 -0
  26. package/extensions/services/evol/routes/__init__.py +1 -0
  27. package/extensions/services/evol/routes/routes_management_ws.py +127 -0
  28. package/extensions/services/evol/routes/routes_rpc.py +89 -0
  29. package/extensions/services/evol/routes/routes_test.py +61 -0
  30. package/extensions/services/evol/server.py +875 -0
  31. package/extensions/services/evol/static/css/style.css +1200 -0
  32. package/extensions/services/evol/static/index.html +781 -0
  33. package/extensions/services/evol/static/index_evol.html +14 -0
  34. package/extensions/services/evol/static/js/app.js +6304 -0
  35. package/extensions/services/evol/static/js/auth.js +326 -0
  36. package/extensions/services/evol/static/js/dialog.js +285 -0
  37. package/extensions/services/evol/static/js/evol-app-fixed.js +50 -0
  38. package/extensions/services/evol/static/js/evol-app.js +1949 -0
  39. package/extensions/services/evol/static/js/evol-app.js.bak +1800 -0
  40. package/extensions/services/evol/static/js/kernel-client-example.js +228 -0
  41. package/extensions/services/evol/static/js/kernel-client.js +396 -0
  42. package/extensions/services/evol/static/js/main.js +141 -0
  43. package/extensions/services/evol/static/js/registry-tests.js +585 -0
  44. package/extensions/services/evol/static/js/stats.js +217 -0
  45. package/extensions/services/evol/static/js/token-manager.js +175 -0
  46. package/extensions/services/evol/static/pairing.html +248 -0
  47. package/extensions/services/evol/static/test_registry.html +262 -0
  48. package/extensions/services/evol/static/test_relay.html +462 -0
  49. package/extensions/services/evol/stats_manager.py +240 -0
  50. package/extensions/services/model_service/entry.py +167 -19
  51. package/extensions/services/model_service/module.md +21 -22
  52. package/extensions/services/proxy/.claude/settings.local.json +13 -0
  53. package/extensions/services/proxy/CHANGELOG_20260308.md +258 -0
  54. package/extensions/services/proxy/_fix_prints.py +133 -0
  55. package/extensions/services/proxy/_fix_prints2.py +87 -0
  56. package/extensions/services/proxy/agentcp/LICENCE +178 -0
  57. package/extensions/services/proxy/agentcp/README copy.md +85 -0
  58. package/extensions/services/proxy/agentcp/README.md +260 -0
  59. package/extensions/services/proxy/agentcp/__init__.py +16 -0
  60. package/extensions/services/proxy/agentcp/agent.py +4 -0
  61. package/extensions/services/proxy/agentcp/agentcp.py +2494 -0
  62. package/extensions/services/proxy/agentcp/agentprofile.json +89 -0
  63. package/extensions/services/proxy/agentcp/ap/__init__.py +16 -0
  64. package/extensions/services/proxy/agentcp/ap/ap_client.py +316 -0
  65. package/extensions/services/proxy/agentcp/assets/images/wechat_qr.png +0 -0
  66. package/extensions/services/proxy/agentcp/backup/metrics.json +31 -0
  67. package/extensions/services/proxy/agentcp/base/__init__.py +20 -0
  68. package/extensions/services/proxy/agentcp/base/auth_client.py +257 -0
  69. package/extensions/services/proxy/agentcp/base/client.py +112 -0
  70. package/extensions/services/proxy/agentcp/base/env.py +34 -0
  71. package/extensions/services/proxy/agentcp/base/html_util.py +336 -0
  72. package/extensions/services/proxy/agentcp/base/log.py +98 -0
  73. package/extensions/services/proxy/agentcp/ca/__init__.py +17 -0
  74. package/extensions/services/proxy/agentcp/ca/ca_client.py +414 -0
  75. package/extensions/services/proxy/agentcp/ca/ca_root.py +74 -0
  76. package/extensions/services/proxy/agentcp/context/__init__.py +20 -0
  77. package/extensions/services/proxy/agentcp/context/context.py +73 -0
  78. package/extensions/services/proxy/agentcp/context/exceptions.py +114 -0
  79. package/extensions/services/proxy/agentcp/create_profile.py +125 -0
  80. package/extensions/services/proxy/agentcp/create_profile_weather.py +125 -0
  81. package/extensions/services/proxy/agentcp/db/__init__.py +15 -0
  82. package/extensions/services/proxy/agentcp/db/db_mananger.py +550 -0
  83. package/extensions/services/proxy/agentcp/docs/UDP_HEARTBEAT_FIX_REPORT.md +265 -0
  84. package/extensions/services/proxy/agentcp/docs/heartbeat_issue_analysis.md +291 -0
  85. package/extensions/services/proxy/agentcp/file/__init__.py +16 -0
  86. package/extensions/services/proxy/agentcp/file/file_client.py +141 -0
  87. package/extensions/services/proxy/agentcp/file/wss_binary_message.py +137 -0
  88. package/extensions/services/proxy/agentcp/hcp.py +299 -0
  89. package/extensions/services/proxy/agentcp/heartbeat/__init__.py +16 -0
  90. package/extensions/services/proxy/agentcp/heartbeat/heartbeat_client.py +360 -0
  91. package/extensions/services/proxy/agentcp/improved_scheduler.py +498 -0
  92. package/extensions/services/proxy/agentcp/llm_agent_utils.py +249 -0
  93. package/extensions/services/proxy/agentcp/llm_server.py +172 -0
  94. package/extensions/services/proxy/agentcp/mermaid.py +210 -0
  95. package/extensions/services/proxy/agentcp/message.py +149 -0
  96. package/extensions/services/proxy/agentcp/metrics.py +256 -0
  97. package/extensions/services/proxy/agentcp/monitoring/__init__.py +20 -0
  98. package/extensions/services/proxy/agentcp/monitoring/global_monitor.py +27 -0
  99. package/extensions/services/proxy/agentcp/monitoring/metrics_store.py +325 -0
  100. package/extensions/services/proxy/agentcp/monitoring/monitoring_service.py +269 -0
  101. package/extensions/services/proxy/agentcp/monitoring/sliding_window.py +222 -0
  102. package/extensions/services/proxy/agentcp/monitoring/standalone_reader.py +224 -0
  103. package/extensions/services/proxy/agentcp/msg/__init__.py +21 -0
  104. package/extensions/services/proxy/agentcp/msg/connection_manager.py +456 -0
  105. package/extensions/services/proxy/agentcp/msg/message_client.py +2058 -0
  106. package/extensions/services/proxy/agentcp/msg/message_serialize.py +263 -0
  107. package/extensions/services/proxy/agentcp/msg/open_ai_message.py +88 -0
  108. package/extensions/services/proxy/agentcp/msg/session_manager.py +1062 -0
  109. package/extensions/services/proxy/agentcp/msg/stream_client.py +267 -0
  110. package/extensions/services/proxy/agentcp/msg/websocket_file_receiver.py +89 -0
  111. package/extensions/services/proxy/agentcp/msg/ws_logger.py +685 -0
  112. package/extensions/services/proxy/agentcp/msg/wss_binary_message.py +137 -0
  113. package/extensions/services/proxy/agentcp/requirements.txt +7 -0
  114. package/extensions/services/proxy/agentcp/samples/agent_graph/README.md +37 -0
  115. package/extensions/services/proxy/agentcp/samples/agent_graph/agentprofile.json +89 -0
  116. package/extensions/services/proxy/agentcp/samples/agent_graph/create_profile.py +138 -0
  117. package/extensions/services/proxy/agentcp/samples/agent_graph/main.py +164 -0
  118. package/extensions/services/proxy/agentcp/samples/agent_use/create_profile.py +123 -0
  119. package/extensions/services/proxy/agentcp/samples/agent_use/llm/create_profile.py +129 -0
  120. package/extensions/services/proxy/agentcp/samples/agent_use/llm/env.json +5 -0
  121. package/extensions/services/proxy/agentcp/samples/agent_use/llm/main.py +146 -0
  122. package/extensions/services/proxy/agentcp/samples/agent_use/main.py +123 -0
  123. package/extensions/services/proxy/agentcp/samples/agent_use/readme.md +379 -0
  124. package/extensions/services/proxy/agentcp/samples/agent_use/search/create_profile.py +129 -0
  125. package/extensions/services/proxy/agentcp/samples/agent_use/search/main.py +28 -0
  126. package/extensions/services/proxy/agentcp/samples/agent_use/tool/create_profile.py +129 -0
  127. package/extensions/services/proxy/agentcp/samples/agent_use/tool/main.py +20 -0
  128. package/extensions/services/proxy/agentcp/samples/ali_amap/README.md +97 -0
  129. package/extensions/services/proxy/agentcp/samples/ali_amap/amap_agent.py +88 -0
  130. package/extensions/services/proxy/agentcp/samples/ali_amap/create_profile.py +125 -0
  131. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/powershell.py +228 -0
  132. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/software.py +63 -0
  133. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/tools.py +36 -0
  134. package/extensions/services/proxy/agentcp/samples/compute_agent/browser_user.py +41 -0
  135. package/extensions/services/proxy/agentcp/samples/deepseek/README.md +79 -0
  136. package/extensions/services/proxy/agentcp/samples/deepseek/create_profile.py +126 -0
  137. package/extensions/services/proxy/agentcp/samples/deepseek/deepseek.py +42 -0
  138. package/extensions/services/proxy/agentcp/samples/dify_chat/README.md +78 -0
  139. package/extensions/services/proxy/agentcp/samples/dify_chat/create_profile.py +126 -0
  140. package/extensions/services/proxy/agentcp/samples/dify_chat/dify_chat.py +47 -0
  141. package/extensions/services/proxy/agentcp/samples/dify_workflow/README.md +78 -0
  142. package/extensions/services/proxy/agentcp/samples/dify_workflow/create_profile.py +126 -0
  143. package/extensions/services/proxy/agentcp/samples/dify_workflow/dify_workflow.py +46 -0
  144. package/extensions/services/proxy/agentcp/samples/executor/README.md +44 -0
  145. package/extensions/services/proxy/agentcp/samples/executor/agentprofile.json +89 -0
  146. package/extensions/services/proxy/agentcp/samples/executor/create_profile.py +139 -0
  147. package/extensions/services/proxy/agentcp/samples/executor/main.py +160 -0
  148. package/extensions/services/proxy/agentcp/samples/filereader/README.md +45 -0
  149. package/extensions/services/proxy/agentcp/samples/filereader/agentprofile.json +90 -0
  150. package/extensions/services/proxy/agentcp/samples/filereader/create_profile.py +137 -0
  151. package/extensions/services/proxy/agentcp/samples/filereader/main.py +253 -0
  152. package/extensions/services/proxy/agentcp/samples/filewriter/README.md +38 -0
  153. package/extensions/services/proxy/agentcp/samples/filewriter/agentprofile.json +91 -0
  154. package/extensions/services/proxy/agentcp/samples/filewriter/create_profile.py +138 -0
  155. package/extensions/services/proxy/agentcp/samples/filewriter/main.py +289 -0
  156. package/extensions/services/proxy/agentcp/samples/hcp/README.md +85 -0
  157. package/extensions/services/proxy/agentcp/samples/hcp/acp_weather_agent.zip +0 -0
  158. package/extensions/services/proxy/agentcp/samples/hcp/create_profile.py +125 -0
  159. package/extensions/services/proxy/agentcp/samples/hcp/hcp.py +237 -0
  160. package/extensions/services/proxy/agentcp/samples/helloworld/README.md +68 -0
  161. package/extensions/services/proxy/agentcp/samples/helloworld/hello_world.py +40 -0
  162. package/extensions/services/proxy/agentcp/samples/llm_agent/MEADME.md +117 -0
  163. package/extensions/services/proxy/agentcp/samples/llm_agent/create_profile.py +125 -0
  164. package/extensions/services/proxy/agentcp/samples/llm_agent/qwen_agent.py +136 -0
  165. package/extensions/services/proxy/agentcp/samples/local_llm_agent/README.md +90 -0
  166. package/extensions/services/proxy/agentcp/samples/local_llm_agent/create_profile.py +125 -0
  167. package/extensions/services/proxy/agentcp/samples/local_llm_agent/main.py +49 -0
  168. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/README.md +55 -0
  169. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/create_profile.py +125 -0
  170. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/main.py +23 -0
  171. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/README.md +103 -0
  172. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/create_profile.py +125 -0
  173. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/main.py +69 -0
  174. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/README.md +58 -0
  175. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/create_profile.py +125 -0
  176. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/main.py +25 -0
  177. package/extensions/services/proxy/agentcp/samples/qwen3/README.md +71 -0
  178. package/extensions/services/proxy/agentcp/samples/qwen3/create_profile.py +126 -0
  179. package/extensions/services/proxy/agentcp/samples/qwen3/qwen3.py +37 -0
  180. package/extensions/services/proxy/agentcp/samples/qwen3_tools/README.md +133 -0
  181. package/extensions/services/proxy/agentcp/samples/qwen3_tools/create_profile.py +126 -0
  182. package/extensions/services/proxy/agentcp/samples/qwen3_tools/qwen3_tools.py +98 -0
  183. package/extensions/services/proxy/agentcp/samples/search/create_profile_qwen.py +125 -0
  184. package/extensions/services/proxy/agentcp/samples/search/create_profile_search.py +125 -0
  185. package/extensions/services/proxy/agentcp/samples/search/qwen_agent.py +136 -0
  186. package/extensions/services/proxy/agentcp/samples/search/search_agent.py +170 -0
  187. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/README.md +89 -0
  188. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/create_profile.py +125 -0
  189. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/main.py +44 -0
  190. package/extensions/services/proxy/agentcp/utils/__init__.py +15 -0
  191. package/extensions/services/proxy/agentcp/utils/file_util.py +117 -0
  192. package/extensions/services/proxy/agentcp/utils/proxy_bypass.py +99 -0
  193. package/extensions/services/proxy/agentcp/workflow.py +203 -0
  194. package/extensions/services/proxy/console_auth.py +109 -0
  195. package/extensions/services/proxy/evol/__init__.py +1 -0
  196. package/extensions/services/proxy/evol/config.py +37 -0
  197. package/extensions/services/proxy/evol/http/__init__.py +1 -0
  198. package/extensions/services/proxy/evol/http/async_http.py +551 -0
  199. package/extensions/services/proxy/evol/log.py +28 -0
  200. package/extensions/services/proxy/evol/presenter/__init__.py +2 -0
  201. package/extensions/services/proxy/evol/presenter/agentIdPresenter.py +1031 -0
  202. package/extensions/services/proxy/evol/presenter/apikeyPresenter.py +106 -0
  203. package/extensions/services/proxy/evol/presenter/configPresenter.py +1281 -0
  204. package/extensions/services/proxy/evol/presenter/userPresenter.py +477 -0
  205. package/extensions/services/proxy/evol/server/__init__.py +1 -0
  206. package/extensions/services/proxy/evol/server/claude_proxy_async.py +3430 -0
  207. package/extensions/services/proxy/evol/server/openclaw_proxy.py +1861 -0
  208. package/extensions/services/proxy/evol/server/proxy_config.py +15 -0
  209. package/extensions/services/proxy/evol/server/proxy_engine.py +501 -0
  210. package/extensions/services/proxy/evol/version.py +24 -0
  211. package/extensions/services/proxy/logs/websocket.log +260 -0
  212. package/extensions/services/proxy/main.py +240 -0
  213. package/extensions/services/proxy/requirements.txt +13 -0
  214. package/extensions/services/proxy/server.py +271 -0
  215. package/extensions/services/watchdog/entry.py +215 -26
  216. package/extensions/services/watchdog/module.md +1 -0
  217. package/extensions/services/watchdog/monitor.py +178 -38
  218. package/extensions/services/web/WEBSOCKET_STATUS.md +143 -0
  219. package/extensions/services/web/config_example.py +35 -0
  220. package/extensions/services/web/config_loader.py +110 -0
  221. package/extensions/services/web/entry.py +114 -26
  222. package/extensions/services/web/module.md +35 -24
  223. package/extensions/services/web/pairing.py +250 -0
  224. package/extensions/services/web/pairing_codes.jsonl +16 -0
  225. package/extensions/services/web/relay.py +643 -0
  226. package/extensions/services/web/relay_config.json5 +67 -0
  227. package/extensions/services/web/routes/routes_management_ws.py +127 -0
  228. package/extensions/services/web/routes/routes_rpc.py +89 -0
  229. package/extensions/services/web/routes/routes_test.py +61 -0
  230. package/extensions/services/web/routes/schemas.py +0 -22
  231. package/extensions/services/web/server.py +434 -99
  232. package/extensions/services/web/static/css/style.css +67 -28
  233. package/extensions/services/web/static/index.html +234 -44
  234. package/extensions/services/web/static/js/app.js +1335 -48
  235. package/extensions/services/web/static/js/kernel-client-example.js +161 -0
  236. package/extensions/services/web/static/js/kernel-client.js +383 -0
  237. package/extensions/services/web/static/js/registry-tests.js +558 -0
  238. package/extensions/services/web/static/js/token-manager.js +175 -0
  239. package/extensions/services/web/static/pairing.html +248 -0
  240. package/extensions/services/web/static/test_registry.html +262 -0
  241. package/extensions/services/web/web_config.json5 +29 -0
  242. package/kernel/entry.py +120 -32
  243. package/kernel/event_hub.py +141 -16
  244. package/kernel/module.md +60 -33
  245. package/kernel/registry_store.py +45 -36
  246. package/kernel/rpc_router.py +152 -59
  247. package/kernel/server.py +322 -26
  248. package/kite_cli/__init__.py +3 -0
  249. package/kite_cli/__main__.py +5 -0
  250. package/kite_cli/commands/__init__.py +1 -0
  251. package/kite_cli/commands/clean.py +101 -0
  252. package/kite_cli/commands/deps_install.py +67 -0
  253. package/kite_cli/commands/doctor.py +35 -0
  254. package/kite_cli/commands/env_check.py +45 -0
  255. package/kite_cli/commands/history.py +111 -0
  256. package/kite_cli/commands/info.py +96 -0
  257. package/kite_cli/commands/install.py +313 -0
  258. package/kite_cli/commands/list.py +143 -0
  259. package/kite_cli/commands/log.py +81 -0
  260. package/kite_cli/commands/prepare.py +49 -0
  261. package/kite_cli/commands/rollback.py +88 -0
  262. package/kite_cli/commands/search.py +73 -0
  263. package/kite_cli/commands/uninstall.py +85 -0
  264. package/kite_cli/commands/update.py +118 -0
  265. package/kite_cli/commands/venv_setup.py +56 -0
  266. package/kite_cli/core/__init__.py +1 -0
  267. package/kite_cli/core/checker.py +142 -0
  268. package/kite_cli/core/dependency.py +229 -0
  269. package/kite_cli/core/downloader.py +209 -0
  270. package/kite_cli/core/install_info.py +40 -0
  271. package/kite_cli/core/tool_installer.py +397 -0
  272. package/kite_cli/core/validator.py +78 -0
  273. package/kite_cli/main.py +317 -0
  274. package/kite_cli/utils/__init__.py +1 -0
  275. package/kite_cli/utils/i18n.py +252 -0
  276. package/kite_cli/utils/interactive.py +63 -0
  277. package/kite_cli/utils/operation_log.py +77 -0
  278. package/kite_cli/utils/paths.py +34 -0
  279. package/kite_cli/utils/version.py +308 -0
  280. package/launcher/entry.py +1124 -178
  281. package/launcher/logging_setup.py +104 -0
  282. package/launcher/module.md +46 -37
  283. package/launcher/module_scanner.py +11 -1
  284. package/main.py +4 -1
  285. package/package.json +9 -1
  286. package/python_version.json +4 -0
  287. package/requirements.txt +38 -0
  288. package/scripts/env-manager.js +328 -0
  289. package/scripts/plan_manager.py +315 -0
  290. package/scripts/python-env.js +79 -0
  291. package/scripts/scan_dependencies.py +461 -0
  292. package/scripts/setup-python-env.js +191 -0
  293. package/extensions/services/web/routes/routes_modules.py +0 -249
@@ -0,0 +1,443 @@
1
+ """
2
+ 认证管理模块 - Token 缓存、验证、吊销
3
+
4
+ 使用 JSONL 格式存储 Token 历史记录,每次变更追加一条记录。
5
+ 查询时使用每个 Token 的最新记录。
6
+ """
7
+
8
+ import json
9
+ import os
10
+ import secrets
11
+ import time
12
+ from datetime import datetime, timezone
13
+ from typing import Optional
14
+
15
+
16
+ class AuthManager:
17
+ """认证管理器"""
18
+
19
+ def __init__(self, data_dir: str):
20
+ """
21
+ 初始化认证管理器
22
+
23
+ Args:
24
+ data_dir: 数据目录($KITE_DATA/evol)
25
+ """
26
+ self.data_dir = data_dir
27
+ self.auth_dir = os.path.join(data_dir, "auth")
28
+ os.makedirs(self.auth_dir, exist_ok=True)
29
+
30
+ self.evol_tokens_file = os.path.join(self.auth_dir, "evol_tokens.jsonl")
31
+ self.kite_tokens_file = os.path.join(self.auth_dir, "kite_tokens.jsonl")
32
+
33
+ self.kite_token_ttl = 30 * 24 * 3600 # 30 days
34
+ self.evol_token_ttl = 7 * 24 * 3600 # 7 days
35
+ self.token_update_interval = 24 * 3600 # 1 day - Token 更新间隔
36
+
37
+ # 内存缓存
38
+ self._kite_tokens_cache = None
39
+ self._kite_tokens_cache_time = 0
40
+ self._evol_tokens_cache = {} # 改为字典:{phone: record}
41
+ self._evol_tokens_cache_time = 0
42
+ self.cache_ttl = 60 * 60 # 1 小时缓存
43
+
44
+ def save_evol_token(self, phone: str, token: str, data: dict):
45
+ """保存 Evol Token 到 JSONL"""
46
+ now = time.time()
47
+ record = {
48
+ "phone": phone,
49
+ "token": token,
50
+ "action": "login",
51
+ "obtainedAt": now,
52
+ "obtainedAt_human": datetime.fromtimestamp(now, timezone.utc).isoformat(),
53
+ "expiresAt": now + self.evol_token_ttl,
54
+ "expiresAt_human": datetime.fromtimestamp(now + self.evol_token_ttl, timezone.utc).isoformat(),
55
+ "lastUsedAt": now,
56
+ "lastUsedAt_human": datetime.fromtimestamp(now, timezone.utc).isoformat(),
57
+ "userInfo": data.get("userInfo", {}),
58
+ "accountInfo": data.get("accountInfo", {}),
59
+ "teamInfo": data.get("teamInfo", {}),
60
+ "gatewayInfo": data.get("gatewayInfo", {}),
61
+ "timestamp": now
62
+ }
63
+
64
+ self._append_evol_record(record)
65
+ # 清除缓存
66
+ self._evol_tokens_cache = {}
67
+ self._evol_tokens_cache_time = 0
68
+
69
+ def get_evol_token(self, phone: str) -> Optional[dict]:
70
+ """获取指定手机号的最新 Evol Token(带缓存)"""
71
+ now = time.time()
72
+
73
+ # 检查缓存是否有效
74
+ if self._evol_tokens_cache and (now - self._evol_tokens_cache_time < self.cache_ttl):
75
+ return self._evol_tokens_cache.get(phone)
76
+
77
+ # 从文件读取所有 Evol Token
78
+ if not os.path.exists(self.evol_tokens_file):
79
+ return None
80
+
81
+ try:
82
+ # 按 phone 分组,保留每个 phone 的最新记录
83
+ phone_tokens = {}
84
+ with open(self.evol_tokens_file, "r", encoding="utf-8") as f:
85
+ for line in f:
86
+ line = line.strip()
87
+ if not line:
88
+ continue
89
+ try:
90
+ record = json.loads(line)
91
+ record_phone = record.get("phone")
92
+ if not record_phone:
93
+ continue
94
+ # 使用最新的记录(后面的覆盖前面的)
95
+ phone_tokens[record_phone] = record
96
+ except json.JSONDecodeError:
97
+ continue
98
+
99
+ # 过滤掉已过期和已登出的 token
100
+ valid_tokens = {}
101
+ for p, record in phone_tokens.items():
102
+ # 检查是否登出
103
+ if record.get("action") == "logout":
104
+ continue
105
+ # 检查是否过期
106
+ if time.time() > record.get("expiresAt", 0):
107
+ continue
108
+ valid_tokens[p] = record
109
+
110
+ # 更新缓存
111
+ self._evol_tokens_cache = valid_tokens
112
+ self._evol_tokens_cache_time = now
113
+
114
+ return valid_tokens.get(phone)
115
+
116
+ except Exception:
117
+ return None
118
+
119
+ def update_evol_token_usage(self, phone: str):
120
+ """更新 Evol Token 使用时间(超过 1 天才记录)"""
121
+ latest_record = self.get_evol_token(phone)
122
+ if not latest_record:
123
+ return
124
+
125
+ now = time.time()
126
+ last_used = latest_record.get("lastUsedAt", 0)
127
+
128
+ # 如果距离上次使用不到 1 天,不记录
129
+ if now - last_used < self.token_update_interval:
130
+ return
131
+
132
+ # 追加使用记录
133
+ record = {
134
+ "phone": phone,
135
+ "token": latest_record.get("token"),
136
+ "action": "used",
137
+ "obtainedAt": latest_record.get("obtainedAt"),
138
+ "obtainedAt_human": latest_record.get("obtainedAt_human"),
139
+ "expiresAt": latest_record.get("expiresAt"),
140
+ "expiresAt_human": latest_record.get("expiresAt_human"),
141
+ "lastUsedAt": now,
142
+ "lastUsedAt_human": datetime.fromtimestamp(now, timezone.utc).isoformat(),
143
+ "userInfo": latest_record.get("userInfo", {}),
144
+ "accountInfo": latest_record.get("accountInfo", {}),
145
+ "teamInfo": latest_record.get("teamInfo", {}),
146
+ "gatewayInfo": latest_record.get("gatewayInfo", {}),
147
+ "timestamp": now
148
+ }
149
+
150
+ self._append_evol_record(record)
151
+ # 清除缓存
152
+ self._evol_tokens_cache = {}
153
+ self._evol_tokens_cache_time = 0
154
+
155
+ def _append_evol_record(self, record: dict):
156
+ """追加 Evol Token 记录到 JSONL"""
157
+ try:
158
+ with open(self.evol_tokens_file, "a", encoding="utf-8") as f:
159
+ f.write(json.dumps(record, ensure_ascii=False) + "\n")
160
+ except Exception as e:
161
+ print(f"[auth] Failed to append evol token record: {e}")
162
+
163
+ def revoke_evol_token(self, phone: str):
164
+ """吊销 Evol Token(追加 logout 记录)"""
165
+ latest_record = self.get_evol_token(phone)
166
+ if not latest_record:
167
+ return
168
+
169
+ now = time.time()
170
+ record = {
171
+ "phone": phone,
172
+ "token": latest_record.get("token"),
173
+ "action": "logout",
174
+ "obtainedAt": latest_record.get("obtainedAt"),
175
+ "obtainedAt_human": latest_record.get("obtainedAt_human"),
176
+ "expiresAt": latest_record.get("expiresAt"),
177
+ "expiresAt_human": latest_record.get("expiresAt_human"),
178
+ "lastUsedAt": now,
179
+ "lastUsedAt_human": datetime.fromtimestamp(now, timezone.utc).isoformat(),
180
+ "userInfo": latest_record.get("userInfo", {}),
181
+ "accountInfo": latest_record.get("accountInfo", {}),
182
+ "teamInfo": latest_record.get("teamInfo", {}),
183
+ "gatewayInfo": latest_record.get("gatewayInfo", {}),
184
+ "timestamp": now
185
+ }
186
+
187
+ self._append_evol_record(record)
188
+ # 清除缓存
189
+ self._evol_tokens_cache = {}
190
+ self._evol_tokens_cache_time = 0
191
+
192
+ def generate_kite_token(self, device_info: dict) -> str:
193
+ """生成 Kite Token 并追加到 JSONL"""
194
+ token = "kite_" + secrets.token_urlsafe(32)
195
+ now = time.time()
196
+
197
+ record = {
198
+ "token": token,
199
+ "deviceId": device_info.get("deviceId", "unknown"),
200
+ "deviceName": device_info.get("deviceName", "Unknown Device"),
201
+ "phone": None, # 初始未绑定手机号
202
+ "action": "created",
203
+ "createdAt": now,
204
+ "createdAt_human": datetime.fromtimestamp(now, timezone.utc).isoformat(),
205
+ "lastUsedAt": now,
206
+ "lastUsedAt_human": datetime.fromtimestamp(now, timezone.utc).isoformat(),
207
+ "expiresAt": now + self.kite_token_ttl,
208
+ "expiresAt_human": datetime.fromtimestamp(now + self.kite_token_ttl, timezone.utc).isoformat(),
209
+ "isValid": True,
210
+ "timestamp": now
211
+ }
212
+
213
+ # 追加到 JSONL
214
+ self._append_token_record(record)
215
+ # 清除缓存
216
+ self._kite_tokens_cache = None
217
+ self._kite_tokens_cache_time = 0
218
+
219
+ return token
220
+
221
+ def verify_kite_token(self, token: str) -> bool:
222
+ """验证 Kite Token 并更新使用时间(超过 1 天才记录)"""
223
+ latest_tokens = self._get_latest_tokens()
224
+ now = time.time()
225
+
226
+ token_info = latest_tokens.get(token)
227
+ if not token_info:
228
+ return False
229
+
230
+ if not token_info.get("isValid", True):
231
+ return False
232
+
233
+ if now > token_info.get("expiresAt", 0):
234
+ return False
235
+
236
+ # 检查距离上次使用是否超过 1 天
237
+ last_used = token_info.get("lastUsedAt", 0)
238
+ if now - last_used < self.token_update_interval:
239
+ # 不到 1 天,不记录
240
+ return True
241
+
242
+ # 追加使用记录
243
+ expires_at = now + self.kite_token_ttl
244
+ record = {
245
+ "token": token,
246
+ "deviceId": token_info.get("deviceId"),
247
+ "deviceName": token_info.get("deviceName"),
248
+ "phone": token_info.get("phone"), # 保留绑定的手机号
249
+ "action": "used",
250
+ "createdAt": token_info.get("createdAt"),
251
+ "createdAt_human": token_info.get("createdAt_human"),
252
+ "lastUsedAt": now,
253
+ "lastUsedAt_human": datetime.fromtimestamp(now, timezone.utc).isoformat(),
254
+ "expiresAt": expires_at,
255
+ "expiresAt_human": datetime.fromtimestamp(expires_at, timezone.utc).isoformat(),
256
+ "isValid": True,
257
+ "timestamp": now
258
+ }
259
+ self._append_token_record(record)
260
+ # 清除缓存
261
+ self._kite_tokens_cache = None
262
+ self._kite_tokens_cache_time = 0
263
+
264
+ return True
265
+
266
+ def revoke_kite_token(self, token: str):
267
+ """吊销 Kite Token"""
268
+ latest_tokens = self._get_latest_tokens()
269
+ token_info = latest_tokens.get(token)
270
+
271
+ if not token_info:
272
+ return
273
+
274
+ now = time.time()
275
+ record = {
276
+ "token": token,
277
+ "deviceId": token_info.get("deviceId"),
278
+ "deviceName": token_info.get("deviceName"),
279
+ "phone": token_info.get("phone"), # 保留绑定的手机号
280
+ "action": "revoked",
281
+ "createdAt": token_info.get("createdAt"),
282
+ "createdAt_human": token_info.get("createdAt_human"),
283
+ "lastUsedAt": token_info.get("lastUsedAt"),
284
+ "lastUsedAt_human": token_info.get("lastUsedAt_human"),
285
+ "expiresAt": token_info.get("expiresAt"),
286
+ "expiresAt_human": token_info.get("expiresAt_human"),
287
+ "isValid": False,
288
+ "timestamp": now
289
+ }
290
+ self._append_token_record(record)
291
+ # 清除缓存
292
+ self._kite_tokens_cache = None
293
+ self._kite_tokens_cache_time = 0
294
+
295
+ def bind_kite_token_to_phone(self, token: str, phone: str):
296
+ """绑定 Kite Token 到手机号"""
297
+ latest_tokens = self._get_latest_tokens()
298
+ token_info = latest_tokens.get(token)
299
+
300
+ if not token_info:
301
+ raise ValueError(f"Token not found: {token}")
302
+
303
+ # 如果已经绑定到同一个手机号,不需要重复记录
304
+ if token_info.get("phone") == phone:
305
+ return
306
+
307
+ now = time.time()
308
+ record = {
309
+ "token": token,
310
+ "deviceId": token_info.get("deviceId"),
311
+ "deviceName": token_info.get("deviceName"),
312
+ "phone": phone, # 绑定手机号
313
+ "action": "bound",
314
+ "createdAt": token_info.get("createdAt"),
315
+ "createdAt_human": token_info.get("createdAt_human"),
316
+ "lastUsedAt": now,
317
+ "lastUsedAt_human": datetime.fromtimestamp(now, timezone.utc).isoformat(),
318
+ "expiresAt": token_info.get("expiresAt"),
319
+ "expiresAt_human": token_info.get("expiresAt_human"),
320
+ "isValid": token_info.get("isValid", True),
321
+ "timestamp": now
322
+ }
323
+ self._append_token_record(record)
324
+ # 清除缓存
325
+ self._kite_tokens_cache = None
326
+ self._kite_tokens_cache_time = 0
327
+
328
+ def get_phone_by_kite_token(self, token: str) -> Optional[str]:
329
+ """根据 Kite Token 获取绑定的手机号"""
330
+ latest_tokens = self._get_latest_tokens()
331
+ token_info = latest_tokens.get(token)
332
+
333
+ if not token_info:
334
+ return None
335
+
336
+ return token_info.get("phone")
337
+
338
+ def list_all_evol_tokens(self) -> list:
339
+ """列出所有有效的 Evol Token(用于前端显示)"""
340
+ if not os.path.exists(self.evol_tokens_file):
341
+ return []
342
+
343
+ try:
344
+ # 按 phone 分组,保留每个 phone 的最新记录
345
+ phone_tokens = {}
346
+ with open(self.evol_tokens_file, "r", encoding="utf-8") as f:
347
+ for line in f:
348
+ line = line.strip()
349
+ if not line:
350
+ continue
351
+ try:
352
+ record = json.loads(line)
353
+ record_phone = record.get("phone")
354
+ if not record_phone:
355
+ continue
356
+ # 使用最新的记录(后面的覆盖前面的)
357
+ phone_tokens[record_phone] = record
358
+ except json.JSONDecodeError:
359
+ continue
360
+
361
+ # 过滤掉已过期和已登出的 token,返回列表
362
+ valid_tokens = []
363
+ for phone, record in phone_tokens.items():
364
+ # 检查是否登出
365
+ if record.get("action") == "logout":
366
+ continue
367
+ # 检查是否过期
368
+ if time.time() > record.get("expiresAt", 0):
369
+ continue
370
+ valid_tokens.append(record)
371
+
372
+ return valid_tokens
373
+
374
+ except Exception:
375
+ return []
376
+
377
+ def list_devices(self, current_token: str) -> list:
378
+ """列出所有已配对设备"""
379
+ latest_tokens = self._get_latest_tokens()
380
+ devices = []
381
+
382
+ for token, info in latest_tokens.items():
383
+ devices.append({
384
+ "deviceId": info.get("deviceId"),
385
+ "deviceName": info.get("deviceName"),
386
+ "createdAt": info.get("createdAt"),
387
+ "lastUsedAt": info.get("lastUsedAt"),
388
+ "expiresAt": info.get("expiresAt"),
389
+ "isValid": info.get("isValid", True),
390
+ "isCurrent": token == current_token
391
+ })
392
+
393
+ return devices
394
+
395
+ def _get_latest_tokens(self) -> dict:
396
+ """
397
+ 获取所有 Token 的最新状态(带缓存)
398
+
399
+ Returns:
400
+ {token: latest_record} 字典
401
+ """
402
+ now = time.time()
403
+
404
+ # 检查缓存是否有效
405
+ if self._kite_tokens_cache and (now - self._kite_tokens_cache_time < self.cache_ttl):
406
+ return self._kite_tokens_cache
407
+
408
+ # 从文件读取
409
+ if not os.path.exists(self.kite_tokens_file):
410
+ return {}
411
+
412
+ tokens = {}
413
+ try:
414
+ with open(self.kite_tokens_file, "r", encoding="utf-8") as f:
415
+ for line in f:
416
+ line = line.strip()
417
+ if not line:
418
+ continue
419
+
420
+ try:
421
+ record = json.loads(line)
422
+ token = record.get("token")
423
+ if token:
424
+ # 使用最新的记录(后面的覆盖前面的)
425
+ tokens[token] = record
426
+ except json.JSONDecodeError:
427
+ continue
428
+ except Exception:
429
+ return {}
430
+
431
+ # 更新缓存
432
+ self._kite_tokens_cache = tokens
433
+ self._kite_tokens_cache_time = now
434
+
435
+ return tokens
436
+
437
+ def _append_token_record(self, record: dict):
438
+ """追加 Token 记录到 JSONL"""
439
+ try:
440
+ with open(self.kite_tokens_file, "a", encoding="utf-8") as f:
441
+ f.write(json.dumps(record, ensure_ascii=False) + "\n")
442
+ except Exception as e:
443
+ print(f"[auth] Failed to append token record: {e}")
@@ -0,0 +1,149 @@
1
+ user:
2
+ phone_number: "" # 当前 AI 使用的号码
3
+ phone_numbers: [] # AI 号码列表,可在配置页面管理
4
+ owners: [] # 主人列表,每项包含 phone/name 等基础信息
5
+ # 示例:
6
+ # phone_numbers:
7
+ # - "13800138000"
8
+ # - "13800138001"
9
+ # owners:
10
+ # - phone: "13900139000"
11
+ # name: "张三"
12
+ # note: "公司CEO"
13
+ # - phone: "13700137000"
14
+ # name: "李四"
15
+
16
+ server:
17
+ host: "0.0.0.0"
18
+ port: 18766
19
+ ssl: true
20
+
21
+ bluetooth:
22
+ auto_connect: true
23
+ reconnect_interval: 5
24
+ reconnect_max_interval: 60
25
+ device_address: ""
26
+
27
+ audio:
28
+ sample_rate: 16000
29
+ channels: 1
30
+ format: "s16le"
31
+ record_calls: true
32
+
33
+ llm:
34
+ active_provider: "openai"
35
+ providers:
36
+ openai:
37
+ base_url: "https://api.openai.com/v1"
38
+ api_key: ""
39
+ model: "gpt-4o"
40
+ temperature: 0.7
41
+ max_tokens: 1024
42
+ claude:
43
+ base_url: "https://api.anthropic.com/v1"
44
+ api_key: ""
45
+ model: "claude-sonnet-4-20250514"
46
+ temperature: 0.7
47
+ max_tokens: 1024
48
+ gemini:
49
+ base_url: "https://generativelanguage.googleapis.com/v1beta"
50
+ api_key: ""
51
+ model: "gemini-2.0-flash"
52
+ temperature: 0.7
53
+ max_tokens: 1024
54
+
55
+ asr:
56
+ provider: "whisper"
57
+ whisper:
58
+ base_url: "https://api.openai.com/v1"
59
+ api_key: ""
60
+ model: "whisper-1"
61
+ language: "zh"
62
+ xunfei:
63
+ app_id: ""
64
+ api_key: ""
65
+ api_secret: ""
66
+ volcengine:
67
+ app_id: ""
68
+ access_token: ""
69
+ resource_id: "volc.bigasr.sauc.duration"
70
+ ws_url: "wss://openspeech.bytedance.com/api/v3/sauc/bigmodel_async"
71
+ tencent:
72
+ app_id: ""
73
+ secret_id: ""
74
+ secret_key: ""
75
+ engine_model_type: "16k_zh_large"
76
+
77
+ tts:
78
+ provider: "edge-tts"
79
+ edge_tts:
80
+ voice: "zh-CN-XiaoxiaoNeural"
81
+ rate: "+0%"
82
+ volume: "+0%"
83
+ volcengine:
84
+ app_id: ""
85
+ access_token: ""
86
+ cluster: "volcano_tts"
87
+ voice_type: "BV001_streaming"
88
+ speed_ratio: 1.0
89
+ volume_ratio: 1.0
90
+ pitch_ratio: 1.0
91
+ encoding: "mp3"
92
+ sample_rate: 24000
93
+ tencent:
94
+ app_id: ""
95
+ secret_id: ""
96
+ secret_key: ""
97
+ voice_type: 101001
98
+ codec: "pcm"
99
+ sample_rate: 16000
100
+ speed: 0
101
+ volume: 0
102
+ azure:
103
+ api_key: ""
104
+ region: "eastasia"
105
+ voice: "zh-CN-XiaoxiaoNeural"
106
+ rate: "1.0"
107
+ volume: "100"
108
+ openai:
109
+ api_key: ""
110
+ base_url: "https://api.openai.com/v1"
111
+ model: "tts-1"
112
+ voice: "alloy"
113
+ speed: 1.0
114
+ aliyun:
115
+ access_key_id: ""
116
+ access_key_secret: ""
117
+ app_key: ""
118
+ voice: "zhixiaobai"
119
+ sample_rate: 16000
120
+ speech_rate: 0
121
+ volume: 50
122
+ local:
123
+ engine: "sherpa-onnx"
124
+ model_path: ""
125
+ voice: ""
126
+ sample_rate: 16000
127
+
128
+ vad:
129
+ mode: 3
130
+ energy_threshold: 300
131
+ silence_threshold_ms: 800
132
+ min_speech_ms: 250
133
+
134
+ call:
135
+ max_duration_seconds: 300
136
+ no_response_timeout: 15
137
+ no_response_max_retries: 3
138
+ incoming_confirm_timeout: 15
139
+ incoming_default_action: "reject"
140
+ opening_message: ""
141
+
142
+ webhook:
143
+ default_url: ""
144
+ retry_count: 3
145
+ retry_delays: [1, 5, 30]
146
+ timeout: 10
147
+
148
+ data:
149
+ base_dir: "./data"
@@ -0,0 +1,117 @@
1
+ """
2
+ 配置加载工具
3
+
4
+ 用于加载模块的业务配置。支持从 module.md 读取业务配置块,
5
+ 并动态加载对应的 JSON5 配置文件。
6
+
7
+ 零共享代码依赖 - 此文件可以独立拷贝到其他模块使用。
8
+ """
9
+
10
+ import os
11
+ import re
12
+ import json5
13
+ import yaml
14
+
15
+
16
+ def load_module_metadata(module_dir: str) -> dict:
17
+ """
18
+ 读取 module.md 的 YAML frontmatter。
19
+
20
+ Args:
21
+ module_dir: 模块目录路径
22
+
23
+ Returns:
24
+ 模块元数据字典
25
+ """
26
+ md_path = os.path.join(module_dir, "module.md")
27
+
28
+ if not os.path.exists(md_path):
29
+ return {}
30
+
31
+ try:
32
+ with open(md_path, "r", encoding="utf-8") as f:
33
+ text = f.read()
34
+
35
+ # 提取 YAML frontmatter (--- ... ---)
36
+ m = re.match(r'^---\s*\n(.*?)\n---', text, re.DOTALL)
37
+ if m:
38
+ return yaml.safe_load(m.group(1)) or {}
39
+ except Exception as e:
40
+ print(f"[config_loader] Error loading module.md: {e}")
41
+
42
+ return {}
43
+
44
+
45
+ def load_business_configs(module_dir: str) -> dict:
46
+ """
47
+ 加载所有业务配置。
48
+
49
+ 从 module.md 的 businesses 块读取业务列表,
50
+ 然后加载每个业务的配置文件(JSON5 格式)。
51
+
52
+ Args:
53
+ module_dir: 模块目录路径
54
+
55
+ Returns:
56
+ 业务配置字典,格式:
57
+ {
58
+ "business_name": {
59
+ "metadata": {
60
+ "name": "business_name",
61
+ "type": "business_type",
62
+ "description": "...",
63
+ "config_file": "config.json5"
64
+ },
65
+ "config": { ... } # JSON5 配置内容
66
+ }
67
+ }
68
+ """
69
+ metadata = load_module_metadata(module_dir)
70
+ businesses = metadata.get('businesses', [])
71
+
72
+ if not businesses:
73
+ print(f"[config_loader] No businesses configured in module.md")
74
+ return {}
75
+
76
+ configs = {}
77
+ for business in businesses:
78
+ name = business.get('name')
79
+ config_file = business.get('config_file')
80
+
81
+ if not name or not config_file:
82
+ print(f"[config_loader] Warning: Invalid business entry (missing name or config_file): {business}")
83
+ continue
84
+
85
+ config_path = os.path.join(module_dir, config_file)
86
+
87
+ if os.path.exists(config_path):
88
+ try:
89
+ with open(config_path, "r", encoding="utf-8") as f:
90
+ configs[name] = {
91
+ 'metadata': business,
92
+ 'config': json5.load(f)
93
+ }
94
+ print(f"[config_loader] Loaded business config: {name} from {config_file}")
95
+ except json5.JSON5DecodeError as e:
96
+ print(f"[config_loader] Error: Invalid JSON5 syntax in {config_file}: {e}")
97
+ except Exception as e:
98
+ print(f"[config_loader] Error loading {config_file}: {e}")
99
+ else:
100
+ print(f"[config_loader] Warning: Config file not found: {config_path}")
101
+
102
+ return configs
103
+
104
+
105
+ def get_business_config(module_dir: str, business_name: str) -> dict | None:
106
+ """
107
+ 获取指定业务的配置。
108
+
109
+ Args:
110
+ module_dir: 模块目录路径
111
+ business_name: 业务名称
112
+
113
+ Returns:
114
+ 业务配置字典,如果不存在则返回 None
115
+ """
116
+ configs = load_business_configs(module_dir)
117
+ return configs.get(business_name)