@agentunion/kite 1.0.7 → 1.3.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.
- package/CHANGELOG.md +208 -0
- package/README.md +48 -0
- package/cli.js +1 -1
- package/extensions/agents/__init__.py +1 -0
- package/extensions/agents/assistant/__init__.py +1 -0
- package/extensions/agents/assistant/entry.py +329 -0
- package/extensions/agents/assistant/module.md +22 -0
- package/extensions/agents/assistant/server.py +197 -0
- package/extensions/channels/__init__.py +1 -0
- package/extensions/channels/acp_channel/__init__.py +1 -0
- package/extensions/channels/acp_channel/entry.py +329 -0
- package/extensions/channels/acp_channel/module.md +22 -0
- package/extensions/channels/acp_channel/server.py +197 -0
- package/extensions/event_hub_bench/entry.py +624 -379
- package/extensions/event_hub_bench/module.md +2 -1
- package/extensions/services/backup/__init__.py +1 -0
- package/extensions/services/backup/entry.py +508 -0
- package/extensions/services/backup/module.md +22 -0
- package/extensions/services/model_service/__init__.py +1 -0
- package/extensions/services/model_service/entry.py +508 -0
- package/extensions/services/model_service/module.md +22 -0
- package/extensions/services/watchdog/entry.py +468 -102
- package/extensions/services/watchdog/module.md +3 -0
- package/extensions/services/watchdog/monitor.py +170 -69
- package/extensions/services/web/__init__.py +1 -0
- package/extensions/services/web/config.yaml +149 -0
- package/extensions/services/web/entry.py +390 -0
- package/extensions/services/web/module.md +24 -0
- package/extensions/services/web/routes/__init__.py +1 -0
- package/extensions/services/web/routes/routes_call.py +189 -0
- package/extensions/services/web/routes/routes_config.py +512 -0
- package/extensions/services/web/routes/routes_contacts.py +98 -0
- package/extensions/services/web/routes/routes_devlog.py +99 -0
- package/extensions/services/web/routes/routes_phone.py +81 -0
- package/extensions/services/web/routes/routes_sms.py +48 -0
- package/extensions/services/web/routes/routes_stats.py +17 -0
- package/extensions/services/web/routes/routes_voicechat.py +554 -0
- package/extensions/services/web/routes/schemas.py +216 -0
- package/extensions/services/web/server.py +375 -0
- package/extensions/services/web/static/css/style.css +1064 -0
- package/extensions/services/web/static/index.html +1445 -0
- package/extensions/services/web/static/js/app.js +4671 -0
- package/extensions/services/web/vendor/__init__.py +1 -0
- package/extensions/services/web/vendor/bluetooth/audio.py +348 -0
- package/extensions/services/web/vendor/bluetooth/contacts.py +251 -0
- package/extensions/services/web/vendor/bluetooth/manager.py +395 -0
- package/extensions/services/web/vendor/bluetooth/sms.py +290 -0
- package/extensions/services/web/vendor/bluetooth/telephony.py +274 -0
- package/extensions/services/web/vendor/config.py +139 -0
- package/extensions/services/web/vendor/conversation/asr.py +936 -0
- package/extensions/services/web/vendor/conversation/engine.py +548 -0
- package/extensions/services/web/vendor/conversation/llm.py +534 -0
- package/extensions/services/web/vendor/conversation/mcp_tools.py +190 -0
- package/extensions/services/web/vendor/conversation/tts.py +322 -0
- package/extensions/services/web/vendor/conversation/vad.py +138 -0
- package/extensions/services/web/vendor/storage/__init__.py +1 -0
- package/extensions/services/web/vendor/storage/identity.py +312 -0
- package/extensions/services/web/vendor/storage/store.py +507 -0
- package/extensions/services/web/vendor/task/manager.py +864 -0
- package/extensions/services/web/vendor/task/models.py +45 -0
- package/extensions/services/web/vendor/task/webhook.py +263 -0
- package/extensions/services/web/vendor/tools/registry.py +321 -0
- package/kernel/__init__.py +0 -0
- package/kernel/entry.py +407 -0
- package/{core/event_hub/hub.py → kernel/event_hub.py} +62 -74
- package/kernel/module.md +33 -0
- package/{core/registry/store.py → kernel/registry_store.py} +23 -8
- 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/launcher/entry.py +1778 -0
- package/launcher/logging_setup.py +289 -0
- package/{core/launcher → launcher}/module_scanner.py +11 -6
- package/launcher/process_manager.py +880 -0
- package/main.py +11 -210
- 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/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 -157
- package/core/event_hub/module.md +0 -20
- package/core/event_hub/server.py +0 -206
- package/core/launcher/entry.py +0 -1158
- package/core/launcher/process_manager.py +0 -470
- package/core/registry/entry.py +0 -110
- package/core/registry/module.md +0 -30
- package/core/registry/server.py +0 -289
- package/extensions/services/watchdog/server.py +0 -167
- /package/{core → extensions/services/web/vendor/bluetooth}/__init__.py +0 -0
- /package/{core/event_hub → extensions/services/web/vendor/conversation}/__init__.py +0 -0
- /package/{core/launcher → extensions/services/web/vendor/task}/__init__.py +0 -0
- /package/{core/registry → extensions/services/web/vendor/tools}/__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/event_hub/entry.py
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Event Hub entry point.
|
|
3
|
-
Reads token from stdin boot_info, reads launcher_ws_token from stdin second line,
|
|
4
|
-
starts FastAPI server, outputs ws_endpoint via stdout, waits for Launcher to connect,
|
|
5
|
-
then registers to Registry after stdio disconnect.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import json
|
|
9
|
-
import os
|
|
10
|
-
import re
|
|
11
|
-
import socket
|
|
12
|
-
import sys
|
|
13
|
-
import threading
|
|
14
|
-
|
|
15
|
-
import uvicorn
|
|
16
|
-
|
|
17
|
-
# Ensure project root is on sys.path (set by main.py or cli.js)
|
|
18
|
-
_this_dir = os.path.dirname(os.path.abspath(__file__))
|
|
19
|
-
_project_root = os.environ.get("KITE_PROJECT") or os.path.dirname(os.path.dirname(_this_dir))
|
|
20
|
-
if _project_root not in sys.path:
|
|
21
|
-
sys.path.insert(0, _project_root)
|
|
22
|
-
|
|
23
|
-
from core.event_hub.hub import EventHub
|
|
24
|
-
from core.event_hub.server import EventHubServer
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def _read_module_md() -> dict:
|
|
28
|
-
"""Read preferred_port, advertise_ip from own module.md."""
|
|
29
|
-
md_path = os.path.join(_this_dir, "module.md")
|
|
30
|
-
result = {"preferred_port": 0, "advertise_ip": "127.0.0.1"}
|
|
31
|
-
try:
|
|
32
|
-
with open(md_path, encoding="utf-8") as f:
|
|
33
|
-
text = f.read()
|
|
34
|
-
m = re.match(r'^---\s*\n(.*?)\n---', text, re.DOTALL)
|
|
35
|
-
if m:
|
|
36
|
-
try:
|
|
37
|
-
import yaml
|
|
38
|
-
fm = yaml.safe_load(m.group(1)) or {}
|
|
39
|
-
except ImportError:
|
|
40
|
-
fm = {}
|
|
41
|
-
result["preferred_port"] = int(fm.get("preferred_port", 0))
|
|
42
|
-
result["advertise_ip"] = fm.get("advertise_ip", "127.0.0.1")
|
|
43
|
-
except Exception:
|
|
44
|
-
pass
|
|
45
|
-
return result
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def _bind_port(preferred: int, host: str) -> int:
|
|
49
|
-
"""Try preferred port first, fall back to OS-assigned."""
|
|
50
|
-
if preferred:
|
|
51
|
-
try:
|
|
52
|
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
53
|
-
s.bind((host, preferred))
|
|
54
|
-
return preferred
|
|
55
|
-
except OSError:
|
|
56
|
-
pass
|
|
57
|
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
58
|
-
s.bind((host, 0))
|
|
59
|
-
return s.getsockname()[1]
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def _read_stdin_kite_message() -> dict | None:
|
|
63
|
-
"""Read a structured kite message from stdin (second line after boot_info).
|
|
64
|
-
Uses a short timeout thread to avoid blocking forever if Launcher doesn't send.
|
|
65
|
-
"""
|
|
66
|
-
result = [None]
|
|
67
|
-
|
|
68
|
-
def _read():
|
|
69
|
-
try:
|
|
70
|
-
line = sys.stdin.readline().strip()
|
|
71
|
-
if line:
|
|
72
|
-
msg = json.loads(line)
|
|
73
|
-
if isinstance(msg, dict) and "kite" in msg:
|
|
74
|
-
result[0] = msg
|
|
75
|
-
except Exception:
|
|
76
|
-
pass
|
|
77
|
-
|
|
78
|
-
t = threading.Thread(target=_read, daemon=True)
|
|
79
|
-
t.start()
|
|
80
|
-
t.join(timeout=5)
|
|
81
|
-
return result[0]
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def main():
|
|
85
|
-
# Kite environment
|
|
86
|
-
kite_instance = os.environ.get("KITE_INSTANCE", "")
|
|
87
|
-
is_debug = os.environ.get("KITE_DEBUG") == "1"
|
|
88
|
-
|
|
89
|
-
# Step 1: Read token from stdin boot_info
|
|
90
|
-
token = ""
|
|
91
|
-
try:
|
|
92
|
-
line = sys.stdin.readline().strip()
|
|
93
|
-
if line:
|
|
94
|
-
boot_info = json.loads(line)
|
|
95
|
-
token = boot_info.get("token", "")
|
|
96
|
-
except Exception:
|
|
97
|
-
pass
|
|
98
|
-
|
|
99
|
-
if not token:
|
|
100
|
-
print("[event_hub] 错误: boot_info 中缺少令牌")
|
|
101
|
-
sys.exit(1)
|
|
102
|
-
|
|
103
|
-
# Step 2: Read launcher_ws_token from stdin (second line, structured kite message)
|
|
104
|
-
launcher_ws_token = ""
|
|
105
|
-
kite_msg = _read_stdin_kite_message()
|
|
106
|
-
if kite_msg and kite_msg.get("kite") == "launcher_ws_token":
|
|
107
|
-
launcher_ws_token = kite_msg.get("launcher_ws_token", "")
|
|
108
|
-
|
|
109
|
-
if launcher_ws_token:
|
|
110
|
-
print(f"[event_hub] 已收到启动器 WS 令牌 ({len(launcher_ws_token)} 字符)")
|
|
111
|
-
else:
|
|
112
|
-
print("[event_hub] 警告: 未收到 launcher_ws_token,启动器引导认证已禁用")
|
|
113
|
-
|
|
114
|
-
# Step 3: Read registry_port from environment variable
|
|
115
|
-
registry_port = int(os.environ.get("KITE_REGISTRY_PORT", "0"))
|
|
116
|
-
if not registry_port:
|
|
117
|
-
print("[event_hub] 错误: KITE_REGISTRY_PORT 未设置")
|
|
118
|
-
sys.exit(1)
|
|
119
|
-
|
|
120
|
-
print(f"[event_hub] 已收到令牌 ({len(token)} 字符),Registry 端口: {registry_port}")
|
|
121
|
-
|
|
122
|
-
# Step 4: Read config from own module.md
|
|
123
|
-
md_config = _read_module_md()
|
|
124
|
-
advertise_ip = md_config["advertise_ip"]
|
|
125
|
-
preferred_port = md_config["preferred_port"]
|
|
126
|
-
|
|
127
|
-
# Step 5: Bind port and create server
|
|
128
|
-
bind_host = advertise_ip
|
|
129
|
-
port = _bind_port(preferred_port, bind_host)
|
|
130
|
-
registry_url = f"http://127.0.0.1:{registry_port}"
|
|
131
|
-
|
|
132
|
-
if is_debug:
|
|
133
|
-
print("[event_hub] 调试模式已启用 (KITE_DEBUG=1),接受所有令牌")
|
|
134
|
-
|
|
135
|
-
hub = EventHub()
|
|
136
|
-
server = EventHubServer(
|
|
137
|
-
hub,
|
|
138
|
-
own_token=token,
|
|
139
|
-
registry_url=registry_url,
|
|
140
|
-
launcher_ws_token=launcher_ws_token,
|
|
141
|
-
advertise_ip=advertise_ip,
|
|
142
|
-
port=port,
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
# Step 6: Output ws_endpoint via stdout (Launcher reads this)
|
|
146
|
-
ws_endpoint = f"ws://{advertise_ip}:{port}/ws"
|
|
147
|
-
print(json.dumps({"kite": "ws_endpoint", "ws_endpoint": ws_endpoint}), flush=True)
|
|
148
|
-
|
|
149
|
-
# Step 7: Start HTTP + WS server
|
|
150
|
-
# Launcher will connect with launcher_ws_token → Event Hub sends module.ready → stdio disconnect
|
|
151
|
-
# After stdio disconnect, Event Hub registers to Registry (done by server on_launcher_connected callback)
|
|
152
|
-
print(f"[event_hub] 启动中 {bind_host}:{port}")
|
|
153
|
-
uvicorn.run(server.app, host=bind_host, port=port, log_level="warning")
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if __name__ == "__main__":
|
|
157
|
-
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
|
package/core/event_hub/server.py
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Event Hub HTTP + WebSocket server.
|
|
3
|
-
FastAPI app: /ws (WebSocket), /health, /stats.
|
|
4
|
-
30s timer for heartbeat renewal + dedup cleanup.
|
|
5
|
-
|
|
6
|
-
Launcher bootstrap sequence:
|
|
7
|
-
Launcher connects with launcher_ws_token → Event Hub verifies locally →
|
|
8
|
-
sends module.ready → registers to Registry.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
import asyncio
|
|
12
|
-
import json
|
|
13
|
-
import os
|
|
14
|
-
import uuid
|
|
15
|
-
from datetime import datetime, timezone
|
|
16
|
-
|
|
17
|
-
import httpx
|
|
18
|
-
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
|
19
|
-
|
|
20
|
-
from .hub import EventHub
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class EventHubServer:
|
|
24
|
-
|
|
25
|
-
def __init__(self, hub: EventHub, own_token: str, registry_url: str,
|
|
26
|
-
launcher_ws_token: str = "",
|
|
27
|
-
advertise_ip: str = "127.0.0.1", port: int = 0):
|
|
28
|
-
self.hub = hub
|
|
29
|
-
self.own_token = own_token
|
|
30
|
-
self.registry_url = registry_url
|
|
31
|
-
self.is_debug = os.environ.get("KITE_DEBUG") == "1"
|
|
32
|
-
self.launcher_ws_token = launcher_ws_token
|
|
33
|
-
self.advertise_ip = advertise_ip
|
|
34
|
-
self.port = port
|
|
35
|
-
self._timer_task: asyncio.Task | None = None
|
|
36
|
-
self._launcher_connected = False
|
|
37
|
-
self._registered_to_registry = False
|
|
38
|
-
self.app = self._create_app()
|
|
39
|
-
|
|
40
|
-
# ── Token verification ──
|
|
41
|
-
|
|
42
|
-
async def _verify_token(self, token: str, module_id_hint: str = "") -> str | None:
|
|
43
|
-
"""Verify a token. Launcher's launcher_ws_token is verified locally.
|
|
44
|
-
Other tokens are verified via Registry POST /verify.
|
|
45
|
-
In debug mode (KITE_DEBUG=1), any non-empty token is accepted."""
|
|
46
|
-
if self.is_debug and token:
|
|
47
|
-
return module_id_hint or "debug"
|
|
48
|
-
# Local verification for Launcher bootstrap (before Registry registration)
|
|
49
|
-
if self.launcher_ws_token and token == self.launcher_ws_token:
|
|
50
|
-
return "launcher"
|
|
51
|
-
# Normal verification via Registry
|
|
52
|
-
try:
|
|
53
|
-
async with httpx.AsyncClient() as client:
|
|
54
|
-
resp = await client.post(
|
|
55
|
-
f"{self.registry_url}/verify",
|
|
56
|
-
json={"token": token},
|
|
57
|
-
headers={"Authorization": f"Bearer {self.own_token}"},
|
|
58
|
-
timeout=5,
|
|
59
|
-
)
|
|
60
|
-
if resp.status_code == 200:
|
|
61
|
-
body = resp.json()
|
|
62
|
-
if body.get("ok"):
|
|
63
|
-
return body.get("module_id")
|
|
64
|
-
except Exception as e:
|
|
65
|
-
print(f"[event_hub] Token verification failed: {e}")
|
|
66
|
-
return None
|
|
67
|
-
|
|
68
|
-
# ── Launcher bootstrap ──
|
|
69
|
-
|
|
70
|
-
async def _on_launcher_connected(self, ws: WebSocket):
|
|
71
|
-
"""Called on first Launcher WS connect. Sends module.ready, then registers to Registry."""
|
|
72
|
-
self._launcher_connected = True
|
|
73
|
-
msg = {
|
|
74
|
-
"type": "event",
|
|
75
|
-
"event_id": str(uuid.uuid4()),
|
|
76
|
-
"event": "module.ready",
|
|
77
|
-
"source": "event_hub",
|
|
78
|
-
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
79
|
-
"data": {
|
|
80
|
-
"module_id": "event_hub",
|
|
81
|
-
"ws_endpoint": f"ws://{self.advertise_ip}:{self.port}/ws",
|
|
82
|
-
},
|
|
83
|
-
}
|
|
84
|
-
try:
|
|
85
|
-
await ws.send_text(json.dumps(msg))
|
|
86
|
-
print("[event_hub] Sent module.ready to Launcher")
|
|
87
|
-
except Exception as e:
|
|
88
|
-
print(f"[event_hub] Failed to send module.ready: {e}")
|
|
89
|
-
# Register to Registry in background
|
|
90
|
-
asyncio.create_task(self._register_to_registry())
|
|
91
|
-
|
|
92
|
-
async def _register_to_registry(self):
|
|
93
|
-
"""Register to Registry. Triggered after Launcher connects."""
|
|
94
|
-
if self._registered_to_registry:
|
|
95
|
-
return
|
|
96
|
-
payload = {
|
|
97
|
-
"action": "register",
|
|
98
|
-
"module_id": "event_hub",
|
|
99
|
-
"module_type": "infrastructure",
|
|
100
|
-
"name": "Event Hub",
|
|
101
|
-
"api_endpoint": f"http://{self.advertise_ip}:{self.port}",
|
|
102
|
-
"health_endpoint": "/health",
|
|
103
|
-
"metadata": {
|
|
104
|
-
"ws_endpoint": f"ws://{self.advertise_ip}:{self.port}/ws",
|
|
105
|
-
},
|
|
106
|
-
}
|
|
107
|
-
headers = {"Authorization": f"Bearer {self.own_token}"}
|
|
108
|
-
try:
|
|
109
|
-
async with httpx.AsyncClient() as client:
|
|
110
|
-
resp = await client.post(
|
|
111
|
-
f"{self.registry_url}/modules",
|
|
112
|
-
json=payload,
|
|
113
|
-
headers=headers,
|
|
114
|
-
timeout=5,
|
|
115
|
-
)
|
|
116
|
-
if resp.status_code == 200:
|
|
117
|
-
self._registered_to_registry = True
|
|
118
|
-
print("[event_hub] Registered to Registry")
|
|
119
|
-
else:
|
|
120
|
-
print(f"[event_hub] WARNING: Registry returned {resp.status_code}")
|
|
121
|
-
except Exception as e:
|
|
122
|
-
print(f"[event_hub] WARNING: Failed to register to Registry: {e}")
|
|
123
|
-
|
|
124
|
-
# ── App factory ──
|
|
125
|
-
|
|
126
|
-
def _create_app(self) -> FastAPI:
|
|
127
|
-
app = FastAPI(title="Kite Event Hub", docs_url=None, redoc_url=None)
|
|
128
|
-
server = self
|
|
129
|
-
|
|
130
|
-
@app.on_event("startup")
|
|
131
|
-
async def _startup():
|
|
132
|
-
server._timer_task = asyncio.create_task(server._timer_loop())
|
|
133
|
-
|
|
134
|
-
@app.on_event("shutdown")
|
|
135
|
-
async def _shutdown():
|
|
136
|
-
if server._timer_task:
|
|
137
|
-
server._timer_task.cancel()
|
|
138
|
-
|
|
139
|
-
# ── WebSocket endpoint ──
|
|
140
|
-
|
|
141
|
-
@app.websocket("/ws")
|
|
142
|
-
async def ws_endpoint(ws: WebSocket):
|
|
143
|
-
token = ws.query_params.get("token", "")
|
|
144
|
-
mid_hint = ws.query_params.get("id", "")
|
|
145
|
-
module_id = await server._verify_token(token, mid_hint)
|
|
146
|
-
if not module_id:
|
|
147
|
-
# Must accept before close — Starlette drops TCP without close frame otherwise,
|
|
148
|
-
# causing websockets 15.x clients to get "no close frame received or sent" errors.
|
|
149
|
-
await ws.accept()
|
|
150
|
-
print(f"[event_hub] 认证失败: token={token[:8]}... hint={mid_hint}")
|
|
151
|
-
await ws.close(code=4001, reason="Authentication failed")
|
|
152
|
-
return
|
|
153
|
-
|
|
154
|
-
await ws.accept()
|
|
155
|
-
server.hub.add_connection(module_id, ws)
|
|
156
|
-
|
|
157
|
-
# Launcher bootstrap: first connection triggers module.ready + Registry registration
|
|
158
|
-
if (module_id == "launcher"
|
|
159
|
-
and not server._launcher_connected
|
|
160
|
-
and server.launcher_ws_token
|
|
161
|
-
and token == server.launcher_ws_token):
|
|
162
|
-
await server._on_launcher_connected(ws)
|
|
163
|
-
|
|
164
|
-
try:
|
|
165
|
-
while True:
|
|
166
|
-
raw = await ws.receive_text()
|
|
167
|
-
await server.hub.handle_message(module_id, ws, raw)
|
|
168
|
-
except WebSocketDisconnect:
|
|
169
|
-
pass
|
|
170
|
-
except Exception as e:
|
|
171
|
-
print(f"[event_hub] WebSocket error for {module_id}: {e}")
|
|
172
|
-
finally:
|
|
173
|
-
server.hub.remove_connection(module_id)
|
|
174
|
-
|
|
175
|
-
# ── HTTP endpoints ──
|
|
176
|
-
|
|
177
|
-
@app.get("/health")
|
|
178
|
-
async def health():
|
|
179
|
-
return server.hub.get_health()
|
|
180
|
-
|
|
181
|
-
@app.get("/stats")
|
|
182
|
-
async def stats():
|
|
183
|
-
return server.hub.get_stats()
|
|
184
|
-
|
|
185
|
-
return app
|
|
186
|
-
|
|
187
|
-
# ── 30s timer: heartbeat + dedup cleanup ──
|
|
188
|
-
|
|
189
|
-
async def _timer_loop(self):
|
|
190
|
-
while True:
|
|
191
|
-
await asyncio.sleep(30)
|
|
192
|
-
await asyncio.get_event_loop().run_in_executor(None, self.hub.dedup.cleanup)
|
|
193
|
-
if self._registered_to_registry:
|
|
194
|
-
await self._heartbeat()
|
|
195
|
-
|
|
196
|
-
async def _heartbeat(self):
|
|
197
|
-
try:
|
|
198
|
-
async with httpx.AsyncClient() as client:
|
|
199
|
-
await client.post(
|
|
200
|
-
f"{self.registry_url}/modules",
|
|
201
|
-
json={"action": "heartbeat", "module_id": "event_hub"},
|
|
202
|
-
headers={"Authorization": f"Bearer {self.own_token}"},
|
|
203
|
-
timeout=5,
|
|
204
|
-
)
|
|
205
|
-
except Exception:
|
|
206
|
-
pass
|