feat(opencode): sync all providers to live config on directory change

Add additive mode support for OpenCode in sync_current_to_live:
- Add AppType::is_additive_mode() to distinguish switch vs additive mode
- Add AppType::all() iterator to avoid hardcoding app lists
- Add sync_all_providers_to_live() for additive mode apps
- Refactor sync_current_to_live to handle both modes

Frontend changes (directory settings):
- Track opencodeDirChanged in useDirectorySettings
- Trigger syncCurrentProvidersLiveSafe when OpenCode dir changes
- Add i18n strings for OpenCode directory settings
This commit is contained in:
Jason
2026-01-26 15:46:51 +08:00
parent 29a0643d74
commit c00f431d67
12 changed files with 180 additions and 28 deletions
+52 -8
View File
@@ -5,13 +5,14 @@ import { homeDir, join } from "@tauri-apps/api/path";
import { settingsApi, type AppId } from "@/lib/api";
import type { SettingsFormState } from "./useSettingsForm";
type DirectoryKey = "appConfig" | "claude" | "codex" | "gemini";
type DirectoryKey = "appConfig" | "claude" | "codex" | "gemini" | "opencode";
export interface ResolvedDirectories {
appConfig: string;
claude: string;
codex: string;
gemini: string;
opencode: string;
}
const sanitizeDir = (value?: string | null): string | undefined => {
@@ -39,7 +40,13 @@ const computeDefaultConfigDir = async (
try {
const home = await homeDir();
const folder =
app === "claude" ? ".claude" : app === "codex" ? ".codex" : ".gemini";
app === "claude"
? ".claude"
: app === "codex"
? ".codex"
: app === "gemini"
? ".gemini"
: ".config/opencode";
return await join(home, folder);
} catch (error) {
console.error(
@@ -70,6 +77,7 @@ export interface UseDirectorySettingsResult {
claudeDir?: string,
codexDir?: string,
geminiDir?: string,
opencodeDir?: string,
) => void;
}
@@ -96,6 +104,7 @@ export function useDirectorySettings({
claude: "",
codex: "",
gemini: "",
opencode: "",
});
const [isLoading, setIsLoading] = useState(true);
@@ -104,6 +113,7 @@ export function useDirectorySettings({
claude: "",
codex: "",
gemini: "",
opencode: "",
});
const initialAppConfigDirRef = useRef<string | undefined>(undefined);
@@ -119,19 +129,23 @@ export function useDirectorySettings({
claudeDir,
codexDir,
geminiDir,
opencodeDir,
defaultAppConfig,
defaultClaudeDir,
defaultCodexDir,
defaultGeminiDir,
defaultOpencodeDir,
] = await Promise.all([
settingsApi.getAppConfigDirOverride(),
settingsApi.getConfigDir("claude"),
settingsApi.getConfigDir("codex"),
settingsApi.getConfigDir("gemini"),
settingsApi.getConfigDir("opencode"),
computeDefaultAppConfigDir(),
computeDefaultConfigDir("claude"),
computeDefaultConfigDir("codex"),
computeDefaultConfigDir("gemini"),
computeDefaultConfigDir("opencode"),
]);
if (!active) return;
@@ -143,6 +157,7 @@ export function useDirectorySettings({
claude: defaultClaudeDir ?? "",
codex: defaultCodexDir ?? "",
gemini: defaultGeminiDir ?? "",
opencode: defaultOpencodeDir ?? "",
};
setAppConfigDir(normalizedOverride);
@@ -153,6 +168,7 @@ export function useDirectorySettings({
claude: claudeDir || defaultsRef.current.claude,
codex: codexDir || defaultsRef.current.codex,
gemini: geminiDir || defaultsRef.current.gemini,
opencode: opencodeDir || defaultsRef.current.opencode,
});
} catch (error) {
console.error(
@@ -183,7 +199,9 @@ export function useDirectorySettings({
? { claudeConfigDir: sanitized }
: key === "codex"
? { codexConfigDir: sanitized }
: { geminiConfigDir: sanitized },
: key === "gemini"
? { geminiConfigDir: sanitized }
: { opencodeConfigDir: sanitized },
);
}
@@ -205,7 +223,13 @@ export function useDirectorySettings({
const updateDirectory = useCallback(
(app: AppId, value?: string) => {
updateDirectoryState(
app === "claude" ? "claude" : app === "codex" ? "codex" : "gemini",
app === "claude"
? "claude"
: app === "codex"
? "codex"
: app === "gemini"
? "gemini"
: "opencode",
value,
);
},
@@ -215,13 +239,21 @@ export function useDirectorySettings({
const browseDirectory = useCallback(
async (app: AppId) => {
const key: DirectoryKey =
app === "claude" ? "claude" : app === "codex" ? "codex" : "gemini";
app === "claude"
? "claude"
: app === "codex"
? "codex"
: app === "gemini"
? "gemini"
: "opencode";
const currentValue =
key === "claude"
? (settings?.claudeConfigDir ?? resolvedDirs.claude)
: key === "codex"
? (settings?.codexConfigDir ?? resolvedDirs.codex)
: (settings?.geminiConfigDir ?? resolvedDirs.gemini);
: key === "gemini"
? (settings?.geminiConfigDir ?? resolvedDirs.gemini)
: (settings?.opencodeConfigDir ?? resolvedDirs.opencode);
try {
const picked = await settingsApi.selectConfigDirectory(currentValue);
@@ -263,7 +295,13 @@ export function useDirectorySettings({
const resetDirectory = useCallback(
async (app: AppId) => {
const key: DirectoryKey =
app === "claude" ? "claude" : app === "codex" ? "codex" : "gemini";
app === "claude"
? "claude"
: app === "codex"
? "codex"
: app === "gemini"
? "gemini"
: "opencode";
if (!defaultsRef.current[key]) {
const fallback = await computeDefaultConfigDir(app);
if (fallback) {
@@ -292,7 +330,12 @@ export function useDirectorySettings({
}, [updateDirectoryState]);
const resetAllDirectories = useCallback(
(claudeDir?: string, codexDir?: string, geminiDir?: string) => {
(
claudeDir?: string,
codexDir?: string,
geminiDir?: string,
opencodeDir?: string,
) => {
setAppConfigDir(initialAppConfigDirRef.current);
setResolvedDirs({
appConfig:
@@ -300,6 +343,7 @@ export function useDirectorySettings({
claude: claudeDir ?? defaultsRef.current.claude,
codex: codexDir ?? defaultsRef.current.codex,
gemini: geminiDir ?? defaultsRef.current.gemini,
opencode: opencodeDir ?? defaultsRef.current.opencode,
});
},
[],