hermes-agent/web/src/lib
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
..
api.test.ts fix(dashboard): reload loopback tabs after stale session-token closes 2026-08-04 15:26:37 +05:30
api.ts fix(dashboard): reload loopback tabs after stale session-token closes 2026-08-04 15:26:37 +05:30
chat-activation.test.ts fix(dashboard): only open the chat PTY once the chat tab is active (#59551) 2026-07-18 00:50:13 -04:00
chat-activation.ts fix(dashboard): only open the chat PTY once the chat tab is active (#59551) 2026-07-18 00:50:13 -04:00
chat-sidebar-session-params.test.ts
…
chat-title.test.ts
…
chat-title.ts
…
chatImagePaste.test.ts
…
chatImagePaste.ts
…
clipboard.test.ts
…
clipboard.ts
…
cron-job.test.ts
…
cron-job.ts
…
dashboard-auth-reload.test.ts fix(dashboard): reload loopback tabs after stale session-token closes 2026-08-04 15:26:37 +05:30
dashboard-auth-reload.ts fix(dashboard): reload loopback tabs after stale session-token closes 2026-08-04 15:26:37 +05:30
dashboard-flags.ts
…
dashboard-modal-shell.test.ts fix(dashboard): opaque MoA presets modal (stop page bleed-through) (#67410) 2026-07-19 19:09:12 -04:00
dashboard-modal-shell.ts fix(dashboard): opaque MoA presets modal (stop page bleed-through) (#67410) 2026-07-19 19:09:12 -04:00
events-reconnect.test.ts fix(dashboard): auto-reconnect the events WebSocket with backoff (supersedes #47876, #47921, #24315) (#79524) 2026-08-05 14:27:04 -06:00
events-reconnect.ts fix(dashboard): auto-reconnect the events WebSocket with backoff (supersedes #47876, #47921, #24315) (#79524) 2026-08-05 14:27:04 -06:00
format.ts
…
fuzzy.ts
…
gatewayClient.test.ts fix(dashboard): reload loopback tabs after stale session-token closes 2026-08-04 15:26:37 +05:30
gatewayClient.ts fix(dashboard): reload loopback tabs after stale session-token closes 2026-08-04 15:26:37 +05:30
log-classify.test.ts fix(dashboard): QA pass — log colors, nameless channels, config bool, UX gaps 2026-07-26 15:17:30 -07:00
log-classify.ts fix(dashboard): QA pass — log colors, nameless channels, config bool, UX gaps 2026-07-26 15:17:30 -07:00
mcp-dashboard-oauth.test.ts fix(mcp): close hosted OAuth lifecycle gaps 2026-07-17 04:50:47 -07:00
mcp-dashboard-oauth.ts fix(mcp): close hosted OAuth lifecycle gaps 2026-07-17 04:50:47 -07:00
mcp-server-create.test.ts
…
mcp-server-create.ts
…
model-picker-filter.test.ts fix(dashboard): don't let a provider-name query hide the selected provider's models (#65374) (#65413) 2026-07-19 19:05:44 -04:00
model-picker-filter.ts fix(dashboard): don't let a provider-name query hide the selected provider's models (#65374) (#65413) 2026-07-19 19:05:44 -04:00
model-search-text.ts fix(models): match bare Kimi Coding k3 when searching kimi 2026-07-26 21:22:40 -07:00
nested.ts
…
pty-mobile-input.test.ts
…
pty-mobile-input.ts
…
pty-reconnect.test.ts
…
pty-reconnect.ts fix(web): make PTY resume sanitizer work against real PTY output 2026-07-29 12:21:48 -04:00
pty-resume-loading.test.ts fix(dashboard): gate resume hydration on sanitized PTY payload 2026-07-31 09:47:06 -04:00
pty-resume-loading.ts feat(dashboard): add resume loading overlay helpers 2026-07-31 09:47:06 -04:00
pty-resume-sanitizer.test.ts fix(web): make PTY resume sanitizer work against real PTY output 2026-07-29 12:21:48 -04:00
pty-resume-sanitizer.ts fix(web): make PTY resume sanitizer work against real PTY output 2026-07-29 12:21:48 -04:00
reasoning-effort.test.ts
…
reasoning-effort.ts
…
resolve-page-title.test.ts fix(dashboard): QA pass — log colors, nameless channels, config bool, UX gaps 2026-07-26 15:17:30 -07:00
resolve-page-title.ts fix(dashboard): QA pass — log colors, nameless channels, config bool, UX gaps 2026-07-26 15:17:30 -07:00
schedule.test.ts
…
schedule.ts
…
session-import.test.ts
…
session-import.ts
…
session-refresh.test.ts
…
session-refresh.ts
…
slashExec.ts
…
utils.ts
…