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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user