Skip to content

feat: complete profile switcher — backend + frontend - #33973

Closed
RobinAngele wants to merge 1 commit into
NousResearch:mainfrom
RobinAngele:feat/complete-profile-switcher
Closed

feat: complete profile switcher — backend + frontend#33973
RobinAngele wants to merge 1 commit into
NousResearch:mainfrom
RobinAngele:feat/complete-profile-switcher

Conversation

@RobinAngele

@RobinAngele RobinAngele commented May 28, 2026

Copy link
Copy Markdown

Status: tested on live server (Debian 12, v0.15.1 — 2026-05-29)
Merge: single commit, rebased on current main


What this does

Adds a profile switcher to the dashboard chat sidebar so users can switch agent profiles from the UI -- no config edits, no restart.

Layer Change
Backend POST /api/profiles/{name}/activate (auth), sticky file, GET /api/active-profile (public)
Frontend ProfilePickerDialog modal, read-only active card, clickable switcher card
Wiring channelBumpKey -> PTY reconnects under new HERMES_HOME in-place

Screenshots

Default Profile
Screenshot_20260528_180049 default profil

Custom Profile
Screenshot_20260528_180240_custom_profile

Profile Switcher Overlay
Screenshot_20260528_180127_profile_picker_layout


Architecture

┌─ BROWSER ───────────────────────────────────────────────────────────┐
│                                                                     │
│   ┌── ChatSidebar ────────────────┐   ┌── ProfilePickerDialog ────┐ │
│   │                               │   │                           │ │
│   │  ♛ acronyc     ← active      │   │  ○ default                │ │
│   │  ──────────────────────────  │   │  ● acronyc   ← checked    │ │
│   │  switch profile ▼            │──▶│  ○ infra                  │ │
│   │                               │   │  ○ wedding                │ │
│   └───────────────────────────────┘   └─────────────┬─────────────┘ │
│                                                      │ click         │
└──────────────────────────────────────────────────────┼───────────────┘
                                                       │
        GET /api/active-profile (public, on mount)     │ POST (auth)
                                                       │
┌─ SERVER ────────────────────────────────────────────┼───────────────┐
│                                                      ▼               │
│   ~/.hermes/active_profile   ◄──  sticky file  ──▶  read + write    │
│   _resolve_chat_argv()  ──▶  reads sticky, sets HERMES_HOME         │
│                                                                     │
│   channelBumpKey++  ──▶  new channel  ──▶  PTY reconnect            │
└─────────────────────────────────────────────────────┬───────────────┘
                                                       │
┌─ PTY (new profile) ────────────────────────────────▼───────────────┐
│   HERMES_HOME = ~/.hermes/profiles/acronyc                         │
│   ├── sessions/   ├── skills/   ├── config.yaml   ├── SOUL.md      │
└─────────────────────────────────────────────────────────────────────┘

Data flow -- profile switch

 click "infra"
     │
     ▼
 ProfilePickerDialog
     │  api.activateProfile("infra")        ── POST, writes sticky file
     ▼
 ChatSidebar.handleProfileActivated("infra")
     │  setActiveProfile("infra")            ── ♛ card updates instantly
     │  onProfileActivated?.()               ── bumps channel
     ▼
 App.bumpChannel()
     │  channelBumpKey++
     ▼
 ChatPage ── channel memo re-computes
     │  new channel ID
     ▼
 PTY WebSocket reconnects
     │  _resolve_chat_argv() reads sticky
     ▼
 HERMES_HOME = ~/.hermes/profiles/infra   ✅

Files changed

File Delta Notes
hermes_cli/web_server.py +40 Activate endpoint, sticky file read, /api/active-profile
web/src/components/ProfilePickerDialog.tsx +196 New. Modal, sort, loading/error/empty states
web/src/components/ChatSidebar.tsx +73 / -3 Active card, switcher card, handleProfileActivated
web/src/lib/api.ts +8 api.activateProfile() via fetchJSON
web/src/App.tsx +21 Config flag, channelBumpKey, bumpChannel()
web/src/pages/ChatPage.tsx +20 / -7 Props wiring, channel memo

Key details

  • Auth -- activate endpoint gated via _require_token(request), only dashboard sessions can switch
  • Security -- path traversal guard in _resolve_chat_argv() prevents ../../etc escapes
  • Feature gate -- dashboard.chat_by_agent_profile in config.yaml, defaults true
  • Events WS -- preserves upstream buildWsAuthParam() (ticket-based in gated mode, token-based in loopback)

Testing

# public endpoint -- no auth
curl http://localhost:9119/api/active-profile
# -> {"name":"acronyc"}

# activate -- requires session token
curl -X POST http://localhost:9119/api/profiles/infra/activate \
  -H "X-Hermes-Session-Token: <token>"
# -> {"ok":true,"active_profile":"infra","profile_dir":"/home/hermes/.hermes/profiles/infra"}
Browser:
  1. open /chat  ->  ♛ card shows current profile
  2. click "switch profile"  ->  picker lists all profiles
  3. select one  ->  ♛ card updates instantly, PTY reconnects

Closes

Issue
#33056 Backend -- sticky file, activate endpoint, /api/active-profile
#33739 Frontend -- ProfilePickerDialog, sidebar cards, PTY reconnect

Related (open -- not fixed here)

Issue
#30815 Original ProfilePickerDialog PR -- 3 bugs found in testing, fixed here
#30626 Gateway is profile-blind (serves boot-time profile, ignores live switches)
#33913 Double-.hermes path mismatch, HOME env leak in profile resolution
#32624 Config traps for memory / multi-profile users
#10674 Dashboard multi-profile switching -- original design analysis
#24914 One gateway for multiple agent profiles (future)
#29948 PID-file resolver ignores HERMES_PROFILE -> cross-profile SIGKILL
#27259 Docker -- align process HOME with active profile home

@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard comp/tui Terminal UI (ui-tui/ + tui_gateway/) javascript labels May 28, 2026
@RobinAngele
RobinAngele force-pushed the feat/complete-profile-switcher branch from e4340ed to b91e4f8 Compare May 29, 2026 02:03
@RobinAngele

Copy link
Copy Markdown
Author

Branch updated (2026-05-29)

Rebased on current main (v0.15.1) and squashed to a single clean commit — the diff is now exactly the 349 lines of the feature with zero noise from the original sub-PR merge history.

Verified working on a live Debian 12 server running v0.15.1: profile switch, PTY reconnect, active-profile endpoint, and feature flag all confirmed.

@RobinAngele

Copy link
Copy Markdown
Author

Hey @benbarclay and @austinpickett — would love a look when you have a moment.

This touches web_server.py (two new endpoints) and adds a ProfilePickerDialog component to the sidebar — areas you both know well. The branch is now a single clean commit rebased on current main (v0.15.1), so the diff should be easy to review.

Happy to adjust anything based on your feedback.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a dashboard profile switcher: a backend activate endpoint plus a public /api/active-profile, sticky-file consultation when spawning the chat PTY, and frontend wiring (new ProfilePickerDialog, sidebar active/switcher cards, channel-bump key) so the PTY reconnects under the new HERMES_HOME without a page reload.

Changes:

  • Backend: POST /api/profiles/{name}/activate (auth-gated), public GET /api/active-profile, and sticky-profile resolution in _resolve_chat_argv().
  • Frontend: new ProfilePickerDialog, two sidebar cards (active + switcher) gated on dashboard.chat_by_agent_profile.
  • Wiring: channelBumpKey in App.tsxChatPage channel memo dep → PTY reconnect.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
hermes_cli/web_server.py Adds activate endpoint, sticky-file consultation in PTY argv resolver, public active-profile endpoint
hermes_cli/dashboard_auth/public_paths.py Allowlists /api/active-profile for unauthenticated reads
web/src/lib/api.ts Adds api.activateProfile() client wrapper
web/src/components/ProfilePickerDialog.tsx New modal listing profiles and triggering activation
web/src/components/ChatSidebar.tsx Adds active-profile card and gated switcher card opening the dialog
web/src/pages/ChatPage.tsx Threads new props through and includes channelBumpKey in channel memo deps
web/src/App.tsx Reads chat sub-feature flags from config, owns channelBumpKey/bumpChannel

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread hermes_cli/web_server.py Outdated
Comment thread hermes_cli/web_server.py
Comment thread web/src/components/ChatSidebar.tsx Outdated
- /api/active-profile added to PUBLIC_API_PATHS (public_paths.py, both gates)
- POST /api/profiles/{name}/activate endpoint with auth + validation
- Sticky file ~/.hermes/active_profile resolved in _resolve_chat_argv()
- ProfilePickerDialog component with profile list, activation, loading states
- chatByAgentProfile / channelBumpKey feature flags in App.tsx
- ChatSidebar integration with Crown icon and onProfileActivated callback
- ChatPage channelBumpKey mechanism restarts PTY on profile switch
@RobinAngele
RobinAngele force-pushed the feat/complete-profile-switcher branch from 04fcbf4 to 109b85b Compare May 29, 2026 05:59
@RobinAngele

RobinAngele commented May 29, 2026

Copy link
Copy Markdown
Author

@austinpickett Thanks for the Copilot review — all three addressed in the latest push:

  1. _flavour.sep → replaced with _pd.is_relative_to(_profiles_root) (idiomatic, no private API)
  2. Hard-coded Path.home() / ".hermes" → both _resolve_chat_argv and /api/active-profile now use profiles_mod.get_active_profile(), _get_default_hermes_home(), get_profile_dir(), and _get_profiles_root() — Docker / custom-HERMES_HOME deployments will resolve correctly
  3. Dead profile_name?: string → removed from SessionInfo

@alt-glitch alt-glitch added comp/dashboard Web dashboard / control panel UI (dashboard/, landing) and removed javascript comp/cli CLI entry point, hermes_cli/, setup wizard comp/tui Terminal UI (ui-tui/ + tui_gateway/) labels Jun 22, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the profile-switcher work. An automated hermes-sweeper review found that the requested dashboard behavior is already implemented on current main through a broader, profile-scoped dashboard design.

  • Commit 875aa8f162aa40f07b19b2ca229720da70193d41 (feat(dashboard): unify multi-profile management — one machine dashboard, global profile switcher (#44007)) added the sidebar selector, scoped chat PTY flow, docs, and regression coverage.
  • web/src/components/ProfileSwitcher.tsx:18 provides the dashboard profile selector; web/src/pages/ChatPage.tsx:296 and :911 key/recreate the PTY and send the selected profile to /api/pty.
  • hermes_cli/web_server.py:14572:14645 binds scoped chat children to the selected profile’s HERMES_HOME and avoids cross-profile gateway attachment.
  • tests/hermes_cli/test_web_server_profile_unification.py:582 verifies scoped HERMES_HOME, legacy behavior, and unknown-profile rejection.

This superseding implementation shipped in v2026.6.19.

@teknium1 teknium1 closed this Jul 13, 2026
@teknium1 teknium1 added the sweeper:implemented-on-main Sweeper: behavior already present on current main label Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/dashboard Web dashboard / control panel UI (dashboard/, landing) P3 Low — cosmetic, nice to have sweeper:implemented-on-main Sweeper: behavior already present on current main type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants