@agentunion/kite 1.3.1 → 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 +287 -1
  2. package/cli.js +76 -0
  3. package/extensions/agents/assistant/entry.py +111 -1
  4. package/extensions/agents/assistant/server.py +263 -197
  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 -197
  8. package/extensions/event_hub_bench/entry.py +107 -1
  9. package/extensions/services/backup/entry.py +408 -72
  10. package/extensions/services/backup/module.md +24 -22
  11. package/extensions/services/model_service/entry.py +255 -71
  12. package/extensions/services/model_service/module.md +21 -22
  13. package/extensions/services/watchdog/entry.py +344 -90
  14. package/extensions/services/watchdog/monitor.py +237 -21
  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/server.py +445 -99
  28. package/extensions/services/web/static/css/style.css +138 -2
  29. package/extensions/services/web/static/index.html +295 -2
  30. package/extensions/services/web/static/js/app.js +1579 -5
  31. package/extensions/services/web/static/js/kernel-client-example.js +161 -0
  32. package/extensions/services/web/static/js/kernel-client.js +383 -0
  33. package/extensions/services/web/static/js/registry-tests.js +558 -0
  34. package/extensions/services/web/static/js/token-manager.js +175 -0
  35. package/extensions/services/web/static/pairing.html +248 -0
  36. package/extensions/services/web/static/test_registry.html +262 -0
  37. package/extensions/services/web/web_config.json5 +29 -0
  38. package/kernel/entry.py +120 -32
  39. package/kernel/event_hub.py +159 -16
  40. package/kernel/module.md +36 -33
  41. package/kernel/registry_store.py +70 -20
  42. package/kernel/rpc_router.py +134 -57
  43. package/kernel/server.py +292 -15
  44. package/kite_cli/__init__.py +3 -0
  45. package/kite_cli/__main__.py +5 -0
  46. package/kite_cli/commands/__init__.py +1 -0
  47. package/kite_cli/commands/clean.py +101 -0
  48. package/kite_cli/commands/doctor.py +35 -0
  49. package/kite_cli/commands/history.py +111 -0
  50. package/kite_cli/commands/info.py +96 -0
  51. package/kite_cli/commands/install.py +313 -0
  52. package/kite_cli/commands/list.py +143 -0
  53. package/kite_cli/commands/log.py +81 -0
  54. package/kite_cli/commands/rollback.py +88 -0
  55. package/kite_cli/commands/search.py +73 -0
  56. package/kite_cli/commands/uninstall.py +85 -0
  57. package/kite_cli/commands/update.py +118 -0
  58. package/kite_cli/core/__init__.py +1 -0
  59. package/kite_cli/core/checker.py +142 -0
  60. package/kite_cli/core/dependency.py +229 -0
  61. package/kite_cli/core/downloader.py +209 -0
  62. package/kite_cli/core/install_info.py +40 -0
  63. package/kite_cli/core/tool_installer.py +397 -0
  64. package/kite_cli/core/validator.py +78 -0
  65. package/kite_cli/main.py +289 -0
  66. package/kite_cli/utils/__init__.py +1 -0
  67. package/kite_cli/utils/i18n.py +252 -0
  68. package/kite_cli/utils/interactive.py +63 -0
  69. package/kite_cli/utils/operation_log.py +77 -0
  70. package/kite_cli/utils/paths.py +34 -0
  71. package/kite_cli/utils/version.py +308 -0
  72. package/launcher/count_lines.py +34 -0
  73. package/launcher/entry.py +905 -166
  74. package/launcher/logging_setup.py +104 -0
  75. package/launcher/module.md +37 -37
  76. package/launcher/process_manager.py +12 -1
  77. package/package.json +2 -1
  78. package/scripts/plan_manager.py +315 -0
@@ -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
@@ -40,6 +40,12 @@ def _loads(raw: str):
40
40
 
41
41
  QUEUE_MAXSIZE = 10000
42
42
 
43
+ # System events that are auto-broadcast to ALL connected modules (no subscription needed)
44
+ SYSTEM_EVENTS = {"module.offline", "module.ready", "module.shutdown"}
45
+
46
+ # Callback execution pool size (max concurrent callback tasks)
47
+ CALLBACK_POOL_SIZE = 100
48
+
43
49
 
44
50
  class EventHub:
45
51
 
@@ -63,6 +69,13 @@ class EventHub:
63
69
  self._cnt_errors = 0
64
70
  self._start_time = time.time()
65
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
+
66
79
  # ── Connection lifecycle ──
67
80
 
68
81
  def add_connection(self, module_id: str, ws: WebSocket):
@@ -116,13 +129,80 @@ class EventHub:
116
129
  await ws.send_text(raw)
117
130
  self._cnt_routed += 1
118
131
  except Exception:
119
- print(f"[kernel] Send failed to {mid}, closing sender")
132
+ # Connection closed (normal during shutdown) exit silently
120
133
  break
121
134
  except asyncio.CancelledError:
122
135
  pass
123
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
+
124
159
  # ── Subscribe / Unsubscribe ──
125
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
+
126
206
  def handle_subscribe(self, module_id: str, events: list[str]):
127
207
  if module_id not in self.subscriptions:
128
208
  self.subscriptions[module_id] = set()
@@ -131,28 +211,32 @@ class EventHub:
131
211
  )
132
212
  print(f"[kernel] {module_id} subscribed: {events}")
133
213
 
134
- 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."""
135
216
  subs = self.subscriptions.get(module_id)
217
+ unsubscribed = []
136
218
  if subs:
137
219
  to_remove = {item for item in subs if item[0] in events}
220
+ unsubscribed = [item[0] for item in to_remove]
138
221
  subs.difference_update(to_remove)
139
222
  print(f"[kernel] {module_id} unsubscribed: {events}")
223
+ return {"unsubscribed": unsubscribed, "count": len(unsubscribed)}
140
224
 
141
225
  # ── Event publishing (called by RpcRouter) ──
142
226
 
143
227
  def publish_event(self, sender_id: str, event_id: str, event_type: str,
144
228
  data: dict = None, echo: bool = False) -> dict:
145
229
  """Publish an event from a module. Called by RpcRouter for event.publish RPC.
146
- Returns {ok: True} on success, or error dict if validation fails."""
230
+ Returns empty dict on success, raises exception on validation failure."""
147
231
  self._cnt_received += 1
148
232
 
149
233
  if not event_id or not event_type:
150
234
  self._cnt_errors += 1
151
- return {"ok": False, "error": "Missing required field: event_id or event"}
235
+ raise ValueError("Missing required field: event_id or event")
152
236
 
153
237
  if self.dedup.is_duplicate(event_id):
154
238
  self._cnt_dedup += 1
155
- return {"ok": True} # silently accept duplicates
239
+ return {} # silently accept duplicates
156
240
 
157
241
  msg = {
158
242
  "jsonrpc": "2.0",
@@ -167,11 +251,17 @@ class EventHub:
167
251
  }
168
252
 
169
253
  self._route_event(sender_id, msg, event_type, echo)
170
- return {"ok": True}
254
+ return {}
171
255
 
172
- def publish_internal(self, event_type: str, data: dict):
256
+ def publish_internal(self, event_type: str, data: dict, source: str):
173
257
  """Publish a Kernel-originated event (e.g. module.offline, module.registered).
174
- 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
+ """
175
265
  import uuid
176
266
  event_id = str(uuid.uuid4())
177
267
  self._cnt_received += 1
@@ -182,21 +272,71 @@ class EventHub:
182
272
  "params": {
183
273
  "event_id": event_id,
184
274
  "event": event_type,
185
- "source": "kernel",
275
+ "source": source,
186
276
  "timestamp": datetime.now(timezone.utc).isoformat(),
187
277
  "data": data,
188
278
  },
189
279
  }
190
280
 
191
- self._route_event("kernel", msg, event_type, echo=False)
281
+ self._route_event(source, msg, event_type, echo=False)
192
282
 
193
283
  # ── Routing ──
194
284
 
195
285
  def _route_event(self, sender_id: str, msg: dict, event_type: str, echo: bool):
196
- """Enqueue event to all matching subscribers' delivery queues."""
286
+ """Enqueue event to all matching subscribers' delivery queues.
287
+ System events (module.offline, module.ready, module.shutdown) are auto-broadcast
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
+ """
197
292
  e_parts = tuple(event_type.split("."))
198
293
  raw = None # lazy serialization
199
-
294
+ params = msg.get("params", {})
295
+ data = params.get("data", {})
296
+
297
+ # System events → broadcast to all connected modules (unless targeted)
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)
322
+ for mid, queue in self._queues.items():
323
+ if mid == sender_id and not echo:
324
+ continue
325
+ if raw is None:
326
+ raw = _dumps(msg)
327
+ try:
328
+ queue.put_nowait(raw)
329
+ self._cnt_queued += 1
330
+ except asyncio.QueueFull:
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
+
337
+ return
338
+
339
+ # Normal events → subscription-based routing
200
340
  for mid, patterns in self.subscriptions.items():
201
341
  if mid == sender_id and not echo:
202
342
  continue
@@ -208,11 +348,14 @@ class EventHub:
208
348
  raw = _dumps(msg)
209
349
  try:
210
350
  queue.put_nowait(raw)
351
+ self._cnt_queued += 1
211
352
  except asyncio.QueueFull:
212
- # Best-effort: drop if queue is full and we can't await
213
- # (we're not in an async context in _route_event)
214
- pass
215
- 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")
216
359
  break
217
360
 
218
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