diff --git a/.github/workflows/ai-code-review.yml b/.github/workflows/ai-code-review.yml index e3183b55..f690c58d 100644 --- a/.github/workflows/ai-code-review.yml +++ b/.github/workflows/ai-code-review.yml @@ -13,10 +13,16 @@ on: types: [opened, synchronize, reopened] issue_comment: types: [created] + workflow_dispatch: + inputs: + pr_number: + description: 'PR number to review' + required: true + type: string # Cancel in-progress runs for same PR concurrency: - group: ai-review-${{ github.event.pull_request.number || github.event.issue.number }} + group: ai-review-${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number }} cancel-in-progress: true jobs: @@ -30,6 +36,7 @@ jobs: # - Comment event: only if it's a PR and contains /review if: > github.event_name == 'pull_request' || + github.event_name == 'workflow_dispatch' || (github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '/review')) @@ -52,6 +59,8 @@ jobs: run: | if [ "${{ github.event_name }}" = "pull_request" ]; then echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT + elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "number=${{ github.event.inputs.pr_number }}" >> $GITHUB_OUTPUT else echo "number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT fi diff --git a/package.json b/package.json index 56aa1cec..20f213bf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kaitranntt/ccs", - "version": "7.15.0-dev.3", + "version": "7.15.0-dev.5", "description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6", "keywords": [ "cli", diff --git a/src/cliproxy/binary/lifecycle.ts b/src/cliproxy/binary/lifecycle.ts index 51867a4e..2aaf8f6c 100644 --- a/src/cliproxy/binary/lifecycle.ts +++ b/src/cliproxy/binary/lifecycle.ts @@ -5,12 +5,17 @@ import * as fs from 'fs'; import { BinaryManagerConfig } from '../types'; -import { checkForUpdates, fetchLatestVersion, isNewerVersion } from './version-checker'; +import { + checkForUpdates, + fetchLatestVersion, + isNewerVersion, + isVersionFaulty, +} from './version-checker'; import { downloadAndInstall, deleteBinary, getBinaryPath } from './installer'; import { info, warn } from '../../utils/ui'; import { isCliproxyRunning } from '../stats-fetcher'; import { CLIPROXY_DEFAULT_PORT } from '../config-generator'; -import { CLIPROXY_MAX_STABLE_VERSION } from '../platform-detector'; +import { CLIPROXY_MAX_STABLE_VERSION, CLIPROXY_FAULTY_RANGE } from '../platform-detector'; /** Log helper */ function log(message: string, verbose: boolean): void { @@ -46,15 +51,26 @@ async function handleAutoUpdate(config: BinaryManagerConfig, verbose: boolean): const currentVersion = updateResult.currentVersion; const latestVersion = updateResult.latestVersion; - // Check if user is on known unstable version - inform but don't force downgrade - if (isAboveMaxStable(currentVersion)) { + // Check if user is on known faulty version - recommend upgrade + if (isVersionFaulty(currentVersion)) { console.log( warn( - `CLIProxy Plus v${currentVersion} has known stability issues. ` + - `Stable version: v${CLIPROXY_MAX_STABLE_VERSION}` + `CLIProxy Plus v${currentVersion} has known bugs (v${CLIPROXY_FAULTY_RANGE.min.replace(/-\d+$/, '')}-${CLIPROXY_FAULTY_RANGE.max.replace(/-\d+$/, '')}). ` + + `Upgrade to v${CLIPROXY_MAX_STABLE_VERSION.replace(/-\d+$/, '')} recommended.` + ) + ); + console.log( + info( + `Run "ccs cliproxy install ${CLIPROXY_MAX_STABLE_VERSION.replace(/-\d+$/, '')}" to upgrade` + ) + ); + } else if (isAboveMaxStable(currentVersion)) { + // Version newer than max stable (experimental) + console.log( + warn( + `CLIProxy Plus v${currentVersion} is experimental (above stable v${CLIPROXY_MAX_STABLE_VERSION.replace(/-\d+$/, '')})` ) ); - console.log(info('Run "ccs cliproxy install 80" to downgrade, or wait for upstream fix')); } if (!updateResult.hasUpdate) return; diff --git a/src/cliproxy/binary/version-checker.ts b/src/cliproxy/binary/version-checker.ts index 8ebfeb4d..383b6bbc 100644 --- a/src/cliproxy/binary/version-checker.ts +++ b/src/cliproxy/binary/version-checker.ts @@ -17,7 +17,7 @@ import { GITHUB_API_ALL_RELEASES, VersionListResult, } from './types'; -import { CLIPROXY_MAX_STABLE_VERSION } from '../platform-detector'; +import { CLIPROXY_MAX_STABLE_VERSION, CLIPROXY_FAULTY_RANGE } from '../platform-detector'; /** * Compare semver versions (true if latest > current) @@ -43,6 +43,17 @@ export function isNewerVersion(latest: string, current: string): boolean { return false; // Equal versions } +/** + * Check if version is within the faulty range (v81-85) + * @returns true if version has known critical bugs + */ +export function isVersionFaulty(version: string): boolean { + const { min, max } = CLIPROXY_FAULTY_RANGE; + const atOrAboveMin = !isNewerVersion(min, version); // version >= min + const atOrBelowMax = !isNewerVersion(version, max); // version <= max + return atOrAboveMin && atOrBelowMax; +} + /** * Fetch latest version from GitHub API */ @@ -123,9 +134,9 @@ export async function fetchAllVersions(verbose = false): Promise !isNewerVersion(v, CLIPROXY_MAX_STABLE_VERSION)) || + versions.find((v) => !isNewerVersion(v, CLIPROXY_MAX_STABLE_VERSION) && !isVersionFaulty(v)) || CLIPROXY_MAX_STABLE_VERSION; const result: VersionListResult = { diff --git a/src/cliproxy/platform-detector.ts b/src/cliproxy/platform-detector.ts index bbe76688..40a7f884 100644 --- a/src/cliproxy/platform-detector.ts +++ b/src/cliproxy/platform-detector.ts @@ -21,6 +21,13 @@ export const CLIPROXY_FALLBACK_VERSION = '6.6.40-0'; */ export const CLIPROXY_MAX_STABLE_VERSION = '6.6.80-0'; +/** + * Faulty version range - versions with known critical bugs + * v81+ have context cancellation bugs causing intermittent 500 errors + * When a stable version is found, update MAX_STABLE and set faulty range accordingly + */ +export const CLIPROXY_FAULTY_RANGE = { min: '6.6.81-0', max: '6.6.999-0' }; + /** @deprecated Use CLIPROXY_FALLBACK_VERSION instead */ export const CLIPROXY_VERSION = CLIPROXY_FALLBACK_VERSION; diff --git a/src/web-server/health/cliproxy-checks.ts b/src/web-server/health/cliproxy-checks.ts index fb41c7af..e6432dd1 100644 --- a/src/web-server/health/cliproxy-checks.ts +++ b/src/web-server/health/cliproxy-checks.ts @@ -16,7 +16,7 @@ import { 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'; +import { isNewerVersion, isVersionFaulty } from '../../cliproxy/binary/version-checker'; /** * Check CLIProxy binary installation @@ -26,17 +26,30 @@ export function checkCliproxyBinary(): HealthCheck { const version = getInstalledCliproxyVersion(); const binaryPath = getCLIProxyPath(); + // Check if version is in faulty range (v81-85) + const isFaulty = isVersionFaulty(version); // Check if version exceeds stable cap const isUnstable = isNewerVersion(version, CLIPROXY_MAX_STABLE_VERSION); + if (isFaulty) { + return { + id: 'cliproxy-binary', + name: 'CLIProxy Binary', + status: 'warning', + message: `v${version} (faulty)`, + details: binaryPath, + fix: `Upgrade: ccs cliproxy install ${CLIPROXY_MAX_STABLE_VERSION.replace(/-\d+$/, '')}`, + }; + } + if (isUnstable) { return { id: 'cliproxy-binary', name: 'CLIProxy Binary', status: 'warning', - message: `v${version} (unstable)`, + message: `v${version} (experimental)`, details: binaryPath, - fix: `Downgrade: ccs cliproxy install ${CLIPROXY_MAX_STABLE_VERSION.replace(/-\d+$/, '')}`, + fix: `Stable: ccs cliproxy install ${CLIPROXY_MAX_STABLE_VERSION.replace(/-\d+$/, '')}`, }; } diff --git a/src/web-server/routes/cliproxy-stats-routes.ts b/src/web-server/routes/cliproxy-stats-routes.ts index 2c07ef99..25319e51 100644 --- a/src/web-server/routes/cliproxy-stats-routes.ts +++ b/src/web-server/routes/cliproxy-stats-routes.ts @@ -26,8 +26,15 @@ import { getInstalledCliproxyVersion, installCliproxyVersion, } from '../../cliproxy/binary-manager'; -import { fetchAllVersions, isNewerVersion } from '../../cliproxy/binary/version-checker'; -import { CLIPROXY_MAX_STABLE_VERSION } from '../../cliproxy/platform-detector'; +import { + fetchAllVersions, + isNewerVersion, + isVersionFaulty, +} from '../../cliproxy/binary/version-checker'; +import { + CLIPROXY_MAX_STABLE_VERSION, + CLIPROXY_FAULTY_RANGE, +} from '../../cliproxy/platform-detector'; const router = Router(); @@ -549,6 +556,7 @@ router.get('/versions', async (_req: Request, res: Response): Promise => { ...result, currentVersion, maxStableVersion: CLIPROXY_MAX_STABLE_VERSION, + faultyRange: CLIPROXY_FAULTY_RANGE, }); } catch (error) { res.status(500).json({ error: (error as Error).message }); @@ -575,14 +583,24 @@ router.post('/install', async (req: Request, res: Response): Promise => { return; } - // Check if version is unstable - const isUnstable = isNewerVersion(version, CLIPROXY_MAX_STABLE_VERSION); + // Check if version is faulty (v81-85) or experimental (above max stable) + const isFaulty = isVersionFaulty(version); + const isExperimental = isNewerVersion(version, CLIPROXY_MAX_STABLE_VERSION); - if (isUnstable && !force) { + if (isFaulty && !force) { res.json({ success: false, requiresConfirmation: true, - message: `Version ${version} is unstable (above max stable ${CLIPROXY_MAX_STABLE_VERSION}). Set force=true to proceed.`, + message: `Version ${version} has known bugs (v${CLIPROXY_FAULTY_RANGE.min.replace(/-\d+$/, '')}-${CLIPROXY_FAULTY_RANGE.max.replace(/-\d+$/, '')}). Set force=true to proceed.`, + }); + return; + } + + if (isExperimental && !force) { + res.json({ + success: false, + requiresConfirmation: true, + message: `Version ${version} is experimental (above stable ${CLIPROXY_MAX_STABLE_VERSION.replace(/-\d+$/, '')}). Set force=true to proceed.`, }); return; } @@ -599,7 +617,8 @@ router.post('/install', async (req: Request, res: Response): Promise => { res.json({ success: true, version, - isUnstable, + isFaulty, + isExperimental, message: `Successfully installed CLIProxy Plus v${version}`, }); } catch (error) {