hermes-agent/ui-tui/src/__tests__/useSessionLifecycle.test.ts
Nick 0a83247e9f feat: add TUI session orchestrator
Add a first-class active-session orchestrator for the Ink TUI:

- list, activate, close, and launch live process-local TUI sessions
- hydrate committed and in-flight output when switching sessions
- dispatch a new prompt session from the +new row with session-scoped model picks
- expose a clickable live-session count in the status chrome
- preserve stable row order while initially focusing the current session
- support mouse hit-testing for floating orchestrator overlays
- add backend and frontend regression coverage for the lifecycle and UI helpers
2026-05-26 20:51:59 -07:00

60 lines
2 KiB
TypeScript

import { mkdtempSync, readFileSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { turnController } from '../app/turnController.js'
import { getTurnState, resetTurnState } from '../app/turnStore.js'
import { patchUiState, resetUiState } from '../app/uiStore.js'
import { hydrateLiveSessionInflight, liveSessionInflightMessages, writeActiveSessionFile } from '../app/useSessionLifecycle.js'
describe('writeActiveSessionFile', () => {
let dir = ''
afterEach(() => {
if (dir) {
rmSync(dir, { force: true, recursive: true })
dir = ''
}
})
it('writes the actual resumed session id for the shell exit summary', () => {
dir = mkdtempSync(join(tmpdir(), 'hermes-tui-active-'))
const path = join(dir, 'active.json')
writeActiveSessionFile('actual_session', path)
expect(JSON.parse(readFileSync(path, 'utf8'))).toEqual({ session_id: 'actual_session' })
})
})
describe('live session activation in-flight state', () => {
beforeEach(() => {
resetUiState()
resetTurnState()
turnController.fullReset()
patchUiState({ streaming: true })
})
it('keeps the in-flight user prompt in history and hydrates partial assistant text', () => {
const inflight = { assistant: 'partial answer', streaming: true, user: 'write a long answer' }
expect(liveSessionInflightMessages(inflight)).toEqual([{ role: 'user', text: 'write a long answer' }])
hydrateLiveSessionInflight(inflight)
expect(turnController.bufRef).toBe('partial answer')
expect(getTurnState().streaming).toBe('partial answer')
})
it('ignores empty in-flight payloads', () => {
expect(liveSessionInflightMessages({ assistant: '', streaming: false, user: ' ' })).toEqual([])
hydrateLiveSessionInflight({ assistant: '', streaming: false, user: '' })
expect(turnController.bufRef).toBe('')
expect(getTurnState().streaming).toBe('')
})
})