Skip to content

fix(desktop): keep quiet sessions visibly running - #65870

Merged
austinpickett merged 3 commits into
NousResearch:mainfrom
UnathiCodex:fix/desktop-sidebar-running-watchdog
Jul 20, 2026
Merged

fix(desktop): keep quiet sessions visibly running#65870
austinpickett merged 3 commits into
NousResearch:mainfrom
UnathiCodex:fix/desktop-sidebar-running-watchdog

Conversation

@UnathiCodex

@UnathiCodex UnathiCodex commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes three related Hermes Desktop sidebar-state gaps that can make a live session look finished:

  1. After eight minutes without stream activity, the watchdog cleared the client-side working state even though the backend turn was still running.
  2. After a renderer/backend disconnect and reconnect, Desktop lost its in-memory working/attention lists. The backend turn remained alive, but its sidebar row stayed dark until the user opened that chat.
  3. Auto-compression can rotate a running conversation onto a new stored session id. With the previous 15-second recovery cadence, the new sidebar row could look idle during that healthy transition.

The watchdog now treats silence as presentation state only, never as completion. A quiet but authoritative running turn keeps the unmistakable running arc while using a softer dot. Desktop also reads the gateway's non-mutating session.active_list snapshot immediately on connection and every 1.5 seconds while visible (matching the TUI cadence), restoring running or waiting rows without resuming, focusing, or otherwise changing a chat.

Related Issue

No issue filed; all three paths were reproduced in the packaged Windows desktop app.

Type of Change

  • ?? Bug fix (non-breaking change that fixes an issue)
  • ? Tests (adding or improving test coverage)

Changes Made

  • Rebased onto current main and re-homed the implementation on the post-refactor(desktop): derive working/attention session sets from $sessionStates #66454 session-states.ts / use-session-state-cache.ts architecture.

  • Added a separate stalled-session presentation state without clearing authoritative working state.

  • Removed the watchdog callback that incorrectly cleared busy/awaiting state after silence.

  • Kept the row's running arc visible for quiet/stalled turns; only a blocking prompt replaces it with the needs-input treatment.

  • Rehydrates live working and waiting sessions from session.active_list after reconnect.

  • Rechecks the lightweight in-memory live-session snapshot every 1.5 seconds while Desktop is visible, matching Hermes TUI.

  • Preserves quiet styling for rehydrated turns whose last activity exceeds the watchdog window.

  • Added regressions for silent turns, reconnect recovery, waiting-for-input recovery, malformed/idle snapshots, and the quiet-running row appearance.

How to Test

  1. From apps/desktop, run:

    npx vitest run --project ui src/app/chat/sidebar/session-row-state.test.ts src/app/contrib/hooks/use-background-sync.test.ts src/store/session-watchdog.test.ts src/store/gateway-switch.test.ts src/app/session/hooks/use-session-state-cache.test.tsx

  2. Start a desktop turn and leave it without stream events for the watchdog window.

  3. Confirm the dot softens but the running arc and composer running state remain visible.

  4. Disconnect/reconnect Desktop while another chat is still running.

  5. Confirm its sidebar row restores automatically without opening that chat.

  6. Let a long conversation auto-compress and confirm its rotated sidebar row recovers within about 1.5 seconds.

  7. Confirm a real terminal event clears the running indicator.

Checklist

Code

  • I've read the contributing guide
  • My commits follow Conventional Commits
  • I searched existing PRs for the same desktop/sidebar watchdog symptom
  • This PR contains only changes related to this fix
  • I've added regression tests for the reproduced paths
  • Tested on Windows 11
  • Focused regression command passes (34/34)
  • ESLint passes for all changed files
  • GitHub clean-install Desktop typecheck, full test suite, and build pass
  • Production desktop build and packaged-app smoke test pass

Documentation & Housekeeping

  • Documentation changes are N/A; behavior is covered by code comments and regressions
  • Config/example changes are N/A
  • Architecture/workflow documentation changes are N/A
  • Cross-platform impact considered; changes are renderer state plus an existing gateway RPC
  • Tool schema changes are N/A

Screenshots / Logs

Validated against the packaged Windows desktop application. After reinstall/restart, the live gateway still reported the ongoing Temporary Folder Cleanup session as working, and its sidebar liveness now rehydrates without requiring a click.

@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/desktop Electron desktop app (apps/desktop/*) platform/windows Native Windows-specific behavior or breakage sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 16, 2026
@tonydwb

tonydwb commented Jul 16, 2026

Copy link
Copy Markdown

Code Review Summary

Verdict: Comment

Desktop sidebar running watchdog fix. Keeps quiet sessions visibly running. +78/-82.

--
Reviewed by Hermes Agent

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM! Fix keeps quiet sessions visibly running. Clean fix for the watchdog timer handling.

Reviewed by Hermes Agent

@UnathiCodex
UnathiCodex force-pushed the fix/desktop-sidebar-running-watchdog branch from c73c192 to b0e2998 Compare July 17, 2026 07:32

@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 identifying the quiet-turn and reconnect behavior; the current-main watchdog still clears an otherwise live session after eight minutes of silence.

Problems

  • This branch predates #66454 (cf52edbb5), which moved working/attention state into computed projections of $sessionStates in apps/desktop/src/store/session-states.ts:193-208. The PR imports mutable session.ts APIs that current main no longer provides.
  • On current main, the behavior to replace spans apps/desktop/src/store/session-states.ts:58-70 and apps/desktop/src/app/session/hooks/use-session-state-cache.ts:290-307: the timeout invokes a callback that publishes busy: false and clears awaiting/input state.

Suggested changes

  • Rework the quiet presentation and session.active_list reconciliation through the current cache plus publishSessionState, preserving busy until an authoritative terminal event.
  • Port the regressions to apps/desktop/src/store/session-watchdog.test.ts, where the current watchdog contract is tested at lines 169-195.

This is an automated hermes-sweeper review.

@@ -1,7 +1,16 @@
import { useEffect } from 'react'

import { refreshActiveProfile } from '@/store/profile'
import { $activeSessionId, $currentCwd, setCurrentCwd } from '@/store/session'
import {

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.

Current main no longer exposes these mutable session-status APIs: #66454 moved working/attention to computed projections in apps/desktop/src/store/session-states.ts:193-208. Please adapt recovery through the current cache/state publisher rather than restoring independent atoms.

@teknium1 teknium1 added the sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform label Jul 18, 2026
@UnathiCodex
UnathiCodex force-pushed the fix/desktop-sidebar-running-watchdog branch from 4680097 to cd327cb Compare July 18, 2026 22:39
@UnathiCodex

Copy link
Copy Markdown
Contributor Author

Refreshed onto current main after the #66454 session-state refactor. The old stale implementation has been replaced with the already-adapted three-commit stack using session-states.ts and use-session-state-cache.ts: 11 Desktop-only files, no backend changes. Validation: the documented five-file UI command passes 34/34; Prettier, ESLint, and git diff --check pass.

@UnathiCodex
UnathiCodex force-pushed the fix/desktop-sidebar-running-watchdog branch from cd327cb to 6b02541 Compare July 18, 2026 22:44
@UnathiCodex

Copy link
Copy Markdown
Contributor Author

CI correctly caught one cumulative-branch dependency on the still-unmerged #67118 helper setActiveSessionStoredIdRotation. The PR now uses current main's setActiveSessionStoredId API, so #65870 is independent and #67118 can update that call when it merges. Revalidated: 34/34 focused UI tests, Prettier, ESLint, and diff-check pass. New head: 6b02541a4.

@UnathiCodex
UnathiCodex force-pushed the fix/desktop-sidebar-running-watchdog branch from 6b02541 to 5a97d94 Compare July 18, 2026 22:50
@UnathiCodex

Copy link
Copy Markdown
Contributor Author

Follow-up: the reconnect commit itself also carried the historical helper diff, so the first autosquash corrected an intermediate commit but not the final snapshot. That later commit is now corrected too. Verified final head 5a97d9437 contains only setActiveSessionStoredId (no setActiveSessionStoredIdRotation reference), and the full focused command still passes 34/34.

@UnathiCodex

Copy link
Copy Markdown
Contributor Author

Final CI confirmation for head 5a97d9437: clean-install Desktop typecheck, full Desktop test suite, build, and the aggregate required check all pass. The only checks still running at this note are the two non-blocking Docker image builds; there are no failures.

@teknium1 teknium1 added the area/sessions Session lifecycle, resume, persistence, history label Jul 19, 2026
@UnathiCodex
UnathiCodex force-pushed the fix/desktop-sidebar-running-watchdog branch from 5a97d94 to aee688d Compare July 20, 2026 09:42

@austinpickett austinpickett left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review — approve

Verified against head aee688d9203b32bd731cb3661270f20247b4910c in a fresh worktree on top of a full local checkout.

Prior review reconciliation

The hermes-sweeper review (2026-07-18, keep_open salvageability=medium) flagged that the branch predated #66454 and imported mutable session-status APIs that no longer exist. Both concerns are resolved at the current head:

  • The old setWatchdogClearFn wiring in use-session-state-cache.ts and the mutable-clear path in session-states.ts are removed; the implementation is re-homed on the post-#66454 architecture ($sessionStates + publishSessionState, with $workingSessionIds / $attentionSessionIds staying computed projections).
  • The regressions were ported to src/store/session-watchdog.test.ts as requested, and the new stalled contract is covered there (silent turn stays in $workingSessionIds, activity re-arms, terminal transition clears both, gateway wipe clears both).

Correctness spot-checks

  • Design is right per the app's authority model: the 8-minute watchdog becomes a presentation hint ($stalledSessionIds) and never mutates backend-derived busy. Silence ≠ completion — long tool calls legitimately go quiet. Only an authoritative terminal transition (or the authoritative session.active_list snapshot reporting non-working) clears the running state, which also means a genuinely finished-while-disconnected session gets cleared by the 1.5 s poll rather than by a heuristic timer.
  • Gateway contract verified against current main's tui_gateway/server.py: session.active_list is non-mutating (snapshots _sessions, filters _finalized), and _session_live_item really does emit id, session_key, last_active (epoch seconds — the * 1000 conversion in rehydrateLiveSessionStatuses is correct), and status ∈ {idle, starting, waiting, working} from _session_live_status, which matches the renderer's union exactly. waitingbusy && needsInput mirrors how live stream events treat a blocking prompt.
  • Poll hygiene: visiblePoll + inFlight guard + publish-only-on-diff means the 1.5 s cadence doesn't re-arm the watchdog or churn atoms on no-ops; the catch-and-ignore keeps older gateways without session.active_list working unchanged. activeGatewayProfile in the effect deps correctly re-seeds after a profile swap.
  • Stalled bookkeeping is keyed by stored id while timers stay keyed by runtime id, and every clear path is covered: busy publish, terminal transition (both next and previous stored ids — handles compression rotation), dropSessionState, clearAllSessionStates, and the gateway-switch wipe.
  • sessionDotState / sessionShowsRunningArc extraction is a clean DI-testable move consistent with the repo's "never read source in tests" rule.

Local verification (worktree at PR head, Windows)

  • npx vitest run --project ui on the five touched suites: 36/36 pass
  • npm run typecheck (both tsc -p . and tsconfig.electron.json): clean
  • npx eslint across all 11 changed files: clean
  • Upstream CI on this head: all required checks green (All required checks pass ✅), mergeable: MERGEABLE, merge state CLEAN — no conflicts, no salvage needed.

One non-blocking note (fine as follow-up)

rehydrateLiveSessionStatuses restores/reconciles rows present in the snapshot but doesn't clear a renderer-side working row whose session was finished and reaped (torn down) while Desktop was disconnected — such a session is absent from session.active_list, and with the watchdog no longer force-clearing, that row keeps its (stalled) arc until the chat is opened or the app restarts. Narrow edge (requires the reap to happen inside the disconnect window); if it shows up in practice, the rehydrator could additionally clear busy sessions absent from a successful snapshot.

Good to merge.

@austinpickett
austinpickett merged commit c59ca46 into NousResearch:main Jul 20, 2026
25 checks passed
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
* fix(desktop): keep quiet sessions visibly running

* fix(desktop): restore running status after reconnect

* fix(desktop): keep live session status unmistakable
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/sessions Session lifecycle, resume, persistence, history comp/desktop Electron desktop app (apps/desktop/*) P3 Low — cosmetic, nice to have platform/windows Native Windows-specific behavior or breakage sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform 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