From aaffd6c598750ac7e6fe97ee4ef351b5cd421313 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Wed, 20 May 2026 17:29:00 +0300 Subject: [PATCH] fix: improve OpenCode update and desktop menu behavior Restart OpenCode after successful updates so the new version is active Open native About menu into the app About dialog Update desktop View menu actions for the new layout --- packages/electron/main.mjs | 12 ++- packages/ui/src/components/ui/AboutDialog.tsx | 94 ++++++++++--------- packages/ui/src/hooks/useMenuActions.ts | 49 ++++++---- packages/ui/src/lib/i18n/messages/en.ts | 8 +- packages/ui/src/lib/i18n/messages/es.ts | 8 +- packages/ui/src/lib/i18n/messages/ko.ts | 8 +- packages/ui/src/lib/i18n/messages/pl.ts | 8 +- packages/ui/src/lib/i18n/messages/pt-BR.ts | 8 +- packages/ui/src/lib/i18n/messages/uk.ts | 8 +- packages/ui/src/lib/i18n/messages/zh-CN.ts | 8 +- .../web/server/lib/opencode/DOCUMENTATION.md | 2 + packages/web/server/lib/opencode/routes.js | 15 ++- 12 files changed, 130 insertions(+), 98 deletions(-) diff --git a/packages/electron/main.mjs b/packages/electron/main.mjs index abfd9e52..963168b3 100644 --- a/packages/electron/main.mjs +++ b/packages/electron/main.mjs @@ -2414,7 +2414,7 @@ const buildMacMenu = () => { { label: app.name, submenu: [ - { role: 'about' }, + { label: 'About OpenChamber', click: () => dispatchAction('about') }, { label: 'Check for Updates', click: () => dispatchCheckForUpdates(), @@ -2461,10 +2461,12 @@ const buildMacMenu = () => { { label: 'View', submenu: [ - { label: 'Git', accelerator: 'Cmd+G', click: () => dispatchAction('open-git-tab') }, - { label: 'Diff', accelerator: 'Cmd+E', click: () => dispatchAction('open-diff-tab') }, - { label: 'Files', click: () => dispatchAction('open-files-tab') }, - { label: 'Terminal', accelerator: 'Cmd+T', click: () => dispatchAction('open-terminal-tab') }, + { label: 'Toggle Right Sidebar', accelerator: 'Cmd+B', click: () => dispatchAction('toggle-right-sidebar') }, + { label: 'Open Git Sidebar', accelerator: 'Cmd+Shift+G', click: () => dispatchAction('open-right-sidebar-git') }, + { label: 'Open Files Sidebar', accelerator: 'Cmd+Shift+F', click: () => dispatchAction('open-right-sidebar-files') }, + { type: 'separator' }, + { label: 'Toggle Terminal Dock', accelerator: 'Cmd+J', click: () => dispatchAction('toggle-terminal') }, + { label: 'Toggle Terminal Expanded', accelerator: 'Cmd+Shift+J', click: () => dispatchAction('toggle-terminal-expanded') }, { type: 'separator' }, { label: 'Light Theme', click: () => dispatchAction('theme-light') }, { label: 'Dark Theme', click: () => dispatchAction('theme-dark') }, diff --git a/packages/ui/src/components/ui/AboutDialog.tsx b/packages/ui/src/components/ui/AboutDialog.tsx index f1a67226..a58cda8c 100644 --- a/packages/ui/src/components/ui/AboutDialog.tsx +++ b/packages/ui/src/components/ui/AboutDialog.tsx @@ -21,6 +21,7 @@ export const AboutDialog: React.FC = ({ onOpenChange, }) => { const { t } = useI18n(); + const showDiagnostics = import.meta.env.DEV; const [version, setVersion] = React.useState(null); const [openCodeVersion, setOpenCodeVersion] = React.useState(null); const [isCopyingDiagnostics, setIsCopyingDiagnostics] = React.useState(false); @@ -29,6 +30,7 @@ export const AboutDialog: React.FC = ({ const [isPreparingDiagnostics, setIsPreparingDiagnostics] = React.useState(false); const handleCopyDiagnostics = React.useCallback(async () => { + if (!showDiagnostics) return; if (isCopyingDiagnostics) return; setIsCopyingDiagnostics(true); setCopiedDiagnostics(false); @@ -55,7 +57,7 @@ export const AboutDialog: React.FC = ({ } finally { setIsCopyingDiagnostics(false); } - }, [diagnosticsReport, isCopyingDiagnostics, t]); + }, [diagnosticsReport, isCopyingDiagnostics, showDiagnostics, t]); React.useEffect(() => { if (!open) return; @@ -107,7 +109,7 @@ export const AboutDialog: React.FC = ({ }, [open]); React.useEffect(() => { - if (!open) { + if (!open || !showDiagnostics) { setDiagnosticsReport(null); setIsPreparingDiagnostics(false); return; @@ -133,7 +135,7 @@ export const AboutDialog: React.FC = ({ return () => { cancelled = true; }; - }, [open]); + }, [open, showDiagnostics]); const displayVersion = version; @@ -155,54 +157,58 @@ export const AboutDialog: React.FC = ({ -
- -

- {t('aboutDialog.diagnosticsDescription')} -

-
+ {showDiagnostics && ( +
+ +

+ {t('aboutDialog.diagnosticsDescription')} +

+
+ )} -
+ diff --git a/packages/ui/src/hooks/useMenuActions.ts b/packages/ui/src/hooks/useMenuActions.ts index 6db3ac60..9ba3a921 100644 --- a/packages/ui/src/hooks/useMenuActions.ts +++ b/packages/ui/src/hooks/useMenuActions.ts @@ -72,10 +72,11 @@ type MenuAction = | 'new-session' | 'new-worktree-session' | 'change-workspace' - | 'open-git-tab' - | 'open-diff-tab' - | 'open-files-tab' - | 'open-terminal-tab' + | 'toggle-right-sidebar' + | 'open-right-sidebar-git' + | 'open-right-sidebar-files' + | 'toggle-terminal' + | 'toggle-terminal-expanded' | 'copy' | 'theme-light' | 'theme-dark' @@ -97,6 +98,11 @@ export const useMenuActions = ( const setActiveMainTab = useUIStore((s) => s.setActiveMainTab); const setSettingsDialogOpen = useUIStore((s) => s.setSettingsDialogOpen); const setAboutDialogOpen = useUIStore((s) => s.setAboutDialogOpen); + const toggleRightSidebar = useUIStore((s) => s.toggleRightSidebar); + const setRightSidebarOpen = useUIStore((s) => s.setRightSidebarOpen); + const setRightSidebarTab = useUIStore((s) => s.setRightSidebarTab); + const toggleBottomTerminal = useUIStore((s) => s.toggleBottomTerminal); + const setBottomTerminalExpanded = useUIStore((s) => s.setBottomTerminalExpanded); const checkForUpdates = useUpdateStore((state) => state.checkForUpdates); const { setThemeMode } = useThemeSystem(); const checkUpdatesInFlightRef = React.useRef(false); @@ -165,29 +171,27 @@ export const useMenuActions = ( handleChangeWorkspace(); break; - case 'open-git-tab': { - const { activeMainTab } = useUIStore.getState(); - setActiveMainTab(activeMainTab === 'git' ? 'chat' : 'git'); + case 'toggle-right-sidebar': + toggleRightSidebar(); break; - } - case 'open-diff-tab': { - const { activeMainTab } = useUIStore.getState(); - setActiveMainTab(activeMainTab === 'diff' ? 'chat' : 'diff'); + case 'open-right-sidebar-git': + setRightSidebarOpen(true); + setRightSidebarTab('git'); break; - } - case 'open-files-tab': { - const { activeMainTab } = useUIStore.getState(); - setActiveMainTab(activeMainTab === 'files' ? 'chat' : 'files'); + case 'open-right-sidebar-files': + setRightSidebarOpen(true); + setRightSidebarTab('files'); break; - } - case 'open-terminal-tab': { - const { activeMainTab } = useUIStore.getState(); - setActiveMainTab(activeMainTab === 'terminal' ? 'chat' : 'terminal'); + case 'toggle-terminal': + toggleBottomTerminal(); + break; + + case 'toggle-terminal-expanded': + setBottomTerminalExpanded(!useUIStore.getState().isBottomTerminalExpanded); break; - } case 'copy': { const copyEvent = new Event('openchamber:copy', { cancelable: true }); @@ -239,9 +243,14 @@ export const useMenuActions = ( setSessionSwitcherOpen, setCommandPaletteOpen, setSettingsDialogOpen, + setBottomTerminalExpanded, + setRightSidebarOpen, + setRightSidebarTab, setThemeMode, + toggleBottomTerminal, toggleCommandPalette, toggleHelpDialog, + toggleRightSidebar, toggleSidebar, ] ); diff --git a/packages/ui/src/lib/i18n/messages/en.ts b/packages/ui/src/lib/i18n/messages/en.ts index f85abbfb..6cd51cba 100644 --- a/packages/ui/src/lib/i18n/messages/en.ts +++ b/packages/ui/src/lib/i18n/messages/en.ts @@ -2208,16 +2208,16 @@ export const dict = { 'updateDialog.status.updating': 'Updating...', 'updateDialog.error.updateFailed': 'Update failed', 'updateDialog.error.takingLonger': 'Update is taking longer than expected. Wait a bit and refresh, or run: openchamber update', - 'opencodeUpdate.toast.available.title': 'OpenCode update available', - 'opencodeUpdate.toast.available.description': 'Version {version} is ready to install.', + 'opencodeUpdate.toast.available.title': 'OpenCode update', + 'opencodeUpdate.toast.available.description': 'Version {version} available.', 'opencodeUpdate.toast.actions.update': 'Update', 'opencodeUpdate.toast.actions.dismiss': 'Dismiss', 'opencodeUpdate.toast.actions.reload': 'Reload OpenCode', 'opencodeUpdate.toast.upgrading.title': 'Updating OpenCode...', 'opencodeUpdate.toast.upgrading.description': 'Keep OpenChamber open.', 'opencodeUpdate.toast.updated.title': 'OpenCode updated', - 'opencodeUpdate.toast.updated.description': 'Reload OpenCode to start using the updated version.', - 'opencodeUpdate.toast.updated.descriptionWithVersion': 'Version {version} is installed. Reload OpenCode to use it.', + 'opencodeUpdate.toast.updated.description': 'Update installed.', + 'opencodeUpdate.toast.updated.descriptionWithVersion': 'Version {version} installed.', 'opencodeUpdate.toast.failed.title': 'Could not update OpenCode', 'opencodeUpdate.toast.failed.description': 'The OpenCode upgrade failed.', 'opencodeUpdate.toast.reload.message': 'Restarting OpenCode...', diff --git a/packages/ui/src/lib/i18n/messages/es.ts b/packages/ui/src/lib/i18n/messages/es.ts index a0269d6d..583da0c2 100644 --- a/packages/ui/src/lib/i18n/messages/es.ts +++ b/packages/ui/src/lib/i18n/messages/es.ts @@ -2174,16 +2174,16 @@ export const dict: Record = { "updateDialog.status.updating": "Actualizando...", "updateDialog.error.updateFailed": "No se pudo actualizar", "updateDialog.error.takingLonger": "La actualización está tardando más de lo esperado. Espera un poco y refresca, o ejecuta: openchamber update", - "opencodeUpdate.toast.available.title": "Actualización de OpenCode disponible", - "opencodeUpdate.toast.available.description": "La versión {version} está lista para instalar.", + "opencodeUpdate.toast.available.title": "Actualización de OpenCode", + "opencodeUpdate.toast.available.description": "Versión {version} disponible.", "opencodeUpdate.toast.actions.update": "Actualizar", "opencodeUpdate.toast.actions.dismiss": "Descartar", "opencodeUpdate.toast.actions.reload": "Recargar OpenCode", "opencodeUpdate.toast.upgrading.title": "Actualizando OpenCode...", "opencodeUpdate.toast.upgrading.description": "Mantén OpenChamber abierto.", "opencodeUpdate.toast.updated.title": "OpenCode actualizado", - "opencodeUpdate.toast.updated.description": "Recarga OpenCode para empezar a usar la versión actualizada.", - "opencodeUpdate.toast.updated.descriptionWithVersion": "La versión {version} está instalada. Recarga OpenCode para usarla.", + "opencodeUpdate.toast.updated.description": "Actualización instalada.", + "opencodeUpdate.toast.updated.descriptionWithVersion": "Versión {version} instalada.", "opencodeUpdate.toast.failed.title": "No se pudo actualizar OpenCode", "opencodeUpdate.toast.failed.description": "La actualización de OpenCode falló.", "opencodeUpdate.toast.reload.message": "Reiniciando OpenCode...", diff --git a/packages/ui/src/lib/i18n/messages/ko.ts b/packages/ui/src/lib/i18n/messages/ko.ts index f8ef57f6..fb2bf00b 100644 --- a/packages/ui/src/lib/i18n/messages/ko.ts +++ b/packages/ui/src/lib/i18n/messages/ko.ts @@ -2208,16 +2208,16 @@ export const dict: Record = { 'updateDialog.status.updating': '업데이트 중…', 'updateDialog.error.updateFailed': '업데이트 실패', 'updateDialog.error.takingLonger': '업데이트가 예상보다 오래 걸립니다. 잠시 기다린 뒤 새로고침하거나 `openchamber update`를 실행하세요.', - 'opencodeUpdate.toast.available.title': 'OpenCode 업데이트 사용 가능', - 'opencodeUpdate.toast.available.description': '버전 {version}을 설치할 수 있습니다.', + 'opencodeUpdate.toast.available.title': 'OpenCode 업데이트', + 'opencodeUpdate.toast.available.description': '버전 {version} 사용 가능.', 'opencodeUpdate.toast.actions.update': '업데이트', 'opencodeUpdate.toast.actions.dismiss': '닫기', 'opencodeUpdate.toast.actions.reload': 'OpenCode 다시 로드', 'opencodeUpdate.toast.upgrading.title': 'OpenCode 업데이트 중...', 'opencodeUpdate.toast.upgrading.description': 'OpenChamber를 열어 두세요.', 'opencodeUpdate.toast.updated.title': 'OpenCode 업데이트 완료', - 'opencodeUpdate.toast.updated.description': '업데이트된 버전을 사용하려면 OpenCode를 다시 로드하세요.', - 'opencodeUpdate.toast.updated.descriptionWithVersion': '버전 {version}이 설치되었습니다. 사용하려면 OpenCode를 다시 로드하세요.', + 'opencodeUpdate.toast.updated.description': '업데이트가 설치되었습니다.', + 'opencodeUpdate.toast.updated.descriptionWithVersion': '버전 {version}이 설치되었습니다.', 'opencodeUpdate.toast.failed.title': 'OpenCode를 업데이트할 수 없음', 'opencodeUpdate.toast.failed.description': 'OpenCode 업그레이드에 실패했습니다.', 'opencodeUpdate.toast.reload.message': 'OpenCode 다시 시작 중...', diff --git a/packages/ui/src/lib/i18n/messages/pl.ts b/packages/ui/src/lib/i18n/messages/pl.ts index f2aa3233..e204fa86 100644 --- a/packages/ui/src/lib/i18n/messages/pl.ts +++ b/packages/ui/src/lib/i18n/messages/pl.ts @@ -2189,16 +2189,16 @@ export const dict: Record = { 'updateDialog.status.serverRestarting': 'Ponowne uruchamianie serwera...', 'updateDialog.status.updating': 'Aktualizowanie...', 'updateDialog.status.waitingForServer': 'Oczekiwanie na serwer...', - 'opencodeUpdate.toast.available.title': 'Dostępna aktualizacja OpenCode', - 'opencodeUpdate.toast.available.description': 'Wersja {version} jest gotowa do instalacji.', + 'opencodeUpdate.toast.available.title': 'Aktualizacja OpenCode', + 'opencodeUpdate.toast.available.description': 'Wersja {version} dostępna.', 'opencodeUpdate.toast.actions.update': 'Aktualizuj', 'opencodeUpdate.toast.actions.dismiss': 'Odrzuć', 'opencodeUpdate.toast.actions.reload': 'Przeładuj OpenCode', 'opencodeUpdate.toast.upgrading.title': 'Aktualizowanie OpenCode...', 'opencodeUpdate.toast.upgrading.description': 'Pozostaw OpenChamber otwarte.', 'opencodeUpdate.toast.updated.title': 'OpenCode zaktualizowany', - 'opencodeUpdate.toast.updated.description': 'Przeładuj OpenCode, aby zacząć używać zaktualizowanej wersji.', - 'opencodeUpdate.toast.updated.descriptionWithVersion': 'Wersja {version} jest zainstalowana. Przeładuj OpenCode, aby jej użyć.', + 'opencodeUpdate.toast.updated.description': 'Aktualizacja zainstalowana.', + 'opencodeUpdate.toast.updated.descriptionWithVersion': 'Wersja {version} zainstalowana.', 'opencodeUpdate.toast.failed.title': 'Nie udało się zaktualizować OpenCode', 'opencodeUpdate.toast.failed.description': 'Aktualizacja OpenCode nie powiodła się.', 'opencodeUpdate.toast.reload.message': 'Ponowne uruchamianie OpenCode...', diff --git a/packages/ui/src/lib/i18n/messages/pt-BR.ts b/packages/ui/src/lib/i18n/messages/pt-BR.ts index 05e75d04..6274d841 100644 --- a/packages/ui/src/lib/i18n/messages/pt-BR.ts +++ b/packages/ui/src/lib/i18n/messages/pt-BR.ts @@ -2174,16 +2174,16 @@ export const dict: Record = { "updateDialog.status.updating": "Atualizando...", "updateDialog.error.updateFailed": "Não foi possível atualizar", "updateDialog.error.takingLonger": "A atualização está demorando mais do que o esperado. Aguarde um pouco e atualize, ou execute: openchamber update", - "opencodeUpdate.toast.available.title": "Atualização do OpenCode disponível", - "opencodeUpdate.toast.available.description": "A versão {version} está pronta para instalar.", + "opencodeUpdate.toast.available.title": "Atualização do OpenCode", + "opencodeUpdate.toast.available.description": "Versão {version} disponível.", "opencodeUpdate.toast.actions.update": "Atualizar", "opencodeUpdate.toast.actions.dismiss": "Dispensar", "opencodeUpdate.toast.actions.reload": "Recarregar OpenCode", "opencodeUpdate.toast.upgrading.title": "Atualizando OpenCode...", "opencodeUpdate.toast.upgrading.description": "Mantenha o OpenChamber aberto.", "opencodeUpdate.toast.updated.title": "OpenCode atualizado", - "opencodeUpdate.toast.updated.description": "Recarregue o OpenCode para começar a usar a versão atualizada.", - "opencodeUpdate.toast.updated.descriptionWithVersion": "A versão {version} está instalada. Recarregue o OpenCode para usá-la.", + "opencodeUpdate.toast.updated.description": "Atualização instalada.", + "opencodeUpdate.toast.updated.descriptionWithVersion": "Versão {version} instalada.", "opencodeUpdate.toast.failed.title": "Não foi possível atualizar o OpenCode", "opencodeUpdate.toast.failed.description": "A atualização do OpenCode falhou.", "opencodeUpdate.toast.reload.message": "Reiniciando OpenCode...", diff --git a/packages/ui/src/lib/i18n/messages/uk.ts b/packages/ui/src/lib/i18n/messages/uk.ts index d3292661..7dd35ad2 100644 --- a/packages/ui/src/lib/i18n/messages/uk.ts +++ b/packages/ui/src/lib/i18n/messages/uk.ts @@ -2174,16 +2174,16 @@ export const dict: Record = { "updateDialog.status.updating": "Оновлення...", "updateDialog.error.updateFailed": "Помилка оновлення", "updateDialog.error.takingLonger": "Оновлення триває довше, ніж очікувалося. Зачекайте трохи та оновіть або запустіть: openchamber update", - "opencodeUpdate.toast.available.title": "Доступне оновлення OpenCode", - "opencodeUpdate.toast.available.description": "Версія {version} готова до встановлення.", + "opencodeUpdate.toast.available.title": "Оновлення OpenCode", + "opencodeUpdate.toast.available.description": "Версія {version} доступна.", "opencodeUpdate.toast.actions.update": "Оновити", "opencodeUpdate.toast.actions.dismiss": "Сховати", "opencodeUpdate.toast.actions.reload": "Перезавантажити OpenCode", "opencodeUpdate.toast.upgrading.title": "Оновлення OpenCode...", "opencodeUpdate.toast.upgrading.description": "Залиште OpenChamber відкритим.", "opencodeUpdate.toast.updated.title": "OpenCode оновлено", - "opencodeUpdate.toast.updated.description": "Перезавантажте OpenCode, щоб почати використовувати оновлену версію.", - "opencodeUpdate.toast.updated.descriptionWithVersion": "Версію {version} встановлено. Перезавантажте OpenCode, щоб її використовувати.", + "opencodeUpdate.toast.updated.description": "Оновлення встановлено.", + "opencodeUpdate.toast.updated.descriptionWithVersion": "Версію {version} встановлено.", "opencodeUpdate.toast.failed.title": "Не вдалося оновити OpenCode", "opencodeUpdate.toast.failed.description": "Оновлення OpenCode не вдалося.", "opencodeUpdate.toast.reload.message": "Перезапуск OpenCode...", diff --git a/packages/ui/src/lib/i18n/messages/zh-CN.ts b/packages/ui/src/lib/i18n/messages/zh-CN.ts index 721cb7b7..8c58ebbb 100644 --- a/packages/ui/src/lib/i18n/messages/zh-CN.ts +++ b/packages/ui/src/lib/i18n/messages/zh-CN.ts @@ -2174,16 +2174,16 @@ export const dict: Record = { 'updateDialog.status.updating': '更新中...', 'updateDialog.error.updateFailed': '更新失败', 'updateDialog.error.takingLonger': '更新耗时超出预期。请稍等后刷新,或运行:openchamber update', - 'opencodeUpdate.toast.available.title': 'OpenCode 有可用更新', - 'opencodeUpdate.toast.available.description': '版本 {version} 已可安装。', + 'opencodeUpdate.toast.available.title': 'OpenCode 更新', + 'opencodeUpdate.toast.available.description': '版本 {version} 可用。', 'opencodeUpdate.toast.actions.update': '更新', 'opencodeUpdate.toast.actions.dismiss': '忽略', 'opencodeUpdate.toast.actions.reload': '重载 OpenCode', 'opencodeUpdate.toast.upgrading.title': '正在更新 OpenCode...', 'opencodeUpdate.toast.upgrading.description': '请保持 OpenChamber 打开。', 'opencodeUpdate.toast.updated.title': 'OpenCode 已更新', - 'opencodeUpdate.toast.updated.description': '重载 OpenCode 以开始使用更新后的版本。', - 'opencodeUpdate.toast.updated.descriptionWithVersion': '版本 {version} 已安装。重载 OpenCode 后即可使用。', + 'opencodeUpdate.toast.updated.description': '更新已安装。', + 'opencodeUpdate.toast.updated.descriptionWithVersion': '版本 {version} 已安装。', 'opencodeUpdate.toast.failed.title': '无法更新 OpenCode', 'opencodeUpdate.toast.failed.description': 'OpenCode 升级失败。', 'opencodeUpdate.toast.reload.message': '正在重启 OpenCode...', diff --git a/packages/web/server/lib/opencode/DOCUMENTATION.md b/packages/web/server/lib/opencode/DOCUMENTATION.md index fd74239a..c68d0fda 100644 --- a/packages/web/server/lib/opencode/DOCUMENTATION.md +++ b/packages/web/server/lib/opencode/DOCUMENTATION.md @@ -73,6 +73,8 @@ This module provides OpenCode server integration utilities for the web server ru - `GET /api/config/settings` - `PUT /api/config/settings` - `GET /api/config/opencode-resolution` + - `POST /api/opencode/upgrade` (proxies OpenCode upgrade, then restarts managed OpenCode so the new binary is active) + - `GET /api/opencode/upgrade-status` - `POST /api/opencode/directory` - `GET /api/provider/:providerId/source` - `DELETE /api/provider/:providerId/auth` diff --git a/packages/web/server/lib/opencode/routes.js b/packages/web/server/lib/opencode/routes.js index 24b8bb13..151977a4 100644 --- a/packages/web/server/lib/opencode/routes.js +++ b/packages/web/server/lib/opencode/routes.js @@ -155,7 +155,20 @@ export const registerOpenCodeRoutes = (app, dependencies) => { error: payload?.error || response.statusText || 'Failed to upgrade OpenCode', }); } - return res.json(payload ?? { success: true }); + + try { + await refreshOpenCodeAfterConfigChange('OpenCode upgrade'); + } catch (restartError) { + return res.status(500).json({ + success: false, + upgraded: true, + error: restartError instanceof Error + ? `OpenCode upgraded, but restart failed: ${restartError.message}` + : 'OpenCode upgraded, but restart failed', + }); + } + + return res.json({ ...(payload ?? { success: true }), restarted: true }); } catch (error) { console.error('Failed to upgrade OpenCode:', error); return res.status(500).json({