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.
- package/.env.example +104 -0
- package/Dockerfile +30 -0
- package/LICENSE +21 -0
- package/README.md +638 -0
- package/SETUP.md +360 -0
- package/app.py +232 -0
- package/auto-approve-devices.js +111 -0
- package/cli/index.js +372 -0
- package/config/__init__.py +4 -0
- package/config/default.yaml +43 -0
- package/config/flags.yaml +67 -0
- package/config/loader.py +203 -0
- package/config/providers.yaml +71 -0
- package/config/speech_normalization.yaml +182 -0
- package/config/theme.json +4 -0
- package/data/greetings.json +25 -0
- package/default-pages/ai-image-creator.html +915 -0
- package/default-pages/bulk-image-uploader.html +492 -0
- package/default-pages/desktop.html +2865 -0
- package/default-pages/file-explorer.html +854 -0
- package/default-pages/interactive-map.html +655 -0
- package/default-pages/style-guide.html +1005 -0
- package/default-pages/website-setup.html +1623 -0
- package/deploy/openclaw/Dockerfile +46 -0
- package/deploy/openvoiceui.service +30 -0
- package/deploy/setup-nginx.sh +50 -0
- package/deploy/setup-sudo.sh +306 -0
- package/deploy/skill-runner/Dockerfile +19 -0
- package/deploy/skill-runner/requirements.txt +14 -0
- package/deploy/skill-runner/server.py +269 -0
- package/deploy/supertonic/Dockerfile +22 -0
- package/deploy/supertonic/server.py +79 -0
- package/docker-compose.pinokio.yml +11 -0
- package/docker-compose.yml +59 -0
- package/greetings.json +25 -0
- package/index.html +65 -0
- package/inject-device-identity.js +142 -0
- package/package.json +82 -0
- package/profiles/default.json +114 -0
- package/profiles/manager.py +354 -0
- package/profiles/schema.json +337 -0
- package/prompts/voice-system-prompt.md +149 -0
- package/providers/__init__.py +39 -0
- package/providers/base.py +63 -0
- package/providers/llm/__init__.py +12 -0
- package/providers/llm/base.py +71 -0
- package/providers/llm/clawdbot_provider.py +112 -0
- package/providers/llm/zai_provider.py +115 -0
- package/providers/registry.py +320 -0
- package/providers/stt/__init__.py +12 -0
- package/providers/stt/base.py +58 -0
- package/providers/stt/webspeech_provider.py +49 -0
- package/providers/stt/whisper_provider.py +100 -0
- package/providers/tts/__init__.py +20 -0
- package/providers/tts/base.py +91 -0
- package/providers/tts/groq_provider.py +74 -0
- package/providers/tts/supertonic_provider.py +72 -0
- package/requirements.txt +38 -0
- package/routes/__init__.py +10 -0
- package/routes/admin.py +515 -0
- package/routes/canvas.py +1315 -0
- package/routes/chat.py +51 -0
- package/routes/conversation.py +2158 -0
- package/routes/elevenlabs_hybrid.py +306 -0
- package/routes/greetings.py +98 -0
- package/routes/icons.py +279 -0
- package/routes/image_gen.py +364 -0
- package/routes/instructions.py +190 -0
- package/routes/music.py +838 -0
- package/routes/onboarding.py +43 -0
- package/routes/pi.py +62 -0
- package/routes/profiles.py +215 -0
- package/routes/report_issue.py +68 -0
- package/routes/static_files.py +533 -0
- package/routes/suno.py +664 -0
- package/routes/theme.py +81 -0
- package/routes/transcripts.py +199 -0
- package/routes/vision.py +348 -0
- package/routes/workspace.py +288 -0
- package/server.py +1510 -0
- package/services/__init__.py +1 -0
- package/services/auth.py +143 -0
- package/services/canvas_versioning.py +239 -0
- package/services/db_pool.py +107 -0
- package/services/gateway.py +16 -0
- package/services/gateway_manager.py +333 -0
- package/services/gateways/__init__.py +12 -0
- package/services/gateways/base.py +110 -0
- package/services/gateways/compat.py +264 -0
- package/services/gateways/openclaw.py +1134 -0
- package/services/health.py +100 -0
- package/services/memory_client.py +455 -0
- package/services/paths.py +26 -0
- package/services/speech_normalizer.py +285 -0
- package/services/tts.py +270 -0
- package/setup-config.js +262 -0
- package/sounds/air_horn.mp3 +0 -0
- package/sounds/bruh.mp3 +0 -0
- package/sounds/crowd_cheer.mp3 +0 -0
- package/sounds/gunshot.mp3 +0 -0
- package/sounds/impact.mp3 +0 -0
- package/sounds/lets_go.mp3 +0 -0
- package/sounds/record_stop.mp3 +0 -0
- package/sounds/rewind.mp3 +0 -0
- package/sounds/sad_trombone.mp3 +0 -0
- package/sounds/scratch_long.mp3 +0 -0
- package/sounds/yeah.mp3 +0 -0
- package/src/adapters/ClawdBotAdapter.js +264 -0
- package/src/adapters/_template.js +133 -0
- package/src/adapters/elevenlabs-classic.js +841 -0
- package/src/adapters/elevenlabs-hybrid.js +812 -0
- package/src/adapters/hume-evi.js +676 -0
- package/src/admin.html +1339 -0
- package/src/app.js +8802 -0
- package/src/core/Config.js +173 -0
- package/src/core/EmotionEngine.js +307 -0
- package/src/core/EventBridge.js +180 -0
- package/src/core/EventBus.js +117 -0
- package/src/core/VoiceSession.js +607 -0
- package/src/face/BaseFace.js +259 -0
- package/src/face/EyeFace.js +208 -0
- package/src/face/HaloSmokeFace.js +509 -0
- package/src/face/manifest.json +27 -0
- package/src/face/previews/eyes.svg +16 -0
- package/src/face/previews/orb.svg +29 -0
- package/src/features/MusicPlayer.js +620 -0
- package/src/features/Soundboard.js +128 -0
- package/src/providers/DeepgramSTT.js +472 -0
- package/src/providers/DeepgramStreamingSTT.js +766 -0
- package/src/providers/GroqSTT.js +559 -0
- package/src/providers/TTSPlayer.js +323 -0
- package/src/providers/WebSpeechSTT.js +479 -0
- package/src/providers/tts/BaseTTSProvider.js +81 -0
- package/src/providers/tts/HumeProvider.js +77 -0
- package/src/providers/tts/SupertonicProvider.js +174 -0
- package/src/providers/tts/index.js +140 -0
- package/src/shell/adapter-registry.js +154 -0
- package/src/shell/caller-bridge.js +35 -0
- package/src/shell/camera-bridge.js +28 -0
- package/src/shell/canvas-bridge.js +32 -0
- package/src/shell/commercial-bridge.js +44 -0
- package/src/shell/face-bridge.js +44 -0
- package/src/shell/music-bridge.js +60 -0
- package/src/shell/orchestrator.js +233 -0
- package/src/shell/profile-discovery.js +303 -0
- package/src/shell/sounds-bridge.js +28 -0
- package/src/shell/transcript-bridge.js +61 -0
- package/src/shell/waveform-bridge.js +33 -0
- package/src/styles/base.css +2862 -0
- package/src/styles/face.css +417 -0
- package/src/styles/pi-overrides.css +89 -0
- package/src/styles/theme-dark.css +67 -0
- package/src/test-tts.html +175 -0
- package/src/ui/AppShell.js +544 -0
- package/src/ui/ProfileSwitcher.js +228 -0
- package/src/ui/SessionControl.js +240 -0
- package/src/ui/face/FacePicker.js +195 -0
- package/src/ui/face/FaceRenderer.js +309 -0
- package/src/ui/settings/PlaylistEditor.js +366 -0
- package/src/ui/settings/SettingsPanel.css +684 -0
- package/src/ui/settings/SettingsPanel.js +419 -0
- package/src/ui/settings/TTSVoicePreview.js +210 -0
- package/src/ui/themes/ThemeManager.js +213 -0
- package/src/ui/visualizers/BaseVisualizer.js +29 -0
- package/src/ui/visualizers/PartyFXVisualizer.css +291 -0
- package/src/ui/visualizers/PartyFXVisualizer.js +637 -0
- package/static/emulators/jsdos/js-dos.css +1 -0
- package/static/emulators/jsdos/js-dos.js +22 -0
- package/static/favicon.svg +55 -0
- package/static/icons/apple-touch-icon.png +0 -0
- package/static/icons/favicon-32.png +0 -0
- package/static/icons/icon-192.png +0 -0
- package/static/icons/icon-512.png +0 -0
- package/static/install.html +449 -0
- package/static/manifest.json +26 -0
- package/static/sw.js +21 -0
- package/tts_providers/__init__.py +136 -0
- package/tts_providers/base_provider.py +319 -0
- package/tts_providers/groq_provider.py +155 -0
- package/tts_providers/hume_provider.py +226 -0
- package/tts_providers/providers_config.json +119 -0
- package/tts_providers/qwen3_provider.py +371 -0
- package/tts_providers/resemble_provider.py +315 -0
- package/tts_providers/supertonic_provider.py +557 -0
- package/tts_providers/supertonic_tts.py +399 -0
package/setup-config.js
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// setup-config.js — Generates all config files for OpenVoiceUI Pinokio install.
|
|
3
|
+
// Reads API keys from PINOKIO_* environment variables (set by install.js env: block).
|
|
4
|
+
// This avoids the broken json.set / pinokio-input.json flow entirely.
|
|
5
|
+
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const crypto = require("crypto");
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Helpers
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
function getKey(envName) {
|
|
15
|
+
// install.js passes keys as PINOKIO_<KEY_NAME> via env
|
|
16
|
+
const v = process.env["PINOKIO_" + envName] || "";
|
|
17
|
+
if (!v || v === "undefined" || v === "null" || v.startsWith("{{")) return "";
|
|
18
|
+
return v.trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const PORT = getKey("PORT") || "5001";
|
|
22
|
+
const token = crypto.randomBytes(24).toString("hex");
|
|
23
|
+
const secret = crypto.randomBytes(32).toString("hex");
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// 1. Write openclaw.json (nested gateway/agents format for v2026.3.2+)
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
// Pick default model based on which keys the user provided
|
|
30
|
+
let defaultModel = "groq/llama-3.3-70b-versatile"; // fallback — Groq is required
|
|
31
|
+
if (getKey("ZAI_API_KEY")) defaultModel = "z-ai/glm-4.7";
|
|
32
|
+
if (getKey("ANTHROPIC_API_KEY")) defaultModel = "anthropic/claude-sonnet-4-5";
|
|
33
|
+
if (getKey("OPENAI_API_KEY")) defaultModel = "openai/gpt-4o";
|
|
34
|
+
// Z.AI wins if multiple are set (best value)
|
|
35
|
+
if (getKey("ZAI_API_KEY")) defaultModel = "z-ai/glm-4.7";
|
|
36
|
+
|
|
37
|
+
const openclawConfig = {
|
|
38
|
+
gateway: {
|
|
39
|
+
mode: "local",
|
|
40
|
+
port: 18791,
|
|
41
|
+
bind: "lan",
|
|
42
|
+
auth: { mode: "token", token: token },
|
|
43
|
+
trustedProxies: ["127.0.0.1", "172.16.0.0/12", "10.0.0.0/8"],
|
|
44
|
+
controlUi: {
|
|
45
|
+
allowInsecureAuth: true,
|
|
46
|
+
dangerouslyDisableDeviceAuth: true,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
agents: {
|
|
50
|
+
defaults: {
|
|
51
|
+
model: defaultModel,
|
|
52
|
+
thinkingDefault: "off",
|
|
53
|
+
blockStreamingDefault: "on",
|
|
54
|
+
blockStreamingBreak: "text_end",
|
|
55
|
+
timeoutSeconds: 300,
|
|
56
|
+
},
|
|
57
|
+
list: [{ id: "main", default: true, workspace: "/root/.openclaw/workspace" }],
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
fs.mkdirSync("openclaw-data/workspace", { recursive: true });
|
|
62
|
+
fs.writeFileSync(
|
|
63
|
+
"openclaw-data/openclaw.json",
|
|
64
|
+
JSON.stringify(openclawConfig, null, 2) + "\n"
|
|
65
|
+
);
|
|
66
|
+
console.log(" Wrote openclaw-data/openclaw.json");
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// 2. Write .env
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
const envLines = [
|
|
73
|
+
"# OpenVoiceUI — generated by Pinokio installer",
|
|
74
|
+
`PORT=${PORT}`,
|
|
75
|
+
"DOMAIN=localhost",
|
|
76
|
+
`SECRET_KEY=${secret}`,
|
|
77
|
+
"",
|
|
78
|
+
"# OpenClaw Gateway",
|
|
79
|
+
"CLAWDBOT_GATEWAY_URL=ws://127.0.0.1:18791",
|
|
80
|
+
`CLAWDBOT_AUTH_TOKEN=${token}`,
|
|
81
|
+
"GATEWAY_SESSION_KEY=voice-main-1",
|
|
82
|
+
"",
|
|
83
|
+
"# AI Provider Keys",
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
const envKeyList = [
|
|
87
|
+
"ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY",
|
|
88
|
+
"OPENROUTER_API_KEY", "MISTRAL_API_KEY", "XAI_API_KEY", "ZAI_API_KEY",
|
|
89
|
+
"CEREBRAS_API_KEY", "TOGETHER_API_KEY", "HF_TOKEN",
|
|
90
|
+
"MOONSHOT_API_KEY", "KIMI_API_KEY", "MINIMAX_API_KEY", "QIANFAN_API_KEY",
|
|
91
|
+
"MODELSTUDIO_API_KEY", "XIAOMI_API_KEY", "VOLCANO_ENGINE_API_KEY",
|
|
92
|
+
"BYTEPLUS_API_KEY", "SYNTHETIC_API_KEY", "VENICE_API_KEY",
|
|
93
|
+
"OPENCODE_ZEN_API_KEY", "KILOCODE_API_KEY", "AI_GATEWAY_API_KEY",
|
|
94
|
+
"CLOUDFLARE_AI_GATEWAY_API_KEY", "LITELLM_API_KEY", "SUNO_API_KEY",
|
|
95
|
+
"RESEMBLE_API_KEY", "RESEMBLE_VOICE_UUID",
|
|
96
|
+
];
|
|
97
|
+
for (const k of envKeyList) envLines.push(`${k}=${getKey(k)}`);
|
|
98
|
+
|
|
99
|
+
envLines.push(
|
|
100
|
+
"",
|
|
101
|
+
"# TTS",
|
|
102
|
+
`GROQ_API_KEY=${getKey("GROQ_API_KEY")}`,
|
|
103
|
+
"USE_GROQ=true",
|
|
104
|
+
"USE_GROQ_TTS=true",
|
|
105
|
+
"",
|
|
106
|
+
"# STT",
|
|
107
|
+
`DEEPGRAM_API_KEY=${getKey("DEEPGRAM_API_KEY")}`,
|
|
108
|
+
"",
|
|
109
|
+
"# Supertonic TTS",
|
|
110
|
+
"SUPERTONIC_API_URL=http://supertonic:8765"
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
fs.writeFileSync(".env", envLines.join("\n") + "\n");
|
|
114
|
+
console.log(" Wrote .env");
|
|
115
|
+
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// 3. Write auth-profiles.json (so OpenClaw has working providers on first start)
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
const providerMap = {
|
|
121
|
+
ANTHROPIC_API_KEY: "anthropic",
|
|
122
|
+
OPENAI_API_KEY: "openai",
|
|
123
|
+
GEMINI_API_KEY: "google",
|
|
124
|
+
OPENROUTER_API_KEY: "openrouter",
|
|
125
|
+
MISTRAL_API_KEY: "mistral",
|
|
126
|
+
XAI_API_KEY: "xai",
|
|
127
|
+
ZAI_API_KEY: "zai",
|
|
128
|
+
CEREBRAS_API_KEY: "cerebras",
|
|
129
|
+
TOGETHER_API_KEY: "together",
|
|
130
|
+
HF_TOKEN: "huggingface",
|
|
131
|
+
GROQ_API_KEY: "groq",
|
|
132
|
+
DEEPGRAM_API_KEY: "deepgram",
|
|
133
|
+
MOONSHOT_API_KEY: "moonshot",
|
|
134
|
+
KIMI_API_KEY: "kimi-coding",
|
|
135
|
+
MINIMAX_API_KEY: "minimax",
|
|
136
|
+
QIANFAN_API_KEY: "qianfan",
|
|
137
|
+
MODELSTUDIO_API_KEY: "modelstudio",
|
|
138
|
+
XIAOMI_API_KEY: "xiaomi",
|
|
139
|
+
VOLCANO_ENGINE_API_KEY: "volcano-engine",
|
|
140
|
+
BYTEPLUS_API_KEY: "byteplus",
|
|
141
|
+
SYNTHETIC_API_KEY: "synthetic",
|
|
142
|
+
VENICE_API_KEY: "venice",
|
|
143
|
+
OPENCODE_ZEN_API_KEY: "opencode",
|
|
144
|
+
KILOCODE_API_KEY: "kilocode",
|
|
145
|
+
AI_GATEWAY_API_KEY: "ai-gateway",
|
|
146
|
+
CLOUDFLARE_AI_GATEWAY_API_KEY: "cloudflare-ai-gateway",
|
|
147
|
+
LITELLM_API_KEY: "litellm",
|
|
148
|
+
SUNO_API_KEY: "suno",
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const authProfiles = {};
|
|
152
|
+
let keyCount = 0;
|
|
153
|
+
for (const [envVar, providerId] of Object.entries(providerMap)) {
|
|
154
|
+
const key = getKey(envVar);
|
|
155
|
+
if (key) {
|
|
156
|
+
authProfiles[providerId] = [
|
|
157
|
+
{
|
|
158
|
+
id: "default",
|
|
159
|
+
type: "api_key",
|
|
160
|
+
key,
|
|
161
|
+
lastUsed: null,
|
|
162
|
+
disabled: false,
|
|
163
|
+
cooldown: null,
|
|
164
|
+
},
|
|
165
|
+
];
|
|
166
|
+
keyCount++;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const agentDir = "openclaw-data/agents/main/agent";
|
|
171
|
+
fs.mkdirSync(agentDir, { recursive: true });
|
|
172
|
+
fs.writeFileSync(
|
|
173
|
+
path.join(agentDir, "auth-profiles.json"),
|
|
174
|
+
JSON.stringify(authProfiles, null, 2) + "\n"
|
|
175
|
+
);
|
|
176
|
+
console.log(` Wrote auth-profiles.json (${keyCount} provider(s))`);
|
|
177
|
+
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
// 4. Pre-generate device identity and pre-pair it
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// OpenClaw requires device pairing for WebSocket connections. Even with
|
|
182
|
+
// dangerouslyDisableDeviceAuth:true, the gateway still requires pairing for
|
|
183
|
+
// the WS protocol (that flag only affects the control UI).
|
|
184
|
+
//
|
|
185
|
+
// Fix: generate an Ed25519 keypair at install time, register the public key
|
|
186
|
+
// in OpenClaw's devices/paired.json, and save the full identity so start.js
|
|
187
|
+
// can inject it into the OpenVoiceUI container via docker exec.
|
|
188
|
+
|
|
189
|
+
// Generate Ed25519 keypair
|
|
190
|
+
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
|
|
191
|
+
|
|
192
|
+
// Raw 32-byte public key — extract via JWK.x (most reliable cross-platform)
|
|
193
|
+
const devJwk = publicKey.export({ format: "jwk" });
|
|
194
|
+
const rawPub = Buffer.from(devJwk.x, "base64url");
|
|
195
|
+
|
|
196
|
+
// deviceId = SHA256(raw public key) — matches Python's hashlib.sha256(raw_pub).hexdigest()
|
|
197
|
+
const deviceId = crypto.createHash("sha256").update(rawPub).digest("hex");
|
|
198
|
+
|
|
199
|
+
// PEM formats — OpenVoiceUI Python client uses PEM for signing
|
|
200
|
+
const pubPem = publicKey.export({ type: "spki", format: "pem" });
|
|
201
|
+
const privPem = privateKey.export({ type: "pkcs8", format: "pem" });
|
|
202
|
+
|
|
203
|
+
// base64url of raw Ed25519 bytes — this is what the gateway compares during WS handshake
|
|
204
|
+
const pubB64url = rawPub.toString("base64url");
|
|
205
|
+
|
|
206
|
+
// Save full identity for injection into OpenVoiceUI container at start time
|
|
207
|
+
// (Python client reads PEM format for Ed25519 signing)
|
|
208
|
+
const deviceIdentity = {
|
|
209
|
+
deviceId: deviceId,
|
|
210
|
+
publicKeyPem: pubPem,
|
|
211
|
+
privateKeyPem: privPem,
|
|
212
|
+
};
|
|
213
|
+
fs.writeFileSync(
|
|
214
|
+
"openclaw-data/pre-paired-device.json",
|
|
215
|
+
JSON.stringify(deviceIdentity, null, 2) + "\n"
|
|
216
|
+
);
|
|
217
|
+
console.log(` Generated device identity: ${deviceId.slice(0, 16)}...`);
|
|
218
|
+
|
|
219
|
+
// Pre-register in OpenClaw's devices/paired.json so it accepts this device.
|
|
220
|
+
// Format must match what approveDevicePairing() writes:
|
|
221
|
+
// - publicKey: base64url of raw Ed25519 bytes (NOT PEM — gateway compares literally)
|
|
222
|
+
// - role/roles/scopes/approvedScopes: authorization metadata
|
|
223
|
+
// - tokens: per-role auth tokens for verifyDeviceToken() calls
|
|
224
|
+
fs.mkdirSync("openclaw-data/devices", { recursive: true });
|
|
225
|
+
const nowMs = Date.now();
|
|
226
|
+
const pairingToken = crypto.randomBytes(32).toString("hex");
|
|
227
|
+
const pairedDevices = {};
|
|
228
|
+
pairedDevices[deviceId] = {
|
|
229
|
+
deviceId: deviceId,
|
|
230
|
+
publicKey: pubB64url,
|
|
231
|
+
displayName: "pinokio-openvoiceui",
|
|
232
|
+
platform: "linux",
|
|
233
|
+
clientId: "cli",
|
|
234
|
+
clientMode: "cli",
|
|
235
|
+
role: "operator",
|
|
236
|
+
roles: ["operator"],
|
|
237
|
+
scopes: ["operator.read", "operator.write"],
|
|
238
|
+
approvedScopes: ["operator.read", "operator.write"],
|
|
239
|
+
tokens: {
|
|
240
|
+
operator: {
|
|
241
|
+
token: pairingToken,
|
|
242
|
+
role: "operator",
|
|
243
|
+
scopes: ["operator.read", "operator.write"],
|
|
244
|
+
createdAtMs: nowMs,
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
createdAtMs: nowMs,
|
|
248
|
+
approvedAtMs: nowMs,
|
|
249
|
+
};
|
|
250
|
+
fs.writeFileSync(
|
|
251
|
+
"openclaw-data/devices/paired.json",
|
|
252
|
+
JSON.stringify(pairedDevices, null, 2) + "\n"
|
|
253
|
+
);
|
|
254
|
+
// Clear pending.json — stale entries with silent:false permanently block auto-approval
|
|
255
|
+
fs.writeFileSync("openclaw-data/devices/pending.json", "{}\n");
|
|
256
|
+
console.log(" Wrote devices/paired.json (pre-paired)");
|
|
257
|
+
|
|
258
|
+
// ---------------------------------------------------------------------------
|
|
259
|
+
// Done
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
|
|
262
|
+
console.log("\n Configuration complete!\n");
|
|
Binary file
|
package/sounds/bruh.mp3
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/sounds/yeah.mp3
ADDED
|
Binary file
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClawdBotAdapter — Multi-Agent Framework adapter for ClawdBot / OpenClaw (P6-T2)
|
|
3
|
+
*
|
|
4
|
+
* Wraps the existing VoiceSession (P3-T7) and exposes it through the EventBridge
|
|
5
|
+
* contract so the app shell can treat ClawdBot as a swappable agent adapter.
|
|
6
|
+
*
|
|
7
|
+
* Internally:
|
|
8
|
+
* - VoiceSession handles STT (Web Speech API), HTTP streaming to /api/conversation,
|
|
9
|
+
* TTS playback (TTSPlayer), and canvas/music command parsing.
|
|
10
|
+
* - This adapter translates between EventBus events (VoiceSession's internal bus)
|
|
11
|
+
* and EventBridge AgentEvents (the shell's canonical event vocabulary).
|
|
12
|
+
*
|
|
13
|
+
* Ref: future-dev-plans/17-MULTI-AGENT-FRAMEWORK.md — "ClawdBot Adapter" section
|
|
14
|
+
*
|
|
15
|
+
* Adapter contract:
|
|
16
|
+
* init(bridge, config) — called when this mode is selected
|
|
17
|
+
* start() — called when user clicks the call button
|
|
18
|
+
* stop() — called when user clicks stop
|
|
19
|
+
* destroy() — called when switching to a different adapter
|
|
20
|
+
*
|
|
21
|
+
* Config shape:
|
|
22
|
+
* {
|
|
23
|
+
* serverUrl: string, // e.g. 'http://localhost:5001'
|
|
24
|
+
* sessionKey: string, // e.g. 'voice-main-3' (passed to VoiceSession)
|
|
25
|
+
* musicPlayer: object, // optional — MusicPlayer instance from shell
|
|
26
|
+
* }
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { AgentEvents, AgentActions } from '../core/EventBridge.js';
|
|
30
|
+
import { eventBus } from '../core/EventBus.js';
|
|
31
|
+
import { VoiceSession } from '../core/VoiceSession.js';
|
|
32
|
+
|
|
33
|
+
const ClawdBotAdapter = {
|
|
34
|
+
// ── Identity & capabilities ───────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
name: 'ClawdBot (OpenClaw)',
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Feature flags: app shell shows/hides UI elements based on this array.
|
|
40
|
+
* Ref: doc 17 — "capability-driven UI shell"
|
|
41
|
+
*/
|
|
42
|
+
capabilities: [
|
|
43
|
+
'canvas', // [CANVAS:...] commands parsed and emitted
|
|
44
|
+
'vps_control', // agent can run server-side tools
|
|
45
|
+
'file_ops', // agent can read/write files on VPS
|
|
46
|
+
'code_execution', // agent can execute code on VPS
|
|
47
|
+
'dj_soundboard', // future: soundboard integration
|
|
48
|
+
'music_sync', // [MUSIC_PLAY/STOP/NEXT] commands parsed and emitted
|
|
49
|
+
'camera', // webcam + Gemini vision + face recognition
|
|
50
|
+
],
|
|
51
|
+
|
|
52
|
+
// ── Private state ─────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
_bridge: null, // EventBridge singleton
|
|
55
|
+
_session: null, // VoiceSession instance
|
|
56
|
+
_config: null, // adapter config passed to init()
|
|
57
|
+
_eventUnsubs: [], // eventBus.on() cleanup functions
|
|
58
|
+
_bridgeUnsubs: [], // bridge.on() cleanup functions (belt-and-suspenders)
|
|
59
|
+
|
|
60
|
+
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Initialize the adapter.
|
|
64
|
+
* Called by AgentOrchestrator when this mode is selected.
|
|
65
|
+
*
|
|
66
|
+
* @param {import('../core/EventBridge.js').EventBridge} bridge
|
|
67
|
+
* @param {object} config
|
|
68
|
+
*/
|
|
69
|
+
async init(bridge, config) {
|
|
70
|
+
this._bridge = bridge;
|
|
71
|
+
this._config = config;
|
|
72
|
+
|
|
73
|
+
// Create VoiceSession — the existing ClawdBot conversation engine
|
|
74
|
+
this._session = new VoiceSession({
|
|
75
|
+
serverUrl: config.serverUrl || '',
|
|
76
|
+
musicPlayer: config.musicPlayer || null,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Translate VoiceSession's internal EventBus events → EventBridge AgentEvents
|
|
80
|
+
this._wireSessionEvents();
|
|
81
|
+
|
|
82
|
+
// Listen for UI→Agent actions emitted on the bridge
|
|
83
|
+
this._bridgeUnsubs.push(
|
|
84
|
+
bridge.on(AgentActions.SEND_MESSAGE, (d) => this._session.sendMessage(d.text))
|
|
85
|
+
);
|
|
86
|
+
this._bridgeUnsubs.push(
|
|
87
|
+
bridge.on(AgentActions.FORCE_MESSAGE, (d) => this._session.sendMessage(d.text))
|
|
88
|
+
);
|
|
89
|
+
this._bridgeUnsubs.push(
|
|
90
|
+
bridge.on(AgentActions.END_SESSION, () => this.stop())
|
|
91
|
+
);
|
|
92
|
+
// CONTEXT_UPDATE: inject background context as a silent system message
|
|
93
|
+
this._bridgeUnsubs.push(
|
|
94
|
+
bridge.on(AgentActions.CONTEXT_UPDATE, (d) => this._session.sendMessage(`[CONTEXT: ${d.text}]`))
|
|
95
|
+
);
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Start the conversation.
|
|
100
|
+
* Called when the user clicks the call button.
|
|
101
|
+
*/
|
|
102
|
+
async start() {
|
|
103
|
+
if (!this._session) return;
|
|
104
|
+
// VoiceSession.start() emits session:start on success, which we forward as CONNECTED
|
|
105
|
+
await this._session.start();
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Stop the conversation.
|
|
110
|
+
* Called when the user clicks stop.
|
|
111
|
+
*/
|
|
112
|
+
async stop() {
|
|
113
|
+
if (!this._session) return;
|
|
114
|
+
this._session.stop();
|
|
115
|
+
// session:stop handler emits DISCONNECTED via the wired event
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Full teardown — called when switching to a different adapter.
|
|
120
|
+
* MUST release all resources.
|
|
121
|
+
*/
|
|
122
|
+
async destroy() {
|
|
123
|
+
// Stop conversation first
|
|
124
|
+
this._session?.stop();
|
|
125
|
+
|
|
126
|
+
// Destroy VoiceSession's AudioContext
|
|
127
|
+
this._session?.destroy();
|
|
128
|
+
this._session = null;
|
|
129
|
+
|
|
130
|
+
// Clean up EventBus subscriptions (prevents stale handlers from a dead session)
|
|
131
|
+
this._eventUnsubs.forEach(fn => fn());
|
|
132
|
+
this._eventUnsubs = [];
|
|
133
|
+
|
|
134
|
+
// Bridge unsubs: AgentOrchestrator calls bridge.clearAll() anyway,
|
|
135
|
+
// but we clean up explicitly for correctness.
|
|
136
|
+
this._bridgeUnsubs.forEach(fn => fn());
|
|
137
|
+
this._bridgeUnsubs = [];
|
|
138
|
+
|
|
139
|
+
this._bridge = null;
|
|
140
|
+
this._config = null;
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// ── Private: VoiceSession → EventBridge wiring ───────────────────────────
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Subscribe to all EventBus events emitted by VoiceSession and translate
|
|
147
|
+
* them into EventBridge AgentEvents for the app shell.
|
|
148
|
+
*/
|
|
149
|
+
_wireSessionEvents() {
|
|
150
|
+
const b = this._bridge;
|
|
151
|
+
const push = (unsub) => this._eventUnsubs.push(unsub);
|
|
152
|
+
|
|
153
|
+
// ── Connection lifecycle ──────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
push(eventBus.on('session:start', () => {
|
|
156
|
+
b.emit(AgentEvents.CONNECTED);
|
|
157
|
+
b.emit(AgentEvents.MOOD, { mood: 'happy' });
|
|
158
|
+
}));
|
|
159
|
+
|
|
160
|
+
push(eventBus.on('session:stop', () => {
|
|
161
|
+
b.emit(AgentEvents.DISCONNECTED);
|
|
162
|
+
b.emit(AgentEvents.MOOD, { mood: 'neutral' });
|
|
163
|
+
}));
|
|
164
|
+
|
|
165
|
+
push(eventBus.on('session:reset', (d) => {
|
|
166
|
+
// Server-side session was reset (e.g. context overflow)
|
|
167
|
+
console.info('[ClawdBotAdapter] Session reset:', d.old, '→', d.new);
|
|
168
|
+
}));
|
|
169
|
+
|
|
170
|
+
// ── Conversation state ────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
push(eventBus.on('session:thinking', () => {
|
|
173
|
+
b.emit(AgentEvents.STATE_CHANGED, { state: 'thinking' });
|
|
174
|
+
b.emit(AgentEvents.MOOD, { mood: 'thinking' });
|
|
175
|
+
}));
|
|
176
|
+
|
|
177
|
+
push(eventBus.on('session:listening', () => {
|
|
178
|
+
b.emit(AgentEvents.STATE_CHANGED, { state: 'listening' });
|
|
179
|
+
}));
|
|
180
|
+
|
|
181
|
+
// ── Content ───────────────────────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
// Final messages (user speech → text, assistant response)
|
|
184
|
+
push(eventBus.on('session:message', (d) => {
|
|
185
|
+
b.emit(AgentEvents.MESSAGE, {
|
|
186
|
+
role: d.role,
|
|
187
|
+
text: d.text,
|
|
188
|
+
final: true,
|
|
189
|
+
});
|
|
190
|
+
}));
|
|
191
|
+
|
|
192
|
+
// Streaming text deltas (assistant thinking → partial transcript display)
|
|
193
|
+
push(eventBus.on('session:streaming', (d) => {
|
|
194
|
+
b.emit(AgentEvents.TRANSCRIPT, { text: d.text, partial: true });
|
|
195
|
+
}));
|
|
196
|
+
|
|
197
|
+
// ── Audio / TTS ───────────────────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
push(eventBus.on('tts:start', () => {
|
|
200
|
+
b.emit(AgentEvents.TTS_PLAYING);
|
|
201
|
+
b.emit(AgentEvents.STATE_CHANGED, { state: 'speaking' });
|
|
202
|
+
}));
|
|
203
|
+
|
|
204
|
+
push(eventBus.on('tts:stop', () => {
|
|
205
|
+
b.emit(AgentEvents.TTS_STOPPED);
|
|
206
|
+
b.emit(AgentEvents.STATE_CHANGED, { state: 'listening' });
|
|
207
|
+
}));
|
|
208
|
+
|
|
209
|
+
// ── Errors ────────────────────────────────────────────────────────────
|
|
210
|
+
|
|
211
|
+
push(eventBus.on('session:error', (d) => {
|
|
212
|
+
b.emit(AgentEvents.ERROR, { message: d.message });
|
|
213
|
+
b.emit(AgentEvents.MOOD, { mood: 'sad' });
|
|
214
|
+
}));
|
|
215
|
+
|
|
216
|
+
// ── Tool calls ────────────────────────────────────────────────────────
|
|
217
|
+
|
|
218
|
+
push(eventBus.on('session:tool', (d) => {
|
|
219
|
+
b.emit(AgentEvents.TOOL_CALLED, { name: d.name, params: {}, result: null });
|
|
220
|
+
}));
|
|
221
|
+
|
|
222
|
+
// ── Emotion / mood ────────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
// Server-provided emotion state (ADR-004: mood + intensity + directives)
|
|
225
|
+
push(eventBus.on('session:emotion', (d) => {
|
|
226
|
+
if (d.mood) b.emit(AgentEvents.MOOD, { mood: d.mood });
|
|
227
|
+
}));
|
|
228
|
+
|
|
229
|
+
// ── Canvas commands ───────────────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
// [CANVAS_MENU] → open canvas page picker
|
|
232
|
+
push(eventBus.on('cmd:canvas_menu', () => {
|
|
233
|
+
b.emit(AgentEvents.CANVAS_CMD, { action: 'menu' });
|
|
234
|
+
}));
|
|
235
|
+
|
|
236
|
+
// [CANVAS:pagename] → present a specific canvas page
|
|
237
|
+
push(eventBus.on('cmd:canvas_page', (d) => {
|
|
238
|
+
b.emit(AgentEvents.CANVAS_CMD, { action: 'present', url: d.page });
|
|
239
|
+
}));
|
|
240
|
+
|
|
241
|
+
// ── Music commands ────────────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
// [MUSIC_PLAY] or [MUSIC_PLAY:trackname]
|
|
244
|
+
push(eventBus.on('cmd:music_play', (d) => {
|
|
245
|
+
b.emit(AgentEvents.MUSIC_PLAY, {
|
|
246
|
+
action: 'play',
|
|
247
|
+
track: d.track || null,
|
|
248
|
+
});
|
|
249
|
+
}));
|
|
250
|
+
|
|
251
|
+
// [MUSIC_STOP]
|
|
252
|
+
push(eventBus.on('cmd:music_stop', () => {
|
|
253
|
+
b.emit(AgentEvents.MUSIC_PLAY, { action: 'stop' });
|
|
254
|
+
}));
|
|
255
|
+
|
|
256
|
+
// [MUSIC_NEXT]
|
|
257
|
+
push(eventBus.on('cmd:music_next', () => {
|
|
258
|
+
b.emit(AgentEvents.MUSIC_PLAY, { action: 'skip' });
|
|
259
|
+
}));
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
export default ClawdBotAdapter;
|
|
264
|
+
export { ClawdBotAdapter };
|