Adds bounded internal retry to the desktop embedded-start path so a transient first-attempt failure self-heals before the operator ever sees the "Couldn't start local Fusion" error screen.
- LocalRuntimeManager.startEmbedded() now retries startEmbeddedAttempt() up to startupRetries (default 3) total attempts with a startupRetryDelayMs (default 150ms) delay between attempts, both overridable via constructor options for deterministic zero-delay tests.
- status.state stays "starting" across retried attempts; only the final attempt's real error sets state "error" and is thrown/surfaced, so genuine failures still report their real message unchanged.
- Only affects the embedded-start path (never external-cli or already-running paths).
- Adds regression tests covering retry-then-success, exhausted-retries-surfaces-final-error, and status transitions across attempts.
Files changed:
.../desktop/src/__tests__/local-runtime.test.ts | 158 ++++++++++++++++++++-
packages/desktop/src/local-runtime.ts | 76 +++++++++-
2 files changed, 228 insertions(+), 6 deletions(-)
Fusion-Task-Id: FN-7617
Fusion-Task-Lineage: 2baf90f0-052c-42ff-a5b1-bc0b802d0ddc
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
First-run project setup surfaced a blocking "Agent with this name already exists" error when creating
the default CEO. The default first agent can be created from more than one first-run surface (the
unified ModelOnboarding agent step and the project-setup SetupWizard sub-flow), and agent names are
unique per store, so the second create returned 409. The step's goal — a first agent exists — was
already satisfied, so both onboarding surfaces now treat a name collision as success and advance
instead of blocking. The user still creates the agent; they just aren't punished for the flow offering
it twice.
Separately, the desktop header "Desktop local mode / Switch server" button did nothing: main relays
the click as a `shell:open-connection-manager` IPC (webContents.send), but the preload never forwarded
it to the `window` DOM event ShellContext listens for, so the signal was dropped. Add the preload
bridge so the button opens NativeShellConnectionManager (Local/Remote toggle + remote-server profiles),
letting operators switch between running locally, connecting to a remote server, or a different remote.
Regression tests: SetupWizard advances on an already-exists agent; preload dispatches the window event
on the IPC.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up to #1878 (merged). While building the Windows desktop
installer locally, `pnpm deploy`'s final temp-dir rename hits `EPERM` on
the orca-managed Windows workspace mount and litters deeply-nested
`deploy_tmp_*` dirs that are painful to remove.
This lets local Windows installer builds redirect the staged production
closure to an external, plain-NTFS path via a new
`FUSION_DESKTOP_DEPLOY_DIR` env var. **CI and the default in-tree
`packages/desktop/deploy` path are unchanged** — the override is opt-in.
Verified locally: with
`FUSION_DESKTOP_DEPLOY_DIR=C:\Users\...\fusion-build\deploy`, `pnpm
--filter @fusion/desktop build` stages the full closure externally
without the EPERM race, and `electron-builder --projectDir <that dir>
--win nsis --x64` produces a signed installer.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Desktop deployment staging can now be configured with an environment
variable, making it easier to customize where build files are prepared.
* **Documentation**
* Added notes about updated desktop deployment behavior on Windows,
including common rename and cleanup issues.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
- Add a one-time copy of the previous default Electron profile into
~/.fusion/desktop-user-data (user-data-migration.ts) so upgrading operators
keep window geometry/session instead of starting fresh (report Issue 8).
- Verify main.js/preload.js/client assets in scripts/build.ts (pre-package
staging) and in the packaged app.asar via desktop-windows.yml, so an
incomplete package fails the build/CI instead of dead-ending at runtime with
a silent missing preload (report Issue 5, recommendation #2).
- Fix two pre-existing stale workflow-assertion tests that expected a bare
'electron-builder --win' after the scripts moved to '--projectDir deploy'.
- Update the field report with a per-issue resolution matrix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Regenerate pnpm-lock.yaml for the new electron@^33.4.11 CLI dependency so
--frozen-lockfile stops failing every PR-check job.
- Gate the GPU/sandbox-disabling Electron flags (--no-sandbox, --disable-gpu,
etc.) to Windows only via os.platform(); applying them on macOS/Linux was a
security and rendering regression. This also resolves the unused os import
that failed lint.
- Add FNXC comments for the Windows GPU flags, desktop user-data isolation, and
CLI dashboard-server reuse per project comment convention.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pnpm deploy's final temp-dir rename hits EPERM on the orca-managed Windows workspace mount and litters
deeply-nested deploy_tmp_* dirs. Let local Windows installer builds redirect the staged closure to an
external plain-NTFS path via FUSION_DESKTOP_DEPLOY_DIR; CI and the default in-tree path are unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add reports/desktop-release-issues-2026-07-03.md documenting the
regressions observed in the Fusion 0.52.0 Windows desktop release,
including:
- fusion desktop fails to launch because electron is a devDependency
- native launcher walks ancestor dirs and fails on unrelated workspace JSON
- Manage Projects opens Settings instead of overview
- Windows Terminal Help version dialogs on dashboard load
- packaged preload.cjs missing in unpacked release layout
- port collisions and dashboard/gateway drift on Windows
- GPU/sandbox rendering instability on Windows Electron
- isolated user-data path needed
- CLI desktop command not reusing an already-running dashboard server
Also include the two experimental mitigations we applied locally:
- disable GPU/sandbox Electron flags in desktop.ts
- skip embedded local runtime when FUSION_SERVER_PORT is already set
These changes are intended as supporting evidence and starting points
for the Fusion team, not as a final fix.
createServer's modelRegistry is optional and, when absent, the /api/models endpoint returns an
empty list — so after connecting a provider (e.g. Anthropic) the onboarding model picker showed
"no models". The desktop wired authStorage but no ModelRegistry.
Add a shared createFusionModelRegistry(authStorage) factory to @fusion/engine (which already depends
on @earendil-works/pi-coding-agent) so non-CLI hosts don't need that dep directly, and pass its
result as modelRegistry from both desktop server paths. Verified against a clean embedded server:
/api/models now returns 24 Claude models instead of an empty list.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
local-server now imports createFusionAuthStorage from @fusion/engine; add it to the test's engine
mock so the startup path resolves.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The desktop embedded server called createServer WITHOUT an authStorage, so GET /api/auth/status
returned 500 "Authentication is not configured". The dashboard's first-run onboarding hook
(useAuthOnboarding -> fetchAuthStatus) catches that failure silently and never opens the model
onboarding wizard — so the desktop skipped straight to project creation with no AI/GitHub setup,
regardless of onboarding-completion flags (which is why clearing them did nothing). Providers also
couldn't be authenticated at all.
Wire createFusionAuthStorage() (the same storage the CLI dashboard/serve commands use) into both the
primary (local-runtime) and legacy (local-server) desktop server paths. Verified against a clean
embedded server: /api/auth/status now returns 200 with the provider list instead of 500. (Full
API-key provider wrapping stays CLI-only for now; OAuth + CLI providers are available on desktop.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- DesktopLaunchGate: the chooser onPick("local") path still called the removed
applyServerBaseUrl helper (broke Typecheck and the desktop local chooser flow) —
call navigateToLocalRuntimeOrigin. Also re-read shell.getState() immediately before the
self-healing start check so a stale mount snapshot doesn't fire a redundant
setDesktopMode("local") on normal boots (Greptile).
- register-fn-binary-routes: on Windows the npm install runs under a shell, so a timeout's
child.kill only stopped cmd.exe and left npm.cmd/node running — kill the whole process tree
via taskkill /T on win32 (CodeRabbit).
- local-server.test: align with the new resolver contract — assert the runtime never
auto-registers the root project and starts engine-less when no projects exist (CodeRabbit).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The desktop embedded runtime auto-registered its runtime root (the user's HOME directory) as a
project on first launch, and bare `fusion` / `fn` / `fn dashboard` / `fusion dashboard`
auto-registered the CWD as a project. Both silently created a "cwd-mode" project the operator
never chose and dropped them onto a board for it.
- Desktop: replace ensureDesktopRuntimeProject (which registered home) with
resolveDesktopRuntimePrimaryProject, which only PICKS an already-registered project as the
primary engine target and registers nothing. With no projects the server starts engine-less
(createServer's engine is optional) and the dashboard shows its onboarding empty state.
Applied to both the primary (local-runtime) and legacy (local-server) desktop server paths.
- CLI dashboard command: ensureCwdProjectRegistered now runs with autoRegister:false, so it uses
the CWD project only if already registered, else starts with none and the dashboard onboards.
(serve/daemon keep their existing --no-auto-register flag; the CLI `desktop` launcher unchanged.)
Verified: with zero projects the embedded server starts, /api/health -> 200, /api/projects -> [],
/ serves the client. Unit test asserts resolveDesktopRuntimePrimaryProject registers nothing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Desktop startup used two independent persisted sources of truth that could
disagree: desktop-launch-mode.json decided whether main STARTS the embedded
local runtime, while shell-connections.json (desktopMode) decided whether the
renderer launch gate WAITS for it. shell:setDesktopMode persists shell settings
before the fallible startLocalRuntimeOnce()/saveDesktopLaunchMode(), so a first
"local" selection whose runtime start failed or was interrupted left
shell=local / launch-mode=choose permanently. Every subsequent launch then sat
at "Starting local Fusion runtime…" polling a runtime nobody started, timing out
after 30s.
Fix (defense in depth):
- initializeApp reconciles: a completed shell "local" selection is authoritative;
it heals the launch-mode file and starts the runtime.
- onDesktopModeChange/onDesktopLaunchModeChange persist launch-mode BEFORE the
fallible start so an interrupted start cannot re-create the desync.
- DesktopLaunchGate no longer assumes main started the runtime; if it is not
running/starting it actively (re)starts via setDesktopMode("local") before polling.
- Add env-gated startup trace (FUSION_STARTUP_TRACE) so packaged builds, which
otherwise log nothing, can diagnose this class of stall.
Regression tests assert the invariant (split-brain -> runtime starts + file heals;
agreement-on-choose -> no start). flushPromises now drains via a macrotask so
run()-based tests observe a fully-initialized app regardless of async chain length.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Desktop "Local" mode crashed with ERR_MODULE_NOT_FOUND because electron-builder's
pnpm collector drops `deduped` subtrees, so engine's transitive closure
(@modelcontextprotocol/sdk, pi-ai provider SDKs, etc.) was never packed. Stage the
complete flat prod closure with `pnpm deploy --legacy --config.node-linker=hoisted`
and package it via `electron-builder --projectDir deploy`, bypassing the lossy
collector entirely.
Also make the dashboard-imported example plugins loadable under plain Node (the
Electron main runtime): cursor/droid/roadmap now expose compiled `dist` on the
`import` condition (keeping `source`→src for the bun CLI) and are built during the
desktop build. Add `source` conditions to paperclip/agent-browser/even-cards/
even-realities-glasses/whatsapp-chat so the bun `--conditions=source` Windows CLI
compile resolves them from source.
Validated on macOS: @fusion/core|engine|dashboard import cleanly from the staged
deploy; packing yields a complete 705-package asar; bun-windows-x64 cross-compiles
with all plugin dist removed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The packaged desktop "Local" runtime dynamically imports @fusion/engine, whose
tsc dist is gitignored. desktop-windows.yml built only `@fusion/desktop build`
(no root `pnpm build`), so it packaged an empty engine/dist and the app crashed
on Local mode with ERR_MODULE_NOT_FOUND for app.asar/node_modules/@fusion/engine.
Make the desktop build self-contained (build core then engine before packaging)
and add the parity `pnpm build` step to desktop-windows.yml.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
local-runtime.ts dynamically imports @fusion/engine, but it was missing from the
esbuild externals list (only @fusion/core and @fusion/dashboard were there). esbuild
followed the import and tried to bundle engine's transitive node-pty native .node
binaries, failing with "No loader is configured for .node files" — which broke every
desktop Windows EXE and macOS DMG build leg. Externalize @fusion/engine like the other
workspace packages; it resolves from node_modules at runtime.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Clarify that desktop CLI launches with the local AI engine by default.\n\n- Document fn desktop as engine-on by default, matching dashboard startup behavior.\n- Clarify --paused keeps the engine process running with automation disabled.\n- Note that desktop has no --no-engine mode and update desktop README launch steps.\n\nFiles changed:\n docs/cli-reference.md | 12 ++++++++++--\n packages/desktop/README.md | 15 +++++++++++----\n 2 files changed, 21 insertions(+), 6 deletions(-)
Fusion-Task-Id: FN-6852
Fusion-Task-Lineage: 3ae2ea10-ede1-4cc5-a3b5-71f692b2a9ac
electron-builder's default @electron/rebuild step walked the desktop app's
whole dependency tree and tried to source-build @homebridge/node-pty-prebuilt-multiarch
(Node-ABI prebuilds only, none for Electron) via node-gyp, which fails on the
Windows runner with "Could not find any Visual Studio installation". The app
bundles no native addon needing an Electron-ABI rebuild (it uses node:sqlite;
keytar/node-pty aren't in the files list and are loaded optionally), so the
rebuild was pointless. Set npmRebuild: false to skip it on all platforms.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>