@agentunion/kite 1.2.0 → 1.3.1
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.
- package/CHANGELOG.md +208 -0
- package/README.md +48 -0
- package/cli.js +1 -1
- package/extensions/agents/assistant/entry.py +30 -81
- package/extensions/agents/assistant/module.md +1 -1
- package/extensions/agents/assistant/server.py +83 -122
- package/extensions/channels/acp_channel/entry.py +30 -81
- package/extensions/channels/acp_channel/module.md +1 -1
- package/extensions/channels/acp_channel/server.py +83 -122
- package/extensions/event_hub_bench/entry.py +81 -121
- package/extensions/services/backup/entry.py +213 -85
- package/extensions/services/model_service/entry.py +213 -85
- package/extensions/services/watchdog/entry.py +513 -460
- package/extensions/services/watchdog/monitor.py +55 -69
- package/extensions/services/web/entry.py +11 -108
- package/extensions/services/web/server.py +120 -77
- package/{core/registry → kernel}/entry.py +65 -37
- package/{core/event_hub/hub.py → kernel/event_hub.py} +61 -81
- package/kernel/module.md +33 -0
- package/{core/registry/store.py → kernel/registry_store.py} +13 -4
- package/kernel/rpc_router.py +388 -0
- package/kernel/server.py +267 -0
- package/launcher/__init__.py +10 -0
- package/launcher/__main__.py +6 -0
- package/launcher/count_lines.py +258 -0
- package/{core/launcher → launcher}/entry.py +693 -767
- package/launcher/logging_setup.py +289 -0
- package/{core/launcher → launcher}/module_scanner.py +11 -6
- package/main.py +11 -350
- package/package.json +6 -9
- package/__init__.py +0 -1
- package/__main__.py +0 -15
- package/core/event_hub/BENCHMARK.md +0 -94
- package/core/event_hub/__init__.py +0 -0
- package/core/event_hub/bench.py +0 -459
- package/core/event_hub/bench_extreme.py +0 -308
- package/core/event_hub/bench_perf.py +0 -350
- package/core/event_hub/entry.py +0 -436
- package/core/event_hub/module.md +0 -20
- package/core/event_hub/server.py +0 -269
- package/core/kite_log.py +0 -241
- package/core/launcher/__init__.py +0 -0
- package/core/registry/__init__.py +0 -0
- package/core/registry/module.md +0 -30
- package/core/registry/server.py +0 -339
- package/extensions/services/backup/server.py +0 -244
- package/extensions/services/model_service/server.py +0 -236
- package/extensions/services/watchdog/server.py +0 -229
- /package/{core → kernel}/__init__.py +0 -0
- /package/{core/event_hub → kernel}/dedup.py +0 -0
- /package/{core/event_hub → kernel}/router.py +0 -0
- /package/{core/launcher → launcher}/module.md +0 -0
- /package/{core/launcher → launcher}/process_manager.py +0 -0
package/core/event_hub/entry.py
DELETED
|
@@ -1,436 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Event Hub entry point.
|
|
3
|
-
Reads token from stdin boot_info, then reads launcher_ws_token + registry_port from
|
|
4
|
-
stdin kite messages (in any order), starts FastAPI server, outputs ws_endpoint via
|
|
5
|
-
stdout, waits for Launcher to connect, then registers to Registry after stdio disconnect.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import builtins
|
|
9
|
-
import json
|
|
10
|
-
import os
|
|
11
|
-
import re
|
|
12
|
-
import socket
|
|
13
|
-
import sys
|
|
14
|
-
import threading
|
|
15
|
-
import time
|
|
16
|
-
import traceback
|
|
17
|
-
from datetime import datetime, timezone
|
|
18
|
-
|
|
19
|
-
import uvicorn
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
# ── Module configuration ──
|
|
23
|
-
MODULE_NAME = "event_hub"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def _fmt_elapsed(t0: float) -> str:
|
|
27
|
-
"""Format elapsed time since t0: <1s → 'NNNms', >=1s → 'N.Ns', >=10s → 'NNs'."""
|
|
28
|
-
d = time.monotonic() - t0
|
|
29
|
-
if d < 1:
|
|
30
|
-
return f"{d * 1000:.0f}ms"
|
|
31
|
-
if d < 10:
|
|
32
|
-
return f"{d:.1f}s"
|
|
33
|
-
return f"{d:.0f}s"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
# ── Safe stdout/stderr: ignore BrokenPipeError after Launcher closes stdio ──
|
|
37
|
-
|
|
38
|
-
class _SafeWriter:
|
|
39
|
-
"""Wraps a stream to silently swallow BrokenPipeError on write/flush."""
|
|
40
|
-
def __init__(self, stream):
|
|
41
|
-
self._stream = stream
|
|
42
|
-
|
|
43
|
-
def write(self, s):
|
|
44
|
-
try:
|
|
45
|
-
self._stream.write(s)
|
|
46
|
-
except (BrokenPipeError, OSError):
|
|
47
|
-
pass
|
|
48
|
-
|
|
49
|
-
def flush(self):
|
|
50
|
-
try:
|
|
51
|
-
self._stream.flush()
|
|
52
|
-
except (BrokenPipeError, OSError):
|
|
53
|
-
pass
|
|
54
|
-
|
|
55
|
-
def __getattr__(self, name):
|
|
56
|
-
return getattr(self._stream, name)
|
|
57
|
-
|
|
58
|
-
sys.stdout = _SafeWriter(sys.stdout)
|
|
59
|
-
sys.stderr = _SafeWriter(sys.stderr)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
# ── Timestamped print + log file writer ──
|
|
63
|
-
# Independent implementation per module (no shared code dependency)
|
|
64
|
-
|
|
65
|
-
_builtin_print = builtins.print
|
|
66
|
-
_start_ts = time.monotonic()
|
|
67
|
-
_last_ts = time.monotonic()
|
|
68
|
-
_ANSI_RE = re.compile(r"\033\[[0-9;]*m")
|
|
69
|
-
_log_lock = threading.Lock()
|
|
70
|
-
_log_latest_path = None
|
|
71
|
-
_log_daily_path = None
|
|
72
|
-
_log_daily_date = ""
|
|
73
|
-
_log_dir = None
|
|
74
|
-
_crash_log_path = None
|
|
75
|
-
|
|
76
|
-
def _strip_ansi(s: str) -> str:
|
|
77
|
-
return _ANSI_RE.sub("", s)
|
|
78
|
-
|
|
79
|
-
def _resolve_daily_log_path():
|
|
80
|
-
"""Resolve daily log path based on current date."""
|
|
81
|
-
global _log_daily_path, _log_daily_date
|
|
82
|
-
if not _log_dir:
|
|
83
|
-
return
|
|
84
|
-
today = datetime.now().strftime("%Y-%m-%d")
|
|
85
|
-
if today == _log_daily_date and _log_daily_path:
|
|
86
|
-
return
|
|
87
|
-
month_dir = os.path.join(_log_dir, today[:7])
|
|
88
|
-
os.makedirs(month_dir, exist_ok=True)
|
|
89
|
-
_log_daily_path = os.path.join(month_dir, f"{today}.log")
|
|
90
|
-
_log_daily_date = today
|
|
91
|
-
|
|
92
|
-
def _write_log(plain_line: str):
|
|
93
|
-
"""Write a plain-text line to both latest.log and daily log."""
|
|
94
|
-
with _log_lock:
|
|
95
|
-
if _log_latest_path:
|
|
96
|
-
try:
|
|
97
|
-
with open(_log_latest_path, "a", encoding="utf-8") as f:
|
|
98
|
-
f.write(plain_line)
|
|
99
|
-
except Exception:
|
|
100
|
-
pass
|
|
101
|
-
_resolve_daily_log_path()
|
|
102
|
-
if _log_daily_path:
|
|
103
|
-
try:
|
|
104
|
-
with open(_log_daily_path, "a", encoding="utf-8") as f:
|
|
105
|
-
f.write(plain_line)
|
|
106
|
-
except Exception:
|
|
107
|
-
pass
|
|
108
|
-
|
|
109
|
-
def _write_crash(exc_type, exc_value, exc_tb, thread_name=None, severity="critical", handled=False):
|
|
110
|
-
"""Write crash record to crashes.jsonl + daily crash archive."""
|
|
111
|
-
record = {
|
|
112
|
-
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
113
|
-
"module": MODULE_NAME,
|
|
114
|
-
"thread": thread_name or threading.current_thread().name,
|
|
115
|
-
"exception_type": exc_type.__name__ if exc_type else "Unknown",
|
|
116
|
-
"exception_message": str(exc_value),
|
|
117
|
-
"traceback": "".join(traceback.format_exception(exc_type, exc_value, exc_tb)),
|
|
118
|
-
"severity": severity,
|
|
119
|
-
"handled": handled,
|
|
120
|
-
"process_id": os.getpid(),
|
|
121
|
-
"platform": sys.platform,
|
|
122
|
-
"runtime_version": f"Python {sys.version.split()[0]}",
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if exc_tb:
|
|
126
|
-
tb_entries = traceback.extract_tb(exc_tb)
|
|
127
|
-
if tb_entries:
|
|
128
|
-
last = tb_entries[-1]
|
|
129
|
-
record["context"] = {
|
|
130
|
-
"function": last.name,
|
|
131
|
-
"file": os.path.basename(last.filename),
|
|
132
|
-
"line": last.lineno,
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
line = json.dumps(record, ensure_ascii=False) + "\n"
|
|
136
|
-
|
|
137
|
-
if _crash_log_path:
|
|
138
|
-
try:
|
|
139
|
-
with open(_crash_log_path, "a", encoding="utf-8") as f:
|
|
140
|
-
f.write(line)
|
|
141
|
-
except Exception:
|
|
142
|
-
pass
|
|
143
|
-
|
|
144
|
-
if _log_dir:
|
|
145
|
-
try:
|
|
146
|
-
today = datetime.now().strftime("%Y-%m-%d")
|
|
147
|
-
archive_dir = os.path.join(_log_dir, "crashes", today[:7])
|
|
148
|
-
os.makedirs(archive_dir, exist_ok=True)
|
|
149
|
-
archive_path = os.path.join(archive_dir, f"{today}.jsonl")
|
|
150
|
-
with open(archive_path, "a", encoding="utf-8") as f:
|
|
151
|
-
f.write(line)
|
|
152
|
-
except Exception:
|
|
153
|
-
pass
|
|
154
|
-
|
|
155
|
-
def _print_crash_summary(exc_type, exc_tb, thread_name=None):
|
|
156
|
-
"""Print crash summary to console (red highlight)."""
|
|
157
|
-
RED = "\033[91m"
|
|
158
|
-
RESET = "\033[0m"
|
|
159
|
-
|
|
160
|
-
if exc_tb:
|
|
161
|
-
tb_entries = traceback.extract_tb(exc_tb)
|
|
162
|
-
if tb_entries:
|
|
163
|
-
last = tb_entries[-1]
|
|
164
|
-
location = f"{os.path.basename(last.filename)}:{last.lineno}"
|
|
165
|
-
else:
|
|
166
|
-
location = "unknown"
|
|
167
|
-
else:
|
|
168
|
-
location = "unknown"
|
|
169
|
-
|
|
170
|
-
prefix = f"[{MODULE_NAME}]"
|
|
171
|
-
if thread_name:
|
|
172
|
-
_builtin_print(f"{prefix} {RED}线程 {thread_name} 崩溃: "
|
|
173
|
-
f"{exc_type.__name__} in {location}{RESET}")
|
|
174
|
-
else:
|
|
175
|
-
_builtin_print(f"{prefix} {RED}崩溃: {exc_type.__name__} in {location}{RESET}")
|
|
176
|
-
if _crash_log_path:
|
|
177
|
-
_builtin_print(f"{prefix} 崩溃日志: {_crash_log_path}")
|
|
178
|
-
|
|
179
|
-
def _setup_exception_hooks():
|
|
180
|
-
"""Set up global exception hooks."""
|
|
181
|
-
_orig_excepthook = sys.excepthook
|
|
182
|
-
|
|
183
|
-
def _excepthook(exc_type, exc_value, exc_tb):
|
|
184
|
-
_write_crash(exc_type, exc_value, exc_tb, severity="critical", handled=False)
|
|
185
|
-
_print_crash_summary(exc_type, exc_tb)
|
|
186
|
-
_orig_excepthook(exc_type, exc_value, exc_tb)
|
|
187
|
-
|
|
188
|
-
sys.excepthook = _excepthook
|
|
189
|
-
|
|
190
|
-
if hasattr(threading, "excepthook"):
|
|
191
|
-
def _thread_excepthook(args):
|
|
192
|
-
_write_crash(args.exc_type, args.exc_value, args.exc_traceback,
|
|
193
|
-
thread_name=args.thread.name if args.thread else "unknown",
|
|
194
|
-
severity="error", handled=False)
|
|
195
|
-
_print_crash_summary(args.exc_type, args.exc_traceback,
|
|
196
|
-
thread_name=args.thread.name if args.thread else None)
|
|
197
|
-
|
|
198
|
-
threading.excepthook = _thread_excepthook
|
|
199
|
-
|
|
200
|
-
def _tprint(*args, **kwargs):
|
|
201
|
-
"""Timestamped print that adds [timestamp] HH:MM:SS.mmm +delta prefix."""
|
|
202
|
-
global _last_ts
|
|
203
|
-
now = time.monotonic()
|
|
204
|
-
elapsed = now - _start_ts
|
|
205
|
-
delta = now - _last_ts
|
|
206
|
-
_last_ts = now
|
|
207
|
-
|
|
208
|
-
if elapsed < 1:
|
|
209
|
-
elapsed_str = f"{elapsed * 1000:.0f}ms"
|
|
210
|
-
elif elapsed < 100:
|
|
211
|
-
elapsed_str = f"{elapsed:.1f}s"
|
|
212
|
-
else:
|
|
213
|
-
elapsed_str = f"{elapsed:.0f}s"
|
|
214
|
-
|
|
215
|
-
if delta < 0.001:
|
|
216
|
-
delta_str = ""
|
|
217
|
-
elif delta < 1:
|
|
218
|
-
delta_str = f"+{delta * 1000:.0f}ms"
|
|
219
|
-
elif delta < 100:
|
|
220
|
-
delta_str = f"+{delta:.1f}s"
|
|
221
|
-
else:
|
|
222
|
-
delta_str = f"+{delta:.0f}s"
|
|
223
|
-
|
|
224
|
-
ts = datetime.now().strftime("%H:%M:%S.%f")[:-3]
|
|
225
|
-
|
|
226
|
-
_builtin_print(*args, **kwargs)
|
|
227
|
-
|
|
228
|
-
if _log_latest_path or _log_daily_path:
|
|
229
|
-
sep = kwargs.get("sep", " ")
|
|
230
|
-
end = kwargs.get("end", "\n")
|
|
231
|
-
text = sep.join(str(a) for a in args)
|
|
232
|
-
prefix = f"[{elapsed_str:>6}] {ts} {delta_str:>8} "
|
|
233
|
-
_write_log(prefix + _strip_ansi(text) + end)
|
|
234
|
-
|
|
235
|
-
builtins.print = _tprint
|
|
236
|
-
|
|
237
|
-
# Ensure project root is on sys.path
|
|
238
|
-
_this_dir = os.path.dirname(os.path.abspath(__file__))
|
|
239
|
-
_project_root = os.environ.get("KITE_PROJECT") or os.path.dirname(os.path.dirname(_this_dir))
|
|
240
|
-
if _project_root not in sys.path:
|
|
241
|
-
sys.path.insert(0, _project_root)
|
|
242
|
-
|
|
243
|
-
from core.event_hub.hub import EventHub
|
|
244
|
-
from core.event_hub.server import EventHubServer
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
def _read_module_md() -> dict:
|
|
248
|
-
"""Read preferred_port, advertise_ip from own module.md."""
|
|
249
|
-
md_path = os.path.join(_this_dir, "module.md")
|
|
250
|
-
result = {"preferred_port": 0, "advertise_ip": "127.0.0.1"}
|
|
251
|
-
try:
|
|
252
|
-
with open(md_path, encoding="utf-8") as f:
|
|
253
|
-
text = f.read()
|
|
254
|
-
m = re.match(r'^---\s*\n(.*?)\n---', text, re.DOTALL)
|
|
255
|
-
if m:
|
|
256
|
-
try:
|
|
257
|
-
import yaml
|
|
258
|
-
fm = yaml.safe_load(m.group(1)) or {}
|
|
259
|
-
except ImportError:
|
|
260
|
-
fm = {}
|
|
261
|
-
result["preferred_port"] = int(fm.get("preferred_port", 0))
|
|
262
|
-
result["advertise_ip"] = fm.get("advertise_ip", "127.0.0.1")
|
|
263
|
-
except Exception:
|
|
264
|
-
pass
|
|
265
|
-
return result
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
def _bind_port(preferred: int, host: str) -> int:
|
|
269
|
-
"""Try preferred port first, fall back to OS-assigned."""
|
|
270
|
-
if preferred:
|
|
271
|
-
try:
|
|
272
|
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
273
|
-
s.bind((host, preferred))
|
|
274
|
-
return preferred
|
|
275
|
-
except OSError:
|
|
276
|
-
pass
|
|
277
|
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
278
|
-
s.bind((host, 0))
|
|
279
|
-
return s.getsockname()[1]
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
def _read_stdin_kite_messages(expected: set[str], timeout: float = 10) -> dict[str, dict]:
|
|
283
|
-
"""Read multiple structured kite messages from stdin until all expected types received.
|
|
284
|
-
|
|
285
|
-
Args:
|
|
286
|
-
expected: set of kite message types to wait for (e.g. {"launcher_ws_token", "registry_port"})
|
|
287
|
-
timeout: total timeout in seconds
|
|
288
|
-
|
|
289
|
-
Returns:
|
|
290
|
-
dict mapping kite type -> message dict. Missing types are absent from the result.
|
|
291
|
-
"""
|
|
292
|
-
collected: dict[str, dict] = {}
|
|
293
|
-
remaining = set(expected)
|
|
294
|
-
|
|
295
|
-
def _read():
|
|
296
|
-
while remaining:
|
|
297
|
-
try:
|
|
298
|
-
line = sys.stdin.readline().strip()
|
|
299
|
-
if not line:
|
|
300
|
-
return # stdin closed
|
|
301
|
-
msg = json.loads(line)
|
|
302
|
-
if isinstance(msg, dict) and "kite" in msg:
|
|
303
|
-
kite_type = msg["kite"]
|
|
304
|
-
collected[kite_type] = msg
|
|
305
|
-
remaining.discard(kite_type)
|
|
306
|
-
except Exception:
|
|
307
|
-
return # parse error or stdin closed
|
|
308
|
-
|
|
309
|
-
t = threading.Thread(target=_read, daemon=True)
|
|
310
|
-
t.start()
|
|
311
|
-
t.join(timeout=timeout)
|
|
312
|
-
return collected
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
def main():
|
|
316
|
-
# Initialize log file paths
|
|
317
|
-
global _log_dir, _log_latest_path, _crash_log_path
|
|
318
|
-
module_data = os.environ.get("KITE_MODULE_DATA")
|
|
319
|
-
if module_data:
|
|
320
|
-
_log_dir = os.path.join(module_data, "log")
|
|
321
|
-
os.makedirs(_log_dir, exist_ok=True)
|
|
322
|
-
suffix = os.environ.get("KITE_INSTANCE_SUFFIX", "")
|
|
323
|
-
|
|
324
|
-
_log_latest_path = os.path.join(_log_dir, f"latest{suffix}.log")
|
|
325
|
-
try:
|
|
326
|
-
with open(_log_latest_path, "w", encoding="utf-8") as f:
|
|
327
|
-
pass
|
|
328
|
-
except Exception:
|
|
329
|
-
_log_latest_path = None
|
|
330
|
-
|
|
331
|
-
_crash_log_path = os.path.join(_log_dir, f"crashes{suffix}.jsonl")
|
|
332
|
-
try:
|
|
333
|
-
with open(_crash_log_path, "w", encoding="utf-8") as f:
|
|
334
|
-
pass
|
|
335
|
-
except Exception:
|
|
336
|
-
_crash_log_path = None
|
|
337
|
-
|
|
338
|
-
_resolve_daily_log_path()
|
|
339
|
-
|
|
340
|
-
_setup_exception_hooks()
|
|
341
|
-
|
|
342
|
-
_t0 = time.monotonic()
|
|
343
|
-
|
|
344
|
-
# Kite environment
|
|
345
|
-
kite_instance = os.environ.get("KITE_INSTANCE", "")
|
|
346
|
-
is_debug = os.environ.get("KITE_DEBUG") == "1"
|
|
347
|
-
|
|
348
|
-
# Step 1: Read token from stdin boot_info
|
|
349
|
-
token = ""
|
|
350
|
-
try:
|
|
351
|
-
line = sys.stdin.readline().strip()
|
|
352
|
-
if line:
|
|
353
|
-
boot_info = json.loads(line)
|
|
354
|
-
token = boot_info.get("token", "")
|
|
355
|
-
except Exception:
|
|
356
|
-
pass
|
|
357
|
-
|
|
358
|
-
if not token:
|
|
359
|
-
print("[event_hub] 错误: boot_info 中缺少令牌")
|
|
360
|
-
sys.exit(1)
|
|
361
|
-
|
|
362
|
-
# Step 2: Check env for registry_port first (fast path for restart / Phase 3.5+ scenarios)
|
|
363
|
-
registry_port = int(os.environ.get("KITE_REGISTRY_PORT", "0"))
|
|
364
|
-
|
|
365
|
-
# Determine which stdin messages to wait for
|
|
366
|
-
stdin_expected = {"launcher_ws_token"}
|
|
367
|
-
if not registry_port:
|
|
368
|
-
stdin_expected.add("registry_port")
|
|
369
|
-
|
|
370
|
-
kite_msgs = _read_stdin_kite_messages(stdin_expected, timeout=10)
|
|
371
|
-
|
|
372
|
-
launcher_ws_token = ""
|
|
373
|
-
ws_msg = kite_msgs.get("launcher_ws_token")
|
|
374
|
-
if ws_msg:
|
|
375
|
-
launcher_ws_token = ws_msg.get("launcher_ws_token", "")
|
|
376
|
-
|
|
377
|
-
if launcher_ws_token:
|
|
378
|
-
print(f"[event_hub] 已收到启动器 WS 令牌 ({len(launcher_ws_token)} 字符)")
|
|
379
|
-
else:
|
|
380
|
-
print("[event_hub] 警告: 未收到 launcher_ws_token,启动器引导认证已禁用")
|
|
381
|
-
|
|
382
|
-
# Step 3: registry_port — env already had it, or read from stdin
|
|
383
|
-
if not registry_port:
|
|
384
|
-
port_msg = kite_msgs.get("registry_port")
|
|
385
|
-
if port_msg:
|
|
386
|
-
registry_port = int(port_msg.get("registry_port", 0))
|
|
387
|
-
if not registry_port:
|
|
388
|
-
print("[event_hub] 错误: 未收到 registry_port(stdin 和环境变量均无)")
|
|
389
|
-
sys.exit(1)
|
|
390
|
-
|
|
391
|
-
print(f"[event_hub] 已收到令牌 ({len(token)} 字符),Registry 端口: {registry_port} ({_fmt_elapsed(_t0)})")
|
|
392
|
-
|
|
393
|
-
# Step 4: Read config from own module.md
|
|
394
|
-
md_config = _read_module_md()
|
|
395
|
-
advertise_ip = md_config["advertise_ip"]
|
|
396
|
-
preferred_port = md_config["preferred_port"]
|
|
397
|
-
|
|
398
|
-
# Step 5: Bind port and create server
|
|
399
|
-
bind_host = advertise_ip
|
|
400
|
-
port = _bind_port(preferred_port, bind_host)
|
|
401
|
-
registry_url = f"http://127.0.0.1:{registry_port}"
|
|
402
|
-
|
|
403
|
-
if is_debug:
|
|
404
|
-
print("[event_hub] 调试模式已启用 (KITE_DEBUG=1),接受所有令牌")
|
|
405
|
-
|
|
406
|
-
hub = EventHub()
|
|
407
|
-
server = EventHubServer(
|
|
408
|
-
hub,
|
|
409
|
-
own_token=token,
|
|
410
|
-
registry_url=registry_url,
|
|
411
|
-
launcher_ws_token=launcher_ws_token,
|
|
412
|
-
advertise_ip=advertise_ip,
|
|
413
|
-
port=port,
|
|
414
|
-
)
|
|
415
|
-
|
|
416
|
-
# Step 6: Output ws_endpoint via stdout (Launcher reads this)
|
|
417
|
-
ws_endpoint = f"ws://{advertise_ip}:{port}/ws"
|
|
418
|
-
print(json.dumps({"kite": "ws_endpoint", "ws_endpoint": ws_endpoint}), flush=True)
|
|
419
|
-
|
|
420
|
-
# Step 7: Start HTTP + WS server
|
|
421
|
-
# Launcher will connect with launcher_ws_token → Event Hub sends module.ready → stdio disconnect
|
|
422
|
-
# After stdio disconnect, Event Hub registers to Registry (done by server on_launcher_connected callback)
|
|
423
|
-
print(f"[event_hub] 启动中 {bind_host}:{port} ({_fmt_elapsed(_t0)})")
|
|
424
|
-
try:
|
|
425
|
-
config = uvicorn.Config(server.app, host=bind_host, port=port, log_level="warning")
|
|
426
|
-
uvi_server = uvicorn.Server(config)
|
|
427
|
-
server._uvicorn_server = uvi_server
|
|
428
|
-
uvi_server.run()
|
|
429
|
-
except Exception as e:
|
|
430
|
-
_write_crash(type(e), e, e.__traceback__, severity="critical", handled=True)
|
|
431
|
-
_print_crash_summary(type(e), e.__traceback__)
|
|
432
|
-
sys.exit(1)
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if __name__ == "__main__":
|
|
436
|
-
main()
|
package/core/event_hub/module.md
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: event_hub
|
|
3
|
-
display_name: Event Hub
|
|
4
|
-
version: "1.0"
|
|
5
|
-
type: infrastructure
|
|
6
|
-
state: enabled
|
|
7
|
-
runtime: python
|
|
8
|
-
entry: entry.py
|
|
9
|
-
events: []
|
|
10
|
-
subscriptions: []
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# Event Hub
|
|
14
|
-
|
|
15
|
-
Kite 系统的实时事件路由器。
|
|
16
|
-
|
|
17
|
-
- 接收模块通过 WebSocket 发送的事件
|
|
18
|
-
- 根据订阅关系(支持 NATS 风格通配符)转发给匹配的模块
|
|
19
|
-
- 事件去重(1h 滑动窗口)防止 outbox 重放导致重复转发
|
|
20
|
-
- ACK 确认机制,模块据此清理本地 outbox
|