Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion electron-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
48 changes: 46 additions & 2 deletions electron/gateway/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>.app/Contents/Frameworks/<ProductName> Helper.app/Contents/MacOS/<ProductName> 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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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, {
Expand Down
2 changes: 1 addition & 1 deletion electron/main/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
},
},
{
Expand Down
Binary file added resources/dmg-background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/pages/Settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ export function Settings() {
<Button
variant="link"
className="h-auto p-0"
onClick={() => window.electron.openExternal('https://docs.clawx.app')}
onClick={() => window.electron.openExternal('https://clawx.dev')}
>
Documentation
</Button>
Expand Down
Loading