mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-20 15:33:54 +00:00
Opt-in $petRoam (localStorage), $petMotion (run/jump pose) and $petRoamDir (-1/0/1) feed the shared $petState only while the agent is at rest ($petAtRest), so a wander never overrides real activity.
91 lines
3 KiB
TypeScript
91 lines
3 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import {
|
|
$petActivity,
|
|
$petAtRest,
|
|
$petMotion,
|
|
$petState,
|
|
derivePetState,
|
|
flashPetActivity,
|
|
setPetActivity
|
|
} from './pet'
|
|
|
|
describe('derivePetState', () => {
|
|
it('rests at idle by default and uses waiting when awaiting input', () => {
|
|
expect(derivePetState({})).toBe('idle')
|
|
expect(derivePetState({ awaitingInput: true })).toBe('waiting')
|
|
})
|
|
|
|
it('runs when busy or a tool is executing', () => {
|
|
expect(derivePetState({ busy: true })).toBe('run')
|
|
expect(derivePetState({ toolRunning: true })).toBe('run')
|
|
})
|
|
|
|
it('reviews while reasoning (below tool, above bare busy)', () => {
|
|
expect(derivePetState({ reasoning: true })).toBe('review')
|
|
expect(derivePetState({ reasoning: true, busy: true })).toBe('review')
|
|
expect(derivePetState({ reasoning: true, toolRunning: true })).toBe('run')
|
|
})
|
|
|
|
it('waits (blocked on the user) above the in-flight signals', () => {
|
|
expect(derivePetState({ awaitingInput: true, toolRunning: true, busy: true })).toBe('waiting')
|
|
// but a finish beat still wins over waiting
|
|
expect(derivePetState({ justCompleted: true, awaitingInput: true })).toBe('wave')
|
|
})
|
|
|
|
it('honors the full priority chain: error > celebrate > complete > tool', () => {
|
|
expect(derivePetState({ error: true, celebrate: true, busy: true })).toBe('failed')
|
|
expect(derivePetState({ celebrate: true, justCompleted: true, toolRunning: true })).toBe('jump')
|
|
expect(derivePetState({ justCompleted: true, toolRunning: true })).toBe('wave')
|
|
})
|
|
})
|
|
|
|
describe('roam motion', () => {
|
|
it('only reports at-rest when the agent-driven state is plain idle', () => {
|
|
$petActivity.set({})
|
|
expect($petAtRest.get()).toBe(true)
|
|
|
|
$petActivity.set({ busy: true })
|
|
expect($petAtRest.get()).toBe(false)
|
|
|
|
$petActivity.set({})
|
|
expect($petAtRest.get()).toBe(true)
|
|
})
|
|
|
|
it('shows the roam pose while wandering, but never overrides real activity', () => {
|
|
$petActivity.set({})
|
|
$petMotion.set('run')
|
|
expect($petState.get()).toBe('run')
|
|
|
|
// Hops surface the jump pose.
|
|
$petMotion.set('jump')
|
|
expect($petState.get()).toBe('jump')
|
|
|
|
// Activity wins over a wander in progress.
|
|
$petActivity.set({ reasoning: true, busy: true })
|
|
expect($petState.get()).toBe('review')
|
|
|
|
// Back at rest, the wander resumes its pose; clearing it returns to idle.
|
|
$petActivity.set({})
|
|
expect($petState.get()).toBe('jump')
|
|
$petMotion.set(null)
|
|
expect($petState.get()).toBe('idle')
|
|
|
|
$petActivity.set({})
|
|
})
|
|
})
|
|
|
|
describe('flashPetActivity', () => {
|
|
it('clears stale sibling beats so a completion never inherits a prior error', () => {
|
|
// A turn errors (sad), then the next turn finishes cleanly. The celebrate
|
|
// beat must win — error is highest priority, so a merge-only flash would
|
|
// keep the pet on the failed pose.
|
|
setPetActivity({ error: true })
|
|
flashPetActivity({ celebrate: true })
|
|
|
|
expect($petActivity.get().error).toBe(false)
|
|
expect($petState.get()).toBe('jump')
|
|
|
|
setPetActivity({})
|
|
})
|
|
})
|