This doc explains how the embedded agent starts in the packaged desktop app and why the exception-handling guards in eliza/packages/app-core/platforms/electrobun/src/native/agent.ts must not be removed.
- Electrobun main process starts, creates the window, and resolves the renderer URL (Vite dev server via
ELIZA_RENDERER_URLor the built-in static asset server for packagedapps/app/dist). AgentManager.start()(innative/agent.ts) spawns a child Bun process:bun run <eliza-dist>/entry.js start(or the equivalent path for your bundle layout). The child is not an in-process dynamic import ofserver.js/eliza.js.- Child process boots the Eliza CLI entrypoint, starts the API server, and runs the elizaOS runtime in headless mode inside that process.
- Main process health-polls
http://127.0.0.1:{port}/api/healthuntil the child reports ready (or times out / errors). - Main process pushes
apiBaseUpdate(and related RPC) to the renderer so the boot-configapiBasematches the live API.
If the child fails to start or never becomes healthy:
- The Electrobun window stays up so the user is not left with a blank shell.
- Status is set to
state: "error"with an error message so the UI can show Agent unavailable: … instead of a generic Failed to fetch.
For dev orchestration (Vite + API + Electrobun in separate processes), see Desktop local development.
Goal: When the runtime fails to load (e.g. missing native binary), the user should see a clear error in the UI, not a dead window. That requires (1) the main process and renderer staying alive, and (2) status / RPC updates so the UI can show Agent unavailable: ….
Without explicit handling:
- If the child process crashes or health never succeeds, the main process must surface that as error state to the renderer.
- If the outer
start()tore down the window or assumed the API lived in-process, the renderer could lose API base and show Failed to fetch with no explanation.
So we keep:
- Child process isolation — API + runtime failures are contained in the child; the main process observes exit codes / health.
- try/catch and
.catch()where still applicable — Any remaining async paths that could reject should set error state instead of leaving the UI uninitialized. - Outer paths that must NOT kill the shell when the goal is to show an in-app error — align with
native/agent.tscomments and this doc.
The remaining try/catch and .catch() paths are intentional. They keep the app
window usable when the runtime fails to load. Removing them would bring back
broken behavior: a dead window, Failed to fetch, and no useful error
message.
The key sites in agent.ts include comments that reference this doc. When
editing that file, preserve the guards and the rationale.
Packaged app writes a startup log to:
- macOS:
~/Library/Application Support/Eliza/eliza-startup.log - Windows:
%APPDATA%\Eliza\eliza-startup.log - Linux:
~/.config/Eliza/eliza-startup.log
Use it to debug load failures (missing modules, native binary path, etc.).
- Plugin resolution and NODE_PATH — why dynamic plugin imports need
NODE_PATHand where it's set. - Build and release — CI pipeline, Rosetta builds, plugin/dep copying.