Fleet P2b: run agent sandboxes on connected nodes (RemoteDriver + placement)
Agents can now provision their sandbox on a connected fleet node instead of the gateway host. Local stays the strict default, so existing agents are byte-for- byte unaffected until explicitly placed elsewhere. Security parity: the daemon links the REAL cm-sandbox DockerDriver and runs the typed container ops (sb_provision/sb_exec/sb_destroy/sb_health/sb_list) through it — identical hardening (cap-drop ALL, seccomp, no-net, read-only, non-root) to local sandboxes. cm-sandbox spec types are now Serialize/Deserialize so the spec crosses the channel. - cm-api: RemoteDriver (impl SandboxDriver over the node channel) + HubDriverProvider (impl cm_runtime::NodeDriverProvider, hands out a driver only for connected nodes via a sync online set) + NodeHub.call/is_connected. AppState.with_node_hub so the hub is shared with the placement provider. - cm-runtime SandboxManager: driver_for(node_id) routes by the recorded agent_containers.node_id (local default = existing driver, identical path); placement_node() reads the workspace setting and falls back to local if the node is offline; exec/release route accordingly. NodeDriverProvider trait. - DB: 0020_workspace_placement + repo (for_agent/get/set/clear). - main.rs: build the NodeHub first; inject HubDriverProvider into the agent manager + share the hub with AppState. - API+UI: GET/PUT /api/fleet/placement + a "Run agents on: Local / <node>" selector in the Fleet overview. Note: a node must be able to pull the agent image (the daemon docker-pulls it); interactive PTY for agent containers on remote nodes is not wired (Terminal app stays local) — the in-dashboard node shell already covers host access. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
33aa9c0693
commit
fb59378aa2
@@ -0,0 +1,464 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script src="./support.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<x-dc>
|
||||
<helmet>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; padding: 0; background: #08080a; }
|
||||
body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; }
|
||||
@keyframes cm-flow { to { stroke-dashoffset: -24; } }
|
||||
@keyframes cm-blink { 0%,100% { opacity: 1; } 50% { opacity: .25; } }
|
||||
@keyframes cm-halo { 0% { transform: scale(.7); opacity: .5; } 100% { transform: scale(2); opacity: 0; } }
|
||||
@keyframes cm-dash { to { stroke-dashoffset: -40; } }
|
||||
</style>
|
||||
</helmet>
|
||||
|
||||
<div style="width:100%; height:100vh; min-height:640px; background:#08080a; color:#f3f3f5; display:flex; flex-direction:column; overflow:hidden;">
|
||||
|
||||
<!-- TOP BAR -->
|
||||
<div style="height:50px; flex:none; display:flex; align-items:center; gap:13px; padding:0 16px; border-bottom:1px solid rgba(255,255,255,.06); background:linear-gradient(180deg,#0d0d10,#0a0a0c);">
|
||||
<div style="display:flex; align-items:center; gap:9px;">
|
||||
<svg width="20" height="20" viewBox="0 0 22 22" fill="none"><path d="M11 3 L18.5 17 L3.5 17 Z" stroke="#ff6f61" stroke-width="1.3" stroke-linejoin="round" opacity="0.55"></path><circle cx="11" cy="3.5" r="2.2" fill="#ff6f61"></circle><circle cx="18" cy="17" r="2.2" fill="#ff6f61"></circle><circle cx="4" cy="17" r="2.2" fill="#ff6f61"></circle></svg>
|
||||
<span style="font-size:14px; font-weight:700;">Clawmates</span>
|
||||
</div>
|
||||
<span style="font-family:'JetBrains Mono',monospace; font-size:12px; color:#f3f3f5; background:rgba(255,111,97,.12); border:1px solid rgba(255,111,97,.28); padding:3px 9px; border-radius:6px;">Large World</span>
|
||||
<span style="color:#3a3a40;">/</span>
|
||||
<span style="font-family:'JetBrains Mono',monospace; font-size:12px; color:#6a6a72;">Agents</span>
|
||||
<div style="flex:1;"></div>
|
||||
<div style="display:flex; align-items:center; gap:7px; font-family:'JetBrains Mono',monospace; font-size:11px; color:#5fd08a; padding:5px 10px; border:1px solid rgba(95,208,138,.25); border-radius:7px; background:rgba(95,208,138,.06);">
|
||||
<span style="width:6px; height:6px; border-radius:50%; background:#5fd08a; animation:cm-blink 1.6s infinite;"></span>1 org · 2 agents
|
||||
</div>
|
||||
<div style="width:30px; height:30px; border-radius:8px; background:linear-gradient(135deg,#ff8a7a,#ff5f57); display:flex; align-items:center; justify-content:center; font-size:12px; font-weight:700; color:#2a0d0a;">O</div>
|
||||
</div>
|
||||
|
||||
<!-- BODY -->
|
||||
<div style="flex:1; display:flex; min-height:0;">
|
||||
|
||||
<!-- ICON RAIL -->
|
||||
<div style="width:54px; flex:none; border-right:1px solid rgba(255,255,255,.06); background:#0a0a0c; display:flex; flex-direction:column; align-items:center; padding:12px 0; gap:6px;">
|
||||
<div style="position:relative; width:40px; height:44px; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:4px; border-radius:9px; color:#ff6f61; background:rgba(255,111,97,.1);">
|
||||
<span style="position:absolute; left:0; top:7px; bottom:7px; width:3px; border-radius:0 3px 3px 0; background:#ff6f61;"></span>
|
||||
<svg width="18" height="18" viewBox="0 0 20 20"><circle cx="10" cy="10" r="7.5" stroke="currentColor" stroke-width="1.4" fill="none"></circle><circle cx="10" cy="10" r="2.4" fill="currentColor"></circle></svg>
|
||||
<span style="font-family:'JetBrains Mono',monospace; font-size:7px; letter-spacing:.05em;">WORLD</span>
|
||||
</div>
|
||||
<div style="width:40px; height:44px; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:4px; border-radius:9px; color:#5a5a62;">
|
||||
<svg width="18" height="18" viewBox="0 0 20 20"><rect x="4" y="4" width="12" height="12" rx="3.5" stroke="currentColor" stroke-width="1.4" fill="none"></rect><circle cx="10" cy="10" r="2.2" fill="currentColor"></circle></svg>
|
||||
<span style="font-family:'JetBrains Mono',monospace; font-size:7px; letter-spacing:.05em;">AGENT</span>
|
||||
</div>
|
||||
<div style="flex:1;"></div>
|
||||
<div style="width:28px; height:28px; border-radius:8px; border:1px dashed rgba(255,255,255,.16); display:flex; align-items:center; justify-content:center; color:#ff6f61; font-size:16px; font-weight:300;">+</div>
|
||||
</div>
|
||||
|
||||
<!-- LEFT LIST -->
|
||||
<div style="width:200px; flex:none; border-right:1px solid rgba(255,255,255,.06); background:#0b0b0e; display:flex; flex-direction:column; min-height:0;">
|
||||
<div style="padding:14px 14px 10px;">
|
||||
<div style="font-family:'JetBrains Mono',monospace; font-size:10px; letter-spacing:.12em; color:#5a5a62;">1 ORG · 2 AGENTS</div>
|
||||
<div style="font-size:17px; font-weight:700; margin-top:3px;">Large World</div>
|
||||
</div>
|
||||
<div style="flex:1; overflow:hidden; padding:6px;">
|
||||
<!-- WORLD_TREE_LIST -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CENTER STAGE -->
|
||||
<div style="flex:1; position:relative; min-width:0; overflow:hidden; background:radial-gradient(130% 100% at 50% 0%, #0e0e13 0%, #08080a 65%);">
|
||||
|
||||
<!-- VIEW SWITCHER -->
|
||||
<div style="position:absolute; top:14px; left:50%; transform:translateX(-50%); z-index:20; display:flex; padding:4px; border-radius:11px; background:rgba(14,14,18,.85); border:1px solid rgba(255,255,255,.1); backdrop-filter:blur(10px); gap:3px;">
|
||||
<sc-for list="{{ modes }}" as="m" hint-placeholder-count="3">
|
||||
<div style="display:flex; align-items:center; gap:7px; font-family:'JetBrains Mono',monospace; font-size:12px; font-weight:600; padding:7px 15px; border-radius:8px; cursor:pointer; transition:all .2s; {{ m.style }}" onClick="{{ m.onPick }}">{{ m.icon }} {{ m.label }}</div>
|
||||
</sc-for>
|
||||
</div>
|
||||
|
||||
<!-- GRAPH TOOLS -->
|
||||
<div style="position:absolute; top:14px; left:16px; z-index:15; width:150px; border-radius:11px; border:1px solid rgba(255,255,255,.08); background:rgba(14,14,18,.7); backdrop-filter:blur(8px); padding:10px;">
|
||||
<div style="font-family:'JetBrains Mono',monospace; font-size:9px; letter-spacing:.12em; color:#5a5a62; margin-bottom:9px;">GRAPH TOOLS</div>
|
||||
<div style="display:flex; flex-direction:column; gap:6px;">
|
||||
<div style="display:flex; align-items:center; gap:8px; font-size:11px; color:#ff8a7a; padding:6px 8px; border-radius:7px; background:rgba(255,111,97,.08);">▣ Save layout</div>
|
||||
<div style="display:flex; align-items:center; gap:8px; font-size:11px; color:#cfcfd5; padding:6px 8px; border-radius:7px; background:rgba(255,255,255,.03);">⤢ Fit to view</div>
|
||||
<div style="display:flex; align-items:center; gap:8px; font-size:11px; color:#cfcfd5; padding:6px 8px; border-radius:7px; background:rgba(255,255,255,.03);">↺ Reset layout</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- caption -->
|
||||
<div style="position:absolute; bottom:16px; left:50%; transform:translateX(-50%); z-index:15; text-align:center;">
|
||||
<div style="font-family:'JetBrains Mono',monospace; font-size:10px; color:#5a5a62;">{{ caption }}</div>
|
||||
</div>
|
||||
|
||||
<!-- VIEW LAYERS -->
|
||||
<div style="position:absolute; inset:0; display:{{ hShow }};">
|
||||
<div style="position:absolute; inset:0;">
|
||||
<svg viewBox="0 0 100 100" preserveAspectRatio="none" style="position:absolute; inset:0; width:100%; height:100%;">
|
||||
<path d="M50,15 C50,22 42,24 42,31" fill="none" stroke="rgba(95,208,138,.4)" stroke-width="1.1" stroke-dasharray="2 3" vector-effect="non-scaling-stroke" style="animation:cm-dash 2s linear infinite;"></path>
|
||||
<path d="M42,33 C42,42 50,44 50,51" fill="none" stroke="rgba(95,208,138,.4)" stroke-width="1.1" stroke-dasharray="2 3" vector-effect="non-scaling-stroke" style="animation:cm-dash 2.4s linear infinite;"></path>
|
||||
<path d="M50,55 C50,64 38,66 36,74" fill="none" stroke="rgba(95,208,138,.35)" stroke-width="1.1" stroke-dasharray="2 3" vector-effect="non-scaling-stroke" style="animation:cm-dash 2.1s linear infinite;"></path>
|
||||
<path d="M50,55 C50,64 60,66 62,74" fill="none" stroke="rgba(255,111,97,.5)" stroke-width="1.2" stroke-dasharray="2 3" vector-effect="non-scaling-stroke" style="animation:cm-dash 1.6s linear infinite;"></path>
|
||||
</svg>
|
||||
|
||||
<!-- Zeus (ORG) -->
|
||||
<div style="position:absolute; left:50%; top:15%; transform:translate(-50%,-50%); display:flex; align-items:center; gap:10px; padding:10px 14px; border-radius:12px; background:#11101a; border:1px solid rgba(201,138,240,.4); box-shadow:0 8px 24px rgba(0,0,0,.4); cursor:pointer;">
|
||||
<div style="width:32px; height:32px; border-radius:9px; background:linear-gradient(135deg,#c98af0,#9a5ad8); display:flex; align-items:center; justify-content:center; font-weight:700; color:#1a0a2a; font-size:14px;">Z</div>
|
||||
<div><div style="font-size:14px; font-weight:700;">Zeus</div><div style="font-family:'JetBrains Mono',monospace; font-size:9px; color:#c98af0;">ORG · 1 company</div></div>
|
||||
<span style="color:#5a5a62; font-size:11px; margin-left:6px;">▾</span>
|
||||
</div>
|
||||
|
||||
<!-- thor (COMPANY) -->
|
||||
<div style="position:absolute; left:42%; top:32%; transform:translate(-50%,-50%); display:flex; align-items:center; gap:10px; padding:10px 14px; border-radius:12px; background:#0e0f1a; border:1px solid rgba(138,154,240,.4); box-shadow:0 8px 24px rgba(0,0,0,.4); cursor:pointer;">
|
||||
<div style="width:32px; height:32px; border-radius:9px; background:linear-gradient(135deg,#8a9af0,#5a6ad8); display:flex; align-items:center; justify-content:center; font-weight:700; color:#0a0e2a; font-size:14px;">T</div>
|
||||
<div><div style="font-size:14px; font-weight:700;">thor</div><div style="font-family:'JetBrains Mono',monospace; font-size:9px; color:#8a9af0;">COMPANY · 1 team</div></div>
|
||||
<span style="color:#5a5a62; font-size:11px; margin-left:6px;">▾</span>
|
||||
</div>
|
||||
|
||||
<!-- POD01 (TEAM) -->
|
||||
<div style="position:absolute; left:50%; top:53%; transform:translate(-50%,-50%); display:flex; align-items:center; gap:10px; padding:10px 14px; border-radius:12px; background:#0a1412; border:1px solid rgba(95,208,138,.4); box-shadow:0 8px 24px rgba(0,0,0,.4); cursor:pointer;">
|
||||
<div style="width:32px; height:32px; border-radius:9px; background:linear-gradient(135deg,#6fd0c0,#4aa3b8); display:flex; align-items:center; justify-content:center; font-weight:700; color:#06201f; font-size:14px;">P</div>
|
||||
<div><div style="font-size:14px; font-weight:700;">POD01</div><div style="font-family:'JetBrains Mono',monospace; font-size:9px; color:#5fd08a;">TEAM · 2 agents</div></div>
|
||||
<span style="color:#5a5a62; font-size:11px; margin-left:6px;">▾</span>
|
||||
</div>
|
||||
|
||||
<!-- Morpheus -->
|
||||
<div style="position:absolute; left:36%; top:76%; transform:translate(-50%,-50%); display:flex; flex-direction:column; align-items:center; gap:7px; cursor:pointer;">
|
||||
<div style="width:44px; height:44px; border-radius:50%; background:linear-gradient(135deg,#ff8a7a,#ff5f57); display:flex; align-items:center; justify-content:center; font-size:16px; font-weight:700; color:#2a0d0a; box-shadow:0 0 22px rgba(255,111,97,.35); position:relative;">M<span style="position:absolute; right:1px; bottom:1px; width:9px; height:9px; border-radius:50%; background:#5fd08a; border:2px solid #08080a;"></span></div>
|
||||
<div style="text-align:center;"><div style="font-size:12px; font-weight:600;">Morpheus</div><div style="font-family:'JetBrains Mono',monospace; font-size:9px; color:#6a6a72;">Project Manager</div></div>
|
||||
</div>
|
||||
|
||||
<!-- Smith (selected) -->
|
||||
<div style="position:absolute; left:62%; top:76%; transform:translate(-50%,-50%); display:flex; flex-direction:column; align-items:center; gap:7px; cursor:pointer;">
|
||||
<div style="position:relative; width:50px; height:50px;">
|
||||
<div style="position:absolute; inset:-5px; border-radius:50%; border:2px solid #ff6f61; box-shadow:0 0 0 4px rgba(255,111,97,.12);"></div>
|
||||
<div style="position:absolute; inset:0; border-radius:50%; background:rgba(255,111,97,.4); animation:cm-halo 1.8s ease-out infinite;"></div>
|
||||
<div style="position:relative; width:50px; height:50px; border-radius:50%; background:linear-gradient(135deg,#ff8a7a,#ff5f57); display:flex; align-items:center; justify-content:center; font-size:18px; font-weight:700; color:#2a0d0a; box-shadow:0 0 28px rgba(255,111,97,.5);">S<span style="position:absolute; right:2px; bottom:2px; width:10px; height:10px; border-radius:50%; background:#5fd08a; border:2px solid #08080a;"></span></div>
|
||||
</div>
|
||||
<div style="text-align:center;"><div style="font-size:12px; font-weight:700; color:#fff;">Smith</div><div style="font-family:'JetBrains Mono',monospace; font-size:9px; color:#ff8a7a;">Research Specialist</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position:absolute; inset:0; display:{{ fShow }};">
|
||||
<div style="position:absolute; inset:0;">
|
||||
<svg viewBox="0 0 100 100" preserveAspectRatio="none" style="position:absolute; inset:0; width:100%; height:100%;">
|
||||
<!-- flat peer mesh: no hierarchy, every node links to peers -->
|
||||
<path d="M50,20 L82,44" stroke="rgba(255,255,255,.1)" stroke-width="1" vector-effect="non-scaling-stroke"></path>
|
||||
<path d="M82,44 L68,78" stroke="rgba(255,255,255,.1)" stroke-width="1" vector-effect="non-scaling-stroke"></path>
|
||||
<path d="M68,78 L32,78" stroke="rgba(255,111,97,.45)" stroke-width="1.2" stroke-dasharray="2 3" vector-effect="non-scaling-stroke" style="animation:cm-dash 1.8s linear infinite;"></path>
|
||||
<path d="M32,78 L18,44" stroke="rgba(255,255,255,.1)" stroke-width="1" vector-effect="non-scaling-stroke"></path>
|
||||
<path d="M18,44 L50,20" stroke="rgba(255,255,255,.1)" stroke-width="1" vector-effect="non-scaling-stroke"></path>
|
||||
<path d="M50,20 L68,78" stroke="rgba(94,200,216,.35)" stroke-width="1" stroke-dasharray="2 3" vector-effect="non-scaling-stroke" style="animation:cm-dash 2.3s linear infinite;"></path>
|
||||
<path d="M50,20 L32,78" stroke="rgba(255,255,255,.07)" stroke-width="1" vector-effect="non-scaling-stroke"></path>
|
||||
<path d="M18,44 L82,44" stroke="rgba(255,255,255,.07)" stroke-width="1" vector-effect="non-scaling-stroke"></path>
|
||||
</svg>
|
||||
|
||||
<!-- peer chip: Zeus -->
|
||||
<div style="position:absolute; left:50%; top:20%; transform:translate(-50%,-50%); display:flex; flex-direction:column; align-items:center; gap:7px; cursor:pointer;">
|
||||
<div style="width:40px; height:40px; border-radius:11px; background:linear-gradient(135deg,#c98af0,#9a5ad8); display:flex; align-items:center; justify-content:center; font-weight:700; color:#1a0a2a; font-size:15px;">Z</div>
|
||||
<div style="text-align:center;"><div style="font-size:12px; font-weight:600;">Zeus</div><div style="font-family:'JetBrains Mono',monospace; font-size:9px; color:#6a6a72;">org</div></div>
|
||||
</div>
|
||||
<!-- thor -->
|
||||
<div style="position:absolute; left:82%; top:44%; transform:translate(-50%,-50%); display:flex; flex-direction:column; align-items:center; gap:7px; cursor:pointer;">
|
||||
<div style="width:40px; height:40px; border-radius:11px; background:linear-gradient(135deg,#8a9af0,#5a6ad8); display:flex; align-items:center; justify-content:center; font-weight:700; color:#0a0e2a; font-size:15px;">T</div>
|
||||
<div style="text-align:center;"><div style="font-size:12px; font-weight:600;">thor</div><div style="font-family:'JetBrains Mono',monospace; font-size:9px; color:#6a6a72;">company</div></div>
|
||||
</div>
|
||||
<!-- POD01 -->
|
||||
<div style="position:absolute; left:68%; top:78%; transform:translate(-50%,-50%); display:flex; flex-direction:column; align-items:center; gap:7px; cursor:pointer;">
|
||||
<div style="width:40px; height:40px; border-radius:11px; background:linear-gradient(135deg,#6fd0c0,#4aa3b8); display:flex; align-items:center; justify-content:center; font-weight:700; color:#06201f; font-size:15px;">P</div>
|
||||
<div style="text-align:center;"><div style="font-size:12px; font-weight:600;">POD01</div><div style="font-family:'JetBrains Mono',monospace; font-size:9px; color:#6a6a72;">team</div></div>
|
||||
</div>
|
||||
<!-- Smith (selected) -->
|
||||
<div style="position:absolute; left:32%; top:78%; transform:translate(-50%,-50%); display:flex; flex-direction:column; align-items:center; gap:7px; cursor:pointer;">
|
||||
<div style="position:relative; width:46px; height:46px;">
|
||||
<div style="position:absolute; inset:-4px; border-radius:50%; border:2px solid #ff6f61;"></div>
|
||||
<div style="position:relative; width:46px; height:46px; border-radius:50%; background:linear-gradient(135deg,#ff8a7a,#ff5f57); display:flex; align-items:center; justify-content:center; font-weight:700; color:#2a0d0a; font-size:16px;">S<span style="position:absolute; right:1px; bottom:1px; width:9px; height:9px; border-radius:50%; background:#5fd08a; border:2px solid #08080a;"></span></div>
|
||||
</div>
|
||||
<div style="text-align:center;"><div style="font-size:12px; font-weight:700; color:#fff;">Smith</div><div style="font-family:'JetBrains Mono',monospace; font-size:9px; color:#ff8a7a;">agent</div></div>
|
||||
</div>
|
||||
<!-- Morpheus -->
|
||||
<div style="position:absolute; left:18%; top:44%; transform:translate(-50%,-50%); display:flex; flex-direction:column; align-items:center; gap:7px; cursor:pointer;">
|
||||
<div style="width:46px; height:46px; border-radius:50%; background:linear-gradient(135deg,#ff8a7a,#ff5f57); display:flex; align-items:center; justify-content:center; font-weight:700; color:#2a0d0a; font-size:16px; position:relative;">M<span style="position:absolute; right:1px; bottom:1px; width:9px; height:9px; border-radius:50%; background:#5fd08a; border:2px solid #08080a;"></span></div>
|
||||
<div style="text-align:center;"><div style="font-size:12px; font-weight:600;">Morpheus</div><div style="font-family:'JetBrains Mono',monospace; font-size:9px; color:#6a6a72;">agent</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position:absolute; inset:0; display:{{ lShow }};">
|
||||
<div style="position:absolute; inset:0;">
|
||||
<canvas ref="{{ canvasRef }}" style="width:100%; height:100%; display:block;"></canvas>
|
||||
<!-- live legend -->
|
||||
<div style="position:absolute; top:64px; left:16px; z-index:14; display:flex; flex-direction:column; gap:7px; padding:11px 13px; border-radius:11px; border:1px solid rgba(255,255,255,.08); background:rgba(14,14,18,.7); backdrop-filter:blur(8px);">
|
||||
<div style="font-family:'JetBrains Mono',monospace; font-size:9px; letter-spacing:.12em; color:#5a5a62; margin-bottom:1px;">CONVERGING ON</div>
|
||||
<div style="display:flex; align-items:center; gap:8px;"><span style="width:9px; height:9px; border-radius:50%; background:#ff8a7a;"></span><span style="font-family:'JetBrains Mono',monospace; font-size:11px; color:#cfcfd5;">services</span></div>
|
||||
<div style="display:flex; align-items:center; gap:8px;"><span style="width:9px; height:9px; border-radius:50%; background:#5ec8d8;"></span><span style="font-family:'JetBrains Mono',monospace; font-size:11px; color:#cfcfd5;">events</span></div>
|
||||
<div style="display:flex; align-items:center; gap:8px; margin-top:4px;"><span style="width:9px; height:9px; border-radius:50%; background:#ff5f57; box-shadow:0 0 8px #ff5f57;"></span><span style="font-family:'JetBrains Mono',monospace; font-size:11px; color:#cfcfd5;">agents (M · S)</span></div>
|
||||
</div>
|
||||
<!-- live HUD -->
|
||||
<div style="position:absolute; top:64px; right:16px; z-index:14; display:flex; gap:8px;">
|
||||
<div style="display:flex; align-items:center; gap:6px; padding:6px 11px; border-radius:8px; border:1px solid rgba(94,200,216,.25); background:rgba(94,200,216,.06); font-family:'JetBrains Mono',monospace; font-size:10px; color:#5ec8d8;"><span style="width:6px; height:6px; border-radius:50%; background:#5ec8d8; animation:cm-blink 1s infinite;"></span>9 active nodes</div>
|
||||
<div style="display:flex; align-items:center; gap:6px; padding:6px 11px; border-radius:8px; border:1px solid rgba(255,255,255,.08); background:rgba(20,20,24,.6); font-family:'JetBrains Mono',monospace; font-size:10px; color:#9a9aa2;">▮▮ 14 touches/min</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ZOOM -->
|
||||
<div style="position:absolute; bottom:16px; left:16px; z-index:15; display:flex; flex-direction:column; gap:5px;">
|
||||
<div style="width:28px; height:28px; border-radius:7px; border:1px solid rgba(255,255,255,.1); background:rgba(20,20,24,.7); display:flex; align-items:center; justify-content:center; color:#9a9aa2; font-size:15px;">+</div>
|
||||
<div style="width:28px; height:28px; border-radius:7px; border:1px solid rgba(255,255,255,.1); background:rgba(20,20,24,.7); display:flex; align-items:center; justify-content:center; color:#9a9aa2; font-size:15px;">−</div>
|
||||
<div style="width:28px; height:28px; border-radius:7px; border:1px solid rgba(255,255,255,.1); background:rgba(20,20,24,.7); display:flex; align-items:center; justify-content:center; color:#9a9aa2;"><svg width="12" height="12" viewBox="0 0 14 14"><path d="M2 2h3M2 2v3M12 2h-3M12 2v3M2 12h3M2 12v-3M12 12h-3M12 12v-3" stroke="currentColor" stroke-width="1.3"></path></svg></div>
|
||||
</div>
|
||||
|
||||
<!-- MINIMAP -->
|
||||
<div style="position:absolute; bottom:16px; right:16px; z-index:15; width:150px; height:96px; border-radius:9px; border:1px solid rgba(255,255,255,.08); background:rgba(12,12,15,.8); overflow:hidden;">
|
||||
<div style="position:absolute; top:5px; left:8px; font-family:'JetBrains Mono',monospace; font-size:8px; letter-spacing:.1em; color:#5a5a62;">{{ minimapLabel }}</div>
|
||||
<svg viewBox="0 0 150 96" style="position:absolute; inset:0; width:100%; height:100%; opacity:.7;">
|
||||
<line x1="75" y1="48" x2="50" y2="30" stroke="rgba(255,255,255,.12)" stroke-width="1"></line>
|
||||
<line x1="75" y1="48" x2="100" y2="34" stroke="rgba(255,255,255,.12)" stroke-width="1"></line>
|
||||
<line x1="75" y1="48" x2="60" y2="66" stroke="rgba(255,255,255,.12)" stroke-width="1"></line>
|
||||
<line x1="75" y1="48" x2="96" y2="64" stroke="rgba(255,255,255,.12)" stroke-width="1"></line>
|
||||
<circle cx="75" cy="48" r="4" fill="#ff6f61"></circle>
|
||||
<circle cx="50" cy="30" r="2.5" fill="#5ec8d8"></circle>
|
||||
<circle cx="100" cy="34" r="2.5" fill="#5fd08a"></circle>
|
||||
<circle cx="60" cy="66" r="2.5" fill="#c98af0"></circle>
|
||||
<circle cx="96" cy="64" r="2.5" fill="#e8b465"></circle>
|
||||
<rect x="60" y="58" width="42" height="30" rx="2" fill="none" stroke="rgba(255,255,255,.2)" stroke-width="1"></rect>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT PANEL -->
|
||||
<div style="width:300px; flex:none; border-left:1px solid rgba(255,255,255,.06); background:#0b0b0e; display:flex; flex-direction:column; min-height:0;">
|
||||
<div style="display:flex; align-items:center; gap:8px; padding:12px 14px; border-bottom:1px solid rgba(255,255,255,.06);">
|
||||
<span style="font-family:'JetBrains Mono',monospace; font-size:10px; letter-spacing:.14em; color:#5a5a62;">PANEL</span>
|
||||
<span style="flex:1;"></span>
|
||||
<span style="color:#5a5a62; font-size:13px;">✕</span>
|
||||
</div>
|
||||
<div style="flex:1; overflow-y:auto; padding:18px 16px;">
|
||||
<div style="display:flex; flex-direction:column; align-items:center; text-align:center; margin-bottom:18px;">
|
||||
<div style="position:relative; width:64px; height:64px; margin-bottom:11px;">
|
||||
<div style="position:absolute; inset:-3px; border-radius:50%; border:1.5px solid rgba(255,111,97,.4);"></div>
|
||||
<div style="width:64px; height:64px; border-radius:50%; background:linear-gradient(135deg,#ff8a7a,#ff5f57); display:flex; align-items:center; justify-content:center; font-size:24px; font-weight:700; color:#2a0d0a;">S</div>
|
||||
</div>
|
||||
<div style="font-size:17px; font-weight:700;">Smith</div>
|
||||
<div style="font-family:'JetBrains Mono',monospace; font-size:10px; color:#6a6a72; margin-top:2px;">Research Specialist</div>
|
||||
<span style="margin-top:8px; font-family:'JetBrains Mono',monospace; font-size:9px; color:#5fd08a; padding:3px 9px; border-radius:6px; background:rgba(95,208,138,.1); border:1px solid rgba(95,208,138,.25);">● online</span>
|
||||
</div>
|
||||
|
||||
<div style="font-family:'JetBrains Mono',monospace; font-size:9px; letter-spacing:.14em; color:#5a5a62; margin-bottom:10px;">BELONGS TO</div>
|
||||
<div style="display:flex; flex-direction:column; gap:1px; margin-bottom:18px;">
|
||||
<div style="display:flex; align-items:center; gap:9px; padding:9px 10px; border-radius:8px;"><span style="width:7px; height:7px; border-radius:50%; background:#5fd08a;"></span><span style="font-family:'JetBrains Mono',monospace; font-size:9px; color:#6a6a72; width:54px;">TEAM</span><span style="font-size:13px; font-weight:600;">POD01</span></div>
|
||||
<div style="display:flex; align-items:center; gap:9px; padding:9px 10px; border-radius:8px;"><span style="width:7px; height:7px; border-radius:50%; background:#8a9af0;"></span><span style="font-family:'JetBrains Mono',monospace; font-size:9px; color:#6a6a72; width:54px;">COMPANY</span><span style="font-size:13px; font-weight:600;">thor</span></div>
|
||||
<div style="display:flex; align-items:center; gap:9px; padding:9px 10px; border-radius:8px;"><span style="width:7px; height:7px; border-radius:50%; background:#c98af0;"></span><span style="font-family:'JetBrains Mono',monospace; font-size:9px; color:#6a6a72; width:54px;">ORG</span><span style="font-size:13px; font-weight:600;">Zeus</span></div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; gap:8px; margin-bottom:18px;">
|
||||
<div style="flex:1; text-align:center; padding:12px 0; border-radius:10px; border:1px solid rgba(255,255,255,.07); background:#0d0d10;"><div style="font-size:18px; font-weight:700;">0</div><div style="font-family:'JetBrains Mono',monospace; font-size:9px; color:#6a6a72; margin-top:2px;">SKILLS</div></div>
|
||||
<div style="flex:1; text-align:center; padding:12px 0; border-radius:10px; border:1px solid rgba(255,255,255,.07); background:#0d0d10;"><div style="font-size:18px; font-weight:700;">0</div><div style="font-family:'JetBrains Mono',monospace; font-size:9px; color:#6a6a72; margin-top:2px;">TOOLS</div></div>
|
||||
<div style="flex:1; text-align:center; padding:12px 0; border-radius:10px; border:1px solid rgba(255,255,255,.07); background:#0d0d10;"><div style="font-size:18px; font-weight:700;">0</div><div style="font-family:'JetBrains Mono',monospace; font-size:9px; color:#6a6a72; margin-top:2px;">RUNNING</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex:none; padding:12px 14px; border-top:1px solid rgba(255,255,255,.06);">
|
||||
<div style="display:flex; align-items:center; justify-content:center; gap:8px; height:38px; border-radius:9px; background:linear-gradient(135deg,#ff8a7a,#ff5f57); color:#2a0d0a; font-size:13px; font-weight:700; cursor:pointer;">⛶ More details</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- STATUS BAR -->
|
||||
<div style="height:26px; flex:none; display:flex; align-items:center; gap:16px; padding:0 16px; border-top:1px solid rgba(255,255,255,.06); background:#0a0a0c; font-family:'JetBrains Mono',monospace; font-size:10px; color:#5a5a62;">
|
||||
<span style="color:#5fd08a;">● durable runner ok</span>
|
||||
<span>checkpoint 3s ago</span>
|
||||
<span style="flex:1;"></span>
|
||||
<span>§15 sandbox: isolated</span>
|
||||
<span style="color:#e8b465;">doors awaiting approval</span>
|
||||
</div>
|
||||
</div>
|
||||
</x-dc>
|
||||
<script type="text/x-dc" data-dc-script>
|
||||
class Component extends DCLogic {
|
||||
state = { view: 'live', selected: 'smith' };
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevState && prevState.view !== this.state.view) {
|
||||
if (this.state.view === 'live') this._startLive();
|
||||
else this._stopLive();
|
||||
}
|
||||
}
|
||||
componentWillUnmount() { this._stopLive(); }
|
||||
|
||||
_setCanvas = (el) => {
|
||||
this._cv = el;
|
||||
if (el && this.state.view === 'live') this._startLive();
|
||||
else if (!el) this._stopLive();
|
||||
};
|
||||
|
||||
_stopLive() { if (this._raf) { cancelAnimationFrame(this._raf); this._raf = null; } }
|
||||
|
||||
_startLive() {
|
||||
const cv = this._cv;
|
||||
if (!cv) return;
|
||||
this._stopLive();
|
||||
|
||||
// Gource-style world: a root blooms into area/service/event nodes (the "files"),
|
||||
// and agent particles stream toward whatever node is currently active, emitting beams.
|
||||
const targets = [
|
||||
{ id:'runtime', label:'cm-runtime', kind:'service', col:'#ff8a7a', ang:-0.5, rad:0.30 },
|
||||
{ id:'pr214', label:'PR #214', kind:'event', col:'#5ec8d8', ang:0.15, rad:0.40 },
|
||||
{ id:'advdb', label:'advisory-db',kind:'service', col:'#5fd08a', ang:0.9, rad:0.34 },
|
||||
{ id:'slack', label:'#eng', kind:'event', col:'#c98af0', ang:1.7, rad:0.42 },
|
||||
{ id:'orch', label:'cm-orch', kind:'service', col:'#e8b465', ang:2.5, rad:0.30 },
|
||||
{ id:'deploy', label:'PROD deploy',kind:'event', col:'#ff6f61', ang:3.5, rad:0.40 },
|
||||
{ id:'docs', label:'spec §15', kind:'service', col:'#6fd0c0', ang:4.3, rad:0.33 },
|
||||
{ id:'bench', label:'topo-bench', kind:'service', col:'#8a9af0', ang:5.1, rad:0.41 },
|
||||
{ id:'broker', label:'secret-broker',kind:'service',col:'#9a9aa2', ang:5.7, rad:0.28 },
|
||||
];
|
||||
const agents = [
|
||||
{ id:'m', label:'M', col:'#ff5f57', ink:'#2a0d0a', t:0, target:1 },
|
||||
{ id:'s', label:'S', col:'#4aa3b8', ink:'#06201f', t:2, target:2 },
|
||||
];
|
||||
const beams = []; // {ax, ay, tx, ty, col, life}
|
||||
const sparks = []; // {x, y, col, r, life}
|
||||
|
||||
const dpr = Math.min(2, window.devicePixelRatio || 1);
|
||||
const ctx = cv.getContext('2d');
|
||||
let W = 0, H = 0, cx = 0, cy = 0, rmin = 0;
|
||||
const resize = () => {
|
||||
const r = cv.getBoundingClientRect();
|
||||
W = r.width; H = r.height;
|
||||
cv.width = W * dpr; cv.height = H * dpr;
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
cx = W / 2; cy = H / 2 + 8; rmin = Math.min(W, H);
|
||||
};
|
||||
resize();
|
||||
this._onResize = resize;
|
||||
window.addEventListener('resize', resize);
|
||||
|
||||
const tpos = (t) => ({ x: cx + Math.cos(t.ang) * rmin * t.rad, y: cy + Math.sin(t.ang) * rmin * t.rad });
|
||||
|
||||
// each agent orbits + drifts toward its current target; retargets periodically
|
||||
agents.forEach(a => { a.x = cx; a.y = cy; a.retime = 1.5 + Math.random()*2; });
|
||||
|
||||
let last = performance.now();
|
||||
const tick = (now) => {
|
||||
const dt = Math.min(0.05, (now - last) / 1000); last = now;
|
||||
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
|
||||
// faint tree: root -> each target
|
||||
ctx.lineWidth = 1;
|
||||
targets.forEach(t => {
|
||||
const p = tpos(t);
|
||||
ctx.strokeStyle = 'rgba(255,255,255,0.05)';
|
||||
ctx.beginPath(); ctx.moveTo(cx, cy); ctx.lineTo(p.x, p.y); ctx.stroke();
|
||||
});
|
||||
|
||||
// target nodes
|
||||
targets.forEach(t => {
|
||||
const p = tpos(t); t._x = p.x; t._y = p.y;
|
||||
const pulse = t._hot ? 1 + 0.25 * Math.sin(now/120) : 1;
|
||||
const baseR = (t.kind === 'service' ? 7 : 5) * pulse;
|
||||
// glow when hot
|
||||
if (t._hot) {
|
||||
const g = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, 26);
|
||||
g.addColorStop(0, t.col + '55'); g.addColorStop(1, t.col + '00');
|
||||
ctx.fillStyle = g; ctx.beginPath(); ctx.arc(p.x, p.y, 26, 0, 7); ctx.fill();
|
||||
}
|
||||
ctx.fillStyle = t.col; ctx.globalAlpha = t._hot ? 1 : 0.55;
|
||||
ctx.beginPath(); ctx.arc(p.x, p.y, baseR, 0, 7); ctx.fill();
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.fillStyle = t._hot ? 'rgba(255,255,255,0.85)' : 'rgba(255,255,255,0.4)';
|
||||
ctx.font = "600 10px 'JetBrains Mono', monospace";
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(t.label, p.x, p.y - baseR - 7);
|
||||
t._hot = false;
|
||||
});
|
||||
|
||||
// root
|
||||
const rg = ctx.createRadialGradient(cx, cy, 0, cx, cy, 30);
|
||||
rg.addColorStop(0, 'rgba(255,111,97,0.5)'); rg.addColorStop(1, 'rgba(255,111,97,0)');
|
||||
ctx.fillStyle = rg; ctx.beginPath(); ctx.arc(cx, cy, 30, 0, 7); ctx.fill();
|
||||
ctx.fillStyle = '#ff6f61'; ctx.beginPath(); ctx.arc(cx, cy, 9, 0, 7); ctx.fill();
|
||||
ctx.fillStyle = '#fff'; ctx.font = "700 9px 'JetBrains Mono', monospace"; ctx.textAlign = 'center';
|
||||
ctx.fillText('Z', cx, cy + 3);
|
||||
|
||||
// agents move + emit beams
|
||||
agents.forEach(a => {
|
||||
a.retime -= dt;
|
||||
if (a.retime <= 0) { a.target = Math.floor(Math.random() * targets.length); a.retime = 1.2 + Math.random()*2.2; }
|
||||
const tg = targets[a.target]; const p = tpos(tg);
|
||||
// approach a point near the target (orbit a bit)
|
||||
a.t += dt;
|
||||
const ox = p.x + Math.cos(a.t*1.5) * 26;
|
||||
const oy = p.y + Math.sin(a.t*1.5) * 26;
|
||||
a.x += (ox - a.x) * Math.min(1, dt * 2.2);
|
||||
a.y += (oy - a.y) * Math.min(1, dt * 2.2);
|
||||
// proximity → emit beam + heat the node
|
||||
const d = Math.hypot(a.x - p.x, a.y - p.y);
|
||||
if (d < 60) {
|
||||
tg._hot = true;
|
||||
if (Math.random() < 0.5) beams.push({ ax:a.x, ay:a.y, tx:p.x, ty:p.y, col:a.col, life:1 });
|
||||
if (Math.random() < 0.25) sparks.push({ x:p.x, y:p.y, col:tg.col, r:2, life:1 });
|
||||
}
|
||||
});
|
||||
|
||||
// beams
|
||||
for (let i = beams.length - 1; i >= 0; i--) {
|
||||
const b = beams[i]; b.life -= dt * 2.5;
|
||||
if (b.life <= 0) { beams.splice(i, 1); continue; }
|
||||
ctx.strokeStyle = b.col + Math.floor(b.life * 200).toString(16).padStart(2,'0');
|
||||
ctx.lineWidth = 1.5 * b.life + 0.4;
|
||||
ctx.beginPath(); ctx.moveTo(b.ax, b.ay); ctx.lineTo(b.tx, b.ty); ctx.stroke();
|
||||
}
|
||||
// sparks
|
||||
for (let i = sparks.length - 1; i >= 0; i--) {
|
||||
const s = sparks[i]; s.life -= dt * 1.8; s.r += dt * 22;
|
||||
if (s.life <= 0) { sparks.splice(i, 1); continue; }
|
||||
ctx.strokeStyle = s.col + Math.floor(s.life * 160).toString(16).padStart(2,'0');
|
||||
ctx.lineWidth = 1.2;
|
||||
ctx.beginPath(); ctx.arc(s.x, s.y, s.r, 0, 7); ctx.stroke();
|
||||
}
|
||||
|
||||
// agent avatars on top
|
||||
agents.forEach(a => {
|
||||
ctx.fillStyle = a.col;
|
||||
ctx.shadowColor = a.col; ctx.shadowBlur = 14;
|
||||
ctx.beginPath(); ctx.arc(a.x, a.y, 11, 0, 7); ctx.fill();
|
||||
ctx.shadowBlur = 0;
|
||||
ctx.fillStyle = a.ink; ctx.font = "700 11px 'JetBrains Mono', monospace"; ctx.textAlign = 'center';
|
||||
ctx.fillText(a.label, a.x, a.y + 4);
|
||||
});
|
||||
|
||||
this._raf = requestAnimationFrame(tick);
|
||||
};
|
||||
this._raf = requestAnimationFrame(tick);
|
||||
}
|
||||
|
||||
renderVals() {
|
||||
const v = this.state.view;
|
||||
const mk = (id, label, icon) => {
|
||||
const active = v === id;
|
||||
return { id, label, icon, active,
|
||||
style: active ? 'background:rgba(255,111,97,.16); color:#ff8a7a;' : 'color:#9a9aa2;',
|
||||
onPick: () => this.setState({ view: id }) };
|
||||
};
|
||||
const captions = {
|
||||
hierarchy: 'hierarchy — org ▸ company ▸ team ▸ claw',
|
||||
flat: 'flat topology — every node a peer, no nesting',
|
||||
live: 'live — agents converge on the projects, services & events they touch (Gource-style)',
|
||||
};
|
||||
const minimaps = { hierarchy:'TREE', flat:'FLAT', live:'LIVE' };
|
||||
return {
|
||||
modes: [ mk('hierarchy','Hierarchy','▤'), mk('flat','Flat','⬡'), mk('live','Live','✦') ],
|
||||
isHierarchy: v==='hierarchy', isFlat: v==='flat', isLive: v==='live',
|
||||
hShow: v==='hierarchy'?'block':'none', fShow: v==='flat'?'block':'none', lShow: v==='live'?'block':'none',
|
||||
caption: captions[v], minimapLabel: minimaps[v],
|
||||
canvasRef: this._setCanvas,
|
||||
};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user