1- /**
2- * useEngineBoardBridge -- React hook that bridges SwarmOrchestrator's
3- * TypedEventEmitter events to the Zustand board store.
4- *
5- * Maps engine lifecycle, task, topology, and guard events to board node
6- * creation, status updates, layout repositioning, and receipt nodes.
7- *
8- * Follows the same pattern as useCoordinatorBoardBridge:
9- * - Subscribe in useEffect
10- * - Dedup by agentId/taskId before creating nodes (INTG-08)
11- * - Call useSwarmBoardStore.getState().actions.* (not hook selectors)
12- * - Clean up on unmount
13- *
14- * Additionally implements the evaluating glow pattern from
15- * usePolicyEvalBoardBridge for guard.evaluated events (INTG-09).
16- */
1+ /** Bridges SwarmOrchestrator events to the Zustand board store. */
172
183import { useEffect , useRef } from "react" ;
194import type { SwarmOrchestrator } from "@clawdstrike/swarm-engine" ;
@@ -27,18 +12,9 @@ import type { Node } from "@xyflow/react";
2712import { computeLayout } from "@/features/swarm/layout/topology-layout" ;
2813import { nextNodePosition } from "./node-position" ;
2914
30- // ---------------------------------------------------------------------------
31- // Constants
32- // ---------------------------------------------------------------------------
33-
34- /** Duration in ms that the evaluating glow remains visible. */
3515const EVAL_GLOW_DURATION_MS = 2000 ;
3616const DEFAULT_LAYOUT_VIEWPORT = { width : 1200 , height : 800 } ;
3717
38- // ---------------------------------------------------------------------------
39- // Status mapping: engine AgentSessionStatus -> board SessionStatus
40- // ---------------------------------------------------------------------------
41-
4218function mapEngineStatus ( engineStatus : string ) : SessionStatus {
4319 switch ( engineStatus ) {
4420 case "running" :
@@ -208,17 +184,7 @@ function getLayoutViewport(): { width: number; height: number } {
208184 } ;
209185}
210186
211- // ---------------------------------------------------------------------------
212- // Hook
213- // ---------------------------------------------------------------------------
214-
215- /**
216- * Bridge SwarmOrchestrator engine events to the Zustand board store.
217- *
218- * @param engine - The SwarmOrchestrator instance, or null if unavailable.
219- */
220187export function useEngineBoardBridge ( engine : SwarmOrchestrator | null ) : void {
221- // Glow tracking refs (matching usePolicyEvalBoardBridge exactly)
222188 const timeoutsRef = useRef < Map < string , ReturnType < typeof setTimeout > > > ( new Map ( ) ) ;
223189 const restoreStatusRef = useRef < Map < string , SessionStatus > > ( new Map ( ) ) ;
224190
@@ -230,18 +196,12 @@ export function useEngineBoardBridge(engine: SwarmOrchestrator | null): void {
230196 const timeouts = timeoutsRef . current ;
231197 const restoreStatuses = restoreStatusRef . current ;
232198
233- // Access the shared event emitter via the orchestrator's public accessor.
234199 const events = engine . getEvents ( ) ;
235200 seedBoardFromEngineSnapshot ( engine ) ;
236201
237- // -----------------------------------------------------------------------
238- // 1. agent.spawned -> addNode({ nodeType: "agentSession" }) (INTG-02, INTG-08)
239- // -----------------------------------------------------------------------
240202 unsubs . push (
241203 events . on ( "agent.spawned" , ( event : any ) => {
242204 const { nodes, actions } = store ( ) ;
243-
244- // Dedup: skip if a node with this agentId already exists
245205 if ( nodes . some ( ( n : Node < SwarmBoardNodeData > ) => n . data . agentId === event . agent . id ) ) return ;
246206
247207 const position = nextNodePosition ( nodes ) ;
@@ -262,9 +222,6 @@ export function useEngineBoardBridge(engine: SwarmOrchestrator | null): void {
262222 } ) ,
263223 ) ;
264224
265- // -----------------------------------------------------------------------
266- // 2. agent.status_changed -> updateNode (INTG-02)
267- // -----------------------------------------------------------------------
268225 unsubs . push (
269226 events . on ( "agent.status_changed" , ( event : any ) => {
270227 const { nodes, actions } = store ( ) ;
@@ -279,12 +236,7 @@ export function useEngineBoardBridge(engine: SwarmOrchestrator | null): void {
279236 } ) ,
280237 ) ;
281238
282- // -----------------------------------------------------------------------
283- // 3. agent.heartbeat -> updateNode (INTG-02)
284- // Engine-managed nodes don't have a sessionId, so setSessionMetadata
285- // (which matches by sessionId) would be a no-op. Use updateNode which
286- // matches by node.id directly.
287- // -----------------------------------------------------------------------
239+ // Engine-managed nodes lack sessionId, so use updateNode (not setSessionMetadata).
288240 unsubs . push (
289241 events . on ( "agent.heartbeat" , ( event : any ) => {
290242 const { nodes, actions } = store ( ) ;
@@ -301,9 +253,6 @@ export function useEngineBoardBridge(engine: SwarmOrchestrator | null): void {
301253 } ) ,
302254 ) ;
303255
304- // -----------------------------------------------------------------------
305- // 4. agent.terminated -> updateNode({ status: "completed" }) (INTG-02)
306- // -----------------------------------------------------------------------
307256 unsubs . push (
308257 events . on ( "agent.terminated" , ( event : any ) => {
309258 const { nodes, actions } = store ( ) ;
@@ -320,23 +269,16 @@ export function useEngineBoardBridge(engine: SwarmOrchestrator | null): void {
320269 } ) ,
321270 ) ;
322271
323- // -----------------------------------------------------------------------
324- // 5. task.created -> addNode({ nodeType: "terminalTask" }) + addEdge (INTG-02, INTG-08)
325- // -----------------------------------------------------------------------
326272 unsubs . push (
327273 events . on ( "task.created" , ( event : any ) => {
328274 const { nodes, actions } = store ( ) ;
329-
330- // Dedup: skip if a node with this taskId already exists
331275 if ( nodes . some ( ( n : Node < SwarmBoardNodeData > ) => n . data . taskId === event . task . id ) ) return ;
332276
333- // Find parent agent node by assignedTo
334277 const parentNode = nodes . find (
335278 ( n : Node < SwarmBoardNodeData > ) =>
336279 n . data . agentId === event . task . assignedTo ,
337280 ) ;
338281
339- // Position: below parent if found, else nextNodePosition
340282 const position = parentNode
341283 ? { x : parentNode . position . x , y : parentNode . position . y + 200 }
342284 : nextNodePosition ( nodes ) ;
@@ -356,7 +298,6 @@ export function useEngineBoardBridge(engine: SwarmOrchestrator | null): void {
356298 } ,
357299 } ) ;
358300
359- // Add spawned edge from parent agent to task
360301 if ( parentNode ) {
361302 actions . addEdge ( {
362303 id : `edge-spawn-${ taskNode . id } ` ,
@@ -368,9 +309,6 @@ export function useEngineBoardBridge(engine: SwarmOrchestrator | null): void {
368309 } ) ,
369310 ) ;
370311
371- // -----------------------------------------------------------------------
372- // 6. task.completed -> updateNode (INTG-02)
373- // -----------------------------------------------------------------------
374312 unsubs . push (
375313 events . on ( "task.completed" , ( event : any ) => {
376314 const { nodes, actions } = store ( ) ;
@@ -384,9 +322,6 @@ export function useEngineBoardBridge(engine: SwarmOrchestrator | null): void {
384322 } ) ,
385323 ) ;
386324
387- // -----------------------------------------------------------------------
388- // 7. task.failed -> updateNode({ status: "failed" }) (INTG-02)
389- // -----------------------------------------------------------------------
390325 unsubs . push (
391326 events . on ( "task.failed" , ( event : any ) => {
392327 const { nodes, actions } = store ( ) ;
@@ -400,14 +335,9 @@ export function useEngineBoardBridge(engine: SwarmOrchestrator | null): void {
400335 } ) ,
401336 ) ;
402337
403- // -----------------------------------------------------------------------
404- // 8. guard.evaluated -> guardEvaluate action + evaluating glow (INTG-02, INTG-09)
405- // -----------------------------------------------------------------------
406338 unsubs . push (
407339 events . on ( "guard.evaluated" , ( event : any ) => {
408340 const { nodes, actions } = store ( ) ;
409-
410- // Find agent node by action.agentId
411341 const agentNode = nodes . find (
412342 ( n : Node < SwarmBoardNodeData > ) =>
413343 n . data . agentId === event . action ?. agentId ,
@@ -417,30 +347,26 @@ export function useEngineBoardBridge(engine: SwarmOrchestrator | null): void {
417347 const nodeId = agentNode . id ;
418348 const currentStatus = ( agentNode . data as SwarmBoardNodeData ) . status ;
419349
420- // Evaluating glow pattern (copied from usePolicyEvalBoardBridge)
421350 const existingTimeout = timeouts . get ( nodeId ) ;
422351 if ( existingTimeout != null ) {
423352 clearTimeout ( existingTimeout ) ;
424353 } else {
425- // Only save restore status if this is a fresh evaluation
426354 restoreStatuses . set (
427355 nodeId ,
428356 currentStatus === "evaluating" ? "running" : currentStatus ,
429357 ) ;
430358 }
431359
432- // Set the node to evaluating status (triggers gold glow ring)
433360 actions . updateNode ( nodeId , { status : "evaluating" } ) ;
434361
435- // Schedule reset back to previous status after glow duration
436362 const timeout = setTimeout ( ( ) => {
437363 timeouts . delete ( nodeId ) ;
438364 const restoreTo = restoreStatuses . get ( nodeId ) ?? "running" ;
439365 restoreStatuses . delete ( nodeId ) ;
440366 const currentNode = store ( ) . nodes . find ( ( node ) => node . id === nodeId ) ;
441367 const latestStatus = currentNode ?. data . status ;
442368
443- // Do not overwrite fresher lifecycle updates that landed during the glow.
369+ // Don't overwrite fresher lifecycle updates that landed during the glow
444370 if ( latestStatus !== "evaluating" ) {
445371 return ;
446372 }
@@ -450,7 +376,6 @@ export function useEngineBoardBridge(engine: SwarmOrchestrator | null): void {
450376
451377 timeouts . set ( nodeId , timeout ) ;
452378
453- // Create receipt node via guardEvaluate action
454379 actions . guardEvaluate (
455380 nodeId ,
456381 event . result ?. verdict ?? "deny" ,
@@ -466,9 +391,6 @@ export function useEngineBoardBridge(engine: SwarmOrchestrator | null): void {
466391 } ) ,
467392 ) ;
468393
469- // -----------------------------------------------------------------------
470- // 9 & 10. Shared handler for topology.updated and topology.rebalanced
471- // -----------------------------------------------------------------------
472394 function handleTopologyEvent ( topologyState : { type ?: string ; edges ?: Array < { from : string ; to : string } > } | undefined ) : void {
473395 const { nodes, edges, actions } = store ( ) ;
474396 const topoType = ( topologyState ?. type ?? "mesh" ) as Parameters < typeof computeLayout > [ 2 ] ;
@@ -489,23 +411,18 @@ export function useEngineBoardBridge(engine: SwarmOrchestrator | null): void {
489411 actions . applyTopologyLayout ( nextTopologyEdges , result . positions ) ;
490412 }
491413
492- // 9. topology.updated (INTG-02)
493414 unsubs . push (
494415 events . on ( "topology.updated" , ( event : any ) => {
495416 handleTopologyEvent ( event . newTopology ) ;
496417 } ) ,
497418 ) ;
498419
499- // 10. topology.rebalanced (INTG-02)
500420 unsubs . push (
501421 events . on ( "topology.rebalanced" , ( event : any ) => {
502422 handleTopologyEvent ( event . topology ) ;
503423 } ) ,
504424 ) ;
505425
506- // -----------------------------------------------------------------------
507- // Cleanup: unsubscribe all events + clear all glow timeouts
508- // -----------------------------------------------------------------------
509426 return ( ) => {
510427 unsubs . forEach ( ( fn ) => fn ( ) ) ;
511428
0 commit comments