diff --git a/ccw/frontend/src/components/session-detail/context/FieldRenderer.tsx b/ccw/frontend/src/components/session-detail/context/FieldRenderer.tsx
index b6478bbe..a1eca72e 100644
--- a/ccw/frontend/src/components/session-detail/context/FieldRenderer.tsx
+++ b/ccw/frontend/src/components/session-detail/context/FieldRenderer.tsx
@@ -58,6 +58,10 @@ function StringRenderer({ value, className }: { value: string; className?: strin
}
function ArrayRenderer({ value, className }: { value: unknown[]; className?: string }) {
+ if (!Array.isArray(value)) {
+ return ;
+ }
+
if (value.length === 0) {
return Empty;
}
@@ -98,6 +102,10 @@ function ObjectRenderer({ value, className }: { value: Record;
}
function FilesRenderer({ value, className }: { value: Array<{ path: string }>; className?: string }) {
+ if (!Array.isArray(value)) {
+ return ;
+ }
+
if (value.length === 0) {
return No files;
}
@@ -118,6 +126,10 @@ function FilesRenderer({ value, className }: { value: Array<{ path: string }>; c
}
function TagsRenderer({ value, className }: { value: string[]; className?: string }) {
+ if (!Array.isArray(value)) {
+ return ;
+ }
+
if (value.length === 0) {
return No tags;
}
diff --git a/ccw/frontend/src/pages/ApiSettingsPage.tsx b/ccw/frontend/src/pages/ApiSettingsPage.tsx
index e94c65df..1e009579 100644
--- a/ccw/frontend/src/pages/ApiSettingsPage.tsx
+++ b/ccw/frontend/src/pages/ApiSettingsPage.tsx
@@ -10,7 +10,7 @@ import {
RefreshCw,
} from 'lucide-react';
import { Button } from '@/components/ui/Button';
-import { TabsNavigation, type TabItem } from '@/components/ui/TabsNavigation';
+import { TabsNavigation } from '@/components/ui/TabsNavigation';
import {
ProviderList,
ProviderModal,
@@ -25,7 +25,7 @@ import {
ManageModelsModal,
} from '@/components/api-settings';
import { ConfigSync } from '@/components/shared';
-import { useProviders, useEndpoints, useModelPools, useCliSettings } from '@/hooks/useApiSettings';
+import { useProviders, useEndpoints, useModelPools, useCliSettings, useSyncApiConfig } from '@/hooks/useApiSettings';
import { useNotifications } from '@/hooks/useNotifications';
// Tab type definitions
@@ -35,6 +35,7 @@ export function ApiSettingsPage() {
const { formatMessage } = useIntl();
const { success, error } = useNotifications();
const [activeTab, setActiveTab] = useState('providers');
+ const syncMutation = useSyncApiConfig();
// Get providers, endpoints, model pools, and CLI settings data
const { providers } = useProviders();
@@ -170,10 +171,19 @@ export function ApiSettingsPage() {
// Sync to CodexLens handler
const handleSyncToCodexLens = async (providerId: string) => {
+ const providerName = providers.find((p) => p.id === providerId)?.name ?? providerId;
+
try {
- // TODO: Implement actual sync API call
- // For now, just show a success message
- success(formatMessage({ id: 'apiSettings.messages.configSynced' }));
+ const result = await syncMutation.mutateAsync();
+ const messageParts = [
+ providerName,
+ result.yamlPath ?? result.message,
+ ].filter(Boolean);
+
+ success(
+ formatMessage({ id: 'apiSettings.messages.configSynced' }),
+ messageParts.length > 0 ? messageParts.join('\n') : undefined
+ );
} catch (err) {
error(formatMessage({ id: 'apiSettings.providers.saveError' }));
}
diff --git a/ccw/frontend/src/pages/SettingsPage.tsx b/ccw/frontend/src/pages/SettingsPage.tsx
index 7de1e903..ab29649d 100644
--- a/ccw/frontend/src/pages/SettingsPage.tsx
+++ b/ccw/frontend/src/pages/SettingsPage.tsx
@@ -25,6 +25,7 @@ import {
Package,
Home,
Folder,
+ FolderOpen,
Calendar,
File,
ArrowUpCircle,
@@ -36,6 +37,7 @@ import { Input } from '@/components/ui/Input';
import { Badge } from '@/components/ui/Badge';
import { ThemeSelector } from '@/components/shared/ThemeSelector';
import { useTheme } from '@/hooks';
+import { toast } from 'sonner';
import { useConfigStore, selectCliTools, selectDefaultCliTool, selectUserPreferences } from '@/stores/configStore';
import type { CliToolConfig, UserPreferences } from '@/types/store';
import { cn } from '@/lib/utils';
@@ -60,9 +62,6 @@ import {
const ENV_FILE_TOOLS = new Set(['gemini', 'qwen', 'opencode']);
/** Tools that use --settings for Claude CLI settings file */
const SETTINGS_FILE_TOOLS = new Set(['claude']);
-/** Tools that don't need any config file */
-const NO_CONFIG_FILE_TOOLS = new Set(['codex']);
-
function getConfigFileType(toolId: string): 'envFile' | 'settingsFile' | 'none' {
if (ENV_FILE_TOOLS.has(toolId)) return 'envFile';
if (SETTINGS_FILE_TOOLS.has(toolId)) return 'settingsFile';
@@ -874,18 +873,13 @@ export function SettingsPage() {
throw new Error(`HTTP ${res.status}`);
}
- // Show success notification via a brief visual indicator
- const toast = document.createElement('div');
- toast.className = 'fixed bottom-4 right-4 z-50 bg-green-600 text-white px-4 py-2 rounded-lg shadow-lg text-sm animate-in fade-in slide-in-from-bottom-2';
- toast.textContent = formatMessage({ id: 'settings.cliTools.configSaved' });
- document.body.appendChild(toast);
- setTimeout(() => toast.remove(), 3000);
+ toast.success(formatMessage({ id: 'settings.cliTools.configSaved' }), {
+ description: toolId,
+ });
} catch {
- const toast = document.createElement('div');
- toast.className = 'fixed bottom-4 right-4 z-50 bg-red-600 text-white px-4 py-2 rounded-lg shadow-lg text-sm animate-in fade-in slide-in-from-bottom-2';
- toast.textContent = formatMessage({ id: 'settings.cliTools.configSaveError' });
- document.body.appendChild(toast);
- setTimeout(() => toast.remove(), 4000);
+ toast.error(formatMessage({ id: 'settings.cliTools.configSaveError' }), {
+ description: toolId,
+ });
} finally {
setSavingTools((prev) => {
const next = new Set(prev);
diff --git a/ccw/frontend/src/stores/configStore.ts b/ccw/frontend/src/stores/configStore.ts
index f323f9a9..fa026fc4 100644
--- a/ccw/frontend/src/stores/configStore.ts
+++ b/ccw/frontend/src/stores/configStore.ts
@@ -43,6 +43,13 @@ const defaultCliTools: Record = {
tags: [],
type: 'builtin',
},
+ opencode: {
+ enabled: true,
+ primaryModel: 'opencode/glm-4.7-free',
+ secondaryModel: 'opencode/glm-4.7-free',
+ tags: [],
+ type: 'builtin',
+ },
};
// Default API endpoints