@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
package/kernel/module.md CHANGED
@@ -9,6 +9,7 @@ version: 1.0.1
9
9
  advertise_ip: 127.0.0.1
10
10
  monitor: true
11
11
  preferred_port: 0
12
+ display_order: 100
12
13
  ---
13
14
  # Kernel
14
15
 
@@ -16,10 +17,11 @@ Unified infrastructure module that merges Registry (service discovery) and Event
16
17
 
17
18
  ## Responsibilities
18
19
 
19
- - **Service Registry**: Module registration, heartbeat TTL, glob lookup, dot-path queries
20
+ - **Service Registry**: Module registration, glob lookup, dot-path queries
20
21
  - **Event Routing**: NATS-style wildcard subscriptions, per-subscriber queues, 1h dedup
21
22
  - **RPC Routing**: JSON-RPC 2.0 dispatch for builtin methods + cross-module forwarding
22
23
  - **Token Verification**: In-memory token→module_id resolution (no cross-process HTTP)
24
+ - **Ping/Pong Monitoring**: Active ping to all connected modules, track response latencies
23
25
 
24
26
  ## Protocol
25
27
 
@@ -34,3 +36,25 @@ Three frame types on the wire:
34
36
 
35
37
  - `GET /health` — combined registry + event hub health
36
38
  - `GET /stats` — connections, subscriptions, counters
39
+
40
+ ## RPC Methods
41
+
42
+ ### Registry Methods
43
+ - `registry.register` — Register a module
44
+ - `registry.deregister` — Deregister a module
45
+ - `registry.lookup` — Lookup modules by field/value
46
+ - `registry.get` — Get module info by dot-path
47
+ - `registry.verify` — Verify a token
48
+
49
+ ### Event Methods
50
+ - `event.publish` — Publish an event
51
+ - `event.subscribe` — Subscribe to events
52
+ - `event.unsubscribe` — Unsubscribe from events
53
+
54
+ ### Kernel Methods
55
+ - `kernel.ping` — Ping (used for latency measurement)
56
+ - `kernel.stats` — Get kernel statistics
57
+ - `kernel.health` — Get kernel health status
58
+ - `kernel.latencies` — Get ping/pong latencies for all modules
59
+ - `kernel.generate_tokens` — Generate tokens for modules (Launcher only)
60
+ - `kernel.register_tokens` — Register token mapping (Launcher only)
@@ -20,9 +20,6 @@ class RegistryStore:
20
20
  self.launcher_token = launcher_token
21
21
  self.token_map: dict[str, str] = {} # module_id -> token
22
22
  self.modules: dict[str, dict] = {} # module_id -> registration payload
23
- self.heartbeats: dict[str, float] = {} # module_id -> last heartbeat timestamp
24
- self.ttl = 60 # seconds before marking offline
25
- self.heartbeat_interval = 30
26
23
  self.is_debug = os.environ.get("KITE_DEBUG") == "1"
27
24
  self.last_update_time = time.time() # Global registry update timestamp
28
25
 
@@ -59,7 +56,7 @@ class RegistryStore:
59
56
 
60
57
  def register_module(self, data: dict) -> dict:
61
58
  """Register or update a module. Idempotent — same module_id overwrites.
62
- Returns dict with ttl, heartbeat_interval, and changed flag on success, raises exception on failure."""
59
+ Returns dict with changed flag on success, raises exception on failure."""
63
60
  # Validate required fields
64
61
  missing = [f for f in self._REQUIRED_FIELDS if not data.get(f)]
65
62
  if missing:
@@ -111,30 +108,20 @@ class RegistryStore:
111
108
  record["status"] = "registered" # State machine: connected → registered (via register RPC)
112
109
  record["registered_at"] = time.time()
113
110
  self.modules[mid] = record
114
- self.heartbeats[mid] = time.time()
115
111
 
116
112
  # Update global timestamp if content changed
117
113
  if changed:
118
114
  self.last_update_time = time.time()
119
115
 
120
- return {"ttl": self.ttl, "heartbeat_interval": self.heartbeat_interval, "changed": changed}
116
+ return {"changed": changed}
121
117
 
122
118
  def deregister_module(self, module_id: str) -> dict:
123
119
  """Remove a module record immediately. Returns empty dict."""
124
120
  self.modules.pop(module_id, None)
125
- self.heartbeats.pop(module_id, None)
126
121
  # Update global timestamp
127
122
  self.last_update_time = time.time()
128
123
  return {}
129
124
 
130
- def heartbeat(self, module_id: str) -> dict:
131
- """Renew heartbeat for a module. Returns empty dict on success, raises exception on failure."""
132
- if module_id not in self.modules:
133
- raise KeyError(f"Module '{module_id}' not registered")
134
- self.heartbeats[module_id] = time.time()
135
- # Don't change status — heartbeat just keeps alive, doesn't upgrade state
136
- return {}
137
-
138
125
  def set_connected(self, module_id: str):
139
126
  """Mark a module as connected (WS established, not yet registered).
140
127
  Only upgrades from offline; doesn't downgrade from registered/ready."""
@@ -157,17 +144,6 @@ class RegistryStore:
157
144
  mod = self.modules.get(module_id)
158
145
  return mod is not None and mod.get("status") == "ready"
159
146
 
160
- def check_ttl(self) -> list[str]:
161
- """Mark modules as offline if heartbeat expired. Returns list of newly-offline module_ids."""
162
- now = time.time()
163
- expired = []
164
- for mid, last in list(self.heartbeats.items()):
165
- if mid in self.modules and now - last > self.ttl:
166
- if self.modules[mid].get("status") not in ("offline",):
167
- self.modules[mid]["status"] = "offline"
168
- expired.append(mid)
169
- return expired
170
-
171
147
  # ── Get by dot-path ──
172
148
 
173
149
  def get_by_path(self, path: str) -> Any | None:
@@ -83,7 +83,6 @@ class RpcRouter:
83
83
  self.methods: dict[str, callable] = {
84
84
  "registry.register": self._registry_register,
85
85
  "registry.deregister": self._registry_deregister,
86
- "registry.heartbeat": self._registry_heartbeat,
87
86
  "registry.lookup": self._registry_lookup,
88
87
  "registry.get": self._registry_get,
89
88
  "registry.verify": self._registry_verify,
@@ -93,6 +92,7 @@ class RpcRouter:
93
92
  "kernel.ping": self._kernel_ping,
94
93
  "kernel.stats": self._kernel_stats,
95
94
  "kernel.health": self._kernel_health,
95
+ "kernel.latencies": self._kernel_latencies,
96
96
  "kernel.generate_tokens": self._kernel_generate_tokens,
97
97
  "kernel.register_tokens": self._kernel_register_tokens,
98
98
  }
@@ -218,6 +218,11 @@ class RpcRouter:
218
218
  # Strip target prefix from method for the forwarded request
219
219
  actual_method = method[len(target) + 1:] # e.g. "watchdog.get_status" -> "get_status"
220
220
 
221
+ # Inject caller_id into params for permission checking
222
+ if not isinstance(params, dict):
223
+ params = {}
224
+ params["_caller_id"] = caller_id
225
+
221
226
  # Record pending forward
222
227
  loop = asyncio.get_event_loop()
223
228
  pending = PendingForward(
@@ -324,15 +329,6 @@ class RpcRouter:
324
329
 
325
330
  return {}
326
331
 
327
- async def _registry_heartbeat(self, caller_id: str, params: dict) -> dict:
328
- mid = params.get("module_id")
329
- if not mid:
330
- raise ValueError("module_id required")
331
- if caller_id != "launcher" and caller_id != mid:
332
- raise PermissionError(f"Module '{caller_id}' cannot heartbeat for '{mid}'")
333
-
334
- return self.registry.heartbeat(mid)
335
-
336
332
  async def _registry_lookup(self, caller_id: str, params: dict) -> dict:
337
333
  field = params.get("field")
338
334
  module = params.get("module")
@@ -382,7 +378,9 @@ class RpcRouter:
382
378
  # When a module publishes module.ready, update its status in registry
383
379
  if event_type == "module.ready":
384
380
  mid = (data or {}).get("module_id", caller_id)
381
+ print(f"[kernel] DEBUG: 收到 module.ready 事件,module_id={mid}, caller_id={caller_id}")
385
382
  self.registry.set_ready(mid)
383
+ print(f"[kernel] DEBUG: 已调用 set_ready({mid})")
386
384
 
387
385
  return self.event_hub.publish_event(caller_id, event_id, event_type, data, echo)
388
386
 
@@ -428,6 +426,34 @@ class RpcRouter:
428
426
  "event_stats": eh_health.get("details", {}),
429
427
  }
430
428
 
429
+ async def _kernel_latencies(self, caller_id: str, params: dict) -> dict:
430
+ """Get ping/pong latencies for all connected modules.
431
+
432
+ Returns:
433
+ {
434
+ "latencies": {
435
+ "module1": {
436
+ "outbound": 12.34, # ms, Kernel → module
437
+ "inbound": 23.45, # ms, module → Kernel
438
+ "status": "ok" | "timeout" | "never"
439
+ },
440
+ ...
441
+ }
442
+ }
443
+ """
444
+ result = {}
445
+ for module_id in self.kernel_server.connections.keys():
446
+ latency_data = self.kernel_server._pong_latencies.get(module_id, {})
447
+ status = self.kernel_server._pong_status.get(module_id, "never")
448
+
449
+ result[module_id] = {
450
+ "outbound": latency_data.get("outbound"),
451
+ "inbound": latency_data.get("inbound"),
452
+ "status": status,
453
+ }
454
+
455
+ return {"latencies": result}
456
+
431
457
  async def _kernel_generate_tokens(self, caller_id: str, params: dict) -> dict:
432
458
  """Generate tokens for a list of module names.
433
459
 
package/kernel/server.py CHANGED
@@ -33,12 +33,13 @@ class KernelServer:
33
33
  - Event notifications (delivered to subscribers)
34
34
  """
35
35
 
36
- def __init__(self, launcher_token: str = None, advertise_ip: str = "127.0.0.1", module_id: str = None):
36
+ def __init__(self, launcher_token: str = None, advertise_ip: str = "127.0.0.1", module_id: str = None, boot_t0: float = 0):
37
37
  if module_id is None:
38
38
  raise ValueError("module_id is required")
39
39
  self.module_id = module_id
40
40
  self.advertise_ip = advertise_ip
41
41
  self.port: int = 0 # set by entry.py before uvicorn.run
42
+ self.boot_t0 = boot_t0 # Startup time for ready event
42
43
 
43
44
  # Core components
44
45
  self.registry = RegistryStore(launcher_token) # Can be None
@@ -57,7 +58,6 @@ class KernelServer:
57
58
  )
58
59
 
59
60
  # Background tasks
60
- self._ttl_task: asyncio.Task | None = None
61
61
  self._dedup_task: asyncio.Task | None = None
62
62
  self._uvicorn_server = None # set by entry.py for graceful shutdown
63
63
  self._shutting_down = False
@@ -69,7 +69,7 @@ class KernelServer:
69
69
 
70
70
  # Subscribe to events that Kernel needs to handle
71
71
  # Kernel 通过订阅机制接收事件(与其他模块一致)
72
- self.event_hub.handle_subscribe(self.module_id, ["module.shutdown"])
72
+ self.event_hub.handle_subscribe(self.module_id, ["module.shutdown", "system.pong"])
73
73
 
74
74
  # Register internal event callback for Kernel
75
75
  # Kernel 自身没有 WebSocket 连接,通过回调机制接收订阅的事件
@@ -80,6 +80,12 @@ class KernelServer:
80
80
  # Launcher loss timer (35s after launcher offline)
81
81
  self._launcher_loss_task: asyncio.Task | None = None
82
82
 
83
+ # Ping/Pong tracking
84
+ self._ping_sent_times: dict[str, float] = {} # module_id -> last ping sent time (t1)
85
+ self._pong_latencies: dict[str, dict] = {} # module_id -> {"outbound": ms, "inbound": ms, "last_update": timestamp}
86
+ self._pong_status: dict[str, str] = {} # module_id -> "ok" | "timeout" | "never"
87
+ self._ping_task: asyncio.Task | None = None # Global ping broadcast task
88
+
83
89
  # Build FastAPI app
84
90
  self.app = self._create_app()
85
91
 
@@ -92,15 +98,15 @@ class KernelServer:
92
98
  @app.on_event("startup")
93
99
  async def _startup():
94
100
  server.event_hub.start_internal_senders()
95
- server._ttl_task = asyncio.create_task(server._ttl_loop())
96
101
  server._dedup_task = asyncio.create_task(server._dedup_loop())
102
+ server._ping_task = asyncio.create_task(server._ping_broadcast_loop())
97
103
 
98
104
  @app.on_event("shutdown")
99
105
  async def _shutdown():
100
- if server._ttl_task:
101
- server._ttl_task.cancel()
102
106
  if server._dedup_task:
103
107
  server._dedup_task.cancel()
108
+ if server._ping_task:
109
+ server._ping_task.cancel()
104
110
 
105
111
  # ── WebSocket endpoint ──
106
112
 
@@ -150,9 +156,9 @@ class KernelServer:
150
156
  print(f"[kernel] launcher reconnected, cancelled loss timer")
151
157
  print(f"[kernel] launcher connected")
152
158
 
153
- # Renew heartbeat on connect
154
- if module_id in server.registry.modules:
155
- server.registry.heartbeat(module_id)
159
+ # Initialize ping status for new connection
160
+ if module_id not in server._pong_status:
161
+ server._pong_status[module_id] = "never"
156
162
 
157
163
  try:
158
164
  while True:
@@ -243,17 +249,54 @@ class KernelServer:
243
249
 
244
250
  # ── Background loops ──
245
251
 
246
- async def _ttl_loop(self):
247
- """Check heartbeat TTL every 10s and publish offline events."""
252
+ async def _ping_broadcast_loop(self):
253
+ """Broadcast system.ping event every 20s to all connected modules."""
254
+ import time
255
+ import uuid
248
256
  while True:
249
- await asyncio.sleep(10)
250
257
  try:
251
- expired = self.registry.check_ttl()
252
- for mid in expired:
253
- self.event_hub.publish_internal(
254
- "module.offline", {"module_id": mid}, source=self.module_id)
258
+ await asyncio.sleep(20)
259
+
260
+ # Record ping send time for all connected modules
261
+ t1 = time.time()
262
+ for module_id in list(self.connections.keys()):
263
+ self._ping_sent_times[module_id] = t1
264
+ # Mark as timeout if previous ping didn't get pong
265
+ if module_id in self._pong_status:
266
+ if self._pong_status[module_id] == "ok":
267
+ # Previous was ok, now waiting for new pong
268
+ pass
269
+ elif self._pong_status[module_id] == "never":
270
+ # Still never received pong
271
+ pass
272
+ # If was timeout, keep it as timeout until pong arrives
273
+
274
+ # Broadcast system.ping event
275
+ self.event_hub.publish_internal(
276
+ "system.ping",
277
+ {"ping_time": t1},
278
+ source=self.module_id
279
+ )
280
+
281
+ # Check for timeouts after 20s
282
+ await asyncio.sleep(20)
283
+ t_check = time.time()
284
+ for module_id in list(self.connections.keys()):
285
+ if module_id in self._ping_sent_times:
286
+ # If no pong received within 20s, mark as timeout
287
+ if module_id not in self._pong_latencies or \
288
+ self._pong_latencies[module_id].get("last_update", 0) < self._ping_sent_times[module_id]:
289
+ if self._pong_status.get(module_id) == "never":
290
+ # Keep as "never" if never received
291
+ pass
292
+ else:
293
+ self._pong_status[module_id] = "timeout"
294
+ print(f"[kernel] {module_id} ping timeout (no pong in 20s)")
295
+
296
+ except asyncio.CancelledError:
297
+ break
255
298
  except Exception as e:
256
- print(f"[kernel] TTL loop error: {e}")
299
+ print(f"[kernel] Ping broadcast loop error: {e}")
257
300
 
258
301
  async def _dedup_loop(self):
259
302
  """Clean up dedup table every 30s."""
@@ -325,10 +368,13 @@ class KernelServer:
325
368
 
326
369
  def publish_ready(self):
327
370
  """Publish module.ready event for Kernel (internal, no WS needed)."""
371
+ import time
372
+ startup_time = time.monotonic() - self.boot_t0 if self.boot_t0 else 0
328
373
  self.event_hub.publish_internal("module.ready", {
329
374
  "module_id": self.module_id,
330
375
  "ws_endpoint": f"ws://{self.advertise_ip}:{self.port}/ws",
331
376
  "graceful_shutdown": True,
377
+ "startup_time": startup_time,
332
378
  }, source=self.module_id)
333
379
 
334
380
  async def _handle_internal_event(self, event_type: str, data: dict):
@@ -347,6 +393,8 @@ class KernelServer:
347
393
  # 处理 Kernel 订阅的事件
348
394
  if event_type == "module.shutdown":
349
395
  await self.handle_shutdown_event(data)
396
+ elif event_type == "system.pong":
397
+ await self._handle_pong_event(data)
350
398
  # 忽略系统广播事件(Kernel 没有订阅但会收到广播)
351
399
  elif event_type in ("module.ready", "module.registered", "module.started",
352
400
  "module.stopped", "module.crashed", "module.exiting",
@@ -363,6 +411,47 @@ class KernelServer:
363
411
  traceback.print_exc()
364
412
  raise # 重新抛出,让 EventHub 的包装器记录
365
413
 
414
+ async def _handle_pong_event(self, data: dict):
415
+ """处理 system.pong 事件,计算往返延迟
416
+
417
+ Args:
418
+ data: {
419
+ "module_id": str,
420
+ "ping_time": float, # t1 - Kernel 发送 ping 的时间
421
+ "pong_time": float, # t2 - 模块收到 ping 并发送 pong 的时间
422
+ }
423
+ """
424
+ import time
425
+ module_id = data.get("module_id")
426
+ t1 = data.get("ping_time") # Kernel 发送 ping 的时间
427
+ t2 = data.get("pong_time") # 模块收到 ping 并发送 pong 的时间
428
+ t3 = time.time() # Kernel 收到 pong 的时间
429
+
430
+ if not module_id or t1 is None or t2 is None:
431
+ print(f"[kernel] Invalid pong event data: {data}")
432
+ return
433
+
434
+ # 验证 ping_time 是否匹配
435
+ if module_id not in self._ping_sent_times:
436
+ print(f"[kernel] Received pong from {module_id} but no ping record")
437
+ return
438
+
439
+ expected_t1 = self._ping_sent_times[module_id]
440
+ if abs(t1 - expected_t1) > 0.1: # 允许 100ms 误差
441
+ print(f"[kernel] Pong from {module_id} has mismatched ping_time (expected {expected_t1}, got {t1})")
442
+ return
443
+
444
+ # 计算两个延迟
445
+ outbound_ms = (t2 - t1) * 1000 # 去程:Kernel → 模块
446
+ inbound_ms = (t3 - t2) * 1000 # 回程:模块 → Kernel
447
+
448
+ self._pong_latencies[module_id] = {
449
+ "outbound": round(outbound_ms, 2),
450
+ "inbound": round(inbound_ms, 2),
451
+ "last_update": t3,
452
+ }
453
+ self._pong_status[module_id] = "ok"
454
+
366
455
  async def handle_shutdown_event(self, event_data):
367
456
  """处理 module.shutdown 事件(标准优雅退出流程)
368
457
 
@@ -0,0 +1,67 @@
1
+ """
2
+ kite deps-install 命令
3
+
4
+ 只负责安装依赖库
5
+ """
6
+
7
+ import sys
8
+ from pathlib import Path
9
+
10
+
11
+ def run_deps_install(args):
12
+ """安装依赖库"""
13
+ project_root = Path(__file__).parent.parent.parent
14
+ sys.path.insert(0, str(project_root))
15
+
16
+ from core.env_checker import (
17
+ check_dependencies_installed,
18
+ install_dependencies,
19
+ load_dependencies_lock
20
+ )
21
+
22
+ print("=" * 60)
23
+ print(" Kite 依赖库安装")
24
+ print("=" * 60)
25
+ print()
26
+
27
+ try:
28
+ # 1. 加载依赖锁定
29
+ lock_data = load_dependencies_lock()
30
+ if not lock_data:
31
+ print("✓ 无依赖锁定文件,跳过")
32
+ return 0
33
+
34
+ dependencies = lock_data.get("dependencies", {})
35
+ if not dependencies:
36
+ print("✓ 无依赖要求,跳过")
37
+ return 0
38
+
39
+ print(f"依赖锁定文件: {len(dependencies)} 个依赖")
40
+
41
+ # 2. 检查依赖
42
+ deps_ok, deps_msg, deps_details = check_dependencies_installed()
43
+ if deps_ok:
44
+ print(f"✓ 依赖库已安装且版本一致")
45
+ return 0
46
+
47
+ # 3. 安装依赖
48
+ print(f"✗ {deps_msg}")
49
+ if deps_details:
50
+ if deps_details.get("missing"):
51
+ print(f" 缺失: {len(deps_details['missing'])} 个")
52
+ if deps_details.get("version_mismatch"):
53
+ print(f" 版本不匹配: {len(deps_details['version_mismatch'])} 个")
54
+
55
+ print(f"\n正在安装依赖...")
56
+ if not install_dependencies():
57
+ print("✗ 依赖安装失败")
58
+ return 1
59
+
60
+ print(f"✓ 依赖安装成功")
61
+ return 0
62
+
63
+ except Exception as e:
64
+ print(f"\n✗ 依赖安装失败: {e}")
65
+ import traceback
66
+ traceback.print_exc()
67
+ return 1
@@ -0,0 +1,45 @@
1
+ """
2
+ kite env-check 命令
3
+
4
+ 环境检查和准备:
5
+ 1. 检查虚拟环境是否存在
6
+ 2. 检查依赖库是否安装且版本一致
7
+ 3. 创建虚拟环境(如果需要)
8
+ 4. 安装依赖库(如果需要)
9
+ """
10
+
11
+ import sys
12
+ from pathlib import Path
13
+
14
+
15
+ def run_env_check(args):
16
+ """执行环境检查"""
17
+ # 导入环境检查器
18
+ project_root = Path(__file__).parent.parent.parent
19
+
20
+ # 添加到 sys.path
21
+ sys.path.insert(0, str(project_root))
22
+
23
+ # 导入并执行环境检查
24
+ from core.env_checker import check_environment
25
+
26
+ print("=" * 60)
27
+ print(" Kite 环境检查")
28
+ print("=" * 60)
29
+ print()
30
+
31
+ try:
32
+ success = check_environment()
33
+
34
+ if success:
35
+ print("\n✓ 环境检查通过")
36
+ return 0
37
+ else:
38
+ print("\n✗ 环境检查失败")
39
+ return 1
40
+
41
+ except Exception as e:
42
+ print(f"\n✗ 环境检查异常: {e}")
43
+ import traceback
44
+ traceback.print_exc()
45
+ return 1
@@ -0,0 +1,49 @@
1
+ """
2
+ kite prepare 命令
3
+
4
+ 打包前的准备工作:
5
+ 1. 扫描所有模块的依赖
6
+ 2. 生成 dependencies_lock.json
7
+ 3. 更新 python_version.json
8
+ """
9
+
10
+ import sys
11
+ from pathlib import Path
12
+
13
+
14
+ def run_prepare(args):
15
+ """执行打包准备"""
16
+ # 导入扫描脚本
17
+ project_root = Path(__file__).parent.parent.parent
18
+
19
+ # 添加到 sys.path
20
+ sys.path.insert(0, str(project_root))
21
+
22
+ # 导入并执行扫描
23
+ from scripts.scan_dependencies import main as scan_main
24
+
25
+ print("=" * 60)
26
+ print(" Kite 打包准备")
27
+ print("=" * 60)
28
+ print()
29
+
30
+ try:
31
+ scan_main()
32
+ print()
33
+ print("✓ 打包准备完成")
34
+ print()
35
+ print("生成的文件:")
36
+ print(f" - {project_root / 'dependencies_lock.json'}")
37
+ print(f" - {project_root / 'python_version.json'}")
38
+ print()
39
+ print("下一步:")
40
+ print(" 1. 检查生成的文件")
41
+ print(" 2. 提交到 git: git add dependencies_lock.json python_version.json")
42
+ print(" 3. 发布: npm publish")
43
+ return 0
44
+
45
+ except Exception as e:
46
+ print(f"\n✗ 打包准备失败: {e}")
47
+ import traceback
48
+ traceback.print_exc()
49
+ return 1
@@ -0,0 +1,56 @@
1
+ """
2
+ kite venv-setup 命令
3
+
4
+ 只负责创建虚拟环境
5
+ """
6
+
7
+ import sys
8
+ from pathlib import Path
9
+
10
+
11
+ def run_venv_setup(args):
12
+ """创建虚拟环境"""
13
+ project_root = Path(__file__).parent.parent.parent
14
+ sys.path.insert(0, str(project_root))
15
+
16
+ from core.env_checker import (
17
+ get_venv_path,
18
+ check_venv_exists,
19
+ create_venv,
20
+ check_python_version
21
+ )
22
+
23
+ print("=" * 60)
24
+ print(" Kite 虚拟环境准备")
25
+ print("=" * 60)
26
+ print()
27
+
28
+ try:
29
+ # 1. 检查 Python 版本
30
+ py_ok, py_version, required_version = check_python_version()
31
+ if not py_ok:
32
+ print(f"✗ Python 版本不匹配: {py_version}(需要 {required_version})")
33
+ return 1
34
+
35
+ print(f"✓ Python 版本: {py_version}")
36
+
37
+ # 2. 检查虚拟环境
38
+ venv_ok, venv_msg = check_venv_exists()
39
+ if venv_ok:
40
+ print(f"✓ 虚拟环境已存在: {get_venv_path()}")
41
+ return 0
42
+
43
+ # 3. 创建虚拟环境
44
+ print(f"正在创建虚拟环境...")
45
+ if not create_venv():
46
+ print("✗ 虚拟环境创建失败")
47
+ return 1
48
+
49
+ print(f"✓ 虚拟环境创建成功: {get_venv_path()}")
50
+ return 0
51
+
52
+ except Exception as e:
53
+ print(f"\n✗ 虚拟环境准备失败: {e}")
54
+ import traceback
55
+ traceback.print_exc()
56
+ return 1