diagnose.js
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env node
// diagnose.js — Check every part of the OpenVoiceUI + OpenClaw connection chain.
// Run this after containers are up to find exactly where things break.
//
// Usage: node diagnose.js
const { execSync } = require("child_process");
const fs = require("fs");
const path = require("path");
const COMPOSE = "docker compose -f docker-compose.yml -f docker-compose.local.yml";
let failures = 0;
function check(name, fn) {
try {
const result = fn();
console.log(` ✅ ${name}${result ? ': ' + result : ''}`);
return true;
} catch (e) {
console.log(` ❌ ${name}: ${e.message}`);
failures++;
return false;
}
}
function exec(cmd, opts = {}) {
return execSync(cmd, { encoding: "utf8", timeout: 15000, ...opts }).trim();
}
console.log("\n=== OpenVoiceUI Pinokio Diagnostics ===\n");
// --- 1. Config files exist ---
console.log("1. Config files:");
check("openclaw.json exists", () => {
if (!fs.existsSync("openclaw-data/openclaw.json")) throw new Error("MISSING — run install first");
return "ok";
});
check(".env exists", () => {
if (!fs.existsSync(".env")) throw new Error("MISSING — run install first");
return "ok";
});
check("auth-profiles.json exists", () => {
const p = "openclaw-data/agents/main/agent/auth-profiles.json";
if (!fs.existsSync(p)) throw new Error("MISSING");
const data = JSON.parse(fs.readFileSync(p, "utf8"));
return `${Object.keys(data).length} provider(s)`;
});
check("devices/paired.json exists", () => {
const p = "openclaw-data/devices/paired.json";
if (!fs.existsSync(p)) throw new Error("MISSING — device pairing will fail");
const data = JSON.parse(fs.readFileSync(p, "utf8"));
return `${Object.keys(data).length} paired device(s)`;
});
check("pre-paired-device.json exists", () => {
if (!fs.existsSync("openclaw-data/pre-paired-device.json")) throw new Error("MISSING — inject will fail");
return "ok";
});
// --- 2. Auth token consistency ---
console.log("\n2. Auth token match:");
check("tokens match between .env and openclaw.json", () => {
const env = fs.readFileSync(".env", "utf8");
const tokenMatch = env.match(/CLAWDBOT_AUTH_TOKEN=(.+)/);
if (!tokenMatch) throw new Error(".env missing CLAWDBOT_AUTH_TOKEN");
const envToken = tokenMatch[1].trim();
const config = JSON.parse(fs.readFileSync("openclaw-data/openclaw.json", "utf8"));
const configToken = config?.gateway?.auth?.token;
if (!configToken) throw new Error("openclaw.json missing gateway.auth.token");
if (envToken !== configToken) throw new Error(`MISMATCH! .env="${envToken.slice(0,8)}..." vs config="${configToken.slice(0,8)}..."`);
return `both = ${envToken.slice(0, 8)}...`;
});
// --- 3. OpenClaw config validation ---
console.log("\n3. OpenClaw config:");
check("openclaw.json structure", () => {
const config = JSON.parse(fs.readFileSync("openclaw-data/openclaw.json", "utf8"));
const issues = [];
if (!config.gateway) issues.push("missing gateway");
if (!config.agents) issues.push("missing agents");
if (!config.gateway?.auth?.token) issues.push("missing gateway.auth.token");
if (!config.gateway?.controlUi?.dangerouslyDisableDeviceAuth) issues.push("missing dangerouslyDisableDeviceAuth");
if (!config.agents?.defaults?.thinkingDefault) issues.push("missing thinkingDefault");
if (issues.length) throw new Error(issues.join(", "));
const model = config.agents.defaults.model || "(auto-select from available providers)";
return `model=${model}`;
});
// --- 4. Device identity consistency ---
console.log("\n4. Device identity:");
check("pre-paired identity matches paired.json", () => {
const identity = JSON.parse(fs.readFileSync("openclaw-data/pre-paired-device.json", "utf8"));
const paired = JSON.parse(fs.readFileSync("openclaw-data/devices/paired.json", "utf8"));
if (!paired[identity.deviceId]) throw new Error(`deviceId ${identity.deviceId.slice(0,16)}... NOT in paired.json`);
return `deviceId=${identity.deviceId.slice(0, 16)}... is paired`;
});
// --- 5. Containers running ---
console.log("\n5. Containers:");
check("openclaw container running", () => {
const out = exec(`${COMPOSE} ps --format json openclaw 2>/dev/null || echo ""`);
if (!out || out.includes('"State":"exited"') || !out.includes('"State":"running"'))
throw new Error("NOT RUNNING");
return "running";
});
check("openvoiceui container running", () => {
const out = exec(`${COMPOSE} ps --format json openvoiceui 2>/dev/null || echo ""`);
if (!out || out.includes('"State":"exited"') || !out.includes('"State":"running"'))
throw new Error("NOT RUNNING");
return "running";
});
// --- 6. Gateway health ---
console.log("\n6. Gateway health:");
check("openclaw gateway responding on 18791", () => {
const out = exec(`${COMPOSE} exec -T openclaw node -e "const h=require('http');h.get('http://localhost:18791',r=>{console.log(r.statusCode);process.exit(0)}).on('error',e=>{console.log('ERROR:'+e.message);process.exit(1)})"`);
if (out.includes("ERROR")) throw new Error(out);
return `HTTP ${out}`;
});
// --- 7. OpenVoiceUI health ---
console.log("\n7. OpenVoiceUI health:");
check("flask app responding on 5001", () => {
const port = (fs.readFileSync(".env", "utf8").match(/PORT=(\d+)/) || [, "5001"])[1];
const out = exec(`${COMPOSE} exec -T openvoiceui python3 -c "import urllib.request; r=urllib.request.urlopen('http://localhost:${port}/health/ready',timeout=5); print(r.status)"`);
if (!out.includes("200")) throw new Error(`got ${out}`);
return `HTTP ${out}`;
});
// --- 8. Device identity in container ---
console.log("\n8. Device identity injection:");
check("identity file in openvoiceui container", () => {
const out = exec(`${COMPOSE} exec -T openvoiceui sh -c 'cat /app/runtime/uploads/.device-identity.json 2>/dev/null || echo MISSING'`);
if (out === "MISSING") throw new Error("NOT INJECTED — inject-device-identity.js may have failed");
const data = JSON.parse(out);
return `deviceId=${data.deviceId.slice(0, 16)}...`;
});
check("container identity matches paired.json", () => {
const containerOut = exec(`${COMPOSE} exec -T openvoiceui sh -c 'cat /app/runtime/uploads/.device-identity.json 2>/dev/null || echo "{}"'`);
const containerIdentity = JSON.parse(containerOut);
if (!containerIdentity.deviceId) throw new Error("no identity in container");
const paired = JSON.parse(fs.readFileSync("openclaw-data/devices/paired.json", "utf8"));
if (!paired[containerIdentity.deviceId]) throw new Error(`container deviceId ${containerIdentity.deviceId.slice(0,16)}... NOT in paired.json`);
return "matched";
});
// --- 9. Gateway connection test ---
console.log("\n9. Gateway connection test:");
check("openvoiceui can reach openclaw gateway", () => {
const out = exec(`${COMPOSE} exec -T openvoiceui python3 -c "
import asyncio, websockets, json, os
async def test():
url = os.getenv('CLAWDBOT_GATEWAY_URL', 'ws://127.0.0.1:18791')
try:
async with websockets.connect(url, open_timeout=5) as ws:
msg = json.loads(await asyncio.wait_for(ws.recv(), timeout=5))
if 'challenge' in str(msg.get('event','')):
print('CHALLENGE_OK')
else:
print('UNEXPECTED:' + json.dumps(msg)[:100])
except Exception as e:
print('FAIL:' + str(e))
asyncio.run(test())
"`, { timeout: 20000 });
if (out.includes("FAIL")) throw new Error(out);
if (!out.includes("CHALLENGE_OK")) throw new Error(`unexpected: ${out}`);
return "received challenge";
});
// --- 10. Recent logs ---
console.log("\n10. Recent container logs (last 20 lines each):");
try {
console.log("\n --- openclaw logs ---");
const clawLogs = exec(`${COMPOSE} logs --tail=20 openclaw 2>&1`);
console.log(" " + clawLogs.split("\n").join("\n "));
} catch (e) {
console.log(" (could not read openclaw logs)");
}
try {
console.log("\n --- openvoiceui logs ---");
const ovuLogs = exec(`${COMPOSE} logs --tail=20 openvoiceui 2>&1`);
console.log(" " + ovuLogs.split("\n").join("\n "));
} catch (e) {
console.log(" (could not read openvoiceui logs)");
}
// --- Summary ---
console.log(`\n=== Summary: ${failures === 0 ? 'All checks passed ✅' : `${failures} failure(s) ❌`} ===\n`);
if (failures > 0) {
console.log("Fix the ❌ items above. The first failure is usually the root cause.\n");
}