import { ensureOpenClawRuntimeLayout, getOpenClawPackageStatus, getOpenClawRuntimePaths, type OpenClawRuntimePaths, } from '@electron/utils/paths'; export type OpenClawProcessOwnerState = | 'idle' | 'preparing' | 'running' | 'stopping' | 'stopped' | 'failed'; export interface OpenClawProcessOwnerStatus { state: OpenClawProcessOwnerState; prepared: boolean; runtimePaths: OpenClawRuntimePaths; packageExists: boolean; entryExists: boolean; nodeModulesPath: string; nodeModulesExists: boolean; lastError?: string; } export interface OpenClawProcessOwnerOptions { runtimePaths?: Partial; } export interface OpenClawProcessOwnerLike { prepare(): Promise; start(): Promise; stop(): Promise; restart(): Promise; getStatus(): OpenClawProcessOwnerStatus; getRuntimePaths(): OpenClawRuntimePaths; } function mergeRuntimePaths( base: OpenClawRuntimePaths, override?: Partial, ): OpenClawRuntimePaths { if (!override) { return base; } return { configDir: override.configDir ?? base.configDir, runtimeDir: override.runtimeDir ?? base.runtimeDir, dir: override.dir ?? base.dir, resolvedDir: override.resolvedDir ?? base.resolvedDir, entryPath: override.entryPath ?? base.entryPath, }; } export class OpenClawProcessOwner implements OpenClawProcessOwnerLike { private status: OpenClawProcessOwnerStatus; private syncPackageStatus(): void { const packageStatus = getOpenClawPackageStatus(this.status.runtimePaths); this.status.packageExists = packageStatus.packageExists; this.status.entryExists = packageStatus.entryExists; this.status.nodeModulesPath = packageStatus.nodeModulesDir; this.status.nodeModulesExists = packageStatus.nodeModulesExists; } constructor(options?: OpenClawProcessOwnerOptions) { const runtimePaths = mergeRuntimePaths( getOpenClawRuntimePaths(), options?.runtimePaths, ); this.status = { state: 'idle', prepared: false, runtimePaths, packageExists: false, entryExists: false, nodeModulesPath: '', nodeModulesExists: false, }; this.syncPackageStatus(); } async prepare(): Promise { if (this.status.prepared) { return; } this.status.state = 'preparing'; ensureOpenClawRuntimeLayout(this.status.runtimePaths); this.status.prepared = true; this.syncPackageStatus(); this.status.lastError = this.status.entryExists ? undefined : `OpenClaw entry not found at ${this.status.runtimePaths.entryPath}`; this.status.state = 'idle'; } async start(): Promise { if (this.status.state === 'running') { return; } await this.prepare(); this.syncPackageStatus(); if (!this.status.entryExists) { this.status.state = 'failed'; throw new Error(this.status.lastError || `OpenClaw entry not found at ${this.status.runtimePaths.entryPath}`); } this.status.lastError = undefined; this.status.state = 'running'; } async stop(): Promise { if (this.status.state === 'idle' || this.status.state === 'stopped') { return; } this.status.state = 'stopping'; this.status.state = 'stopped'; } async restart(): Promise { await this.stop(); await this.start(); } getStatus(): OpenClawProcessOwnerStatus { this.syncPackageStatus(); return { ...this.status, runtimePaths: { ...this.status.runtimePaths }, }; } getRuntimePaths(): OpenClawRuntimePaths { return { ...this.status.runtimePaths }; } }