@agentunion/kite 1.0.6 → 1.0.7

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 (49) hide show
  1. package/cli.js +127 -25
  2. package/core/event_hub/entry.py +105 -61
  3. package/core/event_hub/module.md +0 -1
  4. package/core/event_hub/server.py +96 -28
  5. package/core/launcher/entry.py +477 -290
  6. package/core/launcher/module_scanner.py +10 -9
  7. package/core/launcher/process_manager.py +120 -96
  8. package/core/registry/entry.py +66 -30
  9. package/core/registry/server.py +47 -14
  10. package/core/registry/store.py +6 -1
  11. package/{core → extensions}/event_hub_bench/entry.py +17 -9
  12. package/{core → extensions}/event_hub_bench/module.md +2 -1
  13. package/extensions/services/watchdog/entry.py +11 -7
  14. package/extensions/services/watchdog/server.py +1 -1
  15. package/main.py +204 -4
  16. package/package.json +11 -2
  17. package/core/__pycache__/__init__.cpython-313.pyc +0 -0
  18. package/core/__pycache__/data_dir.cpython-313.pyc +0 -0
  19. package/core/data_dir.py +0 -62
  20. package/core/event_hub/__pycache__/__init__.cpython-313.pyc +0 -0
  21. package/core/event_hub/__pycache__/bench.cpython-313.pyc +0 -0
  22. package/core/event_hub/__pycache__/bench_perf.cpython-313.pyc +0 -0
  23. package/core/event_hub/__pycache__/dedup.cpython-313.pyc +0 -0
  24. package/core/event_hub/__pycache__/entry.cpython-313.pyc +0 -0
  25. package/core/event_hub/__pycache__/hub.cpython-313.pyc +0 -0
  26. package/core/event_hub/__pycache__/router.cpython-313.pyc +0 -0
  27. package/core/event_hub/__pycache__/server.cpython-313.pyc +0 -0
  28. package/core/event_hub/bench_results/.gitkeep +0 -0
  29. package/core/event_hub/bench_results/2026-02-28_13-26-48.json +0 -51
  30. package/core/event_hub/bench_results/2026-02-28_13-44-45.json +0 -51
  31. package/core/event_hub/bench_results/2026-02-28_13-45-39.json +0 -51
  32. package/core/launcher/__pycache__/__init__.cpython-313.pyc +0 -0
  33. package/core/launcher/__pycache__/entry.cpython-313.pyc +0 -0
  34. package/core/launcher/__pycache__/module_scanner.cpython-313.pyc +0 -0
  35. package/core/launcher/__pycache__/process_manager.cpython-313.pyc +0 -0
  36. package/core/launcher/data/log/lifecycle.jsonl +0 -1158
  37. package/core/launcher/data/token.txt +0 -1
  38. package/core/registry/__pycache__/__init__.cpython-313.pyc +0 -0
  39. package/core/registry/__pycache__/entry.cpython-313.pyc +0 -0
  40. package/core/registry/__pycache__/server.cpython-313.pyc +0 -0
  41. package/core/registry/__pycache__/store.cpython-313.pyc +0 -0
  42. package/core/registry/data/port.txt +0 -1
  43. package/core/registry/data/port_484.txt +0 -1
  44. package/extensions/__pycache__/__init__.cpython-313.pyc +0 -0
  45. package/extensions/services/__pycache__/__init__.cpython-313.pyc +0 -0
  46. package/extensions/services/watchdog/__pycache__/__init__.cpython-313.pyc +0 -0
  47. package/extensions/services/watchdog/__pycache__/entry.cpython-313.pyc +0 -0
  48. package/extensions/services/watchdog/__pycache__/monitor.cpython-313.pyc +0 -0
  49. package/extensions/services/watchdog/__pycache__/server.cpython-313.pyc +0 -0
@@ -2,7 +2,11 @@
2
2
  Registry HTTP server.
3
3
  7 endpoints: /modules, /lookup, /get/{path}, /tokens, /verify, /query, /health.
4
4
  All endpoints except /health require Bearer token auth.
5
- Connects to Event Hub to publish module lifecycle events.
5
+
6
+ Delayed ready mechanism (mechanism 7):
7
+ Registry does NOT send module.ready immediately after HTTP starts.
8
+ When Event Hub registers (POST /modules with metadata.ws_endpoint),
9
+ Registry connects to Event Hub WS and then sends module.ready.
6
10
  """
7
11
 
8
12
  import asyncio
@@ -20,15 +24,19 @@ from .store import RegistryStore
20
24
  class RegistryServer:
21
25
  """FastAPI-based Registry HTTP server."""
22
26
 
23
- def __init__(self, store: RegistryStore, launcher_token: str = ""):
27
+ def __init__(self, store: RegistryStore, launcher_token: str = "", advertise_ip: str = "127.0.0.1"):
24
28
  self.store = store
25
29
  self.launcher_token = launcher_token
30
+ self.advertise_ip = advertise_ip
31
+ self.port: int = 0 # set by entry.py before uvicorn.run
26
32
  self.app = self._create_app()
27
33
  self._ttl_task: asyncio.Task | None = None
28
34
  # Event Hub WebSocket
29
35
  self._event_hub_ws_url: str = ""
30
36
  self._ws: object | None = None
31
37
  self._ws_task: asyncio.Task | None = None
38
+ self._event_hub_connected = False
39
+ self._ready_sent = False
32
40
 
33
41
  def _extract_token(self, request: Request) -> str:
34
42
  """Extract Bearer token from Authorization header."""
@@ -51,13 +59,12 @@ class RegistryServer:
51
59
  if not self.store.is_launcher(token):
52
60
  raise HTTPException(status_code=403, detail="Only Launcher may call this endpoint")
53
61
 
54
- # ── Event Hub connection ──
62
+ # ── Event Hub connection + delayed ready ──
55
63
 
56
64
  async def _try_connect_event_hub(self):
57
- """Check if Event Hub is registered and connect if not already connected."""
58
- if self._ws:
65
+ """Event Hub just registered connect to it and send module.ready."""
66
+ if self._event_hub_connected:
59
67
  return
60
- # Look up Event Hub ws_endpoint from our own store
61
68
  eh = self.store.modules.get("event_hub")
62
69
  if not eh:
63
70
  return
@@ -78,17 +85,24 @@ class RegistryServer:
78
85
  except Exception as e:
79
86
  print(f"[registry] Event Hub connection error: {e}")
80
87
  self._ws = None
88
+ self._event_hub_connected = False
81
89
  await asyncio.sleep(5)
82
90
 
83
91
  async def _ws_connect(self):
84
- """Single WebSocket session."""
85
- # Use registry's own per-module token (registered by Launcher via /tokens)
86
- # to avoid conflicting with Launcher's connection (same launcher_token → same module_id)
92
+ """Single WebSocket session. On first connect, send module.ready."""
93
+ # Use registry's own per-module token to avoid conflicting with Launcher's connection
87
94
  token = self.store.token_map.get("registry", "") or self.launcher_token
88
95
  ws_url = f"{self._event_hub_ws_url}?token={token}"
89
- async with websockets.connect(ws_url) as ws:
96
+ async with websockets.connect(ws_url, ping_interval=None, ping_timeout=None, close_timeout=10) as ws:
90
97
  self._ws = ws
98
+ self._event_hub_connected = True
91
99
  print("[registry] Connected to Event Hub")
100
+
101
+ # Send module.ready on first successful connection (delayed ready mechanism)
102
+ if not self._ready_sent:
103
+ self._ready_sent = True
104
+ await self._send_module_ready()
105
+
92
106
  async for raw in ws:
93
107
  try:
94
108
  msg = json.loads(raw)
@@ -98,6 +112,26 @@ class RegistryServer:
98
112
  if msg_type == "error":
99
113
  print(f"[registry] Event Hub error: {msg.get('message')}")
100
114
 
115
+ async def _send_module_ready(self):
116
+ """Send module.ready event to Event Hub. Launcher is listening for this."""
117
+ from datetime import datetime, timezone
118
+ msg = {
119
+ "type": "event",
120
+ "event_id": str(uuid.uuid4()),
121
+ "event": "module.ready",
122
+ "source": "registry",
123
+ "timestamp": datetime.now(timezone.utc).isoformat(),
124
+ "data": {
125
+ "module_id": "registry",
126
+ "api_endpoint": f"http://{self.advertise_ip}:{self.port}",
127
+ },
128
+ }
129
+ try:
130
+ await self._ws.send(json.dumps(msg))
131
+ print("[registry] Sent module.ready")
132
+ except Exception as e:
133
+ print(f"[registry] Failed to send module.ready: {e}")
134
+
101
135
  async def _publish_event(self, event_type: str, data: dict):
102
136
  """Publish event to Event Hub. Best-effort, no-op if not connected."""
103
137
  if not self._ws:
@@ -153,8 +187,9 @@ class RegistryServer:
153
187
  if result.get("ok"):
154
188
  mid = body["module_id"]
155
189
  await server._publish_event("module.registered", {"module_id": mid})
156
- # If Event Hub just registered, try connecting
157
- if mid == "event_hub":
190
+ # If Event Hub just registered, connect and send module.ready
191
+ ws_endpoint = (body.get("metadata") or {}).get("ws_endpoint")
192
+ if ws_endpoint and mid == "event_hub":
158
193
  await server._try_connect_event_hub()
159
194
  return result
160
195
 
@@ -222,8 +257,6 @@ class RegistryServer:
222
257
  return {"ok": False}
223
258
 
224
259
  # ── 6. POST /query (stub) ──
225
- # TODO: implement LLM semantic query per design §5.1
226
- # accept {"question": "..."}, search registry with LLM, return matched modules/tools
227
260
 
228
261
  @app.post("/query")
229
262
  async def query(request: Request):
@@ -5,6 +5,7 @@ No persistence — Registry crash triggers Kite full restart, all modules re-reg
5
5
  """
6
6
 
7
7
  import fnmatch
8
+ import os
8
9
  import time
9
10
  from typing import Any
10
11
 
@@ -18,11 +19,15 @@ class RegistryStore:
18
19
  self.heartbeats: dict[str, float] = {} # module_id -> last heartbeat timestamp
19
20
  self.ttl = 60 # seconds before marking offline
20
21
  self.heartbeat_interval = 30
22
+ self.is_debug = os.environ.get("KITE_DEBUG") == "1"
21
23
 
22
24
  # ── Token verification ──
23
25
 
24
26
  def verify_token(self, token: str) -> str | None:
25
- """Return module_id if token is valid, None otherwise."""
27
+ """Return module_id if token is valid, None otherwise.
28
+ In debug mode (KITE_DEBUG=1), any non-empty token is accepted."""
29
+ if self.is_debug and token:
30
+ return "debug"
26
31
  if token == self.launcher_token:
27
32
  return "launcher"
28
33
  for mid, tok in self.token_map.items():
@@ -8,7 +8,6 @@ import json
8
8
  import os
9
9
  import platform
10
10
  import sys
11
- import threading
12
11
  import time
13
12
  from datetime import datetime, timezone
14
13
 
@@ -18,12 +17,16 @@ import uuid
18
17
  import httpx
19
18
  import websockets
20
19
 
21
- _this_dir = os.path.dirname(os.path.abspath(__file__))
22
- _project_root = os.path.dirname(os.path.dirname(_this_dir))
20
+ _project_root = os.environ.get("KITE_PROJECT") or os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
23
21
  if _project_root not in sys.path:
24
22
  sys.path.insert(0, _project_root)
25
23
 
26
- RESULTS_DIR = os.path.join(_project_root, "core", "event_hub", "bench_results")
24
+ # Results go to KITE_MODULE_DATA (set by Launcher per module)
25
+ # Fallback to source tree for standalone runs
26
+ RESULTS_DIR = os.environ.get("KITE_MODULE_DATA") or os.path.join(
27
+ os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
28
+ "core", "event_hub", "bench_results",
29
+ )
27
30
  TAG = "[event_hub_bench]"
28
31
 
29
32
 
@@ -34,7 +37,7 @@ def _discover_hub(registry_url: str, token: str, timeout: float = 30) -> str | N
34
37
  while time.time() < deadline:
35
38
  try:
36
39
  resp = httpx.get(
37
- f"{registry_url}/lookup/event_hub",
40
+ f"{registry_url}/get/event_hub",
38
41
  headers=headers, timeout=5,
39
42
  )
40
43
  if resp.status_code == 200:
@@ -335,20 +338,25 @@ async def _run(ws_url: str, token: str):
335
338
 
336
339
 
337
340
  def main():
338
- # Read boot_info from stdin
341
+ # Read boot_info from stdin (only token)
339
342
  token = ""
340
- registry_port = 0
341
343
  try:
342
344
  line = sys.stdin.readline().strip()
343
345
  if line:
344
346
  boot = json.loads(line)
345
347
  token = boot.get("token", "")
346
- registry_port = boot.get("registry_port", 0)
347
348
  except Exception:
348
349
  pass
349
350
 
351
+ # Read registry_port from environment variable
352
+ registry_port = int(os.environ.get("KITE_REGISTRY_PORT", "0"))
353
+
350
354
  if not token or not registry_port:
351
- print(f"{TAG} ERROR: Missing token or registry_port")
355
+ print(f"{TAG} ERROR: Missing token or KITE_REGISTRY_PORT")
356
+ sys.exit(1)
357
+
358
+ if os.environ.get("KITE_DEBUG") != "1":
359
+ print(f"{TAG} ERROR: Benchmark requires KITE_DEBUG=1 (multiple clients share one token)")
352
360
  sys.exit(1)
353
361
 
354
362
  registry_url = f"http://127.0.0.1:{registry_port}"
@@ -18,8 +18,9 @@ subscriptions: []
18
18
  - 通过 Registry 发现 Event Hub,以真实模块身份连接
19
19
  - 多线程模拟多个 publisher/subscriber 压测 hub
20
20
  - 监控 hub 进程 CPU/内存占用
21
- - 结果保存到 `core/event_hub/bench_results/`
21
+ - 结果保存到 `KITE_MODULE_DATA/`
22
22
  - 测试完成后自动退出
23
+ - 需要 `KITE_DEBUG=1`(多客户端共用一个 token)
23
24
 
24
25
  启用:将 `state` 改为 `enabled`
25
26
  停用:将 `state` 改为 `disabled`
@@ -11,9 +11,8 @@ import sys
11
11
  import httpx
12
12
  import uvicorn
13
13
 
14
- # Ensure project root is on sys.path
15
- _this_dir = os.path.dirname(os.path.abspath(__file__))
16
- _project_root = os.path.dirname(os.path.dirname(os.path.dirname(_this_dir)))
14
+ # Ensure project root is on sys.path (set by main.py or cli.js)
15
+ _project_root = os.environ.get("KITE_PROJECT") or os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
17
16
  if _project_root not in sys.path:
18
17
  sys.path.insert(0, _project_root)
19
18
 
@@ -93,20 +92,25 @@ def _get_event_hub_ws(token: str, registry_url: str) -> str:
93
92
 
94
93
 
95
94
  def main():
96
- # Read boot_info from stdin
95
+ # Kite environment
96
+ kite_instance = os.environ.get("KITE_INSTANCE", "")
97
+ is_debug = os.environ.get("KITE_DEBUG") == "1"
98
+
99
+ # Read boot_info from stdin (only token)
97
100
  token = ""
98
- registry_port = 0
99
101
  try:
100
102
  line = sys.stdin.readline().strip()
101
103
  if line:
102
104
  boot_info = json.loads(line)
103
105
  token = boot_info.get("token", "")
104
- registry_port = boot_info.get("registry_port", 0)
105
106
  except Exception:
106
107
  pass
107
108
 
109
+ # Read registry_port from environment variable
110
+ registry_port = int(os.environ.get("KITE_REGISTRY_PORT", "0"))
111
+
108
112
  if not token or not registry_port:
109
- print("[watchdog] ERROR: Missing token or registry_port in boot_info")
113
+ print("[watchdog] ERROR: Missing token or KITE_REGISTRY_PORT")
110
114
  sys.exit(1)
111
115
 
112
116
  print(f"[watchdog] Token received ({len(token)} chars), registry port: {registry_port}")
@@ -93,7 +93,7 @@ class WatchdogServer:
93
93
  async def _ws_connect(self):
94
94
  """Single WebSocket session: connect, subscribe, receive loop."""
95
95
  url = f"{self.event_hub_ws}?token={self.token}"
96
- async with websockets.connect(url) as ws:
96
+ async with websockets.connect(url, ping_interval=None, ping_timeout=None, close_timeout=10) as ws:
97
97
  self._ws = ws
98
98
  print("[watchdog] Connected to Event Hub")
99
99
 
package/main.py CHANGED
@@ -1,16 +1,216 @@
1
+ """
2
+ Kite development entry point.
3
+ Loads .env, sets KITE_* defaults, parses --debug, starts Launcher.
4
+ """
5
+ import atexit
6
+ import builtins
7
+ import os
1
8
  import secrets
2
9
  import sys
3
- import os
10
+ import threading
11
+ import time
12
+ from datetime import datetime
13
+
14
+ # ── Timestamped print with delta + color ──
15
+ # Covers the entire Launcher process. Child processes (registry, event_hub) are
16
+ # separate PIDs — unaffected. Their stdout is relayed via ProcessManager._read_stdout
17
+ # → print(), so timestamps & deltas are added at the relay point automatically.
18
+
19
+ _builtin_print = builtins.print
20
+ _last_ts = time.monotonic()
21
+
22
+ # ANSI escape codes
23
+ _DIM = "\033[90m" # gray — fast, normal
24
+ _GREEN = "\033[32m" # green — noticeable but expected (subprocess launch, network wait)
25
+ _RED = "\033[91m" # red — abnormally slow, likely a problem
26
+ _RESET = "\033[0m"
27
+
28
+ # ── Log file handles ──
29
+ # Initialized lazily after KITE_MODULE_DATA is resolved (see _init_log_files)
30
+ _log_lock = threading.Lock()
31
+ _log_latest = None # file handle: latest.log (cleared each startup)
32
+ _log_daily = None # file handle: {YYYY-MM}/{YYYY-MM-DD}.log (append)
33
+ _log_daily_date = "" # current date string to detect day rollover
34
+
35
+ # Strip ANSI escape sequences for plain-text log files
36
+ import re
37
+ _ANSI_RE = re.compile(r"\033\[[0-9;]*m")
38
+
39
+ def _strip_ansi(s: str) -> str:
40
+ return _ANSI_RE.sub("", s)
41
+
42
+
43
+ def _init_log_files():
44
+ """Initialize log file handles. Called after KITE_MODULE_DATA is set."""
45
+ global _log_latest, _log_daily, _log_daily_date
46
+ module_data = os.environ.get("KITE_MODULE_DATA")
47
+ if not module_data:
48
+ return
49
+ log_dir = os.path.join(module_data, "log")
50
+ os.makedirs(log_dir, exist_ok=True)
51
+
52
+ # latest.log — truncate on each startup
53
+ try:
54
+ _log_latest = open(os.path.join(log_dir, "latest.log"), "w", encoding="utf-8")
55
+ except Exception as e:
56
+ _builtin_print(f"[launcher] 警告: 无法打开 latest.log: {e}")
57
+
58
+ # daily log — append
59
+ _open_daily_log(log_dir)
60
+
61
+
62
+ def _open_daily_log(log_dir: str = None):
63
+ """Open (or rotate) the daily log file based on current date."""
64
+ global _log_daily, _log_daily_date
65
+ today = datetime.now().strftime("%Y-%m-%d")
66
+ if today == _log_daily_date and _log_daily:
67
+ return
68
+ if _log_daily:
69
+ try:
70
+ _log_daily.close()
71
+ except Exception:
72
+ pass
73
+ if not log_dir:
74
+ module_data = os.environ.get("KITE_MODULE_DATA")
75
+ if not module_data:
76
+ return
77
+ log_dir = os.path.join(module_data, "log")
78
+ month_dir = os.path.join(log_dir, today[:7]) # YYYY-MM
79
+ os.makedirs(month_dir, exist_ok=True)
80
+ try:
81
+ _log_daily = open(os.path.join(month_dir, f"{today}.log"), "a", encoding="utf-8")
82
+ _log_daily_date = today
83
+ except Exception as e:
84
+ _builtin_print(f"[launcher] 警告: 无法打开每日日志: {e}")
85
+
86
+
87
+ def _write_log(plain_line: str):
88
+ """Write a plain-text line to both latest.log and daily log."""
89
+ global _log_daily
90
+ with _log_lock:
91
+ if _log_latest:
92
+ try:
93
+ _log_latest.write(plain_line)
94
+ _log_latest.flush()
95
+ except Exception:
96
+ pass
97
+ # Check daily rotation
98
+ today = datetime.now().strftime("%Y-%m-%d")
99
+ if today != _log_daily_date:
100
+ _open_daily_log()
101
+ if _log_daily:
102
+ try:
103
+ _log_daily.write(plain_line)
104
+ _log_daily.flush()
105
+ except Exception:
106
+ pass
107
+
108
+
109
+ def _close_log_files():
110
+ """Flush and close log files on exit."""
111
+ global _log_latest, _log_daily
112
+ for f in (_log_latest, _log_daily):
113
+ if f:
114
+ try:
115
+ f.flush()
116
+ f.close()
117
+ except Exception:
118
+ pass
119
+ _log_latest = None
120
+ _log_daily = None
121
+
122
+ atexit.register(_close_log_files)
123
+
124
+
125
+ def _tprint(*args, **kwargs):
126
+ global _last_ts
127
+ now = time.monotonic()
128
+ delta = now - _last_ts
129
+ _last_ts = now
130
+
131
+ # Timestamp
132
+ ts = datetime.now().strftime("%H:%M:%S.%f")[:-3]
133
+
134
+ # Format delta string
135
+ if delta < 1:
136
+ delta_str = f"+{delta * 1000:.0f}ms"
137
+ elif delta < 100:
138
+ delta_str = f"+{delta:.1f}s"
139
+ else:
140
+ delta_str = f"+{delta:.0f}s"
141
+
142
+ # Color: gray < 1s, green 1–5s, red > 5s
143
+ if delta >= 5:
144
+ color = _RED
145
+ elif delta >= 1:
146
+ color = _GREEN
147
+ else:
148
+ color = _DIM
149
+
150
+ prefix = f"{ts} {color}{delta_str:>8}{_RESET} "
151
+ _builtin_print(prefix, end="")
152
+ _builtin_print(*args, **kwargs)
153
+
154
+ # Write to log files (plain text, no ANSI)
155
+ if _log_latest or _log_daily:
156
+ sep = kwargs.get("sep", " ")
157
+ end = kwargs.get("end", "\n")
158
+ text = sep.join(str(a) for a in args)
159
+ plain_prefix = f"{ts} {delta_str:>8} "
160
+ _write_log(plain_prefix + text + end)
161
+
162
+ builtins.print = _tprint
163
+
164
+ # Load .env (development convenience, not required in production)
165
+ try:
166
+ from dotenv import load_dotenv
167
+ load_dotenv()
168
+ except ImportError:
169
+ pass
170
+
171
+ # Resolve project root (directory containing this file)
172
+ _project_root = os.path.dirname(os.path.abspath(__file__))
173
+
174
+ # Home base for Kite data
175
+ _home = os.environ.get("HOME") or os.environ.get("USERPROFILE") or os.path.expanduser("~")
176
+ _kite_home = os.path.join(_home, ".kite")
177
+
178
+ # Set KITE_* defaults (only if not already set by cli.js or .env)
179
+ _defaults = {
180
+ "KITE_PROJECT": _project_root,
181
+ "KITE_CWD": os.getcwd(),
182
+ "KITE_WORKSPACE": os.path.join(_kite_home, "workspace"),
183
+ "KITE_DATA": os.path.join(_kite_home, "data"),
184
+ "KITE_MODULES": os.path.join(_kite_home, "modules"),
185
+ "KITE_REPO": os.path.join(_kite_home, "repo"),
186
+ "KITE_ENV": "development",
187
+ }
188
+ for key, value in _defaults.items():
189
+ if not os.environ.get(key):
190
+ os.environ[key] = value
191
+
192
+ # Parse --debug flag
193
+ if "--debug" in sys.argv:
194
+ os.environ["KITE_DEBUG"] = "1"
195
+ sys.argv.remove("--debug")
4
196
 
5
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
197
+ # Ensure project root is on sys.path
198
+ sys.path.insert(0, os.environ["KITE_PROJECT"])
6
199
 
7
200
  from core.launcher.entry import Launcher
8
201
 
9
202
 
10
203
  def main():
11
204
  token = secrets.token_hex(32)
12
- print(f"[main] KITE_TOKEN generated ({len(token)} chars)")
13
- Launcher(kite_token=token).run()
205
+ print(f"[main] KITE_TOKEN 已生成 ({len(token)} 字符)")
206
+ env = os.environ.get("KITE_ENV", "development")
207
+ debug = os.environ.get("KITE_DEBUG") == "1"
208
+ print(f"[main] 环境={env}, 调试={debug}, 项目={os.environ['KITE_PROJECT']}")
209
+ launcher = Launcher(kite_token=token)
210
+ # KITE_MODULE_DATA is now set — initialize log files
211
+ _init_log_files()
212
+ print(f"[main] 日志文件已初始化: {os.environ.get('KITE_MODULE_DATA', '')}/log/")
213
+ launcher.run()
14
214
 
15
215
 
16
216
  if __name__ == "__main__":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentunion/kite",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "Kite framework launcher — start Kite from anywhere",
5
5
  "bin": {
6
6
  "kite": "./cli.js"
@@ -11,7 +11,16 @@
11
11
  "__main__.py",
12
12
  "__init__.py",
13
13
  "core/**",
14
- "extensions/**"
14
+ "extensions/**",
15
+ "!**/__pycache__",
16
+ "!**/__pycache__/**",
17
+ "!**/*.pyc",
18
+ "!**/data",
19
+ "!**/data/**",
20
+ "!**/bench_results",
21
+ "!**/bench_results/**",
22
+ "!core/event_hub_bench",
23
+ "!core/event_hub_bench/**"
15
24
  ],
16
25
  "engines": {
17
26
  "node": ">=16"
package/core/data_dir.py DELETED
@@ -1,62 +0,0 @@
1
- """
2
- Data directory management for Kite.
3
- When installed globally via npm, use ~/.kite/ for runtime data.
4
- When running locally, use project_root/core/*/data/.
5
- """
6
- import os
7
- import sys
8
-
9
-
10
- def get_data_root() -> str:
11
- """Get the root directory for runtime data.
12
-
13
- Returns:
14
- - ~/.kite/ if installed globally (no write permission to install dir)
15
- - project_root if running locally (development mode)
16
- """
17
- # Check if we're in a global npm install (no write permission)
18
- project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
19
- test_file = os.path.join(project_root, "core", "launcher", "data", ".write_test")
20
-
21
- try:
22
- os.makedirs(os.path.dirname(test_file), exist_ok=True)
23
- with open(test_file, "w") as f:
24
- f.write("test")
25
- os.remove(test_file)
26
- # Write permission OK, use project_root
27
- return project_root
28
- except (OSError, PermissionError):
29
- # No write permission, use user home
30
- home = os.path.expanduser("~")
31
- kite_home = os.path.join(home, ".kite")
32
- os.makedirs(kite_home, exist_ok=True)
33
- return kite_home
34
-
35
-
36
- def get_launcher_data_dir() -> str:
37
- """Get launcher data directory."""
38
- root = get_data_root()
39
- if root.endswith(".kite"):
40
- # User home mode: ~/.kite/launcher/
41
- return os.path.join(root, "launcher")
42
- else:
43
- # Project mode: project_root/core/launcher/data/
44
- return os.path.join(root, "core", "launcher", "data")
45
-
46
-
47
- def get_registry_data_dir() -> str:
48
- """Get registry data directory."""
49
- root = get_data_root()
50
- if root.endswith(".kite"):
51
- return os.path.join(root, "registry")
52
- else:
53
- return os.path.join(root, "core", "registry", "data")
54
-
55
-
56
- def get_event_hub_data_dir() -> str:
57
- """Get event hub data directory."""
58
- root = get_data_root()
59
- if root.endswith(".kite"):
60
- return os.path.join(root, "event_hub")
61
- else:
62
- return os.path.join(root, "core", "event_hub", "data")
File without changes
@@ -1,51 +0,0 @@
1
- {
2
- "timestamp": "2026-02-28T13:26:48.644977",
3
- "env": {
4
- "platform": "win32",
5
- "python": "3.13.12"
6
- },
7
- "throughput": {
8
- "events": 10000,
9
- "send_rate": 13121,
10
- "client_recv": 10000,
11
- "hub_queued": 10000,
12
- "hub_routed": 10000,
13
- "send_time": 0.76,
14
- "recv_time": 1.59
15
- },
16
- "latency": {
17
- "samples": 200,
18
- "avg_ms": 0.69,
19
- "p50_ms": 0.65,
20
- "p95_ms": 0.92,
21
- "p99_ms": 1.27
22
- },
23
- "fanout_1": {
24
- "subs": 1,
25
- "events": 2000,
26
- "send_rate": 17292,
27
- "avg_recv": 2000,
28
- "min_recv": 2000
29
- },
30
- "fanout_10": {
31
- "subs": 10,
32
- "events": 2000,
33
- "send_rate": 18074,
34
- "avg_recv": 2000,
35
- "min_recv": 2000
36
- },
37
- "fanout_50": {
38
- "subs": 50,
39
- "events": 2000,
40
- "send_rate": 6138,
41
- "avg_recv": 2000,
42
- "min_recv": 2000
43
- },
44
- "hub_counters": {
45
- "events_received": 16200,
46
- "events_routed": 132200,
47
- "events_queued": 132200,
48
- "events_deduplicated": 0,
49
- "errors": 0
50
- }
51
- }