From 29ee21754a3570bc60bf04d572484a34803e7a63 Mon Sep 17 00:00:00 2001 From: Haze <709547807@qq.com> Date: Fri, 6 Feb 2026 00:24:36 +0800 Subject: [PATCH] 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 --- .gitmodules | 3 ++ electron-builder.yml | 16 ++++++++++ electron/gateway/manager.ts | 58 ++++++++++++++++++++++++++++++--- electron/main/ipc-handlers.ts | 21 ++++++++++++ electron/preload/index.ts | 3 ++ electron/utils/paths.ts | 60 +++++++++++++++++++++++++++++++++++ openclaw | 1 + package.json | 7 +++- src/pages/Setup/index.tsx | 47 +++++++++++++++++++++++---- 9 files changed, 205 insertions(+), 11 deletions(-) create mode 100644 .gitmodules create mode 160000 openclaw diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..a5cd3d5f2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "openclaw"] + path = openclaw + url = /Users/guoyuliang/Project/openclaw diff --git a/electron-builder.yml b/electron-builder.yml index 7f84d21fd..e551be086 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -20,6 +20,22 @@ extraResources: - "**/*" - "!icons/*.md" - "!icons/*.svg" + # OpenClaw submodule - include only necessary files for runtime + - from: openclaw/ + to: openclaw/ + filter: + - "openclaw.mjs" + - "package.json" + - "dist/**/*" + - "skills/**/*" + - "extensions/**/*" + - "scripts/run-node.mjs" + - "!**/*.test.ts" + - "!**/*.test.js" + - "!**/test/**" + - "!**/.git" + - "!**/.github" + - "!**/docs/**" asar: true asarUnpack: diff --git a/electron/gateway/manager.ts b/electron/gateway/manager.ts index cc15919d4..a977fdad6 100644 --- a/electron/gateway/manager.ts +++ b/electron/gateway/manager.ts @@ -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 { 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) => { diff --git a/electron/main/ipc-handlers.ts b/electron/main/ipc-handlers.ts index 9d0df4509..eb93e5f25 100644 --- a/electron/main/ipc-handlers.ts +++ b/electron/main/ipc-handlers.ts @@ -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 */ diff --git a/electron/preload/index.ts b/electron/preload/index.ts index 44b51d6c9..b5832147c 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -22,6 +22,9 @@ const electronAPI = { 'gateway:restart', 'gateway:rpc', 'gateway:health', + // OpenClaw + 'openclaw:status', + 'openclaw:isReady', // Shell 'shell:openExternal', 'shell:showItemInFolder', diff --git a/electron/utils/paths.ts b/electron/utils/paths.ts index c6b66ca70..8f3d31be5 100644 --- a/electron/utils/paths.ts +++ b/electron/utils/paths.ts @@ -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, + }; +} diff --git a/openclaw b/openclaw new file mode 160000 index 000000000..852466645 --- /dev/null +++ b/openclaw @@ -0,0 +1 @@ +Subproject commit 85246664548b4481f2da2b706dd58157484abe86 diff --git a/package.json b/package.json index 224b36ae0..5fe0f6a90 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,12 @@ "publish:mac": "electron-builder --mac --publish always", "publish:win": "electron-builder --win --publish always", "publish:linux": "electron-builder --linux --publish always", - "release": "pnpm run build:vite && pnpm run publish" + "release": "pnpm run build:vite && pnpm run publish", + "postinstall": "git submodule update --init", + "openclaw:init": "git submodule update --init && cd openclaw && pnpm install", + "openclaw:install": "cd openclaw && pnpm install", + "openclaw:build": "cd openclaw && pnpm build", + "openclaw:update": "git submodule update --remote openclaw && cd openclaw && pnpm install" }, "dependencies": { "electron-store": "^10.0.0", diff --git a/src/pages/Setup/index.tsx b/src/pages/Setup/index.tsx index 2a2df92fc..9cdfafdeb 100644 --- a/src/pages/Setup/index.tsx +++ b/src/pages/Setup/index.tsx @@ -396,12 +396,47 @@ function RuntimeContent({ onStatusChange }: RuntimeContentProps) { })); } - // Check OpenClaw (simulated - in real app would check if openclaw is installed) - await new Promise((resolve) => setTimeout(resolve, 500)); - setChecks((prev) => ({ - ...prev, - openclaw: { status: 'success', message: 'OpenClaw package ready' }, - })); + // Check OpenClaw submodule status + try { + const openclawStatus = await window.electron.ipcRenderer.invoke('openclaw:status') as { + submoduleExists: boolean; + isInstalled: boolean; + isBuilt: boolean; + dir: string; + }; + + if (!openclawStatus.submoduleExists) { + setChecks((prev) => ({ + ...prev, + openclaw: { + status: 'error', + message: 'OpenClaw submodule not found. Run: git submodule update --init' + }, + })); + } else if (!openclawStatus.isInstalled) { + setChecks((prev) => ({ + ...prev, + openclaw: { + status: 'error', + message: 'Dependencies not installed. Run: cd openclaw && pnpm install' + }, + })); + } else { + const modeLabel = openclawStatus.isBuilt ? 'production' : 'development'; + setChecks((prev) => ({ + ...prev, + openclaw: { + status: 'success', + message: `OpenClaw package ready (${modeLabel} mode)` + }, + })); + } + } catch (error) { + setChecks((prev) => ({ + ...prev, + openclaw: { status: 'error', message: `Failed to check: ${error}` }, + })); + } // Check Gateway await new Promise((resolve) => setTimeout(resolve, 500));