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

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 (407) hide show
  1. package/README.md +69 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/agentSessions/BridgeHostInstance.d.ts +8 -3
  4. package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -1
  5. package/dist/agentSessions/BridgeHostInstance.js +14 -4
  6. package/dist/agentSessions/BridgeHostInstance.js.map +1 -1
  7. package/dist/agentSessions/HandoffOfferExpiry.d.ts +10 -0
  8. package/dist/agentSessions/HandoffOfferExpiry.d.ts.map +1 -0
  9. package/dist/agentSessions/HandoffOfferExpiry.js +18 -0
  10. package/dist/agentSessions/HandoffOfferExpiry.js.map +1 -0
  11. package/dist/agentSessions/ReturningVisitorRecap.js +1 -0
  12. package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
  13. package/dist/agentSessions/SessionJanitor.d.ts +31 -8
  14. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  15. package/dist/agentSessions/SessionJanitor.js +85 -41
  16. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  17. package/dist/agentSessions/SessionManager.d.ts +14 -4
  18. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  19. package/dist/agentSessions/SessionManager.js +28 -4
  20. package/dist/agentSessions/SessionManager.js.map +1 -1
  21. package/dist/agentSessions/conversationBrowsers.d.ts +61 -0
  22. package/dist/agentSessions/conversationBrowsers.d.ts.map +1 -0
  23. package/dist/agentSessions/conversationBrowsers.js +118 -0
  24. package/dist/agentSessions/conversationBrowsers.js.map +1 -0
  25. package/dist/agentSessions/sessionDeadline.d.ts +43 -0
  26. package/dist/agentSessions/sessionDeadline.d.ts.map +1 -0
  27. package/dist/agentSessions/sessionDeadline.js +68 -0
  28. package/dist/agentSessions/sessionDeadline.js.map +1 -0
  29. package/dist/auth/APIKeyScopeAuth.d.ts.map +1 -1
  30. package/dist/auth/APIKeyScopeAuth.js +5 -4
  31. package/dist/auth/APIKeyScopeAuth.js.map +1 -1
  32. package/dist/auth/loginEmailResolution.d.ts +41 -0
  33. package/dist/auth/loginEmailResolution.d.ts.map +1 -0
  34. package/dist/auth/loginEmailResolution.js +47 -0
  35. package/dist/auth/loginEmailResolution.js.map +1 -0
  36. package/dist/auth/magicLink/MagicLinkKeys.d.ts +50 -2
  37. package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
  38. package/dist/auth/magicLink/MagicLinkKeys.js +129 -12
  39. package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
  40. package/dist/auth/magicLink/MagicLinkRouter.d.ts +3 -0
  41. package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
  42. package/dist/auth/magicLink/MagicLinkRouter.js +23 -5
  43. package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
  44. package/dist/auth/magicLink/MagicLinkService.d.ts +39 -5
  45. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  46. package/dist/auth/magicLink/MagicLinkService.js +221 -9
  47. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  48. package/dist/auth/magicLink/index.d.ts +1 -1
  49. package/dist/auth/magicLink/index.d.ts.map +1 -1
  50. package/dist/auth/magicLink/index.js +1 -1
  51. package/dist/auth/magicLink/index.js.map +1 -1
  52. package/dist/auth/magicLink/redeemLanding.d.ts +2 -2
  53. package/dist/auth/magicLink/redeemLanding.d.ts.map +1 -1
  54. package/dist/auth/magicLink/redeemLanding.js +7 -4
  55. package/dist/auth/magicLink/redeemLanding.js.map +1 -1
  56. package/dist/auth/magicLink/types.d.ts +27 -3
  57. package/dist/auth/magicLink/types.d.ts.map +1 -1
  58. package/dist/auth/magicLink/types.js +0 -4
  59. package/dist/auth/magicLink/types.js.map +1 -1
  60. package/dist/auth/newUsers.d.ts.map +1 -1
  61. package/dist/auth/newUsers.js +1 -0
  62. package/dist/auth/newUsers.js.map +1 -1
  63. package/dist/backgroundSweepConfigUnits.d.ts +231 -0
  64. package/dist/backgroundSweepConfigUnits.d.ts.map +1 -0
  65. package/dist/backgroundSweepConfigUnits.js +180 -0
  66. package/dist/backgroundSweepConfigUnits.js.map +1 -0
  67. package/dist/config.d.ts +906 -8
  68. package/dist/config.d.ts.map +1 -1
  69. package/dist/config.js +59 -0
  70. package/dist/config.js.map +1 -1
  71. package/dist/context.d.ts +2 -0
  72. package/dist/context.d.ts.map +1 -1
  73. package/dist/context.js +35 -4
  74. package/dist/context.js.map +1 -1
  75. package/dist/coreRoutes.d.ts +3 -1
  76. package/dist/coreRoutes.d.ts.map +1 -1
  77. package/dist/coreRoutes.js +6 -4
  78. package/dist/coreRoutes.js.map +1 -1
  79. package/dist/generated/graphql-schemas/__mj.d.ts +228 -0
  80. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  81. package/dist/generated/graphql-schemas/__mj.js +1163 -6
  82. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  83. package/dist/generic/OrphanedConversationDetailReconciler.d.ts +11 -0
  84. package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
  85. package/dist/generic/OrphanedConversationDetailReconciler.js +27 -1
  86. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  87. package/dist/generic/ResolverBase.d.ts +50 -5
  88. package/dist/generic/ResolverBase.d.ts.map +1 -1
  89. package/dist/generic/ResolverBase.js +321 -132
  90. package/dist/generic/ResolverBase.js.map +1 -1
  91. package/dist/generic/RunViewResolver.d.ts +4 -0
  92. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  93. package/dist/generic/RunViewResolver.js +47 -7
  94. package/dist/generic/RunViewResolver.js.map +1 -1
  95. package/dist/index.d.ts +1 -0
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +157 -35
  98. package/dist/index.js.map +1 -1
  99. package/dist/integration/ConnectionPause.d.ts +121 -0
  100. package/dist/integration/ConnectionPause.d.ts.map +1 -0
  101. package/dist/integration/ConnectionPause.js +183 -0
  102. package/dist/integration/ConnectionPause.js.map +1 -0
  103. package/dist/integration/CustomColumnPromoter.d.ts +72 -6
  104. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  105. package/dist/integration/CustomColumnPromoter.js +87 -46
  106. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  107. package/dist/integration/EntityMapLifecycle.d.ts +78 -4
  108. package/dist/integration/EntityMapLifecycle.d.ts.map +1 -1
  109. package/dist/integration/EntityMapLifecycle.js +199 -5
  110. package/dist/integration/EntityMapLifecycle.js.map +1 -1
  111. package/dist/integration/InactiveRowWarnings.d.ts +31 -0
  112. package/dist/integration/InactiveRowWarnings.d.ts.map +1 -1
  113. package/dist/integration/InactiveRowWarnings.js +29 -0
  114. package/dist/integration/InactiveRowWarnings.js.map +1 -1
  115. package/dist/integration/ObjectNamesByID.d.ts +28 -0
  116. package/dist/integration/ObjectNamesByID.d.ts.map +1 -0
  117. package/dist/integration/ObjectNamesByID.js +87 -0
  118. package/dist/integration/ObjectNamesByID.js.map +1 -0
  119. package/dist/integration/PostRestartIntrospect.d.ts +26 -0
  120. package/dist/integration/PostRestartIntrospect.d.ts.map +1 -0
  121. package/dist/integration/PostRestartIntrospect.js +27 -0
  122. package/dist/integration/PostRestartIntrospect.js.map +1 -0
  123. package/dist/integration/RSUPostRestartProgress.d.ts +176 -0
  124. package/dist/integration/RSUPostRestartProgress.d.ts.map +1 -0
  125. package/dist/integration/RSUPostRestartProgress.js +323 -0
  126. package/dist/integration/RSUPostRestartProgress.js.map +1 -0
  127. package/dist/integration/RSUProgressBridge.d.ts +41 -2
  128. package/dist/integration/RSUProgressBridge.d.ts.map +1 -1
  129. package/dist/integration/RSUProgressBridge.js +84 -1
  130. package/dist/integration/RSUProgressBridge.js.map +1 -1
  131. package/dist/integration/RunArtifactAuthorization.d.ts +39 -0
  132. package/dist/integration/RunArtifactAuthorization.d.ts.map +1 -0
  133. package/dist/integration/RunArtifactAuthorization.js +45 -0
  134. package/dist/integration/RunArtifactAuthorization.js.map +1 -0
  135. package/dist/integration/SchemaRefreshLaunch.d.ts +9 -0
  136. package/dist/integration/SchemaRefreshLaunch.d.ts.map +1 -1
  137. package/dist/integration/SchemaRefreshLaunch.js +3 -2
  138. package/dist/integration/SchemaRefreshLaunch.js.map +1 -1
  139. package/dist/integration/ScopedFieldsForBuild.d.ts +46 -0
  140. package/dist/integration/ScopedFieldsForBuild.d.ts.map +1 -0
  141. package/dist/integration/ScopedFieldsForBuild.js +91 -0
  142. package/dist/integration/ScopedFieldsForBuild.js.map +1 -0
  143. package/dist/integration/TargetConfigGuard.d.ts +40 -0
  144. package/dist/integration/TargetConfigGuard.d.ts.map +1 -0
  145. package/dist/integration/TargetConfigGuard.js +40 -0
  146. package/dist/integration/TargetConfigGuard.js.map +1 -0
  147. package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
  148. package/dist/realtimeSessions/ownedRealtimeSession.js +2 -3
  149. package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
  150. package/dist/realtimeSessions/sessionAccess.d.ts +15 -0
  151. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
  152. package/dist/realtimeSessions/sessionAccess.js +22 -0
  153. package/dist/realtimeSessions/sessionAccess.js.map +1 -1
  154. package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
  155. package/dist/realtimeWidget/WidgetSessionService.js +2 -0
  156. package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
  157. package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
  158. package/dist/realtimeWidget/visitorIdentity.js +1 -0
  159. package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
  160. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
  161. package/dist/realtimeWidget/widgetCore.js +4 -7
  162. package/dist/realtimeWidget/widgetCore.js.map +1 -1
  163. package/dist/realtimeWidget/widgetGuestElevation.js +1 -0
  164. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  165. package/dist/requestOrigin.d.ts +22 -0
  166. package/dist/requestOrigin.d.ts.map +1 -0
  167. package/dist/requestOrigin.js +60 -0
  168. package/dist/requestOrigin.js.map +1 -0
  169. package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
  170. package/dist/resolvers/AgentSessionResolver.js +6 -2
  171. package/dist/resolvers/AgentSessionResolver.js.map +1 -1
  172. package/dist/resolvers/AutotagPipelineResolver.d.ts.map +1 -1
  173. package/dist/resolvers/AutotagPipelineResolver.js +2 -1
  174. package/dist/resolvers/AutotagPipelineResolver.js.map +1 -1
  175. package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
  176. package/dist/resolvers/ConversationRunEventsResolver.js +2 -0
  177. package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
  178. package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts +6 -0
  179. package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts.map +1 -1
  180. package/dist/resolvers/ExecuteRemoteOperationResolver.js +30 -7
  181. package/dist/resolvers/ExecuteRemoteOperationResolver.js.map +1 -1
  182. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  183. package/dist/resolvers/GetDataResolver.js +34 -13
  184. package/dist/resolvers/GetDataResolver.js.map +1 -1
  185. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +401 -11
  186. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  187. package/dist/resolvers/IntegrationDiscoveryResolver.js +2690 -1305
  188. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  189. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  190. package/dist/resolvers/MCPResolver.js +13 -2
  191. package/dist/resolvers/MCPResolver.js.map +1 -1
  192. package/dist/resolvers/QuerySystemUserResolver.d.ts +36 -0
  193. package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
  194. package/dist/resolvers/QuerySystemUserResolver.js +174 -87
  195. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
  196. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
  197. package/dist/resolvers/RealtimeBridgeResolver.js +1 -0
  198. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
  199. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +6 -0
  200. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  201. package/dist/resolvers/RealtimeClientSessionResolver.js +36 -5
  202. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  203. package/dist/resolvers/RemoteBrowserActionResolver.d.ts +62 -10
  204. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  205. package/dist/resolvers/RemoteBrowserActionResolver.js +217 -34
  206. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  207. package/dist/resolvers/RunAIAgentResolver.d.ts +24 -8
  208. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  209. package/dist/resolvers/RunAIAgentResolver.js +185 -67
  210. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  211. package/dist/resolvers/RunAIPromptResolver.d.ts +5 -3
  212. package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
  213. package/dist/resolvers/RunAIPromptResolver.js +19 -18
  214. package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
  215. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  216. package/dist/resolvers/RunTemplateResolver.js +3 -1
  217. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  218. package/dist/resolvers/meetingRecordingRegistration.d.ts.map +1 -1
  219. package/dist/resolvers/meetingRecordingRegistration.js +2 -0
  220. package/dist/resolvers/meetingRecordingRegistration.js.map +1 -1
  221. package/dist/rest/MediaAccessKeys.d.ts.map +1 -1
  222. package/dist/rest/MediaAccessKeys.js +6 -2
  223. package/dist/rest/MediaAccessKeys.js.map +1 -1
  224. package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
  225. package/dist/rest/OAuthCallbackHandler.js +11 -5
  226. package/dist/rest/OAuthCallbackHandler.js.map +1 -1
  227. package/dist/rest/RealtimeRecordingHandler.d.ts +3 -3
  228. package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -1
  229. package/dist/rest/RealtimeRecordingHandler.js +2 -4
  230. package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
  231. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  232. package/dist/rest/ViewOperationsHandler.js +2 -1
  233. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  234. package/dist/rest/setupRESTEndpoints.d.ts +3 -1
  235. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
  236. package/dist/rest/setupRESTEndpoints.js +11 -2
  237. package/dist/rest/setupRESTEndpoints.js.map +1 -1
  238. package/dist/services/DurableEntityActionTaskSubmitter.d.ts.map +1 -1
  239. package/dist/services/DurableEntityActionTaskSubmitter.js +6 -1
  240. package/dist/services/DurableEntityActionTaskSubmitter.js.map +1 -1
  241. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  242. package/dist/services/TaskGraphActionRunner.js +2 -26
  243. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  244. package/dist/services/TaskGraphAgentRunner.d.ts +6 -0
  245. package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
  246. package/dist/services/TaskGraphAgentRunner.js +38 -0
  247. package/dist/services/TaskGraphAgentRunner.js.map +1 -1
  248. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  249. package/dist/services/TaskGraphContinuationDeliverer.js +1 -0
  250. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  251. package/dist/services/WorkQueueHostService.d.ts +2 -1
  252. package/dist/services/WorkQueueHostService.d.ts.map +1 -1
  253. package/dist/services/WorkQueueHostService.js +19 -4
  254. package/dist/services/WorkQueueHostService.js.map +1 -1
  255. package/dist/services/workQueueConfig.d.ts +2 -2
  256. package/dist/services/workQueueConfig.js +5 -5
  257. package/dist/services/workQueueConfig.js.map +1 -1
  258. package/dist/sharedCache.js +1 -1
  259. package/dist/sharedCache.js.map +1 -1
  260. package/dist/types.d.ts +17 -0
  261. package/dist/types.d.ts.map +1 -1
  262. package/dist/types.js.map +1 -1
  263. package/package.json +98 -98
  264. package/src/__tests__/APIKeyScopeAuth.test.ts +4 -8
  265. package/src/__tests__/ActiveOperations.test.ts +68 -0
  266. package/src/__tests__/AgentSessionResolver.ownership.test.ts +13 -0
  267. package/src/__tests__/AssertTargetConfigs.test.ts +75 -0
  268. package/src/__tests__/BatchApplyParity.test.ts +109 -0
  269. package/src/__tests__/BridgeHostInstance.test.ts +42 -0
  270. package/src/__tests__/CollectInactiveRowWarnings.test.ts +115 -0
  271. package/src/__tests__/ConnectionPause.test.ts +192 -0
  272. package/src/__tests__/ConnectionPauseResolver.test.ts +166 -0
  273. package/src/__tests__/ConnectionPauseSave.test.ts +143 -0
  274. package/src/__tests__/CustomColumnPromoterCompletion.test.ts +142 -0
  275. package/src/__tests__/DeleteConnectionRemovesPerConnectionCatalog.test.ts +51 -0
  276. package/src/__tests__/DetachedDiscoveryStart.test.ts +65 -0
  277. package/src/__tests__/DurableEntityActionTaskSubmitter.test.ts +73 -0
  278. package/src/__tests__/EntityMapLifecycle.rekey.test.ts +252 -0
  279. package/src/__tests__/ExecuteRemoteOperationResolver.test.ts +213 -0
  280. package/src/__tests__/HandoffOfferExpiry.test.ts +40 -0
  281. package/src/__tests__/IntegrationRunArtifactAuthorization.test.ts +126 -0
  282. package/src/__tests__/MagicLinkKeys.test.ts +102 -0
  283. package/src/__tests__/MediaAccessKeys.test.ts +57 -10
  284. package/src/__tests__/NewArrivalsDefaultDisabled.test.ts +50 -0
  285. package/src/__tests__/ObjectNamesByID.test.ts +176 -0
  286. package/src/__tests__/PostRestartMapsInCatalogScope.test.ts +146 -0
  287. package/src/__tests__/QuerySystemUserResolver.childRows.test.ts +80 -0
  288. package/src/__tests__/QuerySystemUserResolver.collision.test.ts +200 -0
  289. package/src/__tests__/QuerySystemUserResolver.permissions.test.ts +132 -0
  290. package/src/__tests__/RSUPostRestartProgress.test.ts +343 -0
  291. package/src/__tests__/RSUProgressBridge.test.ts +188 -5
  292. package/src/__tests__/RSUStartupSweepWiring.test.ts +30 -0
  293. package/src/__tests__/ReactivateRefreshMessage.test.ts +10 -3
  294. package/src/__tests__/RealtimeClientSessionResolver.test.ts +22 -0
  295. package/src/__tests__/{RefreshAdoptsNewColumns.test.ts → RefreshReportsNewColumns.test.ts} +25 -16
  296. package/src/__tests__/RemoteBrowserActionRecovery.test.ts +287 -0
  297. package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +3 -0
  298. package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +6 -4
  299. package/src/__tests__/RemoteBrowserAudioStream.test.ts +3 -0
  300. package/src/__tests__/RemoteBrowserSnapshot.test.ts +3 -0
  301. package/src/__tests__/ResolverBase.clientOldValues.test.ts +323 -26
  302. package/src/__tests__/ResolverBase.encryptedApiZeroReencrypt.test.ts +307 -0
  303. package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +53 -2
  304. package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +60 -0
  305. package/src/__tests__/RunAIAgentResolver.branch.test.ts +4 -0
  306. package/src/__tests__/RunAIAgentResolver.consultPredecessor.test.ts +289 -0
  307. package/src/__tests__/RunAIAgentResolver.handoff.test.ts +649 -0
  308. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +2 -0
  309. package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +2 -0
  310. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +87 -12
  311. package/src/__tests__/SchemaEvolutionCustomColumns.test.ts +119 -0
  312. package/src/__tests__/SchemaRefreshLaunch.test.ts +24 -2
  313. package/src/__tests__/ScopedFieldsForBuild.test.ts +192 -0
  314. package/src/__tests__/SessionJanitor.test.ts +195 -4
  315. package/src/__tests__/SessionManager.test.ts +69 -0
  316. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +2 -2
  317. package/src/__tests__/TaskGraphAgentRunner.test.ts +272 -16
  318. package/src/__tests__/ViewOperationsHandler.test.ts +2 -5
  319. package/src/__tests__/WorkQueueHostService.test.ts +25 -7
  320. package/src/__tests__/backgroundSweepConfigUnits.test.ts +153 -0
  321. package/src/__tests__/contextRequestOrigin.test.ts +105 -0
  322. package/src/__tests__/conversationBrowsers.test.ts +62 -0
  323. package/src/__tests__/magicLinkExtensionCapabilities.test.ts +413 -0
  324. package/src/__tests__/orphanedConversationDetailReconciler.start.test.ts +82 -0
  325. package/src/__tests__/ownedRealtimeSession.test.ts +14 -0
  326. package/src/__tests__/realtimeRecordingHandler.test.ts +7 -2
  327. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +79 -1
  328. package/src/__tests__/requestOrigin.test.ts +104 -0
  329. package/src/__tests__/securityHardeningPr6034.test.ts +120 -0
  330. package/src/__tests__/sessionDeadline.test.ts +51 -0
  331. package/src/__tests__/sharedCache.test.ts +11 -0
  332. package/src/__tests__/unifiedAuth.test.ts +73 -1
  333. package/src/agentSessions/BridgeHostInstance.ts +20 -5
  334. package/src/agentSessions/HandoffOfferExpiry.ts +18 -0
  335. package/src/agentSessions/ReturningVisitorRecap.ts +1 -0
  336. package/src/agentSessions/SessionJanitor.ts +92 -42
  337. package/src/agentSessions/SessionManager.ts +38 -8
  338. package/src/agentSessions/conversationBrowsers.ts +143 -0
  339. package/src/agentSessions/sessionDeadline.ts +81 -0
  340. package/src/auth/APIKeyScopeAuth.ts +5 -4
  341. package/src/auth/__tests__/loginEmailResolution.test.ts +67 -0
  342. package/src/auth/loginEmailResolution.ts +67 -0
  343. package/src/auth/magicLink/MagicLinkKeys.ts +146 -14
  344. package/src/auth/magicLink/MagicLinkRouter.ts +33 -6
  345. package/src/auth/magicLink/MagicLinkService.ts +250 -10
  346. package/src/auth/magicLink/index.ts +1 -1
  347. package/src/auth/magicLink/redeemLanding.ts +7 -4
  348. package/src/auth/magicLink/types.ts +28 -3
  349. package/src/auth/newUsers.ts +1 -0
  350. package/src/backgroundSweepConfigUnits.ts +213 -0
  351. package/src/config.ts +69 -0
  352. package/src/context.ts +39 -6
  353. package/src/coreRoutes.ts +14 -7
  354. package/src/generated/graphql-schemas/__mj.ts +822 -7
  355. package/src/generic/OrphanedConversationDetailReconciler.ts +32 -1
  356. package/src/generic/ResolverBase.ts +414 -143
  357. package/src/generic/RunViewResolver.ts +96 -49
  358. package/src/index.ts +201 -35
  359. package/src/integration/ConnectionPause.ts +222 -0
  360. package/src/integration/CustomColumnPromoter.ts +123 -53
  361. package/src/integration/EntityMapLifecycle.ts +229 -6
  362. package/src/integration/InactiveRowWarnings.ts +50 -0
  363. package/src/integration/ObjectNamesByID.ts +97 -0
  364. package/src/integration/PostRestartIntrospect.ts +49 -0
  365. package/src/integration/RSUPostRestartProgress.ts +359 -0
  366. package/src/integration/RSUProgressBridge.ts +85 -2
  367. package/src/integration/RunArtifactAuthorization.ts +65 -0
  368. package/src/integration/SchemaRefreshLaunch.ts +12 -2
  369. package/src/integration/ScopedFieldsForBuild.ts +113 -0
  370. package/src/integration/TargetConfigGuard.ts +71 -0
  371. package/src/realtimeSessions/ownedRealtimeSession.ts +2 -6
  372. package/src/realtimeSessions/sessionAccess.ts +30 -0
  373. package/src/realtimeWidget/WidgetSessionService.ts +2 -0
  374. package/src/realtimeWidget/visitorIdentity.ts +1 -0
  375. package/src/realtimeWidget/widgetCore.ts +4 -8
  376. package/src/realtimeWidget/widgetGuestElevation.ts +1 -0
  377. package/src/requestOrigin.ts +64 -0
  378. package/src/resolvers/AgentSessionResolver.ts +6 -2
  379. package/src/resolvers/AutotagPipelineResolver.ts +2 -1
  380. package/src/resolvers/ConversationRunEventsResolver.ts +2 -0
  381. package/src/resolvers/ExecuteRemoteOperationResolver.ts +41 -9
  382. package/src/resolvers/GetDataResolver.ts +35 -13
  383. package/src/resolvers/IntegrationDiscoveryResolver.ts +1522 -203
  384. package/src/resolvers/MCPResolver.ts +15 -2
  385. package/src/resolvers/QuerySystemUserResolver.ts +197 -98
  386. package/src/resolvers/RealtimeBridgeResolver.ts +1 -0
  387. package/src/resolvers/RealtimeClientSessionResolver.ts +40 -7
  388. package/src/resolvers/RemoteBrowserActionResolver.ts +242 -32
  389. package/src/resolvers/RunAIAgentResolver.ts +265 -86
  390. package/src/resolvers/RunAIPromptResolver.ts +18 -18
  391. package/src/resolvers/RunTemplateResolver.ts +3 -1
  392. package/src/resolvers/__tests__/GetDataResolver.tokens.test.ts +52 -0
  393. package/src/resolvers/meetingRecordingRegistration.ts +2 -0
  394. package/src/rest/MediaAccessKeys.ts +6 -2
  395. package/src/rest/OAuthCallbackHandler.ts +13 -5
  396. package/src/rest/RealtimeRecordingHandler.ts +5 -9
  397. package/src/rest/ViewOperationsHandler.ts +2 -1
  398. package/src/rest/setupRESTEndpoints.ts +11 -2
  399. package/src/services/DurableEntityActionTaskSubmitter.ts +10 -2
  400. package/src/services/TaskGraphActionRunner.ts +2 -23
  401. package/src/services/TaskGraphAgentRunner.ts +43 -0
  402. package/src/services/TaskGraphContinuationDeliverer.ts +1 -0
  403. package/src/services/WorkQueueHostService.ts +26 -4
  404. package/src/services/workQueueConfig.ts +5 -5
  405. package/src/sharedCache.ts +1 -1
  406. package/src/types.ts +18 -0
  407. package/src/services/TaskOrchestration-Integration.md +0 -188
@@ -23,6 +23,19 @@ import { PublishStatusUpdate } from '../generic/PushStatusResolver.js';
23
23
  import { GraphQLJSONObject } from 'graphql-type-json';
24
24
  import { configInfo } from '../config.js';
25
25
 
26
+ const UUID_SHAPE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
27
+
28
+ /**
29
+ * Validates that a caller-supplied record id is a UUID before it is interpolated into a SQL
30
+ * filter, and returns it unchanged. A validated UUID cannot carry SQL.
31
+ */
32
+ function sqlUuid(value: string, argName: string): string {
33
+ if (typeof value !== 'string' || !UUID_SHAPE.test(value)) {
34
+ throw new Error(`${argName} must be a UUID`);
35
+ }
36
+ return value;
37
+ }
38
+
26
39
  /**
27
40
  * Input type for syncing MCP tools
28
41
  */
@@ -1289,7 +1302,7 @@ export class MCPResolver extends ResolverBase {
1289
1302
  // First get connection to get server ID
1290
1303
  const connResult = await rv.RunView<{ MCPServerID: string }>({
1291
1304
  EntityName: 'MJ: MCP Server Connections',
1292
- ExtraFilter: `ID='${connectionId}'`,
1305
+ ExtraFilter: `ID='${sqlUuid(connectionId, 'connectionId')}'`,
1293
1306
  Fields: ['MCPServerID'],
1294
1307
  ResultType: 'simple'
1295
1308
  }, contextUser);
@@ -1310,7 +1323,7 @@ export class MCPResolver extends ResolverBase {
1310
1323
  OAuthRequirePKCE: boolean | null;
1311
1324
  }>({
1312
1325
  EntityName: 'MJ: MCP Servers',
1313
- ExtraFilter: `ID='${serverId}'`,
1326
+ ExtraFilter: `ID='${sqlUuid(serverId, 'serverId')}'`,
1314
1327
  Fields: [
1315
1328
  'OAuthIssuerURL',
1316
1329
  'OAuthScopes',
@@ -6,7 +6,7 @@ import { MJQueryResolver, MJQuery_, MJQueryField_, MJQueryParameter_, MJQueryEnt
6
6
  import { GetReadWriteProvider } from '../util.js';
7
7
  import { DeleteOptionsInput } from '../generic/DeleteOptionsInput.js';
8
8
  import { MJQueryEntityServer } from '@memberjunction/core-entities-server';
9
- import { EscapeSQLString } from '@memberjunction/global';
9
+ import { EscapeSQLString, UUIDsEqual } from '@memberjunction/global';
10
10
 
11
11
  /**
12
12
  * Minimal shape of a query row returned by RunView lookups (plain object, not entity instance).
@@ -243,6 +243,14 @@ export class DeleteQueryResultType {
243
243
  Status?: string;
244
244
  }
245
245
 
246
+ /** A query's child rows, read from the database. */
247
+ interface StoredQueryChildren {
248
+ readonly Fields: MJQueryFieldEntity[];
249
+ readonly Parameters: MJQueryParameterEntity[];
250
+ readonly Entities: MJQueryEntityEntity[];
251
+ readonly Permissions: MJQueryPermissionEntity[];
252
+ }
253
+
246
254
  @Resolver()
247
255
  export class MJQueryResolverExtended extends MJQueryResolver {
248
256
  /**
@@ -269,6 +277,7 @@ export class MJQueryResolverExtended extends MJQueryResolver {
269
277
  }
270
278
 
271
279
  // Check for existing query with same name in the same category
280
+ const requestedName = input.Name;
272
281
  const existingQuery = await this.findExistingQuery(provider, input.Name, finalCategoryID, context.userPayload.userRecord);
273
282
 
274
283
  if (existingQuery) {
@@ -285,84 +294,118 @@ export class MJQueryResolverExtended extends MJQueryResolver {
285
294
  }
286
295
  }
287
296
 
288
- // Use MJQueryEntityServer which handles AI processing
289
- const record = await provider.GetEntityObject<MJQueryEntityServer>("MJ: Queries", context.userPayload.userRecord);
290
-
291
- // Destructure out non-database fields, keep only fields to persist
292
- const { Permissions: _permissions, CategoryPath: _categoryPath, ParameterHints: _parameterHints, AutoResolveCollision: _autoResolve, ...fieldsToSet } = {
293
- ...input,
294
- CategoryID: finalCategoryID || input.CategoryID,
295
- Status: input.Status || 'Approved',
296
- QualityRank: input.QualityRank || 0,
297
- UsesTemplate: input.UsesTemplate || false,
298
- AuditQueryRuns: input.AuditQueryRuns || false,
299
- CacheEnabled: input.CacheEnabled || false,
300
- CacheTTLMinutes: input.CacheTTLMinutes || null,
301
- CacheMaxSize: input.CacheMaxSize || null
297
+ return await this.createQueryAsync(provider, input, requestedName, finalCategoryID, context, pubSub);
298
+ }
299
+ catch (err) {
300
+ LogError(err);
301
+ return {
302
+ Success: false,
303
+ ErrorMessage: `MJQueryResolverExtended::CreateQuerySystemUser --- Error creating query: ${err instanceof Error ? err.message : String(err)}`
302
304
  };
305
+ }
306
+ }
303
307
 
304
- // Field-level security: refuse a key the caller may not read, or that names no field
305
- AssertClientInputWritableForUser(record.EntityInfo, fieldsToSet, context.userPayload.userRecord);
306
- record.SetMany(fieldsToSet, true);
308
+ /** The most times a create with `AutoResolveCollision` picks a new name after losing a same-name race. */
309
+ private static readonly maxCollisionRetries = 25;
307
310
 
308
- // Pass caller-provided parameter sample values to override LLM guesses
309
- if (input.ParameterHints && input.ParameterHints.length > 0) {
310
- record.ParameterHints = new Map(input.ParameterHints.map(h => [h.Name, h.Value]));
311
+ /**
312
+ * Creates the query, handling a same-name race (#6007): when the insert fails and a query with
313
+ * the name now exists (another request created it in between), `AutoResolveCollision` picks a
314
+ * new unique name and tries again, a bounded number of times, and never returns the other
315
+ * request's query. Without it, the query that now exists is returned (create-or-get). Any other
316
+ * failure, including a save-time refusal of the SQL, returns the save's message.
317
+ *
318
+ * @param requestedName - The name the caller asked for; each retry's unique name is built from it.
319
+ */
320
+ private async createQueryAsync(
321
+ provider: DatabaseProviderBase,
322
+ input: CreateQuerySystemUserInput,
323
+ requestedName: string,
324
+ categoryID: string | null | undefined,
325
+ context: AppContext,
326
+ pubSub: PubSubEngine
327
+ ): Promise<QueryMutationResultType> {
328
+ const user = context.userPayload.userRecord;
329
+ for (let attempt = 0; ; attempt++) {
330
+ const record = await this.newQueryRecordAsync(provider, input, categoryID, context, pubSub);
331
+ if (await record.Save()) {
332
+ return this.completeCreateAsync(provider, input, record, user);
311
333
  }
334
+ const existing = await this.findExistingQuery(provider, input.Name, categoryID ?? null, user);
335
+ if (!existing) {
336
+ return { Success: false, ErrorMessage: `Failed to create query: ${record.LatestResult?.CompleteMessage || record.LatestResult?.Message || 'the save reported no reason'}` };
337
+ }
338
+ if (!input.AutoResolveCollision) {
339
+ return this.existingQueryResultAsync(provider, existing, input.Name, user);
340
+ }
341
+ if (attempt >= MJQueryResolverExtended.maxCollisionRetries) {
342
+ return { Success: false, ErrorMessage: `Failed to create query '${input.Name}': another request took each unique name tried, ${attempt + 1} times.` };
343
+ }
344
+ const lost = input.Name;
345
+ input.Name = await this.findUniqueName(provider, requestedName, categoryID ?? null, user);
346
+ LogStatus(`[CreateQuery] Lost a name race for "${lost}"; retrying as "${input.Name}"`);
347
+ }
348
+ }
312
349
 
313
- this.ListenForEntityMessages(record, pubSub, context.userPayload.userRecord);
314
-
315
- // Attempt to save the query
316
- const saveResult = await record.Save();
317
-
318
- if (saveResult) {
319
- // Save succeeded - fire the AfterCreate event and return all the data
320
- await this.AfterCreate(provider, input); // fire event
321
- const queryID = record.ID;
322
-
323
- if (input.Permissions && input.Permissions.length > 0) {
324
- await this.createPermissions(provider, input.Permissions, queryID, context.userPayload.userRecord);
325
-
326
- }
327
-
350
+ /** A new, unsaved query record holding the input's values. */
351
+ private async newQueryRecordAsync(
352
+ provider: DatabaseProviderBase,
353
+ input: CreateQuerySystemUserInput,
354
+ categoryID: string | null | undefined,
355
+ context: AppContext,
356
+ pubSub: PubSubEngine
357
+ ): Promise<MJQueryEntityServer> {
358
+ const record = await provider.GetEntityObject<MJQueryEntityServer>("MJ: Queries", context.userPayload.userRecord);
359
+ // Destructure out non-database fields, keep only fields to persist
360
+ const { Permissions: _permissions, CategoryPath: _categoryPath, ParameterHints: _parameterHints, AutoResolveCollision: _autoResolve, ...fieldsToSet } = {
361
+ ...input,
362
+ CategoryID: categoryID || input.CategoryID,
363
+ Status: input.Status || 'Approved',
364
+ QualityRank: input.QualityRank || 0,
365
+ UsesTemplate: input.UsesTemplate || false,
366
+ AuditQueryRuns: input.AuditQueryRuns || false,
367
+ CacheEnabled: input.CacheEnabled || false,
368
+ CacheTTLMinutes: input.CacheTTLMinutes || null,
369
+ CacheMaxSize: input.CacheMaxSize || null
370
+ };
371
+ // Field-level security: refuse a key the caller may not read, or that names no field
372
+ AssertClientInputWritableForUser(record.EntityInfo, fieldsToSet, context.userPayload.userRecord);
373
+ record.SetMany(fieldsToSet, true);
374
+ // Pass caller-provided parameter sample values (used for new parameters and the described renders)
375
+ if (input.ParameterHints && input.ParameterHints.length > 0) {
376
+ record.ParameterHints = new Map(input.ParameterHints.map(h => [h.Name, h.Value]));
377
+ }
378
+ this.ListenForEntityMessages(record, pubSub, context.userPayload);
379
+ return record;
380
+ }
328
381
 
329
- return await this.buildSuccessResult(record, context.userPayload.userRecord, provider);
382
+ /** After a successful create: the AfterCreate event, the permissions, and the result. */
383
+ private async completeCreateAsync(provider: DatabaseProviderBase, input: CreateQuerySystemUserInput, record: MJQueryEntityServer, user: UserInfo): Promise<QueryMutationResultType> {
384
+ await this.AfterCreate(provider, input); // fire event
385
+ if (input.Permissions && input.Permissions.length > 0) {
386
+ try {
387
+ await this.createPermissions(provider, input.Permissions, record.ID, user);
388
+ } catch (e) {
389
+ return { Success: false, ErrorMessage: `Query '${record.Name}' (ID ${record.ID}) was created, but its permissions were not: ${e instanceof Error ? e.message : String(e)}` };
330
390
  }
331
- else {
332
- // Save failed - check if another request created the same query (race condition)
333
- // Always recheck regardless of error type to handle all duplicate scenarios
334
- const existingQuery = await this.findExistingQuery(provider, input.Name, finalCategoryID, context.userPayload.userRecord);
335
-
336
- if (existingQuery) {
337
- // Found the query that was created by another request — load it as a full entity
338
- // so that related metadata (Fields, Parameters, Entities, Permissions) is populated
339
- LogStatus(`[CreateQuery] Unique constraint detected for query '${input.Name}'. Using existing query (ID: ${existingQuery.ID}) created by concurrent request.`);
340
- const existingEntity = await provider.GetEntityObject<MJQueryEntityServer>('MJ: Queries', context.userPayload.userRecord);
341
- if (await existingEntity.Load(existingQuery.ID)) {
342
- return await this.buildSuccessResult(existingEntity, context.userPayload.userRecord, provider);
343
- }
344
- // Entity load failed after confirming the row exists — extremely rare
345
- return {
346
- Success: false,
347
- ErrorMessage: `Found query '${input.Name}' created by concurrent request (ID: ${existingQuery.ID}) but failed to load it as entity`
348
- };
349
- }
391
+ }
392
+ return this.buildSuccessResult(record, user, provider);
393
+ }
350
394
 
351
- // Genuine failure - couldn't find an existing query with the same name
352
- const errorMessage = record.LatestResult?.Message || '';
353
- return {
354
- Success: false,
355
- ErrorMessage: `Failed to create query: ${errorMessage || 'Unknown error'}`
356
- };
357
- }
358
- }
359
- catch (err) {
360
- LogError(err);
361
- return {
362
- Success: false,
363
- ErrorMessage: `MJQueryResolverExtended::CreateQuerySystemUser --- Error creating query: ${err instanceof Error ? err.message : String(err)}`
364
- };
395
+ /** The create-or-get result: the query another request created with this name. */
396
+ private async existingQueryResultAsync(provider: DatabaseProviderBase, existing: QueryViewRow, name: string, user: UserInfo): Promise<QueryMutationResultType> {
397
+ LogStatus(`[CreateQuery] Unique constraint detected for query '${name}'. Using existing query (ID: ${existing.ID}) created by concurrent request.`);
398
+ const existingEntity = await provider.GetEntityObject<MJQueryEntityServer>('MJ: Queries', user);
399
+ if (await existingEntity.Load(existing.ID)) {
400
+ // The other request may have run on another server, or its rows may not have reached this
401
+ // process's query cache yet, so the child rows are read from the database.
402
+ const children = await this.readStoredChildrenAsync(provider, existing.ID, user);
403
+ return this.buildSuccessResult(existingEntity, user, provider, children);
365
404
  }
405
+ return {
406
+ Success: false,
407
+ ErrorMessage: `Found query '${name}' created by concurrent request (ID: ${existing.ID}) but failed to load it as entity`
408
+ };
366
409
  }
367
410
 
368
411
  /**
@@ -372,17 +415,39 @@ export class MJQueryResolverExtended extends MJQueryResolver {
372
415
  *
373
416
  * @param contextUser The caller the result is for. Field security projects the record for them.
374
417
  */
375
- private async buildSuccessResult(entity: MJQueryEntityServer, contextUser: UserInfo, provider: DatabaseProviderBase): Promise<QueryMutationResultType> {
418
+ private async buildSuccessResult(entity: MJQueryEntityServer, contextUser: UserInfo, provider: DatabaseProviderBase, children?: StoredQueryChildren): Promise<QueryMutationResultType> {
376
419
  return {
377
420
  Success: true,
378
421
  // Mapped for the caller, so the field-security projection applies: the record was just
379
422
  // saved, and a save re-reads the whole row.
380
423
  // fls-guard: projected for contextUser by MapFieldNamesToCodeNames
381
424
  Query: await this.MapFieldNamesToCodeNames('MJ: Queries', entity.GetAll(), contextUser, provider) as MJQuery_,
382
- Fields: this.mapFields(entity.QueryFields),
383
- Parameters: this.mapParameters(entity.QueryParameters),
384
- Entities: this.mapEntities(entity.QueryEntities),
385
- Permissions: this.mapPermissions(entity.QueryPermissions),
425
+ Fields: this.mapFields(children?.Fields ?? entity.QueryFields),
426
+ Parameters: this.mapParameters(children?.Parameters ?? entity.QueryParameters),
427
+ Entities: this.mapEntities(children?.Entities ?? entity.QueryEntities),
428
+ Permissions: this.mapPermissions(children?.Permissions ?? entity.QueryPermissions),
429
+ };
430
+ }
431
+
432
+ /**
433
+ * A query's child rows read from the database with every cache bypassed. A collection whose
434
+ * read fails comes back empty, as the cache would have given it.
435
+ */
436
+ private async readStoredChildrenAsync(provider: DatabaseProviderBase, queryID: string, user: UserInfo): Promise<StoredQueryChildren> {
437
+ const filter = `QueryID='${EscapeSQLString(queryID)}'`;
438
+ const view = (entityName: string, orderBy?: string) => ({ EntityName: entityName, ExtraFilter: filter, OrderBy: orderBy, ResultType: 'entity_object' as const, BypassCache: true, IgnoreMaxRows: true });
439
+ const [fields, parameters, entities, permissions] = await provider.RunViews([
440
+ view('MJ: Query Fields', 'Sequence'),
441
+ view('MJ: Query Parameters', 'Name'),
442
+ view('MJ: Query Entities'),
443
+ view('MJ: Query Permissions'),
444
+ ], user);
445
+ const rows = <T>(result: { Success: boolean; Results: T[] } | undefined): T[] => (result?.Success ? result.Results : []);
446
+ return {
447
+ Fields: rows<MJQueryFieldEntity>(fields),
448
+ Parameters: rows<MJQueryParameterEntity>(parameters),
449
+ Entities: rows<MJQueryEntityEntity>(entities),
450
+ Permissions: rows<MJQueryPermissionEntity>(permissions),
386
451
  };
387
452
  }
388
453
 
@@ -398,6 +463,10 @@ export class MJQueryResolverExtended extends MJQueryResolver {
398
463
  SourceEntityID: f.SourceEntityID,
399
464
  SourceEntity: f.SourceEntity,
400
465
  SourceFieldName: f.SourceFieldName,
466
+ SourceEntityFieldID: f.SourceEntityFieldID,
467
+ SourceEntityField: f.SourceEntityField,
468
+ SourceObjectName: f.SourceObjectName,
469
+ IsNullable: f.IsNullable,
401
470
  IsComputed: f.IsComputed,
402
471
  ComputationDescription: f.ComputationDescription,
403
472
  IsSummary: f.IsSummary,
@@ -421,6 +490,7 @@ export class MJQueryResolverExtended extends MJQueryResolver {
421
490
  DefaultValue: p.DefaultValue,
422
491
  SampleValue: p.SampleValue,
423
492
  ValidationFilters: p.ValidationFilters,
493
+ IsServerSet: p.IsServerSet,
424
494
  _mj__CreatedAt: p.__mj_CreatedAt,
425
495
  _mj__UpdatedAt: p.__mj_UpdatedAt,
426
496
  DetectionMethod: p.DetectionMethod,
@@ -435,6 +505,8 @@ export class MJQueryResolverExtended extends MJQueryResolver {
435
505
  QueryID: e.QueryID,
436
506
  EntityID: e.EntityID,
437
507
  Entity: e.Entity,
508
+ AlternateBaseViewID: e.AlternateBaseViewID,
509
+ AlternateBaseView: e.AlternateBaseView,
438
510
  _mj__CreatedAt: e.__mj_CreatedAt,
439
511
  _mj__UpdatedAt: e.__mj_UpdatedAt,
440
512
  DetectionMethod: e.DetectionMethod,
@@ -455,17 +527,43 @@ export class MJQueryResolverExtended extends MJQueryResolver {
455
527
  }) as MJQueryPermission_);
456
528
  }
457
529
 
530
+ /**
531
+ * Saves one `MJ: Query Permissions` row per input role for the query.
532
+ *
533
+ * @throws Error naming the role and the save's own message when a row is not saved
534
+ */
458
535
  protected async createPermissions(p: DatabaseProviderBase, permissions: QueryPermissionInputType[], queryID: string, contextUser: UserInfo): Promise<void> {
459
536
  for (const perm of permissions) {
460
537
  const permissionEntity = await p.GetEntityObject<MJQueryPermissionEntity>('MJ: Query Permissions', contextUser);
461
538
  if (permissionEntity) {
462
539
  permissionEntity.QueryID = queryID;
463
540
  permissionEntity.RoleID = perm.RoleID;
464
- await permissionEntity.Save();
541
+ if (!await permissionEntity.Save()) {
542
+ throw new Error(MJQueryResolverExtended.permissionWriteError('save', perm.RoleID, permissionEntity));
543
+ }
465
544
  }
466
545
  }
467
546
  }
468
547
 
548
+ /**
549
+ * Deletes every `MJ: Query Permissions` row of the query.
550
+ *
551
+ * @throws Error naming the role and the delete's own message when a row is not deleted
552
+ */
553
+ protected async deletePermissions(queryID: string): Promise<void> {
554
+ for (const perm of QueryEngine.Instance.GetQueryPermissions(queryID)) {
555
+ if (!await perm.Delete()) {
556
+ throw new Error(MJQueryResolverExtended.permissionWriteError('delete', perm.RoleID, perm));
557
+ }
558
+ }
559
+ }
560
+
561
+ /** The message for a permission row that was not saved or deleted, with the entity's own reason. */
562
+ private static permissionWriteError(action: 'save' | 'delete', roleID: string, entity: MJQueryPermissionEntity): string {
563
+ const reason = entity.LatestResult?.CompleteMessage || entity.LatestResult?.Message || `the ${action} reported no reason`;
564
+ return `Failed to ${action} the query permission for role ${roleID}: ${reason}`;
565
+ }
566
+
469
567
  /**
470
568
  * Updates an existing query with the provided attributes. Requires `query:update` scope for API key users;
471
569
  * entity-level update permission on MJ: Queries for all users.
@@ -498,16 +596,14 @@ export class MJQueryResolverExtended extends MJQueryResolver {
498
596
  finalCategoryID = await this.findOrCreateCategoryPath(input.CategoryPath, provider, context.userPayload.userRecord);
499
597
  }
500
598
 
501
- // now make sure there is NO existing query by the same name in the specified category
502
- const existingQueryResult = await provider.RunView({
503
- EntityName: 'MJ: Queries',
504
- ExtraFilter: `Name='${EscapeSQLString(input.Name)}' AND CategoryID='${EscapeSQLString(finalCategoryID)}'`
505
- }, context.userPayload.userRecord);
506
- if (existingQueryResult.Success && existingQueryResult.Results?.length > 0) {
507
- // we have a match! Let's return an error
599
+ // A rename, or a move to another category, must not collide with another query's name
600
+ const targetName = input.Name ?? queryEntity.Name;
601
+ const targetCategoryID = finalCategoryID !== undefined ? finalCategoryID : queryEntity.CategoryID;
602
+ const sameName = await this.findExistingQuery(provider, targetName, targetCategoryID ?? null, context.userPayload.userRecord);
603
+ if (sameName && !UUIDsEqual(sameName.ID, queryEntity.ID)) {
508
604
  return {
509
605
  Success: false,
510
- ErrorMessage: `Query with name '${input.Name}' already exists in the specified ${input.CategoryID ? 'category' : 'categoryPath'}`
606
+ ErrorMessage: `Query with name '${targetName}' already exists in the specified ${input.CategoryID ? 'category' : 'categoryPath'}`
511
607
  };
512
608
  }
513
609
 
@@ -545,7 +641,7 @@ export class MJQueryResolverExtended extends MJQueryResolver {
545
641
  if (!saveResult) {
546
642
  return {
547
643
  Success: false,
548
- ErrorMessage: `Failed to update query: ${queryEntity.LatestResult?.Message || 'Unknown error'}`
644
+ ErrorMessage: `Failed to update query: ${queryEntity.LatestResult?.CompleteMessage || queryEntity.LatestResult?.Message || 'the save reported no reason'}`
549
645
  };
550
646
  }
551
647
 
@@ -553,14 +649,13 @@ export class MJQueryResolverExtended extends MJQueryResolver {
553
649
 
554
650
  // Handle permissions update if provided
555
651
  if (input.Permissions !== undefined) {
556
- // Delete existing permissions (read from QueryEngine cache)
557
- const existingPermissions = QueryEngine.Instance.GetQueryPermissions(queryID);
558
- for (const perm of existingPermissions) {
559
- await perm.Delete();
652
+ // Replace the existing permissions (read from the QueryEngine cache); a failed write stops the update with its reason
653
+ try {
654
+ await this.deletePermissions(queryID);
655
+ await this.createPermissions(provider, input.Permissions, queryID, context.userPayload.userRecord);
656
+ } catch (e) {
657
+ return { Success: false, ErrorMessage: `Failed to update query permissions: ${e instanceof Error ? e.message : String(e)}` };
560
658
  }
561
-
562
- // Create new permissions
563
- await this.createPermissions(provider, input.Permissions, queryID, context.userPayload.userRecord);
564
659
  }
565
660
 
566
661
  return await this.buildSuccessResult(queryEntity, context.userPayload.userRecord, provider);
@@ -740,12 +835,13 @@ export class MJQueryResolverExtended extends MJQueryResolver {
740
835
  ): Promise<QueryViewRow | null> {
741
836
  try {
742
837
  // Query database directly to avoid cache staleness issues
743
- const categoryFilter = categoryID ? `CategoryID='${categoryID}'` : 'CategoryID IS NULL';
744
- const nameFilter = `LOWER(Name) = LOWER('${queryName.replace(/'/g, "''")}')`;
838
+ const categoryFilter = categoryID ? `CategoryID='${EscapeSQLString(categoryID)}'` : 'CategoryID IS NULL';
839
+ const nameFilter = `LOWER(Name) = LOWER('${EscapeSQLString(queryName)}')`;
745
840
 
746
841
  const result = await provider.RunView<QueryViewRow>({
747
842
  EntityName: 'MJ: Queries',
748
- ExtraFilter: `${nameFilter} AND ${categoryFilter}`
843
+ ExtraFilter: `${nameFilter} AND ${categoryFilter}`,
844
+ BypassCache: true
749
845
  }, contextUser);
750
846
 
751
847
  if (result.Success && result.Results && result.Results.length > 0) {
@@ -797,13 +893,16 @@ export class MJQueryResolverExtended extends MJQueryResolver {
797
893
  private async findCategoryByNameAndParent(provider: DatabaseProviderBase, categoryName: string, parentID: string | null, contextUser: UserInfo): Promise<MJQueryCategoryEntity | null> {
798
894
  try {
799
895
  // Query database directly to avoid cache staleness issues
800
- const parentFilter = parentID ? `ParentID='${parentID}'` : 'ParentID IS NULL';
801
- const nameFilter = `LOWER(Name) = LOWER('${categoryName.replace(/'/g, "''")}')`; // Escape single quotes
896
+ const parentFilter = parentID ? `ParentID='${EscapeSQLString(parentID)}'` : 'ParentID IS NULL';
897
+ const nameFilter = `LOWER(Name) = LOWER('${EscapeSQLString(categoryName)}')`;
802
898
 
899
+ // Uncached: after losing a race to create the category, the re-check must see the row the
900
+ // other request just wrote.
803
901
  const result = await provider.RunView<MJQueryCategoryEntity>({
804
902
  EntityName: 'MJ: Query Categories',
805
903
  ExtraFilter: `${nameFilter} AND ${parentFilter}`,
806
- ResultType: 'entity_object'
904
+ ResultType: 'entity_object',
905
+ BypassCache: true
807
906
  }, contextUser);
808
907
 
809
908
  if (result.Success && result.Results && result.Results.length > 0) {
@@ -701,6 +701,7 @@ export class RealtimeBridgeResolver extends ResolverBase {
701
701
  Fields: ['ExternalID'],
702
702
  OrderBy: '__mj_CreatedAt DESC',
703
703
  MaxRows: 1,
704
+ SkipTotalRowCount: true,
704
705
  ResultType: 'simple',
705
706
  },
706
707
  user,
@@ -34,7 +34,7 @@
34
34
  import { Resolver, Mutation, Arg, Ctx, Int, Float, ObjectType, Field, PubSub, PubSubEngine } from 'type-graphql';
35
35
  import { AppContext, UserPayload } from '../types.js';
36
36
  import { AuthorizationEvaluator, UserInfo, IMetadataProvider, DatabaseProviderBase, LogError, LogErrorEx, LogStatus, RunView, BaseEntity } from '@memberjunction/core';
37
- import { UUIDsEqual, IsValidUUID } from '@memberjunction/global';
37
+ import { EscapeSQLString, UUIDsEqual, IsValidUUID } from '@memberjunction/global';
38
38
  import {
39
39
  MJAIAgentEntity,
40
40
  MJAIAgentSessionEntity,
@@ -106,6 +106,14 @@ const SIGNIFICANT_PROGRESS_STEPS = ['prompt_execution', 'action_execution', 'sub
106
106
  */
107
107
  const MAX_USAGE_DETAILS_JSON_LENGTH = 4096;
108
108
 
109
+ /**
110
+ * Sessions already logged as having no co-agent prompt run. Usage, transcript and tool-turn relays all read
111
+ * the id, many times per call, so the gap is logged once per session instead of once per relay. Bounded:
112
+ * past {@link MAX_LOGGED_MISSING_PROMPT_RUN_SESSIONS} entries the oldest is dropped (a Set keeps insertion order).
113
+ */
114
+ const sessionsLoggedWithoutPromptRun = new Set<string>();
115
+ const MAX_LOGGED_MISSING_PROMPT_RUN_SESSIONS = 5000;
116
+
109
117
  /** Entity name — centralised so the `MJ:`-prefix convention is applied in exactly one place. */
110
118
  const CO_AGENT_ENTITY = 'MJ: AI Agent Co Agents';
111
119
  const CONVERSATION_DETAIL_ENTITY = 'MJ: Conversation Details';
@@ -1330,10 +1338,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
1330
1338
  return null;
1331
1339
  }
1332
1340
  if (!config.promptRunID) {
1333
- LogStatus(
1334
- `RelayRealtimeUsage: session ${session.ID} has no co-agent promptRunID (observability run ` +
1335
- 'creation was skipped or failed) — usage delta dropped.',
1336
- );
1341
+ this.logMissingPromptRunOnce(session.ID);
1337
1342
  return null;
1338
1343
  }
1339
1344
  const verified = await this.runIDVerifier.Verify(
@@ -1349,6 +1354,29 @@ export class RealtimeClientSessionResolver extends ResolverBase {
1349
1354
  return verified.PromptRunID;
1350
1355
  }
1351
1356
 
1357
+ /**
1358
+ * Logs, once per session, that the session has no co-agent prompt run, so its usage, cost, transcript and
1359
+ * tool turns are not recorded on one. The reason (no active co-agent prompt, or a failed run save) was
1360
+ * logged when the session was prepared.
1361
+ */
1362
+ private logMissingPromptRunOnce(sessionID: string): void {
1363
+ if (sessionsLoggedWithoutPromptRun.has(sessionID)) {
1364
+ return;
1365
+ }
1366
+ sessionsLoggedWithoutPromptRun.add(sessionID);
1367
+ if (sessionsLoggedWithoutPromptRun.size > MAX_LOGGED_MISSING_PROMPT_RUN_SESSIONS) {
1368
+ const oldest = sessionsLoggedWithoutPromptRun.values().next().value;
1369
+ if (oldest !== undefined) {
1370
+ sessionsLoggedWithoutPromptRun.delete(oldest);
1371
+ }
1372
+ }
1373
+ LogError(
1374
+ `RealtimeClientSessionResolver: session ${sessionID} has no co-agent promptRunID, so its usage, cost, ` +
1375
+ 'transcript and tool turns are not recorded on a prompt run (the reason was logged when the session ' +
1376
+ 'was prepared). Logged once per session.',
1377
+ );
1378
+ }
1379
+
1352
1380
  /**
1353
1381
  * Persist an interactive channel's state of record (e.g. the live whiteboard's serialized
1354
1382
  * scene) onto the session's `MJ: AI Agent Session Channels` row.
@@ -2032,9 +2060,10 @@ export class RealtimeClientSessionResolver extends ResolverBase {
2032
2060
  const existing = await rv.RunView<MJAIAgentSessionChannelEntity>(
2033
2061
  {
2034
2062
  EntityName: SESSION_CHANNEL_ENTITY,
2035
- ExtraFilter: `AgentSessionID='${agentSessionID}' AND ChannelID='${channelID}'`,
2063
+ ExtraFilter: `AgentSessionID='${EscapeSQLString(agentSessionID)}' AND ChannelID='${EscapeSQLString(channelID)}'`,
2036
2064
  ResultType: 'entity_object',
2037
2065
  MaxRows: 1,
2066
+ SkipTotalRowCount: true,
2038
2067
  },
2039
2068
  contextUser,
2040
2069
  );
@@ -2423,6 +2452,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
2423
2452
  Fields: ['ID', 'IsEnabled'],
2424
2453
  ResultType: 'simple',
2425
2454
  MaxRows: 1,
2455
+ SkipTotalRowCount: true,
2426
2456
  },
2427
2457
  contextUser,
2428
2458
  );
@@ -2585,6 +2615,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
2585
2615
  OrderBy: '__mj_CreatedAt DESC',
2586
2616
  ResultType: 'simple',
2587
2617
  MaxRows: 1,
2618
+ SkipTotalRowCount: true,
2588
2619
  },
2589
2620
  contextUser,
2590
2621
  );
@@ -2673,9 +2704,10 @@ export class RealtimeClientSessionResolver extends ResolverBase {
2673
2704
  const existing = await rv.RunView<MJAIAgentSessionChannelEntity>(
2674
2705
  {
2675
2706
  EntityName: SESSION_CHANNEL_ENTITY,
2676
- ExtraFilter: `AgentSessionID='${agentSessionID}' AND ChannelID='${channelID}'`,
2707
+ ExtraFilter: `AgentSessionID='${EscapeSQLString(agentSessionID)}' AND ChannelID='${EscapeSQLString(channelID)}'`,
2677
2708
  ResultType: 'entity_object',
2678
2709
  MaxRows: 1,
2710
+ SkipTotalRowCount: true,
2679
2711
  },
2680
2712
  contextUser,
2681
2713
  );
@@ -3014,6 +3046,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
3014
3046
  ExtraFilter: `AgentSessionID='${session.ID}' AND Role='${mappedRole}'`,
3015
3047
  OrderBy: '__mj_CreatedAt DESC',
3016
3048
  MaxRows: 1,
3049
+ SkipTotalRowCount: true,
3017
3050
  ResultType: 'entity_object',
3018
3051
  },
3019
3052
  writeUser,