mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-18 09:51:59 +00:00
clarify.request is a one-shot blocking event: the gateway turn blocks on clarify.respond. The desktop handler dropped it for any non-focused session (`if (!isActiveEvent) return`) and stored at most one request in a single global atom, so a background session that asked a clarifying question hung forever and re-focusing it could never recover (the event was already gone). - store/clarify.ts: key pending requests by runtime session id; expose the active session's request via a focus-scoped computed view (ClarifyTool is unchanged). clearClarifyRequest takes an optional session id for targeted clears, with a request-id fallback. - use-message-stream.ts: park every session's clarify (drop the isActiveEvent early return); toast when one lands for a background session since the row otherwise just keeps spinning like normal work. - clarify-tool.tsx: clear by session id so answering one chat can't wipe another's pending request. - store/clarify.test.ts: concurrent independence, focus-scoped view, targeted/stale/fallback clears.
69 lines
2.2 KiB
TypeScript
69 lines
2.2 KiB
TypeScript
import { atom, computed } from 'nanostores'
|
|
|
|
import { $activeSessionId } from './session'
|
|
|
|
export interface ClarifyRequest {
|
|
requestId: string
|
|
question: string
|
|
choices: string[] | null
|
|
sessionId: string | null
|
|
}
|
|
|
|
// Pending clarify requests keyed by the runtime session id that raised them.
|
|
// Storing per-session (instead of one shared slot) lets a *background* session
|
|
// park its clarify request while the user is looking at a different chat, then
|
|
// resolve it once they switch over — without a second concurrent clarify
|
|
// clobbering the first. A request with no session id lands under the empty key.
|
|
const keyFor = (sessionId: string | null | undefined): string => sessionId ?? ''
|
|
|
|
export const $clarifyRequests = atom<Record<string, ClarifyRequest>>({})
|
|
|
|
// The clarify request for the currently-viewed session. The inline ClarifyTool
|
|
// only ever mounts inside the active session's transcript, so it reads this
|
|
// focus-scoped view rather than reaching into the whole map.
|
|
export const $clarifyRequest = computed(
|
|
[$clarifyRequests, $activeSessionId],
|
|
(requests, activeId) => requests[keyFor(activeId)] ?? null
|
|
)
|
|
|
|
export function setClarifyRequest(request: ClarifyRequest): void {
|
|
$clarifyRequests.set({ ...$clarifyRequests.get(), [keyFor(request.sessionId)]: request })
|
|
}
|
|
|
|
export function clearClarifyRequest(requestId?: string, sessionId?: string | null): void {
|
|
const requests = $clarifyRequests.get()
|
|
|
|
// Targeted clear when the caller knows the session (the common path from the
|
|
// inline ClarifyTool answering its own request).
|
|
if (sessionId !== undefined) {
|
|
const key = keyFor(sessionId)
|
|
const current = requests[key]
|
|
|
|
if (!current || (requestId && current.requestId !== requestId)) {
|
|
return
|
|
}
|
|
|
|
const next = { ...requests }
|
|
delete next[key]
|
|
$clarifyRequests.set(next)
|
|
|
|
return
|
|
}
|
|
|
|
// Fallback with no session hint: drop every entry matching the request id
|
|
// (or clear all when none is given).
|
|
const next: Record<string, ClarifyRequest> = {}
|
|
let changed = false
|
|
|
|
for (const [key, value] of Object.entries(requests)) {
|
|
if (requestId && value.requestId !== requestId) {
|
|
next[key] = value
|
|
} else {
|
|
changed = true
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
$clarifyRequests.set(next)
|
|
}
|
|
}
|