Spaces:
Sleeping
Sleeping
ashishMenon05 commited on
Commit ·
7fbbd40
1
Parent(s): 174293c
feat(ui): support unlimited agents with scalable terminal grid and scrollable settings
Browse files- backend/config.py +49 -21
- backend/core/agent_runner.py +23 -9
- default.env +13 -1
- frontend/src/components/EpisodeEndOverlay.jsx +61 -85
- frontend/src/hooks/useWebSocket.js +0 -2
- frontend/src/views/DashboardView.jsx +21 -13
- frontend/src/views/SettingsView.jsx +25 -18
backend/config.py
CHANGED
|
@@ -20,31 +20,59 @@ class Settings:
|
|
| 20 |
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434/v1")
|
| 21 |
OLLAMA_API_KEY = os.getenv("OLLAMA_API_KEY", "ollama")
|
| 22 |
|
| 23 |
-
# AGENTS (Dynamic N-Agent Support)
|
| 24 |
import json
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
try:
|
| 44 |
AGENTS_JSON = os.getenv("AGENTS_JSON")
|
| 45 |
-
AGENTS = json.loads(AGENTS_JSON) if AGENTS_JSON else
|
| 46 |
except:
|
| 47 |
-
AGENTS =
|
| 48 |
# EXECUTION ENVIRONMENT
|
| 49 |
EXECUTION_MODE = os.getenv("EXECUTION_MODE", "simulated")
|
| 50 |
SSH_HOST = os.getenv("SSH_HOST", "")
|
|
|
|
| 20 |
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434/v1")
|
| 21 |
OLLAMA_API_KEY = os.getenv("OLLAMA_API_KEY", "ollama")
|
| 22 |
|
| 23 |
+
# AGENTS (Dynamic N-Agent Support - supports unlimited agents)
|
| 24 |
import json
|
| 25 |
+
_built_in_roles = ["INVESTIGATOR", "VALIDATOR", "FORENSIC_ANALYST", "NETWORK_ENGINEER", "SYSTEM_ADMIN", "SECURITY_ARCHITECT", "COMPLIANCE_OFFICER"]
|
| 26 |
+
_default_roles = ["INVESTIGATOR", "VALIDATOR", "FORENSIC_ANALYST", "NETWORK_ENGINEER"]
|
| 27 |
+
|
| 28 |
+
def _build_agents_from_env():
|
| 29 |
+
agents = []
|
| 30 |
+
suffix_map = {'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 4, 'f': 5, 'g': 6, 'h': 7, 'i': 8, 'j': 9}
|
| 31 |
+
for suffix, idx in suffix_map.items():
|
| 32 |
+
model_key = f"AGENT_{suffix.upper()}_MODEL"
|
| 33 |
+
provider_key = f"AGENT_{suffix.upper()}_PROVIDER"
|
| 34 |
+
role_key = f"AGENT_{suffix.upper()}_ROLE"
|
| 35 |
+
prompt_key = f"AGENT_{suffix.upper()}_SYSTEM_PROMPT"
|
| 36 |
+
temp_key = f"AGENT_{suffix.upper()}_TEMPERATURE"
|
| 37 |
+
|
| 38 |
+
model = os.getenv(model_key, "")
|
| 39 |
+
if model:
|
| 40 |
+
role_idx = idx % len(_default_roles)
|
| 41 |
+
agents.append({
|
| 42 |
+
"id": f"agent_{suffix}",
|
| 43 |
+
"model": model,
|
| 44 |
+
"provider": os.getenv(provider_key, "ollama"),
|
| 45 |
+
"role": os.getenv(role_key, _default_roles[role_idx]),
|
| 46 |
+
"system_prompt": os.getenv(prompt_key, ""),
|
| 47 |
+
"temperature": float(os.getenv(temp_key, str(0.7 - idx * 0.05)))
|
| 48 |
+
})
|
| 49 |
+
|
| 50 |
+
if not agents:
|
| 51 |
+
agents = [
|
| 52 |
+
{
|
| 53 |
+
"id": "agent_a",
|
| 54 |
+
"model": os.getenv("AGENT_A_MODEL", "meta-llama/Llama-3.1-8B-Instruct"),
|
| 55 |
+
"provider": os.getenv("AGENT_A_PROVIDER", "hf"),
|
| 56 |
+
"role": "INVESTIGATOR",
|
| 57 |
+
"system_prompt": "",
|
| 58 |
+
"temperature": 0.7
|
| 59 |
+
},
|
| 60 |
+
{
|
| 61 |
+
"id": "agent_b",
|
| 62 |
+
"model": os.getenv("AGENT_B_MODEL", "meta-llama/Llama-3.2-1B-Instruct"),
|
| 63 |
+
"provider": os.getenv("AGENT_B_PROVIDER", "hf"),
|
| 64 |
+
"role": "VALIDATOR",
|
| 65 |
+
"system_prompt": "",
|
| 66 |
+
"temperature": 0.6
|
| 67 |
+
}
|
| 68 |
+
]
|
| 69 |
+
return agents
|
| 70 |
+
|
| 71 |
try:
|
| 72 |
AGENTS_JSON = os.getenv("AGENTS_JSON")
|
| 73 |
+
AGENTS = json.loads(AGENTS_JSON) if AGENTS_JSON else _build_agents_from_env()
|
| 74 |
except:
|
| 75 |
+
AGENTS = _build_agents_from_env()
|
| 76 |
# EXECUTION ENVIRONMENT
|
| 77 |
EXECUTION_MODE = os.getenv("EXECUTION_MODE", "simulated")
|
| 78 |
SSH_HOST = os.getenv("SSH_HOST", "")
|
backend/core/agent_runner.py
CHANGED
|
@@ -92,19 +92,33 @@ class AgentRunner:
|
|
| 92 |
sys_prompt = behavior + "\n\n" + tool_rules
|
| 93 |
|
| 94 |
context = f"Current incident: {scenario.get('description', '')}\n"
|
| 95 |
-
|
| 96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
if hasattr(episode_state, 'clues_found') and episode_state.clues_found:
|
| 98 |
-
context += f"
|
|
|
|
|
|
|
| 99 |
|
| 100 |
messages = [{"role": "system", "content": sys_prompt}]
|
| 101 |
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
|
|
|
| 108 |
|
| 109 |
messages.append({"role": "user", "content": context})
|
| 110 |
|
|
|
|
| 92 |
sys_prompt = behavior + "\n\n" + tool_rules
|
| 93 |
|
| 94 |
context = f"Current incident: {scenario.get('description', '')}\n"
|
| 95 |
+
|
| 96 |
+
other_agents = [a["id"] for a in settings.AGENTS if a["id"] != agent_id]
|
| 97 |
+
if other_agents:
|
| 98 |
+
context += f"Other agents in this investigation: {', '.join(other_agents)}\n"
|
| 99 |
+
|
| 100 |
+
agent_configs = {a["id"]: a for a in settings.AGENTS}
|
| 101 |
+
for other_id in other_agents:
|
| 102 |
+
other_msgs = episode_state.messages_by_agent.get(other_id, [])
|
| 103 |
+
if other_msgs:
|
| 104 |
+
other_role = agent_configs.get(other_id, {}).get("role", "AGENT")
|
| 105 |
+
last_msg = other_msgs[-1] if other_msgs else ""
|
| 106 |
+
context += f"\n[{other_role}] {other_id}'s latest insight: {last_msg[:300]}...\n"
|
| 107 |
+
|
| 108 |
if hasattr(episode_state, 'clues_found') and episode_state.clues_found:
|
| 109 |
+
context += f"\nClues discovered so far:\n"
|
| 110 |
+
for clue in episode_state.clues_found[-5:]:
|
| 111 |
+
context += f"- {clue[:200]}\n"
|
| 112 |
|
| 113 |
messages = [{"role": "system", "content": sys_prompt}]
|
| 114 |
|
| 115 |
+
recent_msgs = episode_state.all_messages[-6:]
|
| 116 |
+
if recent_msgs:
|
| 117 |
+
context += "\nRecent conversation history:\n"
|
| 118 |
+
for i, m in enumerate(recent_msgs[-4:]):
|
| 119 |
+
if len(m) > 150:
|
| 120 |
+
m = m[:150] + "..."
|
| 121 |
+
context += f"- {m}\n"
|
| 122 |
|
| 123 |
messages.append({"role": "user", "content": context})
|
| 124 |
|
default.env
CHANGED
|
@@ -13,16 +13,28 @@ OPENAI_API_KEY=
|
|
| 13 |
OPENAI_BASE_URL=https://api.openai.com/v1
|
| 14 |
|
| 15 |
# AGENTS - HuggingFace models (work with HF Inference API)
|
|
|
|
|
|
|
| 16 |
AGENT_A_MODEL=meta-llama/Llama-3.1-8B-Instruct
|
| 17 |
AGENT_B_MODEL=meta-llama/Llama-3.2-1B-Instruct
|
|
|
|
|
|
|
| 18 |
AGENT_A_PROVIDER=hf
|
| 19 |
AGENT_B_PROVIDER=hf
|
|
|
|
|
|
|
| 20 |
AGENT_A_ROLE=INVESTIGATOR
|
| 21 |
AGENT_B_ROLE=VALIDATOR
|
|
|
|
|
|
|
| 22 |
AGENT_A_TEMPERATURE=0.7
|
| 23 |
-
AGENT_B_TEMPERATURE=0.
|
|
|
|
|
|
|
| 24 |
AGENT_A_MAX_TOKENS=512
|
| 25 |
AGENT_B_MAX_TOKENS=512
|
|
|
|
|
|
|
| 26 |
|
| 27 |
# EXECUTION ENVIRONMENT
|
| 28 |
EXECUTION_MODE=simulated
|
|
|
|
| 13 |
OPENAI_BASE_URL=https://api.openai.com/v1
|
| 14 |
|
| 15 |
# AGENTS - HuggingFace models (work with HF Inference API)
|
| 16 |
+
# Supports agents a through j (10 agents max via env vars)
|
| 17 |
+
# Additional agents can be configured via AGENTS_JSON env var
|
| 18 |
AGENT_A_MODEL=meta-llama/Llama-3.1-8B-Instruct
|
| 19 |
AGENT_B_MODEL=meta-llama/Llama-3.2-1B-Instruct
|
| 20 |
+
AGENT_C_MODEL=
|
| 21 |
+
AGENT_D_MODEL=
|
| 22 |
AGENT_A_PROVIDER=hf
|
| 23 |
AGENT_B_PROVIDER=hf
|
| 24 |
+
AGENT_C_PROVIDER=hf
|
| 25 |
+
AGENT_D_PROVIDER=hf
|
| 26 |
AGENT_A_ROLE=INVESTIGATOR
|
| 27 |
AGENT_B_ROLE=VALIDATOR
|
| 28 |
+
AGENT_C_ROLE=FORENSIC_ANALYST
|
| 29 |
+
AGENT_D_ROLE=NETWORK_ENGINEER
|
| 30 |
AGENT_A_TEMPERATURE=0.7
|
| 31 |
+
AGENT_B_TEMPERATURE=0.6
|
| 32 |
+
AGENT_C_TEMPERATURE=0.5
|
| 33 |
+
AGENT_D_TEMPERATURE=0.5
|
| 34 |
AGENT_A_MAX_TOKENS=512
|
| 35 |
AGENT_B_MAX_TOKENS=512
|
| 36 |
+
AGENT_C_MAX_TOKENS=512
|
| 37 |
+
AGENT_D_MAX_TOKENS=512
|
| 38 |
|
| 39 |
# EXECUTION ENVIRONMENT
|
| 40 |
EXECUTION_MODE=simulated
|
frontend/src/components/EpisodeEndOverlay.jsx
CHANGED
|
@@ -6,10 +6,9 @@ const EpisodeEndOverlay = ({ isOpen, onClose, metrics, gameState }) => {
|
|
| 6 |
const handleDownload = () => {
|
| 7 |
if (!gameState) return;
|
| 8 |
|
| 9 |
-
// Assemble the detailed incident report
|
| 10 |
const sc = gameState.scenario || {};
|
| 11 |
-
const
|
| 12 |
-
const
|
| 13 |
|
| 14 |
let report = `=================================================================\n`;
|
| 15 |
report += ` NEXUS INCIDENT INVESTIGATION REPORT \n`;
|
|
@@ -20,7 +19,17 @@ const EpisodeEndOverlay = ({ isOpen, onClose, metrics, gameState }) => {
|
|
| 20 |
report += `Domain: ${sc.domain || 'N/A'}\n`;
|
| 21 |
report += `Difficulty: ${sc.difficulty || 'N/A'}\n`;
|
| 22 |
report += `Final Grading Score: ${Number(gameState?.cumulativeReward || metrics?.score || 0).toFixed(4)} / 1.00\n`;
|
| 23 |
-
report += `Total Steps: ${gameState?.step || metrics?.steps || 'N/A'}\n
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
report += `[ STEP REWARDS ]\n`;
|
| 26 |
if (gameState?.rewardHistory && gameState.rewardHistory.length > 0) {
|
|
@@ -52,13 +61,10 @@ const EpisodeEndOverlay = ({ isOpen, onClose, metrics, gameState }) => {
|
|
| 52 |
report += `[ INVESTIGATION LOG & DETAILED TRACE ]\n`;
|
| 53 |
report += `=================================================================\n\n`;
|
| 54 |
|
| 55 |
-
// Interweave the messages to show the timeline (roughly)
|
| 56 |
-
// Since we don't have exact timestamps, we'll just print Agent A then Agent B summary,
|
| 57 |
-
// or just print all tools called and errors encountered.
|
| 58 |
const allErrors = [];
|
| 59 |
const allTools = [];
|
| 60 |
|
| 61 |
-
|
| 62 |
if (msg.type === 'tool_call') {
|
| 63 |
allTools.push(`- ${msg.tool_name}(${JSON.stringify(msg.params)})`);
|
| 64 |
}
|
|
@@ -66,7 +72,6 @@ const EpisodeEndOverlay = ({ isOpen, onClose, metrics, gameState }) => {
|
|
| 66 |
allErrors.push(`- Error from ${msg.tool_name}: ${msg.result}`);
|
| 67 |
}
|
| 68 |
if (msg.type === 'tool_result' && msg.result?.toLowerCase().includes('error')) {
|
| 69 |
-
// Catch strings that say error but were marked success true somehow
|
| 70 |
allErrors.push(`- Log/Cmd Error: ${msg.result}`);
|
| 71 |
}
|
| 72 |
});
|
|
@@ -222,70 +227,42 @@ const EpisodeEndOverlay = ({ isOpen, onClose, metrics, gameState }) => {
|
|
| 222 |
{/* Right Column: Agent Metrics */}
|
| 223 |
<div className="space-y-6">
|
| 224 |
<h3 className="font-mono text-[10px] text-outline tracking-widest uppercase mb-4">Agent Performance Breakdown</h3>
|
| 225 |
-
{
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
<div className="
|
| 240 |
-
<
|
| 241 |
-
|
| 242 |
-
<span className="font-headline text-lg font-medium text-primary">{msgCount}</span>
|
| 243 |
-
</div>
|
| 244 |
-
<div className="border-x border-white/5">
|
| 245 |
-
<span className="font-mono text-[9px] text-outline flex flex-col items-center justify-center gap-1 uppercase"><span className="material-symbols-outlined text-[12px]">build</span> TOOLS</span>
|
| 246 |
-
<span className="font-headline text-lg font-medium text-primary">{toolCount}</span>
|
| 247 |
-
</div>
|
| 248 |
-
<div>
|
| 249 |
-
<span className="font-mono text-[9px] text-outline flex flex-col items-center justify-center gap-1 uppercase"><span className="material-symbols-outlined text-[12px]">warning</span> ERRS</span>
|
| 250 |
-
<span className="font-headline text-lg font-medium text-primary">{errCount}</span>
|
| 251 |
-
</div>
|
| 252 |
</div>
|
| 253 |
-
);
|
| 254 |
-
})()}
|
| 255 |
-
</div>
|
| 256 |
-
</div>
|
| 257 |
-
{/* Agent B */}
|
| 258 |
-
<div className="relative group">
|
| 259 |
-
<div className="absolute -left-4 top-0 bottom-0 w-1 bg-secondary shadow-[0_0_8px_rgba(221,183,255,0.4)]"></div>
|
| 260 |
-
<div className="bg-surface-container-low/40 p-5 space-y-4 border border-white/5 rounded-r-lg">
|
| 261 |
-
<div className="flex justify-between items-center">
|
| 262 |
-
<span className="font-headline font-bold text-secondary tracking-tighter uppercase">Agent_Bravo</span>
|
| 263 |
-
<span className="font-mono text-[10px] text-secondary/50">VIOLET_PROTOCOL</span>
|
| 264 |
-
</div>
|
| 265 |
-
{(() => {
|
| 266 |
-
const msgs = gameState?.agents?.agent_b?.messages || [];
|
| 267 |
-
const msgCount = msgs.filter(m => m.type === 'message').length;
|
| 268 |
-
const toolCount = msgs.filter(m => m.type === 'tool_call').length;
|
| 269 |
-
const errCount = msgs.filter(m => m.type === 'tool_result' && m.result?.toLowerCase().includes('error')).length;
|
| 270 |
-
return (
|
| 271 |
<div className="grid grid-cols-3 gap-2 text-center">
|
| 272 |
<div>
|
| 273 |
<span className="font-mono text-[9px] text-outline flex flex-col items-center justify-center gap-1 uppercase"><span className="material-symbols-outlined text-[12px]">chat</span> MSGS</span>
|
| 274 |
-
<span className=
|
| 275 |
</div>
|
| 276 |
<div className="border-x border-white/5">
|
| 277 |
<span className="font-mono text-[9px] text-outline flex flex-col items-center justify-center gap-1 uppercase"><span className="material-symbols-outlined text-[12px]">build</span> TOOLS</span>
|
| 278 |
-
<span className=
|
| 279 |
</div>
|
| 280 |
<div>
|
| 281 |
<span className="font-mono text-[9px] text-outline flex flex-col items-center justify-center gap-1 uppercase"><span className="material-symbols-outlined text-[12px]">warning</span> ERRS</span>
|
| 282 |
-
<span className=
|
| 283 |
</div>
|
| 284 |
</div>
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
</div>
|
| 290 |
</div>
|
| 291 |
|
|
@@ -320,39 +297,38 @@ const EpisodeEndOverlay = ({ isOpen, onClose, metrics, gameState }) => {
|
|
| 320 |
);
|
| 321 |
})()}
|
| 322 |
|
| 323 |
-
{/*
|
| 324 |
{(() => {
|
| 325 |
-
const
|
| 326 |
-
const
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
|
|
|
| 335 |
|
| 336 |
return (
|
| 337 |
<div className="px-8 pb-8">
|
| 338 |
<div className="p-6 bg-surface-container-low/40 border border-white/10 rounded-lg">
|
| 339 |
<h3 className="font-headline font-bold text-on-surface tracking-widest uppercase mb-4 flex items-center gap-2">
|
| 340 |
<span className="material-symbols-outlined">gavel</span>
|
| 341 |
-
|
| 342 |
</h3>
|
| 343 |
<div className="space-y-4">
|
| 344 |
-
{
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
</div>
|
| 355 |
-
)}
|
| 356 |
</div>
|
| 357 |
</div>
|
| 358 |
</div>
|
|
|
|
| 6 |
const handleDownload = () => {
|
| 7 |
if (!gameState) return;
|
| 8 |
|
|
|
|
| 9 |
const sc = gameState.scenario || {};
|
| 10 |
+
const allAgents = gameState.agents || {};
|
| 11 |
+
const allMessages = Object.values(allAgents).flatMap(a => a?.messages || []);
|
| 12 |
|
| 13 |
let report = `=================================================================\n`;
|
| 14 |
report += ` NEXUS INCIDENT INVESTIGATION REPORT \n`;
|
|
|
|
| 19 |
report += `Domain: ${sc.domain || 'N/A'}\n`;
|
| 20 |
report += `Difficulty: ${sc.difficulty || 'N/A'}\n`;
|
| 21 |
report += `Final Grading Score: ${Number(gameState?.cumulativeReward || metrics?.score || 0).toFixed(4)} / 1.00\n`;
|
| 22 |
+
report += `Total Steps: ${gameState?.step || metrics?.steps || 'N/A'}\n`;
|
| 23 |
+
report += `Active Agents: ${Object.keys(allAgents).length}\n\n`;
|
| 24 |
+
|
| 25 |
+
report += `[ AGENTS DEPLOYED ]\n`;
|
| 26 |
+
Object.entries(allAgents).forEach(([agentId, agentData], idx) => {
|
| 27 |
+
const msgs = agentData?.messages || [];
|
| 28 |
+
const msgCount = msgs.filter(m => m.type === 'message').length;
|
| 29 |
+
const toolCount = msgs.filter(m => m.type === 'tool_call').length;
|
| 30 |
+
report += `${idx + 1}. ${agentId}: ${msgCount} messages, ${toolCount} tool calls\n`;
|
| 31 |
+
});
|
| 32 |
+
report += `\n`;
|
| 33 |
|
| 34 |
report += `[ STEP REWARDS ]\n`;
|
| 35 |
if (gameState?.rewardHistory && gameState.rewardHistory.length > 0) {
|
|
|
|
| 61 |
report += `[ INVESTIGATION LOG & DETAILED TRACE ]\n`;
|
| 62 |
report += `=================================================================\n\n`;
|
| 63 |
|
|
|
|
|
|
|
|
|
|
| 64 |
const allErrors = [];
|
| 65 |
const allTools = [];
|
| 66 |
|
| 67 |
+
allMessages.forEach(msg => {
|
| 68 |
if (msg.type === 'tool_call') {
|
| 69 |
allTools.push(`- ${msg.tool_name}(${JSON.stringify(msg.params)})`);
|
| 70 |
}
|
|
|
|
| 72 |
allErrors.push(`- Error from ${msg.tool_name}: ${msg.result}`);
|
| 73 |
}
|
| 74 |
if (msg.type === 'tool_result' && msg.result?.toLowerCase().includes('error')) {
|
|
|
|
| 75 |
allErrors.push(`- Log/Cmd Error: ${msg.result}`);
|
| 76 |
}
|
| 77 |
});
|
|
|
|
| 227 |
{/* Right Column: Agent Metrics */}
|
| 228 |
<div className="space-y-6">
|
| 229 |
<h3 className="font-mono text-[10px] text-outline tracking-widest uppercase mb-4">Agent Performance Breakdown</h3>
|
| 230 |
+
{Object.entries(gameState?.agents || {}).map(([agentId, agentData], idx) => {
|
| 231 |
+
const colors = ['primary', 'secondary', 'tertiary', 'error', 'success'];
|
| 232 |
+
const color = colors[idx % colors.length];
|
| 233 |
+
const msgs = agentData?.messages || [];
|
| 234 |
+
const msgCount = msgs.filter(m => m.type === 'message').length;
|
| 235 |
+
const toolCount = msgs.filter(m => m.type === 'tool_call').length;
|
| 236 |
+
const errCount = msgs.filter(m => m.type === 'tool_result' && m.result?.toLowerCase().includes('error')).length;
|
| 237 |
+
const agentNames = ['ALPHA', 'BRAVO', 'CHARLIE', 'DELTA', 'ECHO'];
|
| 238 |
+
const agentName = agentNames[idx % agentNames.length];
|
| 239 |
+
|
| 240 |
+
return (
|
| 241 |
+
<div key={agentId} className="relative group">
|
| 242 |
+
<div className={`absolute -left-4 top-0 bottom-0 w-1 bg-${color} shadow-[0_0_8px_rgba(var(--${color}),0.4)]`}></div>
|
| 243 |
+
<div className="bg-surface-container-low/40 p-5 space-y-4 border border-white/5 rounded-r-lg">
|
| 244 |
+
<div className="flex justify-between items-center">
|
| 245 |
+
<span className={`font-headline font-bold text-${color} tracking-tighter uppercase`}>Agent_{agentName}</span>
|
| 246 |
+
<span className={`font-mono text-[10px] text-${color}/50`}>{agentId.toUpperCase()}</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
<div className="grid grid-cols-3 gap-2 text-center">
|
| 249 |
<div>
|
| 250 |
<span className="font-mono text-[9px] text-outline flex flex-col items-center justify-center gap-1 uppercase"><span className="material-symbols-outlined text-[12px]">chat</span> MSGS</span>
|
| 251 |
+
<span className={`font-headline text-lg font-medium text-${color}`}>{msgCount}</span>
|
| 252 |
</div>
|
| 253 |
<div className="border-x border-white/5">
|
| 254 |
<span className="font-mono text-[9px] text-outline flex flex-col items-center justify-center gap-1 uppercase"><span className="material-symbols-outlined text-[12px]">build</span> TOOLS</span>
|
| 255 |
+
<span className={`font-headline text-lg font-medium text-${color}`}>{toolCount}</span>
|
| 256 |
</div>
|
| 257 |
<div>
|
| 258 |
<span className="font-mono text-[9px] text-outline flex flex-col items-center justify-center gap-1 uppercase"><span className="material-symbols-outlined text-[12px]">warning</span> ERRS</span>
|
| 259 |
+
<span className={`font-headline text-lg font-medium text-${color}`}>{errCount}</span>
|
| 260 |
</div>
|
| 261 |
</div>
|
| 262 |
+
</div>
|
| 263 |
+
</div>
|
| 264 |
+
);
|
| 265 |
+
})}
|
| 266 |
</div>
|
| 267 |
</div>
|
| 268 |
|
|
|
|
| 297 |
);
|
| 298 |
})()}
|
| 299 |
|
| 300 |
+
{/* Multi-Agent Final Verdict Panel */}
|
| 301 |
{(() => {
|
| 302 |
+
const allAgents = gameState?.agents || {};
|
| 303 |
+
const agentEntries = Object.entries(allAgents);
|
| 304 |
+
const conclusions = agentEntries.map(([agentId, agentData]) => {
|
| 305 |
+
const msgs = agentData?.messages || [];
|
| 306 |
+
const textMsgs = msgs.filter(m => m.type === 'message');
|
| 307 |
+
return { agentId, lastMsg: textMsgs[textMsgs.length - 1] };
|
| 308 |
+
}).filter(c => c.lastMsg);
|
| 309 |
+
|
| 310 |
+
if (conclusions.length === 0) return null;
|
| 311 |
+
|
| 312 |
+
const colors = ['primary', 'secondary', 'tertiary', 'error', 'success'];
|
| 313 |
|
| 314 |
return (
|
| 315 |
<div className="px-8 pb-8">
|
| 316 |
<div className="p-6 bg-surface-container-low/40 border border-white/10 rounded-lg">
|
| 317 |
<h3 className="font-headline font-bold text-on-surface tracking-widest uppercase mb-4 flex items-center gap-2">
|
| 318 |
<span className="material-symbols-outlined">gavel</span>
|
| 319 |
+
Multi-Agent Final Verdict
|
| 320 |
</h3>
|
| 321 |
<div className="space-y-4">
|
| 322 |
+
{conclusions.map(({ agentId, lastMsg }, idx) => {
|
| 323 |
+
const color = colors[idx % colors.length];
|
| 324 |
+
const agentNames = ['ALPHA', 'BRAVO', 'CHARLIE', 'DELTA', 'ECHO'];
|
| 325 |
+
return (
|
| 326 |
+
<div key={agentId} className={`p-4 bg-${color}/5 border-l-2 border-${color} rounded-r`}>
|
| 327 |
+
<span className={`font-mono text-[10px] text-${color} uppercase block mb-1 tracking-widest`}>Agent {agentNames[idx % agentNames.length]} ({agentId}) Conclusion</span>
|
| 328 |
+
<p className="text-sm text-on-surface/90 leading-relaxed">{lastMsg.content || lastMsg.text || lastMsg.message}</p>
|
| 329 |
+
</div>
|
| 330 |
+
);
|
| 331 |
+
})}
|
|
|
|
|
|
|
| 332 |
</div>
|
| 333 |
</div>
|
| 334 |
</div>
|
frontend/src/hooks/useWebSocket.js
CHANGED
|
@@ -9,8 +9,6 @@ const useWebSocket = (url) => {
|
|
| 9 |
step: 0,
|
| 10 |
reward: 0,
|
| 11 |
cumulativeReward: 0,
|
| 12 |
-
agent_a_model: '',
|
| 13 |
-
agent_b_model: '',
|
| 14 |
agents: {},
|
| 15 |
clues_found: [],
|
| 16 |
rewardBreakdown: {},
|
|
|
|
| 9 |
step: 0,
|
| 10 |
reward: 0,
|
| 11 |
cumulativeReward: 0,
|
|
|
|
|
|
|
| 12 |
agents: {},
|
| 13 |
clues_found: [],
|
| 14 |
rewardBreakdown: {},
|
frontend/src/views/DashboardView.jsx
CHANGED
|
@@ -141,9 +141,7 @@ const DashboardView = () => {
|
|
| 141 |
active: false,
|
| 142 |
step: 0,
|
| 143 |
cumulativeReward: 0,
|
| 144 |
-
|
| 145 |
-
agent_a: []
|
| 146 |
-
}
|
| 147 |
};
|
| 148 |
|
| 149 |
const sc = state.scenario || {};
|
|
@@ -219,20 +217,31 @@ const DashboardView = () => {
|
|
| 219 |
</div>
|
| 220 |
|
| 221 |
{/* N-Agent Terminals */}
|
| 222 |
-
<div className={`grid grid-cols-1 ${
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
{configModels.agents.map((agent, index) => {
|
| 224 |
-
const
|
| 225 |
-
const accentColor =
|
| 226 |
-
// We don't have agent specific status tracked deeply beyond STANDBY/ACTIVE globally right now
|
| 227 |
-
// We deduce messages from state.agents
|
| 228 |
const messages = state.agents?.[agent.id]?.messages || [];
|
| 229 |
const agentStatus = state.active ? 'ACTIVE' : 'STANDBY';
|
| 230 |
-
const icon = agent.role.includes('VALIDATOR') ? 'verified_user' : 'search';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
|
| 232 |
return (
|
| 233 |
<AgentTerminal
|
| 234 |
key={agent.id}
|
| 235 |
-
agentName={`Agent ${
|
| 236 |
model={agent.model}
|
| 237 |
status={agentStatus}
|
| 238 |
accentColor={accentColor}
|
|
@@ -322,11 +331,10 @@ const DashboardView = () => {
|
|
| 322 |
onClose={() => setIsOverlayDismissed(true)}
|
| 323 |
metrics={{
|
| 324 |
score: Number(state.cumulativeReward || 0).toFixed(2),
|
| 325 |
-
runtime: '00:00:00',
|
| 326 |
steps: state.step || 0,
|
| 327 |
rootCause: 'VERIFIED',
|
| 328 |
-
|
| 329 |
-
agentB: { accuracy: 'High', latency: '38ms', iops: '7' }
|
| 330 |
}}
|
| 331 |
gameState={state}
|
| 332 |
/>
|
|
|
|
| 141 |
active: false,
|
| 142 |
step: 0,
|
| 143 |
cumulativeReward: 0,
|
| 144 |
+
agents: {}
|
|
|
|
|
|
|
| 145 |
};
|
| 146 |
|
| 147 |
const sc = state.scenario || {};
|
|
|
|
| 217 |
</div>
|
| 218 |
|
| 219 |
{/* N-Agent Terminals */}
|
| 220 |
+
<div className={`grid grid-cols-1 ${
|
| 221 |
+
configModels.agents.length === 1 ? 'lg:grid-cols-1' :
|
| 222 |
+
configModels.agents.length === 2 ? 'lg:grid-cols-2' :
|
| 223 |
+
configModels.agents.length === 3 ? 'lg:grid-cols-3' :
|
| 224 |
+
configModels.agents.length === 4 ? 'lg:grid-cols-2' :
|
| 225 |
+
configModels.agents.length <= 6 ? 'lg:grid-cols-3' : 'lg:grid-cols-4'
|
| 226 |
+
} gap-6 overflow-y-auto max-h-[80vh] custom-scrollbar pr-2`}>
|
| 227 |
{configModels.agents.map((agent, index) => {
|
| 228 |
+
const colors = ['cyan', 'purple', 'green', 'orange', 'pink', 'yellow', 'red', 'blue'];
|
| 229 |
+
const accentColor = colors[index % colors.length];
|
|
|
|
|
|
|
| 230 |
const messages = state.agents?.[agent.id]?.messages || [];
|
| 231 |
const agentStatus = state.active ? 'ACTIVE' : 'STANDBY';
|
| 232 |
+
const icon = agent.role?.includes('VALIDATOR') ? 'verified_user' : 'search';
|
| 233 |
+
|
| 234 |
+
// Scalable naming: ALPHA, BRAVO... then AA, AB...
|
| 235 |
+
const agentNames = ['ALPHA', 'BRAVO', 'CHARLIE', 'DELTA', 'ECHO', 'FOXTROT', 'GOLF', 'HOTEL', 'INDIA', 'JULIETT', 'KILO', 'LIMA', 'MIKE', 'NOVEMBER', 'OSCAR', 'PAPA', 'QUEBEC', 'ROMEO', 'SIERRA', 'TANGO', 'UNIFORM', 'VICTOR', 'WHISKEY', 'X-RAY', 'YANKEE', 'ZULU'];
|
| 236 |
+
let name = agentNames[index % agentNames.length];
|
| 237 |
+
if (index >= agentNames.length) {
|
| 238 |
+
name = `${name}_${Math.floor(index / agentNames.length)}`;
|
| 239 |
+
}
|
| 240 |
|
| 241 |
return (
|
| 242 |
<AgentTerminal
|
| 243 |
key={agent.id}
|
| 244 |
+
agentName={`Agent ${name}: ${agent.role?.replace(/_/g, ' ') || 'AGENT'}`}
|
| 245 |
model={agent.model}
|
| 246 |
status={agentStatus}
|
| 247 |
accentColor={accentColor}
|
|
|
|
| 331 |
onClose={() => setIsOverlayDismissed(true)}
|
| 332 |
metrics={{
|
| 333 |
score: Number(state.cumulativeReward || 0).toFixed(2),
|
| 334 |
+
runtime: '00:00:00',
|
| 335 |
steps: state.step || 0,
|
| 336 |
rootCause: 'VERIFIED',
|
| 337 |
+
agentCount: Object.keys(state.agents || {}).length
|
|
|
|
| 338 |
}}
|
| 339 |
gameState={state}
|
| 340 |
/>
|
frontend/src/views/SettingsView.jsx
CHANGED
|
@@ -279,16 +279,17 @@ const SettingsView = () => {
|
|
| 279 |
};
|
| 280 |
|
| 281 |
const addAgent = () => {
|
| 282 |
-
|
| 283 |
-
const
|
|
|
|
| 284 |
setAgents(prev => [...prev, {
|
| 285 |
-
id: newId, provider: 'hf', model: '', hfModel: 'meta-llama/Llama-3.2-1B-Instruct', openaiModel: 'gpt-4o-mini', temp: 0.
|
| 286 |
}]);
|
| 287 |
};
|
| 288 |
|
| 289 |
const removeAgent = (index) => {
|
| 290 |
if (agents.length <= 1) return;
|
| 291 |
-
setAgents(prev => prev.filter((_, i) => i !== index)
|
| 292 |
};
|
| 293 |
|
| 294 |
const ProviderToggle = ({ agent, index }) => (
|
|
@@ -322,9 +323,14 @@ const SettingsView = () => {
|
|
| 322 |
</div>
|
| 323 |
</section>
|
| 324 |
|
| 325 |
-
<div className="
|
| 326 |
-
|
| 327 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 328 |
const isPrimary = index % 2 === 0;
|
| 329 |
const accentColor = isPrimary ? 'primary' : 'secondary';
|
| 330 |
const titleColor = isPrimary ? 'text-primary' : 'text-secondary';
|
|
@@ -340,8 +346,8 @@ const SettingsView = () => {
|
|
| 340 |
<div className="flex-1">
|
| 341 |
<div className="flex justify-between items-start">
|
| 342 |
<div>
|
| 343 |
-
<h3 className="font-headline text-xl font-bold uppercase">
|
| 344 |
-
<p className="font-mono text-[10px] text-slate-500 uppercase">
|
| 345 |
</div>
|
| 346 |
<div className="flex gap-2 items-center">
|
| 347 |
<ProviderToggle agent={agent} index={index} />
|
|
@@ -443,14 +449,15 @@ const SettingsView = () => {
|
|
| 443 |
</div>
|
| 444 |
);
|
| 445 |
})}
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
</
|
| 453 |
-
|
|
|
|
| 454 |
|
| 455 |
{/* Execution Environment */}
|
| 456 |
<div className="md:col-span-12 glass-panel rounded-xl p-8 refractive-edge">
|
|
@@ -593,7 +600,7 @@ const SettingsView = () => {
|
|
| 593 |
<div className="flex items-center gap-4">
|
| 594 |
<button
|
| 595 |
onClick={() => {
|
| 596 |
-
setAgents([{ id: '
|
| 597 |
setMaxSteps(12);
|
| 598 |
}}
|
| 599 |
className="px-8 py-3 bg-surface-container-high text-on-surface-variant font-headline font-bold text-sm tracking-widest rounded hover:bg-surface-container-highest hover:text-white transition-all uppercase"
|
|
|
|
| 279 |
};
|
| 280 |
|
| 281 |
const addAgent = () => {
|
| 282 |
+
const newId = `agent_${Date.now()}`;
|
| 283 |
+
const roles = ['INVESTIGATOR', 'VALIDATOR', 'FORENSIC_ANALYST', 'NETWORK_ENGINEER', 'SYSTEM_ADMIN', 'SECURITY_ARCHITECT', 'COMPLIANCE_OFFICER'];
|
| 284 |
+
const role = roles[agents.length % roles.length];
|
| 285 |
setAgents(prev => [...prev, {
|
| 286 |
+
id: newId, provider: 'hf', model: '', hfModel: 'meta-llama/Llama-3.2-1B-Instruct', openaiModel: 'gpt-4o-mini', temp: Math.max(0.3, 0.7 - agents.length * 0.05), role, customRoleName: '', customPrompt: ''
|
| 287 |
}]);
|
| 288 |
};
|
| 289 |
|
| 290 |
const removeAgent = (index) => {
|
| 291 |
if (agents.length <= 1) return;
|
| 292 |
+
setAgents(prev => prev.filter((_, i) => i !== index));
|
| 293 |
};
|
| 294 |
|
| 295 |
const ProviderToggle = ({ agent, index }) => (
|
|
|
|
| 323 |
</div>
|
| 324 |
</section>
|
| 325 |
|
| 326 |
+
<div className="md:col-span-12">
|
| 327 |
+
<div className="flex items-center gap-3 mb-4">
|
| 328 |
+
<span className="font-mono text-[10px] tracking-widest text-primary uppercase">Active_Agent_Nodes</span>
|
| 329 |
+
<div className="flex-1 h-px bg-primary/10"></div>
|
| 330 |
+
</div>
|
| 331 |
+
<div className="grid grid-cols-1 md:grid-cols-12 gap-6 items-stretch max-h-[1200px] overflow-y-auto pr-2 custom-scrollbar p-1">
|
| 332 |
+
{/* N-Agents Render */}
|
| 333 |
+
{agents.map((agent, index) => {
|
| 334 |
const isPrimary = index % 2 === 0;
|
| 335 |
const accentColor = isPrimary ? 'primary' : 'secondary';
|
| 336 |
const titleColor = isPrimary ? 'text-primary' : 'text-secondary';
|
|
|
|
| 346 |
<div className="flex-1">
|
| 347 |
<div className="flex justify-between items-start">
|
| 348 |
<div>
|
| 349 |
+
<h3 className="font-headline text-xl font-bold uppercase">{agent.role.replace(/_/g, ' ')} <span className={`${titleColor} text-sm ml-2 tracking-tighter`}>[{agent.id.toUpperCase()}]</span></h3>
|
| 350 |
+
<p className="font-mono text-[10px] text-slate-500 uppercase">Node ID: {agent.id}</p>
|
| 351 |
</div>
|
| 352 |
<div className="flex gap-2 items-center">
|
| 353 |
<ProviderToggle agent={agent} index={index} />
|
|
|
|
| 449 |
</div>
|
| 450 |
);
|
| 451 |
})}
|
| 452 |
+
</div>
|
| 453 |
+
</div>
|
| 454 |
+
|
| 455 |
+
<div className="md:col-span-12 flex justify-center mt-4">
|
| 456 |
+
<button onClick={addAgent} className="flex items-center gap-2 px-8 py-3 rounded-xl border border-dashed border-outline-variant/30 text-outline-variant font-mono text-xs uppercase hover:bg-surface-container-highest hover:text-white transition-all bg-white/5">
|
| 457 |
+
<span className="material-symbols-outlined text-[16px]">add</span>
|
| 458 |
+
<span>Add Agent Node</span>
|
| 459 |
+
</button>
|
| 460 |
+
</div>
|
| 461 |
|
| 462 |
{/* Execution Environment */}
|
| 463 |
<div className="md:col-span-12 glass-panel rounded-xl p-8 refractive-edge">
|
|
|
|
| 600 |
<div className="flex items-center gap-4">
|
| 601 |
<button
|
| 602 |
onClick={() => {
|
| 603 |
+
setAgents([{ id: 'agent_default', provider: 'ollama', model: '', temp: 0.7, role: 'INVESTIGATOR', customRoleName: '', customPrompt: '' }]);
|
| 604 |
setMaxSteps(12);
|
| 605 |
}}
|
| 606 |
className="px-8 py-3 bg-surface-container-high text-on-surface-variant font-headline font-bold text-sm tracking-widest rounded hover:bg-surface-container-highest hover:text-white transition-all uppercase"
|