feat: Telegram fleet alerts, forge sigil scramble, UI polish, agent ops
- Calibrate: per-event Telegram/SMTP toggles, test notification, chat ID help - Notify on agent connect/reconnect, offline/hashrate/rejection, forge complete - Sigil scramble post-forge uniquification and Dispense Reveal ceremony - Full system check, desktop push, BITS/host-binary persistence, Path Tracer - Dashboard/Crucible visual polish, haptics, sacred geometry, mobile nav - README documents alerts, sigil scramble, and pack-usb workflow - USB bundle repacked via pack-usb.bat (AetherForge.exe + synced agent source)
This commit is contained in:
73
server/web/src/components/Sound/SoundBridge.tsx
Normal file
73
server/web/src/components/Sound/SoundBridge.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useSound } from '../../context/SoundContext';
|
||||
import { useWebSocket } from '../../hooks/useWebSocket';
|
||||
import type { FleetAlert, WSMessage } from '../../types';
|
||||
import type { WSCommandResult } from '../../types/ws';
|
||||
|
||||
const SHARE_MIN_MS = 2500;
|
||||
const CMD_RESULT_MIN_MS = 600;
|
||||
const SILENT_CMD_ACTIONS = new Set(['get_log', 'wg_status', 'mesh_status']);
|
||||
|
||||
/**
|
||||
* Plays fleet/event cues from the shared dashboard WebSocket (not UI clicks).
|
||||
*/
|
||||
export default function SoundBridge() {
|
||||
const { enabled, play } = useSound();
|
||||
const { isConnected, latestMessage } = useWebSocket();
|
||||
const wasConnected = useRef<boolean | null>(null);
|
||||
const lastShareAt = useRef(0);
|
||||
const lastCmdAt = useRef(0);
|
||||
const lastMsgRef = useRef<WSMessage | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
if (wasConnected.current === null) {
|
||||
wasConnected.current = isConnected;
|
||||
return;
|
||||
}
|
||||
if (wasConnected.current !== isConnected) {
|
||||
play(isConnected ? 'connect' : 'disconnect');
|
||||
wasConnected.current = isConnected;
|
||||
}
|
||||
}, [isConnected, enabled, play]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled || !latestMessage || latestMessage === lastMsgRef.current) return;
|
||||
lastMsgRef.current = latestMessage;
|
||||
|
||||
switch (latestMessage.type) {
|
||||
case 'agent_online':
|
||||
play('online');
|
||||
break;
|
||||
case 'agent_offline':
|
||||
play('offline');
|
||||
break;
|
||||
case 'new_share': {
|
||||
const now = Date.now();
|
||||
if (now - lastShareAt.current >= SHARE_MIN_MS) {
|
||||
lastShareAt.current = now;
|
||||
play('share');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'fleet_alert': {
|
||||
const alert = latestMessage.payload as FleetAlert;
|
||||
play(alert.level === 'error' ? 'alertCritical' : 'alert');
|
||||
break;
|
||||
}
|
||||
case 'command_result': {
|
||||
const p = latestMessage.payload as WSCommandResult;
|
||||
if (p.action && SILENT_CMD_ACTIONS.has(p.action)) break;
|
||||
const now = Date.now();
|
||||
if (now - lastCmdAt.current < CMD_RESULT_MIN_MS) break;
|
||||
lastCmdAt.current = now;
|
||||
play(p.success ? 'success' : 'error');
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}, [latestMessage, enabled, play]);
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user