diff --git a/electron/utils/paths.ts b/electron/utils/paths.ts index 7037aaf..bce52fb 100644 --- a/electron/utils/paths.ts +++ b/electron/utils/paths.ts @@ -23,13 +23,15 @@ type OpenClawRuntimeResolution = { let cachedOpenClawRuntime: OpenClawRuntimeResolution | null = null; -// Packages that Zhinian loads from the OpenClaw package context at main-process -// module initialization time. Some user-installed OpenClaw packages are valid -// CLIs but do not include these app-side integration dependencies; selecting -// them would crash before the UI opens. -const REQUIRED_OPENCLAW_CONTEXT_PACKAGES = [ - '@whiskeysockets/baileys', - 'qrcode-terminal', +// Modules that Zhinian loads from the OpenClaw package context at main-process +// module initialization time. Some user-installed or previously managed +// OpenClaw packages are valid CLIs but do not include these app-side +// integration dependencies; selecting them would crash before the UI opens. +const REQUIRED_OPENCLAW_CONTEXT_MODULES = [ + '@whiskeysockets/baileys/package.json', + 'qrcode-terminal/package.json', + 'qrcode-terminal/vendor/QRCode/index.js', + 'qrcode-terminal/vendor/QRCode/QRErrorCorrectLevel.js', ] as const; export { @@ -148,20 +150,20 @@ function isValidOpenClawPackageDir(dir: string): boolean { && existsSync(fsPath(join(dir, 'openclaw.mjs'))); } -function hasRequiredOpenClawContextPackages(dir: string): boolean { +function hasRequiredOpenClawContextModules(dir: string): boolean { if (!isValidOpenClawPackageDir(dir)) return false; try { const runtimeRequire = createRequire(join(realpathSync(fsPath(dir)), 'package.json')); - for (const packageName of REQUIRED_OPENCLAW_CONTEXT_PACKAGES) { - runtimeRequire.resolve(`${packageName}/package.json`); + for (const specifier of REQUIRED_OPENCLAW_CONTEXT_MODULES) { + runtimeRequire.resolve(specifier); } return true; } catch { try { const runtimeRequire = createRequire(join(dir, 'package.json')); - for (const packageName of REQUIRED_OPENCLAW_CONTEXT_PACKAGES) { - runtimeRequire.resolve(`${packageName}/package.json`); + for (const specifier of REQUIRED_OPENCLAW_CONTEXT_MODULES) { + runtimeRequire.resolve(specifier); } return true; } catch { @@ -251,10 +253,10 @@ function findExternalOpenClawDir(excludedDirs: string[]): string | null { seen.add(candidate); if (excludedDirs.some((excluded) => samePath(candidate, excluded))) continue; if (!isValidOpenClawPackageDir(candidate)) continue; - if (hasRequiredOpenClawContextPackages(candidate)) return candidate; + if (hasRequiredOpenClawContextModules(candidate)) return candidate; logOpenClawRuntime('[openclaw-runtime] Ignoring external OpenClaw installation because required app dependencies are missing', { candidate, - requiredPackages: REQUIRED_OPENCLAW_CONTEXT_PACKAGES, + requiredModules: REQUIRED_OPENCLAW_CONTEXT_MODULES, }); } @@ -269,7 +271,7 @@ function installBundledOpenClawToManagedRuntime(bundledDir: string, managedDir: ? readOpenClawVersion(managedDir) : undefined; - if (managedVersion && bundledVersion && managedVersion === bundledVersion) { + if (managedVersion && bundledVersion && managedVersion === bundledVersion && hasRequiredOpenClawContextModules(managedDir)) { return false; } @@ -323,7 +325,7 @@ function resolveOpenClawRuntime(): OpenClawRuntimeResolution { } } - if (isValidOpenClawPackageDir(managedDir)) { + if (hasRequiredOpenClawContextModules(managedDir)) { cachedOpenClawRuntime = { dir: managedDir, source: 'managed', @@ -341,6 +343,13 @@ function resolveOpenClawRuntime(): OpenClawRuntimeResolution { return cachedOpenClawRuntime; } + if (isValidOpenClawPackageDir(managedDir)) { + logOpenClawRuntime('[openclaw-runtime] Ignoring managed OpenClaw runtime because required app dependencies are missing', { + managedDir, + requiredModules: REQUIRED_OPENCLAW_CONTEXT_MODULES, + }); + } + cachedOpenClawRuntime = { dir: bundledDir, source: isValidOpenClawPackageDir(bundledDir) ? 'bundled' : 'missing', diff --git a/electron/utils/wechat-login.ts b/electron/utils/wechat-login.ts index 0e4b00b..f1d0255 100644 --- a/electron/utils/wechat-login.ts +++ b/electron/utils/wechat-login.ts @@ -6,7 +6,7 @@ import { homedir } from 'node:os'; import { join } from 'node:path'; import { deflateSync } from 'node:zlib'; import { normalizeOpenClawAccountId } from './channel-alias'; -import { getOpenClawResolvedDir } from './paths'; +import { getOpenClawDir, getOpenClawResolvedDir } from './paths'; export const DEFAULT_WECHAT_BASE_URL = 'https://ilinkai.weixin.qq.com'; const DEFAULT_ILINK_BOT_TYPE = '3'; @@ -44,8 +44,16 @@ function getQrRenderDeps(): QrRenderDeps { } const openclawRequire = createRequire(join(getOpenClawResolvedDir(), 'package.json')); - const qrCodeModulePath = openclawRequire.resolve('qrcode-terminal/vendor/QRCode/index.js'); - const qrErrorCorrectLevelPath = openclawRequire.resolve('qrcode-terminal/vendor/QRCode/QRErrorCorrectLevel.js'); + const fallbackRequire = createRequire(join(getOpenClawDir(), 'package.json')); + const resolveOpenClawModule = (specifier: string) => { + try { + return openclawRequire.resolve(specifier); + } catch { + return fallbackRequire.resolve(specifier); + } + }; + const qrCodeModulePath = resolveOpenClawModule('qrcode-terminal/vendor/QRCode/index.js'); + const qrErrorCorrectLevelPath = resolveOpenClawModule('qrcode-terminal/vendor/QRCode/QRErrorCorrectLevel.js'); qrRenderDeps = { QRCode: require(qrCodeModulePath), QRErrorCorrectLevel: require(qrErrorCorrectLevelPath), diff --git a/electron/utils/whatsapp-login.ts b/electron/utils/whatsapp-login.ts index cbe1375..9fdc010 100644 --- a/electron/utils/whatsapp-login.ts +++ b/electron/utils/whatsapp-login.ts @@ -21,7 +21,10 @@ const openclawRequire = createRequire(join(openclawResolvedPath, 'package.json') const projectRequire = createRequire(join(openclawPath, 'package.json')); function resolveOpenClawPackageJson(packageName: string): string { - const specifier = `${packageName}/package.json`; + return resolveOpenClawModule(`${packageName}/package.json`); +} + +function resolveOpenClawModule(specifier: string): string { // 1. Try openclaw's own deps (works in packaged mode + openclaw transitive deps) try { return openclawRequire.resolve(specifier); @@ -32,7 +35,7 @@ function resolveOpenClawPackageJson(packageName: string): string { } catch (err) { const reason = err instanceof Error ? err.message : String(err); throw new Error( - `Failed to resolve "${packageName}" from OpenClaw context. ` + + `Failed to resolve "${specifier}" from OpenClaw context. ` + `openclawPath=${openclawPath}, resolvedPath=${openclawResolvedPath}. ${reason}`, { cause: err } ); @@ -40,8 +43,8 @@ function resolveOpenClawPackageJson(packageName: string): string { } const baileysPath = dirname(resolveOpenClawPackageJson('@whiskeysockets/baileys')); -const qrCodeModulePath = openclawRequire.resolve('qrcode-terminal/vendor/QRCode/index.js'); -const qrErrorCorrectLevelPath = openclawRequire.resolve('qrcode-terminal/vendor/QRCode/QRErrorCorrectLevel.js'); +const qrCodeModulePath = resolveOpenClawModule('qrcode-terminal/vendor/QRCode/index.js'); +const qrErrorCorrectLevelPath = resolveOpenClawModule('qrcode-terminal/vendor/QRCode/QRErrorCorrectLevel.js'); // Load Baileys dependencies dynamically const { diff --git a/scripts/bundle-openclaw.mjs b/scripts/bundle-openclaw.mjs index bb17683..26a626c 100644 --- a/scripts/bundle-openclaw.mjs +++ b/scripts/bundle-openclaw.mjs @@ -188,6 +188,7 @@ echo` Skipped ${skippedDevCount} dev-only package references`; // then BFS its transitive deps exactly like we did for openclaw above. const EXTRA_BUNDLED_PACKAGES = [ '@whiskeysockets/baileys', // WhatsApp channel (was a dep of old clawdbot, not openclaw) + 'qrcode-terminal', // QR rendering is loaded from OpenClaw context by channel login flows ]; let extraCount = 0;