@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,104 @@
1
+ // type-graphql decorators call `Reflect.getMetadata`, which only exists once this polyfill is
2
+ // loaded. MUST precede any import that pulls in the resolver file.
3
+ import 'reflect-metadata';
4
+
5
+ /**
6
+ * MJServer registers an ad-hoc SQL authorizer with the action engine so that Run Ad-hoc Query, which
7
+ * agents run as the calling user, applies the same checks as the ExecuteAdhocQuery resolver: the
8
+ * rendered SQL must be one read statement, every table it reads must be an entity base view the
9
+ * caller may read in full (no row-level filter, no denied field), and the timeout is limited by the
10
+ * server's request timeout.
11
+ *
12
+ * Every SQL statement below is harmless.
13
+ */
14
+ import { describe, it, expect, vi } from 'vitest';
15
+ import { readFileSync } from 'fs';
16
+ import { resolve } from 'path';
17
+
18
+ vi.mock('type-graphql', () => {
19
+ const noopDecorator = () => () => undefined;
20
+ return {
21
+ Resolver: noopDecorator, Mutation: noopDecorator, Query: noopDecorator, Subscription: noopDecorator,
22
+ ObjectType: noopDecorator, InputType: noopDecorator, Field: noopDecorator, Arg: noopDecorator,
23
+ Args: noopDecorator, Ctx: noopDecorator, PubSub: noopDecorator, Root: noopDecorator,
24
+ Directive: noopDecorator, Authorized: noopDecorator, UseMiddleware: noopDecorator,
25
+ createUnionType: () => class {},
26
+ AuthorizationError: class AuthorizationError extends Error {},
27
+ Float: class {}, Int: class {}, ID: class {},
28
+ };
29
+ });
30
+
31
+ // The server config is read from mj.config at import; the authorizer takes its timeout as an argument.
32
+ vi.mock('../config.js', () => ({ configInfo: { databaseSettings: { requestTimeout: 120_000 } } }));
33
+
34
+ import type { DatabaseProviderBase, UserInfo } from '@memberjunction/core';
35
+ import { CreateAdhocSQLAuthorizer } from '../resolvers/AdhocQueryResolver.js';
36
+ import { ClampAdhocTimeoutSeconds } from '../resolvers/adhocTimeout.js';
37
+
38
+ type EntityShape = { Name: string; SchemaName: string; BaseView: string; rowFilter?: string };
39
+
40
+ /** A read-only provider with the entity metadata the table check reads. */
41
+ function readOnlyProvider(entities: EntityShape[]): DatabaseProviderBase {
42
+ return {
43
+ PlatformKey: 'sqlserver',
44
+ Entities: entities.map((e) => ({
45
+ Name: e.Name,
46
+ SchemaName: e.SchemaName,
47
+ BaseView: e.BaseView,
48
+ GetUserPermisions: () => ({ CanRead: true }),
49
+ GetEffectiveRowFilterWhereClause: () => e.rowFilter ?? '',
50
+ GetDeniedReadFields: () => new Set<string>(),
51
+ })),
52
+ } as unknown as DatabaseProviderBase;
53
+ }
54
+
55
+ const USER = { ID: 'user-1', Email: 'user@example.com' } as unknown as UserInfo;
56
+ const RUBRIC_EVALUATIONS: EntityShape = { Name: 'MJ: Rubric Evaluations', SchemaName: '__mj', BaseView: 'vwRubricEvaluations' };
57
+ const REQUEST_TIMEOUT_MS = 120_000;
58
+
59
+ describe('CreateAdhocSQLAuthorizer', () => {
60
+ const authorizer = CreateAdhocSQLAuthorizer(REQUEST_TIMEOUT_MS);
61
+
62
+ it('admits SQL that reads only entity views the user may read in full', () => {
63
+ const refusal = authorizer.Authorize('SELECT ID FROM [__mj].[vwRubricEvaluations]', readOnlyProvider([RUBRIC_EVALUATIONS]), USER);
64
+
65
+ expect(refusal).toBeNull();
66
+ });
67
+
68
+ it('refuses SQL that reads an entity the user may read only through a row filter', () => {
69
+ const filtered = { ...RUBRIC_EVALUATIONS, rowFilter: "EvaluatorUserID='user-1'" };
70
+
71
+ const refusal = authorizer.Authorize('SELECT * FROM __mj.vwRubricEvaluations', readOnlyProvider([filtered]), USER);
72
+
73
+ expect(refusal).toMatch(/row-level-security filtered/);
74
+ });
75
+
76
+ it('refuses SQL that reads a table that is not an entity view', () => {
77
+ const refusal = authorizer.Authorize('SELECT * FROM [__mj].[User]', readOnlyProvider([RUBRIC_EVALUATIONS]), USER);
78
+
79
+ expect(refusal).toMatch(/entity base view/);
80
+ });
81
+
82
+ it('refuses a stacked batch hidden behind a bracket identifier', () => {
83
+ const refusal = authorizer.Authorize("SELECT 1 AS [a'] ; SELECT 2 AS [b']", readOnlyProvider([RUBRIC_EVALUATIONS]), USER);
84
+
85
+ expect(refusal).toMatch(/single read query/);
86
+ });
87
+
88
+ it('limits the timeout the way ExecuteAdhocQuery does', () => {
89
+ for (const requested of [undefined, 5, 600]) {
90
+ expect(authorizer.ClampTimeoutSeconds(requested)).toBe(ClampAdhocTimeoutSeconds(requested, REQUEST_TIMEOUT_MS));
91
+ }
92
+ expect(authorizer.ClampTimeoutSeconds(600)).toBe(120);
93
+ });
94
+ });
95
+
96
+ describe('MJServer startup', () => {
97
+ // Startup needs a database, so this one assertion reads the source.
98
+ it('registers the read-only provider factory and this authorizer with the action engine', () => {
99
+ const src = readFileSync(resolve(__dirname, '../index.ts'), 'utf8');
100
+
101
+ expect(/ActionEngineServer\.Instance\.SetReadOnlyProviderFactory\(\s*\(\)\s*=>\s*CreateReadOnlyProvider\(dataSources\)\s*\)/.test(src)).toBe(true);
102
+ expect(/ActionEngineServer\.Instance\.SetAdhocSQLAuthorizer\(\s*CreateAdhocSQLAuthorizer\(configInfo\.databaseSettings\.requestTimeout\)\s*\)/.test(src)).toBe(true);
103
+ });
104
+ });
@@ -0,0 +1,232 @@
1
+ import { describe, it, expect, vi, afterEach } from 'vitest';
2
+ import { hostname } from 'os';
3
+ import type { IMetadataProvider, RunViewParams, UserInfo } from '@memberjunction/core';
4
+ import type { IRealtimeSession, RealtimeInputFrame } from '@memberjunction/ai';
5
+ import type { MJAIAgentSessionBridgeEntity, MJAIBridgeProviderEntity } from '@memberjunction/core-entities';
6
+ import { AIBridgeEngine, LOOPBACK_BRIDGE_DRIVER_CLASS } from '@memberjunction/ai-bridge-server';
7
+ import { BindBridgeEngineHostInstance, StartBridgeOrphanReconciliation } from '../agentSessions/BridgeHostInstance.js';
8
+ import { GetBootID, GetCurrentHostInstance, HostInstanceIdentity, SetHostInstancePort } from '../agentSessions/HostInstance.js';
9
+
10
+ /** The port this test process "serves" on, set the way `Serve` sets it, before anything reads the identity. */
11
+ const OUR_PORT = 4000;
12
+ /** The port of a second MJAPI on the same host. */
13
+ const OTHER_PORT = 4100;
14
+ SetHostInstancePort(OUR_PORT);
15
+
16
+ const user = { ID: 'system-user', Email: 'system@example.com' } as unknown as UserInfo;
17
+
18
+ /** A bridge row as the engine reads and writes it. */
19
+ interface BridgeRow {
20
+ ID: string;
21
+ Status: MJAIAgentSessionBridgeEntity['Status'];
22
+ HostInstanceID: string | null;
23
+ AgentSessionID: string;
24
+ CloseReason?: MJAIAgentSessionBridgeEntity['CloseReason'];
25
+ }
26
+
27
+ /** A `LIKE` pattern as a regular expression: `%` any run of characters, `_` any one character. */
28
+ function likePattern(pattern: string): RegExp {
29
+ const body = [...pattern]
30
+ .map((c) => (c === '%' ? '.*' : c === '_' ? '.' : c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')))
31
+ .join('');
32
+ return new RegExp(`^${body}$`);
33
+ }
34
+
35
+ /**
36
+ * A provider over a bridge table, answering the engine the way the database would: the orphan reconcile's read
37
+ * returns the open rows whose `HostInstanceID` matches the filter's `LIKE` pattern and isn't the id it excludes, and a
38
+ * row the engine loads and saves is written back. `row(id)` reads a row's current state.
39
+ */
40
+ function bridgeTable(rows: BridgeRow[]): {
41
+ provider: IMetadataProvider;
42
+ row: (id: string) => BridgeRow;
43
+ runView: ReturnType<typeof vi.fn>;
44
+ } {
45
+ const table = new Map(rows.map((r) => [r.ID, { ...r }]));
46
+ const runView = vi.fn(async (params: RunViewParams) => {
47
+ if (params.EntityName !== 'MJ: AI Agent Session Bridges') {
48
+ return { Success: true, Results: [] };
49
+ }
50
+ const filter = typeof params.ExtraFilter === 'string' ? params.ExtraFilter : '';
51
+ const like = likePattern(/HostInstanceID LIKE '(.*?)'/.exec(filter)?.[1] ?? '');
52
+ const excluded = /HostInstanceID <> '(.*?)'/.exec(filter)?.[1];
53
+ const results = [...table.values()]
54
+ .filter((r) => r.Status === 'Connecting' || r.Status === 'Connected')
55
+ .filter((r) => r.HostInstanceID != null && like.test(r.HostInstanceID) && r.HostInstanceID !== excluded)
56
+ .map((r) => ({ ...r }));
57
+ return { Success: true, Results: results };
58
+ });
59
+ const provider = {
60
+ RunView: runView,
61
+ GetEntityObject: vi.fn(async () => {
62
+ const entity: BridgeRow & { LatestResult: { CompleteMessage: string }; Load: (id: string) => Promise<boolean>; Save: () => Promise<boolean> } = {
63
+ ID: '',
64
+ Status: 'Pending',
65
+ HostInstanceID: null,
66
+ AgentSessionID: '',
67
+ CloseReason: null,
68
+ LatestResult: { CompleteMessage: '' },
69
+ Load: async (id: string) => {
70
+ const stored = table.get(id);
71
+ if (!stored) {
72
+ return false;
73
+ }
74
+ Object.assign(entity, stored);
75
+ return true;
76
+ },
77
+ Save: async () => {
78
+ const stored = table.get(entity.ID);
79
+ if (stored) {
80
+ stored.Status = entity.Status;
81
+ stored.CloseReason = entity.CloseReason;
82
+ }
83
+ return true;
84
+ },
85
+ };
86
+ return entity;
87
+ }),
88
+ } as unknown as IMetadataProvider;
89
+ return { provider, row: (id: string) => table.get(id) as BridgeRow, runView };
90
+ }
91
+
92
+ /** A realtime model session that says nothing: a bridge only needs one to connect. */
93
+ class SilentSession implements IRealtimeSession {
94
+ public SendInput(_frame: RealtimeInputFrame): void {}
95
+ public async RegisterTools(): Promise<void> {}
96
+ public OnOutput(): void {}
97
+ public OnTranscript(): void {}
98
+ public OnToolCall(): void {}
99
+ public async SendToolResult(): Promise<void> {}
100
+ public OnInterruption(): void {}
101
+ public OnError(): void {}
102
+ public OnUsage(): void {}
103
+ public async Close(): Promise<void> {}
104
+ }
105
+
106
+ /** The loopback bridge provider: a bridge with no platform behind it. */
107
+ const loopbackProvider = {
108
+ ID: 'provider-loopback',
109
+ Name: 'Loopback',
110
+ DriverClass: LOOPBACK_BRIDGE_DRIVER_CLASS,
111
+ SupportedFeaturesObject: { AudioIn: true, AudioOut: true },
112
+ } as unknown as MJAIBridgeProviderEntity;
113
+
114
+ afterEach(() => {
115
+ AIBridgeEngine.Instance.StopOrphanReconciliation();
116
+ BindBridgeEngineHostInstance();
117
+ });
118
+
119
+ describe('BindBridgeEngineHostInstance', () => {
120
+ it("makes the engine stamp this MJAPI instance's identity (hostname:port:pid:bootId) on the bridges it creates", async () => {
121
+ BindBridgeEngineHostInstance();
122
+ const row = {
123
+ ID: 'bridge-new',
124
+ Status: 'Pending',
125
+ HostInstanceID: null as string | null,
126
+ NewRecord: vi.fn(),
127
+ Save: vi.fn(async () => true),
128
+ Load: vi.fn(async () => true),
129
+ LatestResult: { CompleteMessage: '' },
130
+ };
131
+ const provider = {
132
+ GetEntityObject: vi.fn(async () => row),
133
+ RunView: vi.fn(async () => ({ Success: true, Results: [] })),
134
+ } as unknown as IMetadataProvider;
135
+
136
+ const active = await AIBridgeEngine.Instance.StartBridgeSession({
137
+ AgentSessionID: 'session-1',
138
+ Provider: loopbackProvider,
139
+ RealtimeSession: new SilentSession(),
140
+ Address: 'loopback://room',
141
+ ContextUser: user,
142
+ MetadataProvider: provider,
143
+ });
144
+ try {
145
+ expect(row.HostInstanceID).toBe(GetCurrentHostInstance().GetHostInstanceID());
146
+ expect(row.HostInstanceID).toBe(`${hostname()}:${OUR_PORT}:${process.pid}:${GetBootID()}`);
147
+ } finally {
148
+ await AIBridgeEngine.Instance.StopBridgeSession(active.SessionBridgeID, 'Explicit', user, provider);
149
+ }
150
+ });
151
+ });
152
+
153
+ describe("the bridge engine's orphan reconcile with this MJAPI's identity (#5310)", () => {
154
+ const ours = GetCurrentHostInstance();
155
+ const theirs = new HostInstanceIdentity(ours.HostName, String(OTHER_PORT), 40404, 'boot-theirs');
156
+
157
+ /** Both MJAPIs' live bridges, and one each left by an earlier boot. */
158
+ function sharedTable(): BridgeRow[] {
159
+ return [
160
+ { ID: 'ours-live', Status: 'Connected', HostInstanceID: ours.GetHostInstanceID(), AgentSessionID: 'session-ours-live' },
161
+ { ID: 'ours-stale', Status: 'Connected', HostInstanceID: `${ours.GetInstancePrefix()}31337:boot-ours-before`, AgentSessionID: 'session-ours-stale' },
162
+ { ID: 'theirs-live', Status: 'Connected', HostInstanceID: theirs.GetHostInstanceID(), AgentSessionID: 'session-theirs-live' },
163
+ { ID: 'theirs-stale', Status: 'Connecting', HostInstanceID: `${theirs.GetInstancePrefix()}27182:boot-theirs-before`, AgentSessionID: 'session-theirs-stale' },
164
+ ];
165
+ }
166
+
167
+ it('closes the bridges an earlier boot of this instance left, and nothing else (one MJAPI, as before)', async () => {
168
+ BindBridgeEngineHostInstance();
169
+ const { provider, row } = bridgeTable([
170
+ { ID: 'before-restart', Status: 'Connected', HostInstanceID: `${ours.GetInstancePrefix()}31337:boot-before`, AgentSessionID: 'session-1' },
171
+ // The OS gave the restarted process the same pid; the boot id still differs.
172
+ { ID: 'before-restart-same-pid', Status: 'Connecting', HostInstanceID: `${ours.GetInstancePrefix()}${ours.ProcessID}:boot-before`, AgentSessionID: 'session-2' },
173
+ { ID: 'this-boot', Status: 'Connected', HostInstanceID: ours.GetHostInstanceID(), AgentSessionID: 'session-3' },
174
+ { ID: 'other-host', Status: 'Connected', HostInstanceID: `other-host:${OUR_PORT}:31337:boot-other`, AgentSessionID: 'session-4' },
175
+ // Stamped before MJServer gave the engine its identity; no instance owns it.
176
+ { ID: 'fallback-stamped', Status: 'Connected', HostInstanceID: `unknown-host:31337:boot-old`, AgentSessionID: 'session-5' },
177
+ ]);
178
+
179
+ const closed = await AIBridgeEngine.Instance.ReconcileOrphans(user, provider);
180
+
181
+ expect(closed).toBe(2);
182
+ expect(row('before-restart')).toMatchObject({ Status: 'Disconnected', CloseReason: 'Janitor' });
183
+ expect(row('before-restart-same-pid')).toMatchObject({ Status: 'Disconnected', CloseReason: 'Janitor' });
184
+ expect(row('this-boot').Status).toBe('Connected');
185
+ expect(row('other-host').Status).toBe('Connected');
186
+ expect(row('fallback-stamped').Status).toBe('Connected');
187
+ });
188
+
189
+ it("closes its own stale bridges and leaves the other MJAPI's live bridges open", async () => {
190
+ BindBridgeEngineHostInstance();
191
+ const { provider, row } = bridgeTable(sharedTable());
192
+
193
+ const closed = await AIBridgeEngine.Instance.ReconcileOrphans(user, provider);
194
+
195
+ expect(closed).toBe(1);
196
+ expect(row('ours-stale')).toMatchObject({ Status: 'Disconnected', CloseReason: 'Janitor' });
197
+ expect(row('ours-live').Status).toBe('Connected');
198
+ expect(row('theirs-live').Status).toBe('Connected');
199
+ expect(row('theirs-stale').Status).toBe('Connecting');
200
+ });
201
+
202
+ it("the other MJAPI's reconcile closes its stale bridges and leaves this one's live bridges open", async () => {
203
+ // What the other MJAPI's own BindBridgeEngineHostInstance gives its engine.
204
+ AIBridgeEngine.Instance.SetHostInstanceIdentity(theirs);
205
+ const { provider, row } = bridgeTable(sharedTable());
206
+
207
+ const closed = await AIBridgeEngine.Instance.ReconcileOrphans(user, provider);
208
+
209
+ expect(closed).toBe(1);
210
+ expect(row('theirs-stale')).toMatchObject({ Status: 'Disconnected', CloseReason: 'Janitor' });
211
+ expect(row('theirs-live').Status).toBe('Connected');
212
+ expect(row('ours-live').Status).toBe('Connected');
213
+ expect(row('ours-stale').Status).toBe('Connected');
214
+ });
215
+ });
216
+
217
+ describe('StartBridgeOrphanReconciliation', () => {
218
+ it("reconciles at once, with this instance's identity, as the given user", async () => {
219
+ BindBridgeEngineHostInstance();
220
+ const ours = GetCurrentHostInstance();
221
+ const { provider, row, runView } = bridgeTable([
222
+ { ID: 'ours-stale', Status: 'Connected', HostInstanceID: `${ours.GetInstancePrefix()}31337:boot-before`, AgentSessionID: 'session-1' },
223
+ { ID: 'theirs-live', Status: 'Connected', HostInstanceID: `${ours.HostName}:${OTHER_PORT}:40404:boot-theirs`, AgentSessionID: 'session-2' },
224
+ ]);
225
+
226
+ StartBridgeOrphanReconciliation(provider, user);
227
+
228
+ await vi.waitFor(() => expect(row('ours-stale').Status).toBe('Disconnected'));
229
+ expect(row('theirs-live').Status).toBe('Connected');
230
+ expect(runView.mock.calls[0][1]).toBe(user);
231
+ });
232
+ });
@@ -0,0 +1,116 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import type { ClientToolResponse } from '@memberjunction/ai-core-plus';
3
+
4
+ /**
5
+ * RespondToClientToolRequest is the seam between a browser's tool answer and the waiting agent run:
6
+ * the resolver checks the media with the real ParseClientToolMedia and hands the answer to
7
+ * ClientToolRequestManager.ReceiveResponse, which this suite replaces to see what the run receives.
8
+ */
9
+ const h = vi.hoisted(() => ({
10
+ receiveResponse: vi.fn((_response: ClientToolResponse): boolean => true),
11
+ logError: vi.fn(),
12
+ }));
13
+
14
+ // The resolver's GraphQL decorators read TypeScript type metadata that the test transform does not
15
+ // emit, so they become no-ops; the suite calls the resolver method directly.
16
+ vi.mock('type-graphql', () => {
17
+ const noopDecorator = () => () => undefined;
18
+ const decorators = [
19
+ 'Resolver', 'Query', 'Mutation', 'Subscription', 'Arg', 'Args', 'ArgsType', 'Ctx', 'Root', 'Info', 'Field',
20
+ 'FieldResolver', 'ObjectType', 'InputType', 'InterfaceType', 'Authorized', 'UseMiddleware', 'Extensions',
21
+ 'Directive', 'PubSub',
22
+ ];
23
+ return {
24
+ ...Object.fromEntries(decorators.map((name) => [name, noopDecorator])),
25
+ Float: class {},
26
+ Int: class {},
27
+ ID: class {},
28
+ PubSubEngine: class {},
29
+ AuthorizationError: class AuthorizationError extends Error {},
30
+ registerEnumType: () => undefined,
31
+ createUnionType: () => class {},
32
+ };
33
+ });
34
+
35
+ // Replace the agent package so the resolver test does not load the @memberjunction/ai-agents graph.
36
+ vi.mock('@memberjunction/ai-agents', () => ({
37
+ ClientToolRequestManager: { Instance: { ReceiveResponse: h.receiveResponse } },
38
+ CLIENT_TOOL_REQUEST_TOPIC: 'CLIENT_TOOL_REQUEST',
39
+ }));
40
+
41
+ // Keep the resolver's error log out of the test output, and observable.
42
+ vi.mock('@memberjunction/core', async (importOriginal) => {
43
+ const actual = await importOriginal<Record<string, unknown>>();
44
+ return { ...actual, LogError: h.logError, LogStatus: vi.fn() };
45
+ });
46
+
47
+ import { ClientToolRequestResolver } from '../resolvers/ClientToolRequestResolver.js';
48
+
49
+ /** Base64 content of 3 bytes ("ABC"). */
50
+ const SMALL_BASE64 = 'QUJD';
51
+ /** Base64 content that decodes to 2,000,004 bytes, over the 2,000,000-byte limit. */
52
+ const OVERSIZED_BASE64 = 'A'.repeat(2_666_672);
53
+
54
+ /** Sends one answer through the mutation, as the browser does. */
55
+ function respond(media: string | undefined, result: string | undefined = '{"ok":true}'): Promise<boolean> {
56
+ return new ClientToolRequestResolver().RespondToClientToolRequest('req-1', true, result, undefined, media);
57
+ }
58
+
59
+ /** The response the waiting run received. */
60
+ function received(): ClientToolResponse {
61
+ expect(h.receiveResponse).toHaveBeenCalledTimes(1);
62
+ return h.receiveResponse.mock.calls[0][0];
63
+ }
64
+
65
+ beforeEach(() => {
66
+ h.receiveResponse.mockReset();
67
+ h.receiveResponse.mockReturnValue(true);
68
+ h.logError.mockReset();
69
+ });
70
+
71
+ describe('ClientToolRequestResolver.RespondToClientToolRequest', () => {
72
+ it('hands valid media to the waiting run with the result', async () => {
73
+ const media = JSON.stringify([{ MimeType: 'image/jpeg', Base64: SMALL_BASE64, Width: 1280, Height: 720 }]);
74
+
75
+ await expect(respond(media)).resolves.toBe(true);
76
+
77
+ expect(received()).toEqual({
78
+ RequestID: 'req-1',
79
+ Success: true,
80
+ Result: { ok: true },
81
+ ErrorMessage: undefined,
82
+ Media: [{ MimeType: 'image/jpeg', Base64: SMALL_BASE64, Width: 1280, Height: 720 }],
83
+ });
84
+ });
85
+
86
+ it('hands no Media to the run when the answer carries none', async () => {
87
+ await respond(undefined);
88
+
89
+ expect(received()).toEqual({ RequestID: 'req-1', Success: true, Result: { ok: true }, ErrorMessage: undefined, Media: undefined });
90
+ });
91
+
92
+ it.each([
93
+ ['a MIME type that is not allowed', JSON.stringify([{ MimeType: 'image/gif', Base64: SMALL_BASE64 }]), 'media MIME type "image/gif" is not allowed'],
94
+ ['an item over the size limit', JSON.stringify([{ MimeType: 'image/png', Base64: OVERSIZED_BASE64 }]), 'media item is 2000004 bytes; the limit is 2000000'],
95
+ [
96
+ 'more than 4 items',
97
+ JSON.stringify(Array.from({ length: 5 }, () => ({ MimeType: 'image/jpeg', Base64: SMALL_BASE64 }))),
98
+ 'media holds 5 items; the limit is 4',
99
+ ],
100
+ ])('gives the run a failed result for %s, without the result or the media', async (_case, media, reason) => {
101
+ await respond(media);
102
+
103
+ expect(received()).toEqual({ RequestID: 'req-1', Success: false, ErrorMessage: `Client tool media rejected: ${reason}` });
104
+ });
105
+
106
+ it('gives the run a failed result and returns false when the result is not valid JSON', async () => {
107
+ await expect(respond(undefined, '{not json')).resolves.toBe(false);
108
+
109
+ expect(received()).toEqual({
110
+ RequestID: 'req-1',
111
+ Success: false,
112
+ ErrorMessage: expect.stringMatching(/^Client tool response rejected: /),
113
+ });
114
+ expect(h.logError).toHaveBeenCalledWith(expect.stringContaining('RespondToClientToolRequest error:'));
115
+ });
116
+ });