Files
EveFlow/tests/hermesEvents.test.ts
T
Claude 127a2083a1 feat!: rebuild EveFlow as a JARVIS-style voice HUD for Hermes Agent (v2.0.0)
Complete rewrite of the application:

- Toolchain: Electron 44, Vite 8, React 19, TypeScript 5.9, Zustand, Vitest;
  main process rewritten in TypeScript (electron/), typed IPC contract (shared/),
  sandboxed renderer with webSecurity on and an HTTP/SSE proxy in the main process.
- Voice: AudioWorklet microphone capture at 16 kHz with adaptive energy VAD and
  auto-stop, WAV encoder, OpenAI-compatible STT, TTS queue with sentence-level
  streaming, prefetch and Web Audio playback feeding an analyser; hands-free mode,
  global push-to-talk hotkey, system-voice fallback.
- Hermes: full API client (capabilities, health, models, skills, toolsets,
  sessions, jobs) with automatic transport selection: runs API (SSE lifecycle,
  approvals, steer, stop) > sessions stream > chat completions with local tools;
  tolerant event normalisation; webhook receiver hardened (secret, size limit).
- UI: JARVIS arc-reactor core on Canvas 2D reacting to the real audio signal,
  holographic HUD layout (transcript, core, Hermes ops panel with tools/crons/
  skills/sessions/link tabs, telemetry), settings drawer, approval modals,
  compact floating widget, four themes, tray icon and global shortcuts.
- Removed the 3D Eve robot, three.js and legacy assets; migrated 1.x settings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Wn5VX9HNbJ7N54hR24u9Y
2026-09-03 13:40:59 +00:00

58 lines
3.5 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { normalizeCompletionChunk, normalizeLifecycleEvent } from '../src/services/hermes/events';
import { hermesBaseUrl, resolveTransport } from '../src/services/hermes/client';
import type { HermesConfig } from '../src/services/hermes/types';
describe('normalizeLifecycleEvent', () => {
it('maps runs API events', () => {
expect(normalizeLifecycleEvent('message.delta', { delta: 'Hi' })).toEqual([{ kind: 'delta', text: 'Hi' }]);
const [start] = normalizeLifecycleEvent('tool.started', { tool: 'terminal', preview: 'ls', run_id: 'r1' });
expect(start).toMatchObject({ kind: 'tool.start', name: 'terminal', args: 'ls' });
const [end] = normalizeLifecycleEvent('tool.completed', { tool: 'terminal', error: 'boom' });
expect(end).toMatchObject({ kind: 'tool.end', ok: false });
const [approval] = normalizeLifecycleEvent('approval.request', { command: 'rm -rf x', choices: ['once', 'deny'] });
expect(approval).toMatchObject({ kind: 'approval.request', description: 'rm -rf x', options: ['once', 'deny'] });
const done = normalizeLifecycleEvent('run.completed', { output: 'ok', usage: { total_tokens: 3 }, session_id: 's1' });
expect(done[0]).toMatchObject({ kind: 'completed', text: 'ok', usage: { total_tokens: 3 } });
expect(done[1]).toEqual({ kind: 'session', sessionId: 's1' });
expect(normalizeLifecycleEvent('run.failed', { error: 'bad' })[0]).toEqual({ kind: 'error', message: 'bad' });
});
it('maps sessions stream and chat-completions tool progress', () => {
expect(normalizeLifecycleEvent('assistant.delta', { delta: 'a' })[0]).toEqual({ kind: 'delta', text: 'a' });
const [progress] = normalizeLifecycleEvent('hermes.tool.progress', { tool_name: 'web_search', status: 'started' });
expect(progress).toMatchObject({ kind: 'tool.progress', name: 'web_search' });
expect(normalizeLifecycleEvent('gateway.ready', {})).toEqual([]);
expect(normalizeLifecycleEvent('weird.event', { x: 1 })[0]).toMatchObject({ kind: 'raw' });
});
});
describe('normalizeCompletionChunk', () => {
it('accumulates streamed tool calls', () => {
const calls = new Map();
normalizeCompletionChunk({ choices: [{ delta: { tool_calls: [{ index: 0, id: 'c1', function: { name: 'set_', arguments: '{"a' } }] } }] }, calls);
const { events, finishReason } = normalizeCompletionChunk(
{ choices: [{ delta: { content: 'x', tool_calls: [{ index: 0, function: { name: 'hud', arguments: '":1}' } }] }, finish_reason: 'tool_calls' }] },
calls
);
expect(events).toEqual([{ kind: 'delta', text: 'x' }]);
expect(finishReason).toBe('tool_calls');
expect(calls.get(0)).toEqual({ id: 'c1', name: 'set_hud', arguments: '{"a":1}' });
});
});
describe('client helpers', () => {
const cfg = { transport: 'auto' } as HermesConfig;
it('normalises base urls', () => {
expect(hermesBaseUrl('http://h:8642/v1/')).toBe('http://h:8642');
expect(hermesBaseUrl('http://h:8642/v1/chat/completions')).toBe('http://h:8642');
expect(hermesBaseUrl('http://h:8642')).toBe('http://h:8642');
});
it('resolves transports from capabilities', () => {
expect(resolveTransport(cfg, null)).toBe('completions');
expect(resolveTransport(cfg, { features: { run_submission: true, run_events_sse: true } })).toBe('runs');
expect(resolveTransport(cfg, { endpoints: { session_chat: true } })).toBe('sessions');
expect(resolveTransport({ transport: 'completions' } as HermesConfig, { features: { run_submission: true, run_events_sse: true } })).toBe('completions');
});
});