@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
@@ -0,0 +1,289 @@
1
+ """
2
+ Launcher logging setup: timestamped print, log files, crash logging, exception hooks.
3
+ """
4
+ import builtins
5
+ import json
6
+ import os
7
+ import re
8
+ import sys
9
+ import threading
10
+ import time
11
+ import traceback
12
+ from datetime import datetime, timezone
13
+
14
+ # ── Timestamped print with delta + color ──
15
+ _builtin_print = builtins.print
16
+ _start_ts = time.monotonic()
17
+ _last_ts = time.monotonic()
18
+ _first_line = True
19
+ _module_last_ts: dict[str, float] = {}
20
+
21
+ _MODULE_PREFIX_RE = re.compile(r"^\[([a-z_]+)\]")
22
+
23
+ # ANSI escape codes
24
+ _DIM = "\033[90m"
25
+ _GREEN = "\033[32m"
26
+ _RED = "\033[91m"
27
+ _ORANGE = "\033[38;5;208m"
28
+ _RESET = "\033[0m"
29
+
30
+ # Log file paths
31
+ _log_lock = threading.Lock()
32
+ _log_latest_path = None
33
+ _log_daily_path = None
34
+ _log_daily_date = ""
35
+ _log_dir = None
36
+ _crash_log_path = None
37
+
38
+ _ANSI_RE = re.compile(r"\033\[[0-9;]*m")
39
+
40
+ def _strip_ansi(s: str) -> str:
41
+ return _ANSI_RE.sub("", s)
42
+
43
+
44
+ def init_log_files():
45
+ """Initialize log file paths. Called after KITE_MODULE_DATA is set."""
46
+ global _log_latest_path, _log_dir, _crash_log_path
47
+ module_data = os.environ.get("KITE_MODULE_DATA")
48
+ if not module_data:
49
+ return
50
+ _log_dir = os.path.join(module_data, "log")
51
+ os.makedirs(_log_dir, exist_ok=True)
52
+
53
+ suffix = os.environ.get("KITE_INSTANCE_SUFFIX", "")
54
+
55
+ # latest.log — truncate on each startup
56
+ _log_latest_path = os.path.join(_log_dir, f"latest{suffix}.log")
57
+ try:
58
+ with open(_log_latest_path, "w", encoding="utf-8") as f:
59
+ pass
60
+ except Exception as e:
61
+ _builtin_print(f"[launcher] 警告: 无法初始化 {os.path.basename(_log_latest_path)}: {e}")
62
+ _log_latest_path = None
63
+
64
+ # crashes.jsonl — truncate on each startup
65
+ _crash_log_path = os.path.join(_log_dir, f"crashes{suffix}.jsonl")
66
+ try:
67
+ with open(_crash_log_path, "w", encoding="utf-8") as f:
68
+ pass
69
+ except Exception:
70
+ _crash_log_path = None
71
+
72
+ # daily log — ensure directory exists
73
+ _resolve_daily_log_path()
74
+
75
+
76
+ def _resolve_daily_log_path():
77
+ """Resolve the daily log file path based on current date."""
78
+ global _log_daily_path, _log_daily_date
79
+ if not _log_dir:
80
+ return
81
+ today = datetime.now().strftime("%Y-%m-%d")
82
+ if today == _log_daily_date and _log_daily_path:
83
+ return
84
+ month_dir = os.path.join(_log_dir, today[:7])
85
+ os.makedirs(month_dir, exist_ok=True)
86
+ _log_daily_path = os.path.join(month_dir, f"{today}.log")
87
+ _log_daily_date = today
88
+
89
+
90
+ def _write_log(plain_line: str):
91
+ """Write a plain-text line to both latest.log and daily log."""
92
+ with _log_lock:
93
+ if _log_latest_path:
94
+ try:
95
+ with open(_log_latest_path, "a", encoding="utf-8") as f:
96
+ f.write(plain_line)
97
+ except Exception:
98
+ pass
99
+ _resolve_daily_log_path()
100
+ if _log_daily_path:
101
+ try:
102
+ with open(_log_daily_path, "a", encoding="utf-8") as f:
103
+ f.write(plain_line)
104
+ except Exception:
105
+ pass
106
+
107
+
108
+ def _write_crash(exc_type, exc_value, exc_tb,
109
+ thread_name=None, severity="critical", handled=False):
110
+ """Write crash record to crashes.jsonl + daily archive."""
111
+ record = {
112
+ "timestamp": datetime.now(timezone.utc).isoformat(),
113
+ "module": "launcher",
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
+ # 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
+ # Write to daily 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
+
158
+ def _print_crash_summary(exc_type, exc_tb, thread_name=None):
159
+ """Print crash summary to console."""
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 = "[launcher]"
171
+ if thread_name:
172
+ print(f"{prefix} {_RED}线程 {thread_name} 崩溃: "
173
+ f"{exc_type.__name__} in {location}{_RESET}")
174
+ else:
175
+ print(f"{prefix} {_RED}崩溃: {exc_type.__name__} in {location}{_RESET}")
176
+ if _crash_log_path:
177
+ print(f"{prefix} 崩溃日志: {_crash_log_path}")
178
+
179
+
180
+ def setup_exception_hooks():
181
+ """Set up global exception hooks for launcher process."""
182
+ _orig_excepthook = sys.excepthook
183
+
184
+ def _excepthook(exc_type, exc_value, exc_tb):
185
+ _write_crash(exc_type, exc_value, exc_tb, severity="critical", handled=False)
186
+ _print_crash_summary(exc_type, exc_tb)
187
+ _orig_excepthook(exc_type, exc_value, exc_tb)
188
+
189
+ sys.excepthook = _excepthook
190
+
191
+ if hasattr(threading, "excepthook"):
192
+ def _thread_excepthook(args):
193
+ _write_crash(args.exc_type, args.exc_value, args.exc_traceback,
194
+ thread_name=args.thread.name if args.thread else "unknown",
195
+ severity="error", handled=False)
196
+ _print_crash_summary(args.exc_type, args.exc_traceback,
197
+ thread_name=args.thread.name if args.thread else None)
198
+
199
+ threading.excepthook = _thread_excepthook
200
+
201
+
202
+ def _tprint(*args, **kwargs):
203
+ """Timestamped print with delta tracking."""
204
+ global _last_ts, _first_line
205
+ now = time.monotonic()
206
+ elapsed = now - _start_ts
207
+
208
+ ts = datetime.now().strftime("%H:%M:%S.%f")[:-3]
209
+
210
+ if elapsed < 1:
211
+ elapsed_str = f"{elapsed * 1000:.0f}ms"
212
+ elif elapsed < 100:
213
+ elapsed_str = f"{elapsed:.1f}s"
214
+ else:
215
+ elapsed_str = f"{elapsed:.0f}s"
216
+
217
+ text_for_module = " ".join(str(a) for a in args) if args else ""
218
+ plain_text = _ANSI_RE.sub("", text_for_module).strip()
219
+ m = _MODULE_PREFIX_RE.match(plain_text)
220
+ module_name = m.group(1) if m else None
221
+
222
+ if module_name:
223
+ if module_name in _module_last_ts:
224
+ delta = now - _module_last_ts[module_name]
225
+ else:
226
+ delta = None
227
+ _module_last_ts[module_name] = now
228
+ else:
229
+ delta = now - _last_ts
230
+ _last_ts = now
231
+
232
+ if delta is None:
233
+ delta_str = ""
234
+ delta_color = _DIM
235
+ elif delta < 1:
236
+ delta_str = f"+{delta * 1000:.0f}ms"
237
+ elif delta < 100:
238
+ delta_str = f"+{delta:.1f}s"
239
+ else:
240
+ delta_str = f"+{delta:.0f}s"
241
+
242
+ if delta is not None:
243
+ if delta >= 5:
244
+ delta_color = _RED
245
+ elif delta >= 1:
246
+ delta_color = _GREEN
247
+ else:
248
+ delta_color = _DIM
249
+
250
+ if _first_line:
251
+ _first_line = False
252
+ prefix = f"{_ORANGE}[{elapsed_str:>6}] {ts} {delta_str:>8} "
253
+ _builtin_print(prefix, end="")
254
+ _builtin_print(*args, end="", **{k: v for k, v in kwargs.items() if k != "end"})
255
+ _builtin_print(_RESET, end=kwargs.get("end", "\n"))
256
+ else:
257
+ prefix = f"[{elapsed_str:>6}] {ts} {delta_color}{delta_str:>8}{_RESET} "
258
+ _builtin_print(prefix, end="")
259
+ _builtin_print(*args, **kwargs)
260
+
261
+ if _log_latest_path or _log_daily_path:
262
+ sep = kwargs.get("sep", " ")
263
+ end = kwargs.get("end", "\n")
264
+ text = sep.join(str(a) for a in args)
265
+ plain_prefix = f"[{elapsed_str:>6}] {ts} {delta_str:>8} "
266
+ _write_log(plain_prefix + _strip_ansi(text) + end)
267
+
268
+
269
+ def setup_timestamped_print():
270
+ """Replace builtins.print with timestamped version."""
271
+ builtins.print = _tprint
272
+
273
+
274
+ def reset_time_baseline():
275
+ """Reset time baseline (used after code stats to exclude that time)."""
276
+ global _start_ts, _last_ts
277
+ _start_ts = time.monotonic()
278
+ _last_ts = time.monotonic()
279
+
280
+
281
+ def get_crash_log_path():
282
+ """Get the crash log path for error reporting."""
283
+ return _crash_log_path
284
+
285
+
286
+ def write_crash_handled(exc_type, exc_value, exc_tb):
287
+ """Write a handled crash to the log."""
288
+ _write_crash(exc_type, exc_value, exc_tb, severity="critical", handled=True)
289
+ _print_crash_summary(exc_type, exc_tb)
@@ -35,9 +35,11 @@ class ModuleInfo:
35
35
  launch: LaunchConfig = field(default_factory=LaunchConfig)
36
36
 
37
37
  def is_core(self) -> bool:
38
- """Core modules live directly under {KITE_PROJECT}/core/."""
39
- core_dir = os.path.join(os.environ["KITE_PROJECT"], "core")
40
- return os.path.normcase(self.module_dir).startswith(os.path.normcase(core_dir + os.sep))
38
+ """Core modules (launcher, kernel) live directly under {KITE_PROJECT}/."""
39
+ project_root = os.environ["KITE_PROJECT"]
40
+ # Check if module_dir is launcher or kernel at project root
41
+ return self.name in ("launcher", "kernel") and \
42
+ os.path.normcase(self.module_dir) == os.path.normcase(os.path.join(project_root, self.name))
41
43
 
42
44
 
43
45
  def _parse_frontmatter(text: str) -> dict:
@@ -92,15 +94,18 @@ class ModuleScanner:
92
94
  def __init__(self, discovery: dict = None, launcher_dir: str = ""):
93
95
  self.discovery = discovery
94
96
  project_root = os.environ["KITE_PROJECT"]
95
- self.launcher_dir = launcher_dir or os.path.join(project_root, "core", "launcher")
97
+ self.launcher_dir = launcher_dir or os.path.join(project_root, "launcher")
96
98
 
97
99
  def scan(self) -> dict[str, ModuleInfo]:
98
100
  """Return dict of {module_name: ModuleInfo}. Duplicate names are skipped."""
99
101
  modules = {}
100
102
  project_root = os.environ["KITE_PROJECT"]
101
103
 
102
- # Built-in: always scan core/ (depth 1) and extensions/ (depth 2)
103
- self._scan_dir(os.path.join(project_root, "core"), 1, modules)
104
+ # Built-in: scan kernel (depth 0) and extensions/ (depth 2)
105
+ # Note: launcher is not scanned (it's the scanner itself)
106
+ kernel_dir = os.path.join(project_root, "kernel")
107
+ if os.path.isdir(kernel_dir):
108
+ self._scan_dir(kernel_dir, 0, modules)
104
109
  self._scan_dir(os.path.join(project_root, "extensions"), 2, modules)
105
110
 
106
111
  # Extra sources from discovery config