hermes-agent/web/src/components
Austin Pickett fb402106f8
fix(dashboard): auto-reconnect the events WebSocket with backoff (supersedes #47876, #47921, #24315) (#79524)
* fix(dashboard): add events-feed reconnect policy helpers

Extract the reconnect arithmetic and close-code classification for the
ChatSidebar /api/events socket into a pure module so both can be tested
without a fake WebSocket or a mounted component.

Two decisions live here rather than inline in the effect:

- `shouldRetryEventsClose` — 1000 (normal) and 4401/4403 (auth) are
  terminal; everything else, including 1005/1006 from a killed gateway
  or a dropped network, is retryable.
- `isEventsFeedMessage` — the sidebar's banner is shared with
  `info.credential_warning` and the JSON-RPC sidecar, so a reconnect may
  only clear a message the events feed wrote itself.

Co-authored-by: Ishan Parihar <ishan@supreme-god.dev>
Co-authored-by: Vyre <vyre@ishanparihar.com>
Co-authored-by: eric-senyao <178080753+eric-senyao@users.noreply.github.com>

* fix(dashboard): auto-reconnect the events WebSocket with backoff

The chat sidebar's /api/events subscriber surfaced a static "disconnected"
banner on a transient drop and never retried, so a gateway restart or a
network blip left the feed dead until the user reloaded the page. The feed
drives the live chat title (session.info) and dashboard.new_session_requested,
both of which silently stopped working.

Reconnect with exponential backoff (1s → 2s → 4s → … → 30s cap, 15 attempts
then a terminal banner). Specifically:

- One scheduling path. `close` always follows `error` for a failed socket,
  so scheduling from both — as the superseded PRs did — queues two timers
  and leaks the one that is no longer tracked for cleanup. `scheduleReconnect`
  returns early when a retry is already pending.
- The auth ticket is re-minted per attempt via `buildWsUrl`; tickets are
  single-use with a short TTL, so replaying the first URL would 4401 on the
  second attempt.
- A superseded socket's late close cannot schedule a retry on top of its
  replacement (`isCurrent` generation check).
- A successful open resets the backoff and clears only the events feed's own
  banner, leaving a credential warning or sidecar error visible.
- The pending timer is cleared on unmount, not merely neutered by the
  `unmounting` flag.

Also retires two strings the tools box left behind when it was removed in
47fccc073 (#51737): the banner no longer promises "tool calls may not
appear" and the button reads "reconnect events feed".

Co-authored-by: Ishan Parihar <ishan@supreme-god.dev>
Co-authored-by: Vyre <vyre@ishanparihar.com>
Co-authored-by: eric-senyao <178080753+eric-senyao@users.noreply.github.com>

* test(dashboard): cover the events-feed reconnect bug class

Fake-timer coverage for the behaviors the superseded PRs changed without
tests. Each case was mutation-checked — reverting the corresponding guard
in ChatSidebar.tsx makes exactly that test fail:

- transient close reconnects, and backoff grows 1s → 2s → 4s
- error + close on one socket schedules ONE retry, not two
- a successful open resets the backoff to 1s
- 4401/4403 and a normal 1000 close never retry
- the attempt cap stops the loop instead of retrying forever
- reconnect clears the feed's own banner but not a credential warning
- unmount clears the pending timer (asserted via `vi.getTimerCount()`,
  since the `unmounting` flag alone hides a leaked timer)

Co-authored-by: Ishan Parihar <ishan@supreme-god.dev>
Co-authored-by: Vyre <vyre@ishanparihar.com>
Co-authored-by: eric-senyao <178080753+eric-senyao@users.noreply.github.com>

* fix(dashboard): stop the events feed overwriting a foreign banner

Review catch: `clearEventsBanner` guarded the shared banner but `surface`
did not, so the guard was only half applied. A sidecar error or
`credential_warning` already on screen when the feed dropped was replaced
by "events feed disconnected" — and lost for good, since `error` is that
message's only home and the sidecar does not re-emit.

`surface` now writes only over an empty banner or one of the feed's own
messages. Declining to write does not affect the retry itself; the
reconnect still runs on schedule, it just stays silent while a more
important message holds the banner.

Both directions are covered: a foreign banner survives a drop, and the
reconnect still fires while suppressed.

---------

Co-authored-by: Ishan Parihar <ishan@supreme-god.dev>
Co-authored-by: Vyre <vyre@ishanparihar.com>
Co-authored-by: eric-senyao <178080753+eric-senyao@users.noreply.github.com>
2026-08-05 14:27:04 -06:00
..
AuthWidget.tsx fix(dashboard): QA pass — log colors, nameless channels, config bool, UX gaps 2026-07-26 15:17:30 -07:00
AutoField.tsx Merge remote-tracking branch 'origin/main' into refactor/use-ds-primitives 2026-05-28 14:20:49 -04:00
AutomationBlueprints.tsx fix(dashboard): use DS Button prefix/size API instead of inline icons 2026-06-18 16:00:26 -04:00
ChatSessionList.tsx fix(sec): update npm deps to resolve `npm audit` warnings 2026-07-31 13:42:03 -04:00
ChatSidebar.test.tsx fix(dashboard): auto-reconnect the events WebSocket with backoff (supersedes #47876, #47921, #24315) (#79524) 2026-08-05 14:27:04 -06:00
ChatSidebar.tsx fix(dashboard): auto-reconnect the events WebSocket with backoff (supersedes #47876, #47921, #24315) (#79524) 2026-08-05 14:27:04 -06:00
ConfirmDialog.tsx fix(web): remove marketing backdrop stack for lighter dashboard shell 2026-06-29 12:30:24 -07:00
DeleteConfirmDialog.tsx refactor(web): consume DS primitives, remove local component copies 2026-05-22 21:57:59 -04:00
HermesConsoleModal.tsx fix: wire HermesConsoleModal WS into stale-token reload guard 2026-08-04 15:26:37 +05:30
LanguageSwitcher.tsx fix(web): polish dashboard sidebar chrome and model card menus 2026-06-29 12:30:24 -07:00
Markdown.tsx harden: restrict markdown link schemes; parse untrusted XML with defusedxml 2026-05-25 23:30:43 -07:00
ModelInfoCard.tsx feat(dashboard): nous-blue theme, bulk sessions, schedule picker (#37383) 2026-06-02 12:37:40 -04:00
ModelPickerDialog.tsx fix(models): match bare Kimi Coding k3 when searching kimi 2026-07-26 21:22:40 -07:00
ModelReloadConfirm.tsx Fix model picker and autorefresh on change 2026-06-19 07:25:35 -07:00
OAuthLoginModal.tsx i18n(dashboard): translate OAuth copy-code strings in all locales 2026-07-09 12:21:16 +05:30
OAuthProvidersCard.tsx Merge remote-tracking branch 'origin/main' into refactor/use-ds-primitives 2026-05-28 14:20:49 -04:00
PlatformsCard.tsx feat(relay): terminal 4401 (opt-out) → clean "Relay disabled" state 2026-06-24 18:43:01 +10:00
ProfileScopeBanner.tsx feat(dashboard): unify multi-profile management — one machine dashboard, global profile switcher (#44007) 2026-06-11 03:29:33 -07:00
ProfileSwitcher.tsx fix(web): polish dashboard sidebar chrome and model card menus 2026-06-29 12:30:24 -07:00
ReasoningPicker.tsx Fix dashboard chat model profile scoping 2026-07-07 17:44:44 -07:00
ScheduleBuilder.tsx feat(dashboard): nous-blue theme, bulk sessions, schedule picker (#37383) 2026-06-02 12:37:40 -04:00
SidebarFooter.tsx fix(web): polish dashboard sidebar chrome and model card menus 2026-06-29 12:30:24 -07:00
SidebarStatusStrip.tsx fix(sec): update npm deps to resolve `npm audit` warnings 2026-07-31 13:42:03 -04:00
SkillEditorDialog.tsx feat(dashboard): SKILL.md editor on Skills page + attach-skill selector in cron modals (#44231) 2026-06-11 06:10:27 -07:00
SlashPopover.tsx refactor(web): dashboard typography & contrast pass 2026-05-22 19:50:32 -07:00
ThemeSwitcher.tsx fix(web): polish dashboard sidebar chrome and model card menus 2026-06-29 12:30:24 -07:00
ToolsetConfigDrawer.tsx fix(dashboard): persist Discord toolsets to Discord platform 2026-07-16 00:20:33 -07:00