diff --git a/electron/build/installer-recovery.nsh b/electron/build/installer-recovery.nsh index accbede9..a1c95020 100644 --- a/electron/build/installer-recovery.nsh +++ b/electron/build/installer-recovery.nsh @@ -54,6 +54,30 @@ Sleep 1500 !macroend +!macro customInstall + ; ---- Defender prewarm: scan the heavy binaries while user is still watching the installer, not on first launch ---- + ; + ; Right after extraction we spawn OpenSwarm.exe --prewarm, which loads + ; python.exe and node.exe just enough to trigger Windows Defender's + ; on-execute scan against them. Defender then has a cached verdict by + ; the time the user double-clicks the app, dropping cold-start by + ; 3-7 seconds on default-Defender Windows installs. + ; + ; --prewarm in main.js: no windows, no single-instance lock, no + ; backend spawn, no UI side effects. Touches binaries via + ; execFileSync('--version') and process.exit(0). 15s internal timeout + ; per binary; outer nsExec call adds no further bound, so worst case + ; this adds ~30-45s to install on a truly pathological machine. + ; Standard machines: 3-8s. + ; + ; Failure here is silent and non-fatal. If --prewarm itself errors, + ; the install still completes; user just pays the cold-start tax on + ; first launch, same as before this macro existed. + + nsExec::Exec '"$INSTDIR\OpenSwarm.exe" --prewarm' + Pop $0 ; discard exit code; prewarm is best-effort +!macroend + !macro customRemoveFiles ; ---- Layer B: bulk-delete heavy dirs with REBOOTOK fallback ---- ; diff --git a/electron/main.js b/electron/main.js index 3ed6e598..16a73b2a 100644 --- a/electron/main.js +++ b/electron/main.js @@ -9,6 +9,22 @@ const getPort = require('get-port'); const http = require('http'); const affiliateTracking = require('./affiliateTracking'); +// Defender warmup: NSIS runs us with --prewarm right after install so Windows scans the bundled binaries while the user is already watching the installer instead of staring at a slow first launch. +if (process.argv.includes('--prewarm') && process.platform === 'win32') { + const touchExe = (rel) => { + const full = path.join(process.resourcesPath, rel); + try { + if (fs.existsSync(full)) { + execFileSync(full, ['--version'], { timeout: 15000, stdio: 'ignore', windowsHide: true }); + } + } catch (_) {} + }; + touchExe(path.join('python-env', 'python.exe')); + touchExe(path.join('node', 'x64', 'node.exe')); + touchExe(path.join('node', 'arm64', 'node.exe')); + process.exit(0); +} + // Prevent duplicate instances. Without this, double-clicking the app icon // (or macOS auto-launch + manual launch overlapping) spawns two independent // processes — each with its own backend on a different port — resulting in diff --git a/electron/package-lock.json b/electron/package-lock.json index c6c40ee7..ff8ca8a1 100644 --- a/electron/package-lock.json +++ b/electron/package-lock.json @@ -1,12 +1,12 @@ { "name": "openswarm", - "version": "1.1.40", + "version": "1.1.41-exp.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "openswarm", - "version": "1.1.40", + "version": "1.1.41-exp.1", "hasInstallScript": true, "dependencies": { "electron-updater": "^6.3.0", diff --git a/electron/package.json b/electron/package.json index 42e6146b..6cb0a4df 100644 --- a/electron/package.json +++ b/electron/package.json @@ -1,6 +1,6 @@ { "name": "openswarm", - "version": "1.1.40", + "version": "1.1.41-exp.1", "description": "OpenSwarm — AI Agent Orchestrator", "main": "main.js", "scripts": {