@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.
Files changed (100) hide show
  1. package/CHANGELOG.md +208 -0
  2. package/README.md +48 -0
  3. package/cli.js +1 -1
  4. package/extensions/agents/__init__.py +1 -0
  5. package/extensions/agents/assistant/__init__.py +1 -0
  6. package/extensions/agents/assistant/entry.py +329 -0
  7. package/extensions/agents/assistant/module.md +22 -0
  8. package/extensions/agents/assistant/server.py +197 -0
  9. package/extensions/channels/__init__.py +1 -0
  10. package/extensions/channels/acp_channel/__init__.py +1 -0
  11. package/extensions/channels/acp_channel/entry.py +329 -0
  12. package/extensions/channels/acp_channel/module.md +22 -0
  13. package/extensions/channels/acp_channel/server.py +197 -0
  14. package/extensions/event_hub_bench/entry.py +624 -379
  15. package/extensions/event_hub_bench/module.md +2 -1
  16. package/extensions/services/backup/__init__.py +1 -0
  17. package/extensions/services/backup/entry.py +508 -0
  18. package/extensions/services/backup/module.md +22 -0
  19. package/extensions/services/model_service/__init__.py +1 -0
  20. package/extensions/services/model_service/entry.py +508 -0
  21. package/extensions/services/model_service/module.md +22 -0
  22. package/extensions/services/watchdog/entry.py +468 -102
  23. package/extensions/services/watchdog/module.md +3 -0
  24. package/extensions/services/watchdog/monitor.py +170 -69
  25. package/extensions/services/web/__init__.py +1 -0
  26. package/extensions/services/web/config.yaml +149 -0
  27. package/extensions/services/web/entry.py +390 -0
  28. package/extensions/services/web/module.md +24 -0
  29. package/extensions/services/web/routes/__init__.py +1 -0
  30. package/extensions/services/web/routes/routes_call.py +189 -0
  31. package/extensions/services/web/routes/routes_config.py +512 -0
  32. package/extensions/services/web/routes/routes_contacts.py +98 -0
  33. package/extensions/services/web/routes/routes_devlog.py +99 -0
  34. package/extensions/services/web/routes/routes_phone.py +81 -0
  35. package/extensions/services/web/routes/routes_sms.py +48 -0
  36. package/extensions/services/web/routes/routes_stats.py +17 -0
  37. package/extensions/services/web/routes/routes_voicechat.py +554 -0
  38. package/extensions/services/web/routes/schemas.py +216 -0
  39. package/extensions/services/web/server.py +375 -0
  40. package/extensions/services/web/static/css/style.css +1064 -0
  41. package/extensions/services/web/static/index.html +1445 -0
  42. package/extensions/services/web/static/js/app.js +4671 -0
  43. package/extensions/services/web/vendor/__init__.py +1 -0
  44. package/extensions/services/web/vendor/bluetooth/audio.py +348 -0
  45. package/extensions/services/web/vendor/bluetooth/contacts.py +251 -0
  46. package/extensions/services/web/vendor/bluetooth/manager.py +395 -0
  47. package/extensions/services/web/vendor/bluetooth/sms.py +290 -0
  48. package/extensions/services/web/vendor/bluetooth/telephony.py +274 -0
  49. package/extensions/services/web/vendor/config.py +139 -0
  50. package/extensions/services/web/vendor/conversation/asr.py +936 -0
  51. package/extensions/services/web/vendor/conversation/engine.py +548 -0
  52. package/extensions/services/web/vendor/conversation/llm.py +534 -0
  53. package/extensions/services/web/vendor/conversation/mcp_tools.py +190 -0
  54. package/extensions/services/web/vendor/conversation/tts.py +322 -0
  55. package/extensions/services/web/vendor/conversation/vad.py +138 -0
  56. package/extensions/services/web/vendor/storage/__init__.py +1 -0
  57. package/extensions/services/web/vendor/storage/identity.py +312 -0
  58. package/extensions/services/web/vendor/storage/store.py +507 -0
  59. package/extensions/services/web/vendor/task/manager.py +864 -0
  60. package/extensions/services/web/vendor/task/models.py +45 -0
  61. package/extensions/services/web/vendor/task/webhook.py +263 -0
  62. package/extensions/services/web/vendor/tools/registry.py +321 -0
  63. package/kernel/__init__.py +0 -0
  64. package/kernel/entry.py +407 -0
  65. package/{core/event_hub/hub.py → kernel/event_hub.py} +62 -74
  66. package/kernel/module.md +33 -0
  67. package/{core/registry/store.py → kernel/registry_store.py} +23 -8
  68. package/kernel/rpc_router.py +388 -0
  69. package/kernel/server.py +267 -0
  70. package/launcher/__init__.py +10 -0
  71. package/launcher/__main__.py +6 -0
  72. package/launcher/count_lines.py +258 -0
  73. package/launcher/entry.py +1778 -0
  74. package/launcher/logging_setup.py +289 -0
  75. package/{core/launcher → launcher}/module_scanner.py +11 -6
  76. package/launcher/process_manager.py +880 -0
  77. package/main.py +11 -210
  78. package/package.json +6 -9
  79. package/__init__.py +0 -1
  80. package/__main__.py +0 -15
  81. package/core/event_hub/BENCHMARK.md +0 -94
  82. package/core/event_hub/bench.py +0 -459
  83. package/core/event_hub/bench_extreme.py +0 -308
  84. package/core/event_hub/bench_perf.py +0 -350
  85. package/core/event_hub/entry.py +0 -157
  86. package/core/event_hub/module.md +0 -20
  87. package/core/event_hub/server.py +0 -206
  88. package/core/launcher/entry.py +0 -1158
  89. package/core/launcher/process_manager.py +0 -470
  90. package/core/registry/entry.py +0 -110
  91. package/core/registry/module.md +0 -30
  92. package/core/registry/server.py +0 -289
  93. package/extensions/services/watchdog/server.py +0 -167
  94. /package/{core → extensions/services/web/vendor/bluetooth}/__init__.py +0 -0
  95. /package/{core/event_hub → extensions/services/web/vendor/conversation}/__init__.py +0 -0
  96. /package/{core/launcher → extensions/services/web/vendor/task}/__init__.py +0 -0
  97. /package/{core/registry → extensions/services/web/vendor/tools}/__init__.py +0 -0
  98. /package/{core/event_hub → kernel}/dedup.py +0 -0
  99. /package/{core/event_hub → kernel}/router.py +0 -0
  100. /package/{core/launcher → launcher}/module.md +0 -0
@@ -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()
@@ -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
@@ -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