@agentunion/kite 1.3.2 → 1.4.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 (78) hide show
  1. package/CHANGELOG.md +200 -0
  2. package/cli.js +76 -0
  3. package/extensions/agents/assistant/entry.py +111 -1
  4. package/extensions/agents/assistant/server.py +263 -215
  5. package/extensions/channels/acp_channel/entry.py +111 -1
  6. package/extensions/channels/acp_channel/module.md +23 -22
  7. package/extensions/channels/acp_channel/server.py +263 -215
  8. package/extensions/event_hub_bench/entry.py +107 -1
  9. package/extensions/services/backup/entry.py +299 -21
  10. package/extensions/services/backup/module.md +24 -22
  11. package/extensions/services/model_service/entry.py +145 -19
  12. package/extensions/services/model_service/module.md +21 -22
  13. package/extensions/services/watchdog/entry.py +188 -25
  14. package/extensions/services/watchdog/monitor.py +144 -34
  15. package/extensions/services/web/WEBSOCKET_STATUS.md +143 -0
  16. package/extensions/services/web/config_example.py +35 -0
  17. package/extensions/services/web/config_loader.py +110 -0
  18. package/extensions/services/web/entry.py +114 -26
  19. package/extensions/services/web/module.md +35 -24
  20. package/extensions/services/web/pairing.py +250 -0
  21. package/extensions/services/web/pairing_codes.jsonl +16 -0
  22. package/extensions/services/web/relay.py +643 -0
  23. package/extensions/services/web/relay_config.json5 +67 -0
  24. package/extensions/services/web/routes/routes_management_ws.py +127 -0
  25. package/extensions/services/web/routes/routes_rpc.py +89 -0
  26. package/extensions/services/web/routes/routes_test.py +61 -0
  27. package/extensions/services/web/routes/schemas.py +0 -22
  28. package/extensions/services/web/server.py +421 -98
  29. package/extensions/services/web/static/css/style.css +67 -28
  30. package/extensions/services/web/static/index.html +234 -44
  31. package/extensions/services/web/static/js/app.js +1335 -48
  32. package/extensions/services/web/static/js/kernel-client-example.js +161 -0
  33. package/extensions/services/web/static/js/kernel-client.js +383 -0
  34. package/extensions/services/web/static/js/registry-tests.js +558 -0
  35. package/extensions/services/web/static/js/token-manager.js +175 -0
  36. package/extensions/services/web/static/pairing.html +248 -0
  37. package/extensions/services/web/static/test_registry.html +262 -0
  38. package/extensions/services/web/web_config.json5 +29 -0
  39. package/kernel/entry.py +120 -32
  40. package/kernel/event_hub.py +141 -16
  41. package/kernel/module.md +36 -33
  42. package/kernel/registry_store.py +48 -15
  43. package/kernel/rpc_router.py +120 -53
  44. package/kernel/server.py +219 -12
  45. package/kite_cli/__init__.py +3 -0
  46. package/kite_cli/__main__.py +5 -0
  47. package/kite_cli/commands/__init__.py +1 -0
  48. package/kite_cli/commands/clean.py +101 -0
  49. package/kite_cli/commands/doctor.py +35 -0
  50. package/kite_cli/commands/history.py +111 -0
  51. package/kite_cli/commands/info.py +96 -0
  52. package/kite_cli/commands/install.py +313 -0
  53. package/kite_cli/commands/list.py +143 -0
  54. package/kite_cli/commands/log.py +81 -0
  55. package/kite_cli/commands/rollback.py +88 -0
  56. package/kite_cli/commands/search.py +73 -0
  57. package/kite_cli/commands/uninstall.py +85 -0
  58. package/kite_cli/commands/update.py +118 -0
  59. package/kite_cli/core/__init__.py +1 -0
  60. package/kite_cli/core/checker.py +142 -0
  61. package/kite_cli/core/dependency.py +229 -0
  62. package/kite_cli/core/downloader.py +209 -0
  63. package/kite_cli/core/install_info.py +40 -0
  64. package/kite_cli/core/tool_installer.py +397 -0
  65. package/kite_cli/core/validator.py +78 -0
  66. package/kite_cli/main.py +289 -0
  67. package/kite_cli/utils/__init__.py +1 -0
  68. package/kite_cli/utils/i18n.py +252 -0
  69. package/kite_cli/utils/interactive.py +63 -0
  70. package/kite_cli/utils/operation_log.py +77 -0
  71. package/kite_cli/utils/paths.py +34 -0
  72. package/kite_cli/utils/version.py +308 -0
  73. package/launcher/entry.py +819 -158
  74. package/launcher/logging_setup.py +104 -0
  75. package/launcher/module.md +37 -37
  76. package/package.json +2 -1
  77. package/scripts/plan_manager.py +315 -0
  78. package/extensions/services/web/routes/routes_modules.py +0 -249
@@ -0,0 +1,29 @@
1
+ {
2
+ // Web 服务配置
3
+ server: {
4
+ host: "0.0.0.0",
5
+ port: 18766,
6
+ ssl: false,
7
+ // SSL 证书路径(如果启用 SSL)
8
+ ssl_cert: null,
9
+ ssl_key: null
10
+ },
11
+
12
+ // 静态文件配置
13
+ static: {
14
+ directory: "static",
15
+ cache_max_age: 3600 // 秒
16
+ },
17
+
18
+ // CORS 配置
19
+ cors: {
20
+ enabled: false,
21
+ origins: ["*"]
22
+ },
23
+
24
+ // 日志配置
25
+ logging: {
26
+ level: "INFO",
27
+ format: "%(asctime)s [%(name)s] %(levelname)s: %(message)s"
28
+ }
29
+ }
package/kernel/entry.py CHANGED
@@ -26,7 +26,113 @@ import uvicorn
26
26
 
27
27
 
28
28
  # ── Module configuration ──
29
- MODULE_NAME = "kernel"
29
+
30
+ def _load_module_config() -> dict:
31
+ """Load module configuration from module.md frontmatter.
32
+
33
+ Returns:
34
+ Dict with keys: name, preferred_port, advertise_ip
35
+
36
+ Raises:
37
+ SystemExit: If module.md is invalid or name is non-compliant
38
+ """
39
+ _this_dir = os.path.dirname(os.path.abspath(__file__))
40
+ module_md = os.path.join(_this_dir, "module.md")
41
+
42
+ # Calculate relative path for error messages
43
+ project_root = os.environ.get("KITE_PROJECT", "")
44
+ if project_root and _this_dir.startswith(project_root):
45
+ rel_path = os.path.relpath(_this_dir, project_root)
46
+ else:
47
+ rel_path = _this_dir
48
+
49
+ # Default values (will be overridden if valid config exists)
50
+ result = {
51
+ "name": "",
52
+ "preferred_port": 0,
53
+ "advertise_ip": "127.0.0.1"
54
+ }
55
+
56
+ # Check if module.md exists
57
+ if not os.path.exists(module_md):
58
+ print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
59
+ print(f" Path: {rel_path}/module.md")
60
+ print(f" Reason: File not found")
61
+ sys.exit(1)
62
+
63
+ try:
64
+ with open(module_md, encoding="utf-8") as f:
65
+ text = f.read()
66
+
67
+ # Extract YAML frontmatter (between --- markers)
68
+ import re
69
+ m = re.match(r'^---\s*\n(.*?)\n---', text, re.DOTALL)
70
+ if not m:
71
+ print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
72
+ print(f" Path: {rel_path}/module.md")
73
+ print(f" Reason: Missing YAML frontmatter")
74
+ sys.exit(1)
75
+
76
+ # Parse YAML frontmatter
77
+ try:
78
+ import yaml
79
+ fm = yaml.safe_load(m.group(1)) or {}
80
+ except ImportError:
81
+ print(f"[{rel_path}] ERROR: PyYAML not installed, cannot parse module.md")
82
+ sys.exit(1)
83
+ except Exception as e:
84
+ print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
85
+ print(f" Path: {rel_path}/module.md")
86
+ print(f" Reason: YAML parse error: {e}")
87
+ sys.exit(1)
88
+
89
+ # Validate 'name' field (required)
90
+ if "name" not in fm:
91
+ print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
92
+ print(f" Path: {rel_path}/module.md")
93
+ print(f" Reason: Missing 'name' field")
94
+ sys.exit(1)
95
+
96
+ raw_name = str(fm["name"]).strip()
97
+
98
+ if not raw_name:
99
+ print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
100
+ print(f" Path: {rel_path}/module.md")
101
+ print(f" Reason: Empty module name")
102
+ sys.exit(1)
103
+
104
+ # Validate name characters
105
+ sanitized = re.sub(r'[^a-zA-Z0-9_\-]', '', raw_name)
106
+
107
+ if sanitized != raw_name:
108
+ invalid_chars = ''.join(sorted(set(c for c in raw_name if c not in sanitized)))
109
+ print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
110
+ print(f" Path: {rel_path}/module.md")
111
+ print(f" Reason: Invalid characters in name '{raw_name}': {repr(invalid_chars)}")
112
+ sys.exit(1)
113
+
114
+ result["name"] = sanitized
115
+
116
+ # Extract optional fields
117
+ if "preferred_port" in fm:
118
+ try:
119
+ result["preferred_port"] = int(fm["preferred_port"])
120
+ except (ValueError, TypeError):
121
+ pass
122
+
123
+ if "advertise_ip" in fm:
124
+ result["advertise_ip"] = str(fm["advertise_ip"])
125
+
126
+ except SystemExit:
127
+ raise # Re-raise exit to prevent catching by outer except
128
+ except Exception as e:
129
+ print(f"[{rel_path}] ERROR: Failed to read module.md: {e}")
130
+ sys.exit(1)
131
+
132
+ return result
133
+
134
+ _module_config = _load_module_config()
135
+ MODULE_NAME = _module_config["name"]
30
136
 
31
137
 
32
138
  def _fmt_elapsed(t0: float) -> str:
@@ -256,27 +362,6 @@ if _project_root not in sys.path:
256
362
  from kernel.server import KernelServer
257
363
 
258
364
 
259
- def _read_module_md() -> dict:
260
- """Read preferred_port and advertise_ip from own module.md."""
261
- md_path = os.path.join(_this_dir, "module.md")
262
- result = {"preferred_port": 0, "advertise_ip": "127.0.0.1"}
263
- try:
264
- with open(md_path, "r", encoding="utf-8") as f:
265
- text = f.read()
266
- m = re.match(r'^---\s*\n(.*?)\n---', text, re.DOTALL)
267
- if m:
268
- try:
269
- import yaml
270
- fm = yaml.safe_load(m.group(1)) or {}
271
- except ImportError:
272
- fm = {}
273
- result["preferred_port"] = int(fm.get("preferred_port", 0))
274
- result["advertise_ip"] = fm.get("advertise_ip", "127.0.0.1")
275
- except Exception:
276
- pass
277
- return result
278
-
279
-
280
365
  def _bind_port(preferred: int, host: str) -> int:
281
366
  """Try preferred port first, fall back to OS-assigned."""
282
367
  if preferred:
@@ -284,11 +369,15 @@ def _bind_port(preferred: int, host: str) -> int:
284
369
  with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
285
370
  s.bind((host, preferred))
286
371
  return preferred
287
- except OSError:
288
- pass
289
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
290
- s.bind((host, 0))
291
- return s.getsockname()[1]
372
+ except OSError as e:
373
+ print(f"\033[31m[kernel] ✖ 指定端口 {preferred} 绑定失败: {e},将使用系统随机端口\033[0m")
374
+ try:
375
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
376
+ s.bind((host, 0))
377
+ return s.getsockname()[1]
378
+ except OSError as e:
379
+ print(f"\033[31m[kernel] ✖ 端口绑定完全失败 ({host}): {e}\033[0m")
380
+ raise
292
381
 
293
382
 
294
383
  def _read_stdin_kite_messages(expected: set[str], timeout: float = 10) -> dict[str, dict]:
@@ -362,17 +451,16 @@ def main():
362
451
  if is_debug:
363
452
  print("[kernel] 调试模式已启用 (KITE_DEBUG=1),接受所有令牌")
364
453
 
365
- # Step 1: Read config from own module.md
366
- md_config = _read_module_md()
367
- advertise_ip = md_config["advertise_ip"]
368
- preferred_port = md_config["preferred_port"]
454
+ # Step 1: Use cached module config (already loaded at module level)
455
+ advertise_ip = _module_config["advertise_ip"]
456
+ preferred_port = _module_config["preferred_port"]
369
457
 
370
458
  # Step 2: Generate launcher token
371
459
  import secrets
372
460
  launcher_token = secrets.token_urlsafe(32)
373
461
 
374
462
  # Step 3: Create KernelServer with launcher_token
375
- server = KernelServer(launcher_token=launcher_token, advertise_ip=advertise_ip)
463
+ server = KernelServer(launcher_token=launcher_token, advertise_ip=advertise_ip, module_id=MODULE_NAME)
376
464
 
377
465
  # Step 4: Bind port
378
466
  bind_host = advertise_ip
@@ -43,6 +43,9 @@ QUEUE_MAXSIZE = 10000
43
43
  # System events that are auto-broadcast to ALL connected modules (no subscription needed)
44
44
  SYSTEM_EVENTS = {"module.offline", "module.ready", "module.shutdown"}
45
45
 
46
+ # Callback execution pool size (max concurrent callback tasks)
47
+ CALLBACK_POOL_SIZE = 100
48
+
46
49
 
47
50
  class EventHub:
48
51
 
@@ -66,6 +69,13 @@ class EventHub:
66
69
  self._cnt_errors = 0
67
70
  self._start_time = time.time()
68
71
 
72
+ # Internal event callbacks (module_id -> callback)
73
+ # 用于没有 WebSocket 连接的模块(如 Kernel)通过回调接收事件
74
+ self._internal_callbacks: dict[str, callable] = {}
75
+
76
+ # Queue overflow tracking (per module)
77
+ self._queue_overflow: dict[str, int] = {} # module_id -> overflow count
78
+
69
79
  # ── Connection lifecycle ──
70
80
 
71
81
  def add_connection(self, module_id: str, ws: WebSocket):
@@ -119,13 +129,80 @@ class EventHub:
119
129
  await ws.send_text(raw)
120
130
  self._cnt_routed += 1
121
131
  except Exception:
122
- print(f"[kernel] Send failed to {mid}, closing sender")
132
+ # Connection closed (normal during shutdown) exit silently
123
133
  break
124
134
  except asyncio.CancelledError:
125
135
  pass
126
136
 
137
+ async def _callback_sender_loop(self, mid: str, callback: callable, queue: asyncio.Queue):
138
+ """回调专用的发送循环(类似 _sender_loop,但调用回调而不是发送 WS)
139
+
140
+ 从队列取出 JSON-RPC 2.0 Notification,解析后调用回调函数。
141
+ """
142
+ try:
143
+ while True:
144
+ raw = await queue.get()
145
+ try:
146
+ msg = _loads(raw)
147
+ params = msg.get("params", {})
148
+ event_type = params.get("event", "")
149
+ data = params.get("data", {})
150
+ await callback(event_type, data)
151
+ self._cnt_routed += 1
152
+ except Exception as e:
153
+ print(f"[kernel] Internal callback error for {mid} on {event_type}: {e}")
154
+ import traceback
155
+ traceback.print_exc()
156
+ except asyncio.CancelledError:
157
+ pass
158
+
127
159
  # ── Subscribe / Unsubscribe ──
128
160
 
161
+ def register_internal_callback(self, module_id: str, callback: callable):
162
+ """注册内部事件回调(用于 Kernel 自身接收事件)
163
+
164
+ Args:
165
+ module_id: 模块 ID(通常是 "kernel")
166
+ callback: 异步回调函数 async def callback(event_type: str, data: dict)
167
+
168
+ Raises:
169
+ TypeError: 如果 callback 不是 callable
170
+
171
+ Note:
172
+ 只注册回调和创建队列,不创建 task。
173
+ 需要在事件循环启动后调用 start_internal_senders() 来创建 sender tasks。
174
+ """
175
+ if not callable(callback):
176
+ raise TypeError(f"callback must be callable, got {type(callback)}")
177
+
178
+ # 存储回调函数引用
179
+ self._internal_callbacks[module_id] = callback
180
+
181
+ # 创建队列(如果还没有)
182
+ if module_id not in self._queues:
183
+ q = asyncio.Queue(maxsize=QUEUE_MAXSIZE)
184
+ self._queues[module_id] = q
185
+
186
+ print(f"[kernel] Registered internal callback for {module_id}")
187
+
188
+ def start_internal_senders(self):
189
+ """启动所有已注册的内部回调的 sender tasks(必须在事件循环中调用)"""
190
+ for module_id, callback in self._internal_callbacks.items():
191
+ if module_id not in self._senders:
192
+ q = self._queues[module_id]
193
+ self._senders[module_id] = asyncio.create_task(
194
+ self._callback_sender_loop(module_id, callback, q)
195
+ )
196
+ print(f"[kernel] Started internal sender for {module_id}")
197
+
198
+ def unregister_internal_callback(self, module_id: str):
199
+ """注销内部事件回调"""
200
+ self._internal_callbacks.pop(module_id, None)
201
+ self._queues.pop(module_id, None)
202
+ task = self._senders.pop(module_id, None)
203
+ if task:
204
+ task.cancel()
205
+
129
206
  def handle_subscribe(self, module_id: str, events: list[str]):
130
207
  if module_id not in self.subscriptions:
131
208
  self.subscriptions[module_id] = set()
@@ -134,28 +211,32 @@ class EventHub:
134
211
  )
135
212
  print(f"[kernel] {module_id} subscribed: {events}")
136
213
 
137
- def handle_unsubscribe(self, module_id: str, events: list[str]):
214
+ def handle_unsubscribe(self, module_id: str, events: list[str]) -> dict:
215
+ """Unsubscribe from events. Returns dict with unsubscribed events and count."""
138
216
  subs = self.subscriptions.get(module_id)
217
+ unsubscribed = []
139
218
  if subs:
140
219
  to_remove = {item for item in subs if item[0] in events}
220
+ unsubscribed = [item[0] for item in to_remove]
141
221
  subs.difference_update(to_remove)
142
222
  print(f"[kernel] {module_id} unsubscribed: {events}")
223
+ return {"unsubscribed": unsubscribed, "count": len(unsubscribed)}
143
224
 
144
225
  # ── Event publishing (called by RpcRouter) ──
145
226
 
146
227
  def publish_event(self, sender_id: str, event_id: str, event_type: str,
147
228
  data: dict = None, echo: bool = False) -> dict:
148
229
  """Publish an event from a module. Called by RpcRouter for event.publish RPC.
149
- Returns {ok: True} on success, or error dict if validation fails."""
230
+ Returns empty dict on success, raises exception on validation failure."""
150
231
  self._cnt_received += 1
151
232
 
152
233
  if not event_id or not event_type:
153
234
  self._cnt_errors += 1
154
- return {"ok": False, "error": "Missing required field: event_id or event"}
235
+ raise ValueError("Missing required field: event_id or event")
155
236
 
156
237
  if self.dedup.is_duplicate(event_id):
157
238
  self._cnt_dedup += 1
158
- return {"ok": True} # silently accept duplicates
239
+ return {} # silently accept duplicates
159
240
 
160
241
  msg = {
161
242
  "jsonrpc": "2.0",
@@ -170,11 +251,17 @@ class EventHub:
170
251
  }
171
252
 
172
253
  self._route_event(sender_id, msg, event_type, echo)
173
- return {"ok": True}
254
+ return {}
174
255
 
175
- def publish_internal(self, event_type: str, data: dict):
256
+ def publish_internal(self, event_type: str, data: dict, source: str):
176
257
  """Publish a Kernel-originated event (e.g. module.offline, module.registered).
177
- No dedup check — internal events are unique by nature."""
258
+ No dedup check — internal events are unique by nature.
259
+
260
+ Args:
261
+ event_type: 事件类型(如 "module.ready")
262
+ data: 事件数据
263
+ source: 事件来源模块 ID(必填,通常是调用者的 module_id)
264
+ """
178
265
  import uuid
179
266
  event_id = str(uuid.uuid4())
180
267
  self._cnt_received += 1
@@ -185,25 +272,53 @@ class EventHub:
185
272
  "params": {
186
273
  "event_id": event_id,
187
274
  "event": event_type,
188
- "source": "kernel",
275
+ "source": source,
189
276
  "timestamp": datetime.now(timezone.utc).isoformat(),
190
277
  "data": data,
191
278
  },
192
279
  }
193
280
 
194
- self._route_event("kernel", msg, event_type, echo=False)
281
+ self._route_event(source, msg, event_type, echo=False)
195
282
 
196
283
  # ── Routing ──
197
284
 
198
285
  def _route_event(self, sender_id: str, msg: dict, event_type: str, echo: bool):
199
286
  """Enqueue event to all matching subscribers' delivery queues.
200
287
  System events (module.offline, module.ready, module.shutdown) are auto-broadcast
201
- to ALL connected modules, regardless of subscription."""
288
+ to ALL connected modules, regardless of subscription.
289
+
290
+ Exception: module.shutdown with a target module_id is sent ONLY to that module.
291
+ """
202
292
  e_parts = tuple(event_type.split("."))
203
293
  raw = None # lazy serialization
294
+ params = msg.get("params", {})
295
+ data = params.get("data", {})
204
296
 
205
- # System events → broadcast to all connected modules
297
+ # System events → broadcast to all connected modules (unless targeted)
206
298
  if event_type in SYSTEM_EVENTS:
299
+ # Check if this is a targeted shutdown (has module_id in data)
300
+ if event_type == "module.shutdown":
301
+ target_module = data.get("module_id", "")
302
+
303
+ # Targeted shutdown → send only to target module
304
+ if target_module:
305
+ queue = self._queues.get(target_module)
306
+ if queue:
307
+ if raw is None:
308
+ raw = _dumps(msg)
309
+ try:
310
+ queue.put_nowait(raw)
311
+ self._cnt_queued += 1
312
+ except asyncio.QueueFull:
313
+ # Queue 满了,记录溢出
314
+ self._queue_overflow[target_module] = self._queue_overflow.get(target_module, 0) + 1
315
+ overflow_count = self._queue_overflow[target_module]
316
+ # 每 100 次溢出打印一次警告
317
+ if overflow_count % 100 == 1:
318
+ print(f"[kernel] Warning: Queue full for {target_module}, dropped {overflow_count} events")
319
+ return
320
+
321
+ # Broadcast system events (no target or non-shutdown events)
207
322
  for mid, queue in self._queues.items():
208
323
  if mid == sender_id and not echo:
209
324
  continue
@@ -211,9 +326,14 @@ class EventHub:
211
326
  raw = _dumps(msg)
212
327
  try:
213
328
  queue.put_nowait(raw)
329
+ self._cnt_queued += 1
214
330
  except asyncio.QueueFull:
215
- pass
216
- self._cnt_queued += 1
331
+ # Queue 满了,记录溢出
332
+ self._queue_overflow[mid] = self._queue_overflow.get(mid, 0) + 1
333
+ overflow_count = self._queue_overflow[mid]
334
+ if overflow_count % 100 == 1:
335
+ print(f"[kernel] Warning: Queue full for {mid}, dropped {overflow_count} events")
336
+
217
337
  return
218
338
 
219
339
  # Normal events → subscription-based routing
@@ -228,9 +348,14 @@ class EventHub:
228
348
  raw = _dumps(msg)
229
349
  try:
230
350
  queue.put_nowait(raw)
351
+ self._cnt_queued += 1
231
352
  except asyncio.QueueFull:
232
- pass
233
- self._cnt_queued += 1
353
+ # Queue 满了,记录溢出
354
+ self._queue_overflow[mid] = self._queue_overflow.get(mid, 0) + 1
355
+ overflow_count = self._queue_overflow[mid]
356
+ # 每 100 次溢出打印一次警告
357
+ if overflow_count % 100 == 1:
358
+ print(f"[kernel] Warning: Queue full for {mid}, dropped {overflow_count} events")
234
359
  break
235
360
 
236
361
  # ── Stats ──
package/kernel/module.md CHANGED
@@ -1,33 +1,36 @@
1
- ---
2
- name: kernel
3
- display_name: Kernel
4
- type: infrastructure
5
- state: enabled
6
- runtime: python
7
- entry: entry.py
8
- ---
9
-
10
- # Kernel
11
-
12
- Unified infrastructure module that merges Registry (service discovery) and Event Hub (event routing) into a single process with a WebSocket JSON-RPC 2.0 interface.
13
-
14
- ## Responsibilities
15
-
16
- - **Service Registry**: Module registration, heartbeat TTL, glob lookup, dot-path queries
17
- - **Event Routing**: NATS-style wildcard subscriptions, per-subscriber queues, 1h dedup
18
- - **RPC Routing**: JSON-RPC 2.0 dispatch for builtin methods + cross-module forwarding
19
- - **Token Verification**: In-memory token→module_id resolution (no cross-process HTTP)
20
-
21
- ## Protocol
22
-
23
- Single WebSocket endpoint: `ws://127.0.0.1:{port}/ws?token={TOKEN}&id={MODULE_ID}`
24
-
25
- Three frame types on the wire:
26
- - **RPC Request**: `{jsonrpc:"2.0", id, method, params}` — client→Kernel or Kernel→client (forwarded)
27
- - **RPC Response**: `{jsonrpc:"2.0", id, result}` or `{jsonrpc:"2.0", id, error}` — response to request
28
- - **Event Notification**: `{jsonrpc:"2.0", method:"event", params:{event_id, event, source, timestamp, data}}` — no id, no response
29
-
30
- ## HTTP Endpoints (debug only)
31
-
32
- - `GET /health` — combined registry + event hub health
33
- - `GET /stats` connections, subscriptions, counters
1
+ ---
2
+ name: kernel
3
+ display_name: Kite内核
4
+ type: infrastructure
5
+ state: enabled
6
+ runtime: python
7
+ entry: entry.py
8
+ version: 1.0.1
9
+ advertise_ip: 127.0.0.1
10
+ monitor: true
11
+ preferred_port: 0
12
+ ---
13
+ # Kernel
14
+
15
+ Unified infrastructure module that merges Registry (service discovery) and Event Hub (event routing) into a single process with a WebSocket JSON-RPC 2.0 interface.
16
+
17
+ ## Responsibilities
18
+
19
+ - **Service Registry**: Module registration, heartbeat TTL, glob lookup, dot-path queries
20
+ - **Event Routing**: NATS-style wildcard subscriptions, per-subscriber queues, 1h dedup
21
+ - **RPC Routing**: JSON-RPC 2.0 dispatch for builtin methods + cross-module forwarding
22
+ - **Token Verification**: In-memory token→module_id resolution (no cross-process HTTP)
23
+
24
+ ## Protocol
25
+
26
+ Single WebSocket endpoint: `ws://127.0.0.1:{port}/ws?token={TOKEN}&id={MODULE_ID}`
27
+
28
+ Three frame types on the wire:
29
+ - **RPC Request**: `{jsonrpc:"2.0", id, method, params}` — client→Kernel or Kernel→client (forwarded)
30
+ - **RPC Response**: `{jsonrpc:"2.0", id, result}` or `{jsonrpc:"2.0", id, error}` — response to request
31
+ - **Event Notification**: `{jsonrpc:"2.0", method:"event", params:{event_id, event, source, timestamp, data}}` — no id, no response
32
+
33
+ ## HTTP Endpoints (debug only)
34
+
35
+ - `GET /health` — combined registry + event hub health
36
+ - `GET /stats` — connections, subscriptions, counters