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:
Cursor Agent
2026-08-03 06:56:38 +00:00
co-authored by Serhii Dziupin
parent 775da6e9f4
commit 57819dd164
31 changed files with 869 additions and 167 deletions
+37 -15
View File
@@ -1,14 +1,12 @@
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { createDeferredSafeJSONStorage } from './utils/safeStorage';
import {
startConfigUpdate,
finishConfigUpdate,
} from '@/lib/configUpdate';
import { startConfigUpdate } from '@/lib/configUpdate';
import { refreshAfterOpenCodeRestart } from '@/stores/useAgentsStore';
import { useProjectsStore } from '@/stores/useProjectsStore';
import { opencodeClient } from '@/lib/opencode/client';
import { runtimeFetch } from '@/lib/runtime-fetch';
import { noteDeferredRestartFromPayload } from '@/lib/opencode/deferredRestart';
export type PluginScope = 'user' | 'project';
type PluginParsedKind = 'npm' | 'path';
@@ -43,6 +41,8 @@ export type PluginMutationResult = {
reloadFailed?: boolean;
message?: string;
warning?: string;
requiresManualRestart?: boolean;
restartDeferred?: boolean;
};
export type RegistryResult =
@@ -91,6 +91,9 @@ type RegistryInfoResponse = {
type PluginMutationPayload = {
success?: boolean;
requiresReload?: boolean;
requiresRestart?: boolean;
restartDeferred?: boolean;
requiresManualRestart?: boolean;
message?: string;
reloadDelayMs?: number;
reloadFailed?: boolean;
@@ -248,7 +251,7 @@ export const usePluginsStore = create<PluginsStore>()(
body: JSON.stringify(buildEntryBody(input)),
});
return response;
}, get);
}, get, { restartId: input.spec });
if (result.ok) {
void get().loadRegistryInfo({ specs: [input.spec], force: true });
}
@@ -265,7 +268,7 @@ export const usePluginsStore = create<PluginsStore>()(
body: JSON.stringify(buildEntryBody(input)),
});
return response;
}, get);
}, get, { restartId: id });
if (result.ok && nextSpec) {
void get().loadRegistryInfo({ specs: [nextSpec], force: true });
}
@@ -280,7 +283,7 @@ export const usePluginsStore = create<PluginsStore>()(
headers: buildDirectoryHeaders(configDirectory),
});
return response;
}, get);
}, get, { restartId: id });
if (result.ok && get().selectedId === id) {
set({ selectedId: null });
@@ -317,7 +320,7 @@ export const usePluginsStore = create<PluginsStore>()(
body: JSON.stringify(input),
});
return response;
}, get);
}, get, { restartId: input.fileName });
},
updateFile: async (id, input) => {
@@ -328,7 +331,7 @@ export const usePluginsStore = create<PluginsStore>()(
body: JSON.stringify(input),
});
return response;
}, get);
}, get, { restartId: id });
},
deleteFile: async (id) => {
@@ -338,7 +341,7 @@ export const usePluginsStore = create<PluginsStore>()(
headers: buildDirectoryHeaders(configDirectory),
});
return response;
}, get);
}, get, { restartId: id });
if (result.ok && get().selectedId === id) {
set({ selectedId: null });
@@ -425,9 +428,8 @@ async function runPluginMutation(
progressMessage: string,
request: (configDirectory: string | null) => Promise<Response>,
get: () => PluginsStore,
options?: { restartId?: string },
): Promise<PluginMutationResult> {
startConfigUpdate(progressMessage);
let requiresReload = false;
try {
const configDirectory = getConfigDirectory();
const response = await request(configDirectory);
@@ -439,8 +441,30 @@ async function runPluginMutation(
invalidatePluginCache(configDirectory);
if (payload?.requiresManualRestart) {
await get().loadPlugins({ force: true });
return {
ok: true,
requiresManualRestart: true,
reloadFailed: payload?.reloadFailed === true,
message: payload?.message,
warning: payload?.warning,
};
}
if (noteDeferredRestartFromPayload(payload, 'plugins', { id: options?.restartId })) {
await get().loadPlugins({ force: true });
return {
ok: true,
restartDeferred: true,
reloadFailed: payload?.reloadFailed === true,
message: payload?.message,
warning: payload?.warning,
};
}
if (payload?.requiresReload) {
requiresReload = true;
startConfigUpdate(progressMessage);
await refreshAfterOpenCodeRestart({
message: payload.message,
delayMs: payload.reloadDelayMs ?? CLIENT_RELOAD_DELAY_MS,
@@ -458,8 +482,6 @@ async function runPluginMutation(
} catch (error) {
console.error('[PluginsStore] Failed to update plugin configuration:', error);
return { ok: false };
} finally {
if (!requiresReload) finishConfigUpdate();
}
}