Files
hermes-agent/apps/desktop/src/store/prompts.test.ts
T
Brooklyn Nicholson 4891f9ae78 feat(desktop): concurrent multi-profile gateway sockets
Keep one persistent socket per profile with live work instead of closing
the single socket on every profile swap, so background sessions across
profiles keep streaming at once. A gateway registry owns the primary
(window) socket plus lazy secondaries (own backoff/reconnect); all feed
the same session-keyed event handler. Secondaries are pruned to profiles
with a working/needs-input session, the keepalive pings every open
backend, and LRU eviction spares freshly-touched backends so the soft cap
can't abort a running agent. Approval/sudo/secret prompts are parked
per-session (surfaced via the needs-input badge) so a background turn can
block without hijacking the foreground. Single-profile users only ever
have the primary, so their path is unchanged.
2026-06-04 20:44:19 -05:00

122 lines
3.7 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import {
$approvalRequest,
$secretRequest,
$sudoRequest,
clearAllPrompts,
clearApprovalRequest,
clearSecretRequest,
clearSudoRequest,
setApprovalRequest,
setSecretRequest,
setSudoRequest
} from './prompts'
import { $activeSessionId } from './session'
// Prompts are parked per-session; the exported $*Request views are scoped to the
// active session, so each test focuses the session it's asserting on.
beforeEach(() => {
$activeSessionId.set('s1')
})
afterEach(() => {
clearAllPrompts()
$activeSessionId.set(null)
})
describe('approval prompt store', () => {
it('holds the active session-keyed approval request', () => {
setApprovalRequest({ command: 'rm -rf /tmp/x', description: 'recursive delete', sessionId: 's1' })
expect($approvalRequest.get()).toEqual({
command: 'rm -rf /tmp/x',
description: 'recursive delete',
sessionId: 's1'
})
})
it('parks a background session prompt out of the active view', () => {
setApprovalRequest({ command: 'x', description: 'd', sessionId: 's2' })
// Not visible while s1 is focused …
expect($approvalRequest.get()).toBeNull()
// … but surfaces once the user switches to the session that raised it.
$activeSessionId.set('s2')
expect($approvalRequest.get()?.sessionId).toBe('s2')
})
it('clears the active session prompt', () => {
setApprovalRequest({ command: 'x', description: 'd', sessionId: 's1' })
clearApprovalRequest('s1')
expect($approvalRequest.get()).toBeNull()
})
})
describe('sudo prompt store', () => {
it('clears only when the request id matches the in-flight prompt', () => {
setSudoRequest({ requestId: 'abc', sessionId: 's1' })
// A stale clear for a different request must NOT drop the live prompt —
// otherwise a late response to a prior sudo ask would dismiss the current
// one and leave the agent blocked.
clearSudoRequest('s1', 'stale')
expect($sudoRequest.get()).toEqual({ requestId: 'abc', sessionId: 's1' })
clearSudoRequest('s1', 'abc')
expect($sudoRequest.get()).toBeNull()
})
it('clears unconditionally when no request id is given', () => {
setSudoRequest({ requestId: 'abc', sessionId: 's1' })
clearSudoRequest('s1')
expect($sudoRequest.get()).toBeNull()
})
})
describe('secret prompt store', () => {
it('carries env var and prompt, and clears on id match', () => {
setSecretRequest({ requestId: 'r1', envVar: 'OPENAI_API_KEY', prompt: 'Paste your key', sessionId: 's1' })
expect($secretRequest.get()).toEqual({
requestId: 'r1',
envVar: 'OPENAI_API_KEY',
prompt: 'Paste your key',
sessionId: 's1'
})
clearSecretRequest('s1', 'mismatch')
expect($secretRequest.get()).not.toBeNull()
clearSecretRequest('s1', 'r1')
expect($secretRequest.get()).toBeNull()
})
})
describe('clearAllPrompts', () => {
it('drops every kind for one session at once (turn end / interrupt)', () => {
setApprovalRequest({ command: 'x', description: 'd', sessionId: 's1' })
setSudoRequest({ requestId: 'abc', sessionId: 's1' })
setSecretRequest({ requestId: 'r1', envVar: 'E', prompt: 'p', sessionId: 's1' })
clearAllPrompts('s1')
expect($approvalRequest.get()).toBeNull()
expect($sudoRequest.get()).toBeNull()
expect($secretRequest.get()).toBeNull()
})
it('leaves other sessions parked prompts intact', () => {
setApprovalRequest({ command: 'x', description: 'd', sessionId: 's1' })
setApprovalRequest({ command: 'y', description: 'e', sessionId: 's2' })
clearAllPrompts('s1')
$activeSessionId.set('s2')
expect($approvalRequest.get()?.command).toBe('y')
})
})