feat(gateway): integrate OpenClaw as git submodule

- Add OpenClaw as git submodule at ./openclaw/
- Update GatewayManager to start gateway from submodule path
- Support both production (dist) and development (pnpm dev) modes
- Add IPC handler for OpenClaw status check
- Update Setup wizard to check real OpenClaw submodule status
- Configure electron-builder to include submodule in packaged app
- Add npm scripts for submodule management:
  - postinstall: auto-init submodule
  - openclaw:init: initialize and install dependencies
  - openclaw:install: install dependencies only
  - openclaw:build: build OpenClaw
  - openclaw:update: update to latest version
This commit is contained in:
Haze
2026-02-06 00:24:36 +08:00
Unverified
parent a04aaf54e3
commit 29ee21754a
9 changed files with 205 additions and 11 deletions

View File

@@ -4,8 +4,16 @@
*/
import { spawn, ChildProcess } from 'child_process';
import { EventEmitter } from 'events';
import { existsSync } from 'fs';
import WebSocket from 'ws';
import { PORTS } from '../utils/config';
import {
getOpenClawDir,
getOpenClawEntryPath,
isOpenClawBuilt,
isOpenClawSubmodulePresent,
isOpenClawInstalled
} from '../utils/paths';
import { GatewayEventType, JsonRpcNotification, isNotification, isResponse } from './protocol';
/**
@@ -308,17 +316,59 @@ export class GatewayManager extends EventEmitter {
/**
* Start Gateway process
* Uses OpenClaw submodule - supports both production (dist) and development modes
*/
private async startProcess(): Promise<void> {
return new Promise((resolve, reject) => {
// Find openclaw command
const command = 'openclaw';
const args = ['gateway', 'run', '--port', String(this.status.port)];
const openclawDir = getOpenClawDir();
const entryScript = getOpenClawEntryPath();
// Verify OpenClaw submodule exists
if (!isOpenClawSubmodulePresent()) {
reject(new Error(
'OpenClaw submodule not found. Please run: git submodule update --init'
));
return;
}
// Verify dependencies are installed
if (!isOpenClawInstalled()) {
reject(new Error(
'OpenClaw dependencies not installed. Please run: cd openclaw && pnpm install'
));
return;
}
let command: string;
let args: string[];
// Check if OpenClaw is built (production mode) or use pnpm dev mode
if (isOpenClawBuilt() && existsSync(entryScript)) {
// Production mode: use openclaw.mjs directly
console.log('Starting Gateway in production mode (using dist)');
command = 'node';
args = [entryScript, 'gateway', 'run', '--port', String(this.status.port), '--allow-unconfigured'];
} else {
// Development mode: use pnpm gateway:dev which handles tsx compilation
console.log('Starting Gateway in development mode (using pnpm)');
command = 'pnpm';
args = ['run', 'dev', 'gateway', 'run', '--port', String(this.status.port), '--allow-unconfigured'];
}
console.log(`Spawning Gateway: ${command} ${args.join(' ')}`);
console.log(`Working directory: ${openclawDir}`);
this.process = spawn(command, args, {
cwd: openclawDir,
stdio: ['ignore', 'pipe', 'pipe'],
detached: false,
shell: true,
shell: process.platform === 'win32', // Use shell on Windows for pnpm
env: {
...process.env,
// Skip channel auto-connect during startup for faster boot
OPENCLAW_SKIP_CHANNELS: '1',
CLAWDBOT_SKIP_CHANNELS: '1',
},
});
this.process.on('error', (error) => {

View File

@@ -19,6 +19,7 @@ import {
isEncryptionAvailable,
type ProviderConfig,
} from '../utils/secure-storage';
import { getOpenClawStatus } from '../utils/paths';
/**
* Register all IPC handlers
@@ -30,6 +31,9 @@ export function registerIpcHandlers(
// Gateway handlers
registerGatewayHandlers(gatewayManager, mainWindow);
// OpenClaw handlers
registerOpenClawHandlers();
// Provider handlers
registerProviderHandlers();
@@ -154,6 +158,23 @@ function registerGatewayHandlers(
});
}
/**
* OpenClaw-related IPC handlers
* For checking submodule status
*/
function registerOpenClawHandlers(): void {
// Get OpenClaw submodule status
ipcMain.handle('openclaw:status', () => {
return getOpenClawStatus();
});
// Check if OpenClaw is ready (submodule present and dependencies installed)
ipcMain.handle('openclaw:isReady', () => {
const status = getOpenClawStatus();
return status.submoduleExists && status.isInstalled;
});
}
/**
* Provider-related IPC handlers
*/

View File

@@ -22,6 +22,9 @@ const electronAPI = {
'gateway:restart',
'gateway:rpc',
'gateway:health',
// OpenClaw
'openclaw:status',
'openclaw:isReady',
// Shell
'shell:openExternal',
'shell:showItemInFolder',

View File

@@ -70,3 +70,63 @@ export function getResourcesDir(): string {
export function getPreloadPath(): string {
return join(__dirname, '../preload/index.js');
}
/**
* Get OpenClaw submodule directory
*/
export function getOpenClawDir(): string {
if (app.isPackaged) {
return join(process.resourcesPath, 'openclaw');
}
return join(__dirname, '../../openclaw');
}
/**
* Get OpenClaw entry script path (openclaw.mjs)
*/
export function getOpenClawEntryPath(): string {
return join(getOpenClawDir(), 'openclaw.mjs');
}
/**
* Check if OpenClaw submodule exists
*/
export function isOpenClawSubmodulePresent(): boolean {
return existsSync(getOpenClawDir()) && existsSync(join(getOpenClawDir(), 'package.json'));
}
/**
* Check if OpenClaw is built (has dist folder with entry.js)
*/
export function isOpenClawBuilt(): boolean {
return existsSync(join(getOpenClawDir(), 'dist', 'entry.js'));
}
/**
* Check if OpenClaw has node_modules installed
*/
export function isOpenClawInstalled(): boolean {
return existsSync(join(getOpenClawDir(), 'node_modules'));
}
/**
* Get OpenClaw status for environment check
*/
export interface OpenClawStatus {
submoduleExists: boolean;
isInstalled: boolean;
isBuilt: boolean;
entryPath: string;
dir: string;
}
export function getOpenClawStatus(): OpenClawStatus {
const dir = getOpenClawDir();
return {
submoduleExists: isOpenClawSubmodulePresent(),
isInstalled: isOpenClawInstalled(),
isBuilt: isOpenClawBuilt(),
entryPath: getOpenClawEntryPath(),
dir,
};
}