SIGN IN SIGN UP

feat(agents-desktop): Electric Cloud onboarding + agent-servers shape (#4329)

## Summary

Three stacked changes, all on the desktop side, that together let a
freshly-installed Electric Agents desktop app sign into Electric Cloud
and connect to a hosted agent server end-to-end:

1. **Electric Cloud sign-in** — Settings → Account panel + main-process
OAuth flow that mirrors the CLI's loopback-redirect path against the
admin-API. JWT persisted via `safeStorage`, name + workspaces refreshed
via `auth.whoami`.
2. **First-launch onboarding wizard** — two-step modal on launch (sign
in to Electric Cloud → set provider API keys). Skip + "Don't show again"
persisted to `settings.json`.
3. **Cloud Agent Servers shape consumer** — new section on Settings →
Servers lists the agent servers the signed-in user can reach across
their workspaces, joined client-side with `workspaces` / `projects` /
`environments` shapes. A Connect button on each row mints a service JWT,
stores it encrypted, and wires the desktop's local runtime (Horton +
Worker) to the cloud agents server with auth headers injected
transparently on every renderer- and Node-side fetch.

This stacks on top of the work in #4316
(kevin/agents-desktop-cloud-auth) for the sign-in commits; if that PR
lands first the rebase here is trivial.

## Architecture notes

- **Renderer fetches** pick up auth via Electron's
\`session.webRequest.onBeforeSendHeaders\`.
- **Main-process fetches** (BuiltinAgentsServer registering types,
health-checks) go through a global undici dispatcher interceptor that
adds the same headers. Same matching logic (saved \`electric-cloud\`
server URL + \`tenantId\`), no per-call-site plumbing.
- **The service JWT is never in \`settings.json\`** — it lives in the
encrypted \`SecretStore\` keyed by tenant id; \`settings.json\` only
stores \`{url, source: 'electric-cloud', tenantId}\`.
- **Pull-wake runner registration** uses the signed-in cloud user's id
as \`owner_user_id\` (so the server's authenticated-owner check passes)
and routes the claim token to the \`electric-claim-token\` header (so it
doesn't collide with the \`Authorization: Bearer\` our interceptor
injects).
- **Onboarding** is gated by \`onboardingDismissed\` in
\`settings.json\`. Auto-advances cloud → API-keys step the moment cloud
sign-in completes; "Don't show this again" link + completing the
API-keys step both set the flag. Skipping leaves the flag alone so the
wizard reappears next launch.

## Test plan

- [ ] Fresh install (delete \`~/Library/Application Support/Electric
Agents/settings.json\` + \`secrets.json\`) → onboarding wizard appears.
- [ ] Step 1: sign in with GitHub / Google → auto-advances to step 2.
- [ ] Step 2: set API keys or skip → wizard closes.
- [ ] Settings → Servers → "Cloud Agent Servers" lists the user's cloud
agent servers with the workspace › project › environment path.
- [ ] Click Connect on a row → server added to "Configured Servers" with
\`source: electric-cloud\` and a stored \`tenantId\`. Status flips to
Connected.
- [ ] Spawn a Horton session, send a message → Horton responds
end-to-end against the cloud server.
- [ ] Quit + relaunch → session restores, wizard doesn't re-appear,
cloud server reconnects automatically.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Ilia Borovitinov <ilia@electric-sql.com>
K
Kevin committed
c4e046fecb927f1fbc2fc9d465713d1770cd46cc
Parent: a15c7b6
Committed by GitHub <noreply@github.com> on 5/18/2026, 11:08:43 AM