@agentunion/kite 1.0.7 → 1.2.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/core/event_hub/entry.py +305 -26
- package/core/event_hub/hub.py +8 -0
- package/core/event_hub/server.py +80 -17
- package/core/kite_log.py +241 -0
- package/core/launcher/entry.py +978 -284
- package/core/launcher/process_manager.py +456 -46
- package/core/registry/entry.py +272 -3
- package/core/registry/server.py +339 -289
- package/core/registry/store.py +10 -4
- package/extensions/agents/__init__.py +1 -0
- package/extensions/agents/assistant/__init__.py +1 -0
- package/extensions/agents/assistant/entry.py +380 -0
- package/extensions/agents/assistant/module.md +22 -0
- package/extensions/agents/assistant/server.py +236 -0
- package/extensions/channels/__init__.py +1 -0
- package/extensions/channels/acp_channel/__init__.py +1 -0
- package/extensions/channels/acp_channel/entry.py +380 -0
- package/extensions/channels/acp_channel/module.md +22 -0
- package/extensions/channels/acp_channel/server.py +236 -0
- package/extensions/event_hub_bench/entry.py +664 -379
- package/extensions/event_hub_bench/module.md +2 -1
- package/extensions/services/backup/__init__.py +1 -0
- package/extensions/services/backup/entry.py +380 -0
- package/extensions/services/backup/module.md +22 -0
- package/extensions/services/backup/server.py +244 -0
- package/extensions/services/model_service/__init__.py +1 -0
- package/extensions/services/model_service/entry.py +380 -0
- package/extensions/services/model_service/module.md +22 -0
- package/extensions/services/model_service/server.py +236 -0
- package/extensions/services/watchdog/entry.py +460 -147
- package/extensions/services/watchdog/module.md +3 -0
- package/extensions/services/watchdog/monitor.py +128 -13
- package/extensions/services/watchdog/server.py +75 -13
- package/extensions/services/web/__init__.py +1 -0
- package/extensions/services/web/config.yaml +149 -0
- package/extensions/services/web/entry.py +487 -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 +332 -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/__init__.py +0 -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/__init__.py +0 -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/__init__.py +0 -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/__init__.py +0 -0
- package/extensions/services/web/vendor/tools/registry.py +321 -0
- package/main.py +230 -90
- package/package.json +1 -1
package/core/registry/entry.py
CHANGED
|
@@ -4,13 +4,243 @@ Reads token from stdin boot_info, starts HTTP server on dynamic port,
|
|
|
4
4
|
outputs port via stdout structured message, waits for Event Hub to trigger module.ready.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import builtins
|
|
7
8
|
import json
|
|
8
9
|
import os
|
|
10
|
+
import re
|
|
9
11
|
import sys
|
|
10
12
|
import socket
|
|
13
|
+
import threading
|
|
14
|
+
import time
|
|
15
|
+
import traceback
|
|
16
|
+
from datetime import datetime, timezone
|
|
11
17
|
|
|
12
18
|
import uvicorn
|
|
13
19
|
|
|
20
|
+
|
|
21
|
+
# ── Module configuration ──
|
|
22
|
+
MODULE_NAME = "registry"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _fmt_elapsed(t0: float) -> str:
|
|
26
|
+
"""Format elapsed time since t0: <1s → 'NNNms', >=1s → 'N.Ns', >=10s → 'NNs'."""
|
|
27
|
+
d = time.monotonic() - t0
|
|
28
|
+
if d < 1:
|
|
29
|
+
return f"{d * 1000:.0f}ms"
|
|
30
|
+
if d < 10:
|
|
31
|
+
return f"{d:.1f}s"
|
|
32
|
+
return f"{d:.0f}s"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ── Safe stdout/stderr: ignore BrokenPipeError after Launcher closes stdio ──
|
|
36
|
+
|
|
37
|
+
class _SafeWriter:
|
|
38
|
+
"""Wraps a stream to silently swallow BrokenPipeError on write/flush."""
|
|
39
|
+
def __init__(self, stream):
|
|
40
|
+
self._stream = stream
|
|
41
|
+
|
|
42
|
+
def write(self, s):
|
|
43
|
+
try:
|
|
44
|
+
self._stream.write(s)
|
|
45
|
+
except (BrokenPipeError, OSError):
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
def flush(self):
|
|
49
|
+
try:
|
|
50
|
+
self._stream.flush()
|
|
51
|
+
except (BrokenPipeError, OSError):
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
def __getattr__(self, name):
|
|
55
|
+
return getattr(self._stream, name)
|
|
56
|
+
|
|
57
|
+
sys.stdout = _SafeWriter(sys.stdout)
|
|
58
|
+
sys.stderr = _SafeWriter(sys.stderr)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ── Timestamped print + log file writer ──
|
|
62
|
+
# Implements format: [timestamp] HH:MM:SS.mmm +delta [module_name] message
|
|
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
|
+
# 1. Write to crashes.jsonl (current run)
|
|
138
|
+
if _crash_log_path:
|
|
139
|
+
try:
|
|
140
|
+
with open(_crash_log_path, "a", encoding="utf-8") as f:
|
|
141
|
+
f.write(line)
|
|
142
|
+
except Exception:
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
# 2. Write to daily crash archive
|
|
146
|
+
if _log_dir:
|
|
147
|
+
try:
|
|
148
|
+
today = datetime.now().strftime("%Y-%m-%d")
|
|
149
|
+
archive_dir = os.path.join(_log_dir, "crashes", today[:7])
|
|
150
|
+
os.makedirs(archive_dir, exist_ok=True)
|
|
151
|
+
archive_path = os.path.join(archive_dir, f"{today}.jsonl")
|
|
152
|
+
with open(archive_path, "a", encoding="utf-8") as f:
|
|
153
|
+
f.write(line)
|
|
154
|
+
except Exception:
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
def _print_crash_summary(exc_type, exc_tb, thread_name=None):
|
|
158
|
+
"""Print crash summary to console (red highlight)."""
|
|
159
|
+
RED = "\033[91m"
|
|
160
|
+
RESET = "\033[0m"
|
|
161
|
+
|
|
162
|
+
if exc_tb:
|
|
163
|
+
tb_entries = traceback.extract_tb(exc_tb)
|
|
164
|
+
if tb_entries:
|
|
165
|
+
last = tb_entries[-1]
|
|
166
|
+
location = f"{os.path.basename(last.filename)}:{last.lineno}"
|
|
167
|
+
else:
|
|
168
|
+
location = "unknown"
|
|
169
|
+
else:
|
|
170
|
+
location = "unknown"
|
|
171
|
+
|
|
172
|
+
prefix = f"[{MODULE_NAME}]"
|
|
173
|
+
if thread_name:
|
|
174
|
+
_builtin_print(f"{prefix} {RED}线程 {thread_name} 崩溃: "
|
|
175
|
+
f"{exc_type.__name__} in {location}{RESET}")
|
|
176
|
+
else:
|
|
177
|
+
_builtin_print(f"{prefix} {RED}崩溃: {exc_type.__name__} in {location}{RESET}")
|
|
178
|
+
if _crash_log_path:
|
|
179
|
+
_builtin_print(f"{prefix} 崩溃日志: {_crash_log_path}")
|
|
180
|
+
|
|
181
|
+
def _setup_exception_hooks():
|
|
182
|
+
"""Set up global exception hooks (sys.excepthook + threading.excepthook)."""
|
|
183
|
+
_orig_excepthook = sys.excepthook
|
|
184
|
+
|
|
185
|
+
def _excepthook(exc_type, exc_value, exc_tb):
|
|
186
|
+
_write_crash(exc_type, exc_value, exc_tb, severity="critical", handled=False)
|
|
187
|
+
_print_crash_summary(exc_type, exc_tb)
|
|
188
|
+
_orig_excepthook(exc_type, exc_value, exc_tb)
|
|
189
|
+
|
|
190
|
+
sys.excepthook = _excepthook
|
|
191
|
+
|
|
192
|
+
if hasattr(threading, "excepthook"):
|
|
193
|
+
def _thread_excepthook(args):
|
|
194
|
+
_write_crash(args.exc_type, args.exc_value, args.exc_traceback,
|
|
195
|
+
thread_name=args.thread.name if args.thread else "unknown",
|
|
196
|
+
severity="error", handled=False)
|
|
197
|
+
_print_crash_summary(args.exc_type, args.exc_traceback,
|
|
198
|
+
thread_name=args.thread.name if args.thread else None)
|
|
199
|
+
|
|
200
|
+
threading.excepthook = _thread_excepthook
|
|
201
|
+
|
|
202
|
+
def _tprint(*args, **kwargs):
|
|
203
|
+
"""Timestamped print that adds [timestamp] HH:MM:SS.mmm +delta prefix."""
|
|
204
|
+
global _last_ts
|
|
205
|
+
now = time.monotonic()
|
|
206
|
+
elapsed = now - _start_ts
|
|
207
|
+
delta = now - _last_ts
|
|
208
|
+
_last_ts = now
|
|
209
|
+
|
|
210
|
+
# Format elapsed (cumulative time since start)
|
|
211
|
+
if elapsed < 1:
|
|
212
|
+
elapsed_str = f"{elapsed * 1000:.0f}ms"
|
|
213
|
+
elif elapsed < 100:
|
|
214
|
+
elapsed_str = f"{elapsed:.1f}s"
|
|
215
|
+
else:
|
|
216
|
+
elapsed_str = f"{elapsed:.0f}s"
|
|
217
|
+
|
|
218
|
+
# Format delta (time since last print)
|
|
219
|
+
if delta < 0.001:
|
|
220
|
+
delta_str = ""
|
|
221
|
+
elif delta < 1:
|
|
222
|
+
delta_str = f"+{delta * 1000:.0f}ms"
|
|
223
|
+
elif delta < 100:
|
|
224
|
+
delta_str = f"+{delta:.1f}s"
|
|
225
|
+
else:
|
|
226
|
+
delta_str = f"+{delta:.0f}s"
|
|
227
|
+
|
|
228
|
+
# Current time
|
|
229
|
+
ts = datetime.now().strftime("%H:%M:%S.%f")[:-3]
|
|
230
|
+
|
|
231
|
+
# Print to console (original behavior)
|
|
232
|
+
_builtin_print(*args, **kwargs)
|
|
233
|
+
|
|
234
|
+
# Write to log files with timestamp prefix
|
|
235
|
+
if _log_latest_path or _log_daily_path:
|
|
236
|
+
sep = kwargs.get("sep", " ")
|
|
237
|
+
end = kwargs.get("end", "\n")
|
|
238
|
+
text = sep.join(str(a) for a in args)
|
|
239
|
+
prefix = f"[{elapsed_str:>6}] {ts} {delta_str:>8} "
|
|
240
|
+
_write_log(prefix + _strip_ansi(text) + end)
|
|
241
|
+
|
|
242
|
+
builtins.print = _tprint
|
|
243
|
+
|
|
14
244
|
# Ensure project root is on sys.path (set by main.py or cli.js)
|
|
15
245
|
_this_dir = os.path.dirname(os.path.abspath(__file__))
|
|
16
246
|
_project_root = os.environ.get("KITE_PROJECT") or os.path.dirname(os.path.dirname(_this_dir))
|
|
@@ -58,6 +288,37 @@ def _bind_port(preferred: int, host: str) -> int:
|
|
|
58
288
|
|
|
59
289
|
|
|
60
290
|
def main():
|
|
291
|
+
# Initialize log file paths
|
|
292
|
+
global _log_dir, _log_latest_path, _crash_log_path
|
|
293
|
+
module_data = os.environ.get("KITE_MODULE_DATA")
|
|
294
|
+
if module_data:
|
|
295
|
+
_log_dir = os.path.join(module_data, "log")
|
|
296
|
+
os.makedirs(_log_dir, exist_ok=True)
|
|
297
|
+
suffix = os.environ.get("KITE_INSTANCE_SUFFIX", "")
|
|
298
|
+
|
|
299
|
+
# latest.log — truncate on each startup
|
|
300
|
+
_log_latest_path = os.path.join(_log_dir, f"latest{suffix}.log")
|
|
301
|
+
try:
|
|
302
|
+
with open(_log_latest_path, "w", encoding="utf-8") as f:
|
|
303
|
+
pass # truncate
|
|
304
|
+
except Exception:
|
|
305
|
+
_log_latest_path = None
|
|
306
|
+
|
|
307
|
+
# crashes.jsonl — truncate on each startup
|
|
308
|
+
_crash_log_path = os.path.join(_log_dir, f"crashes{suffix}.jsonl")
|
|
309
|
+
try:
|
|
310
|
+
with open(_crash_log_path, "w", encoding="utf-8") as f:
|
|
311
|
+
pass # truncate
|
|
312
|
+
except Exception:
|
|
313
|
+
_crash_log_path = None
|
|
314
|
+
|
|
315
|
+
_resolve_daily_log_path()
|
|
316
|
+
|
|
317
|
+
# Setup exception hooks
|
|
318
|
+
_setup_exception_hooks()
|
|
319
|
+
|
|
320
|
+
_t0 = time.monotonic()
|
|
321
|
+
|
|
61
322
|
# Kite environment
|
|
62
323
|
kite_instance = os.environ.get("KITE_INSTANCE", "")
|
|
63
324
|
is_debug = os.environ.get("KITE_DEBUG") == "1"
|
|
@@ -76,7 +337,7 @@ def main():
|
|
|
76
337
|
print("[registry] 错误: 未通过 stdin boot_info 提供启动器令牌")
|
|
77
338
|
sys.exit(1)
|
|
78
339
|
|
|
79
|
-
print(f"[registry] 已收到启动器令牌 ({len(launcher_token)} 字符)")
|
|
340
|
+
print(f"[registry] 已收到启动器令牌 ({len(launcher_token)} 字符) ({_fmt_elapsed(_t0)})")
|
|
80
341
|
|
|
81
342
|
if is_debug:
|
|
82
343
|
print("[registry] 调试模式已启用 (KITE_DEBUG=1),接受所有令牌")
|
|
@@ -98,12 +359,20 @@ def main():
|
|
|
98
359
|
# This message proves HTTP is about to start — Launcher uses it as readiness signal
|
|
99
360
|
print(json.dumps({"kite": "port", "port": port}), flush=True)
|
|
100
361
|
|
|
101
|
-
print(f"[registry] 启动中 {bind_host}:{port}")
|
|
362
|
+
print(f"[registry] 启动中 {bind_host}:{port} ({_fmt_elapsed(_t0)})")
|
|
102
363
|
|
|
103
364
|
# Store port and advertise_ip for module.ready event later
|
|
104
365
|
server.port = port
|
|
105
366
|
|
|
106
|
-
|
|
367
|
+
try:
|
|
368
|
+
config = uvicorn.Config(server.app, host=bind_host, port=port, log_level="warning")
|
|
369
|
+
uvi_server = uvicorn.Server(config)
|
|
370
|
+
server._uvicorn_server = uvi_server
|
|
371
|
+
uvi_server.run()
|
|
372
|
+
except Exception as e:
|
|
373
|
+
_write_crash(type(e), e, e.__traceback__, severity="critical", handled=True)
|
|
374
|
+
_print_crash_summary(type(e), e.__traceback__)
|
|
375
|
+
sys.exit(1)
|
|
107
376
|
|
|
108
377
|
|
|
109
378
|
if __name__ == "__main__":
|