Skip to content

fix(desktop): distinguish pooled profile backends - #85778

Closed
wdon wants to merge 1 commit into
NousResearch:mainfrom
wdon:fix/desktop-local-profile-socket
Closed

fix(desktop): distinguish pooled profile backends#85778
wdon wants to merge 1 commit into
NousResearch:mainfrom
wdon:fix/desktop-local-profile-socket

Conversation

@wdon

@wdon wdon commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes local Desktop profile switching after pooled connection descriptors began carrying profile for WebSocket URL minting.

sharedPrimaryRoute() inferred that any descriptor with profile was served by the shared primary backend. Local pooled descriptors also carry profile, so a named local profile was misclassified and Desktop reused the default socket instead of opening the profile's persistent WebSocket.

This change gives the shared-primary route an explicit sharedPrimary marker and checks that marker instead of overloading profile with two meanings.

Related Issue

Fixes #85777

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • apps/desktop/electron/main.ts: marks only profile-scoped primary descriptors as sharedPrimary.
  • apps/desktop/src/global.d.ts: types the explicit route discriminator.
  • apps/desktop/src/store/gateway.ts: distinguishes shared-primary descriptors from profile-owned pooled descriptors.
  • apps/desktop/src/store/gateway-shared-remote.test.ts: covers both sides of the routing invariant, including a pooled descriptor that carries profile.

How to Test

  1. Configure Desktop with local default and local named profiles.
  2. Launch Desktop on default and select a named profile from the profile rail.
  3. Confirm the named backend receives a persistent WebSocket and the sidebar/agent context switches to it.
  4. Switch back to default and confirm the primary socket is restored.

Automated checks:

cd apps/desktop
npm test -- --run src/store/gateway-shared-remote.test.ts src/store/profile.test.ts
# 2 files passed, 13 tests passed

npm run lint
# 0 errors (88 pre-existing warnings)

npm run typecheck passed on the checkout used to build and validate the installed fix. After rebasing onto current origin/main (edb33be51), it is presently blocked by an unrelated baseline error in src/app/chat/hooks/use-composer-actions.test.ts:294: the new ComposerActionsScope.update field is missing from that test fixture. This PR does not modify that code.

The rebuilt macOS package was installed locally and the reporter confirmed profile switching works after restart.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass — N/A: Desktop TypeScript-only change; focused Vitest coverage ran instead
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS 26.6.1, Apple silicon

Documentation & Housekeeping

  • I've updated relevant documentation — N/A; internal routing bug
  • I've updated cli-config.yaml.example — N/A; no config change
  • I've updated CONTRIBUTING.md or AGENTS.md — N/A; no workflow change
  • I've considered cross-platform impact — the explicit descriptor marker is platform-independent
  • I've updated tool descriptions/schemas — N/A; no tool change

Screenshots / Logs

Before the fix, the named backend started and its one-shot startup probe connected, but no persistent WebSocket followed:

Starting Hermes backend for profile "fiction-writer"
HERMES_BACKEND_READY port=64271
Hermes backend listening on 127.0.0.1:64271
...
tui_gateway.ws: ws accepted ...
tui_gateway.ws: ws closed ... messages=0

After rebuilding with this change and restarting Desktop, switching between default and the local named profile works normally.

@alt-glitch alt-glitch added type/bug Something isn't working comp/desktop Electron desktop app (apps/desktop/*) area/profiles Multi-profile isolation, HERMES_HOME scoping P2 Medium — degraded but workaround exists duplicate This issue or pull request already exists sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Aug 14, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Duplicate of #85750. Both PRs implement the same sharedPrimary route discriminator for pooled-profile WebSocket routing; #85750 is the earlier open canonical PR.

@OutThisLife

Copy link
Copy Markdown
Collaborator

Superseded by #86325, which consolidates the sharedPrimary fix from this PR and its siblings (#85750, #85778, #85932) into one change — credit to all three authors via Co-authored-by. Thanks @wdon!

OutThisLife added a commit that referenced this pull request Aug 14, 2026
…0.20.1

sharedPrimaryRoute() inferred "served by the shared primary backend" from
the mere presence of connection.profile. But pooled backends (a local named
profile, or a per-profile remote override) also carry `profile` so their
WebSocket URL mints against the right backend. Both descriptors looked the
same, so ensureGatewayForProfile() took the shared-primary branch for a
pooled profile and never dialed its socket — Desktop stayed on the default
profile's socket even though the sidebar (REST) listed the right sessions.

Regression from #85665 (d16e236). Tag only the true shared-primary
descriptor with an explicit `sharedPrimary: true` marker in ensureBackend()
and check that marker instead of `profile`. Covers both the local-pool and
remote-override routes — the whole bug class, not one path.

Test asserts both sides of the invariant: a { profile, sharedPrimary: true }
descriptor activates the primary socket without dialing, and a pooled
descriptor carrying { profile } dials its own exact WebSocket URL.

Supersedes #85750, #85778, #85932
Fixes #85777

Co-authored-by: Tigrannnnnnn <122704900+Tigrannnnnnn@users.noreply.github.com>
Co-authored-by: Don Tuttle <11698271+wdon@users.noreply.github.com>
Co-authored-by: plcunha <145560011+plcunha@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/profiles Multi-profile isolation, HERMES_HOME scoping comp/desktop Electron desktop app (apps/desktop/*) duplicate This issue or pull request already exists P2 Medium — degraded but workaround exists sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(desktop): local profile switch reuses default socket after update

3 participants