@@ -2,7 +2,10 @@ import { and, eq, gte, inArray, isNull, max, sql } from "@torus-ts/db";
22import {
33 agentSchema ,
44 computedAgentWeightSchema ,
5+ emissionDistributionTargetsSchema ,
6+ namespacePermissionsSchema ,
57 penalizeAgentVotesSchema ,
8+ permissionsSchema ,
69} from "@torus-ts/db/schema" ;
710import type { TRPCRouterRecord } from "@trpc/server" ;
811import { z } from "zod" ;
@@ -408,4 +411,134 @@ export const agentRouter = {
408411 penalties : penaltiesByAgent [ agent . key ] ?? [ ] ,
409412 } ) ) ;
410413 } ) ,
414+
415+ /**
416+ * Returns connection counts for agents:
417+ * - subagentCounts: For root agents, the count of all connected agents in their swarm
418+ * - parentCounts: For all agents, the count of root agents that grant permissions to them
419+ */
420+ agentConnectionCounts : publicProcedure . query ( async ( { ctx } ) => {
421+ // 1. Fetch all relationships from multiple sources
422+ // Source 1: Base permissions (grantorAccountId -> granteeAccountId)
423+ const basePermissions = await ctx . db
424+ . select ( {
425+ grantor : permissionsSchema . grantorAccountId ,
426+ grantee : permissionsSchema . granteeAccountId ,
427+ } )
428+ . from ( permissionsSchema )
429+ . where ( isNull ( permissionsSchema . deletedAt ) ) ;
430+
431+ // Source 2: Namespace permissions (grantor -> recipient)
432+ const namespaceRelations = await ctx . db
433+ . select ( {
434+ grantor : permissionsSchema . grantorAccountId ,
435+ grantee : namespacePermissionsSchema . recipient ,
436+ } )
437+ . from ( namespacePermissionsSchema )
438+ . innerJoin (
439+ permissionsSchema ,
440+ eq (
441+ namespacePermissionsSchema . permissionId ,
442+ permissionsSchema . permissionId ,
443+ ) ,
444+ )
445+ . where ( isNull ( permissionsSchema . deletedAt ) ) ;
446+
447+ // Source 3: Emission distribution targets (grantor -> targetAccountId)
448+ const emissionTargets = await ctx . db
449+ . select ( {
450+ grantor : permissionsSchema . grantorAccountId ,
451+ grantee : emissionDistributionTargetsSchema . targetAccountId ,
452+ } )
453+ . from ( emissionDistributionTargetsSchema )
454+ . innerJoin (
455+ permissionsSchema ,
456+ eq (
457+ emissionDistributionTargetsSchema . permissionId ,
458+ permissionsSchema . permissionId ,
459+ ) ,
460+ )
461+ . where ( isNull ( permissionsSchema . deletedAt ) ) ;
462+
463+ // 2. Fetch whitelisted (root) agents
464+ const whitelistedAgents = await ctx . db
465+ . select ( { key : agentSchema . key } )
466+ . from ( agentSchema )
467+ . where (
468+ and ( eq ( agentSchema . isWhitelisted , true ) , isNull ( agentSchema . deletedAt ) ) ,
469+ ) ;
470+
471+ const whitelistedSet = new Set ( whitelistedAgents . map ( ( a ) => a . key ) ) ;
472+
473+ // 3. Build adjacency lists from all relationship sources
474+ const adjacencyList = new Map < string , Set < string > > ( ) ;
475+ const reverseAdjacencyList = new Map < string , Set < string > > ( ) ;
476+
477+ const addRelation = ( grantor : string , grantee : string | null ) => {
478+ if ( ! grantee ) return ;
479+
480+ // Forward: grantor -> grantee (for subagent traversal)
481+ const grantorNeighbors = adjacencyList . get ( grantor ) ;
482+ if ( grantorNeighbors ) {
483+ grantorNeighbors . add ( grantee ) ;
484+ } else {
485+ adjacencyList . set ( grantor , new Set ( [ grantee ] ) ) ;
486+ }
487+
488+ // Reverse: grantee -> grantor (for parent lookup)
489+ const granteeNeighbors = reverseAdjacencyList . get ( grantee ) ;
490+ if ( granteeNeighbors ) {
491+ granteeNeighbors . add ( grantor ) ;
492+ } else {
493+ reverseAdjacencyList . set ( grantee , new Set ( [ grantor ] ) ) ;
494+ }
495+ } ;
496+
497+ // Add relationships from all sources
498+ for ( const p of basePermissions ) {
499+ addRelation ( p . grantor , p . grantee ) ;
500+ }
501+ for ( const p of namespaceRelations ) {
502+ addRelation ( p . grantor , p . grantee ) ;
503+ }
504+ for ( const p of emissionTargets ) {
505+ addRelation ( p . grantor , p . grantee ) ;
506+ }
507+
508+ // 4. Calculate subagent counts for root agents (BFS traversal)
509+ const subagentCounts : Record < string , number > = { } ;
510+ for ( const rootKey of whitelistedSet ) {
511+ const visited = new Set < string > ( ) ;
512+ const queue = [ rootKey ] ;
513+
514+ while ( queue . length > 0 ) {
515+ const current = queue . shift ( ) ;
516+ if ( ! current || visited . has ( current ) ) continue ;
517+ visited . add ( current ) ;
518+
519+ const neighbors = adjacencyList . get ( current ) ;
520+ if ( neighbors ) {
521+ for ( const neighbor of neighbors ) {
522+ if ( ! visited . has ( neighbor ) ) {
523+ queue . push ( neighbor ) ;
524+ }
525+ }
526+ }
527+ }
528+
529+ visited . delete ( rootKey ) ; // Don't count self
530+ subagentCounts [ rootKey ] = visited . size ;
531+ }
532+
533+ // 5. Calculate parent counts for all agents (only counting root agent parents)
534+ const parentCounts : Record < string , number > = { } ;
535+ for ( const [ grantee , grantors ] of reverseAdjacencyList ) {
536+ const rootParents = [ ...grantors ] . filter ( ( g ) => whitelistedSet . has ( g ) ) ;
537+ if ( rootParents . length > 0 ) {
538+ parentCounts [ grantee ] = rootParents . length ;
539+ }
540+ }
541+
542+ return { subagentCounts, parentCounts } ;
543+ } ) ,
411544} satisfies TRPCRouterRecord ;
0 commit comments