Skip to content

Commit e657161

Browse files
Roo Coderuvnet
andcommitted
feat: hardening sprint — auth enforcement, graph resilience, infrastructure docs
Rust backend: strengthen auth middleware, rate limiting, validation; harden graph_service_supervisor, message_tracker, task_orchestrator actors; improve neo4j adapters, MCP relay, websocket handlers, binary protocol. Client: refactor bots components and polling service, slim useSolidPod/ useSolidResource hooks, update graph worker force layout, fix settings store, harden nostrAuthService and BinaryWebSocketProtocol, update WASM bridge. Docs: add ADR-011 (auth enforcement), ADR-012 (websocket store decomposition), ADR-013 (render performance), DDD bounded contexts, infrastructure inventory, PRD sprint hardening. Add audit and blob assembly scripts. Co-Authored-By: claude-flow <ruv@ruv.net>
1 parent 9b93d40 commit e657161

50 files changed

Lines changed: 2620 additions & 1403 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

client/src/features/bots/components/AgentDetailPanel.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useState, useEffect } from 'react';
22
import { useBotsData } from '../contexts/BotsDataContext';
33
import { Card } from '../../design-system/components/Card';
4-
import { Select } from '../../design-system/components/Select';
4+
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '../../design-system/components/Select';
55
import { Button } from '../../design-system/components/Button';
66
import { Input } from '../../design-system/components/Input';
77
import type { BotsAgent } from '../types/BotsTypes';
@@ -100,12 +100,16 @@ export const AgentDetailPanel: React.FC<AgentDetailPanelProps> = ({
100100
value={selectedAgent?.id || ''}
101101
onValueChange={handleAgentChange}
102102
>
103-
<option value="">Select Agent</option>
104-
{botsData.agents.map(agent => (
105-
<option key={agent.id} value={agent.id}>
106-
{agent.name || agent.id} ({agent.type})
107-
</option>
108-
))}
103+
<SelectTrigger>
104+
<SelectValue placeholder="Select Agent" />
105+
</SelectTrigger>
106+
<SelectContent>
107+
{botsData.agents.map(agent => (
108+
<SelectItem key={agent.id} value={agent.id}>
109+
{agent.name || agent.id} ({agent.type})
110+
</SelectItem>
111+
))}
112+
</SelectContent>
109113
</Select>
110114
</div>
111115
</div>

client/src/features/bots/components/AgentTelemetryStream.tsx

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -6,45 +6,6 @@ import { createLogger } from '../../../utils/loggerConfig';
66

77
const logger = createLogger('AgentTelemetryStream');
88

9-
// Load GOAP widget script dynamically
10-
const loadGoapWidget = () => {
11-
if (typeof window === 'undefined' || document.getElementById('goap-widget-script')) return;
12-
13-
(window as unknown as Record<string, unknown>).GOAPWidgetConfig = {
14-
primaryColor: '#ff8800',
15-
accentColor: '#fbbf24',
16-
backgroundColor: '#1a1a1a',
17-
cardBackgroundColor: '#262626',
18-
cardBorderColor: '#ff8800',
19-
textColor: '#ff8800',
20-
secondaryTextColor: '#fbbf24',
21-
successColor: '#22c55e',
22-
title: 'Goal-Oriented Action Planning',
23-
description: 'AI-powered research planning using A* pathfinding and dynamic agent coordination',
24-
brandName: 'JunkieJarvis',
25-
defaultGoal: 'Research Knowledge Graphs and Human Scale Spring Force Systems',
26-
fontFamily: 'monospace',
27-
borderRadius: '0.5rem',
28-
animationSpeed: 'fast',
29-
cardSpacing: '0.5rem',
30-
showMetrics: true,
31-
showStats: true,
32-
compactMode: true,
33-
enableAI: true,
34-
aiModel: 'google/gemini-2.5-flash'
35-
};
36-
37-
const script = document.createElement('script');
38-
script.id = 'goap-widget-script';
39-
script.src = 'https://goal.ruv.io/widget.js';
40-
script.async = true;
41-
script.crossOrigin = 'anonymous';
42-
script.onerror = () => {
43-
logger.warn('[GOAP] Widget script failed to load (may be blocked by COEP/CSP policy)');
44-
};
45-
document.body.appendChild(script);
46-
};
47-
489
interface TelemetryMessage {
4910
timestamp: number;
5011
agentId: string;
@@ -67,14 +28,6 @@ export const AgentTelemetryStream: React.FC = () => {
6728
}
6829
}, [messages]);
6930

70-
71-
useEffect(() => {
72-
if (activeTab === 'goap') {
73-
loadGoapWidget();
74-
}
75-
}, [activeTab]);
76-
77-
7831
useEffect(() => {
7932
const pollTelemetry = async () => {
8033
try {

client/src/features/bots/components/BotsControlPanel.tsx

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useEffect } from 'react';
1+
import React, { useState, useEffect, useRef } from 'react';
22
import { Html } from '@react-three/drei';
33
import {
44
configurationMapper,
@@ -24,6 +24,8 @@ export const BotsControlPanel: React.FC<BotsControlPanelProps> = ({
2424
const [activeTab, setActiveTab] = useState<'colors' | 'animation' | 'physics' | 'rendering'>('colors');
2525
const [isCollapsed, setIsCollapsed] = useState(false);
2626
const [agentCount, setAgentCount] = useState(12);
27+
const swarmWsRef = useRef<WebSocket | null>(null);
28+
const swarmWsTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
2729

2830
useEffect(() => {
2931

@@ -35,6 +37,14 @@ export const BotsControlPanel: React.FC<BotsControlPanelProps> = ({
3537

3638
return () => {
3739
configurationMapper.unsubscribe(id);
40+
if (swarmWsRef.current) {
41+
swarmWsRef.current.close();
42+
swarmWsRef.current = null;
43+
}
44+
if (swarmWsTimerRef.current) {
45+
clearTimeout(swarmWsTimerRef.current);
46+
swarmWsTimerRef.current = null;
47+
}
3848
};
3949
}, [onConfigChange]);
4050

@@ -110,16 +120,25 @@ export const BotsControlPanel: React.FC<BotsControlPanelProps> = ({
110120
};
111121

112122
const startSwarmMonitoring = (swarmId: string) => {
113-
114-
const ws = new WebSocket(`/ws/swarm-status/${swarmId}`);
123+
// Close any previous swarm WebSocket
124+
if (swarmWsRef.current) {
125+
swarmWsRef.current.close();
126+
swarmWsRef.current = null;
127+
}
128+
if (swarmWsTimerRef.current) {
129+
clearTimeout(swarmWsTimerRef.current);
130+
swarmWsTimerRef.current = null;
131+
}
132+
133+
const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
134+
const ws = new WebSocket(`${proto}//${window.location.host}/ws/swarm-status/${swarmId}`);
135+
swarmWsRef.current = ws;
115136

116137
ws.onmessage = (event) => {
117138
const statusUpdate = JSON.parse(event.data);
118139
logger.info('Swarm status update:', statusUpdate);
119140

120-
121141
if (statusUpdate.status === 'active') {
122-
123142
logger.info(`Swarm ${swarmId} is now active with ${statusUpdate.activeWorkers} workers`);
124143
} else if (statusUpdate.status === 'failed') {
125144
logger.error(`Swarm ${swarmId} failed:`, statusUpdate.error);
@@ -131,13 +150,14 @@ export const BotsControlPanel: React.FC<BotsControlPanelProps> = ({
131150
logger.error('Swarm monitoring WebSocket error:', error);
132151
};
133152

134-
135-
136-
setTimeout(() => {
153+
swarmWsTimerRef.current = setTimeout(() => {
137154
if (ws.readyState === WebSocket.OPEN) {
138155
ws.close();
139156
}
140-
}, 60000);
157+
if (swarmWsRef.current === ws) {
158+
swarmWsRef.current = null;
159+
}
160+
}, 60000);
141161
};
142162

143163
const showSpawnError = (type: string, message: string) => {

client/src/features/bots/components/BotsVisualization.tsx

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ const QUEEN_GOLD = new THREE.Color('#FFD700');
2121
const ADDITIVE_BLENDING = THREE.AdditiveBlending;
2222
const BACK_SIDE = THREE.BackSide;
2323

24+
// Preallocated particle base-T values (module scope to avoid per-render allocation)
25+
const PARTICLE_BASE_T = [0.15, 0.4, 0.65, 0.9] as const;
26+
2427
// Lightweight line component using standard BufferGeometry (NOT InstancedBufferGeometry).
2528
// drei's <Line> uses Line2/LineGeometry which extends InstancedBufferGeometry — its default
2629
// instanceCount=Infinity crashes WebGPU's drawIndexed(). This is a three.js Line2/troika
@@ -504,6 +507,7 @@ const BotsNode: React.FC<BotsNodeProps> = ({ agent, position, index, color }) =>
504507
const lastPositionRef = useRef<THREE.Vector3 | undefined>(undefined);
505508
const currentPositionRef = useRef<THREE.Vector3>(position.clone());
506509
const targetPositionRef = useRef<THREE.Vector3>(position.clone());
510+
const elapsedTimeRef = useRef(0);
507511
const settings = useSettingsStore(state => state.settings);
508512

509513

@@ -609,6 +613,7 @@ const BotsNode: React.FC<BotsNodeProps> = ({ agent, position, index, color }) =>
609613
groupRef.current.position.copy(currentPositionRef.current);
610614

611615
const elapsedTime = state.clock.elapsedTime;
616+
elapsedTimeRef.current = elapsedTime;
612617
const activity = agent.activity ?? 0;
613618
const healthPulse = agent.health ? (agent.health / 100) : 0.5;
614619
const tokenGlow = agent.tokenRate ? Math.min(agent.tokenRate / 20, 2) : 0;
@@ -878,8 +883,8 @@ const BotsNode: React.FC<BotsNodeProps> = ({ agent, position, index, color }) =>
878883
].map((_, i) => {
879884
const angle = (i / 8) * Math.PI * 2;
880885
const radius = clampedSize * 2;
881-
const x = Math.cos(angle + Date.now() * 0.001) * radius;
882-
const z = Math.sin(angle + Date.now() * 0.001) * radius;
886+
const x = Math.cos(angle + elapsedTimeRef.current) * radius;
887+
const z = Math.sin(angle + elapsedTimeRef.current) * radius;
883888
return (
884889
<mesh key={i} position={[x, 0, z]}>
885890
<sphereGeometry args={[0.03, 6, 6]} />
@@ -1191,6 +1196,15 @@ const BotsEdgeComponent: React.FC<BotsEdgeProps> = ({
11911196
targetAgent
11921197
}) => {
11931198
const [isActive, setIsActive] = useState(false);
1199+
const particleVecs = useRef([
1200+
new THREE.Vector3(), new THREE.Vector3(),
1201+
new THREE.Vector3(), new THREE.Vector3(),
1202+
]);
1203+
const elapsedRef = useRef(0);
1204+
1205+
useFrame((state) => {
1206+
elapsedRef.current = state.clock.elapsedTime;
1207+
});
11941208

11951209
useEffect(() => {
11961210
const checkActivity = () => {
@@ -1228,14 +1242,14 @@ const BotsEdgeComponent: React.FC<BotsEdgeProps> = ({
12281242

12291243
const shouldAnimate = isActive && (avgTokenRate > 15 || edge.messageCount > 50);
12301244
const animationSpeed = Math.min(avgTokenRate / 10, 3) + Math.min(edge.messageCount / 100, 2);
1231-
const dashOffset = shouldAnimate ? -Date.now() * 0.001 * animationSpeed : 0;
1245+
const dashOffset = shouldAnimate ? -elapsedRef.current * animationSpeed : 0;
12321246

12331247
const shouldPulse = avgTokenRate > 40 || edge.messageCount > 200;
1234-
const pulseIntensity = shouldPulse ? Math.sin(Date.now() * 0.005) * 0.3 + 1 : 1;
1248+
const pulseIntensity = shouldPulse ? Math.sin(elapsedRef.current * 5) * 0.3 + 1 : 1;
12351249

12361250
// Organic curve: compute a midpoint with slight perpendicular offset for tendril effect
12371251
const distance = sourcePos.distanceTo(targetPos);
1238-
const now = Date.now() * 0.001;
1252+
const now = elapsedRef.current;
12391253
const organicCurvePoints = useMemo(() => {
12401254
const mid = new THREE.Vector3().copy(sourcePos).add(targetPos).multiplyScalar(0.5);
12411255
const dir = new THREE.Vector3().subVectors(targetPos, sourcePos).normalize();
@@ -1288,14 +1302,17 @@ const BotsEdgeComponent: React.FC<BotsEdgeProps> = ({
12881302
{/* Organic data particles flowing along tendril */}
12891303
{isActive && shouldAnimate && (
12901304
<group>
1291-
{[0.15, 0.4, 0.65, 0.9].map((baseT, i) => {
1305+
{PARTICLE_BASE_T.map((baseT, i) => {
12921306
// Slightly varied speed per particle for organic feel
12931307
const speedVar = 1 + (i * 0.13);
12941308
const t = (baseT + (now * animationSpeed * speedVar * 0.15)) % 1;
1295-
// Lerp along the curve (source -> mid -> target based on t)
1296-
const particlePos = t < 0.5
1297-
? _tempVec3A.clone().lerpVectors(sourcePos, organicCurvePoints[1], t * 2)
1298-
: _tempVec3A.clone().lerpVectors(organicCurvePoints[1], targetPos, (t - 0.5) * 2);
1309+
// Lerp along the curve using preallocated vectors (no per-render clone allocation)
1310+
const particlePos = particleVecs.current[i];
1311+
if (t < 0.5) {
1312+
particlePos.lerpVectors(sourcePos, organicCurvePoints[1], t * 2);
1313+
} else {
1314+
particlePos.lerpVectors(organicCurvePoints[1], targetPos, (t - 0.5) * 2);
1315+
}
12991316
// Slight size variation per particle
13001317
const sizeVar = 0.04 + Math.sin(now * 2 + i * 1.5) * 0.015;
13011318
return (

client/src/features/bots/services/AgentPollingService.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export class AgentPollingService {
7979
private activityLevel: 'active' | 'idle' = 'idle';
8080
private lastActivityCheck: number = 0;
8181
private performanceMonitor: PollingPerformanceMonitor;
82+
private subscriberCount: number = 0;
8283

8384
private constructor() {
8485
this.config = {
@@ -111,26 +112,33 @@ export class AgentPollingService {
111112
}
112113
}
113114

114-
115115
public start(): void {
116-
if (this.isPolling) {
117-
logger.warn('Polling already active');
118-
return;
116+
this.subscriberCount++;
117+
if (this.subscriberCount === 1) {
118+
if (this.isPolling) {
119+
logger.warn('Polling already active');
120+
return;
121+
}
122+
logger.debug('Starting agent swarm polling');
123+
this.isPolling = true;
124+
this.poll();
125+
} else {
126+
logger.debug(`Polling subscriber added (count: ${this.subscriberCount})`);
119127
}
120-
121-
logger.debug('Starting agent swarm polling');
122-
this.isPolling = true;
123-
this.poll();
124128
}
125129

126-
127130
public stop(): void {
128-
if (this.pollingTimer) {
129-
clearTimeout(this.pollingTimer);
130-
this.pollingTimer = null;
131+
this.subscriberCount = Math.max(0, this.subscriberCount - 1);
132+
if (this.subscriberCount === 0) {
133+
if (this.pollingTimer) {
134+
clearTimeout(this.pollingTimer);
135+
this.pollingTimer = null;
136+
}
137+
this.isPolling = false;
138+
logger.debug('Agent swarm polling stopped');
139+
} else {
140+
logger.debug(`Polling subscriber removed (count: ${this.subscriberCount})`);
131141
}
132-
this.isPolling = false;
133-
logger.debug('Agent swarm polling stopped');
134142
}
135143

136144

client/src/features/design-system/patterns/MarkdownRenderer.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,18 @@ const InteractiveCodeBlock = ({ language, code, className }: { language: string;
4444

4545
// Interactive link component
4646
const InteractiveLink = ({ href, children, ...props }: { href?: string, children: React.ReactNode }) => {
47-
const isExternal = href?.startsWith('http');
47+
const safeHref = (() => {
48+
try {
49+
const u = new URL(href!, window.location.origin);
50+
return ['http:', 'https:', 'mailto:'].includes(u.protocol) ? u.href : '#';
51+
} catch {
52+
return '#';
53+
}
54+
})();
55+
const isExternal = safeHref.startsWith('http');
4856
return (
4957
<a
50-
href={href}
58+
href={safeHref}
5159
target={isExternal ? "_blank" : undefined}
5260
rel={isExternal ? "noopener noreferrer" : undefined}
5361
className="text-primary hover:underline inline-flex items-center gap-1"

client/src/features/graph/components/ClusterHulls.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useRef, useMemo, useState } from 'react';
1+
import React, { useRef, useMemo, useEffect, useState } from 'react';
22
import { useFrame } from '@react-three/fiber';
33
import * as THREE from 'three';
44
import { ConvexGeometry } from 'three/examples/jsm/geometries/ConvexGeometry.js';
@@ -179,11 +179,13 @@ export const ClusterHulls: React.FC<ClusterHullsProps> = ({
179179

180180
// ---- Cleanup previous geometries when entries change ----
181181
const prevGeometries = useRef<THREE.BufferGeometry[]>([]);
182-
useMemo(() => {
183-
for (const geo of prevGeometries.current) {
184-
geo.dispose();
185-
}
182+
useEffect(() => {
186183
prevGeometries.current = hullEntries.map((e) => e.geometry);
184+
return () => {
185+
for (const geo of prevGeometries.current) {
186+
geo.dispose();
187+
}
188+
};
187189
}, [hullEntries]);
188190

189191
if (!enabled) return null;

0 commit comments

Comments
 (0)