Add tiered LOTL mining onion and fleet recon so agents can fallback across execution tiers while operators see spread and vuln posture in Crucible. Includes triple-onion chain, spread cred graph, and full Go/TS/E2E test validation.

This commit is contained in:
AetherForge
2026-06-06 23:53:21 -07:00
parent 6372b07e6c
commit 3938bcd1c5
268 changed files with 21347 additions and 1130 deletions

View File

@@ -9,6 +9,11 @@ import {
spreadTechniqueDocUrl,
} from '../help/spreadTechniques';
import { formatHashrate, sparklineBarHeight, sparklineMax, staggerDelayMs } from '../help/warRoom';
import {
aggregateCampaignTelemetry,
maxTelemetryHashrate,
mergeCampaignWithLiveTelemetry,
} from '../help/warRoomTelemetry';
import CampaignConstellations from '../components/WarRoom/CampaignConstellations';
import WarRoomFunnelBoard from '../components/WarRoom/WarRoomFunnelBoard';
import WarRoomOdometer from '../components/WarRoom/WarRoomOdometer';
@@ -42,7 +47,7 @@ const NOTES_TYPING_IDLE_MS = 2000;
const WAR_ROOM_DAYS = 7;
export default function EmberwakePage() {
const { latestMessage } = useWebSocket();
const { latestMessage, agents: wsAgents } = useWebSocket();
const { notesTyping, sendNotesTyping } = usePresence();
const [builds, setBuilds] = useState<BuildRecord[]>([]);
const [publicBuilds, setPublicBuilds] = useState<PublicBuildDTO[]>([]);
@@ -111,6 +116,24 @@ export default function EmberwakePage() {
void load().catch(() => {});
}, [load]);
const liveTelemetry = useMemo(() => aggregateCampaignTelemetry(wsAgents), [wsAgents]);
const maxLiveHashrate = useMemo(() => maxTelemetryHashrate(liveTelemetry), [liveTelemetry]);
const displayCampaigns = useMemo(() => {
if (!warRoom?.campaigns?.length) return [];
return warRoom.campaigns.map((c) =>
mergeCampaignWithLiveTelemetry(c, liveTelemetry.get(c.campaign)),
);
}, [warRoom, liveTelemetry]);
const campaignAgentsMap = useMemo(() => {
const out: Record<string, typeof wsAgents> = {};
for (const [slug, telemetry] of liveTelemetry) {
out[slug] = telemetry.agents;
}
return out;
}, [liveTelemetry]);
useEffect(() => {
if (!latestMessage) return;
if (latestMessage.type === 'emberwake_notes_updated') {
@@ -339,21 +362,23 @@ export default function EmberwakePage() {
</button>
</div>
<HelpTip field="ew_war_room_views" />
<span className="war-room-toolbar-meta">WebSocket push ~30s</span>
<span className="war-room-toolbar-meta">Hash live · funnel WS ~30s</span>
</div>
</div>
{warRoom && warRoom.campaigns.length > 0 ? (
{displayCampaigns.length > 0 ? (
warRoomView === 'constellations' ? (
<CampaignConstellations
campaigns={warRoom.campaigns}
campaigns={displayCampaigns}
onSelectCampaign={handleConstellationSelect}
/>
) : warRoomView === 'funnel' ? (
<WarRoomFunnelBoard
campaigns={warRoom.campaigns}
days={warRoom.days || WAR_ROOM_DAYS}
campaigns={displayCampaigns}
days={warRoom?.days || WAR_ROOM_DAYS}
refreshKey={warRoomUpdated}
highlightCampaign={highlightedCampaign}
campaignAgents={campaignAgentsMap}
maxLiveHashrate={maxLiveHashrate}
/>
) : (
<div className="war-room-table-wrap">
@@ -373,7 +398,7 @@ export default function EmberwakePage() {
</tr>
</thead>
<tbody>
{warRoom.campaigns.map((c, rowIndex) => {
{displayCampaigns.map((c, rowIndex) => {
const max = sparklineMax(c.daily_hits);
const beacon = c.first_beacon ?? c.agents;
const mining = c.mining ?? (c.hashrate > 0 ? 1 : 0);