fix(desktop): make the running-turn timer per-session#41182
Merged
Conversation
The desktop statusbar turn timer read a single process-global $turnStartedAt, set/cleared only for the active session. With multiple same-profile sessions running at once, switching to session B reset the one shared clock, so session A's still-running turn "restarted from zero" the moment you left it — exactly the behaviour @Da7_Tech reported after the profile-scoped session work. Move turnStartedAt onto ClientSessionState so each session owns its own turn clock. The global atom now just mirrors whichever session is focused, written on view-sync (the flush that already stages the active session's state). A backgrounded turn keeps counting in its own cache entry, and focusing it restores its real elapsed time instead of zeroing it. Set/clear sites: message.start (seed), message.complete + error + interrupted bail (clear), and the session.info running-state path (seed if missing / clear on stop) so a turn that goes busy via session.info — e.g. resuming a session that's already running — also gets a clock. Note: the agent loop itself never froze — every same-profile session runs in its own backend thread and background deltas are buffered per-session. This fixes the timer-reset symptom; the "no live progress until you return" is inherent to a single-view transcript and is out of scope here.
Contributor
🔎 Lint report:
|
agogo233
added a commit
to agogo233/hermes-agent
that referenced
this pull request
Jun 8, 2026
* upstream/main: (430 commits) fix(yuanbao): bound ws.close() so an idle server can't stall shutdown ~5s (NousResearch#40607) docs: add Urdu translation of README (NousResearch#40578) fix(hindsight): send only new-turn delta on append retains instead of whole session (NousResearch#40605) feat(gateway): render terminal tool calls as native bash code blocks on markdown platforms (NousResearch#41215) feat(desktop): stop the chat viewport from following streaming output (NousResearch#41414) chore(release): map AlchemistChaos co-author email for NousResearch#40135 salvage fix(desktop): recover chat after sleep/wake by revalidating a stale remote backend fix(web): make _has_env config-aware so SEARXNG_URL auto-detect honors Hermes config fix(web): honor Hermes config-aware SEARXNG_URL lookup install.sh: hint at root-owned npm cache when desktop npm install fails (NousResearch#39688) fix(tools): percent-encode non-ascii URL components fix(skills): browse shows full catalog, not first 5000 (NousResearch#41413) feat(desktop+gateway): remote media relay — attach images/PDFs and display gateway images over the network feat(desktop): full tool-backend config (pickers + per-backend settings) in Settings (NousResearch#41232) hardening(api-server): scan cron prompts on REST create/update for parity with the agent tool fix: skip MCP preflight content-type probe on reconnect when already ready (NousResearch#40604) fix(kanban): sweep deferred scratch parent on non-scratch child completion + tests fix: defer scratch workspace cleanup when task has active children (NousResearch#33774) feat(onboarding): opt-in structured profile-build path on first contact (NousResearch#41114) feat(compression): temporal anchoring in compaction summaries (NousResearch#41102) test(discord): align clarify/model-picker tests with fail-closed component auth (NousResearch#41338) chore(release): map Dusk1e and LaPhilosophie for approval fail-closed salvage (NousResearch#33844, NousResearch#33866, NousResearch#30964) fix(discord): fail closed for component button auth when no allowlist set fix(feishu): fail closed for update prompt card actions fix(slack): re-check gateway auth on approval and slash-confirm buttons fix: guard int(os.getenv()) casts against malformed env vars (NousResearch#40598) fix: respect Honcho env var fallback in doctor and honcho status chore(release): add synapsesx to AUTHOR_MAP for NousResearch#40495 salvage fix(research): keep tool_call/tool_response pairs intact when compressing trajectories fix(simplex): accept display name in SIMPLEX_ALLOWED_USERS fix(desktop): make the running-turn timer per-session (NousResearch#41182) test(approval): regression for shell-escape denylist bypass (NousResearch#36846, NousResearch#36847) fix(security): strip shell escapes in denylist normalizer; fail-closed on missing approval module fix(stream+output-cap): guard empty streams and parse OpenRouter output-cap errors (NousResearch#40589) fix(desktop): bootstrap falls back to installed agent install.sh on GitHub 404 feat(dashboard): change UI font from the theme picker, independent of theme (NousResearch#41145) fix(cli): return bool (not None) when a destructive-slash confirmation is cancelled (NousResearch#40583) fix(desktop): preserve configured base_url on same-provider model switch (NousResearch#41121) fix(desktop): stop bare-URL autolinker swallowing trailing emphasis asterisks (NousResearch#41093) fix(cron): bound the desktop run-history query to one job (NousResearch#41088) fix(desktop): scope in-session /model switch per-session, stop process-env leak (NousResearch#41120) chore: map bmoore210 author email for PR NousResearch#40550 salvage fix(desktop): scope session list to active profile + longer timeout fix: harden gateway startup and turn persistence fix(computer_use): honor custom vision routing fix(aux): honor model.default_headers on auxiliary client too (NousResearch#40033) fix(agent): honor model.default_headers for custom OpenAI-compatible providers (NousResearch#40033) docs(i18n): port deep-audit corrections to zh-Hans mirror (NousResearch#41104) fix(compression): don't overwrite the -1 post-compression sentinel in preflight seed (NousResearch#36718) chore(release): map singhsanidhya741@gmail.com to sanidhyasin (NousResearch#41094) ...
changman
pushed a commit
to changman/hermes-agent
that referenced
this pull request
Jun 10, 2026
…1182) The desktop statusbar turn timer read a single process-global $turnStartedAt, set/cleared only for the active session. With multiple same-profile sessions running at once, switching to session B reset the one shared clock, so session A's still-running turn "restarted from zero" the moment you left it — exactly the behaviour @Da7_Tech reported after the profile-scoped session work. Move turnStartedAt onto ClientSessionState so each session owns its own turn clock. The global atom now just mirrors whichever session is focused, written on view-sync (the flush that already stages the active session's state). A backgrounded turn keeps counting in its own cache entry, and focusing it restores its real elapsed time instead of zeroing it. Set/clear sites: message.start (seed), message.complete + error + interrupted bail (clear), and the session.info running-state path (seed if missing / clear on stop) so a turn that goes busy via session.info — e.g. resuming a session that's already running — also gets a clock. Note: the agent loop itself never froze — every same-profile session runs in its own backend thread and background deltas are buffered per-session. This fixes the timer-reset symptom; the "no live progress until you return" is inherent to a single-view transcript and is out of scope here.
OmarB97
added a commit
to OmarB97/hermes-agent
that referenced
this pull request
Jun 10, 2026
…te renderer tests in CI (#160) The running-turn timer's per-session clock (upstream NousResearch#41182) was clipped by the upstream-sync's internal merge (e866446): ClientSessionState.turnStartedAt and its tests survived, but all six seed/clear sites in use-message-stream.ts and the view-sync mirror in use-session-state-cache.ts vanished — so the statusbar timer never starts, and a backgrounded turn's elapsed time zeroes on focus (the exact bug NousResearch#41182 fixed). Restored all seven sites to upstream's shape. Renderer suite: 602/602 (first fully-green run; also passes under TZ=UTC). Because nothing in CI ran this suite, the clobber merged silently — same class as the sleep/wake-recovery loss (#148). Add renderer-tests.yml gating `vitest run --environment jsdom src` on every PR so the next one can't. Co-authored-by: Omar Baradei <omar@kostudios.io> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The desktop running-turn timer is now per-session: leaving a busy session no longer "restarts its timer from zero."
Root cause: the statusbar turn timer read a single process-global
$turnStartedAt, set/cleared only for the active session. With multiple same-profile sessions running at once (the multi-model setup @Da7_Tech hit after profile-scoped sessions), switching to session B reset the one shared clock, so session A's still-running turn appeared to restart.Changes
app/types.ts: addturnStartedAt: number | nulltoClientSessionState.lib/chat-runtime.ts: initialize it tonullincreateClientSessionState.use-message-stream.ts: seed/clearturnStartedAton the per-session state atmessage.start,message.complete,error, the interrupted bail, and thesession.inforunning-state path (seed-if-missing on busy, clear on stop). The global$turnStartedAtmirror stays gated on the active session.use-session-state-cache.ts: on view-sync flush, mirror the focused session'sturnStartedAtinto the global atom the statusbar reads — so focusing a backgrounded turn restores its real elapsed time instead of zeroing it.use-session-state-cache.test.tsx— background turn keeps its clock and never touches the global atom; focused turn mirrors into the global; clearing the focused turn clears the global.Not in scope
The agent loop never actually froze — every same-profile session runs in its own backend thread and background deltas are buffered per-session. This PR fixes the timer-reset symptom. The "no live thinking until you return, then it dumps at once" behaviour is inherent to a single-view transcript (only the focused session renders) and is left for a follow-up if we want live background-session indicators.
Validation
tsc -bvitestsession hooks + store + chat-runtimeReported via Twitter (@Da7_Tech). Companion to #41120 (per-session
/modelscope) — that fixed the model cross-contamination; this fixes the timer half of the same report.Infographic