From 49e0ed3d2f86591775d8f08870e3fa6c76fafc3b Mon Sep 17 00:00:00 2001 From: ciregenz Date: Tue, 19 May 2026 22:10:22 -0700 Subject: [PATCH] experimental updates: opt-in prerelease channel via settings toggle + semver-suffix auto-detect in publish --- .github/workflows/release-windows.yml | 10 +++++++ backend/apps/settings/models.py | 3 +- electron/main.js | 16 +++++++++++ electron/preload.js | 1 + frontend/src/app/Main.tsx | 6 ++++ frontend/src/app/pages/Settings/Settings.tsx | 19 +++++++++++-- frontend/src/shared/state/settingsSlice.ts | 2 ++ publish.sh | 29 ++++++++++++++++++-- 8 files changed, 81 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml index c26130a1..562a2fdf 100644 --- a/.github/workflows/release-windows.yml +++ b/.github/workflows/release-windows.yml @@ -110,6 +110,16 @@ jobs: $ErrorActionPreference = 'Stop' $shouldPublish = ($env:GITHUB_EVENT_NAME -eq 'push') -or ` ($env:GITHUB_EVENT_NAME -eq 'workflow_dispatch' -and $env:PUBLISH_INPUT -eq 'true') + # electron-builder auto-detects prerelease from semver suffix in electron/package.json, + # but EP_PRE_RELEASE forces the GitHub Releases publisher to mark it Pre-release even + # when the runner's environment differs from local. Set it whenever the version has a "-" suffix. + $version = (Get-Content electron/package.json | ConvertFrom-Json).version + if ($version -match '-') { + $env:EP_PRE_RELEASE = 'true' + Write-Host "Version $version is EXPERIMENTAL; setting EP_PRE_RELEASE=true" + } else { + Write-Host "Version $version is STABLE" + } if ($shouldPublish) { Write-Host "Build mode: PUBLISH" pwsh -NoProfile -File scripts\build-app-win.ps1 -Publish diff --git a/backend/apps/settings/models.py b/backend/apps/settings/models.py index 906bda42..747fa225 100644 --- a/backend/apps/settings/models.py +++ b/backend/apps/settings/models.py @@ -63,7 +63,8 @@ class AppSettings(BaseModel): expand_new_chats_in_dashboard: bool = False auto_reveal_sub_agents: bool = True dev_mode: bool = False - # Subscription tokens (from CLI tools — alternative to API keys) + allow_experimental_updates: bool = False + # Subscription tokens (from CLI tools, alternative to API keys) claude_subscription_token: Optional[str] = None openai_subscription_token: Optional[str] = None gemini_subscription_token: Optional[str] = None diff --git a/electron/main.js b/electron/main.js index 6101908b..81a1d92b 100644 --- a/electron/main.js +++ b/electron/main.js @@ -719,6 +719,8 @@ function setupAutoUpdater() { // running .app / locked .exe), so an active session is never disrupted. autoUpdater.autoDownload = true; autoUpdater.autoInstallOnAppQuit = true; + // Renderer pushes the user's experimental-updates setting via IPC right after settings load. + autoUpdater.allowPrerelease = false; autoUpdater.on('update-available', (info) => { console.log(`Update available: ${info.version}`); @@ -1280,6 +1282,20 @@ ipcMain.handle('download-update', async () => { } }); +ipcMain.handle('set-allow-prerelease', async (_e, value) => { + if (!autoUpdater) return { success: false, error: 'Updater not available' }; + const next = Boolean(value); + if (autoUpdater.allowPrerelease === next) return { success: true, changed: false }; + autoUpdater.allowPrerelease = next; + if (!isPackaged) return { success: true, changed: true }; + try { + await autoUpdater.checkForUpdates(); + } catch (err) { + return { success: false, changed: true, error: err?.message || String(err) }; + } + return { success: true, changed: true }; +}); + ipcMain.handle('install-update', () => { if (!autoUpdater) return; autoUpdater.quitAndInstall(false, true); diff --git a/electron/preload.js b/electron/preload.js index a92abb21..29b87f41 100644 --- a/electron/preload.js +++ b/electron/preload.js @@ -37,6 +37,7 @@ const { contextBridge, ipcRenderer } = require('electron'); checkForUpdates: () => ipcRenderer.invoke('check-for-updates'), downloadUpdate: () => ipcRenderer.invoke('download-update'), installUpdate: () => ipcRenderer.invoke('install-update'), + setAllowPrerelease: (value) => ipcRenderer.invoke('set-allow-prerelease', value), onUpdateAvailable: (cb) => { const listener = (_event, info) => cb(info); diff --git a/frontend/src/app/Main.tsx b/frontend/src/app/Main.tsx index e5454440..ab05e580 100644 --- a/frontend/src/app/Main.tsx +++ b/frontend/src/app/Main.tsx @@ -230,6 +230,7 @@ const SettingsLoader: React.FC<{ children: React.ReactNode }> = ({ children }) = const { setMode: setThemeMode } = useThemeMode(); const theme = useAppSelector((s) => s.settings.data.theme); const loaded = useAppSelector((s) => s.settings.loaded); + const allowExperimentalUpdates = useAppSelector((s) => s.settings.data.allow_experimental_updates); useEffect(() => { dispatch(fetchSettings()); dispatch(fetchModels()); @@ -259,6 +260,11 @@ const SettingsLoader: React.FC<{ children: React.ReactNode }> = ({ children }) = useEffect(() => { if (loaded) setThemeMode(theme as 'light' | 'dark'); }, [loaded, theme, setThemeMode]); + + useEffect(() => { + if (!loaded) return; + (window as any).openswarm?.setAllowPrerelease?.(allowExperimentalUpdates); + }, [loaded, allowExperimentalUpdates]); return <>{children}; }; diff --git a/frontend/src/app/pages/Settings/Settings.tsx b/frontend/src/app/pages/Settings/Settings.tsx index d6d72ff0..64cf30a2 100644 --- a/frontend/src/app/pages/Settings/Settings.tsx +++ b/frontend/src/app/pages/Settings/Settings.tsx @@ -2052,7 +2052,7 @@ const Settings: React.FC = () => { {/* ── Advanced ── */} Advanced - + Developer mode Show transport details, environment variables, raw configs, and other technical metadata throughout the app. @@ -2067,7 +2067,22 @@ const Settings: React.FC = () => { /> - {/* ── About ── */} + + + Experimental updates + Receive pre-release builds with new features earlier. These versions may be less stable than normal releases. + + setForm({ ...form, allow_experimental_updates: e.target.checked })} + sx={{ + '& .MuiSwitch-switchBase.Mui-checked': { color: c.accent.primary }, + '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { bgcolor: c.accent.primary }, + }} + /> + + + {/* About */} About diff --git a/frontend/src/shared/state/settingsSlice.ts b/frontend/src/shared/state/settingsSlice.ts index f2453a0d..e56a4790 100644 --- a/frontend/src/shared/state/settingsSlice.ts +++ b/frontend/src/shared/state/settingsSlice.ts @@ -54,6 +54,7 @@ export interface AppSettings { expand_new_chats_in_dashboard: boolean; auto_reveal_sub_agents: boolean; dev_mode: boolean; + allow_experimental_updates: boolean; // Optional managed-subscription state (surfaces only when user has // subscribed via the cloud). Mirrors AppSettings on the backend. connection_mode?: 'own_key' | 'openswarm-pro'; @@ -129,6 +130,7 @@ const initialState: SettingsState = { expand_new_chats_in_dashboard: false, auto_reveal_sub_agents: true, dev_mode: false, + allow_experimental_updates: false, }, loading: false, loaded: false, diff --git a/publish.sh b/publish.sh index fccaa935..c5727222 100755 --- a/publish.sh +++ b/publish.sh @@ -1,5 +1,19 @@ #!/bin/bash # The comment above is shebang, DO NOT REMOVE +# +# Publishes a macOS build to GitHub Releases. Channel is auto-detected from +# the semver string in electron/package.json: +# +# Stable: "1.0.37" -> normal GitHub release +# Experimental: "1.0.37-exp.1" -> marked Pre-release on GitHub; only reaches +# users with "Experimental updates" enabled +# in Settings > Advanced. +# +# To promote an experimental release to stable: un-check "This is a pre-release" +# on the GitHub release page (native GitHub UI, no code change needed). +# +# Windows release: tag-triggered via .github/workflows/release-windows.yml, +# which performs the same auto-detection. PUBLISH_ABSPATH="$(readlink -f "${BASH_SOURCE[0]}")" if [[ "$OSTYPE" == "darwin"* ]]; then sed -i '' 's/\r//g' "$PUBLISH_ABSPATH" @@ -11,7 +25,18 @@ chmod +x "$PUBLISH_ABSPATH" PROJECT_ROOT="$(dirname "$PUBLISH_ABSPATH")" cd "$PROJECT_ROOT" -echo "Building and deploying to Firebase Hosting..." +# electron-builder auto-detects prerelease from semver suffix in electron/package.json +# (e.g. "1.0.37-exp.1" publishes as GitHub Pre-release; "1.0.37" publishes as stable). +# We also export EP_PRE_RELEASE for belt-and-suspenders so the GitHub Releases publisher +# can't accidentally promote an experimental build. +VERSION="$(node -p "require('./electron/package.json').version")" +if [[ "$VERSION" == *-* ]]; then + export EP_PRE_RELEASE=true + echo "==> Publishing EXPERIMENTAL release: v$VERSION (will be marked Pre-release on GitHub)" +else + echo "==> Publishing STABLE release: v$VERSION" +fi + bash scripts/build-app.sh --publish -cd - \ No newline at end of file +cd -