openvoiceui 1.0.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 (185) hide show
  1. package/.env.example +104 -0
  2. package/Dockerfile +30 -0
  3. package/LICENSE +21 -0
  4. package/README.md +638 -0
  5. package/SETUP.md +360 -0
  6. package/app.py +232 -0
  7. package/auto-approve-devices.js +111 -0
  8. package/cli/index.js +372 -0
  9. package/config/__init__.py +4 -0
  10. package/config/default.yaml +43 -0
  11. package/config/flags.yaml +67 -0
  12. package/config/loader.py +203 -0
  13. package/config/providers.yaml +71 -0
  14. package/config/speech_normalization.yaml +182 -0
  15. package/config/theme.json +4 -0
  16. package/data/greetings.json +25 -0
  17. package/default-pages/ai-image-creator.html +915 -0
  18. package/default-pages/bulk-image-uploader.html +492 -0
  19. package/default-pages/desktop.html +2865 -0
  20. package/default-pages/file-explorer.html +854 -0
  21. package/default-pages/interactive-map.html +655 -0
  22. package/default-pages/style-guide.html +1005 -0
  23. package/default-pages/website-setup.html +1623 -0
  24. package/deploy/openclaw/Dockerfile +46 -0
  25. package/deploy/openvoiceui.service +30 -0
  26. package/deploy/setup-nginx.sh +50 -0
  27. package/deploy/setup-sudo.sh +306 -0
  28. package/deploy/skill-runner/Dockerfile +19 -0
  29. package/deploy/skill-runner/requirements.txt +14 -0
  30. package/deploy/skill-runner/server.py +269 -0
  31. package/deploy/supertonic/Dockerfile +22 -0
  32. package/deploy/supertonic/server.py +79 -0
  33. package/docker-compose.pinokio.yml +11 -0
  34. package/docker-compose.yml +59 -0
  35. package/greetings.json +25 -0
  36. package/index.html +65 -0
  37. package/inject-device-identity.js +142 -0
  38. package/package.json +82 -0
  39. package/profiles/default.json +114 -0
  40. package/profiles/manager.py +354 -0
  41. package/profiles/schema.json +337 -0
  42. package/prompts/voice-system-prompt.md +149 -0
  43. package/providers/__init__.py +39 -0
  44. package/providers/base.py +63 -0
  45. package/providers/llm/__init__.py +12 -0
  46. package/providers/llm/base.py +71 -0
  47. package/providers/llm/clawdbot_provider.py +112 -0
  48. package/providers/llm/zai_provider.py +115 -0
  49. package/providers/registry.py +320 -0
  50. package/providers/stt/__init__.py +12 -0
  51. package/providers/stt/base.py +58 -0
  52. package/providers/stt/webspeech_provider.py +49 -0
  53. package/providers/stt/whisper_provider.py +100 -0
  54. package/providers/tts/__init__.py +20 -0
  55. package/providers/tts/base.py +91 -0
  56. package/providers/tts/groq_provider.py +74 -0
  57. package/providers/tts/supertonic_provider.py +72 -0
  58. package/requirements.txt +38 -0
  59. package/routes/__init__.py +10 -0
  60. package/routes/admin.py +515 -0
  61. package/routes/canvas.py +1315 -0
  62. package/routes/chat.py +51 -0
  63. package/routes/conversation.py +2158 -0
  64. package/routes/elevenlabs_hybrid.py +306 -0
  65. package/routes/greetings.py +98 -0
  66. package/routes/icons.py +279 -0
  67. package/routes/image_gen.py +364 -0
  68. package/routes/instructions.py +190 -0
  69. package/routes/music.py +838 -0
  70. package/routes/onboarding.py +43 -0
  71. package/routes/pi.py +62 -0
  72. package/routes/profiles.py +215 -0
  73. package/routes/report_issue.py +68 -0
  74. package/routes/static_files.py +533 -0
  75. package/routes/suno.py +664 -0
  76. package/routes/theme.py +81 -0
  77. package/routes/transcripts.py +199 -0
  78. package/routes/vision.py +348 -0
  79. package/routes/workspace.py +288 -0
  80. package/server.py +1510 -0
  81. package/services/__init__.py +1 -0
  82. package/services/auth.py +143 -0
  83. package/services/canvas_versioning.py +239 -0
  84. package/services/db_pool.py +107 -0
  85. package/services/gateway.py +16 -0
  86. package/services/gateway_manager.py +333 -0
  87. package/services/gateways/__init__.py +12 -0
  88. package/services/gateways/base.py +110 -0
  89. package/services/gateways/compat.py +264 -0
  90. package/services/gateways/openclaw.py +1134 -0
  91. package/services/health.py +100 -0
  92. package/services/memory_client.py +455 -0
  93. package/services/paths.py +26 -0
  94. package/services/speech_normalizer.py +285 -0
  95. package/services/tts.py +270 -0
  96. package/setup-config.js +262 -0
  97. package/sounds/air_horn.mp3 +0 -0
  98. package/sounds/bruh.mp3 +0 -0
  99. package/sounds/crowd_cheer.mp3 +0 -0
  100. package/sounds/gunshot.mp3 +0 -0
  101. package/sounds/impact.mp3 +0 -0
  102. package/sounds/lets_go.mp3 +0 -0
  103. package/sounds/record_stop.mp3 +0 -0
  104. package/sounds/rewind.mp3 +0 -0
  105. package/sounds/sad_trombone.mp3 +0 -0
  106. package/sounds/scratch_long.mp3 +0 -0
  107. package/sounds/yeah.mp3 +0 -0
  108. package/src/adapters/ClawdBotAdapter.js +264 -0
  109. package/src/adapters/_template.js +133 -0
  110. package/src/adapters/elevenlabs-classic.js +841 -0
  111. package/src/adapters/elevenlabs-hybrid.js +812 -0
  112. package/src/adapters/hume-evi.js +676 -0
  113. package/src/admin.html +1339 -0
  114. package/src/app.js +8802 -0
  115. package/src/core/Config.js +173 -0
  116. package/src/core/EmotionEngine.js +307 -0
  117. package/src/core/EventBridge.js +180 -0
  118. package/src/core/EventBus.js +117 -0
  119. package/src/core/VoiceSession.js +607 -0
  120. package/src/face/BaseFace.js +259 -0
  121. package/src/face/EyeFace.js +208 -0
  122. package/src/face/HaloSmokeFace.js +509 -0
  123. package/src/face/manifest.json +27 -0
  124. package/src/face/previews/eyes.svg +16 -0
  125. package/src/face/previews/orb.svg +29 -0
  126. package/src/features/MusicPlayer.js +620 -0
  127. package/src/features/Soundboard.js +128 -0
  128. package/src/providers/DeepgramSTT.js +472 -0
  129. package/src/providers/DeepgramStreamingSTT.js +766 -0
  130. package/src/providers/GroqSTT.js +559 -0
  131. package/src/providers/TTSPlayer.js +323 -0
  132. package/src/providers/WebSpeechSTT.js +479 -0
  133. package/src/providers/tts/BaseTTSProvider.js +81 -0
  134. package/src/providers/tts/HumeProvider.js +77 -0
  135. package/src/providers/tts/SupertonicProvider.js +174 -0
  136. package/src/providers/tts/index.js +140 -0
  137. package/src/shell/adapter-registry.js +154 -0
  138. package/src/shell/caller-bridge.js +35 -0
  139. package/src/shell/camera-bridge.js +28 -0
  140. package/src/shell/canvas-bridge.js +32 -0
  141. package/src/shell/commercial-bridge.js +44 -0
  142. package/src/shell/face-bridge.js +44 -0
  143. package/src/shell/music-bridge.js +60 -0
  144. package/src/shell/orchestrator.js +233 -0
  145. package/src/shell/profile-discovery.js +303 -0
  146. package/src/shell/sounds-bridge.js +28 -0
  147. package/src/shell/transcript-bridge.js +61 -0
  148. package/src/shell/waveform-bridge.js +33 -0
  149. package/src/styles/base.css +2862 -0
  150. package/src/styles/face.css +417 -0
  151. package/src/styles/pi-overrides.css +89 -0
  152. package/src/styles/theme-dark.css +67 -0
  153. package/src/test-tts.html +175 -0
  154. package/src/ui/AppShell.js +544 -0
  155. package/src/ui/ProfileSwitcher.js +228 -0
  156. package/src/ui/SessionControl.js +240 -0
  157. package/src/ui/face/FacePicker.js +195 -0
  158. package/src/ui/face/FaceRenderer.js +309 -0
  159. package/src/ui/settings/PlaylistEditor.js +366 -0
  160. package/src/ui/settings/SettingsPanel.css +684 -0
  161. package/src/ui/settings/SettingsPanel.js +419 -0
  162. package/src/ui/settings/TTSVoicePreview.js +210 -0
  163. package/src/ui/themes/ThemeManager.js +213 -0
  164. package/src/ui/visualizers/BaseVisualizer.js +29 -0
  165. package/src/ui/visualizers/PartyFXVisualizer.css +291 -0
  166. package/src/ui/visualizers/PartyFXVisualizer.js +637 -0
  167. package/static/emulators/jsdos/js-dos.css +1 -0
  168. package/static/emulators/jsdos/js-dos.js +22 -0
  169. package/static/favicon.svg +55 -0
  170. package/static/icons/apple-touch-icon.png +0 -0
  171. package/static/icons/favicon-32.png +0 -0
  172. package/static/icons/icon-192.png +0 -0
  173. package/static/icons/icon-512.png +0 -0
  174. package/static/install.html +449 -0
  175. package/static/manifest.json +26 -0
  176. package/static/sw.js +21 -0
  177. package/tts_providers/__init__.py +136 -0
  178. package/tts_providers/base_provider.py +319 -0
  179. package/tts_providers/groq_provider.py +155 -0
  180. package/tts_providers/hume_provider.py +226 -0
  181. package/tts_providers/providers_config.json +119 -0
  182. package/tts_providers/qwen3_provider.py +371 -0
  183. package/tts_providers/resemble_provider.py +315 -0
  184. package/tts_providers/supertonic_provider.py +557 -0
  185. package/tts_providers/supertonic_tts.py +399 -0
@@ -0,0 +1,264 @@
1
+ """
2
+ OpenClaw protocol compatibility layer.
3
+
4
+ Centralizes all version-sensitive constants, event names, and protocol
5
+ details so that upgrading OpenClaw requires changing ONE file instead of
6
+ hunting through 4+ modules.
7
+
8
+ When OpenClaw bumps protocol versions or renames events, update the
9
+ mappings here and all consumers benefit automatically.
10
+ """
11
+
12
+ import re
13
+ import logging
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ # ---------------------------------------------------------------------------
18
+ # Protocol version negotiation
19
+ # ---------------------------------------------------------------------------
20
+
21
+ # Supported protocol versions — accept a range so minor bumps don't break us.
22
+ # OpenClaw negotiates the highest mutually supported version.
23
+ PROTOCOL_MIN = 3
24
+ PROTOCOL_MAX = 5 # forward-compatible — OpenClaw ignores unsupported maxes
25
+
26
+ # Version this code was tested against (for warning logs).
27
+ OPENCLAW_TESTED_VERSION = "2026.3.13"
28
+ OPENCLAW_MIN_VERSION = "2026.3.1"
29
+
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # Event name aliases — maps canonical names to sets of accepted alternatives.
33
+ # When OpenClaw renames an event, add the new name here.
34
+ # ---------------------------------------------------------------------------
35
+
36
+ # Top-level event types (data['event'] field)
37
+ _EVENT_ALIASES = {
38
+ 'agent': frozenset({'agent', 'run', 'agent.run'}),
39
+ 'chat': frozenset({'chat', 'conversation', 'chat.state'}),
40
+ 'connect.challenge': frozenset({'connect.challenge', 'auth.challenge'}),
41
+ }
42
+
43
+ # Agent stream types (payload['stream'] field)
44
+ _STREAM_ALIASES = {
45
+ 'assistant': frozenset({'assistant', 'text', 'message'}),
46
+ 'tool': frozenset({'tool', 'tool_use', 'tool-use'}),
47
+ 'lifecycle': frozenset({'lifecycle', 'status', 'run.status'}),
48
+ }
49
+
50
+ # Chat state values (payload['state'] field)
51
+ _STATE_ALIASES = {
52
+ 'final': frozenset({'final', 'complete', 'done', 'finished'}),
53
+ 'aborted': frozenset({'aborted', 'cancelled', 'canceled'}),
54
+ 'error': frozenset({'error', 'failed'}),
55
+ 'delta': frozenset({'delta', 'streaming', 'partial'}),
56
+ }
57
+
58
+ # Subagent tool names — detected by pattern match, not exact strings
59
+ _SUBAGENT_SPAWN_PATTERNS = re.compile(
60
+ r'(sessions?[_-]spawn|spawn[_-]?sub[_-]?agent|sub[_-]?agent\.spawn|agent\.delegate)',
61
+ re.IGNORECASE
62
+ )
63
+
64
+ _SUBAGENT_SEND_PATTERNS = re.compile(
65
+ r'(agent[_-]send|agent\.send|delegate[_-]message)',
66
+ re.IGNORECASE
67
+ )
68
+
69
+ # Stale response model markers — gateway-injected responses to discard
70
+ _STALE_MODEL_MARKERS = frozenset({
71
+ 'gateway-injected',
72
+ 'system-generated',
73
+ 'injected',
74
+ 'replay',
75
+ })
76
+
77
+ # System response patterns to suppress (never surface to user)
78
+ _SYSTEM_RESPONSE_RE = re.compile(
79
+ r'^\s*(HEARTBEAT[_ ]?OK|heartbeat[_ ]?ok|ACK|PONG|system[_-]ok)\s*$',
80
+ re.IGNORECASE
81
+ )
82
+
83
+
84
+ # ---------------------------------------------------------------------------
85
+ # Lookup helpers
86
+ # ---------------------------------------------------------------------------
87
+
88
+ def match_event(event_name: str) -> str | None:
89
+ """Map an event name to its canonical form, or None if unknown.
90
+
91
+ >>> match_event('agent')
92
+ 'agent'
93
+ >>> match_event('run')
94
+ 'agent'
95
+ >>> match_event('unknown')
96
+ None
97
+ """
98
+ for canonical, aliases in _EVENT_ALIASES.items():
99
+ if event_name in aliases:
100
+ return canonical
101
+ return None
102
+
103
+
104
+ def match_stream(stream_name: str) -> str | None:
105
+ """Map a stream name to its canonical form."""
106
+ for canonical, aliases in _STREAM_ALIASES.items():
107
+ if stream_name in aliases:
108
+ return canonical
109
+ return None
110
+
111
+
112
+ def match_state(state_name: str) -> str | None:
113
+ """Map a chat state to its canonical form."""
114
+ for canonical, aliases in _STATE_ALIASES.items():
115
+ if state_name in aliases:
116
+ return canonical
117
+ return None
118
+
119
+
120
+ def is_noise_event(event_name: str) -> bool:
121
+ """Return True for events that should be silently dropped."""
122
+ return event_name in ('health', 'tick', 'presence', 'ping', 'keepalive')
123
+
124
+
125
+ def is_subagent_spawn_tool(tool_name: str) -> bool:
126
+ """Return True if this tool name indicates a subagent spawn."""
127
+ return bool(_SUBAGENT_SPAWN_PATTERNS.search(tool_name))
128
+
129
+
130
+ def is_subagent_send_tool(tool_name: str) -> bool:
131
+ """Return True if this tool name indicates an agent-send."""
132
+ return bool(_SUBAGENT_SEND_PATTERNS.search(tool_name))
133
+
134
+
135
+ def is_subagent_tool(tool_name: str) -> bool:
136
+ """Return True if this tool name is any subagent-related tool."""
137
+ return is_subagent_spawn_tool(tool_name) or is_subagent_send_tool(tool_name)
138
+
139
+
140
+ def is_stale_response(model: str, total_tokens: int) -> bool:
141
+ """Return True if this looks like a gateway-injected stale replay."""
142
+ if model in _STALE_MODEL_MARKERS and total_tokens == 0:
143
+ return True
144
+ # Also check for explicit isStale flag (future OpenClaw versions)
145
+ return False
146
+
147
+
148
+ def is_stale_response_ex(model: str, total_tokens: int, payload: dict) -> bool:
149
+ """Extended stale check — includes payload-level flags."""
150
+ if payload.get('isStale') or payload.get('injected'):
151
+ return True
152
+ return is_stale_response(model, total_tokens)
153
+
154
+
155
+ def is_system_response(text: str) -> bool:
156
+ """Return True if this text is a system response that should be suppressed."""
157
+ return bool(_SYSTEM_RESPONSE_RE.match(text))
158
+
159
+
160
+ def is_subagent_session_key(session_key: str) -> bool:
161
+ """Return True if this session key belongs to a subagent."""
162
+ # Accept multiple possible prefixes
163
+ return any(prefix in session_key for prefix in ('subagent:', 'sub:', 'child:'))
164
+
165
+
166
+ def extract_server_version(hello_result: dict) -> str | None:
167
+ """Extract server version from hello response, trying multiple paths."""
168
+ if not hello_result:
169
+ return None
170
+ return (
171
+ hello_result.get('serverVersion')
172
+ or hello_result.get('version')
173
+ or (hello_result.get('server', {}) or {}).get('version')
174
+ or (hello_result.get('gateway', {}) or {}).get('version')
175
+ )
176
+
177
+
178
+ def extract_run_id(data: dict) -> str | None:
179
+ """Extract runId from an ACK response, trying multiple paths."""
180
+ result = data.get('result') or data.get('payload') or {}
181
+ return (
182
+ result.get('runId')
183
+ or data.get('runId')
184
+ or result.get('run_id')
185
+ or data.get('run_id')
186
+ )
187
+
188
+
189
+ def extract_text_content(content) -> str:
190
+ """Extract text from message content (string or Anthropic-style list).
191
+
192
+ Handles:
193
+ - Plain string
194
+ - List of {type: "text", text: "..."} blocks
195
+ - List of {type: "markdown", text: "..."} blocks
196
+ - Mixed content blocks (extracts all text-like entries)
197
+ """
198
+ if isinstance(content, str):
199
+ return content
200
+ if isinstance(content, list):
201
+ text_parts = []
202
+ for item in content:
203
+ if isinstance(item, str):
204
+ text_parts.append(item)
205
+ elif isinstance(item, dict):
206
+ t = item.get('type', '')
207
+ text = item.get('text', '')
208
+ if t in ('text', 'markdown', 'plain') and text.strip():
209
+ text_parts.append(text)
210
+ elif not t and text.strip():
211
+ # No type field — assume text
212
+ text_parts.append(text)
213
+ return ' '.join(text_parts)
214
+ return ''
215
+
216
+
217
+ def build_connect_params(
218
+ auth_token: str,
219
+ client_id: str = "cli",
220
+ client_mode: str = "cli",
221
+ platform: str = "linux",
222
+ user_agent: str = "openvoice-ui/1.0.0",
223
+ scopes: list | None = None,
224
+ caps: list | None = None,
225
+ device_block: dict | None = None,
226
+ ) -> dict:
227
+ """Build the params dict for a connect request.
228
+
229
+ Centralizes the handshake params shape so all callers stay in sync.
230
+ """
231
+ if scopes is None:
232
+ scopes = ["operator.read", "operator.write"]
233
+ if caps is None:
234
+ caps = ["tool-events"]
235
+
236
+ params = {
237
+ "minProtocol": PROTOCOL_MIN,
238
+ "maxProtocol": PROTOCOL_MAX,
239
+ "client": {
240
+ "id": client_id,
241
+ "version": "1.0.0",
242
+ "platform": platform,
243
+ "mode": client_mode,
244
+ },
245
+ "role": "operator",
246
+ "scopes": scopes,
247
+ "caps": caps,
248
+ "commands": [],
249
+ "permissions": {},
250
+ "auth": {"token": auth_token},
251
+ "locale": "en-US",
252
+ "userAgent": user_agent,
253
+ }
254
+ if device_block:
255
+ params["device"] = device_block
256
+ return params
257
+
258
+
259
+ def is_challenge_event(data: dict) -> bool:
260
+ """Return True if this message is a connect challenge (current or future format)."""
261
+ if data.get('type') != 'event':
262
+ return False
263
+ evt = data.get('event', '')
264
+ return evt in _EVENT_ALIASES.get('connect.challenge', {evt})