diff --git a/electron/preload.ts b/electron/preload.ts
index 1cd1a89..20f6803 100644
--- a/electron/preload.ts
+++ b/electron/preload.ts
@@ -27,6 +27,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
},
updater: {
checkForUpdates: () => ipcRenderer.invoke('updater:check'),
+ downloadUpdate: () => ipcRenderer.invoke('updater:download'),
quitAndInstall: () => ipcRenderer.invoke('updater:quit-and-install'),
onStatus: (callback: (data: unknown) => void) => {
const listener = (_event: unknown, data: unknown) => callback(data);
diff --git a/electron/updater.ts b/electron/updater.ts
index 220b4bf..53b9120 100644
--- a/electron/updater.ts
+++ b/electron/updater.ts
@@ -1,6 +1,6 @@
import { autoUpdater } from 'electron-updater';
import type { BrowserWindow } from 'electron';
-import { ipcMain } from 'electron';
+import { ipcMain, session } from 'electron';
let mainWindow: BrowserWindow | null = null;
@@ -8,13 +8,41 @@ function sendStatus(data: Record) {
mainWindow?.webContents.send('updater:status', data);
}
+/**
+ * Resolve system proxy for GitHub and inject into electron-updater
+ * so that VPN / proxy tools are respected during update downloads.
+ */
+async function configureProxy() {
+ try {
+ const proxy = await session.defaultSession.resolveProxy('https://github.com');
+ // proxy returns "DIRECT" or "PROXY host:port" / "SOCKS5 host:port" etc.
+ if (proxy && proxy !== 'DIRECT') {
+ const match = proxy.match(/^(?:PROXY|HTTPS)\s+(.+)/i);
+ if (match) {
+ process.env.HTTPS_PROXY = `http://${match[1]}`;
+ console.log('[updater] Using system proxy:', process.env.HTTPS_PROXY);
+ }
+ const socksMatch = proxy.match(/^SOCKS5?\s+(.+)/i);
+ if (socksMatch) {
+ process.env.HTTPS_PROXY = `socks5://${socksMatch[1]}`;
+ console.log('[updater] Using system SOCKS proxy:', process.env.HTTPS_PROXY);
+ }
+ }
+ } catch (err) {
+ console.warn('[updater] Failed to resolve proxy:', err);
+ }
+}
+
export function initAutoUpdater(win: BrowserWindow) {
mainWindow = win;
- // Configuration
- autoUpdater.autoDownload = true;
+ // Configuration — don't auto-download, let user trigger manually
+ autoUpdater.autoDownload = false;
autoUpdater.autoInstallOnAppQuit = true;
+ // Resolve and apply system proxy for update downloads
+ configureProxy();
+
// --- Events ---
autoUpdater.on('checking-for-update', () => {
sendStatus({ status: 'checking' });
@@ -69,6 +97,10 @@ export function initAutoUpdater(win: BrowserWindow) {
return autoUpdater.checkForUpdates();
});
+ ipcMain.handle('updater:download', async () => {
+ return autoUpdater.downloadUpdate();
+ });
+
ipcMain.handle('updater:quit-and-install', () => {
autoUpdater.quitAndInstall();
});
diff --git a/package-lock.json b/package-lock.json
index d435780..b522632 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "codepilot",
- "version": "0.17.0",
+ "version": "0.17.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "codepilot",
- "version": "0.17.0",
+ "version": "0.17.1",
"dependencies": {
"@ai-sdk/anthropic": "^3.0.47",
"@ai-sdk/google": "^3.0.31",
diff --git a/package.json b/package.json
index d01538c..d49074b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "codepilot",
- "version": "0.17.0",
+ "version": "0.17.1",
"private": true,
"author": {
"name": "op7418",
diff --git a/src/components/layout/AppShell.tsx b/src/components/layout/AppShell.tsx
index 3d4f3d6..d30c864 100644
--- a/src/components/layout/AppShell.tsx
+++ b/src/components/layout/AppShell.tsx
@@ -298,10 +298,11 @@ export function AppShell({ children }: { children: React.ReactNode }) {
const dismissUpdate = useCallback(() => {
setShowDialog(false);
- if (updateInfo?.latestVersion) {
- localStorage.setItem(DISMISSED_VERSION_KEY, updateInfo.latestVersion);
- }
- }, [updateInfo]);
+ }, []);
+
+ const downloadUpdate = useCallback(() => {
+ window.electronAPI?.updater?.downloadUpdate();
+ }, []);
const quitAndInstall = useCallback(() => {
window.electronAPI?.updater?.quitAndInstall();
@@ -312,12 +313,13 @@ export function AppShell({ children }: { children: React.ReactNode }) {
updateInfo,
checking,
checkForUpdates,
+ downloadUpdate,
dismissUpdate,
showDialog,
setShowDialog,
quitAndInstall,
}),
- [updateInfo, checking, checkForUpdates, dismissUpdate, showDialog, quitAndInstall]
+ [updateInfo, checking, checkForUpdates, downloadUpdate, dismissUpdate, showDialog, quitAndInstall]
);
const panelContextValue = useMemo(
diff --git a/src/components/layout/UpdateDialog.tsx b/src/components/layout/UpdateDialog.tsx
index 1bebc2c..03f5703 100644
--- a/src/components/layout/UpdateDialog.tsx
+++ b/src/components/layout/UpdateDialog.tsx
@@ -14,12 +14,13 @@ import { useUpdate } from "@/hooks/useUpdate";
import { useTranslation } from "@/hooks/useTranslation";
export function UpdateDialog() {
- const { updateInfo, showDialog, dismissUpdate, quitAndInstall } = useUpdate();
+ const { updateInfo, showDialog, dismissUpdate, downloadUpdate, quitAndInstall } = useUpdate();
const { t } = useTranslation();
if (!updateInfo?.updateAvailable) return null;
const { isNativeUpdate, readyToInstall, downloadProgress } = updateInfo;
+ const isDownloading = isNativeUpdate && !readyToInstall && downloadProgress != null && downloadProgress > 0;
return (
- {/* Download progress bar (native update, downloading) */}
- {isNativeUpdate && !readyToInstall && downloadProgress != null && downloadProgress > 0 && (
+ {/* Download progress bar */}
+ {isDownloading && (
- {t('update.downloading')} {Math.round(downloadProgress)}%
+ {t('update.downloading')} {Math.round(downloadProgress!)}%
)}
@@ -88,7 +89,6 @@ export function UpdateDialog() {
{t('update.later')}
{!isNativeUpdate ? (
- // Browser mode: open release page
) : readyToInstall ? (
- // Electron: downloaded, ready to install
- ) : (
- // Electron: still downloading
+ ) : isDownloading ? (
+ ) : (
+
)}
diff --git a/src/components/settings/GeneralSection.tsx b/src/components/settings/GeneralSection.tsx
index 334b893..de51573 100644
--- a/src/components/settings/GeneralSection.tsx
+++ b/src/components/settings/GeneralSection.tsx
@@ -21,10 +21,13 @@ import { SUPPORTED_LOCALES, type Locale } from "@/i18n";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
function UpdateCard() {
- const { updateInfo, checking, checkForUpdates, quitAndInstall } = useUpdate();
+ const { updateInfo, checking, checkForUpdates, downloadUpdate, quitAndInstall, setShowDialog } = useUpdate();
const { t } = useTranslation();
const currentVersion = process.env.NEXT_PUBLIC_APP_VERSION || "0.0.0";
+ const isDownloading = updateInfo?.isNativeUpdate && !updateInfo.readyToInstall
+ && updateInfo.downloadProgress != null && updateInfo.downloadProgress > 0;
+
return (
@@ -32,53 +35,73 @@ function UpdateCard() {
{t('settings.codepilot')}
{t('settings.version', { version: currentVersion })}
-
{updateInfo && !checking && (
{updateInfo.updateAvailable ? (
-
-
-
- {updateInfo.readyToInstall
- ? t('update.readyToInstall', { version: updateInfo.latestVersion })
- : updateInfo.downloadProgress != null && updateInfo.downloadProgress > 0 && !updateInfo.readyToInstall
- ? `${t('update.downloading')} ${Math.round(updateInfo.downloadProgress)}%`
- : t('settings.updateAvailable', { version: updateInfo.latestVersion })}
-
- {updateInfo.readyToInstall ? (
-
- {t('update.restartToUpdate')}
-
- ) : !updateInfo.isNativeUpdate ? (
-
window.open(updateInfo.releaseUrl, "_blank")}
- >
- {t('settings.viewRelease')}
-
- ) : null}
+
+
+
+
+ {updateInfo.readyToInstall
+ ? t('update.readyToInstall', { version: updateInfo.latestVersion })
+ : isDownloading
+ ? `${t('update.downloading')} ${Math.round(updateInfo.downloadProgress!)}%`
+ : t('settings.updateAvailable', { version: updateInfo.latestVersion })}
+
+ {updateInfo.releaseNotes && (
+ setShowDialog(true)}
+ >
+ {t('gallery.viewDetails')}
+
+ )}
+
+ {/* Download progress bar */}
+ {isDownloading && (
+
+ )}
) : (
{t('settings.latestVersion')}
diff --git a/src/hooks/useUpdate.ts b/src/hooks/useUpdate.ts
index bf9cc1f..cce2841 100644
--- a/src/hooks/useUpdate.ts
+++ b/src/hooks/useUpdate.ts
@@ -19,6 +19,7 @@ export interface UpdateContextValue {
updateInfo: UpdateInfo | null;
checking: boolean;
checkForUpdates: () => Promise
;
+ downloadUpdate: () => void;
dismissUpdate: () => void;
showDialog: boolean;
setShowDialog: (v: boolean) => void;
diff --git a/src/i18n/en.ts b/src/i18n/en.ts
index c0ca9e4..1e909d7 100644
--- a/src/i18n/en.ts
+++ b/src/i18n/en.ts
@@ -305,6 +305,7 @@ const en = {
'update.restartToUpdate': 'Restart to Update',
'update.restartNow': 'Restart Now',
'update.readyToInstall': 'CodePilot v{version} is ready — restart to update',
+ 'update.installUpdate': 'Download & Install',
'update.later': 'Later',
// ── Image Generation ──────────────────────────────────────
diff --git a/src/i18n/zh.ts b/src/i18n/zh.ts
index 0583372..c352f55 100644
--- a/src/i18n/zh.ts
+++ b/src/i18n/zh.ts
@@ -302,6 +302,7 @@ const zh: Record = {
'update.restartToUpdate': '重启以更新',
'update.restartNow': '立即重启',
'update.readyToInstall': 'CodePilot v{version} 已就绪 — 重启以完成更新',
+ 'update.installUpdate': '下载并安装',
'update.later': '稍后',
// ── Image Generation ──────────────────────────────────────
diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts
index ce94b02..6e95f7f 100644
--- a/src/types/electron.d.ts
+++ b/src/types/electron.d.ts
@@ -36,6 +36,7 @@ interface UpdateStatusEvent {
interface ElectronUpdaterAPI {
checkForUpdates: () => Promise;
+ downloadUpdate: () => Promise;
quitAndInstall: () => Promise;
onStatus: (callback: (data: UpdateStatusEvent) => void) => () => void;
}