@@ -146,6 +146,7 @@ export class MeshStore implements CommsStore {
146146 { socket : net . Socket ; buffer : MessageBuffer }
147147 > ( ) ;
148148 private peerInfo = new Map < string , PeerInfo > ( ) ;
149+ private staleCheckTimer : ReturnType < typeof setInterval > | undefined ;
149150
150151 onDelivery :
151152 | ( ( agentId : string , event : DeliveryEvent ) => void | Promise < void > )
@@ -250,6 +251,7 @@ export class MeshStore implements CommsStore {
250251
251252 this . coordinatorServer . listen ( COORDINATOR_PORT , COORDINATOR_HOST , ( ) => {
252253 this . isCoordinator = true ;
254+ this . startStaleCheck ( ) ;
253255 this . peerInfo . set ( this . peerId , {
254256 id : this . peerId ,
255257 port : this . dataPort ,
@@ -880,6 +882,54 @@ export class MeshStore implements CommsStore {
880882 return events ;
881883 }
882884
885+ // -----------------------------------------------------------------------
886+ // Stale agent cleanup (coordinator only)
887+ // -----------------------------------------------------------------------
888+
889+ private startStaleCheck ( ) : void {
890+ if ( this . staleCheckTimer ) return ;
891+ this . staleCheckTimer = setInterval ( ( ) => {
892+ void this . probeStaleAgents ( ) ;
893+ } , 5000 ) ;
894+ }
895+
896+ private stopStaleCheck ( ) : void {
897+ if ( this . staleCheckTimer ) {
898+ clearInterval ( this . staleCheckTimer ) ;
899+ this . staleCheckTimer = undefined ;
900+ }
901+ }
902+
903+ private async probeStaleAgents ( ) : Promise < void > {
904+ const deadIds : string [ ] = [ ] ;
905+
906+ for ( const [ id , agent ] of this . agents ) {
907+ if ( agent . status !== "active" ) continue ;
908+ if ( ! this . isProcessAlive ( agent . pid ) ) {
909+ deadIds . push ( id ) ;
910+ }
911+ }
912+
913+ for ( const id of deadIds ) {
914+ const agent = this . agents . get ( id ) ;
915+ if ( agent ) {
916+ agent . status = "offline" ;
917+ this . agents . set ( id , agent ) ;
918+ }
919+ await this . broadcastPatch ( { type : "agent_offline" , agentId : id } ) ;
920+ }
921+ }
922+
923+ private isProcessAlive ( pid : number ) : boolean {
924+ try {
925+ // Sending signal 0 doesn't kill the process — it just checks existence
926+ process . kill ( pid , 0 ) ;
927+ return true ;
928+ } catch {
929+ return false ;
930+ }
931+ }
932+
883933 // -----------------------------------------------------------------------
884934 // Coordinator handover
885935 // -----------------------------------------------------------------------
@@ -908,6 +958,7 @@ export class MeshStore implements CommsStore {
908958 await writeAsync ( peer . socket , encode ( msg ) ) ;
909959 }
910960
961+ this . stopStaleCheck ( ) ;
911962 this . coordinatorServer ?. close ( ) ;
912963 this . coordinatorServer = undefined ;
913964 this . isCoordinator = false ;
@@ -934,6 +985,7 @@ export class MeshStore implements CommsStore {
934985 }
935986 this . peerConnections . clear ( ) ;
936987
988+ this . stopStaleCheck ( ) ;
937989 this . dataServer ?. close ( ) ;
938990 this . dataServer = undefined ;
939991 this . coordinatorServer ?. close ( ) ;
0 commit comments