@@ -81,4 +81,124 @@ describe("runGatewayConversationList", () => {
8181 ] ) ;
8282 expect ( result . conversations [ 0 ] ) . not . toHaveProperty ( "sessionId" ) ;
8383 } ) ;
84+
85+ it ( "merges live directory adapters with config-backed entries" , async ( ) => {
86+ const listPeers = vi . fn ( async ( ) => [
87+ { kind : "user" as const , id : "stale-peer" , name : "Stale Peer" } ,
88+ { kind : "user" as const , id : "shared-peer" , name : "Configured Shared Peer" } ,
89+ ] ) ;
90+ const listPeersLive = vi . fn ( async ( ) => [
91+ { kind : "user" as const , id : "live-peer" , name : "Live Peer" } ,
92+ { kind : "user" as const , id : "shared-peer" , name : "Live Shared Peer" } ,
93+ ] ) ;
94+ const listGroups = vi . fn ( async ( ) => [
95+ { kind : "group" as const , id : "stale-group" , name : "Stale Group" } ,
96+ ] ) ;
97+ const listGroupsLive = vi . fn ( async ( ) => [
98+ { kind : "group" as const , id : "live-group" , name : "Live Group" } ,
99+ ] ) ;
100+ const resolvedTargets : string [ ] = [ ] ;
101+ const deps = {
102+ resolveOutboundChannelPlugin : vi . fn ( ( ) => ( {
103+ id : "discord" ,
104+ config : {
105+ listAccountIds : ( ) => [ "default" ] ,
106+ resolveAccount : ( ) => ( { enabled : true , configured : true } ) ,
107+ isEnabled : ( ) => true ,
108+ isConfigured : ( ) => true ,
109+ } ,
110+ directory : { listPeers, listPeersLive, listGroups, listGroupsLive } ,
111+ } ) ) ,
112+ resolveOutboundSessionRoute : vi . fn ( async ( { target } : { target : string } ) => {
113+ resolvedTargets . push ( target ) ;
114+ const direct = target . endsWith ( "-peer" ) ;
115+ return {
116+ sessionKey : `agent:main:discord:${ direct ? "direct" : "channel" } :${ target } ` ,
117+ baseSessionKey : `agent:main:discord:${ direct ? "direct" : "channel" } :${ target } ` ,
118+ peer : { kind : direct ? ( "direct" as const ) : ( "channel" as const ) , id : target } ,
119+ chatType : direct ? ( "direct" as const ) : ( "channel" as const ) ,
120+ from : `discord:${ target } ` ,
121+ to : target ,
122+ } ;
123+ } ) ,
124+ registerConversationAddresses : vi . fn ( ) ,
125+ listConversations : vi . fn ( ( ) => [ ] ) ,
126+ } ;
127+
128+ await runGatewayConversationList (
129+ { config : { } , agentId : "main" , channel : "discord" , limit : 50 } ,
130+ deps as never ,
131+ ) ;
132+
133+ expect ( listPeersLive ) . toHaveBeenCalledOnce ( ) ;
134+ expect ( listGroupsLive ) . toHaveBeenCalledOnce ( ) ;
135+ expect ( listPeers ) . toHaveBeenCalledOnce ( ) ;
136+ expect ( listGroups ) . toHaveBeenCalledOnce ( ) ;
137+ expect ( resolvedTargets ) . toEqual ( [
138+ "stale-peer" ,
139+ "shared-peer" ,
140+ "live-peer" ,
141+ "stale-group" ,
142+ "live-group" ,
143+ ] ) ;
144+ expect ( deps . resolveOutboundSessionRoute ) . toHaveBeenCalledWith (
145+ expect . objectContaining ( {
146+ target : "shared-peer" ,
147+ resolvedTarget : expect . objectContaining ( { display : "Live Shared Peer" } ) ,
148+ } ) ,
149+ ) ;
150+ } ) ;
151+
152+ it ( "retains configured peers when live discovery is empty or fails" , async ( ) => {
153+ const listPeers = vi . fn ( async ( ) => [
154+ { kind : "user" as const , id : "configured-peer" , name : "Configured Peer" } ,
155+ ] ) ;
156+ const listPeersLive = vi . fn ( async ( { query } : { query ?: string } ) =>
157+ query ? [ { kind : "user" as const , id : "live-peer" , name : "Live Peer" } ] : [ ] ,
158+ ) ;
159+ listPeersLive . mockRejectedValueOnce ( new Error ( "directory unavailable" ) ) ;
160+ const resolvedTargets : string [ ] = [ ] ;
161+ const deps = {
162+ resolveOutboundChannelPlugin : vi . fn ( ( ) => ( {
163+ id : "discord" ,
164+ config : {
165+ listAccountIds : ( ) => [ "default" ] ,
166+ resolveAccount : ( ) => ( { enabled : true , configured : true } ) ,
167+ isEnabled : ( ) => true ,
168+ isConfigured : ( ) => true ,
169+ } ,
170+ directory : { listPeers, listPeersLive } ,
171+ } ) ) ,
172+ resolveOutboundSessionRoute : vi . fn ( async ( { target } : { target : string } ) => {
173+ resolvedTargets . push ( target ) ;
174+ return {
175+ sessionKey : `agent:main:discord:direct:${ target } ` ,
176+ baseSessionKey : `agent:main:discord:direct:${ target } ` ,
177+ peer : { kind : "direct" as const , id : target } ,
178+ chatType : "direct" as const ,
179+ from : `discord:${ target } ` ,
180+ to : target ,
181+ } ;
182+ } ) ,
183+ registerConversationAddresses : vi . fn ( ) ,
184+ listConversations : vi . fn ( ( ) => [ ] ) ,
185+ } ;
186+
187+ await runGatewayConversationList (
188+ { config : { } , agentId : "main" , channel : "discord" , limit : 50 } ,
189+ deps as never ,
190+ ) ;
191+ await runGatewayConversationList (
192+ { config : { } , agentId : "main" , channel : "discord" , limit : 50 } ,
193+ deps as never ,
194+ ) ;
195+
196+ expect ( listPeers ) . toHaveBeenCalledTimes ( 2 ) ;
197+ expect ( listPeersLive ) . toHaveBeenCalledTimes ( 2 ) ;
198+ expect ( listPeersLive . mock . calls . map ( ( [ input ] ) => input ) ) . toEqual ( [
199+ expect . not . objectContaining ( { query : expect . anything ( ) } ) ,
200+ expect . not . objectContaining ( { query : expect . anything ( ) } ) ,
201+ ] ) ;
202+ expect ( resolvedTargets ) . toEqual ( [ "configured-peer" , "configured-peer" ] ) ;
203+ } ) ;
84204} ) ;
0 commit comments