Merge pull request #484 from netease-youdao/liuzhq/gateway-restart

- 添加openclaw gateway 启动失败后重启功能
This commit is contained in:
liuzhq
2026-03-18 15:33:23 +08:00
committed by GitHub
6 changed files with 71 additions and 23 deletions
+26
View File
@@ -1730,6 +1730,32 @@ if (!gotTheLock) {
}
});
let restartGatewayPromise: Promise<OpenClawEngineStatus> | null = null;
ipcMain.handle('openclaw:engine:restartGateway', async () => {
if (restartGatewayPromise) {
const status = await restartGatewayPromise;
return { success: status.phase === 'running' || status.phase === 'ready', status };
}
try {
const manager = getOpenClawEngineManager();
restartGatewayPromise = manager.restartGateway();
const status = await restartGatewayPromise;
return {
success: status.phase === 'running' || status.phase === 'ready',
status,
};
} catch (error) {
const manager = getOpenClawEngineManager();
return {
success: false,
status: manager.getStatus(),
error: error instanceof Error ? error.message : 'Failed to restart OpenClaw gateway',
};
} finally {
restartGatewayPromise = null;
}
});
// MCP Server IPC handlers
ipcMain.handle('mcp:list', () => {
try {
+1
View File
@@ -123,6 +123,7 @@ contextBridge.exposeInMainWorld('electron', {
getStatus: () => ipcRenderer.invoke('openclaw:engine:getStatus'),
install: () => ipcRenderer.invoke('openclaw:engine:install'),
retryInstall: () => ipcRenderer.invoke('openclaw:engine:retryInstall'),
restartGateway: () => ipcRenderer.invoke('openclaw:engine:restartGateway'),
onProgress: (callback: (status: any) => void) => {
const handler = (_event: any, status: any) => callback(status);
ipcRenderer.on('openclaw:engine:onProgress', handler);
+26 -21
View File
@@ -33,6 +33,7 @@ const CoworkView: React.FC<CoworkViewProps> = ({ onRequestAppSettings, onShowSki
const isMac = window.electron.platform === 'darwin';
const [isInitialized, setIsInitialized] = useState(false);
const [openClawStatus, setOpenClawStatus] = useState<OpenClawEngineStatus | null>(null);
const [isRestartingGateway, setIsRestartingGateway] = useState(false);
// Track if we're starting a session to prevent duplicate submissions
const isStartingRef = useRef(false);
// Track pending start request so stop can cancel delayed startup.
@@ -69,9 +70,6 @@ const CoworkView: React.FC<CoworkViewProps> = ({ onRequestAppSettings, onShowSki
};
const resolveEngineStatusText = (status: OpenClawEngineStatus): string => {
if (status.message?.trim()) {
return status.message.trim();
}
switch (status.phase) {
case 'not_installed':
return i18nService.t('coworkOpenClawNotInstalledNotice');
@@ -94,6 +92,18 @@ const CoworkView: React.FC<CoworkViewProps> = ({ onRequestAppSettings, onShowSki
return status.phase === 'running' || status.phase === 'ready';
};
const handleRestartGateway = async () => {
if (isRestartingGateway) return;
setIsRestartingGateway(true);
try {
await coworkService.restartOpenClawGateway();
} catch (error) {
console.error('[CoworkView] Failed to restart gateway:', error);
} finally {
setIsRestartingGateway(false);
}
};
useEffect(() => {
const init = async () => {
await coworkService.init();
@@ -390,11 +400,6 @@ const CoworkView: React.FC<CoworkViewProps> = ({ onRequestAppSettings, onShowSki
const shouldShowEngineStatus = Boolean(isOpenClawEngine && openClawStatus && openClawStatus.phase !== 'running');
const isEngineError = openClawStatus?.phase === 'error';
const shouldShowGoToSettingsInstall = openClawStatus
? (openClawStatus.phase === 'not_installed'
|| openClawStatus.phase === 'installing'
|| openClawStatus.phase === 'error')
: false;
const isEngineReady = isOpenClawEngine
? isOpenClawReadyForSession(openClawStatus)
: true;
@@ -480,10 +485,11 @@ const CoworkView: React.FC<CoworkViewProps> = ({ onRequestAppSettings, onShowSki
)}
<button
type="button"
onClick={() => onRequestAppSettings?.({ initialTab: 'coworkAgentEngine' })}
className="mt-1 rounded-lg px-3 py-1.5 text-xs font-medium bg-claude-accent text-white hover:bg-claude-accentHover transition-colors"
onClick={handleRestartGateway}
disabled={isRestartingGateway || openClawStatus.phase === 'starting'}
className="mt-1 rounded-lg px-3 py-1.5 text-xs font-medium bg-claude-accent text-white hover:bg-claude-accentHover disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{i18nService.t('coworkOpenClawGoToSettingsInstall')}
{i18nService.t('coworkOpenClawRestartGateway')}
</button>
</div>
</div>
@@ -528,17 +534,16 @@ const CoworkView: React.FC<CoworkViewProps> = ({ onRequestAppSettings, onShowSki
</span>
)}
</div>
{shouldShowGoToSettingsInstall && (
<button
type="button"
onClick={() => onRequestAppSettings?.({ initialTab: 'coworkAgentEngine' })}
className={`rounded-lg px-3 py-1.5 text-xs font-medium transition-colors ${isEngineError
? 'bg-red-600 text-white hover:bg-red-700'
: 'bg-amber-600 text-white hover:bg-amber-700'}`}
>
{i18nService.t('coworkOpenClawGoToSettingsInstall')}
<button
type="button"
onClick={handleRestartGateway}
disabled={isRestartingGateway}
className={`shrink-0 rounded-lg px-3 py-1.5 text-xs font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed ${isEngineError
? 'bg-red-600 text-white hover:bg-red-700'
: 'bg-amber-600 text-white hover:bg-amber-700'}`}
>
{i18nService.t('coworkOpenClawRestartGateway')}
</button>
)}
</div>
</div>
)}
+13
View File
@@ -582,6 +582,19 @@ class CoworkService {
return this.openClawStatus;
}
async restartOpenClawGateway(): Promise<OpenClawEngineStatus | null> {
const engineApi = window.electron?.openclaw?.engine;
if (!engineApi?.restartGateway) {
return null;
}
const result = await engineApi.restartGateway();
if (result?.status) {
this.notifyOpenClawStatus(result.status);
return result.status;
}
return this.openClawStatus;
}
async generateSessionTitle(prompt: string | null): Promise<string | null> {
if (!window.electron?.generateSessionTitle) {
return null;
+4 -2
View File
@@ -253,11 +253,12 @@ const translations: Record<LanguageType, Record<string, string>> = {
coworkOpenClawInstalling: '正在启动 OpenClaw...',
coworkOpenClawInstallHint: 'OpenClaw 运行时已内置。切换到该引擎或启动任务时会自动拉起网关。',
coworkOpenClawGoToSettingsInstall: '查看引擎设置',
coworkOpenClawRestartGateway: '重新启动网关',
coworkOpenClawNotInstalledNotice: '未检测到内置 OpenClaw runtime(cfmind),请先执行打包前构建脚本。',
coworkOpenClawReadyNotice: 'OpenClaw runtime 已就绪。开始任务时会自动启动网关。',
coworkOpenClawStarting: 'AI 引擎正在启动网关...',
coworkOpenClawRunning: 'AI 引擎已就绪。',
coworkOpenClawError: 'AI 引擎初始化失败,请前往设置页检查运行时配置。',
coworkOpenClawError: 'OpenClaw 网关未能在规定时间内启动成功。',
coworkMemoryTitle: '记忆',
coworkAgentTab: 'Agent',
coworkBootstrapAgentSectionTitle: 'Agent 设定',
@@ -1167,11 +1168,12 @@ const translations: Record<LanguageType, Record<string, string>> = {
coworkOpenClawInstalling: 'Starting OpenClaw...',
coworkOpenClawInstallHint: 'OpenClaw runtime is bundled. Switching to this engine or starting a task will auto-start the gateway.',
coworkOpenClawGoToSettingsInstall: 'View Engine Settings',
coworkOpenClawRestartGateway: 'Restart Gateway',
coworkOpenClawNotInstalledNotice: 'Bundled OpenClaw runtime (cfmind) was not found. Build the runtime before packaging.',
coworkOpenClawReadyNotice: 'OpenClaw runtime is ready. The gateway will auto-start when you run a task.',
coworkOpenClawStarting: 'AI engine is starting the gateway...',
coworkOpenClawRunning: 'AI engine is ready.',
coworkOpenClawError: 'AI engine initialization failed. Please check the runtime configuration in Settings.',
coworkOpenClawError: 'OpenClaw gateway failed to become healthy in time.',
coworkMemoryTitle: 'Memory',
coworkAgentTab: 'Agent',
coworkBootstrapAgentSectionTitle: 'Agent Settings',
+1
View File
@@ -276,6 +276,7 @@ interface IElectronAPI {
getStatus: () => Promise<{ success: boolean; status?: OpenClawEngineStatus; error?: string }>;
install: () => Promise<{ success: boolean; status?: OpenClawEngineStatus; error?: string }>;
retryInstall: () => Promise<{ success: boolean; status?: OpenClawEngineStatus; error?: string }>;
restartGateway: () => Promise<{ success: boolean; status?: OpenClawEngineStatus; error?: string }>;
onProgress: (callback: (status: OpenClawEngineStatus) => void) => () => void;
};
};