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:
@@ -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) => {
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,9 @@ const electronAPI = {
|
||||
'gateway:restart',
|
||||
'gateway:rpc',
|
||||
'gateway:health',
|
||||
// OpenClaw
|
||||
'openclaw:status',
|
||||
'openclaw:isReady',
|
||||
// Shell
|
||||
'shell:openExternal',
|
||||
'shell:showItemInFolder',
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user