Skip to content

feat(dashboard): add configurable Chat UI sub-features — chat system monitor, agent profile picker, and per-profile gating - #30815

Open
emmanuel-a-otchere wants to merge 7 commits into
NousResearch:mainfrom
emmanuel-a-otchere:feat/chat-ui-feature-gating
Open

feat(dashboard): add configurable Chat UI sub-features — chat system monitor, agent profile picker, and per-profile gating#30815
emmanuel-a-otchere wants to merge 7 commits into
NousResearch:mainfrom
emmanuel-a-otchere:feat/chat-ui-feature-gating

Conversation

@emmanuel-a-otchere

Copy link
Copy Markdown

Summary

Adds three new configurable sub-features to the Hermes dashboard Chat tab:

Flag Default Description
dashboard.chat_ui true Master switch for the Chat tab (requires --tui server flag)
dashboard.chat_system_monitor true Shows live memory · disk · token · time metrics bar above the terminal
dashboard.chat_by_agent_profile true Shows Agent Profile selector card + profile switcher dialog in the Chat sidebar

Backend additions

  • POST /api/profiles/{name}/activate — activates a named profile persistently (sticky across restarts)
  • GET /api/agents/metrics — live telemetry: memory usage, disk usage, token totals+by-model, active profile name, server time

Frontend additions

  • AgentMetricsBar.tsx — mini SVG arc gauges for memory/disk, token bar, live clock. Polls /api/agents/metrics every 30s. Stops polling entirely when visible={false}.
  • ProfilePickerDialog.tsx — profile switcher with active indicator, soul badge, emoji icon, inline soul editor.

Files changed

M hermes_cli/config.py         — 3 new dashboard config keys
M hermes_cli/web_server.py     — 2 new API endpoints
M web/src/App.tsx              — reads all 3 flags, passes sub-feature props
M web/src/pages/ChatPage.tsx   — wires AgentMetricsBar + chatByAgentProfile
M web/src/components/ChatSidebar.tsx — profile card gated on chatByAgentProfile
M web/src/lib/api.ts           — activateProfile(), getAgentMetrics() + types
M web/src/lib/dashboard-flags.ts
A web/src/components/AgentMetricsBar.tsx
A web/src/components/ProfilePickerDialog.tsx

Testing

  1. Start dashboard with hermes dashboard --tui
  2. Verify Chat tab appears in nav
  3. Toggle each sub-feature in Config → Display
  4. Confirm Agent Profile card appears in Chat sidebar and profile switching works

Contributed by Emmanuel A Otchere · feat/chat-ui-feature-gating

…monitor, agent profile picker, and per-profile gating
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/tui Terminal UI (ui-tui/ + tui_gateway/) comp/cli CLI entry point, hermes_cli/, setup wizard area/config Config system, migrations, profiles labels May 23, 2026
Emmanuel A Otchere added 4 commits May 23, 2026 23:01
- Wrap WebglAddon construction + loadAddon() separately so constructor
  failures (WebGL2 unavailable in headless/VM environments) don't crash
  the synchronous React render path
- Fix TS6133 'arcPath' unused by exporting the function
- Add missing MEM_MAX/DISK_MAX constants to AgentMetricsBar
- Remove unused ChevronDown import from ProfilePickerDialog
- Re-add missing cn import to ProfilePickerDialog
- Remove unused profiles state from ChatSidebar (was only set, never read)
- Remove unused getProfiles() call from ChatSidebar (profile list comes from ProfilePickerDialog)
- Fix unused html parameter in vite.config.ts transformIndexHtml hook
- Add missing 'api' import to ChatSidebar
…and Task 8 (rapid switches, no WS leaks); add vitest + jsdom to web devDeps
@emmanuel-a-otchere
emmanuel-a-otchere requested a review from a team May 23, 2026 16:20
Emmanuel A Otchere added 2 commits May 24, 2026 00:39
…ve sidebar label

The Agent Profile card was showing 'default' while the PTY was running a
different profile, because getAgentMetrics reads active_profile from the
web_server process's own HERMES_HOME rather than the profile the PTY child
is actually running under.

Fix: the PTY-side TUI gateway emits session.info with profile_name in the
payload every time it boots.  ChatSidebar now reads ev.payload.profile_name
in the session.info handler and uses it to set activeProfile — this is the
ground-truth label of what the agent loop is actually doing.

getAgentMetrics is retained for the initial mount (in case the PTY hasn't
emitted session.info yet) and as a fallback when session.info has no
profile_name (e.g. older TUI builds).  3 new tests for the session.info
precedence logic.
…em; agent profile card now shows 'switch profile' action
@RobinAngele

RobinAngele commented May 27, 2026

Copy link
Copy Markdown

Live deployment report — working, with 3 issues + fixes

Deployed the profile switching parts of this PR on Debian 12, Hermes v0.14.0, --tui dashboard. The core (ProfilePickerDialog, activateProfile API, activate endpoint) works. Below is exactly what we deployed, what we changed, and why.

What we used as-is from this PR ✅

  • web/src/components/ProfilePickerDialog.tsx — no changes needed
  • web/src/lib/api.ts activateProfile() — correct
  • POST /api/profiles/{name}/activate endpoint — correct

What we skipped (unrelated to profile switching)

  • AgentMetricsBar.tsx + chat_system_monitor — not needed for switcher
  • pnpm-lock.yaml / pnpm-workspace.yaml — npm build works fine without
  • web/vite.config.ts changes — build succeeded without

Issue 1 — PTY ignores switched profile (fixed in #33056)

_resolve_chat_argv() spawns the PTY with the dashboard process's own HERMES_HOME from os.environ.copy(). After activateProfile() updates the sticky file and the page reloads, the new PTY still uses the startup profile. This also breaks the "default" profile case: set_active_profile("default") deletes the sticky file, so the PTY sees no file and falls back to the startup profile.

Fix: #33056 adds 14 lines to _resolve_chat_argv() to read ~/.hermes/active_profile and set HERMES_HOME accordingly before spawning the PTY. Also adds a public GET /api/active-profile endpoint.

Issue 2 — getAgentMetrics() fails silently

In ChatSidebar.tsx, the initial useEffect calls api.getAgentMetrics() which hits GET /api/agents/metrics. This endpoint may not be registered before the SPA catch-all, or the auth middleware blocks it. The .catch(() => {}) swallows the error silently → activeProfile stays "default" until the PTY connects.

Workaround: Replaced api.getAgentMetrics() with fetch("/api/active-profile") (the public endpoint from #33056).

Issue 3 — session.info race condition

When the PTY connects, session.info.profile_name overwrites activeProfile. If the PTY spawned before _resolve_chat_argv was fixed (issue 1), this sets the WRONG profile. Even when fix 1 is applied, there's a timing race with the API fetch on mount — whichever resolves last wins.

Quick fix we used: Removed the setActiveProfile(ev.payload.profile_name) override (3 lines). The /api/active-profile fetch on mount is authoritative.

Proper fix (not yet implemented — suggested for this PR): use a useRef flag so the API fetch takes priority once it completes, but session.info still works as a fallback if the API hasn't responded yet:

const profileFetched = useRef(false);
useEffect(() => {
  fetch("/api/active-profile")
    .then(r => r.json())
    .then(d => { setActiveProfile(d.name); profileFetched.current = true; });
}, []);
// In session.info handler:
if (ev.payload.profile_name && !profileFetched.current) {
  setActiveProfile(ev.payload.profile_name);
}

Modified in ChatSidebar.tsx

// handleProfileActivated: removed api.getAgentMetrics chain, replaced with:
setTimeout(() => location.reload(), 1000);  // API already called by ProfilePickerDialog

// Initial fetch: replaced api.getAgentMetrics() with:
fetch("/api/active-profile").then(r => r.json()).then(d => setActiveProfile(d.name));

// session.info handler: removed profile_name override (3 lines deleted)

Suggestion for this PR

  1. The _resolve_chat_argv fix in fix(web_server): resolve dashboard PTY profile from sticky active_profile on reload #33056 is essential — without it, the PTY never switches profiles
  2. Consider replacing getAgentMetrics() with the lightweight /api/active-profile endpoint (also in fix(web_server): resolve dashboard PTY profile from sticky active_profile on reload #33056)
  3. The session.info approach is correct, but add a useRef guard (shown above) so the API fetch takes priority once it completes

RobinAngele added a commit to RobinAngele/hermes-agent that referenced this pull request May 28, 2026
…oint + public active-profile endpoint

1. _resolve_chat_argv() reads ~/.hermes/active_profile sticky file and sets
   HERMES_HOME before spawning the PTY. Handles named profiles and default
   (missing file = default profile). Includes path traversal guard via
   .resolve() + boundary check.

2. POST /api/profiles/{name}/activate - activates a profile persistently
   (sticky across restarts). Delegates validation to set_active_profile()
   from hermes_cli.profiles (no redundant pre-validation).

3. GET /api/active-profile - public endpoint (no auth) for frontend to
   read the current active profile. Added to _PUBLIC_API_PATHS.

Fixes: PTY ignoring switched profile after page reload.
Related: NousResearch#30815
@alt-glitch alt-glitch added comp/dashboard Web dashboard / control panel UI (dashboard/, landing) and removed comp/cli CLI entry point, hermes_cli/, setup wizard comp/tui Terminal UI (ui-tui/ + tui_gateway/) labels Jun 26, 2026

@teknium1 teknium1 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.

Thanks for the dashboard work. Current main has since adopted a different, machine-level profile-selection architecture, so this needs selective rework before its remaining monitor/gating work can be considered.

Problems

  • web/src/components/ProfilePickerDialog.tsx:92 activates a profile, then its callback reaches web/src/components/ChatSidebar.tsx:339, which activates it a second time.
  • hermes_cli/web_server.py:2986 and :2993 hardcode ~/.hermes and a profiles/active_profile path. Current main resolves sticky selection through hermes_cli.profiles (web_server.py:12922) and scopes Chat explicitly through ?profile= (web_server.py:14572-14645).
  • Main commit 875aa8f162aa40f07b19b2ca229720da70193d41 already supplies a global ProfileSwitcher; Chat derives its PTY scope from it at web/src/pages/ChatPage.tsx:299-302 and forwards it at :911-915. A second sidebar picker would duplicate and conflict with that source of truth.

Suggested changes

  • Salvage only the monitor/gating idea onto the shared profile-scope flow, with profile-safe metrics and one activation owner.
  • Add resolver-level profile-scope coverage rather than mock-only switch-flow tests.

Automated hermes-sweeper review.

if (activating) return;
setActivating(name);
try {
await api.activateProfile(name);

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.

This request is duplicated by the callback path: onProfileActivated(name) reaches ChatSidebar.handleProfileActivated, which calls api.activateProfile(name) again. Make either the dialog or the sidebar the sole mutation owner, then have the other layer only report success/reconnect.

Comment thread hermes_cli/web_server.py
# Disk: walk ~/.hermes for total, and active profile dir for active
import shutil

hermes_home = os.path.expanduser("~/.hermes")

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.

Do not hardcode ~/.hermes here. This bypasses profile-aware path resolution; the adjacent active-profile read also targets ~/.hermes/profiles/active_profile, while the current profile helper owns the sticky-file location. Use the profile helpers / get_hermes_home() and test with a temporary HERMES_HOME.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/dashboard Web dashboard / control panel UI (dashboard/, landing) P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants