@memberjunction/server 6.2.0-edge.5 → 6.2.0-edge.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (401) hide show
  1. package/README.md +27 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/agentSessions/SessionJanitor.d.ts +3 -3
  4. package/dist/agentSessions/SessionJanitor.js +3 -3
  5. package/dist/agentSessions/SessionManager.d.ts +15 -5
  6. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  7. package/dist/agentSessions/SessionManager.js +34 -12
  8. package/dist/agentSessions/SessionManager.js.map +1 -1
  9. package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
  10. package/dist/auth/AuthProviderCatalogRouter.js +2 -0
  11. package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
  12. package/dist/auth/index.d.ts.map +1 -1
  13. package/dist/auth/index.js +51 -10
  14. package/dist/auth/index.js.map +1 -1
  15. package/dist/auth/initializeProviders.d.ts +10 -0
  16. package/dist/auth/initializeProviders.d.ts.map +1 -1
  17. package/dist/auth/initializeProviders.js +37 -1
  18. package/dist/auth/initializeProviders.js.map +1 -1
  19. package/dist/auth/localDevAuth.d.ts +79 -0
  20. package/dist/auth/localDevAuth.d.ts.map +1 -0
  21. package/dist/auth/localDevAuth.js +439 -0
  22. package/dist/auth/localDevAuth.js.map +1 -0
  23. package/dist/auth/magicLink/MagicLinkKeys.d.ts +2 -2
  24. package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
  25. package/dist/auth/magicLink/MagicLinkKeys.js +1 -1
  26. package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
  27. package/dist/auth/magicLink/types.d.ts +12 -0
  28. package/dist/auth/magicLink/types.d.ts.map +1 -1
  29. package/dist/auth/sessionUserClone.d.ts +1 -1
  30. package/dist/auth/sessionUserClone.d.ts.map +1 -1
  31. package/dist/auth/sessionUserClone.js +3 -2
  32. package/dist/auth/sessionUserClone.js.map +1 -1
  33. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts +292 -0
  34. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -0
  35. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +442 -0
  36. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -0
  37. package/dist/broadcast/index.d.ts +6 -0
  38. package/dist/broadcast/index.d.ts.map +1 -0
  39. package/dist/broadcast/index.js +6 -0
  40. package/dist/broadcast/index.js.map +1 -0
  41. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts +43 -0
  42. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts.map +1 -0
  43. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js +144 -0
  44. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js.map +1 -0
  45. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts +46 -0
  46. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts.map +1 -0
  47. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js +149 -0
  48. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js.map +1 -0
  49. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts +49 -0
  50. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts.map +1 -0
  51. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js +131 -0
  52. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js.map +1 -0
  53. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts +24 -0
  54. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts.map +1 -0
  55. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js +51 -0
  56. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js.map +1 -0
  57. package/dist/cache.d.ts +3 -2
  58. package/dist/cache.d.ts.map +1 -1
  59. package/dist/cache.js.map +1 -1
  60. package/dist/config.d.ts +496 -0
  61. package/dist/config.d.ts.map +1 -1
  62. package/dist/config.js +61 -0
  63. package/dist/config.js.map +1 -1
  64. package/dist/context.d.ts +3 -1
  65. package/dist/context.d.ts.map +1 -1
  66. package/dist/context.js +29 -8
  67. package/dist/context.js.map +1 -1
  68. package/dist/coreRoutes.d.ts.map +1 -1
  69. package/dist/coreRoutes.js +14 -0
  70. package/dist/coreRoutes.js.map +1 -1
  71. package/dist/generated/graphql-schemas/__mj.d.ts +635 -14
  72. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  73. package/dist/generated/graphql-schemas/__mj.js +3366 -418
  74. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  75. package/dist/generic/CacheInvalidationResolver.d.ts +50 -30
  76. package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
  77. package/dist/generic/CacheInvalidationResolver.js +154 -79
  78. package/dist/generic/CacheInvalidationResolver.js.map +1 -1
  79. package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
  80. package/dist/generic/OrphanedConversationDetailReconciler.js +2 -0
  81. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  82. package/dist/generic/PubSubManager.d.ts +10 -0
  83. package/dist/generic/PubSubManager.d.ts.map +1 -1
  84. package/dist/generic/PubSubManager.js +44 -1
  85. package/dist/generic/PubSubManager.js.map +1 -1
  86. package/dist/generic/PushStatusResolver.d.ts +39 -6
  87. package/dist/generic/PushStatusResolver.d.ts.map +1 -1
  88. package/dist/generic/PushStatusResolver.js +93 -11
  89. package/dist/generic/PushStatusResolver.js.map +1 -1
  90. package/dist/generic/ResolverBase.d.ts +65 -25
  91. package/dist/generic/ResolverBase.d.ts.map +1 -1
  92. package/dist/generic/ResolverBase.js +201 -101
  93. package/dist/generic/ResolverBase.js.map +1 -1
  94. package/dist/generic/RunViewResolver.d.ts +11 -3
  95. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  96. package/dist/generic/RunViewResolver.js +79 -13
  97. package/dist/generic/RunViewResolver.js.map +1 -1
  98. package/dist/generic/actionOutputFieldSecurity.d.ts +26 -0
  99. package/dist/generic/actionOutputFieldSecurity.d.ts.map +1 -0
  100. package/dist/generic/actionOutputFieldSecurity.js +47 -0
  101. package/dist/generic/actionOutputFieldSecurity.js.map +1 -0
  102. package/dist/generic/fieldSecurityTransport.d.ts +30 -0
  103. package/dist/generic/fieldSecurityTransport.d.ts.map +1 -0
  104. package/dist/generic/fieldSecurityTransport.js +30 -0
  105. package/dist/generic/fieldSecurityTransport.js.map +1 -0
  106. package/dist/index.d.ts +3 -0
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +122 -66
  109. package/dist/index.js.map +1 -1
  110. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  111. package/dist/integration/CustomColumnPromoter.js +6 -1
  112. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  113. package/dist/logging/variablesLoggingMiddleware.d.ts +12 -0
  114. package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
  115. package/dist/logging/variablesLoggingMiddleware.js +50 -1
  116. package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
  117. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -1
  118. package/dist/realtimeSessions/SessionVerificationPortsBase.js +1 -0
  119. package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -1
  120. package/dist/realtimeSessions/ownedRealtimeSession.d.ts +8 -6
  121. package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
  122. package/dist/realtimeSessions/ownedRealtimeSession.js +15 -6
  123. package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
  124. package/dist/realtimeSessions/sessionAccess.d.ts +88 -11
  125. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
  126. package/dist/realtimeSessions/sessionAccess.js +125 -22
  127. package/dist/realtimeSessions/sessionAccess.js.map +1 -1
  128. package/dist/realtimeSessions/sessionConfigStamps.js +1 -1
  129. package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -1
  130. package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -1
  131. package/dist/realtimeSessions/subscriptionAuthorization.js +5 -2
  132. package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -1
  133. package/dist/realtimeSessions/verificationWorkflow.d.ts +2 -0
  134. package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -1
  135. package/dist/realtimeSessions/verificationWorkflow.js +1 -1
  136. package/dist/realtimeSessions/verificationWorkflow.js.map +1 -1
  137. package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
  138. package/dist/realtimeWidget/WidgetRouter.js +19 -3
  139. package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
  140. package/dist/realtimeWidget/WidgetSessionService.d.ts +67 -7
  141. package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
  142. package/dist/realtimeWidget/WidgetSessionService.js +201 -19
  143. package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
  144. package/dist/realtimeWidget/host-identity.d.ts +1 -1
  145. package/dist/realtimeWidget/host-identity.js +1 -1
  146. package/dist/realtimeWidget/widgetCore.d.ts +20 -1
  147. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
  148. package/dist/realtimeWidget/widgetCore.js +17 -1
  149. package/dist/realtimeWidget/widgetCore.js.map +1 -1
  150. package/dist/realtimeWidget/widgetGuestElevation.d.ts +1 -1
  151. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  152. package/dist/realtimeWidget/widgetGuestElevation.js +2 -1
  153. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  154. package/dist/resolvers/ActionResolver.d.ts +19 -1
  155. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  156. package/dist/resolvers/ActionResolver.js +63 -12
  157. package/dist/resolvers/ActionResolver.js.map +1 -1
  158. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  159. package/dist/resolvers/AdhocQueryResolver.js +1 -0
  160. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  161. package/dist/resolvers/AgentRunStatusPublisher.d.ts +18 -4
  162. package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
  163. package/dist/resolvers/AgentRunStatusPublisher.js +32 -8
  164. package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
  165. package/dist/resolvers/AgentSessionResolver.d.ts +10 -4
  166. package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
  167. package/dist/resolvers/AgentSessionResolver.js +40 -17
  168. package/dist/resolvers/AgentSessionResolver.js.map +1 -1
  169. package/dist/resolvers/EntityCommunicationsResolver.d.ts +13 -1
  170. package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -1
  171. package/dist/resolvers/EntityCommunicationsResolver.js +50 -14
  172. package/dist/resolvers/EntityCommunicationsResolver.js.map +1 -1
  173. package/dist/resolvers/FetchEntityVectorsResolver.d.ts +28 -0
  174. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  175. package/dist/resolvers/FetchEntityVectorsResolver.js +117 -21
  176. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  177. package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
  178. package/dist/resolvers/FileCategoryResolver.js +1 -0
  179. package/dist/resolvers/FileCategoryResolver.js.map +1 -1
  180. package/dist/resolvers/FileResolver.d.ts +5 -0
  181. package/dist/resolvers/FileResolver.d.ts.map +1 -1
  182. package/dist/resolvers/FileResolver.js +13 -4
  183. package/dist/resolvers/FileResolver.js.map +1 -1
  184. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  185. package/dist/resolvers/IntegrationDiscoveryResolver.js +45 -7
  186. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  187. package/dist/resolvers/MergeRecordsResolver.d.ts +8 -1
  188. package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
  189. package/dist/resolvers/MergeRecordsResolver.js +31 -3
  190. package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
  191. package/dist/resolvers/PipelineProgressResolver.d.ts +6 -0
  192. package/dist/resolvers/PipelineProgressResolver.d.ts.map +1 -1
  193. package/dist/resolvers/PipelineProgressResolver.js +5 -3
  194. package/dist/resolvers/PipelineProgressResolver.js.map +1 -1
  195. package/dist/resolvers/QueryResolver.d.ts.map +1 -1
  196. package/dist/resolvers/QueryResolver.js +20 -6
  197. package/dist/resolvers/QueryResolver.js.map +1 -1
  198. package/dist/resolvers/QuerySystemUserResolver.d.ts +2 -0
  199. package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
  200. package/dist/resolvers/QuerySystemUserResolver.js +15 -6
  201. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
  202. package/dist/resolvers/RSUResolver.d.ts.map +1 -1
  203. package/dist/resolvers/RSUResolver.js +12 -0
  204. package/dist/resolvers/RSUResolver.js.map +1 -1
  205. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +16 -2
  206. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  207. package/dist/resolvers/RealtimeClientSessionResolver.js +82 -28
  208. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  209. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  210. package/dist/resolvers/RemoteBrowserActionResolver.js +11 -9
  211. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  212. package/dist/resolvers/RunAIAgentResolver.d.ts +56 -8
  213. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  214. package/dist/resolvers/RunAIAgentResolver.js +209 -64
  215. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  216. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  217. package/dist/resolvers/RunTemplateResolver.js +4 -4
  218. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  219. package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
  220. package/dist/resolvers/RunTestResolver.js +3 -1
  221. package/dist/resolvers/RunTestResolver.js.map +1 -1
  222. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  223. package/dist/resolvers/SyncDataResolver.js +4 -0
  224. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  225. package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
  226. package/dist/resolvers/TestQuerySQLResolver.js +1 -0
  227. package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
  228. package/dist/resolvers/TransactionGroupResolver.d.ts +17 -2
  229. package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
  230. package/dist/resolvers/TransactionGroupResolver.js +60 -15
  231. package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
  232. package/dist/resolvers/UserViewResolver.d.ts +1 -1
  233. package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
  234. package/dist/resolvers/UserViewResolver.js +2 -1
  235. package/dist/resolvers/UserViewResolver.js.map +1 -1
  236. package/dist/rest/EntityCRUDHandler.d.ts +6 -0
  237. package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
  238. package/dist/rest/EntityCRUDHandler.js +19 -5
  239. package/dist/rest/EntityCRUDHandler.js.map +1 -1
  240. package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
  241. package/dist/rest/RESTEndpointHandler.js +8 -5
  242. package/dist/rest/RESTEndpointHandler.js.map +1 -1
  243. package/dist/rest/RealtimeRecordingHandler.d.ts +3 -3
  244. package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -1
  245. package/dist/rest/RealtimeRecordingHandler.js +7 -7
  246. package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
  247. package/dist/rest/ViewOperationsHandler.d.ts +6 -0
  248. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  249. package/dist/rest/ViewOperationsHandler.js +22 -5
  250. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  251. package/dist/serverRoleConfigUnits.d.ts +29 -0
  252. package/dist/serverRoleConfigUnits.d.ts.map +1 -0
  253. package/dist/serverRoleConfigUnits.js +33 -0
  254. package/dist/serverRoleConfigUnits.js.map +1 -0
  255. package/dist/services/TaskGraphContinuationDeliverer.d.ts +5 -0
  256. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  257. package/dist/services/TaskGraphContinuationDeliverer.js +42 -15
  258. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  259. package/dist/types.d.ts +23 -1
  260. package/dist/types.d.ts.map +1 -1
  261. package/dist/types.js.map +1 -1
  262. package/package.json +98 -98
  263. package/src/__tests__/ActionResolver.fieldSecurity.test.ts +237 -0
  264. package/src/__tests__/AdhocQueryResolver.dialect.test.ts +137 -0
  265. package/src/__tests__/AgentRunStatusPublisher.test.ts +49 -0
  266. package/src/__tests__/AgentSessionResolver.ownership.test.ts +137 -0
  267. package/src/__tests__/CustomColumnPromoterOnePass.test.ts +15 -1
  268. package/src/__tests__/EntityCommunicationsResolver.actingUser.test.ts +151 -0
  269. package/src/__tests__/EvolutionActsOnWhatItDetects.test.ts +113 -0
  270. package/src/__tests__/FetchEntityVectorsResolver.test.ts +206 -3
  271. package/src/__tests__/FileResolver.fieldSecurity.test.ts +82 -0
  272. package/src/__tests__/PipelineProgressResolver.filter.test.ts +58 -0
  273. package/src/__tests__/QuerySystemUserResolver.fieldSecurity.test.ts +49 -0
  274. package/src/__tests__/RESTEndpointHandler.test.ts +25 -0
  275. package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +23 -3
  276. package/src/__tests__/RealtimeClientSessionResolver.test.ts +324 -46
  277. package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +98 -0
  278. package/src/__tests__/ResolverBase.clientOldValues.test.ts +37 -4
  279. package/src/__tests__/ResolverBase.deleteReturnProjection.test.ts +52 -0
  280. package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +176 -0
  281. package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +23 -1
  282. package/src/__tests__/ResolverBase.publishStatusUpdate.test.ts +68 -0
  283. package/src/__tests__/ResolverBase.userViewCacheAndDedup.test.ts +130 -0
  284. package/src/__tests__/RunAIAgentResolver.branch.test.ts +68 -13
  285. package/src/__tests__/RunAIAgentResolver.conversationWriter.test.ts +210 -0
  286. package/src/__tests__/RunAIAgentResolver.fireAndForgetCompletion.test.ts +209 -0
  287. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +17 -27
  288. package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +8 -2
  289. package/src/__tests__/RunAIAgentResolver.serverReply.test.ts +358 -0
  290. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +4 -1
  291. package/src/__tests__/RunTemplateResolver.test.ts +35 -2
  292. package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +11 -0
  293. package/src/__tests__/SessionManager.test.ts +95 -0
  294. package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +114 -3
  295. package/src/__tests__/TransactionGroupResolver.fieldSecurity.test.ts +199 -0
  296. package/src/__tests__/UserViewResolver.updateWhereClause.test.ts +15 -0
  297. package/src/__tests__/ViewOperationsHandler.test.ts +7 -2
  298. package/src/__tests__/WidgetSessionService.test.ts +583 -4
  299. package/src/__tests__/apiRootBoot.test.ts +1 -1
  300. package/src/__tests__/buildMagicLinkSessionUser.test.ts +96 -0
  301. package/src/__tests__/cacheBroadcastProviders.test.ts +403 -0
  302. package/src/__tests__/cacheInvalidationRecordData.test.ts +45 -0
  303. package/src/__tests__/fieldSecurityFixtures.ts +91 -0
  304. package/src/__tests__/globalCors.test.ts +3 -3
  305. package/src/__tests__/localDevAuth.test.ts +196 -0
  306. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +10 -0
  307. package/src/__tests__/pushStatusResolver.filter.test.ts +57 -1
  308. package/src/__tests__/realtimeRecordingHandler.test.ts +19 -2
  309. package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +29 -1
  310. package/src/__tests__/realtimeSessions/ownedRealtimeSession.anonymous.test.ts +31 -0
  311. package/src/__tests__/realtimeSessions/ownedRealtimeSession.namedScoped.test.ts +37 -0
  312. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +146 -7
  313. package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +1 -1
  314. package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +25 -4
  315. package/src/__tests__/resolverBase.cloudEventChangedFields.test.ts +157 -0
  316. package/src/__tests__/resolverBase.fieldSecurityTransport.test.ts +222 -0
  317. package/src/__tests__/resolverBase.fls.test.ts +146 -97
  318. package/src/__tests__/restTransport.fieldSecurity.test.ts +148 -0
  319. package/src/__tests__/runParamsTimeZone.test.ts +179 -0
  320. package/src/__tests__/runStreams.fieldSecurity.test.ts +100 -0
  321. package/src/__tests__/runViewProperties.test.ts +114 -0
  322. package/src/__tests__/scalabilityBenchmarks.test.ts +765 -0
  323. package/src/__tests__/scalabilityDatabaseIntegration.test.ts +207 -0
  324. package/src/__tests__/sessionUserClone.test.ts +5 -1
  325. package/src/__tests__/startupSweepOrder.test.ts +40 -0
  326. package/src/__tests__/unifiedAuth.test.ts +28 -0
  327. package/src/__tests__/widgetGuestElevation.test.ts +2 -0
  328. package/src/agentSessions/SessionJanitor.ts +3 -3
  329. package/src/agentSessions/SessionManager.ts +35 -12
  330. package/src/auth/AuthProviderCatalogRouter.ts +3 -0
  331. package/src/auth/index.ts +56 -12
  332. package/src/auth/initializeProviders.ts +43 -1
  333. package/src/auth/localDevAuth.ts +528 -0
  334. package/src/auth/magicLink/MagicLinkKeys.ts +2 -2
  335. package/src/auth/magicLink/types.ts +10 -0
  336. package/src/auth/sessionUserClone.ts +3 -2
  337. package/src/broadcast/BaseCacheInvalidationBroadcastProvider.ts +674 -0
  338. package/src/broadcast/index.ts +5 -0
  339. package/src/broadcast/providers/AWSIoTCoreBroadcastProvider.ts +180 -0
  340. package/src/broadcast/providers/AzureWebPubSubBroadcastProvider.ts +182 -0
  341. package/src/broadcast/providers/GCPPubSubBroadcastProvider.ts +169 -0
  342. package/src/broadcast/providers/SequencedRoleBucketBroadcastProvider.ts +57 -0
  343. package/src/cache.ts +2 -1
  344. package/src/config.ts +69 -0
  345. package/src/context.ts +32 -8
  346. package/src/coreRoutes.ts +15 -0
  347. package/src/generated/graphql-schemas/__mj.ts +2239 -102
  348. package/src/generic/CacheInvalidationResolver.ts +135 -78
  349. package/src/generic/OrphanedConversationDetailReconciler.ts +4 -2
  350. package/src/generic/PubSubManager.ts +54 -1
  351. package/src/generic/PushStatusResolver.ts +108 -11
  352. package/src/generic/ResolverBase.ts +237 -107
  353. package/src/generic/RunViewResolver.ts +80 -15
  354. package/src/generic/actionOutputFieldSecurity.ts +75 -0
  355. package/src/generic/fieldSecurityTransport.ts +62 -0
  356. package/src/index.ts +127 -68
  357. package/src/integration/CustomColumnPromoter.ts +6 -1
  358. package/src/logging/variablesLoggingMiddleware.ts +59 -3
  359. package/src/realtimeSessions/README.md +7 -3
  360. package/src/realtimeSessions/SessionVerificationPortsBase.ts +1 -0
  361. package/src/realtimeSessions/ownedRealtimeSession.ts +20 -6
  362. package/src/realtimeSessions/sessionAccess.ts +181 -25
  363. package/src/realtimeSessions/sessionConfigStamps.ts +1 -1
  364. package/src/realtimeSessions/subscriptionAuthorization.ts +4 -2
  365. package/src/realtimeSessions/verificationWorkflow.ts +3 -1
  366. package/src/realtimeWidget/WidgetRouter.ts +22 -4
  367. package/src/realtimeWidget/WidgetSessionService.ts +252 -17
  368. package/src/realtimeWidget/host-identity.ts +1 -1
  369. package/src/realtimeWidget/widgetCore.ts +30 -1
  370. package/src/realtimeWidget/widgetGuestElevation.ts +2 -1
  371. package/src/resolvers/ActionResolver.ts +78 -15
  372. package/src/resolvers/AdhocQueryResolver.ts +1 -0
  373. package/src/resolvers/AgentRunStatusPublisher.ts +36 -8
  374. package/src/resolvers/AgentSessionResolver.ts +46 -18
  375. package/src/resolvers/EntityCommunicationsResolver.ts +56 -15
  376. package/src/resolvers/FetchEntityVectorsResolver.ts +132 -21
  377. package/src/resolvers/FileCategoryResolver.ts +1 -0
  378. package/src/resolvers/FileResolver.ts +14 -4
  379. package/src/resolvers/IntegrationDiscoveryResolver.ts +49 -7
  380. package/src/resolvers/MergeRecordsResolver.ts +23 -2
  381. package/src/resolvers/PipelineProgressResolver.ts +12 -3
  382. package/src/resolvers/QueryResolver.ts +20 -6
  383. package/src/resolvers/QuerySystemUserResolver.ts +15 -6
  384. package/src/resolvers/RSUResolver.ts +12 -0
  385. package/src/resolvers/RealtimeClientSessionResolver.ts +104 -33
  386. package/src/resolvers/RemoteBrowserActionResolver.ts +15 -10
  387. package/src/resolvers/RunAIAgentResolver.ts +258 -82
  388. package/src/resolvers/RunTemplateResolver.ts +6 -4
  389. package/src/resolvers/RunTestResolver.ts +3 -1
  390. package/src/resolvers/SyncDataResolver.ts +4 -0
  391. package/src/resolvers/TestQuerySQLResolver.ts +1 -0
  392. package/src/resolvers/TransactionGroupResolver.ts +66 -15
  393. package/src/resolvers/UserViewResolver.ts +2 -1
  394. package/src/resolvers/__tests__/MergeRecordsResolver.test.ts +43 -0
  395. package/src/rest/EntityCRUDHandler.ts +22 -5
  396. package/src/rest/RESTEndpointHandler.ts +8 -5
  397. package/src/rest/RealtimeRecordingHandler.ts +13 -10
  398. package/src/rest/ViewOperationsHandler.ts +24 -6
  399. package/src/serverRoleConfigUnits.ts +48 -0
  400. package/src/services/TaskGraphContinuationDeliverer.ts +52 -20
  401. package/src/types.ts +23 -1
@@ -41,6 +41,9 @@ vi.mock('@memberjunction/api-keys', async (importOriginal) => ({
41
41
 
42
42
  vi.mock('@memberjunction/ai-agents', () => ({
43
43
  AgentRunner: class {
44
+ async CreateAgentReplyDetail() {
45
+ return { ID: 'reply-1', __mj_CreatedAt: new Date() };
46
+ }
44
47
  RunAgentInConversation(...args: unknown[]) {
45
48
  return hoisted.runAgentInConversation(...args);
46
49
  }
@@ -72,7 +75,10 @@ const CLIENT_DATA = JSON.stringify({
72
75
 
73
76
  const USER = { ID: 'user-1', Email: 'browser.user@example.com' };
74
77
  const SYSTEM_USER = { ID: 'system-user', Email: 'system@example.com' };
75
- const GUEST = { ID: 'guest-1', Email: 'anonymous@magic-link.local', IsMagicLinkAnonymous: true };
78
+ // A widget guest always carries its per-visitor scope id (mj_sid) as its MagicLinkScope.ResourceID, and its
79
+ // conversation is stamped with the same value as ExternalID: that stamp is what tells one guest from another
80
+ // under the shared Anonymous user (MJ#6032), so the fake conversation below carries it.
81
+ const GUEST = { ID: 'guest-1', Email: 'anonymous@magic-link.local', IsMagicLinkAnonymous: true, AnonymousVisitorID: 'sid-1', MagicLinkScope: { ResourceID: 'sid-1' } };
76
82
 
77
83
  type Fn = ReturnType<typeof vi.fn>;
78
84
 
@@ -100,7 +106,7 @@ function makeResolver(): { resolver: RunAIAgentResolver; seams: ResolverSeams }
100
106
 
101
107
  function makeContext(userPayload: Partial<UserPayload>): AppContext {
102
108
  const provider = {
103
- GetEntityObject: vi.fn().mockResolvedValue({ ConversationID: 'conv-1', Load: vi.fn().mockResolvedValue(true) }),
109
+ GetEntityObject: vi.fn().mockResolvedValue({ ConversationID: 'conv-1', UserID: GUEST.ID, ExternalID: 'sid-1', Load: vi.fn().mockResolvedValue(true) }),
104
110
  };
105
111
  return {
106
112
  userPayload: { sessionId: 'session-1', email: USER.Email, userRecord: USER, ...userPayload } as UserPayload,
@@ -0,0 +1,358 @@
1
+ /**
2
+ * @fileoverview `RunAIAgentFromConversationDetail` after A19: only the server writes agent replies.
3
+ *
4
+ * The mutation names the user's message. The server loads the history, creates the agent's reply row as the person
5
+ * asking (marked as server-authored by `AgentRunner.CreateAgentReplyDetail`), and runs the agent into it. The
6
+ * fire-and-forget acknowledgement names the row (`conversationDetailId`, `createdAt`) and is returned before the run
7
+ * publishes anything; the run's status messages carry the same ID (`AgentRunStatusPublisher`, its own tests).
8
+ */
9
+ import 'reflect-metadata'; // must precede any type-graphql decorator evaluation
10
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
11
+ import type { PubSubEngine } from 'type-graphql';
12
+
13
+ vi.mock('type-graphql', () => {
14
+ const decoratorFactory = (..._args: unknown[]) => (..._decorated: unknown[]) => undefined;
15
+ const exportNames = [
16
+ 'Resolver', 'Query', 'Mutation', 'Subscription', 'Arg', 'Args', 'ArgsType', 'Ctx', 'Root',
17
+ 'Info', 'Field', 'FieldResolver', 'ObjectType', 'InputType', 'InterfaceType', 'Authorized',
18
+ 'UseMiddleware', 'Extensions', 'Directive', 'ID', 'Int', 'Float', 'GraphQLISODateTime',
19
+ 'GraphQLTimestamp', 'registerEnumType', 'createMethodDecorator', 'createParamDecorator',
20
+ 'buildSchema', 'buildSchemaSync', 'PubSub',
21
+ ];
22
+ return Object.fromEntries(exportNames.map((name) => [name, decoratorFactory]));
23
+ });
24
+
25
+ const hoisted = vi.hoisted(() => ({
26
+ createReply: vi.fn(),
27
+ runAgentInConversation: vi.fn(),
28
+ widgetContext: vi.fn(),
29
+ agents: new Map<string, { ID: string; Name: string; Status: string }>(),
30
+ }));
31
+
32
+ vi.mock('@memberjunction/aiengine', () => ({
33
+ AIEngine: {
34
+ Instance: {
35
+ Config: async () => undefined,
36
+ GetAgentByID: (id: string) => hoisted.agents.get(id),
37
+ },
38
+ },
39
+ GetAttachmentService: () => ({}),
40
+ }));
41
+
42
+ vi.mock('@memberjunction/ai-agents', () => ({
43
+ AgentRunner: class {
44
+ CreateAgentReplyDetail(args: unknown) {
45
+ return hoisted.createReply(args);
46
+ }
47
+ RunAgentInConversation(...args: unknown[]) {
48
+ return hoisted.runAgentInConversation(...args);
49
+ }
50
+ },
51
+ ArtifactToolManager: class {},
52
+ }));
53
+
54
+ vi.mock('../realtimeWidget/widgetGuestElevation.js', () => ({
55
+ ResolveWidgetGuestRunContext: (...args: unknown[]) => hoisted.widgetContext(...args),
56
+ ElevateUserPayload: (payload: Record<string, unknown>, user: unknown) => ({ ...payload, userRecord: user }),
57
+ }));
58
+
59
+ import { RunAIAgentResolver } from '../resolvers/RunAIAgentResolver.js';
60
+ import type { AppContext, UserPayload } from '../types.js';
61
+
62
+ const USER = { ID: 'user-1', Email: 'user@example.com' };
63
+ const SYSTEM = { ID: 'system-1', Email: 'system@example.com' };
64
+ const CREATED_AT = new Date('2026-10-12T10:00:00.000Z');
65
+ const REPLY = { ID: 'reply-1', ConversationID: 'conv-1', __mj_CreatedAt: CREATED_AT };
66
+
67
+ type Fn = ReturnType<typeof vi.fn>;
68
+
69
+ interface ResolverSeams {
70
+ CheckAPIKeyScopeAuthorization: Fn;
71
+ GetUserFromPayload: Fn;
72
+ loadConversationHistoryWithAttachments: Fn;
73
+ executeAIAgent: Fn;
74
+ executeAgentInBackground: Fn;
75
+ validateAgent: Fn;
76
+ persistInFlightAgentFailure: Fn;
77
+ }
78
+
79
+ /** The order the turn's steps happened in. */
80
+ let steps: string[] = [];
81
+
82
+ function makeResolver(): { resolver: RunAIAgentResolver; seams: ResolverSeams } {
83
+ const resolver = new RunAIAgentResolver();
84
+ const seams = resolver as unknown as ResolverSeams;
85
+ seams.CheckAPIKeyScopeAuthorization = vi.fn().mockResolvedValue(undefined);
86
+ seams.GetUserFromPayload = vi.fn((payload: { userRecord: unknown }) => payload.userRecord);
87
+ seams.loadConversationHistoryWithAttachments = vi.fn(async () => {
88
+ steps.push('history');
89
+ return [];
90
+ });
91
+ seams.executeAIAgent = vi.fn(async () => {
92
+ steps.push('run');
93
+ return { success: true, result: '{}' };
94
+ });
95
+ seams.executeAgentInBackground = vi.fn(() => {
96
+ steps.push('run');
97
+ });
98
+ return { resolver, seams };
99
+ }
100
+
101
+ /**
102
+ * A provider whose conversation details are the user's message ('user-msg-1', in conv-1) plus `others`: message ID →
103
+ * its conversation. A load of any other ID finds nothing, as when the caller can't read it.
104
+ */
105
+ function makeContext(userMessageFound = true, others: Record<string, string> = {}): { ctx: AppContext; loads: Fn } {
106
+ const conversations: Record<string, string> = { ...(userMessageFound ? { 'user-msg-1': 'conv-1' } : {}), ...others };
107
+ const loads = vi.fn();
108
+ const provider = {
109
+ GetEntityObject: vi.fn(async () => {
110
+ const row = { ConversationID: '', Load: async (id: string) => false };
111
+ row.Load = async (id: string) => {
112
+ loads(id);
113
+ row.ConversationID = conversations[id] ?? '';
114
+ return id in conversations;
115
+ };
116
+ return row;
117
+ }),
118
+ };
119
+ const ctx = {
120
+ userPayload: { sessionId: 'session-1', userRecord: USER } as unknown as UserPayload,
121
+ providers: [{ type: 'Read-Write', provider }],
122
+ dataSource: {},
123
+ } as unknown as AppContext;
124
+ return { ctx, loads };
125
+ }
126
+
127
+ const pubSub = { publish: vi.fn() } as unknown as PubSubEngine;
128
+
129
+ function runMutation(
130
+ resolver: RunAIAgentResolver,
131
+ ctx: AppContext,
132
+ fireAndForget: boolean,
133
+ threading: { replyParentId?: string; announcedByAgentId?: string } = {}
134
+ ) {
135
+ return resolver.RunAIAgentFromConversationDetail(
136
+ 'user-msg-1', 'agent-1', ctx, 'session-1', pubSub,
137
+ undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined,
138
+ undefined, undefined, fireAndForget, undefined, undefined, undefined,
139
+ threading.replyParentId, threading.announcedByAgentId
140
+ );
141
+ }
142
+
143
+ beforeEach(() => {
144
+ steps = [];
145
+ hoisted.agents.clear();
146
+ hoisted.agents.set('agent-1', { ID: 'agent-1', Name: 'Research', Status: 'Active' });
147
+ hoisted.agents.set('manager-1', { ID: 'manager-1', Name: 'Sage', Status: 'Active' });
148
+ hoisted.agents.set('retired-1', { ID: 'retired-1', Name: 'Old', Status: 'Disabled' });
149
+ hoisted.widgetContext.mockReset().mockResolvedValue(null);
150
+ hoisted.createReply.mockReset().mockImplementation(async () => {
151
+ steps.push('reply');
152
+ return REPLY;
153
+ });
154
+ vi.spyOn(console, 'log').mockImplementation(() => undefined);
155
+ });
156
+
157
+ afterEach(() => vi.restoreAllMocks());
158
+
159
+ describe('RunAIAgentFromConversationDetail — the server creates the reply row', () => {
160
+ it("acks the reply row it created, with its ID and creation time, before the run publishes anything", async () => {
161
+ const { resolver, seams } = makeResolver();
162
+ const { ctx } = makeContext();
163
+
164
+ const result = await runMutation(resolver, ctx, true);
165
+
166
+ expect(result.success).toBe(true);
167
+ expect(JSON.parse(result.result)).toEqual({
168
+ accepted: true,
169
+ fireAndForget: true,
170
+ conversationDetailId: 'reply-1',
171
+ createdAt: CREATED_AT.toISOString(),
172
+ });
173
+ // History first (so it never holds the empty reply), then the reply row, then the run.
174
+ expect(steps).toEqual(['history', 'reply', 'run']);
175
+ });
176
+
177
+ it("creates the reply as the person asking, answering their message, for the agent that runs", async () => {
178
+ const { resolver } = makeResolver();
179
+ const { ctx } = makeContext();
180
+
181
+ await runMutation(resolver, ctx, true);
182
+
183
+ expect(hoisted.createReply).toHaveBeenCalledOnce();
184
+ expect(hoisted.createReply.mock.calls[0][0]).toMatchObject({
185
+ conversationId: 'conv-1',
186
+ agentId: 'agent-1',
187
+ contextUser: USER,
188
+ parentId: 'user-msg-1',
189
+ });
190
+ });
191
+
192
+ it('runs the agent into that row on both paths: its ID and the row itself reach the run', async () => {
193
+ const background = makeResolver();
194
+ await runMutation(background.resolver, makeContext().ctx, true);
195
+ const bgArgs = background.seams.executeAgentInBackground.mock.calls[0];
196
+ expect(bgArgs[12]).toBe('reply-1');
197
+ expect(bgArgs.at(-1)).toBe(REPLY);
198
+
199
+ const sync = makeResolver();
200
+ await runMutation(sync.resolver, makeContext().ctx, false);
201
+ const syncArgs = sync.seams.executeAIAgent.mock.calls[0];
202
+ expect(syncArgs[13]).toBe('reply-1');
203
+ expect(syncArgs.at(-1)).toBe(REPLY);
204
+ });
205
+
206
+ it('hands agentResponseDetail through the real executeAIAgent into RunAgentInConversation', async () => {
207
+ const resolver = new RunAIAgentResolver();
208
+ const seams = resolver as unknown as ResolverSeams;
209
+ seams.validateAgent = vi.fn().mockResolvedValue({ ID: 'agent-1', Name: 'Research' });
210
+ seams.persistInFlightAgentFailure = vi.fn().mockResolvedValue(undefined);
211
+ hoisted.runAgentInConversation.mockReset().mockRejectedValue(new Error('stop here'));
212
+ vi.spyOn(console, 'error').mockImplementation(() => undefined);
213
+
214
+ const execute = (resolver as unknown as { executeAIAgent(...args: unknown[]): Promise<unknown> }).executeAIAgent.bind(resolver);
215
+ const userPayload = { sessionId: 'session-1', userRecord: USER };
216
+ await execute(
217
+ {}, {}, 'agent-1', userPayload, '[{"role":"user","content":"hi"}]', 'session-1', pubSub,
218
+ undefined, undefined, undefined, undefined, undefined, undefined, 'reply-1', false, false,
219
+ undefined, undefined, 'conv-1', undefined, undefined, undefined, undefined, undefined,
220
+ false, null, REPLY
221
+ );
222
+
223
+ expect(hoisted.runAgentInConversation).toHaveBeenCalledOnce();
224
+ expect(hoisted.runAgentInConversation.mock.calls[0][1]).toMatchObject({
225
+ conversationDetailId: 'reply-1',
226
+ agentResponseDetail: REPLY,
227
+ });
228
+ });
229
+
230
+ it('hands agentResponseDetail through the real executeAgentInBackground into executeAIAgent', async () => {
231
+ const { resolver, seams } = makeResolver();
232
+ seams.executeAgentInBackground = (
233
+ RunAIAgentResolver.prototype as unknown as { executeAgentInBackground: ResolverSeams['executeAgentInBackground'] }
234
+ ).executeAgentInBackground.bind(resolver);
235
+
236
+ await runMutation(resolver, makeContext().ctx, true);
237
+
238
+ expect(seams.executeAIAgent).toHaveBeenCalledOnce();
239
+ const bgToExecArgs = seams.executeAIAgent.mock.calls[0];
240
+ expect(bgToExecArgs[13]).toBe('reply-1');
241
+ expect(bgToExecArgs.at(-1)).toBe(REPLY);
242
+ });
243
+
244
+ it("loads the user's message as the caller, and creates nothing when they can't read it", async () => {
245
+ const { resolver, seams } = makeResolver();
246
+ const { ctx, loads } = makeContext(false);
247
+ vi.spyOn(console, 'error').mockImplementation(() => undefined);
248
+
249
+ const result = await runMutation(resolver, ctx, true);
250
+
251
+ expect(loads).toHaveBeenCalledWith('user-msg-1');
252
+ expect(result.success).toBe(false);
253
+ expect(result.errorMessage).toContain('user-msg-1');
254
+ expect(hoisted.createReply).not.toHaveBeenCalled();
255
+ expect(seams.executeAgentInBackground).not.toHaveBeenCalled();
256
+ });
257
+
258
+ it('fails the request with the reason when the reply row is refused, and starts no run', async () => {
259
+ const { resolver, seams } = makeResolver();
260
+ hoisted.createReply.mockRejectedValue(new Error('Failed to create agent response conversation detail: You have view-only access to this conversation.'));
261
+ vi.spyOn(console, 'error').mockImplementation(() => undefined);
262
+
263
+ const result = await runMutation(resolver, makeContext().ctx, true);
264
+
265
+ expect(result.success).toBe(false);
266
+ expect(result.errorMessage).toContain('view-only access');
267
+ expect(seams.executeAgentInBackground).not.toHaveBeenCalled();
268
+ });
269
+
270
+ it("writes a widget guest's reply as the guest, while the agent runs under the elevated principal", async () => {
271
+ hoisted.widgetContext.mockResolvedValue({ ElevatedUser: SYSTEM, PinnedAgentId: 'pinned-agent' });
272
+ const { resolver, seams } = makeResolver();
273
+
274
+ await runMutation(resolver, makeContext().ctx, true);
275
+
276
+ expect(hoisted.createReply.mock.calls[0][0]).toMatchObject({ contextUser: USER, agentId: 'pinned-agent' });
277
+ const runPayload = seams.executeAgentInBackground.mock.calls[0][3] as { userRecord: unknown };
278
+ expect(runPayload.userRecord).toBe(SYSTEM);
279
+ });
280
+ });
281
+
282
+ describe('RunAIAgentFromConversationDetail — where the reply sits', () => {
283
+ it("threads the reply under a message of the same conversation the caller names", async () => {
284
+ const { resolver } = makeResolver();
285
+
286
+ const result = await runMutation(resolver, makeContext(true, { 'manager-reply-1': 'conv-1' }).ctx, true, { replyParentId: 'manager-reply-1' });
287
+
288
+ expect(result.success).toBe(true);
289
+ expect(hoisted.createReply.mock.calls[0][0]).toMatchObject({ parentId: 'manager-reply-1' });
290
+ });
291
+
292
+ it('refuses a parent from another conversation, or one the caller cannot read, and creates nothing', async () => {
293
+ vi.spyOn(console, 'error').mockImplementation(() => undefined);
294
+ for (const parent of ['elsewhere-1', 'hidden-1']) {
295
+ const { resolver, seams } = makeResolver();
296
+ hoisted.createReply.mockClear();
297
+
298
+ const result = await runMutation(resolver, makeContext(true, { 'elsewhere-1': 'conv-2' }).ctx, true, { replyParentId: parent });
299
+
300
+ expect(result.success).toBe(false);
301
+ expect(result.errorMessage).toContain(parent);
302
+ expect(hoisted.createReply).not.toHaveBeenCalled();
303
+ expect(seams.executeAgentInBackground).not.toHaveBeenCalled();
304
+ }
305
+ });
306
+
307
+ it("posts the announcing agent's fixed line first, threads the reply under it, and names it in the ack", async () => {
308
+ const { resolver, seams } = makeResolver();
309
+ hoisted.createReply.mockImplementation(async (args: { message?: string }) => (
310
+ args.message ? { ID: 'announcement-1', ConversationID: 'conv-1', __mj_CreatedAt: CREATED_AT } : REPLY
311
+ ));
312
+
313
+ const result = await runMutation(resolver, makeContext().ctx, true, { announcedByAgentId: 'manager-1' });
314
+
315
+ expect(seams.CheckAPIKeyScopeAuthorization).toHaveBeenCalledWith('agent:execute', 'manager-1', expect.anything());
316
+ expect(hoisted.createReply.mock.calls[0][0]).toMatchObject({
317
+ agentId: 'manager-1',
318
+ contextUser: USER,
319
+ parentId: 'user-msg-1',
320
+ message: 'Continuing with **Research** for refinement...',
321
+ status: 'Complete',
322
+ });
323
+ expect(hoisted.createReply.mock.calls[1][0]).toMatchObject({ agentId: 'agent-1', parentId: 'announcement-1' });
324
+ expect(JSON.parse(result.result)).toMatchObject({ conversationDetailId: 'reply-1', announcementDetailId: 'announcement-1' });
325
+ });
326
+
327
+ it('ignores announcedByAgentId when elevated under a widget guest token', async () => {
328
+ hoisted.widgetContext.mockResolvedValue({ ElevatedUser: SYSTEM, PinnedAgentId: 'pinned-agent' });
329
+ const { resolver } = makeResolver();
330
+
331
+ const result = await runMutation(resolver, makeContext().ctx, true, { announcedByAgentId: 'manager-1' });
332
+
333
+ expect(result.success).toBe(true);
334
+ expect(hoisted.createReply).toHaveBeenCalledOnce();
335
+ expect(hoisted.createReply.mock.calls[0][0]).toMatchObject({ agentId: 'pinned-agent', parentId: 'user-msg-1' });
336
+ expect(JSON.parse(result.result)).not.toHaveProperty('announcementDetailId');
337
+ });
338
+
339
+ it('refuses an announcement by an agent that is not active, and creates nothing', async () => {
340
+ const { resolver } = makeResolver();
341
+ vi.spyOn(console, 'error').mockImplementation(() => undefined);
342
+
343
+ const result = await runMutation(resolver, makeContext().ctx, true, { announcedByAgentId: 'retired-1' });
344
+
345
+ expect(result.success).toBe(false);
346
+ expect(hoisted.createReply).not.toHaveBeenCalled();
347
+ });
348
+
349
+ it('without threading, answers the user message directly and the ack names no announcement', async () => {
350
+ const { resolver } = makeResolver();
351
+
352
+ const result = await runMutation(resolver, makeContext().ctx, true);
353
+
354
+ expect(hoisted.createReply).toHaveBeenCalledOnce();
355
+ expect(hoisted.createReply.mock.calls[0][0]).toMatchObject({ parentId: 'user-msg-1' });
356
+ expect(JSON.parse(result.result)).not.toHaveProperty('announcementDetailId');
357
+ });
358
+ });
@@ -29,6 +29,9 @@ vi.mock('@memberjunction/ai-agents', () => ({
29
29
  RunAgentInConversation() {
30
30
  return Promise.resolve({ agentResult: { success: true } });
31
31
  }
32
+ CreateAgentReplyDetail() {
33
+ return Promise.resolve({ ID: 'reply-1', __mj_CreatedAt: new Date() }); // the server creates the reply row (A19)
34
+ }
32
35
  },
33
36
  ArtifactToolManager: class {},
34
37
  }));
@@ -140,7 +143,7 @@ describe('RunAIAgentFromConversationDetail — continuing from a user-stopped ru
140
143
  expect(args[BG_AUTO]).toBe(true);
141
144
  });
142
145
 
143
- it('looks up the predecessor on the branch the reply row was created on', async () => {
146
+ it('looks up the predecessor on the branch the person\'s message was posted on', async () => {
144
147
  await runMutation(resolver, { branchId: 'B' });
145
148
  expect(seams.findUserStoppedPredecessorRunId).toHaveBeenCalledWith('conv-1', 'agent-1', 'B', expect.anything(), expect.anything());
146
149
  await runMutation(resolver, {});
@@ -7,7 +7,7 @@ import 'reflect-metadata';
7
7
  * magic-link and widget sessions, so the resolver applies its own rule. These tests pin that it
8
8
  * refuses scope-limited sessions and callers who cannot read templates before it loads anything.
9
9
  */
10
- import { describe, it, expect, vi } from 'vitest';
10
+ import { describe, it, expect, vi, afterEach } from 'vitest';
11
11
 
12
12
  // As in RunDecisionResolver.test.ts: vitest's esbuild transform does not apply
13
13
  // `emitDecoratorMetadata`, so the decorators are no-ops and the resolver runs as a plain class.
@@ -29,7 +29,8 @@ vi.mock('type-graphql', () => {
29
29
  };
30
30
  });
31
31
 
32
- import { EntityInfo, UserInfo, UserRoleInfo } from '@memberjunction/core';
32
+ import { EntityInfo, RunView, UserInfo, UserRoleInfo } from '@memberjunction/core';
33
+ import { TemplateEngineServer } from '@memberjunction/templates';
33
34
  import { RunTemplateResolver } from '../resolvers/RunTemplateResolver.js';
34
35
  import type { AppContext, UserPayload } from '../types.js';
35
36
 
@@ -130,3 +131,35 @@ describe('RunTemplate authorization', () => {
130
131
  expect(provider.EntityObjectRequests).toEqual(['MJ: Templates']);
131
132
  });
132
133
  });
134
+
135
+ /** Metadata plus a template that loads, so the resolver reaches the render. */
136
+ class RenderingProvider {
137
+ public readonly Template = { Load: async () => true, IsSaved: true, Name: 'T' };
138
+ constructor(private readonly entities: EntityInfo[]) {}
139
+ public EntityByName(name: string): EntityInfo | undefined {
140
+ return this.entities.find((e) => e.Name.trim().toLowerCase() === name.trim().toLowerCase());
141
+ }
142
+ public async GetEntityObject(): Promise<object> {
143
+ return this.Template;
144
+ }
145
+ }
146
+
147
+ describe('RunTemplate render user', () => {
148
+ afterEach(() => vi.restoreAllMocks());
149
+
150
+ it('REGRESSION: renders as the calling user, not whoever last configured the engine', async () => {
151
+ const provider = new RenderingProvider(readableEverywhere());
152
+ const content = { ID: 'content-1', TemplateText: 'x' };
153
+ vi.spyOn(RunView, 'FromMetadataProvider').mockReturnValue({
154
+ RunView: async () => ({ Success: true, Results: [content] }),
155
+ } as unknown as RunView);
156
+ vi.spyOn(TemplateEngineServer.Instance, 'Config').mockResolvedValue(undefined);
157
+ const render = vi.spyOn(TemplateEngineServer.Instance, 'RenderTemplate').mockResolvedValue({ Success: true, Output: 'ok', Message: undefined });
158
+ const caller = makeUser([UI_ROLE_ID]);
159
+
160
+ const result = await new RunTemplateResolver().RunTemplate(TEMPLATE_ID, contextFor(caller, provider as unknown as FakeProvider));
161
+
162
+ expect(result).toMatchObject({ success: true, output: 'ok' });
163
+ expect(render.mock.calls[0][5]).toEqual({ ContextUser: caller });
164
+ });
165
+ });
@@ -60,4 +60,15 @@ describe('RunViewsWithCacheCheck resolver — param forwarding', () => {
60
60
  expect(forwarded.DataSource).toBe('Materialized');
61
61
  expect(forwarded.Aggregates).toEqual([{ expression: 'COUNT(*)', alias: 'total' }]);
62
62
  });
63
+
64
+ it('forwards AlternateBaseView (including empty-string override) and leaves it undefined when unset', async () => {
65
+ const withNamed = await forwardedParams({ EntityName: 'Customers', AlternateBaseView: 'Active Customers' });
66
+ expect(withNamed.AlternateBaseView).toBe('Active Customers');
67
+
68
+ const withEmptyOverride = await forwardedParams({ EntityName: 'Customers', AlternateBaseView: '' });
69
+ expect(withEmptyOverride.AlternateBaseView).toBe('');
70
+
71
+ const unset = await forwardedParams({ EntityName: 'Customers' });
72
+ expect(unset.AlternateBaseView).toBeUndefined();
73
+ });
63
74
  });
@@ -186,6 +186,64 @@ describe('SessionManager.CreateSession', () => {
186
186
  expect(result.HostInstanceID).toBeTypeOf('string');
187
187
  });
188
188
 
189
+ describe('anonymous ExternalID stamp: MagicLinkScope.ResourceID when the token carries one, else mj_sid', () => {
190
+ async function stampFor(anon: Record<string, unknown>): Promise<unknown> {
191
+ hasPermissionMock.mockResolvedValue(true);
192
+ const conversation = makeSessionEntity({ ID: 'conv-anon' });
193
+ const session = makeSessionEntity({ ID: 'session-anon' });
194
+ const { provider } = makeProvider((name) => (name === 'MJ: Conversations' ? conversation : session));
195
+ const anonUser = { ID: 'user-anon', Email: 'anonymous@magic-link.local', IsMagicLinkAnonymous: true, ...anon } as unknown as UserInfo;
196
+ await new SessionManager().CreateSession({ agentID: 'agent-1', userID: 'user-anon' }, anonUser, provider);
197
+ return conversation.ExternalID;
198
+ }
199
+
200
+ it('widget guest (ResourceID == mj_sid): stamps the sessionId', async () => {
201
+ expect(await stampFor({ AnonymousVisitorID: 'sid-w', MagicLinkScope: { ResourceID: 'sid-w' } })).toBe('sid-w');
202
+ });
203
+
204
+ it('anonymous invite WITH a ResourceID: stamps the ResourceID, so scope RLS ({{ScopeResourceID}}) still finds the row', async () => {
205
+ expect(await stampFor({ AnonymousVisitorID: 'visitor-sid-1', MagicLinkScope: { ResourceID: 'invite-scope-R' } })).toBe('invite-scope-R');
206
+ });
207
+
208
+ it('anonymous invite WITHOUT a ResourceID: stamps the per-redemption mj_sid', async () => {
209
+ expect(await stampFor({ AnonymousVisitorID: 'visitor-sid-2' })).toBe('visitor-sid-2');
210
+ });
211
+ });
212
+
213
+ it('stamps conversation ExternalID with MagicLinkScope.ResourceID for a non-anonymous scoped principal', async () => {
214
+ hasPermissionMock.mockResolvedValue(true);
215
+ const conversation = makeSessionEntity({ ID: 'conv-scoped' });
216
+ const session = makeSessionEntity({ ID: 'session-scoped' });
217
+ const { provider } = makeProvider((name) =>
218
+ name === 'MJ: Conversations' ? conversation : session,
219
+ );
220
+ const mgr = new SessionManager();
221
+ const scopedUser = {
222
+ ID: 'user-scoped',
223
+ Email: 'scoped@example.com',
224
+ IsMagicLinkAnonymous: false,
225
+ MagicLinkScope: { ResourceID: 'resource-scoped-1' },
226
+ } as unknown as UserInfo;
227
+
228
+ await mgr.CreateSession({ agentID: 'agent-1', userID: 'user-scoped' }, scopedUser, provider);
229
+
230
+ expect(conversation.ExternalID).toBe('resource-scoped-1');
231
+ });
232
+
233
+ it('does not set conversation ExternalID for a named principal without scope', async () => {
234
+ hasPermissionMock.mockResolvedValue(true);
235
+ const conversation = makeSessionEntity({ ID: 'conv-named' });
236
+ const session = makeSessionEntity({ ID: 'session-named' });
237
+ const { provider } = makeProvider((name) =>
238
+ name === 'MJ: Conversations' ? conversation : session,
239
+ );
240
+ const mgr = new SessionManager();
241
+
242
+ await mgr.CreateSession({ agentID: 'agent-1', userID: 'user-1' }, makeUser(), provider);
243
+
244
+ expect(conversation.ExternalID).toBeUndefined();
245
+ });
246
+
189
247
  it('reuses a supplied conversation without creating a new one', async () => {
190
248
  hasPermissionMock.mockResolvedValue(true);
191
249
  const session = makeSessionEntity({ ID: 'session-x' });
@@ -203,6 +261,43 @@ describe('SessionManager.CreateSession', () => {
203
261
  expect(session.ConversationID).toBe('conv-existing');
204
262
  });
205
263
 
264
+ describe('an anonymous caller binding a session to an existing conversation (#4931)', () => {
265
+ const anonUser = { ID: 'user-anon', Email: 'anonymous@magic-link.local', IsMagicLinkAnonymous: true, AnonymousVisitorID: 'sid-a', MagicLinkScope: { ResourceID: 'sid-a' } } as unknown as UserInfo;
266
+
267
+ function providerWith(conversation: FakeEntity, session: FakeEntity) {
268
+ return makeProvider((name) => (name === 'MJ: Conversations' ? conversation : session));
269
+ }
270
+
271
+ it('refuses another visitor\'s conversation and writes no session row', async () => {
272
+ hasPermissionMock.mockResolvedValue(true);
273
+ const session = makeSessionEntity({ ID: 'session-x' });
274
+ const { provider } = providerWith(makeSessionEntity({ ID: 'conv-b', ExternalID: 'sid-b' }), session);
275
+ await expect(
276
+ new SessionManager().CreateSession({ agentID: 'agent-1', userID: 'user-anon', conversationID: 'conv-b' }, anonUser, provider),
277
+ ).rejects.toBeInstanceOf(SessionAuthorizationError);
278
+ expect(session.Save).not.toHaveBeenCalled();
279
+ });
280
+
281
+ it('refuses a conversation it cannot read', async () => {
282
+ hasPermissionMock.mockResolvedValue(true);
283
+ const session = makeSessionEntity({ ID: 'session-x' });
284
+ const { provider } = providerWith(makeSessionEntity({ ID: 'conv-hidden', Load: vi.fn(async () => false) }), session);
285
+ await expect(
286
+ new SessionManager().CreateSession({ agentID: 'agent-1', userID: 'user-anon', conversationID: 'conv-hidden' }, anonUser, provider),
287
+ ).rejects.toBeInstanceOf(SessionAuthorizationError);
288
+ expect(session.Save).not.toHaveBeenCalled();
289
+ });
290
+
291
+ it('binds to its own conversation (ExternalID equals its scope identity)', async () => {
292
+ hasPermissionMock.mockResolvedValue(true);
293
+ const session = makeSessionEntity({ ID: 'session-x' });
294
+ const { provider } = providerWith(makeSessionEntity({ ID: 'conv-a', ExternalID: 'sid-a' }), session);
295
+ await new SessionManager().CreateSession({ agentID: 'agent-1', userID: 'user-anon', conversationID: 'conv-a' }, anonUser, provider);
296
+ expect(session.ConversationID).toBe('conv-a');
297
+ expect(session.Save).toHaveBeenCalled();
298
+ });
299
+ });
300
+
206
301
  it('throws (with the entity error detail) when the Conversation save fails — no session row attempted', async () => {
207
302
  hasPermissionMock.mockResolvedValue(true);
208
303
  const conversation = makeSessionEntity({