From 5adb15b3316d13a1317cff13389099791a4d5a52 Mon Sep 17 00:00:00 2001 From: Haze <709547807@qq.com> Date: Tue, 10 Feb 2026 14:42:51 +0800 Subject: [PATCH 1/3] fix(updater): improve error handling during update checks and add timeout for update invocation --- electron/main/updater.ts | 3 ++- src/stores/update.ts | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/electron/main/updater.ts b/electron/main/updater.ts index 29f0177..1b0a37a 100644 --- a/electron/main/updater.ts +++ b/electron/main/updater.ts @@ -120,7 +120,8 @@ export class AppUpdater extends EventEmitter { return result?.updateInfo || null; } catch (error) { console.error('[Updater] Check for updates failed:', error); - return null; + this.updateStatus({ status: 'error', error: (error as Error).message || String(error) }); + throw error; // Re-throw instead of swallowing } } diff --git a/src/stores/update.ts b/src/stores/update.ts index 2b0ebcb..9d4c3dd 100644 --- a/src/stores/update.ts +++ b/src/stores/update.ts @@ -129,7 +129,10 @@ export const useUpdateStore = create((set, get) => ({ set({ status: 'checking', error: null }); try { - const result = await window.electron.ipcRenderer.invoke('update:check') as { + const result = await Promise.race([ + window.electron.ipcRenderer.invoke('update:check'), + new Promise((_, reject) => setTimeout(() => reject(new Error('Update check timed out')), 30000)) + ]) as { success: boolean; info?: UpdateInfo; error?: string; From 52fc3e1a94a1015a01673cfab708555253672450 Mon Sep 17 00:00:00 2001 From: Haze <709547807@qq.com> Date: Tue, 10 Feb 2026 15:12:03 +0800 Subject: [PATCH 2/3] feat: enhance auto-update configuration with Alibaba Cloud OSS as primary provider and add GitHub fallback --- .github/workflows/release.yml | 153 ++++++++++++++++++++++++++++++++++ electron-builder.yml | 5 ++ electron/main/updater.ts | 6 +- openclaw | 1 + 4 files changed, 164 insertions(+), 1 deletion(-) create mode 160000 openclaw diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 03a5952..16a8c72 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -121,6 +121,9 @@ jobs: release/latest*.yml retention-days: 7 + # ────────────────────────────────────────────────────────────── + # Job: Publish to GitHub Releases + # ────────────────────────────────────────────────────────────── publish: needs: release runs-on: ubuntu-latest @@ -195,3 +198,153 @@ jobs: 💬 Found an issue? Please submit an [Issue](https://github.com/${{ github.repository }}/issues) env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # ────────────────────────────────────────────────────────────── + # Job: Upload to Alibaba Cloud OSS + # Uploads all release artifacts to OSS for: + # - Official website downloads (via release-info.json) + # - electron-updater auto-update (via latest-*.yml) + # + # Directory structure on OSS: + # latest/ → always overwritten with the newest version + # releases/vX.Y.Z/ → permanent archive, never deleted + # ────────────────────────────────────────────────────────────── + upload-oss: + needs: release + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: release-artifacts + + - name: Extract version + id: version + run: | + if [[ "${{ github.ref }}" == refs/tags/v* ]]; then + VERSION="${GITHUB_REF#refs/tags/v}" + else + VERSION="${{ github.event.inputs.version }}" + fi + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "tag=v${VERSION}" >> $GITHUB_OUTPUT + echo "Detected version: ${VERSION}" + + - name: Prepare upload directories + run: | + VERSION="${{ steps.version.outputs.version }}" + TAG="${{ steps.version.outputs.tag }}" + + mkdir -p staging/latest + mkdir -p staging/releases/${TAG} + + # Flatten all platform artifacts into staging directories + find release-artifacts/ -type f | while read file; do + filename=$(basename "$file") + cp "$file" "staging/latest/${filename}" + cp "$file" "staging/releases/${TAG}/${filename}" + done + + echo "=== staging/latest/ ===" + ls -lh staging/latest/ + echo "" + echo "=== staging/releases/${TAG}/ ===" + ls -lh staging/releases/${TAG}/ + + - name: Generate release-info.json + run: | + VERSION="${{ steps.version.outputs.version }}" + BASE_URL="https://valuecell-clawx.oss-cn-hangzhou.aliyuncs.com/latest" + + jq -n \ + --arg version "$VERSION" \ + --arg date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + --arg base "$BASE_URL" \ + --arg changelog "https://github.com/${{ github.repository }}/releases/tag/v${VERSION}" \ + '{ + version: $version, + releaseDate: $date, + downloads: { + mac: { + x64: ($base + "/ClawX-" + $version + "-mac-x64.dmg"), + arm64: ($base + "/ClawX-" + $version + "-mac-arm64.dmg") + }, + win: { + x64: ($base + "/ClawX-" + $version + "-win-x64.exe"), + arm64: ($base + "/ClawX-" + $version + "-win-arm64.exe") + }, + linux: { + deb_amd64: ($base + "/ClawX-" + $version + "-linux-amd64.deb"), + deb_arm64: ($base + "/ClawX-" + $version + "-linux-arm64.deb"), + appimage_x64: ($base + "/ClawX-" + $version + "-linux-x86_64.AppImage"), + appimage_arm64: ($base + "/ClawX-" + $version + "-linux-arm64.AppImage"), + rpm_x64: ($base + "/ClawX-" + $version + "-linux-x86_64.rpm") + } + }, + changelog: $changelog + }' > staging/latest/release-info.json + + echo "=== release-info.json ===" + cat staging/latest/release-info.json + + - name: Install and configure ossutil + env: + OSS_ACCESS_KEY_ID: ${{ secrets.OSS_ACCESS_KEY_ID }} + OSS_ACCESS_KEY_SECRET: ${{ secrets.OSS_ACCESS_KEY_SECRET }} + run: | + curl -sL https://gosspublic.alicdn.com/ossutil/install.sh | sudo bash + + # Write config file for non-interactive use + cat > $HOME/.ossutilconfig << EOF + [Credentials] + language=EN + endpoint=oss-cn-hangzhou.aliyuncs.com + accessKeyID=${OSS_ACCESS_KEY_ID} + accessKeySecret=${OSS_ACCESS_KEY_SECRET} + EOF + + ossutil --version + + - name: "Upload to OSS: latest/ (overwrite)" + run: | + # Clean old latest/ to remove stale version files + ossutil rm -r -f oss://valuecell-clawx/latest/ || true + + # Upload all files with no-cache so clients always get the freshest version + ossutil cp -r -f \ + staging/latest/ \ + oss://valuecell-clawx/latest/ \ + --meta "Cache-Control:no-cache,no-store,must-revalidate" + + echo "Uploaded to latest/" + + - name: "Upload to OSS: releases/vX.Y.Z/ (archive)" + run: | + TAG="${{ steps.version.outputs.tag }}" + + # Upload to permanent archive (long cache, immutable) + ossutil cp -r \ + staging/releases/${TAG}/ \ + oss://valuecell-clawx/releases/${TAG}/ \ + --meta "Cache-Control:public,max-age=31536000,immutable" + + echo "Uploaded to releases/${TAG}/" + + - name: Verify OSS upload + run: | + TAG="${{ steps.version.outputs.tag }}" + + echo "=== latest/ ===" + ossutil ls oss://valuecell-clawx/latest/ --short + + echo "" + echo "=== releases/${TAG}/ ===" + ossutil ls oss://valuecell-clawx/releases/${TAG}/ --short + + echo "" + echo "=== Verify release-info.json ===" + curl -sL "https://valuecell-clawx.oss-cn-hangzhou.aliyuncs.com/latest/release-info.json" | jq . diff --git a/electron-builder.yml b/electron-builder.yml index d8760c6..fdb97af 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -33,7 +33,12 @@ asarUnpack: - "**/*.node" # Auto-update configuration +# Primary: Alibaba Cloud OSS (fast for Chinese users, used for auto-update) +# Fallback: GitHub Releases (backup, used when OSS is unavailable) publish: + - provider: generic + url: https://valuecell-clawx.oss-cn-hangzhou.aliyuncs.com/latest + useMultipleRangeRequest: false - provider: github owner: ValueCell-ai repo: ClawX diff --git a/electron/main/updater.ts b/electron/main/updater.ts index 1b0a37a..080a598 100644 --- a/electron/main/updater.ts +++ b/electron/main/updater.ts @@ -1,6 +1,9 @@ /** * Auto-Updater Module * Handles automatic application updates using electron-updater + * + * Update providers are configured in electron-builder.yml (OSS primary, GitHub fallback). + * electron-updater handles provider resolution automatically. */ import { autoUpdater, UpdateInfo, ProgressInfo, UpdateDownloadedEvent } from 'electron-updater'; import { BrowserWindow, app, ipcMain } from 'electron'; @@ -113,6 +116,7 @@ export class AppUpdater extends EventEmitter { /** * Check for updates + * electron-updater automatically tries providers defined in electron-builder.yml in order */ async checkForUpdates(): Promise { try { @@ -121,7 +125,7 @@ export class AppUpdater extends EventEmitter { } catch (error) { console.error('[Updater] Check for updates failed:', error); this.updateStatus({ status: 'error', error: (error as Error).message || String(error) }); - throw error; // Re-throw instead of swallowing + throw error; } } diff --git a/openclaw b/openclaw new file mode 160000 index 0000000..54ddbc4 --- /dev/null +++ b/openclaw @@ -0,0 +1 @@ +Subproject commit 54ddbc466085b42526983f6c90d152fdd7bf365f From cd5d34746416600bb549a6a419316371efd6eccb Mon Sep 17 00:00:00 2001 From: Haze <709547807@qq.com> Date: Tue, 10 Feb 2026 15:17:36 +0800 Subject: [PATCH 3/3] chore: remove openclaw submodule --- openclaw | 1 - 1 file changed, 1 deletion(-) delete mode 160000 openclaw diff --git a/openclaw b/openclaw deleted file mode 160000 index 54ddbc4..0000000 --- a/openclaw +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 54ddbc466085b42526983f6c90d152fdd7bf365f