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.
122 lines
3.7 KiB
TypeScript
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')
|
|
})
|
|
})
|