@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
@@ -9,8 +9,6 @@ import json
9
9
  import time
10
10
  from datetime import datetime, timezone
11
11
 
12
- import httpx
13
-
14
12
 
15
13
  # Module health states
16
14
  HEALTHY = "healthy"
@@ -68,56 +66,56 @@ class HealthMonitor:
68
66
  # Check intervals per resource state
69
67
  INTERVALS = {NORMAL: 15, WARNING: 5, CRITICAL: 2}
70
68
 
71
- def __init__(self, own_token: str, registry_url: str, launcher_url: str,
72
- publish_event=None):
69
+ def __init__(self, own_token: str, kernel_port: int, publish_event=None):
73
70
  self.own_token = own_token
74
- self.registry_url = registry_url
75
- self.launcher_url = launcher_url
71
+ self.kernel_port = kernel_port
76
72
  self.publish_event = publish_event # async callable(event_dict)
73
+ self.rpc_call = None # set by entry.py: async callable(method, params)
77
74
  self.modules: dict[str, ModuleStatus] = {}
78
75
  self._running = False
79
76
  self._psutil = None # lazy import
80
77
 
78
+ # Restart decision state (module.exiting / module.stopped / module.ready)
79
+ self._exit_intents: dict[str, str] = {} # module_id -> action from module.exiting
80
+ self._graceful_modules: dict[str, bool] = { # module_id -> supports graceful shutdown
81
+ "kernel": True, # started before Watchdog, default True
82
+ }
83
+ self._system_shutting_down = False
84
+ self._system_ready = False
85
+ self._system_ready_event = asyncio.Event()
86
+ self._crash_counts: dict[str, int] = {} # module_id -> consecutive crash count
87
+
81
88
  # ── Module discovery ──
82
89
 
83
90
  async def discover_modules(self):
84
- """Fetch monitored modules from Launcher API + Registry health endpoints."""
85
- # Step 1: Get module list with monitor/pid from Launcher API
86
- monitored = {} # name -> {pid, running}
91
+ """Fetch monitored modules from Launcher + Registry via RPC."""
92
+ # Step 1: Get module list with monitor/pid from Launcher via RPC
93
+ monitored = {} # name -> pid
87
94
  try:
88
- async with httpx.AsyncClient() as client:
89
- resp = await client.get(
90
- f"{self.launcher_url}/launcher/modules", timeout=5,
91
- )
92
- if resp.status_code == 200:
93
- for m in resp.json():
94
- name = m.get("name", "")
95
- if name == "watchdog" or not m.get("monitor", True):
96
- continue
97
- if m.get("actual_state", "").startswith("running"):
98
- monitored[name] = m.get("pid")
95
+ resp = await self.rpc_call("launcher.list_modules", {})
96
+ result = resp.get("result", {})
97
+ for m in result.get("modules", []):
98
+ name = m.get("name", "")
99
+ if name == "watchdog" or not m.get("monitor", True):
100
+ continue
101
+ if m.get("actual_state", "").startswith("running"):
102
+ monitored[name] = m.get("pid")
99
103
  except Exception as e:
100
- print(f"[watchdog] Launcher API failed: {e}")
104
+ print(f"[watchdog] Launcher RPC failed: {e}")
101
105
  return
102
106
 
103
- # Step 2: Get health endpoints from Registry
107
+ # Step 2: Get health endpoints from Registry via RPC
104
108
  health_map = {} # name -> {api_endpoint, health_endpoint}
105
- headers = {"Authorization": f"Bearer {self.own_token}"}
106
109
  try:
107
- async with httpx.AsyncClient() as client:
108
- resp = await client.get(
109
- f"{self.registry_url}/lookup",
110
- params={"field": "health_endpoint"},
111
- headers=headers, timeout=5,
112
- )
113
- if resp.status_code == 200:
114
- for entry in resp.json():
115
- mid = entry.get("module", "")
116
- if mid in monitored:
117
- health_map[mid] = {
118
- "api_endpoint": entry.get("api_endpoint", ""),
119
- "health_endpoint": entry.get("value", "/health"),
120
- }
110
+ resp = await self.rpc_call("registry.lookup", {"field": "health_endpoint"})
111
+ result = resp.get("result", {})
112
+ for entry in result.get("results", []):
113
+ mid = entry.get("module", "")
114
+ if mid in monitored:
115
+ health_map[mid] = {
116
+ "api_endpoint": entry.get("api_endpoint", ""),
117
+ "health_endpoint": entry.get("value", "/health"),
118
+ }
121
119
  except Exception:
122
120
  pass
123
121
 
@@ -126,15 +124,21 @@ class HealthMonitor:
126
124
  for mid, pid in monitored.items():
127
125
  seen.add(mid)
128
126
  h = health_map.get(mid, {})
127
+ ep = h.get("api_endpoint", "")
128
+ hp = h.get("health_endpoint", "/health")
129
129
  if mid not in self.modules:
130
130
  self.modules[mid] = ModuleStatus(
131
131
  module_id=mid,
132
- api_endpoint=h.get("api_endpoint", ""),
133
- health_endpoint=h.get("health_endpoint", "/health"),
132
+ api_endpoint=ep,
133
+ health_endpoint=hp,
134
134
  pid=pid,
135
135
  )
136
136
  else:
137
137
  self.modules[mid].pid = pid
138
+ # Refresh endpoints once Registry has them
139
+ if ep:
140
+ self.modules[mid].api_endpoint = ep
141
+ self.modules[mid].health_endpoint = hp
138
142
 
139
143
  for mid in list(self.modules):
140
144
  if mid not in seen:
@@ -144,6 +148,8 @@ class HealthMonitor:
144
148
 
145
149
  async def _check_one(self, status: ModuleStatus):
146
150
  """Check a single module's /health endpoint."""
151
+ if not status.api_endpoint:
152
+ return # Not yet registered in Registry, will be picked up on next discover
147
153
  url = f"{status.api_endpoint}{status.health_endpoint}"
148
154
  status.last_check = time.time()
149
155
 
@@ -197,28 +203,27 @@ class HealthMonitor:
197
203
  # ── Restart via Launcher API ──
198
204
 
199
205
  async def _restart_module(self, status: ModuleStatus):
200
- """Restart a module via Launcher API."""
206
+ """Restart a module via Launcher RPC."""
201
207
  mid = status.module_id
202
208
  print(f"[watchdog] Restarting {mid} (attempt {status.restarted_count + 1}/{self.MAX_RESTARTS})")
203
209
  try:
204
- async with httpx.AsyncClient() as client:
205
- resp = await client.post(
206
- f"{self.launcher_url}/launcher/modules/{mid}/restart",
207
- json={"reason": "resource_critical" if status.resource_state == CRITICAL else "restart"},
208
- timeout=15,
209
- )
210
- if resp.status_code == 200:
211
- status.restarted_count += 1
212
- status.fail_count = 0
213
- print(f"[watchdog] {mid} restart requested")
214
- if status.restarted_count >= self.ALERT_AFTER_RESTARTS:
215
- await self._publish("watchdog.alert", {
216
- "module_id": mid,
217
- "restarted_count": status.restarted_count,
218
- "message": f"{mid} has been restarted {status.restarted_count} times",
219
- })
220
- else:
221
- print(f"[watchdog] {mid} restart failed: HTTP {resp.status_code}")
210
+ resp = await self.rpc_call("launcher.restart_module", {
211
+ "name": mid,
212
+ "reason": "resource_critical" if status.resource_state == CRITICAL else "restart",
213
+ })
214
+ result = resp.get("result", {})
215
+ if result.get("status") == "restarted":
216
+ status.restarted_count += 1
217
+ status.fail_count = 0
218
+ print(f"[watchdog] {mid} restart requested")
219
+ if status.restarted_count >= self.ALERT_AFTER_RESTARTS:
220
+ await self._publish("watchdog.alert", {
221
+ "module_id": mid,
222
+ "restarted_count": status.restarted_count,
223
+ "message": f"{mid} has been restarted {status.restarted_count} times",
224
+ })
225
+ else:
226
+ print(f"[watchdog] {mid} restart failed: {result}")
222
227
  except Exception as e:
223
228
  print(f"[watchdog] {mid} restart error: {e}")
224
229
 
@@ -348,38 +353,134 @@ class HealthMonitor:
348
353
  # ── Incoming event handler ──
349
354
 
350
355
  async def handle_event(self, msg: dict):
351
- """Handle events from Event Hub (module.started / module.stopped)."""
356
+ """Handle events from Kernel restart decisions + health tracking."""
352
357
  event_type = msg.get("event", "")
353
358
  data = msg.get("data", {})
354
359
  module_id = data.get("module_id", "")
355
360
 
361
+ if event_type == "system.ready":
362
+ print("[watchdog] Received system.ready")
363
+ self._system_ready = True
364
+ self._system_ready_event.set()
365
+ return
366
+
356
367
  if not module_id or module_id == "watchdog":
357
368
  return
358
369
 
359
370
  if event_type == "module.started":
360
371
  print(f"[watchdog] Received module.started: {module_id}")
361
- # Trigger immediate discovery to pick up the new module
372
+ self._crash_counts.pop(module_id, None)
362
373
  await self.discover_modules()
363
374
 
364
375
  elif event_type == "module.stopped":
365
376
  print(f"[watchdog] Received module.stopped: {module_id}")
366
- # Remove from tracking — it's gone
367
377
  self.modules.pop(module_id, None)
378
+ await self._handle_module_stopped(module_id, data)
379
+
380
+ elif event_type == "module.exiting":
381
+ action = data.get("action", "none")
382
+ print(f"[watchdog] Received module.exiting: {module_id}, action={action}")
383
+ self._exit_intents[module_id] = action
384
+
385
+ elif event_type == "module.ready":
386
+ graceful = bool(data.get("graceful_shutdown"))
387
+ print(f"[watchdog] Received module.ready: {module_id}, graceful_shutdown={graceful}")
388
+ self._graceful_modules[module_id] = graceful
389
+
390
+ elif event_type == "module.shutdown":
391
+ reason = data.get("reason", "")
392
+ if reason == "system_shutdown":
393
+ print(f"[watchdog] Received system_shutdown signal")
394
+ self._system_shutting_down = True
395
+
396
+ async def _handle_module_stopped(self, module_id: str, data: dict):
397
+ """Restart decision engine — called when module.stopped is received.
398
+
399
+ Priority:
400
+ 1. System shutting down → no restart
401
+ 2. Has exit_intent → follow the declared action (none/restart/restart_delay)
402
+ 3. No intent (crash) → increment crash count, restart up to MAX_RESTARTS
403
+ """
404
+ # Sync graceful_shutdown from Launcher (covers missed module.ready)
405
+ if "graceful_shutdown" in data:
406
+ self._graceful_modules[module_id] = bool(data["graceful_shutdown"])
407
+
408
+ if self._system_shutting_down:
409
+ print(f"[watchdog] {module_id} stopped during shutdown, skipping restart")
410
+ return
411
+
412
+ intent = self._exit_intents.pop(module_id, None)
413
+ if intent is not None:
414
+ if intent == "none":
415
+ print(f"[watchdog] {module_id} exited intentionally (action=none), no restart")
416
+ return
417
+ elif intent == "restart":
418
+ print(f"[watchdog] {module_id} requested restart, restarting now")
419
+ await self._restart_module_by_id(module_id, reason="module_requested")
420
+ return
421
+ elif intent == "restart_delay":
422
+ delay = 5
423
+ print(f"[watchdog] {module_id} requested delayed restart, waiting {delay}s")
424
+ await asyncio.sleep(delay)
425
+ await self._restart_module_by_id(module_id, reason="module_requested_delay")
426
+ return
427
+ else:
428
+ print(f"[watchdog] {module_id} exited with unknown action '{intent}', no restart")
429
+ return
430
+
431
+ # No exit intent → treat as crash
432
+ self._crash_counts[module_id] = self._crash_counts.get(module_id, 0) + 1
433
+ count = self._crash_counts[module_id]
434
+ exit_code = data.get("exit_code", -1)
435
+
436
+ if count <= self.MAX_RESTARTS:
437
+ print(f"[watchdog] {module_id} crashed (exit_code={exit_code}), restarting ({count}/{self.MAX_RESTARTS})")
438
+ await self._restart_module_by_id(module_id, reason="crash")
439
+ else:
440
+ print(f"[watchdog] {module_id} crashed {count} times, giving up")
441
+ await self._publish("watchdog.alert", {
442
+ "module_id": module_id,
443
+ "message": f"{module_id} exceeded {self.MAX_RESTARTS} crash restarts",
444
+ })
445
+
446
+ async def _restart_module_by_id(self, module_id: str, reason: str = "restart"):
447
+ """Restart a module via Launcher RPC by module_id."""
448
+ print(f"[watchdog] Requesting restart for {module_id} (reason={reason})")
449
+ try:
450
+ resp = await self.rpc_call("launcher.restart_module", {
451
+ "name": module_id,
452
+ "reason": reason,
453
+ })
454
+ result = resp.get("result", {})
455
+ if result.get("status") == "restarted":
456
+ print(f"[watchdog] {module_id} restart requested successfully")
457
+ else:
458
+ print(f"[watchdog] {module_id} restart failed: {result}")
459
+ except Exception as e:
460
+ print(f"[watchdog] {module_id} restart error: {e}")
368
461
 
369
462
  # ── Main loop ──
370
463
 
371
464
  async def run(self):
372
- """Main monitoring loop with dynamic intervals per resource state."""
465
+ """Main monitoring loop with dynamic intervals per resource state.
466
+ Waits for system.ready before starting health checks."""
373
467
  self._running = True
374
- print("[watchdog] Monitor started")
375
- last_discover = 0
468
+ print("[watchdog] Monitor started, waiting for system.ready...")
469
+
470
+ # Wait for system.ready (or manual start via API later)
471
+ while self._running and not self._system_ready:
472
+ try:
473
+ await asyncio.wait_for(self._system_ready_event.wait(), timeout=1.0)
474
+ except asyncio.TimeoutError:
475
+ continue
476
+
477
+ if not self._running:
478
+ return
479
+ print("[watchdog] system.ready received, starting health checks")
376
480
 
377
481
  while self._running:
378
- now = time.time()
379
- # Rediscover every 30s
380
- if now - last_discover >= 30:
381
- await self.discover_modules()
382
- last_discover = now
482
+ # Re-discover every cycle to pick up newly started/stopped modules
483
+ await self.discover_modules()
383
484
 
384
485
  if self.modules:
385
486
  tasks = []
@@ -0,0 +1,149 @@
1
+ user:
2
+ phone_number: "" # 当前 AI 使用的号码
3
+ phone_numbers: [] # AI 号码列表,可在配置页面管理
4
+ owners: [] # 主人列表,每项包含 phone/name 等基础信息
5
+ # 示例:
6
+ # phone_numbers:
7
+ # - "13800138000"
8
+ # - "13800138001"
9
+ # owners:
10
+ # - phone: "13900139000"
11
+ # name: "张三"
12
+ # note: "公司CEO"
13
+ # - phone: "13700137000"
14
+ # name: "李四"
15
+
16
+ server:
17
+ host: "0.0.0.0"
18
+ port: 18766
19
+ ssl: true
20
+
21
+ bluetooth:
22
+ auto_connect: true
23
+ reconnect_interval: 5
24
+ reconnect_max_interval: 60
25
+ device_address: ""
26
+
27
+ audio:
28
+ sample_rate: 16000
29
+ channels: 1
30
+ format: "s16le"
31
+ record_calls: true
32
+
33
+ llm:
34
+ active_provider: "openai"
35
+ providers:
36
+ openai:
37
+ base_url: "https://api.openai.com/v1"
38
+ api_key: ""
39
+ model: "gpt-4o"
40
+ temperature: 0.7
41
+ max_tokens: 1024
42
+ claude:
43
+ base_url: "https://api.anthropic.com/v1"
44
+ api_key: ""
45
+ model: "claude-sonnet-4-20250514"
46
+ temperature: 0.7
47
+ max_tokens: 1024
48
+ gemini:
49
+ base_url: "https://generativelanguage.googleapis.com/v1beta"
50
+ api_key: ""
51
+ model: "gemini-2.0-flash"
52
+ temperature: 0.7
53
+ max_tokens: 1024
54
+
55
+ asr:
56
+ provider: "whisper"
57
+ whisper:
58
+ base_url: "https://api.openai.com/v1"
59
+ api_key: ""
60
+ model: "whisper-1"
61
+ language: "zh"
62
+ xunfei:
63
+ app_id: ""
64
+ api_key: ""
65
+ api_secret: ""
66
+ volcengine:
67
+ app_id: ""
68
+ access_token: ""
69
+ resource_id: "volc.bigasr.sauc.duration"
70
+ ws_url: "wss://openspeech.bytedance.com/api/v3/sauc/bigmodel_async"
71
+ tencent:
72
+ app_id: ""
73
+ secret_id: ""
74
+ secret_key: ""
75
+ engine_model_type: "16k_zh_large"
76
+
77
+ tts:
78
+ provider: "edge-tts"
79
+ edge_tts:
80
+ voice: "zh-CN-XiaoxiaoNeural"
81
+ rate: "+0%"
82
+ volume: "+0%"
83
+ volcengine:
84
+ app_id: ""
85
+ access_token: ""
86
+ cluster: "volcano_tts"
87
+ voice_type: "BV001_streaming"
88
+ speed_ratio: 1.0
89
+ volume_ratio: 1.0
90
+ pitch_ratio: 1.0
91
+ encoding: "mp3"
92
+ sample_rate: 24000
93
+ tencent:
94
+ app_id: ""
95
+ secret_id: ""
96
+ secret_key: ""
97
+ voice_type: 101001
98
+ codec: "pcm"
99
+ sample_rate: 16000
100
+ speed: 0
101
+ volume: 0
102
+ azure:
103
+ api_key: ""
104
+ region: "eastasia"
105
+ voice: "zh-CN-XiaoxiaoNeural"
106
+ rate: "1.0"
107
+ volume: "100"
108
+ openai:
109
+ api_key: ""
110
+ base_url: "https://api.openai.com/v1"
111
+ model: "tts-1"
112
+ voice: "alloy"
113
+ speed: 1.0
114
+ aliyun:
115
+ access_key_id: ""
116
+ access_key_secret: ""
117
+ app_key: ""
118
+ voice: "zhixiaobai"
119
+ sample_rate: 16000
120
+ speech_rate: 0
121
+ volume: 50
122
+ local:
123
+ engine: "sherpa-onnx"
124
+ model_path: ""
125
+ voice: ""
126
+ sample_rate: 16000
127
+
128
+ vad:
129
+ mode: 3
130
+ energy_threshold: 300
131
+ silence_threshold_ms: 800
132
+ min_speech_ms: 250
133
+
134
+ call:
135
+ max_duration_seconds: 300
136
+ no_response_timeout: 15
137
+ no_response_max_retries: 3
138
+ incoming_confirm_timeout: 15
139
+ incoming_default_action: "reject"
140
+ opening_message: ""
141
+
142
+ webhook:
143
+ default_url: ""
144
+ retry_count: 3
145
+ retry_delays: [1, 5, 30]
146
+ timeout: 10
147
+
148
+ data:
149
+ base_dir: "./data"