feat: update openclaw and polish desktop flows

This commit is contained in:
inman
2026-05-13 21:52:17 +08:00
parent 7c8781a6e3
commit 86795078f7
22 changed files with 1145 additions and 186 deletions

View File

@@ -5,6 +5,12 @@ import { EventEmitter } from 'events';
import { existsSync, mkdirSync, rmSync, readdirSync } from 'fs';
import { deflateSync } from 'zlib';
import { getOpenClawDir, getOpenClawResolvedDir } from './paths';
import { getAllSettings } from './store';
import {
isSocksProxyUrl,
redactProxyUrlForLog,
resolveWhatsAppProxyUrl,
} from './whatsapp-proxy';
const require = createRequire(import.meta.url);
@@ -76,6 +82,32 @@ type ConnectionState = {
const QRCode = QRCodeModule;
const QRErrorCorrectLevel = QRErrorCorrectLevelModule;
function loadProxyAgentConstructor(proxyUrl: string) {
const moduleName = isSocksProxyUrl(proxyUrl) ? 'socks-proxy-agent' : 'https-proxy-agent';
const agentModule = require(resolveOpenClawModule(moduleName));
return agentModule.SocksProxyAgent
|| agentModule.HttpsProxyAgent
|| agentModule.default
|| agentModule;
}
async function createWhatsAppSocketAgent(): Promise<unknown | undefined> {
let settings: Awaited<ReturnType<typeof getAllSettings>>;
try {
settings = await getAllSettings();
} catch (error) {
console.warn('[WhatsAppLogin] Failed to read proxy settings; connecting directly', error);
return undefined;
}
const proxyUrl = resolveWhatsAppProxyUrl(settings);
if (!proxyUrl) return undefined;
const AgentCtor = loadProxyAgentConstructor(proxyUrl);
console.log(`[WhatsAppLogin] Using proxy for WhatsApp WebSocket: ${redactProxyUrlForLog(proxyUrl)}`);
return new AgentCtor(proxyUrl);
}
function createQrMatrix(input: string) {
const qr = new QRCode(-1, QRErrorCorrectLevel.L);
qr.addData(input);
@@ -201,6 +233,7 @@ export class WhatsAppLoginManager extends EventEmitter {
private loginSucceeded: boolean = false;
private retryCount: number = 0;
private maxRetries: number = 5;
private lastConnectionError: string | null = null;
constructor() {
super();
@@ -242,10 +275,16 @@ export class WhatsAppLoginManager extends EventEmitter {
this.loginSucceeded = false;
this.qr = null;
this.retryCount = 0;
this.lastConnectionError = null;
await this.connectToWhatsApp(accountId);
}
private buildConnectionFailureMessage(fallback: string): string {
const details = this.lastConnectionError ? ` Last error: ${this.lastConnectionError}` : '';
return `${fallback}. Check network connectivity or enable a proxy in Advanced Settings.${details}`;
}
private async connectToWhatsApp(accountId: string): Promise<void> {
if (!this.active) return;
@@ -292,6 +331,7 @@ export class WhatsAppLoginManager extends EventEmitter {
const { version } = await fetchLatestBaileysVersion();
console.log(`[WhatsAppLogin] Starting login for ${accountId}, version: ${version}`);
const socketAgent = await createWhatsAppSocketAgent();
this.socket = makeWASocket({
version,
@@ -299,6 +339,7 @@ export class WhatsAppLoginManager extends EventEmitter {
printQRInTerminal: false,
logger: pino({ level: 'silent' }), // Silent logger
connectTimeoutMs: 60000,
...(socketAgent ? { agent: socketAgent } : {}),
// mobile: false,
// browser: ['ClawX', 'Chrome', '1.0.0'],
});
@@ -333,6 +374,8 @@ export class WhatsAppLoginManager extends EventEmitter {
if (connection === 'close') {
const error = lastDisconnect?.error as BaileysError | undefined;
const statusCode = error?.output?.statusCode;
this.lastConnectionError = error?.message
|| (statusCode ? `statusCode=${statusCode}` : this.lastConnectionError);
const isLoggedOut = statusCode === DisconnectReason.loggedOut;
// Treat 401 as transient if we haven't exhausted retries (max 2 attempts)
// This handles the case where WhatsApp's session hasn't fully released
@@ -351,7 +394,7 @@ export class WhatsAppLoginManager extends EventEmitter {
} else {
console.log('[WhatsAppLogin] Max retries reached, stopping.');
this.active = false;
this.emit('error', 'Connection failed after multiple retries');
this.emit('error', this.buildConnectionFailureMessage('Connection failed after multiple retries'));
}
} else {
// Logged out or explicitly stopped
@@ -384,6 +427,12 @@ export class WhatsAppLoginManager extends EventEmitter {
}
} catch (innerErr) {
console.error('[WhatsAppLogin] Error in connection update:', innerErr);
const msg = innerErr instanceof Error ? innerErr.message : String(innerErr);
this.lastConnectionError = msg;
if (this.active) {
this.active = false;
this.emit('error', this.buildConnectionFailureMessage('Failed to generate WhatsApp QR code'));
}
}
});
@@ -395,7 +444,8 @@ export class WhatsAppLoginManager extends EventEmitter {
} else {
this.active = false;
const msg = error instanceof Error ? error.message : String(error);
this.emit('error', msg);
this.lastConnectionError = msg;
this.emit('error', this.buildConnectionFailureMessage('Failed to start WhatsApp login'));
}
}
}
@@ -408,6 +458,7 @@ export class WhatsAppLoginManager extends EventEmitter {
const cleanupAccountId = this.accountId;
this.active = false;
this.qr = null;
this.lastConnectionError = null;
if (this.socket) {
try {
// Remove listeners to prevent handling closure as error

View File

@@ -0,0 +1,59 @@
import type { ProxySettings } from './proxy';
import { resolveProxySettings } from './proxy';
const WHATSAPP_WEB_HOST = 'web.whatsapp.com';
function splitBypassRules(rules: string): string[] {
return rules
.split(/[,\n;]/)
.map((rule) => rule.trim())
.filter(Boolean);
}
function matchesBypassRule(host: string, rule: string): boolean {
const normalizedHost = host.toLowerCase();
const normalizedRule = rule.toLowerCase();
if (normalizedRule === '*') return true;
if (normalizedRule === '<local>') {
return normalizedHost === 'localhost'
|| normalizedHost === '127.0.0.1'
|| normalizedHost === '::1'
|| !normalizedHost.includes('.');
}
if (normalizedRule.startsWith('*.')) {
const suffix = normalizedRule.slice(1);
return normalizedHost.endsWith(suffix);
}
if (normalizedRule.startsWith('.')) {
return normalizedHost.endsWith(normalizedRule);
}
return normalizedHost === normalizedRule;
}
export function shouldBypassWhatsAppProxy(bypassRules: string): boolean {
return splitBypassRules(bypassRules).some((rule) => matchesBypassRule(WHATSAPP_WEB_HOST, rule));
}
export function resolveWhatsAppProxyUrl(settings: ProxySettings): string {
if (!settings.proxyEnabled) return '';
const resolved = resolveProxySettings(settings);
if (shouldBypassWhatsAppProxy(resolved.bypassRules)) return '';
return resolved.allProxy || resolved.httpsProxy || resolved.httpProxy;
}
export function isSocksProxyUrl(proxyUrl: string): boolean {
return /^socks[45]h?:\/\//i.test(proxyUrl.trim());
}
export function redactProxyUrlForLog(proxyUrl: string): string {
try {
const parsed = new URL(proxyUrl);
if (parsed.username) parsed.username = 'redacted';
if (parsed.password) parsed.password = 'redacted';
return parsed.toString();
} catch {
return proxyUrl.replace(/\/\/([^/@:]+):([^/@]+)@/, '//redacted:redacted@');
}
}