diff --git a/src/cliproxy/binary-manager.ts b/src/cliproxy/binary-manager.ts index 51a33d45..7e52e81a 100644 --- a/src/cliproxy/binary-manager.ts +++ b/src/cliproxy/binary-manager.ts @@ -8,7 +8,7 @@ import { info, warn } from '../utils/ui'; import { getBinDir, CLIPROXY_DEFAULT_PORT } from './config-generator'; import { BinaryInfo, BinaryManagerConfig } from './types'; -import { CLIPROXY_FALLBACK_VERSION } from './platform-detector'; +import { CLIPROXY_FALLBACK_VERSION, CLIPROXY_MAX_STABLE_VERSION } from './platform-detector'; import { isProxyRunning, stopProxy } from './services/proxy-lifecycle-service'; import { waitForPortFree } from '../utils/port-utils'; import { @@ -151,11 +151,29 @@ export interface CliproxyUpdateCheckResult { latestVersion: string; fromCache: boolean; checkedAt: number; + // Stability fields + isStable: boolean; + maxStableVersion: string; + stabilityMessage?: string; } /** Check for CLIProxyAPI binary updates */ export async function checkCliproxyUpdate(): Promise { - return new BinaryManager().checkForUpdates(); + const result = await new BinaryManager().checkForUpdates(); + + // Import isNewerVersion for stability check + const { isNewerVersion } = await import('./binary/version-checker'); + const isStable = !isNewerVersion(result.currentVersion, CLIPROXY_MAX_STABLE_VERSION); + const stabilityMessage = isStable + ? undefined + : `v${result.currentVersion} has known stability issues. Max stable: v${CLIPROXY_MAX_STABLE_VERSION}`; + + return { + ...result, + isStable, + maxStableVersion: CLIPROXY_MAX_STABLE_VERSION, + stabilityMessage, + }; } // Re-export version pin functions diff --git a/src/web-server/health/cliproxy-checks.ts b/src/web-server/health/cliproxy-checks.ts index 5c1ee2f1..fb41c7af 100644 --- a/src/web-server/health/cliproxy-checks.ts +++ b/src/web-server/health/cliproxy-checks.ts @@ -15,6 +15,8 @@ import { } from '../../cliproxy'; import { getPortProcess, isCLIProxyProcess } from '../../utils/port-utils'; import type { HealthCheck } from './types'; +import { CLIPROXY_MAX_STABLE_VERSION } from '../../cliproxy/platform-detector'; +import { isNewerVersion } from '../../cliproxy/binary/version-checker'; /** * Check CLIProxy binary installation @@ -23,6 +25,21 @@ export function checkCliproxyBinary(): HealthCheck { if (isCLIProxyInstalled()) { const version = getInstalledCliproxyVersion(); const binaryPath = getCLIProxyPath(); + + // Check if version exceeds stable cap + const isUnstable = isNewerVersion(version, CLIPROXY_MAX_STABLE_VERSION); + + if (isUnstable) { + return { + id: 'cliproxy-binary', + name: 'CLIProxy Binary', + status: 'warning', + message: `v${version} (unstable)`, + details: binaryPath, + fix: `Downgrade: ccs cliproxy install ${CLIPROXY_MAX_STABLE_VERSION.replace(/-\d+$/, '')}`, + }; + } + return { id: 'cliproxy-binary', name: 'CLIProxy Binary', diff --git a/ui/src/components/cliproxy/cliproxy-header.tsx b/ui/src/components/cliproxy/cliproxy-header.tsx index a2d39178..956d37ca 100644 --- a/ui/src/components/cliproxy/cliproxy-header.tsx +++ b/ui/src/components/cliproxy/cliproxy-header.tsx @@ -6,11 +6,17 @@ import { useState, useEffect } from 'react'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; -import { RefreshCw, Loader2 } from 'lucide-react'; +import { RefreshCw, Loader2, AlertTriangle } from 'lucide-react'; import { useCliproxyAuth } from '@/hooks/use-cliproxy'; import { useCliproxyAuthFlow } from '@/hooks/use-cliproxy-auth-flow'; import { cn } from '@/lib/utils'; +interface VersionInfo { + currentVersion: string; + isStable: boolean; + stabilityMessage?: string; +} + interface LoginButtonProps { provider: string; displayName: string; @@ -110,6 +116,22 @@ export function CliproxyHeader({ const { data: authData } = useCliproxyAuth(); const { provider: authProvider, isAuthenticating, startAuth } = useCliproxyAuthFlow(); const lastUpdatedText = useRelativeTime(lastUpdated); + const [versionInfo, setVersionInfo] = useState(null); + + useEffect(() => { + fetch('/api/cliproxy/update-check') + .then((res) => (res.ok ? res.json() : null)) + .then((data) => { + if (data) { + setVersionInfo({ + currentVersion: data.currentVersion, + isStable: data.isStable, + stabilityMessage: data.stabilityMessage, + }); + } + }) + .catch(() => {}); // Silently fail + }, []); const providers = [ { id: 'claude', displayName: 'Claude' }, @@ -170,6 +192,21 @@ export function CliproxyHeader({ {isRunning ? 'Running' : 'Offline'} + {versionInfo && ( + + {!versionInfo.isStable && }v + {versionInfo.currentVersion} + + )} + {lastUpdatedText && ( {lastUpdatedText} )}