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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (401) hide show
  1. package/README.md +27 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/agentSessions/SessionJanitor.d.ts +3 -3
  4. package/dist/agentSessions/SessionJanitor.js +3 -3
  5. package/dist/agentSessions/SessionManager.d.ts +15 -5
  6. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  7. package/dist/agentSessions/SessionManager.js +34 -12
  8. package/dist/agentSessions/SessionManager.js.map +1 -1
  9. package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
  10. package/dist/auth/AuthProviderCatalogRouter.js +2 -0
  11. package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
  12. package/dist/auth/index.d.ts.map +1 -1
  13. package/dist/auth/index.js +51 -10
  14. package/dist/auth/index.js.map +1 -1
  15. package/dist/auth/initializeProviders.d.ts +10 -0
  16. package/dist/auth/initializeProviders.d.ts.map +1 -1
  17. package/dist/auth/initializeProviders.js +37 -1
  18. package/dist/auth/initializeProviders.js.map +1 -1
  19. package/dist/auth/localDevAuth.d.ts +79 -0
  20. package/dist/auth/localDevAuth.d.ts.map +1 -0
  21. package/dist/auth/localDevAuth.js +439 -0
  22. package/dist/auth/localDevAuth.js.map +1 -0
  23. package/dist/auth/magicLink/MagicLinkKeys.d.ts +2 -2
  24. package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
  25. package/dist/auth/magicLink/MagicLinkKeys.js +1 -1
  26. package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
  27. package/dist/auth/magicLink/types.d.ts +12 -0
  28. package/dist/auth/magicLink/types.d.ts.map +1 -1
  29. package/dist/auth/sessionUserClone.d.ts +1 -1
  30. package/dist/auth/sessionUserClone.d.ts.map +1 -1
  31. package/dist/auth/sessionUserClone.js +3 -2
  32. package/dist/auth/sessionUserClone.js.map +1 -1
  33. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts +292 -0
  34. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -0
  35. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +442 -0
  36. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -0
  37. package/dist/broadcast/index.d.ts +6 -0
  38. package/dist/broadcast/index.d.ts.map +1 -0
  39. package/dist/broadcast/index.js +6 -0
  40. package/dist/broadcast/index.js.map +1 -0
  41. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts +43 -0
  42. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts.map +1 -0
  43. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js +144 -0
  44. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js.map +1 -0
  45. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts +46 -0
  46. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts.map +1 -0
  47. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js +149 -0
  48. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js.map +1 -0
  49. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts +49 -0
  50. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts.map +1 -0
  51. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js +131 -0
  52. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js.map +1 -0
  53. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts +24 -0
  54. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts.map +1 -0
  55. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js +51 -0
  56. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js.map +1 -0
  57. package/dist/cache.d.ts +3 -2
  58. package/dist/cache.d.ts.map +1 -1
  59. package/dist/cache.js.map +1 -1
  60. package/dist/config.d.ts +496 -0
  61. package/dist/config.d.ts.map +1 -1
  62. package/dist/config.js +61 -0
  63. package/dist/config.js.map +1 -1
  64. package/dist/context.d.ts +3 -1
  65. package/dist/context.d.ts.map +1 -1
  66. package/dist/context.js +29 -8
  67. package/dist/context.js.map +1 -1
  68. package/dist/coreRoutes.d.ts.map +1 -1
  69. package/dist/coreRoutes.js +14 -0
  70. package/dist/coreRoutes.js.map +1 -1
  71. package/dist/generated/graphql-schemas/__mj.d.ts +635 -14
  72. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  73. package/dist/generated/graphql-schemas/__mj.js +3366 -418
  74. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  75. package/dist/generic/CacheInvalidationResolver.d.ts +50 -30
  76. package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
  77. package/dist/generic/CacheInvalidationResolver.js +154 -79
  78. package/dist/generic/CacheInvalidationResolver.js.map +1 -1
  79. package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
  80. package/dist/generic/OrphanedConversationDetailReconciler.js +2 -0
  81. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  82. package/dist/generic/PubSubManager.d.ts +10 -0
  83. package/dist/generic/PubSubManager.d.ts.map +1 -1
  84. package/dist/generic/PubSubManager.js +44 -1
  85. package/dist/generic/PubSubManager.js.map +1 -1
  86. package/dist/generic/PushStatusResolver.d.ts +39 -6
  87. package/dist/generic/PushStatusResolver.d.ts.map +1 -1
  88. package/dist/generic/PushStatusResolver.js +93 -11
  89. package/dist/generic/PushStatusResolver.js.map +1 -1
  90. package/dist/generic/ResolverBase.d.ts +65 -25
  91. package/dist/generic/ResolverBase.d.ts.map +1 -1
  92. package/dist/generic/ResolverBase.js +201 -101
  93. package/dist/generic/ResolverBase.js.map +1 -1
  94. package/dist/generic/RunViewResolver.d.ts +11 -3
  95. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  96. package/dist/generic/RunViewResolver.js +79 -13
  97. package/dist/generic/RunViewResolver.js.map +1 -1
  98. package/dist/generic/actionOutputFieldSecurity.d.ts +26 -0
  99. package/dist/generic/actionOutputFieldSecurity.d.ts.map +1 -0
  100. package/dist/generic/actionOutputFieldSecurity.js +47 -0
  101. package/dist/generic/actionOutputFieldSecurity.js.map +1 -0
  102. package/dist/generic/fieldSecurityTransport.d.ts +30 -0
  103. package/dist/generic/fieldSecurityTransport.d.ts.map +1 -0
  104. package/dist/generic/fieldSecurityTransport.js +30 -0
  105. package/dist/generic/fieldSecurityTransport.js.map +1 -0
  106. package/dist/index.d.ts +3 -0
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +122 -66
  109. package/dist/index.js.map +1 -1
  110. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  111. package/dist/integration/CustomColumnPromoter.js +6 -1
  112. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  113. package/dist/logging/variablesLoggingMiddleware.d.ts +12 -0
  114. package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
  115. package/dist/logging/variablesLoggingMiddleware.js +50 -1
  116. package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
  117. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -1
  118. package/dist/realtimeSessions/SessionVerificationPortsBase.js +1 -0
  119. package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -1
  120. package/dist/realtimeSessions/ownedRealtimeSession.d.ts +8 -6
  121. package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
  122. package/dist/realtimeSessions/ownedRealtimeSession.js +15 -6
  123. package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
  124. package/dist/realtimeSessions/sessionAccess.d.ts +88 -11
  125. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
  126. package/dist/realtimeSessions/sessionAccess.js +125 -22
  127. package/dist/realtimeSessions/sessionAccess.js.map +1 -1
  128. package/dist/realtimeSessions/sessionConfigStamps.js +1 -1
  129. package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -1
  130. package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -1
  131. package/dist/realtimeSessions/subscriptionAuthorization.js +5 -2
  132. package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -1
  133. package/dist/realtimeSessions/verificationWorkflow.d.ts +2 -0
  134. package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -1
  135. package/dist/realtimeSessions/verificationWorkflow.js +1 -1
  136. package/dist/realtimeSessions/verificationWorkflow.js.map +1 -1
  137. package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
  138. package/dist/realtimeWidget/WidgetRouter.js +19 -3
  139. package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
  140. package/dist/realtimeWidget/WidgetSessionService.d.ts +67 -7
  141. package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
  142. package/dist/realtimeWidget/WidgetSessionService.js +201 -19
  143. package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
  144. package/dist/realtimeWidget/host-identity.d.ts +1 -1
  145. package/dist/realtimeWidget/host-identity.js +1 -1
  146. package/dist/realtimeWidget/widgetCore.d.ts +20 -1
  147. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
  148. package/dist/realtimeWidget/widgetCore.js +17 -1
  149. package/dist/realtimeWidget/widgetCore.js.map +1 -1
  150. package/dist/realtimeWidget/widgetGuestElevation.d.ts +1 -1
  151. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  152. package/dist/realtimeWidget/widgetGuestElevation.js +2 -1
  153. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  154. package/dist/resolvers/ActionResolver.d.ts +19 -1
  155. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  156. package/dist/resolvers/ActionResolver.js +63 -12
  157. package/dist/resolvers/ActionResolver.js.map +1 -1
  158. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  159. package/dist/resolvers/AdhocQueryResolver.js +1 -0
  160. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  161. package/dist/resolvers/AgentRunStatusPublisher.d.ts +18 -4
  162. package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
  163. package/dist/resolvers/AgentRunStatusPublisher.js +32 -8
  164. package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
  165. package/dist/resolvers/AgentSessionResolver.d.ts +10 -4
  166. package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
  167. package/dist/resolvers/AgentSessionResolver.js +40 -17
  168. package/dist/resolvers/AgentSessionResolver.js.map +1 -1
  169. package/dist/resolvers/EntityCommunicationsResolver.d.ts +13 -1
  170. package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -1
  171. package/dist/resolvers/EntityCommunicationsResolver.js +50 -14
  172. package/dist/resolvers/EntityCommunicationsResolver.js.map +1 -1
  173. package/dist/resolvers/FetchEntityVectorsResolver.d.ts +28 -0
  174. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  175. package/dist/resolvers/FetchEntityVectorsResolver.js +117 -21
  176. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  177. package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
  178. package/dist/resolvers/FileCategoryResolver.js +1 -0
  179. package/dist/resolvers/FileCategoryResolver.js.map +1 -1
  180. package/dist/resolvers/FileResolver.d.ts +5 -0
  181. package/dist/resolvers/FileResolver.d.ts.map +1 -1
  182. package/dist/resolvers/FileResolver.js +13 -4
  183. package/dist/resolvers/FileResolver.js.map +1 -1
  184. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  185. package/dist/resolvers/IntegrationDiscoveryResolver.js +45 -7
  186. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  187. package/dist/resolvers/MergeRecordsResolver.d.ts +8 -1
  188. package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
  189. package/dist/resolvers/MergeRecordsResolver.js +31 -3
  190. package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
  191. package/dist/resolvers/PipelineProgressResolver.d.ts +6 -0
  192. package/dist/resolvers/PipelineProgressResolver.d.ts.map +1 -1
  193. package/dist/resolvers/PipelineProgressResolver.js +5 -3
  194. package/dist/resolvers/PipelineProgressResolver.js.map +1 -1
  195. package/dist/resolvers/QueryResolver.d.ts.map +1 -1
  196. package/dist/resolvers/QueryResolver.js +20 -6
  197. package/dist/resolvers/QueryResolver.js.map +1 -1
  198. package/dist/resolvers/QuerySystemUserResolver.d.ts +2 -0
  199. package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
  200. package/dist/resolvers/QuerySystemUserResolver.js +15 -6
  201. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
  202. package/dist/resolvers/RSUResolver.d.ts.map +1 -1
  203. package/dist/resolvers/RSUResolver.js +12 -0
  204. package/dist/resolvers/RSUResolver.js.map +1 -1
  205. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +16 -2
  206. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  207. package/dist/resolvers/RealtimeClientSessionResolver.js +82 -28
  208. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  209. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  210. package/dist/resolvers/RemoteBrowserActionResolver.js +11 -9
  211. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  212. package/dist/resolvers/RunAIAgentResolver.d.ts +56 -8
  213. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  214. package/dist/resolvers/RunAIAgentResolver.js +209 -64
  215. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  216. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  217. package/dist/resolvers/RunTemplateResolver.js +4 -4
  218. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  219. package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
  220. package/dist/resolvers/RunTestResolver.js +3 -1
  221. package/dist/resolvers/RunTestResolver.js.map +1 -1
  222. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  223. package/dist/resolvers/SyncDataResolver.js +4 -0
  224. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  225. package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
  226. package/dist/resolvers/TestQuerySQLResolver.js +1 -0
  227. package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
  228. package/dist/resolvers/TransactionGroupResolver.d.ts +17 -2
  229. package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
  230. package/dist/resolvers/TransactionGroupResolver.js +60 -15
  231. package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
  232. package/dist/resolvers/UserViewResolver.d.ts +1 -1
  233. package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
  234. package/dist/resolvers/UserViewResolver.js +2 -1
  235. package/dist/resolvers/UserViewResolver.js.map +1 -1
  236. package/dist/rest/EntityCRUDHandler.d.ts +6 -0
  237. package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
  238. package/dist/rest/EntityCRUDHandler.js +19 -5
  239. package/dist/rest/EntityCRUDHandler.js.map +1 -1
  240. package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
  241. package/dist/rest/RESTEndpointHandler.js +8 -5
  242. package/dist/rest/RESTEndpointHandler.js.map +1 -1
  243. package/dist/rest/RealtimeRecordingHandler.d.ts +3 -3
  244. package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -1
  245. package/dist/rest/RealtimeRecordingHandler.js +7 -7
  246. package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
  247. package/dist/rest/ViewOperationsHandler.d.ts +6 -0
  248. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  249. package/dist/rest/ViewOperationsHandler.js +22 -5
  250. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  251. package/dist/serverRoleConfigUnits.d.ts +29 -0
  252. package/dist/serverRoleConfigUnits.d.ts.map +1 -0
  253. package/dist/serverRoleConfigUnits.js +33 -0
  254. package/dist/serverRoleConfigUnits.js.map +1 -0
  255. package/dist/services/TaskGraphContinuationDeliverer.d.ts +5 -0
  256. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  257. package/dist/services/TaskGraphContinuationDeliverer.js +42 -15
  258. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  259. package/dist/types.d.ts +23 -1
  260. package/dist/types.d.ts.map +1 -1
  261. package/dist/types.js.map +1 -1
  262. package/package.json +98 -98
  263. package/src/__tests__/ActionResolver.fieldSecurity.test.ts +237 -0
  264. package/src/__tests__/AdhocQueryResolver.dialect.test.ts +137 -0
  265. package/src/__tests__/AgentRunStatusPublisher.test.ts +49 -0
  266. package/src/__tests__/AgentSessionResolver.ownership.test.ts +137 -0
  267. package/src/__tests__/CustomColumnPromoterOnePass.test.ts +15 -1
  268. package/src/__tests__/EntityCommunicationsResolver.actingUser.test.ts +151 -0
  269. package/src/__tests__/EvolutionActsOnWhatItDetects.test.ts +113 -0
  270. package/src/__tests__/FetchEntityVectorsResolver.test.ts +206 -3
  271. package/src/__tests__/FileResolver.fieldSecurity.test.ts +82 -0
  272. package/src/__tests__/PipelineProgressResolver.filter.test.ts +58 -0
  273. package/src/__tests__/QuerySystemUserResolver.fieldSecurity.test.ts +49 -0
  274. package/src/__tests__/RESTEndpointHandler.test.ts +25 -0
  275. package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +23 -3
  276. package/src/__tests__/RealtimeClientSessionResolver.test.ts +324 -46
  277. package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +98 -0
  278. package/src/__tests__/ResolverBase.clientOldValues.test.ts +37 -4
  279. package/src/__tests__/ResolverBase.deleteReturnProjection.test.ts +52 -0
  280. package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +176 -0
  281. package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +23 -1
  282. package/src/__tests__/ResolverBase.publishStatusUpdate.test.ts +68 -0
  283. package/src/__tests__/ResolverBase.userViewCacheAndDedup.test.ts +130 -0
  284. package/src/__tests__/RunAIAgentResolver.branch.test.ts +68 -13
  285. package/src/__tests__/RunAIAgentResolver.conversationWriter.test.ts +210 -0
  286. package/src/__tests__/RunAIAgentResolver.fireAndForgetCompletion.test.ts +209 -0
  287. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +17 -27
  288. package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +8 -2
  289. package/src/__tests__/RunAIAgentResolver.serverReply.test.ts +358 -0
  290. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +4 -1
  291. package/src/__tests__/RunTemplateResolver.test.ts +35 -2
  292. package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +11 -0
  293. package/src/__tests__/SessionManager.test.ts +95 -0
  294. package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +114 -3
  295. package/src/__tests__/TransactionGroupResolver.fieldSecurity.test.ts +199 -0
  296. package/src/__tests__/UserViewResolver.updateWhereClause.test.ts +15 -0
  297. package/src/__tests__/ViewOperationsHandler.test.ts +7 -2
  298. package/src/__tests__/WidgetSessionService.test.ts +583 -4
  299. package/src/__tests__/apiRootBoot.test.ts +1 -1
  300. package/src/__tests__/buildMagicLinkSessionUser.test.ts +96 -0
  301. package/src/__tests__/cacheBroadcastProviders.test.ts +403 -0
  302. package/src/__tests__/cacheInvalidationRecordData.test.ts +45 -0
  303. package/src/__tests__/fieldSecurityFixtures.ts +91 -0
  304. package/src/__tests__/globalCors.test.ts +3 -3
  305. package/src/__tests__/localDevAuth.test.ts +196 -0
  306. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +10 -0
  307. package/src/__tests__/pushStatusResolver.filter.test.ts +57 -1
  308. package/src/__tests__/realtimeRecordingHandler.test.ts +19 -2
  309. package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +29 -1
  310. package/src/__tests__/realtimeSessions/ownedRealtimeSession.anonymous.test.ts +31 -0
  311. package/src/__tests__/realtimeSessions/ownedRealtimeSession.namedScoped.test.ts +37 -0
  312. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +146 -7
  313. package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +1 -1
  314. package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +25 -4
  315. package/src/__tests__/resolverBase.cloudEventChangedFields.test.ts +157 -0
  316. package/src/__tests__/resolverBase.fieldSecurityTransport.test.ts +222 -0
  317. package/src/__tests__/resolverBase.fls.test.ts +146 -97
  318. package/src/__tests__/restTransport.fieldSecurity.test.ts +148 -0
  319. package/src/__tests__/runParamsTimeZone.test.ts +179 -0
  320. package/src/__tests__/runStreams.fieldSecurity.test.ts +100 -0
  321. package/src/__tests__/runViewProperties.test.ts +114 -0
  322. package/src/__tests__/scalabilityBenchmarks.test.ts +765 -0
  323. package/src/__tests__/scalabilityDatabaseIntegration.test.ts +207 -0
  324. package/src/__tests__/sessionUserClone.test.ts +5 -1
  325. package/src/__tests__/startupSweepOrder.test.ts +40 -0
  326. package/src/__tests__/unifiedAuth.test.ts +28 -0
  327. package/src/__tests__/widgetGuestElevation.test.ts +2 -0
  328. package/src/agentSessions/SessionJanitor.ts +3 -3
  329. package/src/agentSessions/SessionManager.ts +35 -12
  330. package/src/auth/AuthProviderCatalogRouter.ts +3 -0
  331. package/src/auth/index.ts +56 -12
  332. package/src/auth/initializeProviders.ts +43 -1
  333. package/src/auth/localDevAuth.ts +528 -0
  334. package/src/auth/magicLink/MagicLinkKeys.ts +2 -2
  335. package/src/auth/magicLink/types.ts +10 -0
  336. package/src/auth/sessionUserClone.ts +3 -2
  337. package/src/broadcast/BaseCacheInvalidationBroadcastProvider.ts +674 -0
  338. package/src/broadcast/index.ts +5 -0
  339. package/src/broadcast/providers/AWSIoTCoreBroadcastProvider.ts +180 -0
  340. package/src/broadcast/providers/AzureWebPubSubBroadcastProvider.ts +182 -0
  341. package/src/broadcast/providers/GCPPubSubBroadcastProvider.ts +169 -0
  342. package/src/broadcast/providers/SequencedRoleBucketBroadcastProvider.ts +57 -0
  343. package/src/cache.ts +2 -1
  344. package/src/config.ts +69 -0
  345. package/src/context.ts +32 -8
  346. package/src/coreRoutes.ts +15 -0
  347. package/src/generated/graphql-schemas/__mj.ts +2239 -102
  348. package/src/generic/CacheInvalidationResolver.ts +135 -78
  349. package/src/generic/OrphanedConversationDetailReconciler.ts +4 -2
  350. package/src/generic/PubSubManager.ts +54 -1
  351. package/src/generic/PushStatusResolver.ts +108 -11
  352. package/src/generic/ResolverBase.ts +237 -107
  353. package/src/generic/RunViewResolver.ts +80 -15
  354. package/src/generic/actionOutputFieldSecurity.ts +75 -0
  355. package/src/generic/fieldSecurityTransport.ts +62 -0
  356. package/src/index.ts +127 -68
  357. package/src/integration/CustomColumnPromoter.ts +6 -1
  358. package/src/logging/variablesLoggingMiddleware.ts +59 -3
  359. package/src/realtimeSessions/README.md +7 -3
  360. package/src/realtimeSessions/SessionVerificationPortsBase.ts +1 -0
  361. package/src/realtimeSessions/ownedRealtimeSession.ts +20 -6
  362. package/src/realtimeSessions/sessionAccess.ts +181 -25
  363. package/src/realtimeSessions/sessionConfigStamps.ts +1 -1
  364. package/src/realtimeSessions/subscriptionAuthorization.ts +4 -2
  365. package/src/realtimeSessions/verificationWorkflow.ts +3 -1
  366. package/src/realtimeWidget/WidgetRouter.ts +22 -4
  367. package/src/realtimeWidget/WidgetSessionService.ts +252 -17
  368. package/src/realtimeWidget/host-identity.ts +1 -1
  369. package/src/realtimeWidget/widgetCore.ts +30 -1
  370. package/src/realtimeWidget/widgetGuestElevation.ts +2 -1
  371. package/src/resolvers/ActionResolver.ts +78 -15
  372. package/src/resolvers/AdhocQueryResolver.ts +1 -0
  373. package/src/resolvers/AgentRunStatusPublisher.ts +36 -8
  374. package/src/resolvers/AgentSessionResolver.ts +46 -18
  375. package/src/resolvers/EntityCommunicationsResolver.ts +56 -15
  376. package/src/resolvers/FetchEntityVectorsResolver.ts +132 -21
  377. package/src/resolvers/FileCategoryResolver.ts +1 -0
  378. package/src/resolvers/FileResolver.ts +14 -4
  379. package/src/resolvers/IntegrationDiscoveryResolver.ts +49 -7
  380. package/src/resolvers/MergeRecordsResolver.ts +23 -2
  381. package/src/resolvers/PipelineProgressResolver.ts +12 -3
  382. package/src/resolvers/QueryResolver.ts +20 -6
  383. package/src/resolvers/QuerySystemUserResolver.ts +15 -6
  384. package/src/resolvers/RSUResolver.ts +12 -0
  385. package/src/resolvers/RealtimeClientSessionResolver.ts +104 -33
  386. package/src/resolvers/RemoteBrowserActionResolver.ts +15 -10
  387. package/src/resolvers/RunAIAgentResolver.ts +258 -82
  388. package/src/resolvers/RunTemplateResolver.ts +6 -4
  389. package/src/resolvers/RunTestResolver.ts +3 -1
  390. package/src/resolvers/SyncDataResolver.ts +4 -0
  391. package/src/resolvers/TestQuerySQLResolver.ts +1 -0
  392. package/src/resolvers/TransactionGroupResolver.ts +66 -15
  393. package/src/resolvers/UserViewResolver.ts +2 -1
  394. package/src/resolvers/__tests__/MergeRecordsResolver.test.ts +43 -0
  395. package/src/rest/EntityCRUDHandler.ts +22 -5
  396. package/src/rest/RESTEndpointHandler.ts +8 -5
  397. package/src/rest/RealtimeRecordingHandler.ts +13 -10
  398. package/src/rest/ViewOperationsHandler.ts +24 -6
  399. package/src/serverRoleConfigUnits.ts +48 -0
  400. package/src/services/TaskGraphContinuationDeliverer.ts +52 -20
  401. package/src/types.ts +23 -1
@@ -1,18 +1,21 @@
1
1
  import { Resolver, Mutation, Query, Arg, Ctx, ObjectType, Field, PubSub, PubSubEngine, Subscription, Root, ResolverFilterData, ID, Int } from 'type-graphql';
2
2
  import { AppContext, UserPayload } from '../types.js';
3
3
  import { DatabaseProviderBase, LogError, LogStatus, Metadata, RunView, UserInfo, IMetadataProvider } from '@memberjunction/core';
4
- import { MJConversationDetailEntity, MJConversationDetailAttachmentEntity, MJConversationDetailArtifactEntity, MJArtifactVersionEntity, MJAIAgentRequestEntity, ArtifactMetadataEngine, ConversationEngine } from '@memberjunction/core-entities';
4
+ import { MJConversationDetailEntity, MJConversationDetailEntityExtended, MJConversationDetailAttachmentEntity, MJConversationDetailArtifactEntity, MJArtifactVersionEntity, MJAIAgentRequestEntity, ArtifactMetadataEngine, ConversationEngine } from '@memberjunction/core-entities';
5
5
  import { RouteArtifact } from './artifact-routing.js';
6
6
  import { AgentRunner, ArtifactToolManager } from '@memberjunction/ai-agents';
7
- import { MJAIAgentEntityExtended, MJAIAgentRunEntityExtended, ExecuteAgentResult, ConversationUtility, AttachmentData } from '@memberjunction/ai-core-plus';
7
+ import { MJAIAgentEntityExtended, MJAIAgentRunEntityExtended, ExecuteAgentResult, ConversationUtility, AttachmentData, ContinuationAnnouncementText } from '@memberjunction/ai-core-plus';
8
8
  import { AIEngine } from '@memberjunction/aiengine';
9
9
  import { ChatMessage, ChatMessageContent } from '@memberjunction/ai';
10
10
  import { ResolverBase } from '../generic/ResolverBase.js';
11
11
  import { AgentRunStatusPublisher } from './AgentRunStatusPublisher.js';
12
+ import { ProjectEntityObjectForCaller } from '../generic/fieldSecurityTransport.js';
12
13
  import { StartLivenessPulse } from '../generic/FireAndForgetHeartbeat.js';
14
+ import { StatusUpdateOwnerID } from '../generic/PushStatusResolver.js';
13
15
  import { RequireSystemUser } from '../directives/RequireSystemUser.js';
14
16
  import { GetReadWriteProvider } from '../util.js';
15
17
  import { ResolveWidgetGuestRunContext, ElevateUserPayload } from '../realtimeWidget/widgetGuestElevation.js';
18
+ import { AuthorizeConversationTurn } from '../realtimeSessions/sessionAccess.js';
16
19
  import { IsTrustedAgentRunCaller } from './agent-run-data-guard.js';
17
20
  import { SafeJSONParse, UUIDsEqual } from '@memberjunction/global';
18
21
  import { GetAttachmentService } from '@memberjunction/aiengine';
@@ -32,6 +35,17 @@ function isAgentRunEntity(value: unknown): value is MJAIAgentRunEntityExtended {
32
35
  return typeof value === 'object' && value !== null && 'ID' in value;
33
36
  }
34
37
 
38
+ /** What a conversation turn asks for, before its agent runs (see `RunAIAgentFromConversationDetail`). */
39
+ interface ConversationTurnRequest {
40
+ UserMessageId: string;
41
+ AgentId: string;
42
+ MaxHistoryMessages: number;
43
+ HistoryFrom?: Date;
44
+ ReplyParentId?: string;
45
+ AnnouncedByAgentId?: string;
46
+ ReplyBranchId?: string | null;
47
+ }
48
+
35
49
  @ObjectType()
36
50
  export class AIAgentRunResult {
37
51
  @Field()
@@ -173,7 +187,7 @@ export class RunAIAgentResolver extends ResolverBase {
173
187
  * Sanitize ExecuteAgentResult for JSON serialization
174
188
  * Removes circular references and non-serializable objects
175
189
  */
176
- private sanitizeAgentResult(result: ExecuteAgentResult): any {
190
+ private sanitizeAgentResult(result: ExecuteAgentResult, recipient?: UserInfo): any {
177
191
  const sanitized: any = {
178
192
  success: result.success,
179
193
  payload: result.payload,
@@ -189,8 +203,9 @@ export class RunAIAgentResolver extends ResolverBase {
189
203
 
190
204
  // Safely extract agent run data using GetAll() for proper serialization
191
205
  if (result.agentRun && typeof result.agentRun.GetAll === 'function') {
192
- // Use GetAll() to get the full serialized object including extended properties
193
- sanitized.agentRun = result.agentRun.GetAll();
206
+ // GetAll() for the full serialized object including extended properties, projected for
207
+ // the recipient: the run comes from the agent engine, not from a load bound to them
208
+ sanitized.agentRun = ProjectEntityObjectForCaller(result.agentRun, recipient);
194
209
  }
195
210
  else {
196
211
  // shouldn't ever get here
@@ -306,7 +321,10 @@ export class RunAIAgentResolver extends ResolverBase {
306
321
  /** ExecuteAgentParams.TrustReservedRunData: true only for a caller IsTrustedAgentRunCaller accepts. */
307
322
  trustReservedRunData: boolean = false,
308
323
  /** The conversation branch of the run — threaded into ExecuteAgentParams.ConversationBranchID. */
309
- conversationBranchId?: string | null
324
+ conversationBranchId?: string | null,
325
+ /** The reply row this request created on the server for the run (`conversationDetailId` is its ID). The run
326
+ * writes into this instance, so its writes carry the server-authored marker and the user it was made as. */
327
+ replyDetail?: MJConversationDetailEntityExtended
310
328
  ): Promise<AIAgentRunResult> {
311
329
  const startTime = Date.now();
312
330
  // Best-effort handle for persistInFlightAgentFailure. Populated from a
@@ -315,6 +333,8 @@ export class RunAIAgentResolver extends ResolverBase {
315
333
  // run is not marked Failed — the early-failure case we most want to
316
334
  // persist, but we have no run object yet.
317
335
  const agentRunRef = runRef ?? { current: null as MJAIAgentRunEntityExtended | null };
336
+ // The publisher owns every status message, including the failure completion the catch below sends.
337
+ const statusPublisher = new AgentRunStatusPublisher(pubSub, userPayload, sessionId, conversationDetailId);
318
338
 
319
339
  try {
320
340
  LogStatus(`=== RUNNING AI AGENT FOR ID: ${agentId} ===`);
@@ -348,9 +368,8 @@ export class RunAIAgentResolver extends ResolverBase {
348
368
 
349
369
  console.log(`🚀 Starting agent execution with sessionId: ${sessionId}`);
350
370
 
351
- // The publisher owns every status message. The resolver still copies the run into
352
- // agentRunRef, including noise steps, for the fire-and-forget liveness pulse.
353
- const statusPublisher = new AgentRunStatusPublisher(pubSub, userPayload, sessionId);
371
+ // The resolver still copies the run into agentRunRef, including noise steps, for the
372
+ // fire-and-forget liveness pulse.
354
373
  const publishProgress = statusPublisher.OnProgress;
355
374
 
356
375
  // Execute the agent in conversation context - handles conversation, artifacts, etc.
@@ -384,10 +403,13 @@ export class RunAIAgentResolver extends ResolverBase {
384
403
  }
385
404
  }, {
386
405
  conversationDetailId: conversationDetailId, // Use existing if provided
406
+ agentResponseDetail: replyDetail, // the reply row this request created, when it did
387
407
  conversationId: conversationId, // LATENCY OPT #2: pre-resolved to skip redundant load in AgentRunner
388
408
  userMessage: userMessage, // Provide user message when conversationDetailId not provided
389
409
  createArtifacts: createArtifacts || false,
390
- sourceArtifactId: sourceArtifactId
410
+ sourceArtifactId: sourceArtifactId,
411
+ // A widget guest runs as the system user, but its conversation stays the guest's to write (MJ#4791).
412
+ ConversationWriter: userPayload.ElevatedFrom,
391
413
  });
392
414
 
393
415
  const result = conversationResult.agentResult;
@@ -450,7 +472,7 @@ export class RunAIAgentResolver extends ResolverBase {
450
472
  }
451
473
 
452
474
  // Create sanitized payload for JSON serialization
453
- const sanitizedResult = this.sanitizeAgentResult(result);
475
+ const sanitizedResult = this.sanitizeAgentResult(result, currentUser);
454
476
  const returnResult = JSON.stringify(sanitizedResult);
455
477
 
456
478
  // Publish final events with enriched result data for fire-and-forget clients
@@ -473,11 +495,18 @@ export class RunAIAgentResolver extends ResolverBase {
473
495
  } catch (error) {
474
496
  const executionTime = Date.now() - startTime;
475
497
  LogError(`AI Agent run failed:`, undefined, error);
476
- const errorMessage = (error as Error).message || 'Unknown error occurred';
498
+ // Never dereference a non-Error throw: this catch must not itself throw, since executeAIAgent resolves on
499
+ // every failure (executeAgentInBackground relies on it).
500
+ const errorMessage = (error instanceof Error && error.message) || 'Unknown error occurred';
477
501
 
478
502
  // Fire-and-forget clients otherwise leave the run Running and the
479
503
  // conversation detail In-Progress (Explorer red-pill timer).
480
- await this.persistInFlightAgentFailure(p, userPayload, agentRunRef.current, conversationDetailId, errorMessage);
504
+ await this.persistInFlightAgentFailure(p, userPayload, agentRunRef.current, conversationDetailId, errorMessage, replyDetail);
505
+ // A fire-and-forget client has only this message to learn the run ended: without it, it waits out its
506
+ // idle timeout and then reconciles the run as a success. Published after the rows are closed, so a
507
+ // client that reloads the reply on this message reads the error. PublishFinal is the try's last step
508
+ // that can throw, so a run gets one completion or the other, never both.
509
+ statusPublisher.PublishFailure(conversationDetailId, errorMessage);
481
510
 
482
511
  const errorResult = {
483
512
  success: false,
@@ -503,7 +532,9 @@ export class RunAIAgentResolver extends ResolverBase {
503
532
  userPayload: UserPayload,
504
533
  run: MJAIAgentRunEntityExtended | null | undefined,
505
534
  conversationDetailId: string | undefined,
506
- errorMessage: string
535
+ errorMessage: string,
536
+ /** The run's reply row, when this request created it: written in place, as the server. */
537
+ replyDetail?: MJConversationDetailEntityExtended
507
538
  ): Promise<void> {
508
539
  try {
509
540
  if (run && run.Status === 'Running') {
@@ -520,10 +551,11 @@ export class RunAIAgentResolver extends ResolverBase {
520
551
  return;
521
552
  }
522
553
  if (conversationDetailId) {
523
- const detail = await provider.GetEntityObject<MJConversationDetailEntity>(
554
+ const detail = replyDetail ?? await provider.GetEntityObject<MJConversationDetailEntity>(
524
555
  'MJ: Conversation Details',
525
556
  user
526
557
  );
558
+ await detail.EnsureSaveComplete(); // the run may still be saving into the same reply instance
527
559
  if (await detail.Load(conversationDetailId) && detail.Status === 'In-Progress') {
528
560
  await detail.EnsureSaveComplete();
529
561
  detail.Status = 'Error';
@@ -925,16 +957,27 @@ export class RunAIAgentResolver extends ResolverBase {
925
957
  }
926
958
 
927
959
  /**
928
- * Optimized mutation that loads conversation history server-side.
929
- * This avoids sending large attachment data from client to server.
960
+ * Runs an agent's turn in a conversation: the server loads the history (attachments included, so the client never
961
+ * sends them), creates the agent's reply row, and runs the agent into it.
962
+ *
963
+ * Only the server writes agent replies (A19). The reply row (`Role` `AI`, In-Progress, its `ParentID` the user's
964
+ * message) is created here, as the person asking, before the run starts. In fire-and-forget mode the
965
+ * acknowledgement names it, `{ accepted, fireAndForget, conversationDetailId, createdAt }`, and is returned before
966
+ * the run publishes anything; every progress, streaming and completion message carries the same
967
+ * `conversationDetailId`. In synchronous mode the result's `agentRun.ConversationDetailID` names it.
968
+ *
969
+ * Threading, both optional: `replyParentId` puts the reply under another message of the same conversation the
970
+ * caller can read (MJ's chat threads a delegate's reply under the manager's); `announcedByAgentId` has the server
971
+ * first post that agent's fixed line "Continuing with **{agent}** for refinement..." and thread the reply under
972
+ * it, and the acknowledgement then also carries `announcementDetailId`.
930
973
  *
931
- * @param conversationDetailId - The conversation detail ID (user's message already saved)
974
+ * @param userMessageId - The person's message this turn answers, already saved.
932
975
  * @param agentId - The agent to execute
933
976
  * @param maxHistoryMessages - Maximum number of history messages to include (default: 20)
934
977
  */
935
978
  @Mutation(() => AIAgentRunResult)
936
979
  async RunAIAgentFromConversationDetail(
937
- @Arg('conversationDetailId') conversationDetailId: string,
980
+ @Arg('userMessageId') userMessageId: string,
938
981
  @Arg('agentId') agentId: string,
939
982
  @Ctx() { userPayload, providers, dataSource }: AppContext,
940
983
  @Arg('sessionId') sessionId: string,
@@ -957,11 +1000,17 @@ export class RunAIAgentResolver extends ResolverBase {
957
1000
  * run's own conversation reads honour it too. Omitted (clients send it only when set, so an
958
1001
  * older client never does), the run reads the whole conversation as before. An unreadable
959
1002
  * value fails the request rather than being dropped. */
960
- @Arg('agentHistoryFrom', { nullable: true }) agentHistoryFrom?: string
1003
+ @Arg('agentHistoryFrom', { nullable: true }) agentHistoryFrom?: string,
1004
+ /** The message the reply threads under, when not the user's message: one in the same conversation the
1005
+ * caller can read. */
1006
+ @Arg('replyParentId', { nullable: true }) replyParentId?: string,
1007
+ /** An active agent (MJ's chat passes its conversation manager) whose fixed line "Continuing with
1008
+ * **{agent}** for refinement..." the server posts before the reply, which then threads under it. */
1009
+ @Arg('announcedByAgentId', { nullable: true }) announcedByAgentId?: string,
1010
+ /** The fork the reply and history run on, when the turn forks from `userMessageId` (such as Regenerate).
1011
+ * Checked to be a branch of the conversation whose path holds `userMessageId`. */
1012
+ @Arg('replyBranchId', { nullable: true }) replyBranchId?: string
961
1013
  ): Promise<AIAgentRunResult> {
962
- // Check API key scope authorization for agent execution
963
- await this.CheckAPIKeyScopeAuthorization('agent:execute', agentId, userPayload);
964
-
965
1014
  const p = GetReadWriteProvider(providers);
966
1015
  const currentUser = this.GetUserFromPayload(userPayload);
967
1016
 
@@ -978,10 +1027,20 @@ export class RunAIAgentResolver extends ResolverBase {
978
1027
  // AUTHORITATIVELY from the widget instance (never the client-supplied arg) — so a guest needs
979
1028
  // no grants to WRITE the AI run entities and cannot run an arbitrary agent under elevation.
980
1029
  // Conversation OWNERSHIP is still enforced under the guest principal below: the guest loads its
981
- // own ConversationDetail through the Widget Guest RLS filters, so a detail id from another
982
- // session resolves to "not found" before any elevated work happens.
1030
+ // own ConversationDetail through its role's RLS filters, and startConversationTurn then checks
1031
+ // the conversation's scope stamp against the visitor (MJ#6032), so a detail id from another
1032
+ // visitor's session is refused before any elevated work happens. The reply row is written
1033
+ // as the guest too, never as the elevated principal, which owns no guest's conversation.
983
1034
  const widgetElevation = await ResolveWidgetGuestRunContext(userPayload, p);
984
1035
  const effectiveAgentId = widgetElevation ? widgetElevation.PinnedAgentId : agentId;
1036
+ // Check API key scope authorization for agent execution against the resolved agent id.
1037
+ await this.CheckAPIKeyScopeAuthorization('agent:execute', effectiveAgentId, userPayload);
1038
+ // A widget guest has one pinned agent and never posts a continuation announcement as another agent;
1039
+ // an API-key caller must hold agent:execute for the announcing agent as well as the running agent.
1040
+ const effectiveAnnouncedByAgentId = widgetElevation ? undefined : announcedByAgentId;
1041
+ if (effectiveAnnouncedByAgentId) {
1042
+ await this.CheckAPIKeyScopeAuthorization('agent:execute', effectiveAnnouncedByAgentId, userPayload);
1043
+ }
985
1044
  const effectiveUserPayload = widgetElevation ? ElevateUserPayload(userPayload, widgetElevation.ElevatedUser) : userPayload;
986
1045
  // Only the system user or an API key holding the agent:trusted scope may set a run's scope and agent-type
987
1046
  // params through data (ExecuteAgentParams.TrustReservedRunData). Judged on the caller's own payload: a widget
@@ -993,22 +1052,19 @@ export class RunAIAgentResolver extends ResolverBase {
993
1052
  try {
994
1053
  // Parsed before anything loads: a floor the caller asked for must never be dropped.
995
1054
  const historyFrom = parseAgentHistoryFrom(agentHistoryFrom);
996
-
997
- // LATENCY OPTIMIZATION (Opt #2 + #3): Load ConversationDetail once here to extract
998
- // conversationId, then pass it downstream. Previously this record was loaded multiple
999
- // times: once in loadConversationHistoryWithAttachments (just to get conversationId),
1000
- // once in AgentRunner (same reason), and once in createCompletionNotification. Now we
1001
- // load it a single time and thread conversationId through the call chain.
1002
- const currentDetail = await p.GetEntityObject<MJConversationDetailEntity>(
1003
- 'MJ: Conversation Details',
1004
- currentUser
1005
- );
1006
- if (!await currentDetail.Load(conversationDetailId)) {
1007
- throw new Error(`Conversation detail ${conversationDetailId} not found`);
1008
- }
1009
- const conversationId = currentDetail.ConversationID;
1010
- // The branch the placeholder row was created on decides which path the run reads.
1011
- const branchId: string | null = currentDetail.BranchID ?? null;
1055
+ const turn = await this.startConversationTurn(p, currentUser, {
1056
+ UserMessageId: userMessageId,
1057
+ AgentId: effectiveAgentId,
1058
+ MaxHistoryMessages: maxHistoryMessages || 20,
1059
+ HistoryFrom: historyFrom,
1060
+ ReplyParentId: replyParentId,
1061
+ AnnouncedByAgentId: effectiveAnnouncedByAgentId,
1062
+ ReplyBranchId: replyBranchId
1063
+ });
1064
+ const reply = turn.Reply;
1065
+ // The turn's branch (the message's own branch, or a validated fork whose path holds the message)
1066
+ // decides which path the run reads.
1067
+ const branchId = turn.BranchId;
1012
1068
 
1013
1069
  // A turn that follows a run the user STOPPED continues that run rather than starting
1014
1070
  // over: chain it by LastRunID (the chat client never sends one) and, when the caller
@@ -1016,7 +1072,7 @@ export class RunAIAgentResolver extends ResolverBase {
1016
1072
  let effectiveLastRunId = lastRunId;
1017
1073
  let effectiveAutoPopulate = autoPopulateLastRunPayload;
1018
1074
  if (!effectiveLastRunId) {
1019
- const stoppedRunId = await this.findUserStoppedPredecessorRunId(conversationId, effectiveAgentId, branchId, currentUser, p);
1075
+ const stoppedRunId = await this.findUserStoppedPredecessorRunId(turn.ConversationId, effectiveAgentId, branchId, currentUser, p);
1020
1076
  if (stoppedRunId) {
1021
1077
  effectiveLastRunId = stoppedRunId;
1022
1078
  if (effectiveAutoPopulate === undefined && payload == null) {
@@ -1026,41 +1082,32 @@ export class RunAIAgentResolver extends ResolverBase {
1026
1082
  }
1027
1083
  }
1028
1084
 
1029
- // Load conversation history with attachments from DB
1030
- const messages = await this.loadConversationHistoryWithAttachments(
1031
- conversationId,
1032
- currentUser,
1033
- maxHistoryMessages || 20,
1034
- p,
1035
- // The UI creates the agent-response placeholder row ('⏳ ...') before invoking
1036
- // this mutation — exclude it so the model never sees an empty assistant turn.
1037
- [conversationDetailId],
1038
- historyFrom,
1039
- branchId
1040
- );
1041
-
1042
- // Convert to JSON string for the existing executeAIAgent method
1043
- const messagesJson = JSON.stringify(messages);
1044
-
1045
1085
  if (fireAndForget) {
1046
1086
  // Fire-and-forget mode: start execution in background, return immediately.
1047
1087
  // The client will receive the result via WebSocket PubSub completion event.
1048
1088
  this.executeAgentInBackground(
1049
- p, dataSource, effectiveAgentId, effectiveUserPayload, messagesJson, sessionId, pubSub,
1089
+ p, dataSource, effectiveAgentId, effectiveUserPayload, turn.MessagesJson, sessionId, pubSub,
1050
1090
  data, payload, effectiveLastRunId, effectiveAutoPopulate, configurationId,
1051
- conversationDetailId, createArtifacts || false, createNotification || false,
1052
- sourceArtifactId, sourceArtifactVersionId, conversationId, planMode, requestedSkillIDs,
1091
+ reply.ID, createArtifacts || false, createNotification || false,
1092
+ sourceArtifactId, sourceArtifactVersionId, turn.ConversationId, planMode, requestedSkillIDs,
1053
1093
  undefined, // taskGraphDebug
1054
1094
  historyFrom,
1055
1095
  trustRunData,
1056
- branchId
1096
+ branchId,
1097
+ reply
1057
1098
  );
1058
1099
 
1059
1100
  LogStatus(`🔥 Fire-and-forget: Agent ${effectiveAgentId} execution started in background for session ${sessionId}`);
1060
1101
 
1061
1102
  return {
1062
1103
  success: true,
1063
- result: JSON.stringify({ accepted: true, fireAndForget: true })
1104
+ result: JSON.stringify({
1105
+ accepted: true,
1106
+ fireAndForget: true,
1107
+ conversationDetailId: reply.ID,
1108
+ createdAt: reply.__mj_CreatedAt,
1109
+ ...(turn.Announcement ? { announcementDetailId: turn.Announcement.ID } : {})
1110
+ })
1064
1111
  };
1065
1112
  }
1066
1113
 
@@ -1070,7 +1117,7 @@ export class RunAIAgentResolver extends ResolverBase {
1070
1117
  dataSource,
1071
1118
  effectiveAgentId,
1072
1119
  effectiveUserPayload,
1073
- messagesJson,
1120
+ turn.MessagesJson,
1074
1121
  sessionId,
1075
1122
  pubSub,
1076
1123
  data,
@@ -1079,19 +1126,20 @@ export class RunAIAgentResolver extends ResolverBase {
1079
1126
  effectiveLastRunId,
1080
1127
  effectiveAutoPopulate,
1081
1128
  configurationId,
1082
- conversationDetailId,
1129
+ reply.ID,
1083
1130
  createArtifacts || false,
1084
1131
  createNotification || false,
1085
1132
  sourceArtifactId,
1086
1133
  sourceArtifactVersionId,
1087
- conversationId, // LATENCY OPT #2: pass pre-resolved conversationId
1134
+ turn.ConversationId, // LATENCY OPT #2: pass pre-resolved conversationId
1088
1135
  undefined, // runRef
1089
1136
  planMode,
1090
1137
  requestedSkillIDs,
1091
1138
  undefined, // taskGraphDebug
1092
1139
  historyFrom,
1093
1140
  trustRunData,
1094
- branchId
1141
+ branchId,
1142
+ reply
1095
1143
  );
1096
1144
  } catch (error) {
1097
1145
  const errorMessage = (error as Error).message || 'Unknown error loading conversation history';
@@ -1104,6 +1152,137 @@ export class RunAIAgentResolver extends ResolverBase {
1104
1152
  }
1105
1153
  }
1106
1154
 
1155
+ /**
1156
+ * Everything a conversation turn needs before its agent runs: the user's message (read as the caller, so a
1157
+ * message they can't see is "not found", and for an anonymous visitor also checked against the conversation's
1158
+ * scope stamp), the conversation history, the optional announcement line, and the reply
1159
+ * row, created last so the history never holds the empty reply. Every row is written as `user`, the person asking,
1160
+ * and marked as server-authored by {@link AgentRunner.CreateAgentReplyDetail}.
1161
+ */
1162
+ private async startConversationTurn(
1163
+ p: DatabaseProviderBase,
1164
+ user: UserInfo,
1165
+ request: ConversationTurnRequest
1166
+ ): Promise<{ ConversationId: string; BranchId: string | null; MessagesJson: string; Reply: MJConversationDetailEntityExtended; Announcement?: MJConversationDetailEntityExtended }> {
1167
+ // LATENCY OPTIMIZATION (Opt #2 + #3): the user's message is loaded once here for its conversation and
1168
+ // branch, which are threaded through the call chain rather than reloaded downstream.
1169
+ const placement = await this.loadPlacementOf(p, user, request.UserMessageId);
1170
+ const conversationId = placement.ConversationId;
1171
+ // Loading the message as the caller is the whole ownership check for a named user. It is not for an
1172
+ // anonymous visitor: every visitor is the one shared Anonymous user, so under a guest role whose row
1173
+ // filters key on UserID another visitor's message loads fine (MJ#6032). The conversation's scope stamp
1174
+ // decides instead, exactly as it does for the realtime gates — and before any row is written.
1175
+ const access = await AuthorizeConversationTurn(conversationId, user, p);
1176
+ if (!access.Allowed) {
1177
+ LogError(`RunAIAgentFromConversationDetail: anonymous visitor ${user.AnonymousVisitorID ?? '(no visitor id)'} refused a turn in conversation ${conversationId} (${access.Reason})`);
1178
+ throw new Error('Not authorized: you do not own this conversation');
1179
+ }
1180
+ const branchId = request.ReplyBranchId
1181
+ ? await this.validateReplyBranch(p, user, conversationId, placement, request.ReplyBranchId)
1182
+ : placement.BranchId;
1183
+ const parentId = request.ReplyParentId
1184
+ ? await this.validateReplyParent(p, user, request.ReplyParentId, conversationId)
1185
+ : request.UserMessageId;
1186
+ const messages = await this.loadConversationHistoryWithAttachments(conversationId, user, request.MaxHistoryMessages, p, undefined, request.HistoryFrom, branchId);
1187
+ const runner = new AgentRunner(p);
1188
+ const announcement = request.AnnouncedByAgentId
1189
+ ? await this.postContinuationAnnouncement(runner, user, conversationId, branchId, parentId, request.AnnouncedByAgentId, request.AgentId)
1190
+ : undefined;
1191
+ const reply = await runner.CreateAgentReplyDetail({
1192
+ conversationId,
1193
+ branchId,
1194
+ agentId: request.AgentId,
1195
+ contextUser: user,
1196
+ parentId: announcement?.ID ?? parentId,
1197
+ provider: p
1198
+ });
1199
+ return { ConversationId: conversationId, BranchId: branchId, MessagesJson: JSON.stringify(messages), Reply: reply, Announcement: announcement };
1200
+ }
1201
+
1202
+ /** The conversation, branch (null is Main) and sequence of a message, read as `user`; throws when they can't read it. */
1203
+ private async loadPlacementOf(
1204
+ p: DatabaseProviderBase,
1205
+ user: UserInfo,
1206
+ messageId: string
1207
+ ): Promise<{ ConversationId: string; BranchId: string | null; Sequence: number }> {
1208
+ const message = await p.GetEntityObject<MJConversationDetailEntity>('MJ: Conversation Details', user);
1209
+ if (!await message.Load(messageId)) {
1210
+ throw new Error(`Conversation detail ${messageId} not found`);
1211
+ }
1212
+ return { ConversationId: message.ConversationID, BranchId: message.BranchID ?? null, Sequence: message.Sequence ?? -1 };
1213
+ }
1214
+
1215
+ /**
1216
+ * When a turn forks from an earlier message (such as Regenerate), the reply and history run on the fork while the
1217
+ * user's message stays on the branch it was written on. Checks that `replyBranchId` is a branch of the turn's
1218
+ * conversation whose path holds the user's message (`IsRowOnBranchPath` rather than exact `ForkFromSequence`
1219
+ * equality is deliberate: `ConversationEngine.ForkPointFrom` sets `ForkFromSequence = message.Sequence` and the
1220
+ * fork belongs to this conversation); returns `replyBranchId`, or throws before any row is created.
1221
+ */
1222
+ private async validateReplyBranch(
1223
+ p: DatabaseProviderBase,
1224
+ user: UserInfo,
1225
+ conversationId: string,
1226
+ userMessage: { BranchId: string | null; Sequence: number },
1227
+ replyBranchId: string
1228
+ ): Promise<string> {
1229
+ if (UUIDsEqual(replyBranchId, userMessage.BranchId)) {
1230
+ return replyBranchId;
1231
+ }
1232
+ if (userMessage.Sequence < 0) {
1233
+ throw new Error(`Conversation detail is not on branch ${replyBranchId}'s path, so the reply cannot be written on that branch`);
1234
+ }
1235
+ const branches = await ConversationEngine.LoadBranchesFresh(conversationId, user, p);
1236
+ const chain = ConversationEngine.BuildBranchChain(branches, replyBranchId);
1237
+ if (!ConversationEngine.IsRowOnBranchPath({ BranchID: userMessage.BranchId, Sequence: userMessage.Sequence }, chain)) {
1238
+ throw new Error(`Conversation detail is not on branch ${replyBranchId}'s path, so the reply cannot be written on that branch`);
1239
+ }
1240
+ return replyBranchId;
1241
+ }
1242
+
1243
+ /**
1244
+ * A reply's parent is part of what the reply says, so the server sets it, from a checked input: a message the
1245
+ * caller can read, in the turn's own conversation. Returns its ID; throws otherwise.
1246
+ */
1247
+ private async validateReplyParent(p: DatabaseProviderBase, user: UserInfo, parentId: string, conversationId: string): Promise<string> {
1248
+ const { ConversationId: parentConversationId } = await this.loadPlacementOf(p, user, parentId);
1249
+ if (!UUIDsEqual(parentConversationId, conversationId)) {
1250
+ throw new Error(`Conversation detail ${parentId} is not in this turn's conversation, so the reply cannot thread under it`);
1251
+ }
1252
+ return parentId;
1253
+ }
1254
+
1255
+ /**
1256
+ * Posts the announcing agent's line, "Continuing with **{agent}** for refinement...", as a completed reply of the
1257
+ * announcing agent. Its text is fixed and names the agent from metadata, so a caller chooses only whether it is
1258
+ * posted and by which active agent, never what it says.
1259
+ */
1260
+ private async postContinuationAnnouncement(
1261
+ runner: AgentRunner,
1262
+ user: UserInfo,
1263
+ conversationId: string,
1264
+ branchId: string | null,
1265
+ parentId: string,
1266
+ announcerId: string,
1267
+ agentId: string
1268
+ ): Promise<MJConversationDetailEntityExtended> {
1269
+ await AIEngine.Instance.Config(false, user);
1270
+ const announcer = AIEngine.Instance.GetAgentByID(announcerId);
1271
+ if (!announcer || announcer.Status !== 'Active') {
1272
+ throw new Error(`Agent ${announcerId} cannot announce the turn: it is not an active agent`);
1273
+ }
1274
+ const agentName = AIEngine.Instance.GetAgentByID(agentId)?.Name ?? 'the agent';
1275
+ return runner.CreateAgentReplyDetail({
1276
+ conversationId,
1277
+ branchId,
1278
+ agentId: announcer.ID,
1279
+ contextUser: user,
1280
+ parentId,
1281
+ message: ContinuationAnnouncementText(agentName),
1282
+ status: 'Complete'
1283
+ });
1284
+ }
1285
+
1107
1286
  /**
1108
1287
  * Respond to a pending AIAgentRequest from the dashboard or API.
1109
1288
  * Updates the request record with the response and optionally spawns a
@@ -1283,9 +1462,8 @@ export class RunAIAgentResolver extends ResolverBase {
1283
1462
 
1284
1463
  /**
1285
1464
  * Execute agent in background (fire-and-forget).
1286
- * Handles errors by publishing error completion events via PubSub,
1287
- * so the client receives them via WebSocket even though the HTTP response
1288
- * has already been sent.
1465
+ * The client learns the outcome only from the run's completion status message (executeAIAgent publishes it,
1466
+ * failure included), since the HTTP response has already been sent. A liveness pulse runs until the run ends.
1289
1467
  */
1290
1468
  private executeAgentInBackground(
1291
1469
  p: DatabaseProviderBase,
@@ -1317,36 +1495,34 @@ export class RunAIAgentResolver extends ResolverBase {
1317
1495
  /** ExecuteAgentParams.TrustReservedRunData — see executeAIAgent. */
1318
1496
  trustReservedRunData: boolean = false,
1319
1497
  /** The conversation branch of the run — threaded through to ExecuteAgentParams.ConversationBranchID. */
1320
- conversationBranchId?: string | null
1498
+ conversationBranchId?: string | null,
1499
+ /** The reply row this request created for the run — see executeAIAgent. */
1500
+ replyDetail?: MJConversationDetailEntityExtended
1321
1501
  ): void {
1322
1502
  // Ref the liveness pulse reads to enrich heartbeats once the run is created.
1323
1503
  const runRef: { current: MJAIAgentRunEntityExtended | null } = { current: null };
1324
1504
  const pulse = StartLivenessPulse({
1325
1505
  pubSub,
1326
1506
  sessionId,
1327
- ownerUserId: userPayload.userRecord.ID,
1507
+ ownerUserId: StatusUpdateOwnerID(userPayload),
1328
1508
  resolver: 'RunAIAgentResolver',
1329
1509
  readStatus: () => runRef.current
1330
1510
  ? { runId: runRef.current.ID, status: runRef.current.Status }
1331
1511
  : undefined,
1332
1512
  });
1333
1513
 
1334
- // Execute in background - errors are handled within, not propagated
1514
+ // executeAIAgent resolves on every failure and publishes the run's completion itself (PublishFinal, or
1515
+ // PublishFailure from its catch), so nothing here publishes one.
1335
1516
  this.executeAIAgent(
1336
1517
  p, dataSource, agentId, userPayload, messagesJson, sessionId, pubSub,
1337
1518
  data, payload, undefined, lastRunId, autoPopulateLastRunPayload,
1338
1519
  configurationId, conversationDetailId, createArtifacts, createNotification,
1339
1520
  sourceArtifactId, sourceArtifactVersionId, conversationId, runRef, planMode, requestedSkillIDs, taskGraphDebug,
1340
- conversationHistoryFrom, trustReservedRunData, conversationBranchId
1521
+ conversationHistoryFrom, trustReservedRunData, conversationBranchId, replyDetail
1341
1522
  ).catch((error: unknown) => {
1342
- // Background execution failed unexpectedly (executeAIAgent has its own try-catch,
1343
- // so this would only fire for truly unexpected errors).
1344
- const errorMessage = (error instanceof Error) ? error.message : 'Unknown background execution error';
1345
- LogError(`🔥 Fire-and-forget background execution failed: ${errorMessage}`, undefined, error);
1346
-
1347
- // Publish error completion event so the client knows the agent failed.
1348
- // The publisher is created inside executeAIAgent, which rejected before returning one.
1349
- new AgentRunStatusPublisher(pubSub, userPayload, sessionId).PublishFailure(conversationDetailId, errorMessage);
1523
+ // Only a broken executeAIAgent contract lands here. Logged rather than left as an unhandled rejection;
1524
+ // no completion is published, because executeAIAgent may already have sent one.
1525
+ LogError(`🔥 Fire-and-forget run of agent ${agentId} for session ${sessionId} rejected, which executeAIAgent never should`, undefined, error);
1350
1526
  }).finally(() => pulse.Stop());
1351
1527
  }
1352
1528
 
@@ -84,7 +84,7 @@ export class RunTemplateResolver extends ResolverBase {
84
84
  }
85
85
 
86
86
  // Load template content (get the first/highest priority content)
87
- const rv = new RunView();
87
+ const rv = RunView.FromMetadataProvider(p);
88
88
  const templateContentResult = await rv.RunView<MJTemplateContentEntity>({
89
89
  EntityName: 'MJ: Template Contents',
90
90
  ExtraFilter: `TemplateID = '${templateId}'`,
@@ -102,12 +102,14 @@ export class RunTemplateResolver extends ResolverBase {
102
102
  }
103
103
 
104
104
  // Configure and render the template
105
- await TemplateEngineServer.Instance.Config(true /*always refresh to get latest templates*/, currentUser);
105
+ await TemplateEngineServer.Instance.Config(true /*always refresh to get latest templates*/, currentUser, p);
106
106
  const result = await TemplateEngineServer.Instance.RenderTemplate(
107
107
  templateEntity,
108
108
  templateContentResult.Results[0],
109
- data,
110
- true // skip validation for execution
109
+ data,
110
+ true, // skip validation for execution
111
+ undefined,
112
+ { ContextUser: currentUser },
111
113
  );
112
114
 
113
115
  const executionTime = Date.now() - startTime;
@@ -15,6 +15,7 @@ import { AppContext, UserPayload } from '../types.js';
15
15
  import { LogError, LogStatus, UserInfo } from '@memberjunction/core';
16
16
  import { TestEngine } from '@memberjunction/testing-engine';
17
17
  import { ResolverBase } from '../generic/ResolverBase.js';
18
+ import { ProjectEntityObjectForCaller, ProjectableEntityObject } from '../generic/fieldSecurityTransport.js';
18
19
  import { StartLivenessPulse } from '../generic/FireAndForgetHeartbeat.js';
19
20
  import { TestRunVariables, TestLogMessage, TestRunResult as EngineTestRunResult } from '@memberjunction/testing-engine-base';
20
21
 
@@ -447,7 +448,8 @@ export class RunTestResolver extends ResolverBase {
447
448
  testRunId: (testRun as { ID?: string })?.ID || testId,
448
449
  type: 'progress',
449
450
  testRun: testRun && typeof (testRun as { GetAll?: () => Record<string, unknown> }).GetAll === 'function'
450
- ? (testRun as { GetAll: () => Record<string, unknown> }).GetAll()
451
+ // projected for the recipient: the run object comes from the test engine, not a load bound to them
452
+ ? ProjectEntityObjectForCaller(testRun as unknown as ProjectableEntityObject, userPayload.userRecord)
451
453
  : undefined,
452
454
  progress: {
453
455
  currentStep: progress.step,
@@ -319,6 +319,7 @@ export class SyncDataResolver {
319
319
  }
320
320
  else {
321
321
  // pass back the full record as it was JUST BEFORE the delete, often quite useful on the other end
322
+ // fls-guard: system-user-only resolver (@RequireSystemUser)
322
323
  result.RecordJSON = await altKeyResult.GetDataObjectJSON({
323
324
  includeRelatedEntityData: false,
324
325
  excludeFields: [],
@@ -337,6 +338,7 @@ export class SyncDataResolver {
337
338
  }
338
339
  else if (await entityObject.InnerLoad(pk)) {
339
340
  // pass back the full record as it was JUST BEFORE the delete, often quite useful on the other end
341
+ // fls-guard: system-user-only resolver (@RequireSystemUser)
340
342
  result.RecordJSON = await entityObject.GetDataObjectJSON({
341
343
  includeRelatedEntityData: false,
342
344
  excludeFields: [],
@@ -368,6 +370,7 @@ export class SyncDataResolver {
368
370
  result.Success = true;
369
371
  result.PrimaryKey = new CompositeKey(entityObject.PrimaryKeys.map((pk) => ({FieldName: pk.Name, Value: pk.Value})));
370
372
  // pass back the full record AFTER the sync, that's often quite useful on the other end
373
+ // fls-guard: system-user-only resolver (@RequireSystemUser)
371
374
  result.RecordJSON = await entityObject.GetDataObjectJSON({
372
375
  includeRelatedEntityData: false,
373
376
  excludeFields: [],
@@ -411,6 +414,7 @@ export class SyncDataResolver {
411
414
  result.PrimaryKey = new CompositeKey(entityObject.PrimaryKeys.map((pk) => ({FieldName: pk.Name, Value: pk.Value})));
412
415
  }
413
416
  // pass back the full record AFTER the sync, that's often quite useful on the other end
417
+ // fls-guard: system-user-only resolver (@RequireSystemUser)
414
418
  result.RecordJSON = await entityObject.GetDataObjectJSON({
415
419
  includeRelatedEntityData: false,
416
420
  excludeFields: [],