@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.
Files changed (77) hide show
  1. package/core/event_hub/entry.py +305 -26
  2. package/core/event_hub/hub.py +8 -0
  3. package/core/event_hub/server.py +80 -17
  4. package/core/kite_log.py +241 -0
  5. package/core/launcher/entry.py +978 -284
  6. package/core/launcher/process_manager.py +456 -46
  7. package/core/registry/entry.py +272 -3
  8. package/core/registry/server.py +339 -289
  9. package/core/registry/store.py +10 -4
  10. package/extensions/agents/__init__.py +1 -0
  11. package/extensions/agents/assistant/__init__.py +1 -0
  12. package/extensions/agents/assistant/entry.py +380 -0
  13. package/extensions/agents/assistant/module.md +22 -0
  14. package/extensions/agents/assistant/server.py +236 -0
  15. package/extensions/channels/__init__.py +1 -0
  16. package/extensions/channels/acp_channel/__init__.py +1 -0
  17. package/extensions/channels/acp_channel/entry.py +380 -0
  18. package/extensions/channels/acp_channel/module.md +22 -0
  19. package/extensions/channels/acp_channel/server.py +236 -0
  20. package/extensions/event_hub_bench/entry.py +664 -379
  21. package/extensions/event_hub_bench/module.md +2 -1
  22. package/extensions/services/backup/__init__.py +1 -0
  23. package/extensions/services/backup/entry.py +380 -0
  24. package/extensions/services/backup/module.md +22 -0
  25. package/extensions/services/backup/server.py +244 -0
  26. package/extensions/services/model_service/__init__.py +1 -0
  27. package/extensions/services/model_service/entry.py +380 -0
  28. package/extensions/services/model_service/module.md +22 -0
  29. package/extensions/services/model_service/server.py +236 -0
  30. package/extensions/services/watchdog/entry.py +460 -147
  31. package/extensions/services/watchdog/module.md +3 -0
  32. package/extensions/services/watchdog/monitor.py +128 -13
  33. package/extensions/services/watchdog/server.py +75 -13
  34. package/extensions/services/web/__init__.py +1 -0
  35. package/extensions/services/web/config.yaml +149 -0
  36. package/extensions/services/web/entry.py +487 -0
  37. package/extensions/services/web/module.md +24 -0
  38. package/extensions/services/web/routes/__init__.py +1 -0
  39. package/extensions/services/web/routes/routes_call.py +189 -0
  40. package/extensions/services/web/routes/routes_config.py +512 -0
  41. package/extensions/services/web/routes/routes_contacts.py +98 -0
  42. package/extensions/services/web/routes/routes_devlog.py +99 -0
  43. package/extensions/services/web/routes/routes_phone.py +81 -0
  44. package/extensions/services/web/routes/routes_sms.py +48 -0
  45. package/extensions/services/web/routes/routes_stats.py +17 -0
  46. package/extensions/services/web/routes/routes_voicechat.py +554 -0
  47. package/extensions/services/web/routes/schemas.py +216 -0
  48. package/extensions/services/web/server.py +332 -0
  49. package/extensions/services/web/static/css/style.css +1064 -0
  50. package/extensions/services/web/static/index.html +1445 -0
  51. package/extensions/services/web/static/js/app.js +4671 -0
  52. package/extensions/services/web/vendor/__init__.py +1 -0
  53. package/extensions/services/web/vendor/bluetooth/__init__.py +0 -0
  54. package/extensions/services/web/vendor/bluetooth/audio.py +348 -0
  55. package/extensions/services/web/vendor/bluetooth/contacts.py +251 -0
  56. package/extensions/services/web/vendor/bluetooth/manager.py +395 -0
  57. package/extensions/services/web/vendor/bluetooth/sms.py +290 -0
  58. package/extensions/services/web/vendor/bluetooth/telephony.py +274 -0
  59. package/extensions/services/web/vendor/config.py +139 -0
  60. package/extensions/services/web/vendor/conversation/__init__.py +0 -0
  61. package/extensions/services/web/vendor/conversation/asr.py +936 -0
  62. package/extensions/services/web/vendor/conversation/engine.py +548 -0
  63. package/extensions/services/web/vendor/conversation/llm.py +534 -0
  64. package/extensions/services/web/vendor/conversation/mcp_tools.py +190 -0
  65. package/extensions/services/web/vendor/conversation/tts.py +322 -0
  66. package/extensions/services/web/vendor/conversation/vad.py +138 -0
  67. package/extensions/services/web/vendor/storage/__init__.py +1 -0
  68. package/extensions/services/web/vendor/storage/identity.py +312 -0
  69. package/extensions/services/web/vendor/storage/store.py +507 -0
  70. package/extensions/services/web/vendor/task/__init__.py +0 -0
  71. package/extensions/services/web/vendor/task/manager.py +864 -0
  72. package/extensions/services/web/vendor/task/models.py +45 -0
  73. package/extensions/services/web/vendor/task/webhook.py +263 -0
  74. package/extensions/services/web/vendor/tools/__init__.py +0 -0
  75. package/extensions/services/web/vendor/tools/registry.py +321 -0
  76. package/main.py +230 -90
  77. package/package.json +1 -1
@@ -0,0 +1,241 @@
1
+ """
2
+ Kite module logging utilities.
3
+ Provides latest.log + crashes.jsonl infrastructure per the 异常处理规范.
4
+
5
+ Each module calls init_module_log() and setup_exception_hooks() at startup.
6
+ All paths derived from KITE_MODULE_DATA environment variable.
7
+
8
+ Note: This is a convenience library for Python modules. Node.js/binary modules
9
+ implement the same spec independently (paths + formats are language-agnostic).
10
+ """
11
+
12
+ import json
13
+ import os
14
+ import re
15
+ import sys
16
+ import threading
17
+ import traceback
18
+ from datetime import datetime, timezone
19
+
20
+
21
+ # ── Module-level state (initialized by init_module_log) ──
22
+
23
+ _module_name = ""
24
+ _log_dir = None
25
+ _log_latest_path = None
26
+ _log_daily_path = None
27
+ _log_daily_date = ""
28
+ _crash_log_path = None
29
+ _log_lock = threading.Lock()
30
+
31
+ # Strip ANSI escape sequences for plain-text log files
32
+ _ANSI_RE = re.compile(r"\033\[[0-9;]*m")
33
+
34
+
35
+ def _strip_ansi(s: str) -> str:
36
+ return _ANSI_RE.sub("", s)
37
+
38
+
39
+ def _resolve_daily_log_path():
40
+ """Resolve daily log path based on current date."""
41
+ global _log_daily_path, _log_daily_date
42
+ if not _log_dir:
43
+ return
44
+ today = datetime.now().strftime("%Y-%m-%d")
45
+ if today == _log_daily_date and _log_daily_path:
46
+ return
47
+ month_dir = os.path.join(_log_dir, today[:7])
48
+ os.makedirs(month_dir, exist_ok=True)
49
+ _log_daily_path = os.path.join(month_dir, f"{today}.log")
50
+ _log_daily_date = today
51
+
52
+
53
+ def init_module_log(module_name: str):
54
+ """Initialize log files for a module. Call once at startup.
55
+
56
+ Requires KITE_MODULE_DATA environment variable to be set.
57
+ Creates: {KITE_MODULE_DATA}/log/latest.log (truncated)
58
+ {KITE_MODULE_DATA}/log/crashes.jsonl (truncated)
59
+ {KITE_MODULE_DATA}/log/{YYYY-MM}/ (daily archive directory)
60
+ """
61
+ global _module_name, _log_dir, _log_latest_path, _crash_log_path
62
+
63
+ _module_name = module_name
64
+ module_data = os.environ.get("KITE_MODULE_DATA")
65
+ if not module_data:
66
+ return
67
+
68
+ _log_dir = os.path.join(module_data, "log")
69
+ os.makedirs(_log_dir, exist_ok=True)
70
+
71
+ suffix = os.environ.get("KITE_INSTANCE_SUFFIX", "")
72
+
73
+ # latest.log — truncate on each startup
74
+ _log_latest_path = os.path.join(_log_dir, f"latest{suffix}.log")
75
+ try:
76
+ with open(_log_latest_path, "w", encoding="utf-8") as f:
77
+ pass
78
+ except Exception:
79
+ _log_latest_path = None
80
+
81
+ # crashes.jsonl — truncate on each startup
82
+ _crash_log_path = os.path.join(_log_dir, f"crashes{suffix}.jsonl")
83
+ try:
84
+ with open(_crash_log_path, "w", encoding="utf-8") as f:
85
+ pass
86
+ except Exception:
87
+ _crash_log_path = None
88
+
89
+ # Ensure daily archive directory exists
90
+ _resolve_daily_log_path()
91
+
92
+
93
+ def write_log(plain_line: str):
94
+ """Write a plain-text line to both latest.log and daily log (open-write-close)."""
95
+ with _log_lock:
96
+ if _log_latest_path:
97
+ try:
98
+ with open(_log_latest_path, "a", encoding="utf-8") as f:
99
+ f.write(plain_line)
100
+ except Exception:
101
+ pass
102
+ _resolve_daily_log_path()
103
+ if _log_daily_path:
104
+ try:
105
+ with open(_log_daily_path, "a", encoding="utf-8") as f:
106
+ f.write(plain_line)
107
+ except Exception:
108
+ pass
109
+
110
+
111
+ def write_crash(exc_type, exc_value, exc_tb,
112
+ thread_name=None, severity="critical", handled=False):
113
+ """Write crash record to crashes.jsonl + daily crash archive."""
114
+ record = {
115
+ "timestamp": datetime.now(timezone.utc).isoformat(),
116
+ "module": _module_name,
117
+ "thread": thread_name or threading.current_thread().name,
118
+ "exception_type": exc_type.__name__ if exc_type else "Unknown",
119
+ "exception_message": str(exc_value),
120
+ "traceback": "".join(traceback.format_exception(exc_type, exc_value, exc_tb)),
121
+ "severity": severity,
122
+ "handled": handled,
123
+ "process_id": os.getpid(),
124
+ "platform": sys.platform,
125
+ "runtime_version": f"Python {sys.version.split()[0]}",
126
+ }
127
+
128
+ if exc_tb:
129
+ tb_entries = traceback.extract_tb(exc_tb)
130
+ if tb_entries:
131
+ last = tb_entries[-1]
132
+ record["context"] = {
133
+ "function": last.name,
134
+ "file": os.path.basename(last.filename),
135
+ "line": last.lineno,
136
+ }
137
+
138
+ line = json.dumps(record, ensure_ascii=False) + "\n"
139
+
140
+ # 1. Write to crashes.jsonl (current run)
141
+ if _crash_log_path:
142
+ try:
143
+ with open(_crash_log_path, "a", encoding="utf-8") as f:
144
+ f.write(line)
145
+ except Exception:
146
+ pass
147
+
148
+ # 2. Write to daily crash archive
149
+ if _log_dir:
150
+ try:
151
+ today = datetime.now().strftime("%Y-%m-%d")
152
+ archive_dir = os.path.join(_log_dir, "crashes", today[:7])
153
+ os.makedirs(archive_dir, exist_ok=True)
154
+ archive_path = os.path.join(archive_dir, f"{today}.jsonl")
155
+ with open(archive_path, "a", encoding="utf-8") as f:
156
+ f.write(line)
157
+ except Exception:
158
+ pass
159
+
160
+
161
+ def print_crash_summary(exc_type, exc_tb, thread_name=None):
162
+ """Print crash summary to console (red highlight)."""
163
+ RED = "\033[91m"
164
+ RESET = "\033[0m"
165
+
166
+ if exc_tb:
167
+ tb_entries = traceback.extract_tb(exc_tb)
168
+ if tb_entries:
169
+ last = tb_entries[-1]
170
+ location = f"{os.path.basename(last.filename)}:{last.lineno}"
171
+ else:
172
+ location = "unknown"
173
+ else:
174
+ location = "unknown"
175
+
176
+ prefix = f"[{_module_name}]"
177
+ if thread_name:
178
+ print(f"{prefix} {RED}线程 {thread_name} 崩溃: "
179
+ f"{exc_type.__name__} in {location}{RESET}")
180
+ else:
181
+ print(f"{prefix} {RED}崩溃: {exc_type.__name__} in {location}{RESET}")
182
+ if _crash_log_path:
183
+ print(f"{prefix} 崩溃日志: {_crash_log_path}")
184
+
185
+
186
+ def setup_exception_hooks(module_name: str = ""):
187
+ """Set up global exception hooks (sys.excepthook + threading.excepthook).
188
+ Call once at startup after init_module_log().
189
+ """
190
+ if module_name:
191
+ global _module_name
192
+ _module_name = module_name
193
+
194
+ _orig_excepthook = sys.excepthook
195
+
196
+ def _excepthook(exc_type, exc_value, exc_tb):
197
+ write_crash(exc_type, exc_value, exc_tb, severity="critical", handled=False)
198
+ print_crash_summary(exc_type, exc_tb)
199
+ _orig_excepthook(exc_type, exc_value, exc_tb)
200
+
201
+ sys.excepthook = _excepthook
202
+
203
+ if hasattr(threading, "excepthook"):
204
+ def _thread_excepthook(args):
205
+ write_crash(args.exc_type, args.exc_value, args.exc_traceback,
206
+ thread_name=args.thread.name if args.thread else "unknown",
207
+ severity="error", handled=False)
208
+ print_crash_summary(args.exc_type, args.exc_traceback,
209
+ thread_name=args.thread.name if args.thread else None)
210
+
211
+ threading.excepthook = _thread_excepthook
212
+
213
+
214
+ def install_log_hook():
215
+ """Install a hook on the _SafeWriter or stdout to capture all print output
216
+ to latest.log + daily log. Call after init_module_log().
217
+
218
+ Wraps the current sys.stdout so that every write() also goes to log files.
219
+ """
220
+ if not _log_latest_path:
221
+ return
222
+
223
+ original_stdout = sys.stdout
224
+
225
+ class _LoggingWriter:
226
+ """Wraps stdout to tee all output to log files."""
227
+ def __init__(self, stream):
228
+ self._stream = stream
229
+
230
+ def write(self, s):
231
+ self._stream.write(s)
232
+ if s and s.strip():
233
+ write_log(_strip_ansi(s) + ("\n" if not s.endswith("\n") else ""))
234
+
235
+ def flush(self):
236
+ self._stream.flush()
237
+
238
+ def __getattr__(self, name):
239
+ return getattr(self._stream, name)
240
+
241
+ sys.stdout = _LoggingWriter(original_stdout)