Skip to content

fix(desktop): hydrate canonical Bot Chats on profile switch - #89284

Closed
citizendev9c wants to merge 1 commit into
NousResearch:mainfrom
citizendev9c:fix/bot-switch-hydration-20260819-v2
Closed

citizendev9c wants to merge 1 commit into
NousResearch:mainfrom
citizendev9c:fix/bot-switch-hydration-20260819-v2

Conversation

@citizendev9c

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes Bot Mode profile switches that can leave the Desktop main surface blank even though the target bot's canonical session and persisted history still exist.

The switch now completes only after the target profile is active, the canonical Bot Chat owns the main route, and the main transcript is hydrated (or the session is explicitly known to be empty). It also tightens SSH-managed backend ownership proof so stale cleanup does not mistake a foreign process for a Desktop-owned backend.

Addresses #89206.

Related Issue

Type of Change

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

Changes Made

  • Force canonical Bot Chat opens onto the main session surface, even when the session is already open in a tile.
  • Wait for main-route profile/runtime/transcript hydration before treating a bot switch as complete.
  • Re-resume same-route blank sessions and expose the existing Retry surface on terminal hydration timeout.
  • Keep only the latest rapid bot selection intent and prevent stale async opens from winning.
  • Preserve canonical pins on transient lookup/open failures instead of forking a replacement chat.
  • Distinguish persisted-history sessions from explicitly empty sessions.
  • Require exact SSH backend ownership evidence: entrypoint, nonce, ownership-scoped token file, profile, serve, and post-serve --isolated option placement.
  • Add regression coverage for wrapper argv, wrong profile/nonce/options, main-surface hydration, explicit empty, same-route recovery, transient no-fork, and latest-intent behavior.

How to Test

  1. Configure at least two Bot Mode profiles with canonical Bot Chats containing history.
  2. Repeatedly switch between primary and named bots, including rapid A → B → A clicks.
  3. Confirm every selection lands on that bot's main chat and shows its persisted transcript; a same-route blank state must re-resume rather than stay blank.
  4. Simulate a transient profile lookup/open failure and confirm the canonical pin is preserved with a Retry state, without creating a replacement session.
  5. Run the Desktop gates below.

Verification run on macOS arm64 / Node 26

  • node ../../node_modules/vitest/vitest.mjs run --project ui → 4,780 passed
  • node ../../node_modules/vitest/vitest.mjs run --project electron → 1,433 passed / 2 skipped
  • node --test src/plugins/hermes-bots/tests/*.test.mjs → 261 passed
  • renderer, Electron, and E2E TypeScript configs → PASS
  • ESLint on changed TypeScript/Electron files → PASS
  • npm run build → PASS
  • python3 scripts/check-windows-footguns.py --diff origin/main → no findings (no Python files changed)

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/Electron-only change; Desktop suites above were run)
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS arm64

Documentation & Housekeeping

  • I've updated relevant documentation — N/A; no public API/config change
  • I've updated cli-config.yaml.example — N/A; no config keys changed
  • I've updated CONTRIBUTING.md or AGENTS.md — N/A; no workflow contract changed
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide
  • I've updated tool descriptions/schemas — N/A; no tool schema changed

Screenshots / Logs

Automated regression coverage and exact gate counts are included above. A packaged live-client smoke is tracked separately because the running Desktop client is on a remote macOS machine, while the implementation and build host is the SSH gateway host.

@johnsonAyo

Copy link
Copy Markdown
Contributor

Independent verification — this PR fixes the blank Bot Mode pane for me

Reproduced #89206 on a local (non-SSH) setup, then confirmed this PR resolves it.

Before

On a fresh launch of the packaged v0.20.4 app, the primary bot (default, shown as "Hermes") opened its transcript normally, while every named non-primary bot opened a blank pane with the tab reading NEW SESSION:

Bot Profile Messages in DB Bots pane
Hermes default (primary) 29 renders
Reddit star named profile A 112 blank / NEW SESSION
(third bot) named profile B 87 blank / NEW SESSION
LinkedIn named profile C 2 blank / NEW SESSION

(Profile names redacted — they're work projects.)

Two details that match the "not data loss" reading in the issue:

  • The roster previews were correct throughout — the 87-message bot's row showed the last line of that conversation while its pane stayed blank. So profiles.list was resolving those sessions fine; only the transcript failed to hydrate.
  • Every session was intact on disk: the pins resolved, and message_count matched the count of active = 1 rows in messages for all four.

Restarting the app did not help.

After

Applied this PR to 9664e386f (applies cleanly), built the renderer from source, launched with hermes desktop --source.

All four bots now open their transcripts, including the three that were blank. Switching between them repeatedly, and clicking a non-primary bot first on a cold start, no longer reproduces it.

The Python gateway was running stock 9664e386f — this PR's TypeScript was the only change in play. The Bot Mode sessions were un-hidden (hidden = 0) during both the before and after runs, from an earlier workaround to reach them via the Sessions tab.

node --test src/plugins/hermes-bots/tests/*.test.mjs passes 32/32 locally.

Environment

  • macOS 27.0, arm64
  • Hermes Agent v0.20.4 (2026.8.18), upstream 9664e386
  • Node v26.7.0
  • Local gateway, one serve process per profile, 4 profiles — not the SSH remote setup in the original report, so this reproduces independently of the remote transport

@alt-glitch alt-glitch added type/bug Something isn't working comp/desktop Electron desktop app (apps/desktop/*) sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state P2 Medium — degraded but workaround exists labels Aug 18, 2026
@teknium1

Copy link
Copy Markdown
Collaborator

Merged via PR #89394 — your commit was cherry-picked onto current main with your authorship preserved in git log. Thanks for the deep diagnostic work on this one; the hydration-await design and the SSH ownership tightening landed as-is. On top of it we reworked the resume gate to also fire on the cold-open shape from #89206 (selection not yet settled) and widened the same contract to the profile session browser open path.

@DanBennettUK

Copy link
Copy Markdown
Contributor

Follow-up live validation: PR #89284’s automated tests, typechecks, and package build passed. The packaged build could load a primary/default Bot Chat, but a cold named-profile Chat still timed out during hydration.

I also tested the proposed #89843 profileMatches relaxation and a direct-resume variant locally. Both passed focused tests/typechecks, but the live named-profile timeout remained. In the final run, the target WebSocket processed messages with no parse errors, dispatch crashes, or send failures, while the focused main transcript still did not hydrate.

I’ve added the detailed findings to #89843: #89843 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/desktop Electron desktop app (apps/desktop/*) 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.

5 participants