@memberjunction/server 6.2.0-edge.3 → 6.2.0-edge.4

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 (346) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/agentSessions/BridgeHostInstance.d.ts +25 -0
  3. package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -0
  4. package/dist/agentSessions/BridgeHostInstance.js +30 -0
  5. package/dist/agentSessions/BridgeHostInstance.js.map +1 -0
  6. package/dist/agentSessions/HostInstance.d.ts +75 -11
  7. package/dist/agentSessions/HostInstance.d.ts.map +1 -1
  8. package/dist/agentSessions/HostInstance.js +99 -28
  9. package/dist/agentSessions/HostInstance.js.map +1 -1
  10. package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
  11. package/dist/agentSessions/ReturningVisitorRecap.js +7 -3
  12. package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
  13. package/dist/agentSessions/SessionJanitor.d.ts +173 -25
  14. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  15. package/dist/agentSessions/SessionJanitor.js +452 -54
  16. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  17. package/dist/agentSessions/SessionManager.d.ts +43 -2
  18. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  19. package/dist/agentSessions/SessionManager.js +99 -8
  20. package/dist/agentSessions/SessionManager.js.map +1 -1
  21. package/dist/agentSessions/SessionRunIDVerifier.d.ts +63 -0
  22. package/dist/agentSessions/SessionRunIDVerifier.d.ts.map +1 -0
  23. package/dist/agentSessions/SessionRunIDVerifier.js +129 -0
  24. package/dist/agentSessions/SessionRunIDVerifier.js.map +1 -0
  25. package/dist/agentSessions/index.d.ts +2 -0
  26. package/dist/agentSessions/index.d.ts.map +1 -1
  27. package/dist/agentSessions/index.js +10 -3
  28. package/dist/agentSessions/index.js.map +1 -1
  29. package/dist/auth/actionAuthorization.d.ts +40 -0
  30. package/dist/auth/actionAuthorization.d.ts.map +1 -0
  31. package/dist/auth/actionAuthorization.js +94 -0
  32. package/dist/auth/actionAuthorization.js.map +1 -0
  33. package/dist/auth/index.d.ts.map +1 -1
  34. package/dist/auth/index.js +3 -10
  35. package/dist/auth/index.js.map +1 -1
  36. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  37. package/dist/auth/magicLink/MagicLinkService.js +6 -3
  38. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  39. package/dist/auth/newUsers.d.ts +10 -0
  40. package/dist/auth/newUsers.d.ts.map +1 -1
  41. package/dist/auth/newUsers.js +80 -11
  42. package/dist/auth/newUsers.js.map +1 -1
  43. package/dist/config.d.ts +2378 -0
  44. package/dist/config.d.ts.map +1 -1
  45. package/dist/config.js +123 -0
  46. package/dist/config.js.map +1 -1
  47. package/dist/context.d.ts +8 -0
  48. package/dist/context.d.ts.map +1 -1
  49. package/dist/context.js +14 -11
  50. package/dist/context.js.map +1 -1
  51. package/dist/generated/graphql-schemas/__mj.d.ts +135 -0
  52. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  53. package/dist/generated/graphql-schemas/__mj.js +706 -0
  54. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  55. package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
  56. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  57. package/dist/generic/ResolverBase.d.ts +20 -4
  58. package/dist/generic/ResolverBase.d.ts.map +1 -1
  59. package/dist/generic/ResolverBase.js +39 -10
  60. package/dist/generic/ResolverBase.js.map +1 -1
  61. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  62. package/dist/generic/RunViewResolver.js +4 -0
  63. package/dist/generic/RunViewResolver.js.map +1 -1
  64. package/dist/index.d.ts +6 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +120 -131
  67. package/dist/index.js.map +1 -1
  68. package/dist/isolatedProvider.d.ts +20 -0
  69. package/dist/isolatedProvider.d.ts.map +1 -0
  70. package/dist/isolatedProvider.js +22 -0
  71. package/dist/isolatedProvider.js.map +1 -0
  72. package/dist/realtimeProxy/RealtimeProxyServer.d.ts +44 -4
  73. package/dist/realtimeProxy/RealtimeProxyServer.d.ts.map +1 -1
  74. package/dist/realtimeProxy/RealtimeProxyServer.js +121 -16
  75. package/dist/realtimeProxy/RealtimeProxyServer.js.map +1 -1
  76. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts +171 -0
  77. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts.map +1 -0
  78. package/dist/realtimeProxy/RealtimeRelayTunnel.js +532 -0
  79. package/dist/realtimeProxy/RealtimeRelayTunnel.js.map +1 -0
  80. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts +148 -0
  81. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts.map +1 -0
  82. package/dist/realtimeSessions/RealtimeSessionEventService.js +191 -0
  83. package/dist/realtimeSessions/RealtimeSessionEventService.js.map +1 -0
  84. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts +66 -0
  85. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -0
  86. package/dist/realtimeSessions/RealtimeSessionVerificationService.js +138 -0
  87. package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -0
  88. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts +38 -0
  89. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -0
  90. package/dist/realtimeSessions/RealtimeVerifyRouter.js +98 -0
  91. package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -0
  92. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts +61 -0
  93. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -0
  94. package/dist/realtimeSessions/SessionVerificationPortsBase.js +149 -0
  95. package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -0
  96. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts +57 -0
  97. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts.map +1 -0
  98. package/dist/realtimeSessions/SlidingWindowRateLimiter.js +69 -0
  99. package/dist/realtimeSessions/SlidingWindowRateLimiter.js.map +1 -0
  100. package/dist/realtimeSessions/index.d.ts +17 -0
  101. package/dist/realtimeSessions/index.d.ts.map +1 -0
  102. package/dist/realtimeSessions/index.js +17 -0
  103. package/dist/realtimeSessions/index.js.map +1 -0
  104. package/dist/realtimeSessions/sessionAccess.d.ts +86 -0
  105. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
  106. package/dist/realtimeSessions/sessionAccess.js +90 -0
  107. package/dist/realtimeSessions/sessionAccess.js.map +1 -0
  108. package/dist/realtimeSessions/sessionConfigStamps.d.ts +70 -0
  109. package/dist/realtimeSessions/sessionConfigStamps.d.ts.map +1 -0
  110. package/dist/realtimeSessions/sessionConfigStamps.js +93 -0
  111. package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -0
  112. package/dist/realtimeSessions/subscriptionAuthorization.d.ts +65 -0
  113. package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -0
  114. package/dist/realtimeSessions/subscriptionAuthorization.js +71 -0
  115. package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -0
  116. package/dist/realtimeSessions/verificationCore.d.ts +243 -0
  117. package/dist/realtimeSessions/verificationCore.d.ts.map +1 -0
  118. package/dist/realtimeSessions/verificationCore.js +356 -0
  119. package/dist/realtimeSessions/verificationCore.js.map +1 -0
  120. package/dist/realtimeSessions/verificationPages.d.ts +66 -0
  121. package/dist/realtimeSessions/verificationPages.d.ts.map +1 -0
  122. package/dist/realtimeSessions/verificationPages.js +110 -0
  123. package/dist/realtimeSessions/verificationPages.js.map +1 -0
  124. package/dist/realtimeSessions/verificationPolicy.d.ts +147 -0
  125. package/dist/realtimeSessions/verificationPolicy.d.ts.map +1 -0
  126. package/dist/realtimeSessions/verificationPolicy.js +249 -0
  127. package/dist/realtimeSessions/verificationPolicy.js.map +1 -0
  128. package/dist/realtimeSessions/verificationWorkflow.d.ts +263 -0
  129. package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -0
  130. package/dist/realtimeSessions/verificationWorkflow.js +435 -0
  131. package/dist/realtimeSessions/verificationWorkflow.js.map +1 -0
  132. package/dist/realtimeWidget/widgetGuestElevation.d.ts +20 -2
  133. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  134. package/dist/realtimeWidget/widgetGuestElevation.js +31 -2
  135. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  136. package/dist/resolvers/ActionResolver.d.ts +31 -5
  137. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  138. package/dist/resolvers/ActionResolver.js +75 -14
  139. package/dist/resolvers/ActionResolver.js.map +1 -1
  140. package/dist/resolvers/AdhocQueryResolver.d.ts +14 -1
  141. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  142. package/dist/resolvers/AdhocQueryResolver.js +21 -5
  143. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  144. package/dist/resolvers/CacheStatsResolver.d.ts +27 -0
  145. package/dist/resolvers/CacheStatsResolver.d.ts.map +1 -1
  146. package/dist/resolvers/CacheStatsResolver.js +113 -2
  147. package/dist/resolvers/CacheStatsResolver.js.map +1 -1
  148. package/dist/resolvers/ClientToolRequestResolver.d.ts +4 -2
  149. package/dist/resolvers/ClientToolRequestResolver.d.ts.map +1 -1
  150. package/dist/resolvers/ClientToolRequestResolver.js +28 -10
  151. package/dist/resolvers/ClientToolRequestResolver.js.map +1 -1
  152. package/dist/resolvers/FetchEntityVectorsResolver.d.ts +12 -1
  153. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  154. package/dist/resolvers/FetchEntityVectorsResolver.js +83 -2
  155. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  156. package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -1
  157. package/dist/resolvers/HumanHandoffResolver.js +7 -1
  158. package/dist/resolvers/HumanHandoffResolver.js.map +1 -1
  159. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  160. package/dist/resolvers/MCPResolver.js +9 -4
  161. package/dist/resolvers/MCPResolver.js.map +1 -1
  162. package/dist/resolvers/RealtimeBridgeResolver.d.ts +54 -2
  163. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
  164. package/dist/resolvers/RealtimeBridgeResolver.js +155 -10
  165. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
  166. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +155 -25
  167. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  168. package/dist/resolvers/RealtimeClientSessionResolver.js +394 -90
  169. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  170. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts +61 -0
  171. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts.map +1 -0
  172. package/dist/resolvers/RealtimeSessionEventsResolver.js +130 -0
  173. package/dist/resolvers/RealtimeSessionEventsResolver.js.map +1 -0
  174. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts +85 -0
  175. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts.map +1 -0
  176. package/dist/resolvers/RealtimeSessionVerificationResolver.js +188 -0
  177. package/dist/resolvers/RealtimeSessionVerificationResolver.js.map +1 -0
  178. package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
  179. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  180. package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
  181. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  182. package/dist/resolvers/RunAIAgentResolver.d.ts +10 -0
  183. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  184. package/dist/resolvers/RunAIAgentResolver.js +68 -11
  185. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  186. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  187. package/dist/resolvers/RunTemplateResolver.js +12 -11
  188. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  189. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  190. package/dist/resolvers/SyncDataResolver.js +6 -2
  191. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  192. package/dist/resolvers/SyncRolesUsersResolver.d.ts +2 -0
  193. package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
  194. package/dist/resolvers/SyncRolesUsersResolver.js +23 -6
  195. package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
  196. package/dist/resolvers/UserAvatarResolver.d.ts +16 -0
  197. package/dist/resolvers/UserAvatarResolver.d.ts.map +1 -0
  198. package/dist/resolvers/UserAvatarResolver.js +124 -0
  199. package/dist/resolvers/UserAvatarResolver.js.map +1 -0
  200. package/dist/resolvers/UserResolver.d.ts.map +1 -1
  201. package/dist/resolvers/UserResolver.js +2 -17
  202. package/dist/resolvers/UserResolver.js.map +1 -1
  203. package/dist/resolvers/agentVisionConsentAudit.d.ts +37 -0
  204. package/dist/resolvers/agentVisionConsentAudit.d.ts.map +1 -0
  205. package/dist/resolvers/agentVisionConsentAudit.js +63 -0
  206. package/dist/resolvers/agentVisionConsentAudit.js.map +1 -0
  207. package/dist/resolvers/avatarInputValidation.d.ts +40 -0
  208. package/dist/resolvers/avatarInputValidation.d.ts.map +1 -0
  209. package/dist/resolvers/avatarInputValidation.js +97 -0
  210. package/dist/resolvers/avatarInputValidation.js.map +1 -0
  211. package/dist/resolvers/currentUserRoles.d.ts +29 -0
  212. package/dist/resolvers/currentUserRoles.d.ts.map +1 -1
  213. package/dist/resolvers/currentUserRoles.js +30 -0
  214. package/dist/resolvers/currentUserRoles.js.map +1 -1
  215. package/dist/resolvers/meetings/meetingMutations.d.ts +6 -0
  216. package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -1
  217. package/dist/resolvers/meetings/meetingMutations.js +21 -0
  218. package/dist/resolvers/meetings/meetingMutations.js.map +1 -1
  219. package/dist/services/TaskGraphContinuationDeliverer.d.ts +4 -3
  220. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  221. package/dist/services/TaskGraphContinuationDeliverer.js +13 -7
  222. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  223. package/dist/sharedCache.d.ts +105 -0
  224. package/dist/sharedCache.d.ts.map +1 -0
  225. package/dist/sharedCache.js +276 -0
  226. package/dist/sharedCache.js.map +1 -0
  227. package/dist/types.d.ts +6 -0
  228. package/dist/types.d.ts.map +1 -1
  229. package/dist/types.js.map +1 -1
  230. package/package.json +97 -95
  231. package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
  232. package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
  233. package/src/__tests__/BridgeHostInstance.test.ts +232 -0
  234. package/src/__tests__/ClientToolRequestResolver.test.ts +116 -0
  235. package/src/__tests__/FetchEntityVectorsResolver.test.ts +285 -0
  236. package/src/__tests__/HostInstance.test.ts +84 -0
  237. package/src/__tests__/HumanHandoffResolver.test.ts +17 -0
  238. package/src/__tests__/MeetingResolver.test.ts +75 -1
  239. package/src/__tests__/RealtimeBridgeResolver.test.ts +415 -6
  240. package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +383 -0
  241. package/src/__tests__/RealtimeClientSessionResolver.test.ts +682 -58
  242. package/src/__tests__/RealtimeProxyServer.test.ts +16 -1
  243. package/src/__tests__/RealtimeRelay.test.ts +876 -0
  244. package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
  245. package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
  246. package/src/__tests__/ResolverBase.clientOldValues.test.ts +6 -3
  247. package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
  248. package/src/__tests__/RunAIAgentResolver.branch.test.ts +169 -0
  249. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +4 -4
  250. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +166 -0
  251. package/src/__tests__/RunTemplateResolver.test.ts +132 -0
  252. package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +63 -0
  253. package/src/__tests__/SessionJanitor.test.ts +845 -18
  254. package/src/__tests__/SessionManager.test.ts +213 -17
  255. package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
  256. package/src/__tests__/SyncRolesUsersResolver.userRoles.test.ts +97 -0
  257. package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +50 -3
  258. package/src/__tests__/UserAvatarResolver.test.ts +255 -0
  259. package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
  260. package/src/__tests__/actionAuthorization.test.ts +130 -0
  261. package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
  262. package/src/__tests__/avatarInputValidation.test.ts +160 -0
  263. package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
  264. package/src/__tests__/magicLinkService.consume.test.ts +6 -0
  265. package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
  266. package/src/__tests__/newUsers.template.test.ts +151 -0
  267. package/src/__tests__/newUsers.test.ts +74 -5
  268. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +17 -2
  269. package/src/__tests__/realtimeSessions/RealtimeSessionEventService.test.ts +201 -0
  270. package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +215 -0
  271. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationResolver.test.ts +102 -0
  272. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationService.test.ts +349 -0
  273. package/src/__tests__/realtimeSessions/RealtimeVerifyRouter.test.ts +131 -0
  274. package/src/__tests__/realtimeSessions/SlidingWindowRateLimiter.test.ts +56 -0
  275. package/src/__tests__/realtimeSessions/helpers.ts +4 -0
  276. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +129 -0
  277. package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +124 -0
  278. package/src/__tests__/realtimeSessions/verificationCore.test.ts +355 -0
  279. package/src/__tests__/realtimeSessions/verificationPages.test.ts +84 -0
  280. package/src/__tests__/realtimeSessions/verificationPolicy.test.ts +226 -0
  281. package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +633 -0
  282. package/src/__tests__/resolverBase.fls.test.ts +87 -4
  283. package/src/__tests__/serverExtensionReservedRoots.test.ts +2 -0
  284. package/src/__tests__/sharedCache.test.ts +310 -0
  285. package/src/__tests__/widgetGuestElevation.test.ts +44 -0
  286. package/src/agentSessions/BridgeHostInstance.ts +32 -0
  287. package/src/agentSessions/HostInstance.ts +124 -28
  288. package/src/agentSessions/ReturningVisitorRecap.ts +7 -3
  289. package/src/agentSessions/SessionJanitor.ts +543 -57
  290. package/src/agentSessions/SessionManager.ts +125 -25
  291. package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
  292. package/src/agentSessions/index.ts +10 -3
  293. package/src/auth/actionAuthorization.ts +116 -0
  294. package/src/auth/index.ts +3 -8
  295. package/src/auth/magicLink/MagicLinkService.ts +6 -3
  296. package/src/auth/newUsers.ts +99 -11
  297. package/src/config.ts +125 -0
  298. package/src/context.ts +15 -10
  299. package/src/generated/graphql-schemas/__mj.ts +496 -1
  300. package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
  301. package/src/generic/ResolverBase.ts +39 -9
  302. package/src/generic/RunViewResolver.ts +4 -0
  303. package/src/index.ts +132 -143
  304. package/src/isolatedProvider.ts +22 -0
  305. package/src/realtimeProxy/RealtimeProxyServer.ts +148 -16
  306. package/src/realtimeProxy/RealtimeRelayTunnel.ts +645 -0
  307. package/src/realtimeSessions/README.md +179 -0
  308. package/src/realtimeSessions/RealtimeSessionEventService.ts +274 -0
  309. package/src/realtimeSessions/RealtimeSessionVerificationService.ts +167 -0
  310. package/src/realtimeSessions/RealtimeVerifyRouter.ts +108 -0
  311. package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
  312. package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
  313. package/src/realtimeSessions/index.ts +16 -0
  314. package/src/realtimeSessions/sessionAccess.ts +141 -0
  315. package/src/realtimeSessions/sessionConfigStamps.ts +123 -0
  316. package/src/realtimeSessions/subscriptionAuthorization.ts +116 -0
  317. package/src/realtimeSessions/verificationCore.ts +502 -0
  318. package/src/realtimeSessions/verificationPages.ts +149 -0
  319. package/src/realtimeSessions/verificationPolicy.ts +321 -0
  320. package/src/realtimeSessions/verificationWorkflow.ts +702 -0
  321. package/src/realtimeWidget/widgetGuestElevation.ts +34 -2
  322. package/src/resolvers/ActionResolver.ts +84 -21
  323. package/src/resolvers/AdhocQueryResolver.ts +24 -6
  324. package/src/resolvers/CacheStatsResolver.ts +89 -2
  325. package/src/resolvers/ClientToolRequestResolver.ts +25 -7
  326. package/src/resolvers/FetchEntityVectorsResolver.ts +97 -2
  327. package/src/resolvers/HumanHandoffResolver.ts +8 -0
  328. package/src/resolvers/MCPResolver.ts +8 -5
  329. package/src/resolvers/RealtimeBridgeResolver.ts +163 -11
  330. package/src/resolvers/RealtimeClientSessionResolver.ts +501 -96
  331. package/src/resolvers/RealtimeSessionEventsResolver.ts +133 -0
  332. package/src/resolvers/RealtimeSessionVerificationResolver.ts +174 -0
  333. package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
  334. package/src/resolvers/RunAIAgentResolver.ts +78 -11
  335. package/src/resolvers/RunTemplateResolver.ts +13 -12
  336. package/src/resolvers/SyncDataResolver.ts +6 -2
  337. package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
  338. package/src/resolvers/UserAvatarResolver.ts +111 -0
  339. package/src/resolvers/UserResolver.ts +2 -16
  340. package/src/resolvers/agentVisionConsentAudit.ts +82 -0
  341. package/src/resolvers/avatarInputValidation.ts +123 -0
  342. package/src/resolvers/currentUserRoles.ts +32 -1
  343. package/src/resolvers/meetings/meetingMutations.ts +21 -0
  344. package/src/services/TaskGraphContinuationDeliverer.ts +13 -7
  345. package/src/sharedCache.ts +302 -0
  346. package/src/types.ts +7 -0
@@ -0,0 +1,129 @@
1
+ /**
2
+ * `ExecuteRemoteBrowserGoal` attaches a "Browser goal" step to the realtime co-agent run named by the
3
+ * session's `coAgentRunID`. The session owner can edit `Config`, so the run must be a record of the
4
+ * session before a step is attached to it.
5
+ */
6
+ // type-graphql decorators on the resolver need the reflect-metadata polyfill loaded first.
7
+ import 'reflect-metadata';
8
+
9
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
10
+
11
+ // As in RemoteBrowserActionRecovery.test.ts: the decorators become no-ops so the resolver's plain logic runs.
12
+ vi.mock('type-graphql', () => {
13
+ const noopDecorator = () => () => undefined;
14
+ return {
15
+ Resolver: noopDecorator,
16
+ Mutation: noopDecorator,
17
+ Query: noopDecorator,
18
+ Subscription: noopDecorator,
19
+ ObjectType: noopDecorator,
20
+ InputType: noopDecorator,
21
+ Field: noopDecorator,
22
+ Arg: noopDecorator,
23
+ Args: noopDecorator,
24
+ Ctx: noopDecorator,
25
+ PubSub: noopDecorator,
26
+ Root: noopDecorator,
27
+ Float: class {},
28
+ Int: class {},
29
+ ID: class {},
30
+ };
31
+ });
32
+
33
+ const achieveGoalMock = vi.fn(async (_sessionID: string, _goal: string, _options: Record<string, unknown>) => ({ Success: true, Status: 'Done' }));
34
+ vi.mock('@memberjunction/remote-browser-server', () => ({
35
+ NormalizeInstanceKey: (key?: string) => key,
36
+ RemoteBrowserEngine: {
37
+ Instance: {
38
+ AchieveGoal: (sessionID: string, goal: string, options: Record<string, unknown>) => achieveGoalMock(sessionID, goal, options),
39
+ },
40
+ },
41
+ }));
42
+
43
+ const beginStepMock = vi.fn(async (_provider: unknown, _user: unknown, _coAgentRunID: string | undefined, _goal: string) => null);
44
+ vi.mock('../agentSessions/remoteBrowserGoalEngine.js', async (importOriginal) => ({
45
+ ...(await importOriginal<typeof import('../agentSessions/remoteBrowserGoalEngine.js')>()),
46
+ BeginBrowserGoalStep: (provider: unknown, user: unknown, coAgentRunID: string | undefined, goal: string) =>
47
+ beginStepMock(provider, user, coAgentRunID, goal),
48
+ FinalizeBrowserGoalStep: vi.fn(async () => undefined),
49
+ }));
50
+
51
+ vi.mock('@memberjunction/aiengine', () => ({ AIEngine: { Instance: { Config: vi.fn(), Prompts: [] } } }));
52
+ vi.mock('@memberjunction/ai-prompts', async (importOriginal) => ({
53
+ ...(await importOriginal<typeof import('@memberjunction/ai-prompts')>()),
54
+ AIPromptRunner: class {},
55
+ }));
56
+ vi.mock('@memberjunction/ai-core-plus', async (importOriginal) => ({
57
+ ...(await importOriginal<typeof import('@memberjunction/ai-core-plus')>()),
58
+ AIPromptParams: class {},
59
+ }));
60
+
61
+ import { RemoteBrowserActionResolver } from '../resolvers/RemoteBrowserActionResolver.js';
62
+ import type { AppContext } from '../types.js';
63
+ import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
64
+ import type { MJAIAgentSessionEntity } from '@memberjunction/core-entities';
65
+
66
+ const SESSION_ID = 'c3c3c3c3-2000-4000-8000-000000000001';
67
+ const CO_RUN_ID = 'c3c3c3c3-2000-4000-8000-000000000002';
68
+ /** A run that exists, but belongs to someone else's session. */
69
+ const FOREIGN_RUN_ID = 'c3c3c3c3-2000-4000-8000-0000000000f1';
70
+
71
+ /** The runs the fake lookup reports as records of the session. */
72
+ let sessionRunIDs: string[] = [];
73
+ const runViewsMock = vi.fn(async (paramsList: Array<{ EntityName?: string }>) =>
74
+ paramsList.map((params) => ({
75
+ Success: true,
76
+ Results: params.EntityName === 'MJ: AI Agent Runs' ? sessionRunIDs.map((ID) => ({ ID })) : [],
77
+ })),
78
+ );
79
+
80
+ /** Bypasses the ownership/provider plumbing (covered elsewhere); the session carries `ConfigJson`. */
81
+ class TestableResolver extends RemoteBrowserActionResolver {
82
+ public ConfigJson = '';
83
+ protected override requireUserAndProvider(): { contextUser: UserInfo; provider: IMetadataProvider } {
84
+ return {
85
+ contextUser: { ID: 'c3c3c3c3-2000-4000-8000-0000000000aa' } as unknown as UserInfo,
86
+ provider: { RunViews: runViewsMock } as unknown as IMetadataProvider,
87
+ };
88
+ }
89
+ protected override async loadOwnedSession(): Promise<MJAIAgentSessionEntity> {
90
+ return { ID: SESSION_ID, AgentID: 'agent-1', Config_: this.ConfigJson } as unknown as MJAIAgentSessionEntity;
91
+ }
92
+ protected override async resolveProviderName(): Promise<string | undefined> {
93
+ return 'Self-Hosted Chrome';
94
+ }
95
+ }
96
+
97
+ const ctx = { userPayload: { sessionId: 'push-sess-1' }, providers: [] } as unknown as AppContext;
98
+
99
+ async function startGoal(config: Record<string, unknown>): Promise<void> {
100
+ const resolver = new TestableResolver();
101
+ resolver.ConfigJson = JSON.stringify(config);
102
+ await resolver.ExecuteRemoteBrowserGoal(SESSION_ID, 'open the docs', ctx);
103
+ }
104
+
105
+ beforeEach(() => {
106
+ sessionRunIDs = [];
107
+ runViewsMock.mockClear();
108
+ beginStepMock.mockClear();
109
+ achieveGoalMock.mockClear();
110
+ });
111
+
112
+ describe('RemoteBrowserActionResolver.ExecuteRemoteBrowserGoal — co-agent run from Config', () => {
113
+ it('does not attach the goal to a run that is not a record of the session', async () => {
114
+ await startGoal({ targetAgentID: 'target-1', coAgentRunID: FOREIGN_RUN_ID });
115
+
116
+ expect(beginStepMock).toHaveBeenCalledTimes(1);
117
+ expect(beginStepMock.mock.calls[0][2]).toBeUndefined();
118
+ expect(achieveGoalMock.mock.calls[0][2].AgentRunID).toBeUndefined();
119
+ });
120
+
121
+ it('attaches the goal to the session\'s co-agent run', async () => {
122
+ sessionRunIDs = [CO_RUN_ID];
123
+
124
+ await startGoal({ targetAgentID: 'target-1', coAgentRunID: CO_RUN_ID });
125
+
126
+ expect(beginStepMock.mock.calls[0][2]).toBe(CO_RUN_ID);
127
+ expect(achieveGoalMock.mock.calls[0][2].AgentRunID).toBe(CO_RUN_ID);
128
+ });
129
+ });
@@ -0,0 +1,174 @@
1
+ // ResolverBase transitively pulls in type-graphql decorators, which need the
2
+ // Reflect.metadata polyfill at import time.
3
+ import 'reflect-metadata';
4
+ import { describe, it, expect } from 'vitest';
5
+ import type { AggregateExpression, DatabaseProviderBase, IMetadataProvider, RunViewParams, RunViewResult, UserInfo } from '@memberjunction/core';
6
+ import type { PubSubEngine } from 'type-graphql';
7
+ import { ResolverBase } from '../generic/ResolverBase.js';
8
+ import { RunViewResolver, type RunDynamicViewInput } from '../generic/RunViewResolver.js';
9
+ import type { AppContext, UserPayload } from '../types.js';
10
+
11
+ /**
12
+ * `Aggregates[].expression` is spliced into `SELECT <expression> AS [Agg_0] FROM <view> WHERE …`.
13
+ * The GraphQL-boundary screen must treat it like the other client SQL fragments (ExtraFilter,
14
+ * OrderBy, OverrideExcludeFilter): one statement, no writes, and subqueries only against entity
15
+ * base views the caller can read.
16
+ *
17
+ * The payloads below are harmless (`SELECT 1`).
18
+ */
19
+
20
+ const ENTITY_NAME = 'MJ: Users';
21
+
22
+ /** An aggregate that carries a second statement. */
23
+ const STACKED_AGGREGATE = "COUNT(*) AS [a'] ; SELECT 1 AS [b'], COUNT(*)";
24
+
25
+ /** A subquery against a base table rather than an entity base view. */
26
+ const BASE_TABLE_AGGREGATE = 'COUNT(*) + (SELECT COUNT(*) FROM __mj.[User])';
27
+
28
+ type Captured = { params: RunViewParams | null };
29
+
30
+ /** Entity metadata the screen reads, plus a `RunView` that records what reached it. */
31
+ function fakeProvider(captured: Captured): DatabaseProviderBase {
32
+ return {
33
+ Entities: [
34
+ {
35
+ ID: 'E1',
36
+ Name: ENTITY_NAME,
37
+ SchemaName: '__mj',
38
+ BaseView: 'vwUsers',
39
+ BaseTable: 'User',
40
+ Fields: [
41
+ { Name: 'ID', NeedsQuotes: true },
42
+ { Name: 'Name', NeedsQuotes: true },
43
+ { Name: 'Sequence', NeedsQuotes: false },
44
+ ],
45
+ PrimaryKeys: [{ Name: 'ID' }],
46
+ GetUserPermisions: () => ({ CanRead: true }),
47
+ },
48
+ ],
49
+ EntityByName: (name: string) => (name === ENTITY_NAME ? { ID: 'E1', Name: ENTITY_NAME, PrimaryKeys: [{ Name: 'ID' }] } : undefined),
50
+ RunView: async (params: RunViewParams): Promise<RunViewResult> => {
51
+ captured.params = params;
52
+ return { Success: true, Results: [], RowCount: 0, TotalRowCount: 0, ErrorMessage: '' } as RunViewResult;
53
+ },
54
+ RunViews: async (params: RunViewParams[]): Promise<RunViewResult[]> => {
55
+ captured.params = params[0] ?? null;
56
+ return params.map(() => ({ Success: true, Results: [], RowCount: 0, TotalRowCount: 0, ErrorMessage: '' } as RunViewResult));
57
+ },
58
+ } as unknown as DatabaseProviderBase;
59
+ }
60
+
61
+ const fakeUser = () => ({ Email: 'tester@example.com' } as UserInfo);
62
+ const fakePayload = () => ({ email: 'tester@example.com', userRecord: fakeUser() } as UserPayload);
63
+
64
+ /** Reaches the boundary screen and the dynamic-view entry point. */
65
+ class Probe extends ResolverBase {
66
+ public ScreenAll(
67
+ clauses: { extraFilter?: string | null; aggregates?: AggregateExpression[] | null },
68
+ provider: DatabaseProviderBase,
69
+ ) {
70
+ return this.screenClientViewClauses(clauses, provider as unknown as IMetadataProvider);
71
+ }
72
+
73
+ public RunDynamic(input: RunDynamicViewInput, provider: DatabaseProviderBase) {
74
+ return this.RunDynamicViewGeneric(input, provider, fakePayload(), undefined as unknown as PubSubEngine);
75
+ }
76
+ }
77
+
78
+ describe('ResolverBase.screenClientViewClauses — aggregate expressions', () => {
79
+ it('rejects an aggregate that carries a second statement', () => {
80
+ expect(() =>
81
+ new Probe().ScreenAll({ aggregates: [{ expression: STACKED_AGGREGATE }] }, fakeProvider({ params: null })),
82
+ ).toThrow(/Invalid Aggregate: multiple statements/);
83
+ });
84
+
85
+ it('rejects an aggregate whose subquery reads a base table', () => {
86
+ expect(() =>
87
+ new Probe().ScreenAll({ aggregates: [{ expression: BASE_TABLE_AGGREGATE }] }, fakeProvider({ params: null })),
88
+ ).toThrow(/Invalid Aggregate: subquery must use an entity base view/);
89
+ });
90
+
91
+ it('screens every aggregate in the list, not only the first', () => {
92
+ expect(() =>
93
+ new Probe().ScreenAll(
94
+ { aggregates: [{ expression: 'COUNT(*)' }, { expression: STACKED_AGGREGATE }] },
95
+ fakeProvider({ params: null }),
96
+ ),
97
+ ).toThrow(/Invalid Aggregate: multiple statements/);
98
+ });
99
+
100
+ it('leaves an aggregate the parser cannot read to the provider, which refuses it per aggregate', () => {
101
+ expect(() =>
102
+ new Probe().ScreenAll({ aggregates: [{ expression: 'COUNT_BIG(*)' }] }, fakeProvider({ params: null })),
103
+ ).not.toThrow();
104
+ });
105
+
106
+ it('lets ordinary aggregate expressions through', () => {
107
+ const ordinary = [
108
+ 'COUNT(*)',
109
+ 'SUM(Sequence)',
110
+ 'AVG(Sequence * 2)',
111
+ 'COUNT(DISTINCT Name)',
112
+ "COUNT(CASE WHEN Name = 'x' THEN 1 END)",
113
+ 'MIN([Name])',
114
+ ];
115
+ expect(() =>
116
+ new Probe().ScreenAll({ aggregates: ordinary.map((expression) => ({ expression })) }, fakeProvider({ params: null })),
117
+ ).not.toThrow();
118
+ });
119
+ });
120
+
121
+ describe('ResolverBase RunView entry points — aggregate expressions', () => {
122
+ it('RunDynamicViewGeneric never reaches RunView with a stacked aggregate', async () => {
123
+ const captured: Captured = { params: null };
124
+ const input = { EntityName: ENTITY_NAME, Aggregates: [{ expression: STACKED_AGGREGATE }] } as RunDynamicViewInput;
125
+
126
+ await expect(new Probe().RunDynamic(input, fakeProvider(captured))).rejects.toThrow(/Invalid Aggregate: multiple statements/);
127
+ expect(captured.params).toBeNull();
128
+ });
129
+
130
+ it('RunDynamicViewGeneric passes ordinary aggregates through to RunView', async () => {
131
+ const captured: Captured = { params: null };
132
+ const aggregates = [{ expression: 'COUNT(*)', alias: 'Total' }, { expression: 'SUM(Sequence)' }];
133
+ const input = { EntityName: ENTITY_NAME, Aggregates: aggregates } as RunDynamicViewInput;
134
+
135
+ await new Probe().RunDynamic(input, fakeProvider(captured));
136
+
137
+ expect(captured.params?.Aggregates).toEqual(aggregates);
138
+ });
139
+
140
+ it('RunViews returns a failure result, and never reaches the provider, for a stacked aggregate', async () => {
141
+ const captured: Captured = { params: null };
142
+ const provider = fakeProvider(captured);
143
+ const input = [{ EntityName: ENTITY_NAME, Aggregates: [{ expression: STACKED_AGGREGATE }] }] as Parameters<RunViewResolver['RunViews']>[0];
144
+
145
+ const results = await new RunViewResolver().RunViews(
146
+ input,
147
+ { providers: [{ type: 'Read-Only', provider }] as unknown as AppContext['providers'], userPayload: fakePayload() } as AppContext,
148
+ undefined as unknown as PubSubEngine,
149
+ );
150
+
151
+ expect(results).toHaveLength(1);
152
+ expect(results[0].Success).toBe(false);
153
+ expect(results[0].ErrorMessage).toMatch(/Invalid Aggregate: multiple statements/);
154
+ expect(captured.params).toBeNull();
155
+ });
156
+
157
+ it('RunViews does not fail the batch because one aggregate cannot be parsed', async () => {
158
+ const captured: Captured = { params: null };
159
+ const provider = fakeProvider(captured);
160
+ const input = [
161
+ { EntityName: ENTITY_NAME, Aggregates: [{ expression: 'COUNT_BIG(*)' }] },
162
+ { EntityName: ENTITY_NAME },
163
+ ] as Parameters<RunViewResolver['RunViews']>[0];
164
+
165
+ const results = await new RunViewResolver().RunViews(
166
+ input,
167
+ { providers: [{ type: 'Read-Only', provider }] as unknown as AppContext['providers'], userPayload: fakePayload() } as AppContext,
168
+ undefined as unknown as PubSubEngine,
169
+ );
170
+
171
+ expect(results.map((r) => r.Success)).toEqual([true, true]);
172
+ expect(captured.params?.Aggregates).toEqual([{ expression: 'COUNT_BIG(*)' }]);
173
+ });
174
+ });
@@ -1,7 +1,7 @@
1
1
  // Reflect.metadata polyfill at import time.
2
2
  import 'reflect-metadata';
3
3
  import { describe, it, expect } from 'vitest';
4
- import { EntityFieldTSType, type EntityFieldInfo, type EntityInfo } from '@memberjunction/core';
4
+ import { EntityFieldTSType, EntityInfo, UserInfo, type EntityFieldInfo } from '@memberjunction/core';
5
5
  import { ResolverBase } from '../generic/ResolverBase.js';
6
6
 
7
7
  /**
@@ -13,8 +13,11 @@ class Probe extends ResolverBase {
13
13
  public Convert(field: Partial<EntityFieldInfo>, raw: unknown) {
14
14
  return this.ClientOldValueToFieldValue(field as EntityFieldInfo, raw);
15
15
  }
16
- public MustLoad(entity: Partial<EntityInfo>, oldValues: boolean) {
17
- return this.MustLoadTruthFromDatabase(entity as EntityInfo, { OldValues___: oldValues ? [{ Key: 'Comments', Value: null }] : undefined }, false, false);
16
+ public MustLoad(entity: Record<string, unknown>, oldValues: boolean) {
17
+ // A real entity with no permission rows and a user with no roles: no row filter applies.
18
+ // The Update RLS term itself is covered in resolverBase.fls.test.ts.
19
+ const user = new UserInfo(null, { ID: 'u-1', Name: 'Probe', Email: 'probe@example.test', IsActive: true, UserRoles: [] });
20
+ return this.MustLoadTruthFromDatabase(new EntityInfo({ Fields: [], ...entity }), { OldValues___: oldValues ? [{ Key: 'Comments', Value: null }] : undefined }, false, false, user);
18
21
  }
19
22
  }
20
23
  const probe = new Probe();
@@ -0,0 +1,115 @@
1
+ /**
2
+ * @fileoverview The returning-visitor recap transcript reads Main.
3
+ *
4
+ * The transcript query filters with `ConversationEngine.ScopeFilter(ConversationEngine.TrunkScope(id))`
5
+ * and takes the newest turns by `Sequence`. It reads no branch or conversation scope first.
6
+ */
7
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
8
+ import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
9
+
10
+ interface ViewCall {
11
+ EntityName: string;
12
+ ExtraFilter?: string;
13
+ OrderBy?: string;
14
+ MaxRows?: number;
15
+ }
16
+
17
+ const hoisted = vi.hoisted(() => ({
18
+ views: [] as ViewCall[],
19
+ detailRows: [] as Array<{ Role: string; Message: string }>,
20
+ transcripts: [] as string[],
21
+ logError: vi.fn(),
22
+ logStatus: vi.fn(),
23
+ aiConfig: vi.fn(),
24
+ }));
25
+
26
+ vi.mock('@memberjunction/core', async (importOriginal) => {
27
+ const actual = await importOriginal<typeof import('@memberjunction/core')>();
28
+ return {
29
+ ...actual,
30
+ LogError: hoisted.logError,
31
+ LogStatus: hoisted.logStatus,
32
+ RunView: class {
33
+ async RunView(params: ViewCall) {
34
+ hoisted.views.push(params);
35
+ if (params.EntityName === 'MJ: Conversation Details') {
36
+ return { Success: true, Results: hoisted.detailRows };
37
+ }
38
+ return { Success: true, Results: [] };
39
+ }
40
+ },
41
+ };
42
+ });
43
+
44
+ vi.mock('@memberjunction/aiengine', () => ({
45
+ AIEngine: {
46
+ Instance: {
47
+ Config: hoisted.aiConfig,
48
+ Prompts: [{ Name: 'Returning-Visitor Recap', SelectionStrategy: 'Default', Status: 'Active' }],
49
+ },
50
+ },
51
+ }));
52
+
53
+ vi.mock('@memberjunction/ai-core-plus', () => ({
54
+ AIPromptParams: class {},
55
+ }));
56
+
57
+ vi.mock('@memberjunction/ai-prompts', () => ({
58
+ AIPromptRunner: class {
59
+ async ExecutePrompt(params: { data?: { transcript?: string } }) {
60
+ hoisted.transcripts.push(params.data?.transcript ?? '');
61
+ return { success: true, rawResult: 'NO_RECAP' };
62
+ }
63
+ },
64
+ }));
65
+
66
+ import { ConversationEngine } from '@memberjunction/core-entities';
67
+ import { WriteReturningVisitorRecap } from '../agentSessions/ReturningVisitorRecap.js';
68
+
69
+ const USER = { ID: 'user-1' } as unknown as UserInfo;
70
+
71
+ function makeProvider(): IMetadataProvider {
72
+ const conversation = {
73
+ ID: 'conv-1',
74
+ LinkedEntityID: 'entity-1',
75
+ LinkedRecordID: 'record-1',
76
+ VisitorKey: null,
77
+ Load: vi.fn().mockResolvedValue(true),
78
+ };
79
+ return {
80
+ Entities: [],
81
+ GetEntityObject: vi.fn().mockResolvedValue(conversation),
82
+ } as unknown as IMetadataProvider;
83
+ }
84
+
85
+ function detailView(): ViewCall | undefined {
86
+ return hoisted.views.find((v) => v.EntityName === 'MJ: Conversation Details');
87
+ }
88
+
89
+ describe('WriteReturningVisitorRecap — transcript scope', () => {
90
+ beforeEach(() => {
91
+ hoisted.views.length = 0;
92
+ hoisted.transcripts.length = 0;
93
+ hoisted.detailRows = [
94
+ { Role: 'AI', Message: 'second' },
95
+ { Role: 'User', Message: 'first' },
96
+ ];
97
+ hoisted.logError.mockReset();
98
+ hoisted.logStatus.mockReset();
99
+ hoisted.aiConfig.mockReset();
100
+ });
101
+
102
+ it('reads the newest turns in Main with the trunk predicate, in chronological order', async () => {
103
+ const provider = makeProvider();
104
+
105
+ await WriteReturningVisitorRecap('conv-1', 'agent-1', USER, provider);
106
+
107
+ const view = detailView();
108
+ expect(view?.ExtraFilter).toBe(ConversationEngine.LiveRowsFilter(ConversationEngine.ScopeFilter(ConversationEngine.TrunkScope('conv-1'))));
109
+ expect(view?.ExtraFilter).toBe("[ConversationID]='conv-1' AND [BranchID] IS NULL AND [ReplacedAt] IS NULL");
110
+ expect(view?.OrderBy).toBe('Sequence DESC');
111
+ expect(view?.MaxRows).toBe(60);
112
+ expect(hoisted.views.some((v) => v.EntityName === 'MJ: Conversation Branches' || v.EntityName === 'MJ: Conversations')).toBe(false);
113
+ expect(hoisted.transcripts).toEqual(['User: first\nAI: second']);
114
+ });
115
+ });
@@ -0,0 +1,169 @@
1
+ /**
2
+ * @fileoverview The conversation branch on `RunAIAgentFromConversationDetail`.
3
+ *
4
+ * The branch comes from the placeholder row the mutation is given, never from a request argument.
5
+ * These tests pin that the mutation loads history along that branch's path, hands the branch to the
6
+ * agent run on both the synchronous and fire-and-forget paths, and reads the trunk when the row has
7
+ * no branch; and that `executeAIAgent` puts the branch on the run's params only when there is one.
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
+ // type-graphql's real decorators need emitDecoratorMetadata, which vitest's esbuild transform
14
+ // does not emit; the schema is irrelevant here, so every decorator is a no-op.
15
+ vi.mock('type-graphql', () => {
16
+ const decoratorFactory = (..._args: unknown[]) => (..._decorated: unknown[]) => undefined;
17
+ const exportNames = [
18
+ 'Resolver', 'Query', 'Mutation', 'Subscription', 'Arg', 'Args', 'ArgsType', 'Ctx', 'Root',
19
+ 'Info', 'Field', 'FieldResolver', 'ObjectType', 'InputType', 'InterfaceType', 'Authorized',
20
+ 'UseMiddleware', 'Extensions', 'Directive', 'ID', 'Int', 'Float', 'GraphQLISODateTime',
21
+ 'GraphQLTimestamp', 'registerEnumType', 'createMethodDecorator', 'createParamDecorator',
22
+ 'buildSchema', 'buildSchemaSync', 'PubSub',
23
+ ];
24
+ return Object.fromEntries(exportNames.map((name) => [name, decoratorFactory]));
25
+ });
26
+
27
+ const hoisted = vi.hoisted(() => ({
28
+ runAgentInConversation: vi.fn(),
29
+ }));
30
+
31
+ vi.mock('@memberjunction/ai-agents', () => ({
32
+ AgentRunner: class {
33
+ RunAgentInConversation(...args: unknown[]) {
34
+ return hoisted.runAgentInConversation(...args);
35
+ }
36
+ },
37
+ ArtifactToolManager: class {},
38
+ }));
39
+
40
+ vi.mock('../realtimeWidget/widgetGuestElevation.js', () => ({
41
+ ResolveWidgetGuestRunContext: vi.fn().mockResolvedValue(null),
42
+ ElevateUserPayload: vi.fn(),
43
+ }));
44
+
45
+ import { ConversationEngine } from '@memberjunction/core-entities';
46
+ import { RunAIAgentResolver } from '../resolvers/RunAIAgentResolver.js';
47
+ import type { AppContext, UserPayload } from '../types.js';
48
+
49
+ const USER = { ID: 'user-1', Email: 'user@example.com' };
50
+
51
+ type Fn = ReturnType<typeof vi.fn>;
52
+
53
+ /** The private members this test reaches, through an explicit shape (no `any`). */
54
+ interface ResolverSeams {
55
+ CheckAPIKeyScopeAuthorization: Fn;
56
+ GetUserFromPayload: Fn;
57
+ loadConversationHistoryWithAttachments: Fn;
58
+ executeAIAgent: Fn;
59
+ executeAgentInBackground: Fn;
60
+ loadInputArtifactsBatch: Fn;
61
+ validateAgent: Fn;
62
+ persistInFlightAgentFailure: Fn;
63
+ }
64
+
65
+ function makeResolver(): { resolver: RunAIAgentResolver; seams: ResolverSeams } {
66
+ const resolver = new RunAIAgentResolver();
67
+ const seams = resolver as unknown as ResolverSeams;
68
+ seams.CheckAPIKeyScopeAuthorization = vi.fn().mockResolvedValue(undefined);
69
+ seams.GetUserFromPayload = vi.fn().mockReturnValue(USER);
70
+ return { resolver, seams };
71
+ }
72
+
73
+ function makeContext(branchId: string | null): AppContext {
74
+ const provider = {
75
+ GetEntityObject: vi.fn().mockResolvedValue({
76
+ ConversationID: 'conv-1',
77
+ BranchID: branchId,
78
+ Load: vi.fn().mockResolvedValue(true),
79
+ }),
80
+ };
81
+ return {
82
+ userPayload: { sessionId: 'session-1', userRecord: USER } as unknown as UserPayload,
83
+ providers: [{ type: 'Read-Write', provider }],
84
+ dataSource: {},
85
+ } as unknown as AppContext;
86
+ }
87
+
88
+ const pubSub = { publish: vi.fn() } as unknown as PubSubEngine;
89
+
90
+ async function runMutation(resolver: RunAIAgentResolver, branchId: string | null, fireAndForget?: boolean) {
91
+ return resolver.RunAIAgentFromConversationDetail(
92
+ 'detail-1', 'agent-1', makeContext(branchId), 'session-1', pubSub,
93
+ undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined,
94
+ undefined, undefined, fireAndForget, undefined, undefined, undefined
95
+ );
96
+ }
97
+
98
+ afterEach(() => {
99
+ vi.restoreAllMocks();
100
+ });
101
+
102
+ describe('RunAIAgentFromConversationDetail — branch', () => {
103
+ let resolver: RunAIAgentResolver;
104
+ let seams: ResolverSeams;
105
+
106
+ beforeEach(() => {
107
+ ({ resolver, seams } = makeResolver());
108
+ seams.loadConversationHistoryWithAttachments = vi.fn().mockResolvedValue([]);
109
+ seams.executeAIAgent = vi.fn().mockResolvedValue({ success: true, result: '{}' });
110
+ seams.executeAgentInBackground = vi.fn();
111
+ });
112
+
113
+ it('loads history on the placeholder row\'s branch and hands the branch to the run', async () => {
114
+ await runMutation(resolver, 'branch-b');
115
+ expect(seams.loadConversationHistoryWithAttachments.mock.calls[0][6]).toBe('branch-b');
116
+ expect(seams.executeAIAgent.mock.calls[0].at(-1)).toBe('branch-b');
117
+ });
118
+
119
+ it('passes the branch to the background path too', async () => {
120
+ await runMutation(resolver, 'branch-b', true);
121
+ expect(seams.executeAgentInBackground.mock.calls[0].at(-1)).toBe('branch-b');
122
+ });
123
+
124
+ it('reads the trunk when the placeholder has no branch (control)', async () => {
125
+ await runMutation(resolver, null);
126
+ expect(seams.loadConversationHistoryWithAttachments.mock.calls[0][6]).toBeNull();
127
+ expect(seams.executeAIAgent.mock.calls[0].at(-1)).toBeNull();
128
+ });
129
+ });
130
+
131
+ describe('loadConversationHistoryWithAttachments — branch', () => {
132
+ it('passes the branch to the fresh loader', async () => {
133
+ const { resolver, seams } = makeResolver();
134
+ seams.loadInputArtifactsBatch = vi.fn().mockResolvedValue(new Map());
135
+ const loadRows = vi.spyOn(ConversationEngine, 'LoadWindowRowsFresh').mockResolvedValue([]);
136
+ const provider = {};
137
+ const load = (resolver as unknown as {
138
+ loadConversationHistoryWithAttachments(
139
+ conversationId: string, user: unknown, max: number, provider: unknown,
140
+ exclude?: string[], historyFrom?: Date, branchId?: string | null
141
+ ): Promise<unknown[]>;
142
+ }).loadConversationHistoryWithAttachments.bind(resolver);
143
+ await load('conv-1', USER, 20, provider, ['detail-1'], undefined, 'branch-b');
144
+ expect(loadRows).toHaveBeenCalledWith('conv-1', USER, provider, undefined, 'branch-b');
145
+ });
146
+ });
147
+
148
+ describe('executeAIAgent — ConversationBranchID', () => {
149
+ it('puts the branch on the run\'s params, and leaves it unset for the trunk', async () => {
150
+ const { resolver, seams } = makeResolver();
151
+ seams.validateAgent = vi.fn().mockResolvedValue({ ID: 'agent-1', Name: 'Test Agent' });
152
+ seams.persistInFlightAgentFailure = vi.fn().mockResolvedValue(undefined);
153
+ hoisted.runAgentInConversation.mockReset().mockRejectedValue(new Error('stop here'));
154
+ vi.spyOn(console, 'error').mockImplementation(() => undefined);
155
+ vi.spyOn(console, 'log').mockImplementation(() => undefined);
156
+ const execute = (resolver as unknown as { executeAIAgent(...args: unknown[]): Promise<unknown> }).executeAIAgent.bind(resolver);
157
+ const userPayload = { sessionId: 'session-1', userRecord: USER };
158
+ const base = [
159
+ {}, {}, 'agent-1', userPayload, '[{"role":"user","content":"hi"}]', 'session-1', pubSub,
160
+ undefined, undefined, undefined, undefined, undefined, undefined, 'detail-1', false, false,
161
+ undefined, undefined, 'conv-1', undefined, undefined, undefined, undefined, undefined,
162
+ ];
163
+ await execute(...base, 'branch-b');
164
+ await execute(...base, null);
165
+ const [withBranch, trunk] = hoisted.runAgentInConversation.mock.calls.map(c => c[0] as Record<string, unknown>);
166
+ expect(withBranch.ConversationBranchID).toBe('branch-b');
167
+ expect(trunk).not.toHaveProperty('ConversationBranchID');
168
+ });
169
+ });
@@ -124,7 +124,7 @@ describe('RunAIAgentFromConversationDetail — agentHistoryFrom', () => {
124
124
  expect(loaderArgs[0]).toBe('conv-1');
125
125
  expect(loaderArgs[4]).toEqual(['detail-1']);
126
126
  expect((loaderArgs[5] as Date).toISOString()).toBe(FLOOR_ISO);
127
- const runFloor = seams.executeAIAgent.mock.calls[0].at(-1) as Date;
127
+ const runFloor = seams.executeAIAgent.mock.calls[0].at(-2) as Date;
128
128
  expect(runFloor.toISOString()).toBe(FLOOR_ISO);
129
129
  });
130
130
 
@@ -133,7 +133,7 @@ describe('RunAIAgentFromConversationDetail — agentHistoryFrom', () => {
133
133
 
134
134
  expect(result.success).toBe(true);
135
135
  expect(seams.executeAIAgent).not.toHaveBeenCalled();
136
- const runFloor = seams.executeAgentInBackground.mock.calls[0].at(-1) as Date;
136
+ const runFloor = seams.executeAgentInBackground.mock.calls[0].at(-2) as Date;
137
137
  expect(runFloor.toISOString()).toBe(FLOOR_ISO);
138
138
  });
139
139
 
@@ -141,7 +141,7 @@ describe('RunAIAgentFromConversationDetail — agentHistoryFrom', () => {
141
141
  await runMutation(resolver, {});
142
142
 
143
143
  expect(seams.loadConversationHistoryWithAttachments.mock.calls[0][5]).toBeUndefined();
144
- expect(seams.executeAIAgent.mock.calls[0].at(-1)).toBeUndefined();
144
+ expect(seams.executeAIAgent.mock.calls[0].at(-2)).toBeUndefined();
145
145
  });
146
146
 
147
147
  it('fails the request on an unreadable floor, before loading or running anything', async () => {
@@ -178,7 +178,7 @@ describe('loadConversationHistoryWithAttachments — history floor', () => {
178
178
  }).loadConversationHistoryWithAttachments.bind(resolver);
179
179
  await load('conv-1', USER, 20, provider, ['detail-1'], floor);
180
180
 
181
- expect(loadRows).toHaveBeenCalledWith('conv-1', USER, provider, floor);
181
+ expect(loadRows).toHaveBeenCalledWith('conv-1', USER, provider, floor, undefined);
182
182
  expect(assemble.mock.calls[0][1]).toEqual({ maxTailMessages: 20, excludeDetailIds: ['detail-1'], historyFrom: floor });
183
183
  });
184
184
  });