brand renaming, scenario reloads flight

This commit is contained in:
2026-04-12 08:39:12 -03:00
parent 9dbf89da02
commit 5c82703ebe
25 changed files with 433 additions and 482 deletions

View File

@@ -1,14 +1,14 @@
{
"mcpServers": {
"united-ops-shared": {
"stellar-ops-shared": {
"command": "uv",
"args": ["run", "python", "-m", "mcp_servers.shared"]
},
"united-ops-internal": {
"stellar-ops-internal": {
"command": "uv",
"args": ["run", "python", "-m", "mcp_servers.ops"]
},
"united-ops-passenger": {
"stellar-ops-passenger": {
"command": "uv",
"args": ["run", "python", "-m", "mcp_servers.passenger"]
}

View File

@@ -14,12 +14,12 @@ from typing import Any
from agents.shared.mcp_client import MCPMultiClient
async def run_efhas(
async def run_fce(
flight_id: str,
mcp: MCPMultiClient,
on_event: Any = None,
) -> dict:
"""Run the EFHaS agent for a single flight.
"""Run the FCE agent for a single flight.
Args:
flight_id: The flight to generate a notification for.

View File

@@ -29,7 +29,7 @@ SERVERS = {
# Agent profiles — which servers each agent connects to
AGENT_PROFILES = {
"efhas": ["shared", "ops", "passenger"],
"fce": ["shared", "ops", "passenger"],
"handover": ["shared", "ops"],
}

View File

@@ -10,7 +10,7 @@ from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from agents.efhas import run_efhas
from agents.fce import run_fce
from agents.handover import run_handover
from agents.shared.mcp_client import connect_servers
from mcp_servers.data.scenarios.manager import scenario_manager
@@ -66,7 +66,7 @@ app.add_middleware(
# ── Request/Response models ──
class EFHaSRequest(BaseModel):
class FCERequest(BaseModel):
flight_id: str
class HandoverRequest(BaseModel):
@@ -78,15 +78,15 @@ class ScenarioUpdate(BaseModel):
# ── Agent routes ──
@app.post("/agents/efhas")
async def trigger_efhas(req: EFHaSRequest):
@app.post("/agents/fce")
async def trigger_fce(req: FCERequest):
run_id = str(uuid.uuid4())[:8]
runs[run_id] = {"status": "running", "agent": "efhas", "flight_id": req.flight_id}
runs[run_id] = {"status": "running", "agent": "fce", "flight_id": req.flight_id}
async def _run():
await event_hub.broadcast({
"type": "agent_start", "run_id": run_id,
"agent": "efhas", "flight_id": req.flight_id,
"agent": "fce", "flight_id": req.flight_id,
"timestamp": datetime.now(timezone.utc).isoformat(),
})
@@ -95,10 +95,10 @@ async def trigger_efhas(req: EFHaSRequest):
try:
async with connect_servers(["shared", "ops", "passenger"]) as mcp:
result = await run_efhas(req.flight_id, mcp, on_event=on_event)
runs[run_id] = {"status": "completed", "agent": "efhas", "result": result}
result = await run_fce(req.flight_id, mcp, on_event=on_event)
runs[run_id] = {"status": "completed", "agent": "fce", "result": result}
except Exception as e:
runs[run_id] = {"status": "error", "agent": "efhas", "error": str(e)}
runs[run_id] = {"status": "error", "agent": "fce", "error": str(e)}
asyncio.create_task(_run())
return {"run_id": run_id, "status": "running"}

View File

@@ -16,4 +16,5 @@ RUN pnpm install && pnpm run build
FROM nginx:alpine
COPY --from=build /ui/app/dist /usr/share/nginx/html
COPY docs/ /usr/share/nginx/docs/
COPY ctrl/nginx.conf /etc/nginx/conf.d/default.conf

View File

@@ -6,6 +6,10 @@ server {
try_files $uri $uri/ /index.html;
}
location /docs/ {
alias /usr/share/nginx/docs/;
}
location /agents {
proxy_pass http://api:8000;
}

View File

@@ -4,9 +4,9 @@
<!-- Generated by graphviz version 14.1.2 (0)
-->
<!-- Title: data_flow Pages: 1 -->
<svg width="661pt" height="685pt"
viewBox="0.00 0.00 661.00 685.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 681.25)">
<svg style="background:#0a0e17" width="1323pt" height="1371pt"
viewBox="0.00 0.00 1323.00 1371.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(2 2) rotate(0) translate(4 681.25)">
<title>data_flow</title>
<polygon fill="#0a0e17" stroke="none" points="-4,4 -4,-681.25 657.38,-681.25 657.38,4 -4,4"/>
<text xml:space="preserve" text-anchor="middle" x="326.69" y="-659.95" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#0066ff">Data Flow — Real vs Mock</text>

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -4,9 +4,9 @@
<!-- Generated by graphviz version 14.1.2 (0)
-->
<!-- Title: deployment Pages: 1 -->
<svg width="748pt" height="1083pt"
viewBox="0.00 0.00 748.00 1083.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1079.34)">
<svg style="background:#0a0e17" width="1496pt" height="2167pt"
viewBox="0.00 0.00 1496.00 2167.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(2 2) rotate(0) translate(4 1079.34)">
<title>deployment</title>
<polygon fill="#0a0e17" stroke="none" points="-4,4 -4,-1079.34 744,-1079.34 744,4 -4,4"/>
<text xml:space="preserve" text-anchor="middle" x="370" y="-1058.04" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#0066ff">Deployment — Kind Cluster (dev) / EC2 (prod)</text>

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -4,9 +4,9 @@
<!-- Generated by graphviz version 14.1.2 (0)
-->
<!-- Title: efhas_agent Pages: 1 -->
<svg width="696pt" height="932pt"
viewBox="0.00 0.00 696.00 932.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 927.94)">
<svg style="background:#0a0e17" width="1392pt" height="1864pt"
viewBox="0.00 0.00 1392.00 1864.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(2 2) rotate(0) translate(4 927.94)">
<title>efhas_agent</title>
<polygon fill="#0a0e17" stroke="none" points="-4,4 -4,-927.94 691.94,-927.94 691.94,4 -4,4"/>
<text xml:space="preserve" text-anchor="middle" x="343.97" y="-906.64" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#0066ff">FCE Agent — Behind Every Departure</text>

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -4,9 +4,9 @@
<!-- Generated by graphviz version 14.1.2 (0)
-->
<!-- Title: handover_agent Pages: 1 -->
<svg width="851pt" height="776pt"
viewBox="0.00 0.00 851.00 776.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 771.79)">
<svg style="background:#0a0e17" width="1702pt" height="1552pt"
viewBox="0.00 0.00 1702.00 1552.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(2 2) rotate(0) translate(4 771.79)">
<title>handover_agent</title>
<polygon fill="#0a0e17" stroke="none" points="-4,4 -4,-771.79 847,-771.79 847,4 -4,4"/>
<text xml:space="preserve" text-anchor="middle" x="421.5" y="-750.49" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#0066ff">Shift Handover Agent</text>

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -5,7 +5,7 @@ digraph mcp_servers {
node [fontname="Helvetica" fontsize=10 style=filled color="#1e2a4a" fontcolor="#e8eaf0"]
edge [fontname="Helvetica" fontsize=9 fontcolor="#8892a8" color="#4a5568"]
label="MCP Server Topology — Tools · Resources · Prompts"
label="MCP Server Topology"
labelloc=t
fontsize=14
fontcolor="#0066ff"
@@ -16,129 +16,59 @@ digraph mcp_servers {
style=dashed
color="#1e2a4a"
fontcolor="#8892a8"
efhas [label="FCE\nAgent" fillcolor="#1a1a3a" shape=box]
handover [label="Handover\nAgent" fillcolor="#1a1a3a" shape=box]
fce [label="FCE Agent\n(passenger)" fillcolor="#1a1a3a" shape=box]
handover [label="Handover Agent\n(ops)" fillcolor="#1a1a3a" shape=box]
}
// Shared server
subgraph cluster_shared {
label="united-ops-shared"
label="stellar-ops-shared"
color="#0066ff"
fontcolor="#0066ff"
style=rounded
subgraph cluster_shared_tools {
label="Tools"
color="#1e2a4a"
fontcolor="#4a5568"
st1 [label="get_flight_status" fillcolor="#0d1a33" shape=box]
st2 [label="get_flight_details" fillcolor="#0d1a33" shape=box]
st3 [label="get_irregular_ops" fillcolor="#0d1a33" shape=box]
st4 [label="get_route_weather\n★ LIVE" fillcolor="#0d2a0d" shape=box fontcolor="#00c853"]
st5 [label="get_hub_forecasts\n★ LIVE" fillcolor="#0d2a0d" shape=box fontcolor="#00c853"]
st6 [label="get_airport_status\n★ LIVE" fillcolor="#0d2a0d" shape=box fontcolor="#00c853"]
st7 [label="get_airport_congestion\n★ HYBRID" fillcolor="#0d2a0d" shape=box fontcolor="#ffc107"]
st8 [label="get_maintenance_flags" fillcolor="#0d1a33" shape=box]
}
subgraph cluster_shared_res {
label="Resources"
color="#1e2a4a"
fontcolor="#4a5568"
sr1 [label="ops://hubs/{code}" fillcolor="#1a1a2a" shape=note]
sr2 [label="ops://scenarios/active" fillcolor="#1a1a2a" shape=note]
}
subgraph cluster_shared_prompts {
label="Prompts"
color="#1e2a4a"
fontcolor="#4a5568"
sp1 [label="delay_explainer\n(cause_code, audience)" fillcolor="#2a1a2a" shape=cds]
}
st [label="Tools\nget_flight_status\nget_flight_details\nget_irregular_ops\nget_route_weather ★\nget_hub_forecasts ★\nget_airport_status ★\nget_airport_congestion\nget_maintenance_flags" fillcolor="#0d1a33" shape=box fontsize=9]
sr [label="Resources\nops://hubs/\u007Bcode\u007D\nops://scenarios/active" fillcolor="#1a1a2a" shape=note fontsize=9]
sp [label="Prompts\ndelay_explainer" fillcolor="#2a1a2a" shape=cds fontsize=9]
}
// Ops server
subgraph cluster_ops {
label="united-ops-internal"
label="stellar-ops-internal"
color="#ff3d00"
fontcolor="#ff3d00"
style=rounded
subgraph cluster_ops_tools {
label="Tools"
color="#1e2a4a"
fontcolor="#4a5568"
ot1 [label="get_crew_notes" fillcolor="#0d1a33" shape=box]
ot2 [label="get_crew_duty_status" fillcolor="#0d1a33" shape=box]
ot3 [label="get_pending_rebookings" fillcolor="#0d1a33" shape=box]
ot4 [label="generate_narrative" fillcolor="#0d1a33" shape=box]
}
subgraph cluster_ops_res {
label="Resources"
color="#1e2a4a"
fontcolor="#4a5568"
or1 [label="ops://crew/roster" fillcolor="#1a1a2a" shape=note]
or2 [label="ops://handover/latest" fillcolor="#1a1a2a" shape=note]
}
subgraph cluster_ops_prompts {
label="Prompts"
color="#1e2a4a"
fontcolor="#4a5568"
op1 [label="handover_brief\n(hub, shift_time)" fillcolor="#2a1a2a" shape=cds]
}
ot [label="Tools\nget_crew_notes\nget_crew_duty_status\nget_pending_rebookings\ngenerate_narrative" fillcolor="#0d1a33" shape=box fontsize=9]
or [label="Resources\nops://crew/roster\nops://handover/latest" fillcolor="#1a1a2a" shape=note fontsize=9]
op [label="Prompts\nhandover_brief" fillcolor="#2a1a2a" shape=cds fontsize=9]
}
// Passenger server
subgraph cluster_pax {
label="united-ops-passenger"
label="stellar-ops-passenger"
color="#00c853"
fontcolor="#00c853"
style=rounded
subgraph cluster_pax_tools {
label="Tools"
color="#1e2a4a"
fontcolor="#4a5568"
pt1 [label="generate_notification" fillcolor="#0d1a33" shape=box]
}
subgraph cluster_pax_res {
label="Resources"
color="#1e2a4a"
fontcolor="#4a5568"
pr1 [label="ops://flights/{id}/manifest" fillcolor="#1a1a2a" shape=note]
}
subgraph cluster_pax_prompts {
label="Prompts"
color="#1e2a4a"
fontcolor="#4a5568"
pp1 [label="passenger_notification\n(tone)" fillcolor="#2a1a2a" shape=cds]
}
pt [label="Tools\ngenerate_notification" fillcolor="#0d1a33" shape=box fontsize=9]
pr [label="Resources\nops://flights/\u007Bid\u007D/manifest" fillcolor="#1a1a2a" shape=note fontsize=9]
pp [label="Prompts\npassenger_notification" fillcolor="#2a1a2a" shape=cds fontsize=9]
}
// Connections
efhas -> st1 [color="#0066ff"]
efhas -> st2 [color="#0066ff"]
efhas -> st4 [color="#0066ff"]
efhas -> st6 [color="#0066ff"]
efhas -> st7 [color="#0066ff"]
efhas -> st8 [color="#0066ff"]
efhas -> pt1 [color="#00c853"]
efhas -> sr1 [color="#0066ff" style=dashed]
efhas -> pp1 [color="#00c853" style=dotted]
// Tool calls (solid)
fce -> st [color="#0066ff"]
fce -> pt [color="#00c853"]
handover -> st [color="#0066ff"]
handover -> ot [color="#ff3d00"]
handover -> st3 [color="#0066ff"]
handover -> st5 [color="#0066ff"]
handover -> st6 [color="#0066ff"]
handover -> st8 [color="#0066ff"]
handover -> ot1 [color="#ff3d00"]
handover -> ot2 [color="#ff3d00"]
handover -> ot3 [color="#ff3d00"]
handover -> ot4 [color="#ff3d00"]
handover -> or1 [color="#ff3d00" style=dashed]
handover -> or2 [color="#ff3d00" style=dashed]
handover -> op1 [color="#ff3d00" style=dotted]
// Resource reads (dashed)
fce -> sr [color="#0066ff" style=dashed]
fce -> pr [color="#00c853" style=dashed]
handover -> or [color="#ff3d00" style=dashed]
// Prompt gets (dotted)
fce -> pp [color="#00c853" style=dotted]
handover -> op [color="#ff3d00" style=dotted]
handover -> sp [color="#0066ff" style=dotted]
}

View File

@@ -4,353 +4,186 @@
<!-- Generated by graphviz version 14.1.2 (0)
-->
<!-- Title: mcp_servers Pages: 1 -->
<svg width="381pt" height="1577pt"
viewBox="0.00 0.00 381.00 1577.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1573.25)">
<svg style="background:#0a0e17" width="708pt" height="1469pt"
viewBox="0.00 0.00 708.00 1469.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(2 2) rotate(0) translate(4 730.25)">
<title>mcp_servers</title>
<polygon fill="#0a0e17" stroke="none" points="-4,4 -4,-1573.25 377,-1573.25 377,4 -4,4"/>
<text xml:space="preserve" text-anchor="middle" x="186.5" y="-1551.95" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#0066ff">MCP Server Topology — Tools · Resources · Prompts</text>
<polygon fill="#0a0e17" stroke="none" points="-4,4 -4,-730.25 350,-730.25 350,4 -4,4"/>
<text xml:space="preserve" text-anchor="middle" x="173" y="-708.95" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#0066ff">MCP Server Topology</text>
<g id="clust1" class="cluster">
<title>cluster_clients</title>
<polygon fill="#0a0e17" stroke="#1e2a4a" stroke-dasharray="5,2" points="34.62,-851 34.62,-982 143.62,-982 143.62,-851 34.62,-851"/>
<text xml:space="preserve" text-anchor="middle" x="89.12" y="-964.7" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#8892a8">Agent Clients</text>
<polygon fill="#0a0e17" stroke="#1e2a4a" stroke-dasharray="5,2" points="8,-262 8,-393 118.75,-393 118.75,-262 8,-262"/>
<text xml:space="preserve" text-anchor="middle" x="63.38" y="-375.7" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#8892a8">Agent Clients</text>
</g>
<g id="clust2" class="cluster">
<title>cluster_shared</title>
<path fill="#0a0e17" stroke="#0066ff" d="M173.62,-816C173.62,-816 312.38,-816 312.38,-816 318.38,-816 324.38,-822 324.38,-828 324.38,-828 324.38,-1524 324.38,-1524 324.38,-1530 318.38,-1536 312.38,-1536 312.38,-1536 173.62,-1536 173.62,-1536 167.62,-1536 161.62,-1530 161.62,-1524 161.62,-1524 161.62,-828 161.62,-828 161.62,-822 167.62,-816 173.62,-816"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-1518.7" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#0066ff">united&#45;ops&#45;shared</text>
<path fill="#0a0e17" stroke="#0066ff" d="M168,-428C168,-428 308.75,-428 308.75,-428 314.75,-428 320.75,-434 320.75,-440 320.75,-440 320.75,-681 320.75,-681 320.75,-687 314.75,-693 308.75,-693 308.75,-693 168,-693 168,-693 162,-693 156,-687 156,-681 156,-681 156,-440 156,-440 156,-434 162,-428 168,-428"/>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-675.7" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#0066ff">stellar&#45;ops&#45;shared</text>
</g>
<g id="clust3" class="cluster">
<title>cluster_shared_tools</title>
<path fill="#0a0e17" stroke="#1e2a4a" d="M182.38,-824C182.38,-824 303.62,-824 303.62,-824 309.62,-824 315.62,-830 315.62,-836 315.62,-836 315.62,-1267 315.62,-1267 315.62,-1273 309.62,-1279 303.62,-1279 303.62,-1279 182.38,-1279 182.38,-1279 176.38,-1279 170.38,-1273 170.38,-1267 170.38,-1267 170.38,-836 170.38,-836 170.38,-830 176.38,-824 182.38,-824"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-1261.7" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#4a5568">Tools</text>
<title>cluster_ops</title>
<path fill="#0a0e17" stroke="#ff3d00" d="M178.38,-8C178.38,-8 299.38,-8 299.38,-8 305.38,-8 311.38,-14 311.38,-20 311.38,-20 311.38,-215 311.38,-215 311.38,-221 305.38,-227 299.38,-227 299.38,-227 178.38,-227 178.38,-227 172.38,-227 166.38,-221 166.38,-215 166.38,-215 166.38,-20 166.38,-20 166.38,-14 172.38,-8 178.38,-8"/>
<text xml:space="preserve" text-anchor="middle" x="238.88" y="-209.7" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#ff3d00">stellar&#45;ops&#45;internal</text>
</g>
<g id="clust4" class="cluster">
<title>cluster_shared_res</title>
<path fill="#0a0e17" stroke="#1e2a4a" d="M186.12,-1287C186.12,-1287 299.88,-1287 299.88,-1287 305.88,-1287 311.88,-1293 311.88,-1299 311.88,-1299 311.88,-1406 311.88,-1406 311.88,-1412 305.88,-1418 299.88,-1418 299.88,-1418 186.12,-1418 186.12,-1418 180.12,-1418 174.12,-1412 174.12,-1406 174.12,-1406 174.12,-1299 174.12,-1299 174.12,-1293 180.12,-1287 186.12,-1287"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-1400.7" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#4a5568">Resources</text>
</g>
<g id="clust5" class="cluster">
<title>cluster_shared_prompts</title>
<path fill="#0a0e17" stroke="#1e2a4a" d="M181.62,-1426C181.62,-1426 304.38,-1426 304.38,-1426 310.38,-1426 316.38,-1432 316.38,-1438 316.38,-1438 316.38,-1491 316.38,-1491 316.38,-1497 310.38,-1503 304.38,-1503 304.38,-1503 181.62,-1503 181.62,-1503 175.62,-1503 169.62,-1497 169.62,-1491 169.62,-1491 169.62,-1438 169.62,-1438 169.62,-1432 175.62,-1426 181.62,-1426"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-1485.7" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#4a5568">Prompts</text>
</g>
<g id="clust6" class="cluster">
<title>cluster_ops</title>
<path fill="#0a0e17" stroke="#ff3d00" d="M171.38,-8C171.38,-8 314.62,-8 314.62,-8 320.62,-8 326.62,-14 326.62,-20 326.62,-20 326.62,-500 326.62,-500 326.62,-506 320.62,-512 314.62,-512 314.62,-512 171.38,-512 171.38,-512 165.38,-512 159.38,-506 159.38,-500 159.38,-500 159.38,-20 159.38,-20 159.38,-14 165.38,-8 171.38,-8"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-494.7" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#ff3d00">united&#45;ops&#45;internal</text>
</g>
<g id="clust7" class="cluster">
<title>cluster_ops_tools</title>
<path fill="#0a0e17" stroke="#1e2a4a" d="M179.38,-16C179.38,-16 306.62,-16 306.62,-16 312.62,-16 318.62,-22 318.62,-28 318.62,-28 318.62,-243 318.62,-243 318.62,-249 312.62,-255 306.62,-255 306.62,-255 179.38,-255 179.38,-255 173.38,-255 167.38,-249 167.38,-243 167.38,-243 167.38,-28 167.38,-28 167.38,-22 173.38,-16 179.38,-16"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-237.7" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#4a5568">Tools</text>
</g>
<g id="clust8" class="cluster">
<title>cluster_ops_res</title>
<path fill="#0a0e17" stroke="#1e2a4a" d="M187.62,-263C187.62,-263 298.38,-263 298.38,-263 304.38,-263 310.38,-269 310.38,-275 310.38,-275 310.38,-382 310.38,-382 310.38,-388 304.38,-394 298.38,-394 298.38,-394 187.62,-394 187.62,-394 181.62,-394 175.62,-388 175.62,-382 175.62,-382 175.62,-275 175.62,-275 175.62,-269 181.62,-263 187.62,-263"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-376.7" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#4a5568">Resources</text>
</g>
<g id="clust9" class="cluster">
<title>cluster_ops_prompts</title>
<path fill="#0a0e17" stroke="#1e2a4a" d="M198.88,-402C198.88,-402 287.12,-402 287.12,-402 293.12,-402 299.12,-408 299.12,-414 299.12,-414 299.12,-467 299.12,-467 299.12,-473 293.12,-479 287.12,-479 287.12,-479 198.88,-479 198.88,-479 192.88,-479 186.88,-473 186.88,-467 186.88,-467 186.88,-414 186.88,-414 186.88,-408 192.88,-402 198.88,-402"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-461.7" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#4a5568">Prompts</text>
</g>
<g id="clust10" class="cluster">
<title>cluster_pax</title>
<path fill="#0a0e17" stroke="#00c853" d="M167.62,-520C167.62,-520 318.38,-520 318.38,-520 324.38,-520 330.38,-526 330.38,-532 330.38,-532 330.38,-796 330.38,-796 330.38,-802 324.38,-808 318.38,-808 318.38,-808 167.62,-808 167.62,-808 161.62,-808 155.62,-802 155.62,-796 155.62,-796 155.62,-532 155.62,-532 155.62,-526 161.62,-520 167.62,-520"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-790.7" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#00c853">united&#45;ops&#45;passenger</text>
<path fill="#0a0e17" stroke="#00c853" d="M150.75,-235C150.75,-235 326,-235 326,-235 332,-235 338,-241 338,-247 338,-247 338,-408 338,-408 338,-414 332,-420 326,-420 326,-420 150.75,-420 150.75,-420 144.75,-420 138.75,-414 138.75,-408 138.75,-408 138.75,-247 138.75,-247 138.75,-241 144.75,-235 150.75,-235"/>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-402.7" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#00c853">stellar&#45;ops&#45;passenger</text>
</g>
<g id="clust11" class="cluster">
<title>cluster_pax_tools</title>
<path fill="#0a0e17" stroke="#1e2a4a" d="M187.25,-528C187.25,-528 298.75,-528 298.75,-528 304.75,-528 310.75,-534 310.75,-540 310.75,-540 310.75,-593 310.75,-593 310.75,-599 304.75,-605 298.75,-605 298.75,-605 187.25,-605 187.25,-605 181.25,-605 175.25,-599 175.25,-593 175.25,-593 175.25,-540 175.25,-540 175.25,-534 181.25,-528 187.25,-528"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-587.7" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#4a5568">Tools</text>
</g>
<g id="clust12" class="cluster">
<title>cluster_pax_res</title>
<path fill="#0a0e17" stroke="#1e2a4a" d="M175.62,-613C175.62,-613 310.38,-613 310.38,-613 316.38,-613 322.38,-619 322.38,-625 322.38,-625 322.38,-678 322.38,-678 322.38,-684 316.38,-690 310.38,-690 310.38,-690 175.62,-690 175.62,-690 169.62,-690 163.62,-684 163.62,-678 163.62,-678 163.62,-625 163.62,-625 163.62,-619 169.62,-613 175.62,-613"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-672.7" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#4a5568">Resources</text>
</g>
<g id="clust13" class="cluster">
<title>cluster_pax_prompts</title>
<path fill="#0a0e17" stroke="#1e2a4a" d="M183.88,-698C183.88,-698 302.12,-698 302.12,-698 308.12,-698 314.12,-704 314.12,-710 314.12,-710 314.12,-763 314.12,-763 314.12,-769 308.12,-775 302.12,-775 302.12,-775 183.88,-775 183.88,-775 177.88,-775 171.88,-769 171.88,-763 171.88,-763 171.88,-710 171.88,-710 171.88,-704 177.88,-698 183.88,-698"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-757.7" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#4a5568">Prompts</text>
</g>
<!-- efhas -->
<!-- fce -->
<g id="node1" class="node">
<title>efhas</title>
<polygon fill="#1a1a3a" stroke="#1e2a4a" points="115.62,-949 61.62,-949 61.62,-913 115.62,-913 115.62,-949"/>
<text xml:space="preserve" text-anchor="middle" x="88.62" y="-934.25" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">FCE</text>
<text xml:space="preserve" text-anchor="middle" x="88.62" y="-921.5" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">Agent</text>
<title>fce</title>
<polygon fill="#1a1a3a" stroke="#1e2a4a" points="100.25,-360 26.5,-360 26.5,-324 100.25,-324 100.25,-360"/>
<text xml:space="preserve" text-anchor="middle" x="63.38" y="-345.25" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">FCE Agent</text>
<text xml:space="preserve" text-anchor="middle" x="63.38" y="-332.5" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">(passenger)</text>
</g>
<!-- st1 -->
<!-- st -->
<g id="node3" class="node">
<title>st1</title>
<polygon fill="#0d1a33" stroke="#1e2a4a" points="291.5,-1192 194.5,-1192 194.5,-1156 291.5,-1156 291.5,-1192"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-1170.88" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">get_flight_status</text>
<title>st</title>
<polygon fill="#0d1a33" stroke="#1e2a4a" points="298.88,-599.62 177.88,-599.62 177.88,-490.38 298.88,-490.38 298.88,-599.62"/>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-587.08" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">Tools</text>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-575.83" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">get_flight_status</text>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-564.58" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">get_flight_details</text>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-553.33" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">get_irregular_ops</text>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-542.08" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">get_route_weather ★</text>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-530.83" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">get_hub_forecasts ★</text>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-519.58" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">get_airport_status ★</text>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-508.32" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">get_airport_congestion</text>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-497.07" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">get_maintenance_flags</text>
</g>
<!-- efhas&#45;&gt;st1 -->
<!-- fce&#45;&gt;st -->
<g id="edge1" class="edge">
<title>efhas&#45;&gt;st1</title>
<path fill="none" stroke="#0066ff" d="M89.78,-949.5C91.16,-990.83 100.35,-1091.37 155.62,-1147 163.27,-1154.69 173.07,-1160.2 183.31,-1164.14"/>
<polygon fill="#0066ff" stroke="#0066ff" points="182.04,-1167.41 192.63,-1167.23 184.24,-1160.76 182.04,-1167.41"/>
<title>fce&#45;&gt;st</title>
<path fill="none" stroke="#0066ff" d="M70.8,-360.47C81.42,-388.94 104.86,-444.12 138.75,-481 147.35,-490.36 157.6,-498.95 168.19,-506.59"/>
<polygon fill="#0066ff" stroke="#0066ff" points="166.09,-509.4 176.3,-512.2 170.07,-503.64 166.09,-509.4"/>
</g>
<!-- st2 -->
<!-- sr -->
<g id="node4" class="node">
<title>st2</title>
<polygon fill="#0d1a33" stroke="#1e2a4a" points="293,-1246 193,-1246 193,-1210 293,-1210 293,-1246"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-1224.88" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">get_flight_details</text>
<title>sr</title>
<polygon fill="#1a1a2a" stroke="#1e2a4a" points="306.75,-659.88 164,-659.88 164,-618.12 312.75,-618.12 312.75,-653.88 306.75,-659.88"/>
<polyline fill="none" stroke="#1e2a4a" points="306.75,-659.88 306.75,-653.88"/>
<polyline fill="none" stroke="#1e2a4a" points="312.75,-653.88 306.75,-653.88"/>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-647.33" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">Resources</text>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-636.08" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">ops://hubs/u007Bcodeu007D</text>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-624.83" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">ops://scenarios/active</text>
</g>
<!-- efhas&#45;&gt;st2 -->
<g id="edge2" class="edge">
<title>efhas&#45;&gt;st2</title>
<path fill="none" stroke="#0066ff" d="M91.75,-949.27C98.43,-1003.77 120.7,-1163.79 155.62,-1201 162.81,-1208.66 172.13,-1214.15 181.98,-1218.1"/>
<polygon fill="#0066ff" stroke="#0066ff" points="180.78,-1221.39 191.37,-1221.31 183.04,-1214.76 180.78,-1221.39"/>
</g>
<!-- st4 -->
<g id="node6" class="node">
<title>st4</title>
<polygon fill="#0d2a0d" stroke="#1e2a4a" points="296.75,-1084 189.25,-1084 189.25,-1048 296.75,-1048 296.75,-1084"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-1069.25" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#00c853">get_route_weather</text>
<text xml:space="preserve" text-anchor="middle" x="243" y="-1056.5" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#00c853">★ LIVE</text>
</g>
<!-- efhas&#45;&gt;st4 -->
<g id="edge3" class="edge">
<title>efhas&#45;&gt;st4</title>
<path fill="none" stroke="#0066ff" d="M96.16,-949.49C105.6,-973.56 125.31,-1015.34 155.62,-1039 162.34,-1044.24 170.09,-1048.47 178.13,-1051.89"/>
<polygon fill="#0066ff" stroke="#0066ff" points="176.8,-1055.13 187.39,-1055.39 179.28,-1048.58 176.8,-1055.13"/>
</g>
<!-- st6 -->
<g id="node8" class="node">
<title>st6</title>
<polygon fill="#0d2a0d" stroke="#1e2a4a" points="295.25,-976 190.75,-976 190.75,-940 295.25,-940 295.25,-976"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-961.25" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#00c853">get_airport_status</text>
<text xml:space="preserve" text-anchor="middle" x="243" y="-948.5" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#00c853">★ LIVE</text>
</g>
<!-- efhas&#45;&gt;st6 -->
<g id="edge4" class="edge">
<title>efhas&#45;&gt;st6</title>
<path fill="none" stroke="#0066ff" d="M115.82,-935.64C133.29,-938.74 157.04,-942.95 179.22,-946.88"/>
<polygon fill="#0066ff" stroke="#0066ff" points="178.35,-950.28 188.8,-948.57 179.57,-943.38 178.35,-950.28"/>
</g>
<!-- st7 -->
<g id="node9" class="node">
<title>st7</title>
<polygon fill="#0d2a0d" stroke="#1e2a4a" points="306.88,-1138 179.12,-1138 179.12,-1102 306.88,-1102 306.88,-1138"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-1123.25" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#ffc107">get_airport_congestion</text>
<text xml:space="preserve" text-anchor="middle" x="243" y="-1110.5" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#ffc107">★ HYBRID</text>
</g>
<!-- efhas&#45;&gt;st7 -->
<!-- fce&#45;&gt;sr -->
<g id="edge5" class="edge">
<title>efhas&#45;&gt;st7</title>
<path fill="none" stroke="#0066ff" d="M91.97,-949.44C97.2,-982.89 112.92,-1053.74 155.62,-1093 159.61,-1096.66 164.08,-1099.83 168.84,-1102.56"/>
<polygon fill="#0066ff" stroke="#0066ff" points="167.09,-1105.6 177.6,-1106.92 170.21,-1099.33 167.09,-1105.6"/>
<title>fce&#45;&gt;sr</title>
<path fill="none" stroke="#0066ff" stroke-dasharray="5,2" d="M63.85,-360.46C63.54,-408.41 69.49,-538.09 138.75,-609 143.13,-613.48 148.15,-617.3 153.56,-620.55"/>
<polygon fill="#0066ff" stroke="#0066ff" points="151.84,-623.6 162.32,-625.11 155.07,-617.39 151.84,-623.6"/>
</g>
<!-- st8 -->
<!-- pt -->
<g id="node9" class="node">
<title>pt</title>
<polygon fill="#0d1a33" stroke="#1e2a4a" points="294.38,-279 182.38,-279 182.38,-243 294.38,-243 294.38,-279"/>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-263.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">Tools</text>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-252.45" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">generate_notification</text>
</g>
<!-- fce&#45;&gt;pt -->
<g id="edge2" class="edge">
<title>fce&#45;&gt;pt</title>
<path fill="none" stroke="#00c853" d="M100.67,-326.99C107.03,-323.54 113.34,-319.53 118.75,-315 130.2,-305.41 126.43,-296.44 138.75,-288 148.46,-281.35 159.76,-276.34 171.17,-272.55"/>
<polygon fill="#00c853" stroke="#00c853" points="172.07,-275.94 180.65,-269.72 170.07,-269.23 172.07,-275.94"/>
</g>
<!-- pr -->
<g id="node10" class="node">
<title>st8</title>
<polygon fill="#0d1a33" stroke="#1e2a4a" points="307.62,-1030 178.38,-1030 178.38,-994 307.62,-994 307.62,-1030"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-1008.88" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">get_maintenance_flags</text>
<title>pr</title>
<polygon fill="#1a1a2a" stroke="#1e2a4a" points="324,-333 146.75,-333 146.75,-297 330,-297 330,-327 324,-333"/>
<polyline fill="none" stroke="#1e2a4a" points="324,-333 324,-327"/>
<polyline fill="none" stroke="#1e2a4a" points="330,-327 324,-327"/>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-317.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">Resources</text>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-306.45" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">ops://flights/u007Bidu007D/manifest</text>
</g>
<!-- efhas&#45;&gt;st8 -->
<!-- fce&#45;&gt;pr -->
<g id="edge6" class="edge">
<title>efhas&#45;&gt;st8</title>
<path fill="none" stroke="#0066ff" d="M108.13,-949.43C120.64,-961.02 138.03,-975.52 155.62,-985 159.48,-987.08 163.52,-989.03 167.67,-990.84"/>
<polygon fill="#0066ff" stroke="#0066ff" points="166.13,-994 176.71,-994.53 168.77,-987.51 166.13,-994"/>
<title>fce&#45;&gt;pr</title>
<path fill="none" stroke="#00c853" stroke-dasharray="5,2" d="M100.64,-336.34C110.96,-334.73 122.73,-332.89 134.95,-330.99"/>
<polygon fill="#00c853" stroke="#00c853" points="135.42,-334.45 144.76,-329.45 134.34,-327.54 135.42,-334.45"/>
</g>
<!-- sr1 -->
<!-- pp -->
<g id="node11" class="node">
<title>sr1</title>
<polygon fill="#1a1a2a" stroke="#1e2a4a" points="288.88,-1331 191.12,-1331 191.12,-1295 294.88,-1295 294.88,-1325 288.88,-1331"/>
<polyline fill="none" stroke="#1e2a4a" points="288.88,-1331 288.88,-1325"/>
<polyline fill="none" stroke="#1e2a4a" points="294.88,-1325 288.88,-1325"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-1309.88" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">ops://hubs/{code}</text>
<title>pp</title>
<polygon fill="#2a1a2a" stroke="#1e2a4a" points="285.38,-381 179.38,-381 179.38,-357 285.38,-357 297.38,-369 285.38,-381"/>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-371.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">Prompts</text>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-360.45" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">passenger_notification</text>
</g>
<!-- efhas&#45;&gt;sr1 -->
<!-- fce&#45;&gt;pp -->
<g id="edge8" class="edge">
<title>efhas&#45;&gt;sr1</title>
<path fill="none" stroke="#0066ff" stroke-dasharray="5,2" d="M90.69,-949.46C94.79,-1014.51 111.6,-1231.88 155.62,-1283 162.32,-1290.77 171.14,-1296.53 180.58,-1300.8"/>
<polygon fill="#0066ff" stroke="#0066ff" points="179.01,-1303.95 189.59,-1304.32 181.55,-1297.42 179.01,-1303.95"/>
</g>
<!-- pt1 -->
<g id="node21" class="node">
<title>pt1</title>
<polygon fill="#0d1a33" stroke="#1e2a4a" points="302.75,-572 183.25,-572 183.25,-536 302.75,-536 302.75,-572"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-550.88" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">generate_notification</text>
</g>
<!-- efhas&#45;&gt;pt1 -->
<g id="edge7" class="edge">
<title>efhas&#45;&gt;pt1</title>
<path fill="none" stroke="#00c853" d="M115.89,-924.23C126.32,-920.14 137.31,-913.77 143.62,-904 161.44,-876.45 139.26,-637.43 155.62,-609 162.83,-596.48 174.17,-586.36 186.29,-578.39"/>
<polygon fill="#00c853" stroke="#00c853" points="188.07,-581.4 194.81,-573.23 184.45,-575.41 188.07,-581.4"/>
</g>
<!-- pp1 -->
<g id="node23" class="node">
<title>pp1</title>
<polygon fill="#2a1a2a" stroke="#1e2a4a" points="294.12,-736 179.88,-736 179.88,-712 294.12,-712 306.12,-724 294.12,-736"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-727.25" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">passenger_notification</text>
<text xml:space="preserve" text-anchor="middle" x="243" y="-714.5" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">(tone)</text>
</g>
<!-- efhas&#45;&gt;pp1 -->
<g id="edge9" class="edge">
<title>efhas&#45;&gt;pp1</title>
<path fill="none" stroke="#00c853" stroke-dasharray="1,5" d="M115.98,-923.68C126.16,-919.54 136.94,-913.27 143.62,-904 165.68,-873.41 139.65,-854.16 155.62,-820 168.39,-792.7 191.56,-767.6 210.69,-749.92"/>
<polygon fill="#00c853" stroke="#00c853" points="212.91,-752.63 218.02,-743.35 208.24,-747.42 212.91,-752.63"/>
<title>fce&#45;&gt;pp</title>
<path fill="none" stroke="#00c853" stroke-dasharray="1,5" d="M100.64,-347.66C120.14,-350.7 144.85,-354.56 167.89,-358.15"/>
<polygon fill="#00c853" stroke="#00c853" points="167.11,-361.58 177.53,-359.66 168.19,-354.66 167.11,-361.58"/>
</g>
<!-- handover -->
<g id="node2" class="node">
<title>handover</title>
<polygon fill="#1a1a3a" stroke="#1e2a4a" points="120.25,-895 57,-895 57,-859 120.25,-859 120.25,-895"/>
<text xml:space="preserve" text-anchor="middle" x="88.62" y="-880.25" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">Handover</text>
<text xml:space="preserve" text-anchor="middle" x="88.62" y="-867.5" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">Agent</text>
<polygon fill="#1a1a3a" stroke="#1e2a4a" points="110.75,-306 16,-306 16,-270 110.75,-270 110.75,-306"/>
<text xml:space="preserve" text-anchor="middle" x="63.38" y="-291.25" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">Handover Agent</text>
<text xml:space="preserve" text-anchor="middle" x="63.38" y="-278.5" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">(ops)</text>
</g>
<!-- st3 -->
<!-- handover&#45;&gt;st -->
<g id="edge3" class="edge">
<title>handover&#45;&gt;st</title>
<path fill="none" stroke="#0066ff" d="M110.49,-306.27C113.62,-308.83 116.44,-311.73 118.75,-315 161.62,-375.7 99.43,-417.94 138.75,-481 146.16,-492.89 156.69,-503.01 168.13,-511.44"/>
<polygon fill="#0066ff" stroke="#0066ff" points="165.99,-514.22 176.22,-517 169.96,-508.45 165.99,-514.22"/>
</g>
<!-- sp -->
<g id="node5" class="node">
<title>st3</title>
<polygon fill="#0d1a33" stroke="#1e2a4a" points="293.38,-922 192.62,-922 192.62,-886 293.38,-886 293.38,-922"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-900.88" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">get_irregular_ops</text>
<title>sp</title>
<polygon fill="#2a1a2a" stroke="#1e2a4a" points="269.25,-466 195.5,-466 195.5,-442 269.25,-442 281.25,-454 269.25,-466"/>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-456.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">Prompts</text>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-445.45" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">delay_explainer</text>
</g>
<!-- handover&#45;&gt;st3 -->
<!-- handover&#45;&gt;sp -->
<g id="edge10" class="edge">
<title>handover&#45;&gt;st3</title>
<path fill="none" stroke="#0066ff" d="M120.46,-882.46C137.93,-885.56 160.4,-889.54 181.26,-893.24"/>
<polygon fill="#0066ff" stroke="#0066ff" points="180.37,-896.63 190.82,-894.93 181.59,-889.74 180.37,-896.63"/>
<title>handover&#45;&gt;sp</title>
<path fill="none" stroke="#0066ff" stroke-dasharray="1,5" d="M110.27,-306.43C113.45,-308.96 116.34,-311.8 118.75,-315 148.39,-354.34 105.96,-387.25 138.75,-424 150.38,-437.04 167.48,-444.49 184.19,-448.73"/>
<polygon fill="#0066ff" stroke="#0066ff" points="183.06,-452.07 193.57,-450.75 184.54,-445.22 183.06,-452.07"/>
</g>
<!-- st5 -->
<!-- ot -->
<g id="node6" class="node">
<title>ot</title>
<polygon fill="#0d1a33" stroke="#1e2a4a" points="301.88,-80.12 174.88,-80.12 174.88,-15.88 301.88,-15.88 301.88,-80.12"/>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-67.58" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">Tools</text>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-56.33" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">get_crew_notes</text>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-45.08" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">get_crew_duty_status</text>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-33.83" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">get_pending_rebookings</text>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-22.57" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">generate_narrative</text>
</g>
<!-- handover&#45;&gt;ot -->
<g id="edge4" class="edge">
<title>handover&#45;&gt;ot</title>
<path fill="none" stroke="#ff3d00" d="M66.33,-269.79C71.37,-231.41 88.19,-141.43 138.75,-89 145.98,-81.5 154.79,-75.32 164.13,-70.24"/>
<polygon fill="#ff3d00" stroke="#ff3d00" points="165.66,-73.39 173.07,-65.81 162.56,-67.11 165.66,-73.39"/>
</g>
<!-- or -->
<g id="node7" class="node">
<title>st5</title>
<polygon fill="#0d2a0d" stroke="#1e2a4a" points="295.62,-868 190.38,-868 190.38,-832 295.62,-832 295.62,-868"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-853.25" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#00c853">get_hub_forecasts</text>
<text xml:space="preserve" text-anchor="middle" x="243" y="-840.5" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#00c853">★ LIVE</text>
<title>or</title>
<polygon fill="#1a1a2a" stroke="#1e2a4a" points="288,-139.88 182.75,-139.88 182.75,-98.12 294,-98.12 294,-133.88 288,-139.88"/>
<polyline fill="none" stroke="#1e2a4a" points="288,-139.88 288,-133.88"/>
<polyline fill="none" stroke="#1e2a4a" points="294,-133.88 288,-133.88"/>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-127.33" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">Resources</text>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-116.08" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">ops://crew/roster</text>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-104.83" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">ops://handover/latest</text>
</g>
<!-- handover&#45;&gt;st5 -->
<g id="edge11" class="edge">
<title>handover&#45;&gt;st5</title>
<path fill="none" stroke="#0066ff" d="M120.46,-871.54C137.19,-868.57 158.49,-864.8 178.58,-861.24"/>
<polygon fill="#0066ff" stroke="#0066ff" points="179.18,-864.69 188.41,-859.5 177.96,-857.79 179.18,-864.69"/>
<!-- handover&#45;&gt;or -->
<g id="edge7" class="edge">
<title>handover&#45;&gt;or</title>
<path fill="none" stroke="#ff3d00" stroke-dasharray="5,2" d="M69.19,-269.75C77.97,-240.04 99.4,-181.3 138.75,-149 148.28,-141.17 159.83,-135.39 171.61,-131.11"/>
<polygon fill="#ff3d00" stroke="#ff3d00" points="172.58,-134.48 181.01,-128.06 170.42,-127.82 172.58,-134.48"/>
</g>
<!-- handover&#45;&gt;st6 -->
<g id="edge12" class="edge">
<title>handover&#45;&gt;st6</title>
<path fill="none" stroke="#0066ff" d="M120.64,-888.01C128.88,-892.06 137.25,-897.33 143.62,-904 152.7,-913.49 145.82,-922.26 155.62,-931 162.55,-937.17 170.87,-941.93 179.57,-945.6"/>
<polygon fill="#0066ff" stroke="#0066ff" points="178.3,-948.86 188.9,-949.03 180.72,-942.29 178.3,-948.86"/>
<!-- op -->
<g id="node8" class="node">
<title>op</title>
<polygon fill="#2a1a2a" stroke="#1e2a4a" points="268.12,-188 196.62,-188 196.62,-164 268.12,-164 280.12,-176 268.12,-188"/>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-178.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">Prompts</text>
<text xml:space="preserve" text-anchor="middle" x="238.38" y="-167.45" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e8eaf0">handover_brief</text>
</g>
<!-- handover&#45;&gt;st8 -->
<g id="edge13" class="edge">
<title>handover&#45;&gt;st8</title>
<path fill="none" stroke="#0066ff" d="M120.75,-886.43C129.4,-890.51 137.97,-896.19 143.62,-904 164.98,-933.47 131.97,-957.34 155.62,-985 159.2,-989.18 163.42,-992.73 168.03,-995.73"/>
<polygon fill="#0066ff" stroke="#0066ff" points="166.19,-998.71 176.65,-1000.43 169.55,-992.57 166.19,-998.71"/>
</g>
<!-- ot1 -->
<g id="node14" class="node">
<title>ot1</title>
<polygon fill="#0d1a33" stroke="#1e2a4a" points="289.25,-168 196.75,-168 196.75,-132 289.25,-132 289.25,-168"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-146.88" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">get_crew_notes</text>
</g>
<!-- handover&#45;&gt;ot1 -->
<g id="edge14" class="edge">
<title>handover&#45;&gt;ot1</title>
<path fill="none" stroke="#ff3d00" d="M90.15,-858.84C93.34,-754.24 111.39,-231.6 155.62,-177 163.19,-167.66 173.96,-161.49 185.33,-157.43"/>
<polygon fill="#ff3d00" stroke="#ff3d00" points="186.3,-160.79 194.89,-154.58 184.3,-154.08 186.3,-160.79"/>
</g>
<!-- ot2 -->
<g id="node15" class="node">
<title>ot2</title>
<polygon fill="#0d1a33" stroke="#1e2a4a" points="304.25,-222 181.75,-222 181.75,-186 304.25,-186 304.25,-222"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-200.88" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">get_crew_duty_status</text>
</g>
<!-- handover&#45;&gt;ot2 -->
<g id="edge15" class="edge">
<title>handover&#45;&gt;ot2</title>
<path fill="none" stroke="#ff3d00" d="M89.49,-858.71C89.11,-767.6 91.15,-362.97 155.62,-259 163.46,-246.36 175.41,-236.11 187.93,-228.03"/>
<polygon fill="#ff3d00" stroke="#ff3d00" points="189.48,-231.18 196.27,-223.05 185.89,-225.17 189.48,-231.18"/>
</g>
<!-- ot3 -->
<g id="node16" class="node">
<title>ot3</title>
<polygon fill="#0d1a33" stroke="#1e2a4a" points="310.62,-60 175.38,-60 175.38,-24 310.62,-24 310.62,-60"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-38.88" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">get_pending_rebookings</text>
</g>
<!-- handover&#45;&gt;ot3 -->
<g id="edge16" class="edge">
<title>handover&#45;&gt;ot3</title>
<path fill="none" stroke="#ff3d00" d="M89.91,-858.71C91.86,-744.55 104.63,-132.75 155.62,-69 158.47,-65.44 161.78,-62.35 165.41,-59.65"/>
<polygon fill="#ff3d00" stroke="#ff3d00" points="167.04,-62.76 173.72,-54.53 163.37,-56.79 167.04,-62.76"/>
</g>
<!-- ot4 -->
<g id="node17" class="node">
<title>ot4</title>
<polygon fill="#0d1a33" stroke="#1e2a4a" points="297.5,-114 188.5,-114 188.5,-78 297.5,-78 297.5,-114"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-92.88" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">generate_narrative</text>
</g>
<!-- handover&#45;&gt;ot4 -->
<g id="edge17" class="edge">
<title>handover&#45;&gt;ot4</title>
<path fill="none" stroke="#ff3d00" d="M90.03,-858.52C92.59,-748.47 108.05,-182.12 155.62,-123 161.44,-115.78 169.16,-110.45 177.6,-106.53"/>
<polygon fill="#ff3d00" stroke="#ff3d00" points="178.72,-109.85 186.77,-102.96 176.18,-103.33 178.72,-109.85"/>
</g>
<!-- or1 -->
<g id="node18" class="node">
<title>or1</title>
<polygon fill="#1a1a2a" stroke="#1e2a4a" points="285.5,-307 194.5,-307 194.5,-271 291.5,-271 291.5,-301 285.5,-307"/>
<polyline fill="none" stroke="#1e2a4a" points="285.5,-307 285.5,-301"/>
<polyline fill="none" stroke="#1e2a4a" points="291.5,-301 285.5,-301"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-285.88" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">ops://crew/roster</text>
</g>
<!-- handover&#45;&gt;or1 -->
<g id="edge18" class="edge">
<title>handover&#45;&gt;or1</title>
<path fill="none" stroke="#ff3d00" stroke-dasharray="5,2" d="M89.21,-858.63C87.59,-771.57 85.03,-401.1 155.62,-316 162.85,-307.29 172.84,-301.35 183.47,-297.3"/>
<polygon fill="#ff3d00" stroke="#ff3d00" points="184.34,-300.7 192.8,-294.31 182.21,-294.03 184.34,-300.7"/>
</g>
<!-- or2 -->
<g id="node19" class="node">
<title>or2</title>
<polygon fill="#1a1a2a" stroke="#1e2a4a" points="296.38,-361 183.62,-361 183.62,-325 302.38,-325 302.38,-355 296.38,-361"/>
<polyline fill="none" stroke="#1e2a4a" points="296.38,-361 296.38,-355"/>
<polyline fill="none" stroke="#1e2a4a" points="302.38,-355 296.38,-355"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-339.88" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">ops://handover/latest</text>
</g>
<!-- handover&#45;&gt;or2 -->
<g id="edge19" class="edge">
<title>handover&#45;&gt;or2</title>
<path fill="none" stroke="#ff3d00" stroke-dasharray="5,2" d="M90.13,-858.58C92.57,-780.41 105.2,-476.61 155.62,-398 163.66,-385.48 175.66,-375.27 188.19,-367.19"/>
<polygon fill="#ff3d00" stroke="#ff3d00" points="189.74,-370.35 196.52,-362.21 186.14,-364.34 189.74,-370.35"/>
</g>
<!-- op1 -->
<g id="node20" class="node">
<title>op1</title>
<polygon fill="#2a1a2a" stroke="#1e2a4a" points="279.12,-440 194.88,-440 194.88,-416 279.12,-416 291.12,-428 279.12,-440"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-431.25" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">handover_brief</text>
<text xml:space="preserve" text-anchor="middle" x="243" y="-418.5" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">(hub, shift_time)</text>
</g>
<!-- handover&#45;&gt;op1 -->
<g id="edge20" class="edge">
<title>handover&#45;&gt;op1</title>
<path fill="none" stroke="#ff3d00" stroke-dasharray="1,5" d="M89.63,-858.66C90.34,-804.5 97.51,-641.67 155.62,-524 168.97,-496.98 192.11,-471.87 211.09,-454.11"/>
<polygon fill="#ff3d00" stroke="#ff3d00" points="213.3,-456.83 218.34,-447.51 208.59,-451.65 213.3,-456.83"/>
</g>
<!-- sr2 -->
<g id="node12" class="node">
<title>sr2</title>
<polygon fill="#1a1a2a" stroke="#1e2a4a" points="297.88,-1385 182.12,-1385 182.12,-1349 303.88,-1349 303.88,-1379 297.88,-1385"/>
<polyline fill="none" stroke="#1e2a4a" points="297.88,-1385 297.88,-1379"/>
<polyline fill="none" stroke="#1e2a4a" points="303.88,-1379 297.88,-1379"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-1363.88" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">ops://scenarios/active</text>
</g>
<!-- sp1 -->
<g id="node13" class="node">
<title>sp1</title>
<polygon fill="#2a1a2a" stroke="#1e2a4a" points="296.38,-1464 177.62,-1464 177.62,-1440 296.38,-1440 308.38,-1452 296.38,-1464"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-1455.25" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">delay_explainer</text>
<text xml:space="preserve" text-anchor="middle" x="243" y="-1442.5" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">(cause_code, audience)</text>
</g>
<!-- pr1 -->
<g id="node22" class="node">
<title>pr1</title>
<polygon fill="#1a1a2a" stroke="#1e2a4a" points="308.38,-657 171.62,-657 171.62,-621 314.38,-621 314.38,-651 308.38,-657"/>
<polyline fill="none" stroke="#1e2a4a" points="308.38,-657 308.38,-651"/>
<polyline fill="none" stroke="#1e2a4a" points="314.38,-651 308.38,-651"/>
<text xml:space="preserve" text-anchor="middle" x="243" y="-635.88" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">ops://flights/{id}/manifest</text>
<!-- handover&#45;&gt;op -->
<g id="edge9" class="edge">
<title>handover&#45;&gt;op</title>
<path fill="none" stroke="#ff3d00" stroke-dasharray="1,5" d="M86.54,-269.66C101.01,-258.03 120.53,-242.97 138.75,-231 155.6,-219.93 174.82,-208.88 191.79,-199.62"/>
<polygon fill="#ff3d00" stroke="#ff3d00" points="193.02,-202.94 200.16,-195.11 189.7,-196.77 193.02,-202.94"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -10,7 +10,7 @@ digraph repo_structure {
fontsize=14
fontcolor="#0066ff"
root [label="united-ops/" fillcolor="#0066ff" fontcolor="white"]
root [label="stellar-ops/" fillcolor="#0066ff" fontcolor="white"]
mcp [label="mcp_servers/" fillcolor="#121829"]
agents [label="agents/" fillcolor="#121829"]
@@ -27,7 +27,7 @@ digraph repo_structure {
mcp_data [label="data/\nmodels.py\nreal/ (openmeteo, faa)\nmock/\nscenarios/ (4 scenarios)" fillcolor="#0d1a33" shape=box]
// Agents subtree
ag_efhas [label="efhas.py\nFCE agent" fillcolor="#1a1a3a" shape=box]
ag_efhas [label="fce.py\nFCE agent" fillcolor="#1a1a3a" shape=box]
ag_handover [label="handover.py\nHandover agent" fillcolor="#1a1a3a" shape=box]
ag_shared [label="shared/\nmcp_client.py\nllm.py" fillcolor="#1a1a3a" shape=box]

View File

@@ -4,9 +4,9 @@
<!-- Generated by graphviz version 14.1.2 (0)
-->
<!-- Title: repo_structure Pages: 1 -->
<svg width="2207pt" height="249pt"
viewBox="0.00 0.00 2207.00 249.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 245)">
<svg style="background:#0a0e17" width="4414pt" height="498pt"
viewBox="0.00 0.00 4414.00 498.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(2 2) rotate(0) translate(4 245)">
<title>repo_structure</title>
<polygon fill="#0a0e17" stroke="none" points="-4,4 -4,-245 2203,-245 2203,4 -4,4"/>
<text xml:space="preserve" text-anchor="middle" x="1099.5" y="-223.7" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#0066ff">Repository Structure</text>
@@ -14,7 +14,7 @@
<g id="node1" class="node">
<title>root</title>
<polygon fill="#0066ff" stroke="#1e2a4a" points="1454.75,-215.75 1451.75,-219.75 1430.75,-219.75 1427.75,-215.75 1384,-215.75 1384,-179.75 1454.75,-179.75 1454.75,-215.75"/>
<text xml:space="preserve" text-anchor="middle" x="1419.38" y="-194.62" font-family="Helvetica,sans-Serif" font-size="10.00" fill="white">united&#45;ops/</text>
<text xml:space="preserve" text-anchor="middle" x="1419.38" y="-194.62" font-family="Helvetica,sans-Serif" font-size="10.00" fill="white">stellar&#45;ops/</text>
</g>
<!-- mcp -->
<g id="node2" class="node">
@@ -162,7 +162,7 @@
<g id="node13" class="node">
<title>ag_efhas</title>
<polygon fill="#1a1a3a" stroke="#1e2a4a" points="698.12,-53.88 632.62,-53.88 632.62,-17.88 698.12,-17.88 698.12,-53.88"/>
<text xml:space="preserve" text-anchor="middle" x="665.38" y="-39.12" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">efhas.py</text>
<text xml:space="preserve" text-anchor="middle" x="665.38" y="-39.12" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">fce.py</text>
<text xml:space="preserve" text-anchor="middle" x="665.38" y="-26.38" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#e8eaf0">FCE agent</text>
</g>
<!-- agents&#45;&gt;ag_efhas -->

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -4,9 +4,9 @@
<!-- Generated by graphviz version 14.1.2 (0)
-->
<!-- Title: system_architecture Pages: 1 -->
<svg width="1298pt" height="662pt"
viewBox="0.00 0.00 1298.00 662.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 657.85)">
<svg style="background:#0a0e17" width="2596pt" height="1324pt"
viewBox="0.00 0.00 2596.00 1324.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(2 2) rotate(0) translate(4 657.85)">
<title>system_architecture</title>
<polygon fill="#0a0e17" stroke="none" points="-4,4 -4,-657.85 1294,-657.85 1294,4 -4,4"/>
<text xml:space="preserve" text-anchor="middle" x="645" y="-634.65" font-family="Helvetica,sans-Serif" font-size="16.00" fill="#0066ff">System Architecture</text>

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -144,6 +144,30 @@
.legend .mock::before { background: #ffc107; }
.legend .mcp::before { background: #0066ff; }
.legend .ops::before { background: #ff3d00; }
.graph-container a { display: block; }
.graph-container img { max-width: 100%; height: auto; }
/* Repo tree */
.tree-container {
background: #0a0e17;
border: 1px solid #1e2a4a;
padding: 24px;
overflow: auto;
}
.repo-tree {
font-family: 'JetBrains Mono', monospace;
font-size: 13px;
line-height: 1.7;
color: #8892a8;
}
.t-root { color: #0066ff; font-weight: 600; font-size: 15px; }
.t-dir { color: #e8eaf0; font-weight: 500; }
.t-mcp { color: #0066ff; font-weight: 500; }
.t-ops { color: #ff3d00; font-weight: 500; }
.t-pax { color: #00c853; font-weight: 500; }
.t-live { color: #00c853; }
.t-comment { color: #4a5568; }
</style>
</head>
<body>
@@ -171,7 +195,7 @@
<h2>SYSTEM ARCHITECTURE</h2>
<p>End-to-end view: Vue UI → Kong gateway → FastAPI → MCP servers → live and scenario data sources. Langfuse traces every agent run.</p>
<div class="graph-container">
<img src="graphs/system_architecture.svg" alt="System Architecture">
<a href="viewer.html?src=graphs/system_architecture.svg"><img src="graphs/system_architecture.svg" alt="System Architecture"></a>
</div>
<div class="legend">
<span class="live">Live API</span>
@@ -184,20 +208,25 @@
<h2>MCP SERVER TOPOLOGY</h2>
<p>Three servers scoped by access domain. Each exposes tools, resources, and prompts. FCE connects to shared + passenger. Handover connects to shared + ops.</p>
<div class="graph-container">
<img src="graphs/mcp_servers.svg" alt="MCP Servers">
<a href="viewer.html?src=graphs/mcp_servers.svg"><img src="graphs/mcp_servers.svg" alt="MCP Servers"></a>
</div>
<div class="legend">
<span class="mcp">Shared server</span>
<span class="ops">Ops server</span>
<span class="live">Passenger server</span>
</div>
<div class="legend" style="margin-top: 8px;">
<span style="color:#8892a8">── solid = tool calls</span>
<span style="color:#8892a8">╌╌ dashed = resource reads</span>
<span style="color:#8892a8">··· dotted = prompt gets</span>
</div>
</section>
<section id="efhas" class="graph-section">
<h2>FCE AGENT — BEHIND EVERY DEPARTURE</h2>
<p>Passenger notification agent. Triages flight status, gathers context from 5 parallel tool calls (including live weather and FAA data), synthesizes an empathetic notification.</p>
<div class="graph-container">
<img src="graphs/efhas_agent.svg" alt="FCE Agent">
<a href="viewer.html?src=graphs/efhas_agent.svg"><img src="graphs/efhas_agent.svg" alt="FCE Agent"></a>
</div>
</section>
@@ -205,7 +234,7 @@
<h2>SHIFT HANDOVER AGENT</h2>
<p>Ops briefing agent. Scans all hubs in parallel, scores issues by severity × time sensitivity, categorizes into IMMEDIATE / MONITOR / FYI, generates a structured brief.</p>
<div class="graph-container">
<img src="graphs/handover_agent.svg" alt="Handover Agent">
<a href="viewer.html?src=graphs/handover_agent.svg"><img src="graphs/handover_agent.svg" alt="Handover Agent"></a>
</div>
</section>
@@ -213,7 +242,7 @@
<h2>DATA FLOW — REAL vs MOCK</h2>
<p>Weather and FAA airport status are live (no API key). Flight, crew, passenger, and maintenance data are scenario-based fixtures switchable from the UI.</p>
<div class="graph-container">
<img src="graphs/data_flow.svg" alt="Data Flow">
<a href="viewer.html?src=graphs/data_flow.svg"><img src="graphs/data_flow.svg" alt="Data Flow"></a>
</div>
<div class="legend">
<span class="live">Live data (no API key)</span>
@@ -225,15 +254,50 @@
<h2>DEPLOYMENT</h2>
<p>Kind cluster for dev (Tilt), docker-compose for quick start, EC2 for production demo. Entry point: localhost:8040.</p>
<div class="graph-container">
<img src="graphs/deployment.svg" alt="Deployment">
<a href="viewer.html?src=graphs/deployment.svg"><img src="graphs/deployment.svg" alt="Deployment"></a>
</div>
</section>
<section id="repo" class="graph-section">
<h2>REPOSITORY STRUCTURE</h2>
<p>Monorepo: MCP servers, agents, IRROP engine, API, Vue UI (with shared component framework), and deployment configs.</p>
<div class="graph-container">
<img src="graphs/repo_structure.svg" alt="Repository Structure">
<div class="tree-container">
<pre class="repo-tree"><span class="t-root">stellar-ops/</span>
├── <span class="t-dir">mcp_servers/</span>
│ ├── <span class="t-mcp">shared/</span> <span class="t-comment">server.py — tools/ resources/ prompts/</span>
│ │ └── tools: <span class="t-live">get_route_weather</span> · <span class="t-live">get_hub_forecasts</span> · <span class="t-live">get_airport_status</span>
│ │ get_flight_status · get_flight_details · get_irregular_ops
│ │ get_airport_congestion · get_maintenance_flags
│ ├── <span class="t-ops">ops/</span> <span class="t-comment">server.py — tools/ resources/ prompts/</span>
│ │ └── tools: get_crew_notes · get_crew_duty_status · get_pending_rebookings
│ │ generate_narrative
│ ├── <span class="t-pax">passenger/</span> <span class="t-comment">server.py — tools/ resources/ prompts/</span>
│ │ └── tools: generate_notification
│ └── <span class="t-dir">data/</span>
│ ├── models.py <span class="t-comment">FlightData, CrewMember, Passenger, MELItem, HubInfo</span>
│ ├── real/ <span class="t-live">openmeteo.py · faa.py</span>
│ └── scenarios/ <span class="t-comment">normal_ops · weather_disruption_ord</span>
<span class="t-comment">maintenance_delay_sfo · crew_swap_ewr</span>
├── <span class="t-dir">agents/</span>
│ ├── fce.py <span class="t-comment">FCE — "Behind Every Departure"</span>
│ ├── handover.py <span class="t-comment">Shift Handover agent</span>
│ └── shared/ <span class="t-comment">mcp_client.py · llm.py (Bedrock/Anthropic)</span>
├── <span class="t-dir">irrop/</span> <span class="t-comment">Disruption Recovery Engine (Project 3)</span>
│ ├── models/ <span class="t-comment">flight · passenger · crew · recovery</span>
│ ├── rules/ <span class="t-comment">faa_part117 · rebooking · compensation</span>
│ └── pipeline/ <span class="t-comment">ingest → triage → rebook → compensate</span>
├── <span class="t-dir">api/</span>
│ └── main.py <span class="t-comment">FastAPI + WebSocket + scenario data API</span>
├── <span class="t-dir">ui/</span>
│ ├── framework/ <span class="t-comment">soleprint-ui (shared component library)</span>
│ └── app/ <span class="t-comment">Vue 3 SPA — Operations · Internals · Data</span>
├── <span class="t-dir">ctrl/</span>
│ ├── Dockerfile.api/ui <span class="t-comment">Container builds</span>
│ ├── k8s/ <span class="t-comment">base/ + overlays/dev/ (Kustomize)</span>
│ ├── Tiltfile <span class="t-comment">Dev environment (Kind cluster: unt)</span>
│ └── docker-compose.yml <span class="t-comment">Simple alternative</span>
├── <span class="t-dir">docs/</span> <span class="t-comment">Architecture graphs (this page)</span>
└── .mcp.json <span class="t-comment">Claude Code integration — 3 servers</span></pre>
</div>
</section>
@@ -248,6 +312,7 @@ function show(id) {
document.getElementById(id).classList.add('active');
event.currentTarget.classList.add('active');
}
</script>
</body>

101
docs/viewer.html Normal file
View File

@@ -0,0 +1,101 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Graph Viewer</title>
<style>
* { margin: 0; padding: 0; }
body {
background: #0a0e17;
overflow: hidden;
width: 100vw;
height: 100vh;
}
#container {
width: 100vw;
height: 100vh;
overflow: hidden;
cursor: grab;
}
#container.dragging { cursor: grabbing; }
img {
transform-origin: 0 0;
user-select: none;
-webkit-user-drag: none;
}
</style>
</head>
<body>
<div id="container">
<img id="img" />
</div>
<script>
var src = new URLSearchParams(location.search).get('src');
var img = document.getElementById('img');
var container = document.getElementById('container');
img.src = src;
var scale = 1;
var x = 0, y = 0;
var dragging = false;
var startX, startY, startPanX, startPanY;
function apply() {
img.style.transform = 'translate(' + x + 'px,' + y + 'px) scale(' + scale + ')';
}
// Fit to screen on load
img.onload = function() {
var sw = window.innerWidth / img.naturalWidth;
var sh = window.innerHeight / img.naturalHeight;
scale = Math.min(sw, sh) * 0.95;
x = (window.innerWidth - img.naturalWidth * scale) / 2;
y = (window.innerHeight - img.naturalHeight * scale) / 2;
apply();
};
// Wheel zoom toward cursor
container.addEventListener('wheel', function(e) {
e.preventDefault();
var factor = e.deltaY < 0 ? 1.12 : 0.89;
var rect = container.getBoundingClientRect();
var mx = e.clientX - rect.left;
var my = e.clientY - rect.top;
x = mx - (mx - x) * factor;
y = my - (my - y) * factor;
scale *= factor;
apply();
}, { passive: false });
// Pan
container.addEventListener('mousedown', function(e) {
if (e.button !== 0) return;
dragging = true;
startX = e.clientX;
startY = e.clientY;
startPanX = x;
startPanY = y;
container.classList.add('dragging');
e.preventDefault();
});
window.addEventListener('mousemove', function(e) {
if (!dragging) return;
x = startPanX + (e.clientX - startX);
y = startPanY + (e.clientY - startY);
apply();
});
window.addEventListener('mouseup', function() {
dragging = false;
container.classList.remove('dragging');
});
// Double-click to reset
container.addEventListener('dblclick', function() {
img.onload();
});
</script>
</body>
</html>

View File

@@ -12,7 +12,7 @@ from fastmcp import FastMCP
from mcp_servers.data.scenarios.manager import scenario_manager
mcp = FastMCP(
"united-ops-internal",
"stellar-ops-internal",
instructions=(
"Internal operations tools — crew duty status, pending rebookings, "
"and ops-audience narrative generation. Restricted to ops-facing clients."

View File

@@ -13,7 +13,7 @@ from mcp_servers.data.models import MPStatus
from mcp_servers.data.scenarios.manager import scenario_manager
mcp = FastMCP(
"united-ops-passenger",
"stellar-ops-passenger",
instructions=(
"Passenger-facing tools — notification narrative generation "
"and flight manifest access. Restricted to customer-facing clients."

View File

@@ -11,9 +11,9 @@ from mcp_servers.data.real import faa, openmeteo
from mcp_servers.data.scenarios.manager import scenario_manager
mcp = FastMCP(
"united-ops-shared",
"stellar-ops-shared",
instructions=(
"Shared operational data tools for Stellar Air operations. "
"Shared operational data tools for Stellar Air NOVA operations. "
"Covers: flight status, weather (live OpenMeteo), airport status "
"(live FAA), and maintenance flags. Used by all agent clients."
),

View File

@@ -1,5 +1,5 @@
[project]
name = "united-ops"
name = "stellar-ops"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [

View File

@@ -1,5 +1,5 @@
{
"name": "united-ops-ui",
"name": "stellar-ops-ui",
"version": "0.1.0",
"private": true,
"type": "module",

View File

@@ -1,10 +1,17 @@
<script setup lang="ts">
import { ref } from 'vue'
import { ref, provide } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import ScenarioSelector from './components/ScenarioSelector.vue'
const router = useRouter()
const route = useRoute()
const scenarioVersion = ref(0)
function onScenarioChange() {
scenarioVersion.value++
}
provide('scenarioVersion', scenarioVersion)
</script>
<template>
@@ -18,8 +25,9 @@ const route = useRoute()
<router-link to="/" :class="{ active: route.path === '/' }">Operations</router-link>
<router-link to="/internals" :class="{ active: route.path === '/internals' }">Internals</router-link>
<router-link to="/data" :class="{ active: route.path === '/data' }">Data</router-link>
<a href="/docs/" class="docs-link" target="_blank">Docs</a>
</nav>
<ScenarioSelector />
<ScenarioSelector @change="onScenarioChange" />
</header>
<main class="app-main">
<router-view />
@@ -85,6 +93,11 @@ const route = useRoute()
border-color: var(--accent);
}
.app-nav .docs-link {
color: var(--text-dim);
font-size: 11px;
}
.app-main {
flex: 1;
overflow: auto;

View File

@@ -1,6 +1,8 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const emit = defineEmits<{ change: [id: string] }>()
const scenarios = ref<any[]>([])
const active = ref('')
@@ -19,6 +21,7 @@ async function switchScenario(id: string) {
body: JSON.stringify({ scenario_id: id }),
})
active.value = id
emit('change', id)
}
onMounted(loadScenarios)

View File

@@ -1,40 +1,41 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ref, onMounted, watch, inject } from 'vue'
import { Panel } from 'soleprint-ui'
import NotificationCard from '../components/NotificationCard.vue'
import HandoverBrief from '../components/HandoverBrief.vue'
const flights = ref<any[]>([])
const selectedFlight = ref('')
const efhasStatus = ref<'idle' | 'processing' | 'live' | 'error'>('idle')
const fceStatus = ref<'idle' | 'processing' | 'live' | 'error'>('idle')
const handoverStatus = ref<'idle' | 'processing' | 'live' | 'error'>('idle')
const notification = ref<any>(null)
const handoverBrief = ref<any>(null)
const scenarioVersion = inject<any>('scenarioVersion')
watch(scenarioVersion, () => {
loadFlights()
notification.value = null
handoverBrief.value = null
fceStatus.value = 'idle'
handoverStatus.value = 'idle'
})
async function loadFlights() {
// Load flights from active scenario via a simple status check on known IDs
const res = await fetch('/scenarios/active')
const scenario = await res.json()
// Fetch flights by triggering the shared MCP server isn't exposed directly
// so we'll use a hardcoded list per scenario for now
// TODO: expose flight list via API
flights.value = [
{ id: 'UA432', label: 'UA432 ORD→SFO' },
{ id: 'UA881', label: 'UA881 ORD→LAX' },
{ id: 'UA233', label: 'UA233 ORD→DEN' },
{ id: 'UA094', label: 'UA094 ORD→EWR' },
{ id: 'UA517', label: 'UA517 ORD→IAH (CANCELLED)' },
{ id: 'UA1220', label: 'UA1220 ORD→IAD (on-time)' },
]
const res = await fetch('/scenarios/data/flights')
const data = await res.json()
flights.value = data.map((f: any) => ({
id: f.flight_id,
label: `${f.flight_id} ${f.origin}${f.destination}${f.status !== 'ON_TIME' ? ' (' + f.status + ')' : ''}`,
}))
selectedFlight.value = flights.value[0]?.id || ''
}
async function runEfhas() {
async function runFce() {
if (!selectedFlight.value) return
efhasStatus.value = 'processing'
fceStatus.value = 'processing'
notification.value = null
const res = await fetch('/agents/efhas', {
const res = await fetch('/agents/fce', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ flight_id: selectedFlight.value }),
@@ -48,10 +49,10 @@ async function runEfhas() {
if (data.status === 'completed') {
clearInterval(poll)
notification.value = data.result
efhasStatus.value = 'live'
fceStatus.value = 'live'
} else if (data.status === 'error') {
clearInterval(poll)
efhasStatus.value = 'error'
fceStatus.value = 'error'
}
}, 1000)
}
@@ -86,20 +87,20 @@ onMounted(loadFlights)
<template>
<div class="ops-page">
<Panel title="FCE — Behind Every Departure" :status="efhasStatus">
<Panel title="FCE — Behind Every Departure" :status="fceStatus">
<template #actions>
<select v-model="selectedFlight" class="flight-select">
<option v-for="f in flights" :key="f.id" :value="f.id">{{ f.label }}</option>
</select>
<button class="run-btn" @click="runEfhas" :disabled="efhasStatus === 'processing'">
{{ efhasStatus === 'processing' ? 'Running...' : 'Run FCE' }}
<button class="run-btn" @click="runFce" :disabled="fceStatus === 'processing'">
{{ fceStatus === 'processing' ? 'Running...' : 'Run FCE' }}
</button>
</template>
<div v-if="notification" class="result-area">
<NotificationCard :data="notification" />
</div>
<div v-else-if="efhasStatus === 'processing'" class="loading">
<div v-else-if="fceStatus === 'processing'" class="loading">
Running agent... gathering flight data, weather, crew notes...
</div>
<div v-else class="empty">