Accumulate OpenCode settings restarts behind Apply & Restart
Defer OpenCode reloads after settings mutations, track pending changes, and expose a top-right Apply & Restart OpenCode action with a counter so sessions stay available until the user explicitly applies. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
This commit is contained in:
co-authored by
Serhii Dziupin
parent
775da6e9f4
commit
57819dd164
@@ -1356,6 +1356,11 @@ export interface SkillsInstallResponse {
|
||||
skipped?: Array<{ skillName: string; reason: string }>;
|
||||
error?: SkillsInstallError;
|
||||
requiresReload?: boolean;
|
||||
requiresRestart?: boolean;
|
||||
restartDeferred?: boolean;
|
||||
requiresManualRestart?: boolean;
|
||||
reloadFailed?: boolean;
|
||||
warning?: string;
|
||||
message?: string;
|
||||
reloadDelayMs?: number;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,14 @@ export const settingsDict = {
|
||||
'settings.view.badge.beta': 'beta',
|
||||
'settings.view.actions.reloadOpenCode': 'Reload OpenCode',
|
||||
'settings.view.actions.reloadOpenCodeTooltip': 'Restart OpenCode and reload its configuration.',
|
||||
'settings.view.actions.applyAndRestartOpenCode': 'Apply & Restart OpenCode',
|
||||
'settings.view.actions.applyAndRestartOpenCodeTooltipSingle': 'Apply 1 pending configuration change and restart OpenCode.',
|
||||
'settings.view.actions.applyAndRestartOpenCodeTooltipPlural': 'Apply {count} pending configuration changes and restart OpenCode.',
|
||||
'settings.view.pendingRestart.applying': 'Applying changes and restarting OpenCode…',
|
||||
'settings.view.pendingRestart.applied': 'OpenCode restarted with pending configuration changes.',
|
||||
'settings.view.pendingRestart.applyFailed': 'Failed to apply configuration changes.',
|
||||
'settings.view.pendingRestart.manualRestartRequired': 'Saved on disk. Restart your connected OpenCode server to apply the changes.',
|
||||
'settings.view.pendingRestart.saved': 'Saved. Restart OpenCode to apply.',
|
||||
'settings.view.actions.backToSettings': 'Back to Settings',
|
||||
'settings.view.actions.closeSettings': 'Close settings',
|
||||
'settings.view.actions.openSectionList': 'Open section list',
|
||||
|
||||
@@ -40,6 +40,15 @@ export const settingsDict = {
|
||||
"settings.view.badge.beta": "beta",
|
||||
"settings.view.actions.reloadOpenCode": "Recargar OpenCode",
|
||||
"settings.view.actions.reloadOpenCodeTooltip": "Restablecer OpenCode y recargar su configuración.",
|
||||
|
||||
"settings.view.actions.applyAndRestartOpenCode": "Aplicar y reiniciar OpenCode",
|
||||
"settings.view.actions.applyAndRestartOpenCodeTooltipSingle": "Aplicar 1 cambio de configuración pendiente y reiniciar OpenCode.",
|
||||
"settings.view.actions.applyAndRestartOpenCodeTooltipPlural": "Aplicar {count} cambios de configuración pendientes y reiniciar OpenCode.",
|
||||
"settings.view.pendingRestart.applying": "Aplicando cambios y reiniciando OpenCode…",
|
||||
"settings.view.pendingRestart.applied": "OpenCode se reinició con los cambios de configuración pendientes.",
|
||||
"settings.view.pendingRestart.applyFailed": "No se pudieron aplicar los cambios de configuración.",
|
||||
"settings.view.pendingRestart.manualRestartRequired": "Guardado en disco. Reinicia el servidor OpenCode conectado para aplicar los cambios.",
|
||||
"settings.view.pendingRestart.saved": "Guardado. Reinicia OpenCode para aplicar.",
|
||||
"settings.view.actions.backToSettings": "Volver a Configuración",
|
||||
"settings.view.actions.closeSettings": "Cerrar configuración",
|
||||
"settings.view.actions.openSectionList": "Abrir lista de secciones",
|
||||
|
||||
@@ -40,6 +40,15 @@ export const settingsDict = {
|
||||
'settings.view.badge.beta': 'bêta',
|
||||
'settings.view.actions.reloadOpenCode': 'Recharger OpenCode',
|
||||
'settings.view.actions.reloadOpenCodeTooltip': 'Redémarrez OpenCode et rechargez sa configuration.',
|
||||
|
||||
'settings.view.actions.applyAndRestartOpenCode': 'Appliquer et redémarrer OpenCode',
|
||||
'settings.view.actions.applyAndRestartOpenCodeTooltipSingle': 'Appliquer 1 modification de configuration en attente et redémarrer OpenCode.',
|
||||
'settings.view.actions.applyAndRestartOpenCodeTooltipPlural': 'Appliquer {count} modifications de configuration en attente et redémarrer OpenCode.',
|
||||
'settings.view.pendingRestart.applying': 'Application des modifications et redémarrage de OpenCode…',
|
||||
'settings.view.pendingRestart.applied': 'OpenCode a redémarré avec les modifications de configuration en attente.',
|
||||
'settings.view.pendingRestart.applyFailed': 'Échec de l\'application des modifications de configuration.',
|
||||
'settings.view.pendingRestart.manualRestartRequired': 'Enregistré sur le disque. Redémarrez le serveur OpenCode connecté pour appliquer les modifications.',
|
||||
'settings.view.pendingRestart.saved': 'Enregistré. Redémarrez OpenCode pour appliquer.',
|
||||
'settings.view.actions.backToSettings': 'Retour aux paramètres',
|
||||
'settings.view.actions.closeSettings': 'Fermer les paramètres',
|
||||
'settings.view.actions.openSectionList': 'Ouvrir la liste des sections',
|
||||
|
||||
@@ -40,6 +40,15 @@ export const settingsDict = {
|
||||
'settings.view.badge.beta': 'ベータ',
|
||||
'settings.view.actions.reloadOpenCode': 'OpenCode を再読み込み',
|
||||
'settings.view.actions.reloadOpenCodeTooltip': 'OpenCode を再起動して設定を再読み込みします。',
|
||||
|
||||
'settings.view.actions.applyAndRestartOpenCode': '適用して OpenCode を再起動',
|
||||
'settings.view.actions.applyAndRestartOpenCodeTooltipSingle': '保留中の設定変更 1 件を適用して OpenCode を再起動します。',
|
||||
'settings.view.actions.applyAndRestartOpenCodeTooltipPlural': '保留中の設定変更 {count} 件を適用して OpenCode を再起動します。',
|
||||
'settings.view.pendingRestart.applying': '変更を適用して OpenCode を再起動しています…',
|
||||
'settings.view.pendingRestart.applied': '保留中の設定変更を適用して OpenCode を再起動しました。',
|
||||
'settings.view.pendingRestart.applyFailed': '設定変更の適用に失敗しました。',
|
||||
'settings.view.pendingRestart.manualRestartRequired': 'ディスクに保存しました。接続中の OpenCode サーバーを再起動して変更を適用してください。',
|
||||
'settings.view.pendingRestart.saved': '保存しました。適用するには OpenCode を再起動してください。',
|
||||
'settings.view.actions.backToSettings': '設定に戻る',
|
||||
'settings.view.actions.closeSettings': '設定を閉じる',
|
||||
'settings.view.actions.openSectionList': 'セクション一覧を開く',
|
||||
|
||||
@@ -40,6 +40,15 @@ export const settingsDict = {
|
||||
'settings.view.badge.beta': 'beta',
|
||||
'settings.view.actions.reloadOpenCode': 'OpenCode 다시 로드',
|
||||
'settings.view.actions.reloadOpenCodeTooltip': 'OpenCode를 재시작하고 설정을 다시 불러옵니다.',
|
||||
|
||||
'settings.view.actions.applyAndRestartOpenCode': '적용하고 OpenCode 다시 시작',
|
||||
'settings.view.actions.applyAndRestartOpenCodeTooltipSingle': '보류 중인 구성 변경 1개를 적용하고 OpenCode를 다시 시작합니다.',
|
||||
'settings.view.actions.applyAndRestartOpenCodeTooltipPlural': '보류 중인 구성 변경 {count}개를 적용하고 OpenCode를 다시 시작합니다.',
|
||||
'settings.view.pendingRestart.applying': '변경 사항을 적용하고 OpenCode를 다시 시작하는 중…',
|
||||
'settings.view.pendingRestart.applied': '보류 중인 구성 변경으로 OpenCode를 다시 시작했습니다.',
|
||||
'settings.view.pendingRestart.applyFailed': '구성 변경을 적용하지 못했습니다.',
|
||||
'settings.view.pendingRestart.manualRestartRequired': '디스크에 저장되었습니다. 연결된 OpenCode 서버를 다시 시작하여 변경 사항을 적용하세요.',
|
||||
'settings.view.pendingRestart.saved': '저장되었습니다. 적용하려면 OpenCode를 다시 시작하세요.',
|
||||
'settings.view.actions.backToSettings': '설정으로 돌아가기',
|
||||
'settings.view.actions.closeSettings': '설정 닫기',
|
||||
'settings.view.actions.openSectionList': '섹션 목록 열기',
|
||||
|
||||
@@ -1913,6 +1913,15 @@ export const settingsDict = {
|
||||
'settings.view.actions.openSectionList': 'Otwórz listę sekcji',
|
||||
'settings.view.actions.reloadOpenCode': 'Przeładuj OpenCode',
|
||||
'settings.view.actions.reloadOpenCodeTooltip': 'Uruchom ponownie OpenCode i przeładuj jego konfigurację.',
|
||||
|
||||
'settings.view.actions.applyAndRestartOpenCode': 'Zastosuj i uruchom ponownie OpenCode',
|
||||
'settings.view.actions.applyAndRestartOpenCodeTooltipSingle': 'Zastosuj 1 oczekującą zmianę konfiguracji i uruchom ponownie OpenCode.',
|
||||
'settings.view.actions.applyAndRestartOpenCodeTooltipPlural': 'Zastosuj {count} oczekujących zmian konfiguracji i uruchom ponownie OpenCode.',
|
||||
'settings.view.pendingRestart.applying': 'Stosowanie zmian i ponowne uruchamianie OpenCode…',
|
||||
'settings.view.pendingRestart.applied': 'OpenCode został uruchomiony ponownie z oczekującymi zmianami konfiguracji.',
|
||||
'settings.view.pendingRestart.applyFailed': 'Nie udało się zastosować zmian konfiguracji.',
|
||||
'settings.view.pendingRestart.manualRestartRequired': 'Zapisano na dysku. Uruchom ponownie podłączony serwer OpenCode, aby zastosować zmiany.',
|
||||
'settings.view.pendingRestart.saved': 'Zapisano. Uruchom ponownie OpenCode, aby zastosować.',
|
||||
'settings.view.actions.resizeNavigation': 'Zmień rozmiar nawigacji',
|
||||
'settings.view.search.placeholder': 'Szukaj ustawień',
|
||||
'settings.view.search.aria': 'Szukaj ustawień',
|
||||
|
||||
@@ -40,6 +40,15 @@ export const settingsDict = {
|
||||
"settings.view.badge.beta": "beta",
|
||||
"settings.view.actions.reloadOpenCode": "Recarregar OpenCode",
|
||||
"settings.view.actions.reloadOpenCodeTooltip": "Reiniciar OpenCode e recarregar suas configurações.",
|
||||
|
||||
"settings.view.actions.applyAndRestartOpenCode": "Aplicar e reiniciar o OpenCode",
|
||||
"settings.view.actions.applyAndRestartOpenCodeTooltipSingle": "Aplicar 1 alteração de configuração pendente e reiniciar o OpenCode.",
|
||||
"settings.view.actions.applyAndRestartOpenCodeTooltipPlural": "Aplicar {count} alterações de configuração pendentes e reiniciar o OpenCode.",
|
||||
"settings.view.pendingRestart.applying": "Aplicando alterações e reiniciando o OpenCode…",
|
||||
"settings.view.pendingRestart.applied": "OpenCode reiniciado com as alterações de configuração pendentes.",
|
||||
"settings.view.pendingRestart.applyFailed": "Falha ao aplicar as alterações de configuração.",
|
||||
"settings.view.pendingRestart.manualRestartRequired": "Salvo no disco. Reinicie o servidor OpenCode conectado para aplicar as alterações.",
|
||||
"settings.view.pendingRestart.saved": "Salvo. Reinicie o OpenCode para aplicar.",
|
||||
"settings.view.actions.backToSettings": "Voltar às configurações",
|
||||
"settings.view.actions.closeSettings": "Fechar configurações",
|
||||
"settings.view.actions.openSectionList": "Abrir lista de seções",
|
||||
|
||||
@@ -40,6 +40,15 @@ export const settingsDict = {
|
||||
"settings.view.badge.beta": "бета-версія",
|
||||
"settings.view.actions.reloadOpenCode": "Перезавантажити OpenCode",
|
||||
"settings.view.actions.reloadOpenCodeTooltip": "Перезапустити OpenCode і перезавантажити його конфігурацію.",
|
||||
|
||||
"settings.view.actions.applyAndRestartOpenCode": "Застосувати та перезапустити OpenCode",
|
||||
"settings.view.actions.applyAndRestartOpenCodeTooltipSingle": "Застосувати 1 очікувану зміну конфігурації та перезапустити OpenCode.",
|
||||
"settings.view.actions.applyAndRestartOpenCodeTooltipPlural": "Застосувати {count} очікуваних змін конфігурації та перезапустити OpenCode.",
|
||||
"settings.view.pendingRestart.applying": "Застосування змін і перезапуск OpenCode…",
|
||||
"settings.view.pendingRestart.applied": "OpenCode перезапущено з очікуваними змінами конфігурації.",
|
||||
"settings.view.pendingRestart.applyFailed": "Не вдалося застосувати зміни конфігурації.",
|
||||
"settings.view.pendingRestart.manualRestartRequired": "Збережено на диску. Перезапустіть підключений сервер OpenCode, щоб застосувати зміни.",
|
||||
"settings.view.pendingRestart.saved": "Збережено. Перезапустіть OpenCode, щоб застосувати.",
|
||||
"settings.view.actions.backToSettings": "Назад до налаштувань",
|
||||
"settings.view.actions.closeSettings": "Закрити налаштування",
|
||||
"settings.view.actions.openSectionList": "Відкрити список розділів",
|
||||
|
||||
@@ -40,6 +40,15 @@ export const settingsDict = {
|
||||
'settings.view.badge.beta': '测试版',
|
||||
'settings.view.actions.reloadOpenCode': '重新加载 OpenCode',
|
||||
'settings.view.actions.reloadOpenCodeTooltip': '重启 OpenCode 并重新加载其配置。',
|
||||
|
||||
'settings.view.actions.applyAndRestartOpenCode': '应用并重启 OpenCode',
|
||||
'settings.view.actions.applyAndRestartOpenCodeTooltipSingle': '应用 1 项待处理的配置更改并重启 OpenCode。',
|
||||
'settings.view.actions.applyAndRestartOpenCodeTooltipPlural': '应用 {count} 项待处理的配置更改并重启 OpenCode。',
|
||||
'settings.view.pendingRestart.applying': '正在应用更改并重启 OpenCode…',
|
||||
'settings.view.pendingRestart.applied': '已使用待处理的配置更改重启 OpenCode。',
|
||||
'settings.view.pendingRestart.applyFailed': '应用配置更改失败。',
|
||||
'settings.view.pendingRestart.manualRestartRequired': '已保存到磁盘。请重启已连接的 OpenCode 服务器以应用更改。',
|
||||
'settings.view.pendingRestart.saved': '已保存。请重启 OpenCode 以应用。',
|
||||
'settings.view.actions.backToSettings': '返回设置',
|
||||
'settings.view.actions.closeSettings': '关闭设置',
|
||||
'settings.view.actions.openSectionList': '打开分组列表',
|
||||
|
||||
@@ -38,6 +38,15 @@
|
||||
'settings.view.badge.beta': 'beta',
|
||||
'settings.view.actions.reloadOpenCode': '重新載入 OpenCode',
|
||||
'settings.view.actions.reloadOpenCodeTooltip': '重新啟動 OpenCode 並重新載入其設定。',
|
||||
|
||||
'settings.view.actions.applyAndRestartOpenCode': '套用並重新啟動 OpenCode',
|
||||
'settings.view.actions.applyAndRestartOpenCodeTooltipSingle': '套用 1 項待處理的設定變更並重新啟動 OpenCode。',
|
||||
'settings.view.actions.applyAndRestartOpenCodeTooltipPlural': '套用 {count} 項待處理的設定變更並重新啟動 OpenCode。',
|
||||
'settings.view.pendingRestart.applying': '正在套用變更並重新啟動 OpenCode…',
|
||||
'settings.view.pendingRestart.applied': '已使用待處理的設定變更重新啟動 OpenCode。',
|
||||
'settings.view.pendingRestart.applyFailed': '套用設定變更失敗。',
|
||||
'settings.view.pendingRestart.manualRestartRequired': '已儲存到磁碟。請重新啟動已連線的 OpenCode 伺服器以套用變更。',
|
||||
'settings.view.pendingRestart.saved': '已儲存。請重新啟動 OpenCode 以套用。',
|
||||
'settings.view.actions.backToSettings': '返回設定頁',
|
||||
'settings.view.actions.closeSettings': '關閉設定',
|
||||
'settings.view.actions.openSectionList': '開啟群組清單',
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { describe, expect, mock, test } from 'bun:test';
|
||||
|
||||
describe('deferred OpenCode restart helpers', () => {
|
||||
test('isDeferredRestartPayload detects deferred responses', async () => {
|
||||
const { isDeferredRestartPayload } = await import('./deferredRestart');
|
||||
|
||||
expect(isDeferredRestartPayload({
|
||||
requiresReload: false,
|
||||
requiresRestart: true,
|
||||
restartDeferred: true,
|
||||
})).toBe(true);
|
||||
|
||||
expect(isDeferredRestartPayload({
|
||||
requiresReload: false,
|
||||
requiresRestart: true,
|
||||
})).toBe(true);
|
||||
|
||||
expect(isDeferredRestartPayload({
|
||||
requiresReload: true,
|
||||
message: 'reloading',
|
||||
})).toBe(false);
|
||||
|
||||
expect(isDeferredRestartPayload({
|
||||
requiresManualRestart: true,
|
||||
requiresRestart: true,
|
||||
restartDeferred: true,
|
||||
})).toBe(false);
|
||||
});
|
||||
|
||||
test('noteDeferredRestartFromPayload records pending changes', async () => {
|
||||
const { usePendingOpenCodeRestartStore } = await import('@/stores/usePendingOpenCodeRestartStore');
|
||||
usePendingOpenCodeRestartStore.getState().clear();
|
||||
|
||||
const { noteDeferredRestartFromPayload } = await import('./deferredRestart');
|
||||
const noted = noteDeferredRestartFromPayload({
|
||||
requiresReload: false,
|
||||
requiresRestart: true,
|
||||
restartDeferred: true,
|
||||
}, 'mcp', { id: 'filesystem' });
|
||||
|
||||
expect(noted).toBe(true);
|
||||
expect(usePendingOpenCodeRestartStore.getState().changes).toHaveLength(1);
|
||||
expect(usePendingOpenCodeRestartStore.getState().changes[0]?.scope).toBe('mcp');
|
||||
});
|
||||
|
||||
test('applyPendingOpenCodeRestart clears pending changes after success', async () => {
|
||||
mock.module('@/stores/useAgentsStore', () => ({
|
||||
reloadOpenCodeConfiguration: async () => undefined,
|
||||
}));
|
||||
|
||||
const { usePendingOpenCodeRestartStore } = await import('@/stores/usePendingOpenCodeRestartStore');
|
||||
usePendingOpenCodeRestartStore.getState().clear();
|
||||
usePendingOpenCodeRestartStore.getState().recordChange({ scope: 'agents', id: 'demo' });
|
||||
|
||||
const { applyPendingOpenCodeRestart } = await import('./deferredRestart');
|
||||
const result = await applyPendingOpenCodeRestart({ message: 'Applying…' });
|
||||
|
||||
expect(result).toEqual({ ok: true });
|
||||
expect(usePendingOpenCodeRestartStore.getState().changes).toHaveLength(0);
|
||||
expect(usePendingOpenCodeRestartStore.getState().isApplying).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
usePendingOpenCodeRestartStore,
|
||||
type PendingOpenCodeRestartScope,
|
||||
} from '@/stores/usePendingOpenCodeRestartStore';
|
||||
|
||||
export type ConfigMutationPayload = {
|
||||
requiresReload?: boolean;
|
||||
requiresRestart?: boolean;
|
||||
restartDeferred?: boolean;
|
||||
requiresManualRestart?: boolean;
|
||||
reloadFailed?: boolean;
|
||||
message?: string;
|
||||
warning?: string;
|
||||
reloadDelayMs?: number;
|
||||
} | null | undefined;
|
||||
|
||||
export function isDeferredRestartPayload(payload: ConfigMutationPayload): boolean {
|
||||
if (!payload || typeof payload !== 'object') {
|
||||
return false;
|
||||
}
|
||||
if (payload.requiresManualRestart === true) {
|
||||
return false;
|
||||
}
|
||||
return payload.restartDeferred === true || (payload.requiresRestart === true && payload.requiresReload !== true);
|
||||
}
|
||||
|
||||
export function recordDeferredOpenCodeRestart(
|
||||
scope: PendingOpenCodeRestartScope,
|
||||
options?: { id?: string; label?: string },
|
||||
): void {
|
||||
usePendingOpenCodeRestartStore.getState().recordChange({
|
||||
scope,
|
||||
id: options?.id,
|
||||
label: options?.label,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* If the mutation response deferred the OpenCode restart, record it and return true.
|
||||
* Callers should skip immediate refresh overlays when this returns true.
|
||||
*/
|
||||
export function noteDeferredRestartFromPayload(
|
||||
payload: ConfigMutationPayload,
|
||||
scope: PendingOpenCodeRestartScope,
|
||||
options?: { id?: string; label?: string },
|
||||
): boolean {
|
||||
if (!isDeferredRestartPayload(payload)) {
|
||||
return false;
|
||||
}
|
||||
recordDeferredOpenCodeRestart(scope, options);
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function applyPendingOpenCodeRestart(options?: {
|
||||
message?: string;
|
||||
}): Promise<{ ok: boolean; requiresManualRestart?: boolean }> {
|
||||
const store = usePendingOpenCodeRestartStore.getState();
|
||||
if (store.isApplying) {
|
||||
return { ok: false };
|
||||
}
|
||||
|
||||
store.setApplying(true);
|
||||
try {
|
||||
const { reloadOpenCodeConfiguration } = await import('@/stores/useAgentsStore');
|
||||
await reloadOpenCodeConfiguration({
|
||||
message: options?.message,
|
||||
mode: 'projects',
|
||||
scopes: ['all'],
|
||||
});
|
||||
usePendingOpenCodeRestartStore.getState().clear();
|
||||
return { ok: true };
|
||||
} catch (error) {
|
||||
usePendingOpenCodeRestartStore.getState().setApplying(false);
|
||||
if ((error as Error & { requiresManualRestart?: boolean })?.requiresManualRestart) {
|
||||
return { ok: false, requiresManualRestart: true };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user