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
@@ -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;
}
}