Files
EveFlow/shared/ipc.ts
T
Claude 5f0fa7a1e2 fix: full review pass (main process, voice pipeline, Hermes client, HUD) and e2e-validated local voice (v2.1.0.1)
Electron main
- utility-process channel: sherpa output copied into V8 buffers (Electron rejects external
  buffers), audio exchanged as base64; worker restart on timeout, identity-safe exit handling,
  deterministic native unload by restart
- webhook: loopback by default without secret, UTF-8-safe body assembly, clean restart, port validation
- files IPC: realpath-based root check, openPath allow-list (reveal-only outside document folders),
  Windows reserved names; store: debounced async atomic writes with backup of corrupt files;
  logger: streaming writes with rotation; log message size cap
- HTTP stream proxy: socket released on idle timeout / renderer destroyed, id validation
- model manager: inactivity timeout, retrying rm/rename (Windows locks), engine stopped before
  replacing a model; WAV decoder handles float/24-bit; IPC payload validation
- window: opaque rounded window on Windows, navigation lock-down, visibility events;
  Ctrl+Alt+Escape instead of the Task Manager shortcut; single-instance guard; EVEFLOW_USER_DATA

Voice pipeline
- abort semantics (SendHandle.aborted), abort before the stream opens, session id prefixes per transport
- hands-free re-arm after replies without speech, start/stop race, no chime on auto re-arm,
  no silence shipped to STT (400 ms pre-roll), no transcription of empty manual stops
- TTS: bounded prefetch, cancellable segments, non-interrupting notices, volume applied at play time
- SSE CRLF split, usage in chat completions, finish_reason length, phonetic regex hoisted

HUD
- core renderer: no canvas shadows, cached colours, reusable spectrum buffer, theme read on change,
  30 fps idle, stops when the window is hidden; ping flashes on send / tool / speech
- deltas coalesced per animation frame; stable auto-scroll; narrow selectors everywhere
- bundled fonts (offline), reduce-motion fix, error toasts, ops drawer below 1180 px,
  interim transcript and first-token latency in the core caption, Ctrl+K, dialog semantics,
  switch/aria roles, compact widget cleanup, Whisper small recommended for French
- docs/ROADMAP.md: audit results, e2e results and the plan towards a real JARVIS

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

125 lines
3.0 KiB
TypeScript

/**
* Shared IPC contract between the Electron main process and the renderer.
* Imported by both tsconfig projects; must stay free of Node/DOM-specific types.
*/
export type WindowMode = 'hud' | 'compact';
export interface HttpProxyFile {
name: string;
type: string;
data: Uint8Array;
}
export interface HttpProxyRequest {
url: string;
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
headers?: Record<string, string>;
/** JSON/text body. Ignored when `multipart` is provided. */
body?: string;
/** Multipart form upload (used for speech-to-text). */
multipart?: { fields: Record<string, string>; file: HttpProxyFile; fileField?: string };
responseType?: 'text' | 'binary';
timeoutMs?: number;
}
export interface HttpProxyResponse {
ok: boolean;
status: number;
statusText: string;
headers: Record<string, string>;
/** Present when responseType is 'text' (default). */
text?: string;
/** Present when responseType is 'binary'. */
binary?: Uint8Array;
}
export interface HttpStreamStart {
id: string;
ok: boolean;
status: number;
statusText: string;
headers: Record<string, string>;
}
export type HttpStreamEvent =
| { id: string; type: 'chunk'; text: string }
| { id: string; type: 'end' }
| { id: string; type: 'error'; message: string };
export interface SystemMetrics {
hostname: string;
platform: string;
cpuModel: string;
cpuCores: number;
cpuLoad: number; // 0-100
cpuFreqMhz: number;
memUsedPct: number; // 0-100
memTotalGb: number;
uptimeSec: number;
}
export interface HermesPushEvent {
type: 'message' | 'job' | 'raw';
role: 'user' | 'assistant' | 'system';
text: string;
source: string;
images?: string[];
jobName?: string;
status?: string;
receivedAt: string;
raw?: unknown;
}
export interface LogEntry {
ts: string;
level: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR';
tag: string;
message: string;
data?: unknown;
}
export interface WebhookStatus {
listening: boolean;
port: number;
path: string;
secretConfigured: boolean;
error?: string;
}
export interface AppInfo {
version: string;
platform: string;
isPackaged: boolean;
userDataPath: string;
sharedDir: string;
logPath: string;
}
export const IPC = {
windowControl: 'window:control',
windowSetMode: 'window:set-mode',
windowModeChanged: 'window:mode-changed',
windowVisibility: 'window:visibility',
log: 'log:write',
storeGet: 'store:get',
storeSet: 'store:set',
storeDelete: 'store:delete',
httpFetch: 'http:fetch',
httpStreamStart: 'http:stream:start',
httpStreamAbort: 'http:stream:abort',
httpStreamEvent: 'http:stream:event',
metrics: 'system:metrics',
appInfo: 'app:info',
readLocalFile: 'files:read-local',
writeSharedFile: 'files:write-shared',
openPath: 'files:open-path',
showInFolder: 'files:show-in-folder',
hermesPush: 'hermes:push',
webhookStatus: 'webhook:status',
webhookRestart: 'webhook:restart',
hotkey: 'hotkey:event'
} as const;
export type HotkeyEvent = 'ptt-toggle' | 'toggle-window' | 'stop-speaking';