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
@@ -10,6 +10,7 @@ import {
|
||||
import { createDeferredSafeJSONStorage } from "./utils/safeStorage";
|
||||
import { runtimeFetch } from "@/lib/runtime-fetch";
|
||||
import { runBackgroundNetworkTask } from "@/lib/background-network";
|
||||
import { noteDeferredRestartFromPayload } from "@/lib/opencode/deferredRestart";
|
||||
|
||||
import { opencodeClient } from '@/lib/opencode/client';
|
||||
|
||||
@@ -173,6 +174,44 @@ export const invalidateSkillsLoadCache = (directory: string | null = getCurrentD
|
||||
skillsLastLoadedAt.delete(getSkillsCacheKey(directory));
|
||||
};
|
||||
|
||||
const upsertSkillLocal = (
|
||||
set: (state: Partial<SkillsStore>) => void,
|
||||
get: () => SkillsStore,
|
||||
name: string,
|
||||
config: Partial<SkillConfig>,
|
||||
) => {
|
||||
const existing = get().skills.find((skill) => skill.name === name);
|
||||
const path = config.targetPath ?? existing?.path ?? '';
|
||||
const nextSkill: DiscoveredSkill = {
|
||||
...existing,
|
||||
name,
|
||||
path,
|
||||
scope: config.scope ?? existing?.scope ?? 'user',
|
||||
source: config.source ?? existing?.source ?? 'opencode',
|
||||
description: config.description ?? existing?.description ?? '',
|
||||
group: parseSkillGroup(path),
|
||||
};
|
||||
const skills = get().skills;
|
||||
const nextSkills = skills.some((skill) => skill.name === name)
|
||||
? skills.map((skill) => (skill.name === name ? nextSkill : skill))
|
||||
: [...skills, nextSkill];
|
||||
set({ skills: nextSkills });
|
||||
};
|
||||
|
||||
const removeSkillLocal = (
|
||||
set: (state: Partial<SkillsStore>) => void,
|
||||
get: () => SkillsStore,
|
||||
name: string,
|
||||
) => {
|
||||
const nextState: Partial<SkillsStore> = {
|
||||
skills: get().skills.filter((skill) => skill.name !== name),
|
||||
};
|
||||
if (get().selectedSkillName === name) {
|
||||
nextState.selectedSkillName = null;
|
||||
}
|
||||
set(nextState);
|
||||
};
|
||||
|
||||
const MAX_HEALTH_WAIT_MS = 20000;
|
||||
const FAST_HEALTH_POLL_INTERVAL_MS = 300;
|
||||
const FAST_HEALTH_POLL_ATTEMPTS = 4;
|
||||
@@ -278,8 +317,6 @@ export const useSkillsStore = create<SkillsStore>()(
|
||||
},
|
||||
|
||||
createSkill: async (config: SkillConfig) => {
|
||||
startConfigUpdate("Creating skill...");
|
||||
let requiresReload = false;
|
||||
try {
|
||||
const skillConfig: Record<string, unknown> = {
|
||||
name: config.name,
|
||||
@@ -306,10 +343,21 @@ export const useSkillsStore = create<SkillsStore>()(
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const needsReload = payload?.requiresReload ?? false;
|
||||
invalidateSkillsLoadCache(currentDirectory);
|
||||
if (needsReload) {
|
||||
requiresReload = true;
|
||||
|
||||
if (payload?.requiresManualRestart) {
|
||||
upsertSkillLocal(set, get, config.name, config);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (noteDeferredRestartFromPayload(payload, 'skills', { id: config.name })) {
|
||||
upsertSkillLocal(set, get, config.name, config);
|
||||
emitConfigChange("skills", { source: CONFIG_EVENT_SOURCE });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (payload?.requiresReload) {
|
||||
startConfigUpdate("Creating skill...");
|
||||
await refreshSkillsAfterOpenCodeRestart({
|
||||
message: payload?.message,
|
||||
delayMs: payload?.reloadDelayMs,
|
||||
@@ -324,16 +372,10 @@ export const useSkillsStore = create<SkillsStore>()(
|
||||
return loaded;
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
if (!requiresReload) {
|
||||
finishConfigUpdate();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
updateSkill: async (name: string, config: Partial<SkillConfig>) => {
|
||||
startConfigUpdate("Updating skill...");
|
||||
let requiresReload = false;
|
||||
try {
|
||||
const skillConfig: Record<string, unknown> = {};
|
||||
|
||||
@@ -357,10 +399,21 @@ export const useSkillsStore = create<SkillsStore>()(
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const needsReload = payload?.requiresReload ?? false;
|
||||
invalidateSkillsLoadCache(currentDirectory);
|
||||
if (needsReload) {
|
||||
requiresReload = true;
|
||||
|
||||
if (payload?.requiresManualRestart) {
|
||||
upsertSkillLocal(set, get, name, config);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (noteDeferredRestartFromPayload(payload, 'skills', { id: name })) {
|
||||
upsertSkillLocal(set, get, name, config);
|
||||
emitConfigChange("skills", { source: CONFIG_EVENT_SOURCE });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (payload?.requiresReload) {
|
||||
startConfigUpdate("Updating skill...");
|
||||
await refreshSkillsAfterOpenCodeRestart({
|
||||
message: payload?.message,
|
||||
delayMs: payload?.reloadDelayMs,
|
||||
@@ -375,16 +428,10 @@ export const useSkillsStore = create<SkillsStore>()(
|
||||
return loaded;
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
if (!requiresReload) {
|
||||
finishConfigUpdate();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
deleteSkill: async (name: string) => {
|
||||
startConfigUpdate("Deleting skill...");
|
||||
let requiresReload = false;
|
||||
try {
|
||||
const currentDirectory = getCurrentDirectory();
|
||||
const queryParams = currentDirectory ? `?directory=${encodeURIComponent(currentDirectory)}` : '';
|
||||
@@ -399,10 +446,21 @@ export const useSkillsStore = create<SkillsStore>()(
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const needsReload = payload?.requiresReload ?? false;
|
||||
invalidateSkillsLoadCache(currentDirectory);
|
||||
if (needsReload) {
|
||||
requiresReload = true;
|
||||
|
||||
if (payload?.requiresManualRestart) {
|
||||
removeSkillLocal(set, get, name);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (noteDeferredRestartFromPayload(payload, 'skills', { id: name })) {
|
||||
removeSkillLocal(set, get, name);
|
||||
emitConfigChange("skills", { source: CONFIG_EVENT_SOURCE });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (payload?.requiresReload) {
|
||||
startConfigUpdate("Deleting skill...");
|
||||
await refreshSkillsAfterOpenCodeRestart({
|
||||
message: payload?.message,
|
||||
delayMs: payload?.reloadDelayMs,
|
||||
@@ -422,10 +480,6 @@ export const useSkillsStore = create<SkillsStore>()(
|
||||
return loaded;
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
if (!requiresReload) {
|
||||
finishConfigUpdate();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user