diff --git a/electron-builder.yml b/electron-builder.yml index 1a6ea96f4..d8760c6a6 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -62,9 +62,12 @@ mac: NSCameraUsageDescription: ClawX requires camera access for video features dmg: - # background: resources/dmg-background.png + background: resources/dmg-background.png icon: resources/icons/icon.icns iconSize: 100 + window: + width: 540 + height: 380 contents: - type: file x: 130 diff --git a/electron/gateway/manager.ts b/electron/gateway/manager.ts index 63209bb3f..3eeb1fb98 100644 --- a/electron/gateway/manager.ts +++ b/electron/gateway/manager.ts @@ -63,6 +63,39 @@ const DEFAULT_RECONNECT_CONFIG: ReconnectConfig = { maxDelay: 30000, }; +/** + * Get the Node.js-compatible executable path for spawning child processes. + * + * On macOS in packaged mode, using `process.execPath` directly causes the + * child process to appear as a separate dock icon (named "exec") because the + * binary lives inside a `.app` bundle that macOS treats as a GUI application. + * + * To avoid this, we resolve the Electron Helper binary which has + * `LSUIElement` set in its Info.plist, preventing dock icon creation. + * Falls back to `process.execPath` if the Helper binary is not found. + */ +function getNodeExecutablePath(): string { + if (process.platform === 'darwin' && app.isPackaged) { + // Electron Helper binary lives at: + // .app/Contents/Frameworks/ Helper.app/Contents/MacOS/ Helper + const appName = app.getName(); + const helperName = `${appName} Helper`; + const helperPath = path.join( + path.dirname(process.execPath), // .../Contents/MacOS + '../Frameworks', + `${helperName}.app`, + 'Contents/MacOS', + helperName, + ); + if (existsSync(helperPath)) { + logger.info(`Using Electron Helper binary to avoid dock icon: ${helperPath}`); + return helperPath; + } + logger.warn(`Electron Helper binary not found at ${helperPath}, falling back to process.execPath`); + } + return process.execPath; +} + /** * Gateway Manager * Handles starting, stopping, and communicating with the OpenClaw Gateway @@ -377,11 +410,13 @@ export class GatewayManager extends EventEmitter { const gatewayArgs = ['gateway', '--port', String(this.status.port), '--token', gatewayToken, '--dev', '--allow-unconfigured']; if (app.isPackaged) { - // Production: always use Electron binary as Node.js via ELECTRON_RUN_AS_NODE + // Production: use Electron binary as Node.js via ELECTRON_RUN_AS_NODE + // On macOS, use the Electron Helper binary to avoid extra dock icons if (existsSync(entryScript)) { - command = process.execPath; + command = getNodeExecutablePath(); args = [entryScript, ...gatewayArgs]; logger.info('Starting Gateway in PACKAGED mode (ELECTRON_RUN_AS_NODE)'); + logger.info(`Using executable: ${command}`); } else { const errMsg = `OpenClaw entry script not found at: ${entryScript}`; logger.error(errMsg); @@ -449,6 +484,15 @@ export class GatewayManager extends EventEmitter { // Critical: In packaged mode, make Electron binary act as Node.js if (app.isPackaged) { spawnEnv['ELECTRON_RUN_AS_NODE'] = '1'; + // Prevent OpenClaw entry.ts from respawning itself (which would create + // another child process and a second "exec" dock icon on macOS) + spawnEnv['OPENCLAW_NO_RESPAWN'] = '1'; + // Pre-set the NODE_OPTIONS that entry.ts would have added via respawn + const existingNodeOpts = spawnEnv['NODE_OPTIONS'] ?? ''; + if (!existingNodeOpts.includes('--disable-warning=ExperimentalWarning') && + !existingNodeOpts.includes('--no-warnings')) { + spawnEnv['NODE_OPTIONS'] = `${existingNodeOpts} --disable-warning=ExperimentalWarning`.trim(); + } } this.process = spawn(command, args, { diff --git a/electron/main/menu.ts b/electron/main/menu.ts index 8fbad60df..8aea57346 100644 --- a/electron/main/menu.ts +++ b/electron/main/menu.ts @@ -176,7 +176,7 @@ export function createMenu(): void { { label: 'Documentation', click: async () => { - await shell.openExternal('https://docs.clawx.app'); + await shell.openExternal('https://clawx.dev'); }, }, { diff --git a/resources/dmg-background.png b/resources/dmg-background.png new file mode 100644 index 000000000..653a56357 Binary files /dev/null and b/resources/dmg-background.png differ diff --git a/src/pages/Settings/index.tsx b/src/pages/Settings/index.tsx index 5ab9651e2..7331514ae 100644 --- a/src/pages/Settings/index.tsx +++ b/src/pages/Settings/index.tsx @@ -347,7 +347,7 @@ export function Settings() {