tlnt
Version:
TLNT - HMS-Powered Multi-Agent Platform with Government Agency Analysis, Deep Research, and Enterprise-Ready Deployment. Self-optimizing multi-domain AI agent with continuous learning and enterprise-grade performance monitoring.
234 lines • 11.8 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect, useCallback } from 'react';
import { Box, Text, useInput, useApp } from 'ink';
import { AgentState } from './controlAwareAgent.js';
/**
* Terminal User Interface for Agent Watch Mode
* Provides real-time monitoring and control of HMS Dev agents
*/
export const AgentWatchTUI = ({ messageBus, watchdog }) => {
const { exit } = useApp();
const [state, setState] = useState({
agents: new Map(),
events: [],
alerts: [],
selectedAgent: null,
mode: 'agents',
filter: {
severity: [],
eventType: [],
source: []
}
});
// Update agents list
const updateAgents = useCallback(async () => {
try {
const activeAgents = await messageBus.getActiveAgents();
const agentMap = new Map();
for (const agentId of activeAgents) {
const status = await messageBus.getAgentStatus(agentId);
if (status) {
agentMap.set(agentId, {
agentId,
state: status.state,
lastHeartbeat: new Date(status.lastHeartbeat),
currentTask: status.currentTask,
progress: status.progress,
metadata: status.metadata
});
}
}
setState(prev => ({ ...prev, agents: agentMap }));
}
catch (error) {
console.error('Failed to update agents:', error);
}
}, [messageBus]);
// Update events list
const updateEvents = useCallback(() => {
const events = watchdog.getRecentEvents(50, {
severity: state.filter.severity.length > 0 ? state.filter.severity : undefined,
eventType: state.filter.eventType.length > 0 ? state.filter.eventType : undefined,
source: state.filter.source.length > 0 ? state.filter.source : undefined
});
setState(prev => ({ ...prev, events }));
}, [watchdog, state.filter]);
// Update alerts list
const updateAlerts = useCallback(() => {
const alerts = watchdog.getActiveAlerts();
setState(prev => ({ ...prev, alerts }));
}, [watchdog]);
// Handle keyboard input
useInput((input, key) => {
if (key.escape || input === 'q') {
exit();
return;
}
switch (input.toLowerCase()) {
case '1':
setState(prev => ({ ...prev, mode: 'agents' }));
break;
case '2':
setState(prev => ({ ...prev, mode: 'events' }));
break;
case '3':
setState(prev => ({ ...prev, mode: 'alerts' }));
break;
case 'p':
handleAgentControl('PAUSE');
break;
case 'r':
handleAgentControl('RESUME');
break;
case 'm':
handleAgentControl('MUTE');
break;
case 'u':
handleAgentControl('UNMUTE');
break;
case 'k':
handleAgentControl('KILL');
break;
case 'e':
handleAgentControl('ESCALATE');
break;
case 'a':
acknowledgeAlert();
break;
case 'f':
cycleFilter();
break;
}
// Agent selection with arrow keys
if (state.mode === 'agents') {
const agentIds = Array.from(state.agents.keys());
const currentIndex = state.selectedAgent ? agentIds.indexOf(state.selectedAgent) : -1;
if (key.upArrow && currentIndex > 0) {
setState(prev => ({ ...prev, selectedAgent: agentIds[currentIndex - 1] }));
}
else if (key.downArrow && currentIndex < agentIds.length - 1) {
setState(prev => ({ ...prev, selectedAgent: agentIds[currentIndex + 1] }));
}
}
});
// Handle agent control commands
const handleAgentControl = async (command) => {
if (!state.selectedAgent)
return;
try {
await messageBus.sendControl(state.selectedAgent, {
command: command,
agentId: state.selectedAgent,
operatorId: 'tui_operator',
reason: `Manual ${command.toLowerCase()} from TUI`
});
}
catch (error) {
console.error(`Failed to send ${command} to agent:`, error);
}
};
// Acknowledge selected alert
const acknowledgeAlert = () => {
if (state.alerts.length > 0) {
const alert = state.alerts[0]; // Acknowledge first alert for simplicity
watchdog.acknowledgeAlert(alert.alertId);
updateAlerts();
}
};
// Cycle through filter options
const cycleFilter = () => {
// Simplified filter cycling - in real implementation would have proper filter UI
setState(prev => ({
...prev,
filter: {
severity: prev.filter.severity.length === 0 ? ['error', 'critical'] : [],
eventType: [],
source: []
}
}));
};
// Set up periodic updates
useEffect(() => {
const interval = setInterval(() => {
updateAgents();
updateEvents();
updateAlerts();
}, 2000);
// Initial update
updateAgents();
updateEvents();
updateAlerts();
return () => clearInterval(interval);
}, [updateAgents, updateEvents, updateAlerts]);
// Render header
const renderHeader = () => (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "blue", children: [_jsx(Box, { children: _jsx(Text, { bold: true, color: "cyan", children: "HMS Dev Agent Watch Mode" }) }), _jsx(Box, { children: _jsx(Text, { children: "[1] Agents | [2] Events | [3] Alerts | [Q] Quit" }) }), _jsx(Box, { children: _jsx(Text, { children: "Controls: [P]ause [R]esume [M]ute [U]nmute [K]ill [E]scalate [A]ck Alert [F]ilter" }) })] }));
// Render agents view
const renderAgents = () => (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "green", children: [_jsx(Box, { children: _jsxs(Text, { bold: true, color: "green", children: ["Active Agents (", state.agents.size, ")"] }) }), Array.from(state.agents.entries()).map(([agentId, status], index) => (_jsxs(Box, { children: [_jsx(Box, { width: 20, children: _jsx(Text, { color: getStateColor(status.state), children: agentId }) }), _jsx(Box, { width: 12, children: _jsx(Text, { color: getStateColor(status.state), children: status.state.toUpperCase() }) }), _jsx(Box, { width: 8, children: _jsxs(Text, { children: [(status.progress * 100).toFixed(0), "%"] }) }), _jsx(Box, { width: 30, children: _jsx(Text, { children: status.currentTask || 'No task' }) }), _jsx(Box, { children: _jsx(Text, { color: "gray", children: getTimeSince(status.lastHeartbeat) }) })] }, agentId))), state.agents.size === 0 && (_jsx(Box, { children: _jsx(Text, { color: "yellow", children: "No active agents" }) }))] }));
// Render events view
const renderEvents = () => (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "yellow", children: [_jsx(Box, { children: _jsxs(Text, { bold: true, color: "yellow", children: ["Recent Events (", state.events.length, ")", state.filter.severity.length > 0 && (_jsxs(Text, { color: "gray", children: [" [Filtered: ", state.filter.severity.join(', '), "]"] }))] }) }), state.events.slice(0, 15).map((event, index) => (_jsxs(Box, { children: [_jsx(Box, { width: 8, children: _jsx(Text, { color: getSeverityColor(event.severity), children: event.severity.toUpperCase() }) }), _jsx(Box, { width: 15, children: _jsx(Text, { children: event.source }) }), _jsx(Box, { width: 15, children: _jsx(Text, { children: event.eventType }) }), _jsx(Box, { width: 12, children: _jsx(Text, { color: "gray", children: event.timestamp.toLocaleTimeString() }) }), _jsx(Box, { children: _jsx(Text, { children: getEventSummary(event) }) })] }, event.eventId))), state.events.length === 0 && (_jsx(Box, { children: _jsx(Text, { color: "yellow", children: "No events" }) }))] }));
// Render alerts view
const renderAlerts = () => (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "red", children: [_jsx(Box, { children: _jsxs(Text, { bold: true, color: "red", children: ["Active Alerts (", state.alerts.length, ")"] }) }), state.alerts.map((alert, index) => (_jsxs(Box, { children: [_jsx(Box, { width: 10, children: _jsx(Text, { color: getSeverityColor(alert.severity), children: alert.severity.toUpperCase() }) }), _jsx(Box, { width: 15, children: _jsx(Text, { children: alert.type }) }), _jsx(Box, { width: 12, children: _jsx(Text, { color: "gray", children: alert.timestamp.toLocaleTimeString() }) }), _jsx(Box, { children: _jsx(Text, { children: alert.title }) })] }, alert.alertId))), state.alerts.length === 0 && (_jsx(Box, { children: _jsx(Text, { color: "green", children: "No active alerts" }) }))] }));
// Render status bar
const renderStatusBar = () => (_jsxs(Box, { borderStyle: "single", borderColor: "gray", children: [_jsx(Box, { width: 20, children: _jsxs(Text, { children: ["Mode: ", state.mode.toUpperCase()] }) }), _jsx(Box, { width: 25, children: _jsxs(Text, { children: ["Selected: ", state.selectedAgent || 'None'] }) }), _jsx(Box, { children: _jsxs(Text, { color: "gray", children: ["Last update: ", new Date().toLocaleTimeString()] }) })] }));
return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [renderHeader(), _jsxs(Box, { flexGrow: 1, children: [state.mode === 'agents' && renderAgents(), state.mode === 'events' && renderEvents(), state.mode === 'alerts' && renderAlerts()] }), renderStatusBar()] }));
};
// Helper functions
function getStateColor(state) {
switch (state) {
case AgentState.RUNNING:
return 'green';
case AgentState.PAUSED:
return 'yellow';
case AgentState.MUTED:
return 'blue';
case AgentState.KILLED:
return 'red';
case AgentState.ESCALATED:
return 'magenta';
case AgentState.ERROR:
return 'red';
default:
return 'gray';
}
}
function getSeverityColor(severity) {
switch (severity) {
case 'critical':
return 'red';
case 'error':
return 'red';
case 'warning':
return 'yellow';
case 'info':
return 'blue';
case 'debug':
return 'gray';
default:
return 'white';
}
}
function getTimeSince(date) {
const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
if (seconds < 60)
return `${seconds}s`;
if (seconds < 3600)
return `${Math.floor(seconds / 60)}m`;
if (seconds < 86400)
return `${Math.floor(seconds / 3600)}h`;
return `${Math.floor(seconds / 86400)}d`;
}
function getEventSummary(event) {
// Extract meaningful summary from event data
if (event.data.message) {
return String(event.data.message).substring(0, 40);
}
if (event.data.action) {
return `Action: ${event.data.action}`;
}
if (event.data.error) {
return `Error: ${String(event.data.error).substring(0, 30)}`;
}
return `${event.eventType} event`;
}
export default AgentWatchTUI;
//# sourceMappingURL=agentWatchTui.js.map