mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-20 15:33:54 +00:00
fix(desktop): sidebar status indicators lag for background sessions
The sidebar working dot didn't update for background sessions until the user opened them. Two coupled causes: 1. The gateway's session.info event payload omitted stored_session_id, so the desktop app had no way to map a background session's runtime id to its stored id. Without the stored id, setSessionWorking(null, ...) was a no-op — the $workingSessionIds atom never updated. 2. The running→busy transition in the session.info handler was gated on `apply` (active session only). The gate correctly scopes view-only side effects (setCurrentModel, setCurrentCwd, etc.) to the focused chat, but the per-session busy state drives the sidebar indicator and must reach every session. updateSessionState only mutates the per-runtime cache entry, and syncSessionStateToView already guards the view publish to the active session, so ungating is safe. Fix: add stored_session_id to _session_info() in tui_gateway/server.py, add the field to GatewayEventPayload, pass it to updateSessionState in the session.info handler, and ungate the running→busy transition.
This commit is contained in:
parent
311a5b0a55
commit
42bd4368ae
4 changed files with 33 additions and 13 deletions
|
|
@ -313,10 +313,11 @@ const DOT_VARIANTS: Record<SessionDotState, DotVariant> = {
|
|||
role: 'status'
|
||||
},
|
||||
// Pulsing gray — a terminal(background=true) process is alive while the LLM
|
||||
// is idle. Gray (not accent) reads as "something chugging along".
|
||||
// is idle. Gray (not accent) reads as "something chugging along". Brighter
|
||||
// than muted-foreground so it's visible against the sidebar surface.
|
||||
background: {
|
||||
ariaLabel: r => r.backgroundRunning,
|
||||
className: `${DOT_BASE} bg-muted-foreground/50 ${PING} before:bg-muted-foreground/50 before:opacity-50`,
|
||||
className: `${DOT_BASE} bg-muted-foreground/80 ${PING} before:bg-muted-foreground/80 before:opacity-60`,
|
||||
role: 'status',
|
||||
title: r => r.backgroundRunning
|
||||
},
|
||||
|
|
|
|||
|
|
@ -218,17 +218,30 @@ export function useGatewayEventHandler(deps: GatewayEventDeps) {
|
|||
}
|
||||
|
||||
if (sessionId && hasStatePatch) {
|
||||
updateSessionState(sessionId, state => ({
|
||||
...state,
|
||||
...statePatch,
|
||||
branch: statePatch.branch ?? state.branch,
|
||||
cwd: statePatch.cwd ?? state.cwd
|
||||
}))
|
||||
updateSessionState(
|
||||
sessionId,
|
||||
state => ({
|
||||
...state,
|
||||
...statePatch,
|
||||
branch: statePatch.branch ?? state.branch,
|
||||
cwd: statePatch.cwd ?? state.cwd
|
||||
}),
|
||||
payload?.stored_session_id || undefined
|
||||
)
|
||||
}
|
||||
|
||||
if (apply) {
|
||||
if (runningChanged && sessionId) {
|
||||
updateSessionState(sessionId, state => {
|
||||
// The running→busy transition must reach EVERY session, not just the
|
||||
// active one. The `apply` gate above correctly scopes view-only side
|
||||
// effects (setCurrentModel, setCurrentCwd, etc.) to the focused chat,
|
||||
// but the per-session busy state is what drives the sidebar working
|
||||
// indicator — a background session's turn start/finish must update
|
||||
// its dot without the user opening it. updateSessionState only
|
||||
// mutates the per-runtime cache entry, and syncSessionStateToView
|
||||
// guards the view publish to the active session, so this is safe.
|
||||
if (runningChanged && sessionId) {
|
||||
updateSessionState(
|
||||
sessionId,
|
||||
state => {
|
||||
const busy = Boolean(payload!.running)
|
||||
|
||||
if (state.busy === busy && (busy || !state.awaitingResponse)) {
|
||||
|
|
@ -255,8 +268,9 @@ export function useGatewayEventHandler(deps: GatewayEventDeps) {
|
|||
streamId: null,
|
||||
turnStartedAt: null
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
payload?.stored_session_id || undefined
|
||||
)
|
||||
}
|
||||
|
||||
if (payload?.usage && (!explicitSid || isActiveEvent)) {
|
||||
|
|
|
|||
|
|
@ -79,6 +79,10 @@ export type GatewayEventPayload = {
|
|||
// session.title (live auto-title push) — stored session id + generated title
|
||||
session_id?: string
|
||||
title?: string
|
||||
// session.info — the stored (durable) session id for this runtime session.
|
||||
// Lets the desktop app map runtime→stored for background sessions it hasn't
|
||||
// opened, so the sidebar working indicator updates without opening the chat.
|
||||
stored_session_id?: string
|
||||
// moa.reference / moa.aggregating (Mixture of Agents per-model relay)
|
||||
label?: string
|
||||
index?: number
|
||||
|
|
|
|||
|
|
@ -3656,6 +3656,7 @@ def _session_info(agent, session: dict | None = None) -> dict:
|
|||
"personality": str(personality or ""),
|
||||
"running": bool((session or {}).get("running")),
|
||||
"title": _session_live_title(session or {}, session_key) if session_key else "",
|
||||
"stored_session_id": session_key or "",
|
||||
"desktop_contract": DESKTOP_BACKEND_CONTRACT,
|
||||
"version": "",
|
||||
"release_date": "",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue