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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (346) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/agentSessions/BridgeHostInstance.d.ts +25 -0
  3. package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -0
  4. package/dist/agentSessions/BridgeHostInstance.js +30 -0
  5. package/dist/agentSessions/BridgeHostInstance.js.map +1 -0
  6. package/dist/agentSessions/HostInstance.d.ts +75 -11
  7. package/dist/agentSessions/HostInstance.d.ts.map +1 -1
  8. package/dist/agentSessions/HostInstance.js +99 -28
  9. package/dist/agentSessions/HostInstance.js.map +1 -1
  10. package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
  11. package/dist/agentSessions/ReturningVisitorRecap.js +7 -3
  12. package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
  13. package/dist/agentSessions/SessionJanitor.d.ts +173 -25
  14. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  15. package/dist/agentSessions/SessionJanitor.js +452 -54
  16. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  17. package/dist/agentSessions/SessionManager.d.ts +43 -2
  18. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  19. package/dist/agentSessions/SessionManager.js +99 -8
  20. package/dist/agentSessions/SessionManager.js.map +1 -1
  21. package/dist/agentSessions/SessionRunIDVerifier.d.ts +63 -0
  22. package/dist/agentSessions/SessionRunIDVerifier.d.ts.map +1 -0
  23. package/dist/agentSessions/SessionRunIDVerifier.js +129 -0
  24. package/dist/agentSessions/SessionRunIDVerifier.js.map +1 -0
  25. package/dist/agentSessions/index.d.ts +2 -0
  26. package/dist/agentSessions/index.d.ts.map +1 -1
  27. package/dist/agentSessions/index.js +10 -3
  28. package/dist/agentSessions/index.js.map +1 -1
  29. package/dist/auth/actionAuthorization.d.ts +40 -0
  30. package/dist/auth/actionAuthorization.d.ts.map +1 -0
  31. package/dist/auth/actionAuthorization.js +94 -0
  32. package/dist/auth/actionAuthorization.js.map +1 -0
  33. package/dist/auth/index.d.ts.map +1 -1
  34. package/dist/auth/index.js +3 -10
  35. package/dist/auth/index.js.map +1 -1
  36. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  37. package/dist/auth/magicLink/MagicLinkService.js +6 -3
  38. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  39. package/dist/auth/newUsers.d.ts +10 -0
  40. package/dist/auth/newUsers.d.ts.map +1 -1
  41. package/dist/auth/newUsers.js +80 -11
  42. package/dist/auth/newUsers.js.map +1 -1
  43. package/dist/config.d.ts +2378 -0
  44. package/dist/config.d.ts.map +1 -1
  45. package/dist/config.js +123 -0
  46. package/dist/config.js.map +1 -1
  47. package/dist/context.d.ts +8 -0
  48. package/dist/context.d.ts.map +1 -1
  49. package/dist/context.js +14 -11
  50. package/dist/context.js.map +1 -1
  51. package/dist/generated/graphql-schemas/__mj.d.ts +135 -0
  52. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  53. package/dist/generated/graphql-schemas/__mj.js +706 -0
  54. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  55. package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
  56. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  57. package/dist/generic/ResolverBase.d.ts +20 -4
  58. package/dist/generic/ResolverBase.d.ts.map +1 -1
  59. package/dist/generic/ResolverBase.js +39 -10
  60. package/dist/generic/ResolverBase.js.map +1 -1
  61. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  62. package/dist/generic/RunViewResolver.js +4 -0
  63. package/dist/generic/RunViewResolver.js.map +1 -1
  64. package/dist/index.d.ts +6 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +120 -131
  67. package/dist/index.js.map +1 -1
  68. package/dist/isolatedProvider.d.ts +20 -0
  69. package/dist/isolatedProvider.d.ts.map +1 -0
  70. package/dist/isolatedProvider.js +22 -0
  71. package/dist/isolatedProvider.js.map +1 -0
  72. package/dist/realtimeProxy/RealtimeProxyServer.d.ts +44 -4
  73. package/dist/realtimeProxy/RealtimeProxyServer.d.ts.map +1 -1
  74. package/dist/realtimeProxy/RealtimeProxyServer.js +121 -16
  75. package/dist/realtimeProxy/RealtimeProxyServer.js.map +1 -1
  76. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts +171 -0
  77. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts.map +1 -0
  78. package/dist/realtimeProxy/RealtimeRelayTunnel.js +532 -0
  79. package/dist/realtimeProxy/RealtimeRelayTunnel.js.map +1 -0
  80. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts +148 -0
  81. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts.map +1 -0
  82. package/dist/realtimeSessions/RealtimeSessionEventService.js +191 -0
  83. package/dist/realtimeSessions/RealtimeSessionEventService.js.map +1 -0
  84. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts +66 -0
  85. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -0
  86. package/dist/realtimeSessions/RealtimeSessionVerificationService.js +138 -0
  87. package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -0
  88. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts +38 -0
  89. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -0
  90. package/dist/realtimeSessions/RealtimeVerifyRouter.js +98 -0
  91. package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -0
  92. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts +61 -0
  93. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -0
  94. package/dist/realtimeSessions/SessionVerificationPortsBase.js +149 -0
  95. package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -0
  96. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts +57 -0
  97. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts.map +1 -0
  98. package/dist/realtimeSessions/SlidingWindowRateLimiter.js +69 -0
  99. package/dist/realtimeSessions/SlidingWindowRateLimiter.js.map +1 -0
  100. package/dist/realtimeSessions/index.d.ts +17 -0
  101. package/dist/realtimeSessions/index.d.ts.map +1 -0
  102. package/dist/realtimeSessions/index.js +17 -0
  103. package/dist/realtimeSessions/index.js.map +1 -0
  104. package/dist/realtimeSessions/sessionAccess.d.ts +86 -0
  105. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
  106. package/dist/realtimeSessions/sessionAccess.js +90 -0
  107. package/dist/realtimeSessions/sessionAccess.js.map +1 -0
  108. package/dist/realtimeSessions/sessionConfigStamps.d.ts +70 -0
  109. package/dist/realtimeSessions/sessionConfigStamps.d.ts.map +1 -0
  110. package/dist/realtimeSessions/sessionConfigStamps.js +93 -0
  111. package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -0
  112. package/dist/realtimeSessions/subscriptionAuthorization.d.ts +65 -0
  113. package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -0
  114. package/dist/realtimeSessions/subscriptionAuthorization.js +71 -0
  115. package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -0
  116. package/dist/realtimeSessions/verificationCore.d.ts +243 -0
  117. package/dist/realtimeSessions/verificationCore.d.ts.map +1 -0
  118. package/dist/realtimeSessions/verificationCore.js +356 -0
  119. package/dist/realtimeSessions/verificationCore.js.map +1 -0
  120. package/dist/realtimeSessions/verificationPages.d.ts +66 -0
  121. package/dist/realtimeSessions/verificationPages.d.ts.map +1 -0
  122. package/dist/realtimeSessions/verificationPages.js +110 -0
  123. package/dist/realtimeSessions/verificationPages.js.map +1 -0
  124. package/dist/realtimeSessions/verificationPolicy.d.ts +147 -0
  125. package/dist/realtimeSessions/verificationPolicy.d.ts.map +1 -0
  126. package/dist/realtimeSessions/verificationPolicy.js +249 -0
  127. package/dist/realtimeSessions/verificationPolicy.js.map +1 -0
  128. package/dist/realtimeSessions/verificationWorkflow.d.ts +263 -0
  129. package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -0
  130. package/dist/realtimeSessions/verificationWorkflow.js +435 -0
  131. package/dist/realtimeSessions/verificationWorkflow.js.map +1 -0
  132. package/dist/realtimeWidget/widgetGuestElevation.d.ts +20 -2
  133. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  134. package/dist/realtimeWidget/widgetGuestElevation.js +31 -2
  135. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  136. package/dist/resolvers/ActionResolver.d.ts +31 -5
  137. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  138. package/dist/resolvers/ActionResolver.js +75 -14
  139. package/dist/resolvers/ActionResolver.js.map +1 -1
  140. package/dist/resolvers/AdhocQueryResolver.d.ts +14 -1
  141. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  142. package/dist/resolvers/AdhocQueryResolver.js +21 -5
  143. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  144. package/dist/resolvers/CacheStatsResolver.d.ts +27 -0
  145. package/dist/resolvers/CacheStatsResolver.d.ts.map +1 -1
  146. package/dist/resolvers/CacheStatsResolver.js +113 -2
  147. package/dist/resolvers/CacheStatsResolver.js.map +1 -1
  148. package/dist/resolvers/ClientToolRequestResolver.d.ts +4 -2
  149. package/dist/resolvers/ClientToolRequestResolver.d.ts.map +1 -1
  150. package/dist/resolvers/ClientToolRequestResolver.js +28 -10
  151. package/dist/resolvers/ClientToolRequestResolver.js.map +1 -1
  152. package/dist/resolvers/FetchEntityVectorsResolver.d.ts +12 -1
  153. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  154. package/dist/resolvers/FetchEntityVectorsResolver.js +83 -2
  155. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  156. package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -1
  157. package/dist/resolvers/HumanHandoffResolver.js +7 -1
  158. package/dist/resolvers/HumanHandoffResolver.js.map +1 -1
  159. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  160. package/dist/resolvers/MCPResolver.js +9 -4
  161. package/dist/resolvers/MCPResolver.js.map +1 -1
  162. package/dist/resolvers/RealtimeBridgeResolver.d.ts +54 -2
  163. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
  164. package/dist/resolvers/RealtimeBridgeResolver.js +155 -10
  165. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
  166. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +155 -25
  167. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  168. package/dist/resolvers/RealtimeClientSessionResolver.js +394 -90
  169. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  170. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts +61 -0
  171. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts.map +1 -0
  172. package/dist/resolvers/RealtimeSessionEventsResolver.js +130 -0
  173. package/dist/resolvers/RealtimeSessionEventsResolver.js.map +1 -0
  174. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts +85 -0
  175. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts.map +1 -0
  176. package/dist/resolvers/RealtimeSessionVerificationResolver.js +188 -0
  177. package/dist/resolvers/RealtimeSessionVerificationResolver.js.map +1 -0
  178. package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
  179. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  180. package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
  181. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  182. package/dist/resolvers/RunAIAgentResolver.d.ts +10 -0
  183. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  184. package/dist/resolvers/RunAIAgentResolver.js +68 -11
  185. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  186. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  187. package/dist/resolvers/RunTemplateResolver.js +12 -11
  188. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  189. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  190. package/dist/resolvers/SyncDataResolver.js +6 -2
  191. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  192. package/dist/resolvers/SyncRolesUsersResolver.d.ts +2 -0
  193. package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
  194. package/dist/resolvers/SyncRolesUsersResolver.js +23 -6
  195. package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
  196. package/dist/resolvers/UserAvatarResolver.d.ts +16 -0
  197. package/dist/resolvers/UserAvatarResolver.d.ts.map +1 -0
  198. package/dist/resolvers/UserAvatarResolver.js +124 -0
  199. package/dist/resolvers/UserAvatarResolver.js.map +1 -0
  200. package/dist/resolvers/UserResolver.d.ts.map +1 -1
  201. package/dist/resolvers/UserResolver.js +2 -17
  202. package/dist/resolvers/UserResolver.js.map +1 -1
  203. package/dist/resolvers/agentVisionConsentAudit.d.ts +37 -0
  204. package/dist/resolvers/agentVisionConsentAudit.d.ts.map +1 -0
  205. package/dist/resolvers/agentVisionConsentAudit.js +63 -0
  206. package/dist/resolvers/agentVisionConsentAudit.js.map +1 -0
  207. package/dist/resolvers/avatarInputValidation.d.ts +40 -0
  208. package/dist/resolvers/avatarInputValidation.d.ts.map +1 -0
  209. package/dist/resolvers/avatarInputValidation.js +97 -0
  210. package/dist/resolvers/avatarInputValidation.js.map +1 -0
  211. package/dist/resolvers/currentUserRoles.d.ts +29 -0
  212. package/dist/resolvers/currentUserRoles.d.ts.map +1 -1
  213. package/dist/resolvers/currentUserRoles.js +30 -0
  214. package/dist/resolvers/currentUserRoles.js.map +1 -1
  215. package/dist/resolvers/meetings/meetingMutations.d.ts +6 -0
  216. package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -1
  217. package/dist/resolvers/meetings/meetingMutations.js +21 -0
  218. package/dist/resolvers/meetings/meetingMutations.js.map +1 -1
  219. package/dist/services/TaskGraphContinuationDeliverer.d.ts +4 -3
  220. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  221. package/dist/services/TaskGraphContinuationDeliverer.js +13 -7
  222. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  223. package/dist/sharedCache.d.ts +105 -0
  224. package/dist/sharedCache.d.ts.map +1 -0
  225. package/dist/sharedCache.js +276 -0
  226. package/dist/sharedCache.js.map +1 -0
  227. package/dist/types.d.ts +6 -0
  228. package/dist/types.d.ts.map +1 -1
  229. package/dist/types.js.map +1 -1
  230. package/package.json +97 -95
  231. package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
  232. package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
  233. package/src/__tests__/BridgeHostInstance.test.ts +232 -0
  234. package/src/__tests__/ClientToolRequestResolver.test.ts +116 -0
  235. package/src/__tests__/FetchEntityVectorsResolver.test.ts +285 -0
  236. package/src/__tests__/HostInstance.test.ts +84 -0
  237. package/src/__tests__/HumanHandoffResolver.test.ts +17 -0
  238. package/src/__tests__/MeetingResolver.test.ts +75 -1
  239. package/src/__tests__/RealtimeBridgeResolver.test.ts +415 -6
  240. package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +383 -0
  241. package/src/__tests__/RealtimeClientSessionResolver.test.ts +682 -58
  242. package/src/__tests__/RealtimeProxyServer.test.ts +16 -1
  243. package/src/__tests__/RealtimeRelay.test.ts +876 -0
  244. package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
  245. package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
  246. package/src/__tests__/ResolverBase.clientOldValues.test.ts +6 -3
  247. package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
  248. package/src/__tests__/RunAIAgentResolver.branch.test.ts +169 -0
  249. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +4 -4
  250. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +166 -0
  251. package/src/__tests__/RunTemplateResolver.test.ts +132 -0
  252. package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +63 -0
  253. package/src/__tests__/SessionJanitor.test.ts +845 -18
  254. package/src/__tests__/SessionManager.test.ts +213 -17
  255. package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
  256. package/src/__tests__/SyncRolesUsersResolver.userRoles.test.ts +97 -0
  257. package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +50 -3
  258. package/src/__tests__/UserAvatarResolver.test.ts +255 -0
  259. package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
  260. package/src/__tests__/actionAuthorization.test.ts +130 -0
  261. package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
  262. package/src/__tests__/avatarInputValidation.test.ts +160 -0
  263. package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
  264. package/src/__tests__/magicLinkService.consume.test.ts +6 -0
  265. package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
  266. package/src/__tests__/newUsers.template.test.ts +151 -0
  267. package/src/__tests__/newUsers.test.ts +74 -5
  268. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +17 -2
  269. package/src/__tests__/realtimeSessions/RealtimeSessionEventService.test.ts +201 -0
  270. package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +215 -0
  271. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationResolver.test.ts +102 -0
  272. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationService.test.ts +349 -0
  273. package/src/__tests__/realtimeSessions/RealtimeVerifyRouter.test.ts +131 -0
  274. package/src/__tests__/realtimeSessions/SlidingWindowRateLimiter.test.ts +56 -0
  275. package/src/__tests__/realtimeSessions/helpers.ts +4 -0
  276. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +129 -0
  277. package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +124 -0
  278. package/src/__tests__/realtimeSessions/verificationCore.test.ts +355 -0
  279. package/src/__tests__/realtimeSessions/verificationPages.test.ts +84 -0
  280. package/src/__tests__/realtimeSessions/verificationPolicy.test.ts +226 -0
  281. package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +633 -0
  282. package/src/__tests__/resolverBase.fls.test.ts +87 -4
  283. package/src/__tests__/serverExtensionReservedRoots.test.ts +2 -0
  284. package/src/__tests__/sharedCache.test.ts +310 -0
  285. package/src/__tests__/widgetGuestElevation.test.ts +44 -0
  286. package/src/agentSessions/BridgeHostInstance.ts +32 -0
  287. package/src/agentSessions/HostInstance.ts +124 -28
  288. package/src/agentSessions/ReturningVisitorRecap.ts +7 -3
  289. package/src/agentSessions/SessionJanitor.ts +543 -57
  290. package/src/agentSessions/SessionManager.ts +125 -25
  291. package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
  292. package/src/agentSessions/index.ts +10 -3
  293. package/src/auth/actionAuthorization.ts +116 -0
  294. package/src/auth/index.ts +3 -8
  295. package/src/auth/magicLink/MagicLinkService.ts +6 -3
  296. package/src/auth/newUsers.ts +99 -11
  297. package/src/config.ts +125 -0
  298. package/src/context.ts +15 -10
  299. package/src/generated/graphql-schemas/__mj.ts +496 -1
  300. package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
  301. package/src/generic/ResolverBase.ts +39 -9
  302. package/src/generic/RunViewResolver.ts +4 -0
  303. package/src/index.ts +132 -143
  304. package/src/isolatedProvider.ts +22 -0
  305. package/src/realtimeProxy/RealtimeProxyServer.ts +148 -16
  306. package/src/realtimeProxy/RealtimeRelayTunnel.ts +645 -0
  307. package/src/realtimeSessions/README.md +179 -0
  308. package/src/realtimeSessions/RealtimeSessionEventService.ts +274 -0
  309. package/src/realtimeSessions/RealtimeSessionVerificationService.ts +167 -0
  310. package/src/realtimeSessions/RealtimeVerifyRouter.ts +108 -0
  311. package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
  312. package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
  313. package/src/realtimeSessions/index.ts +16 -0
  314. package/src/realtimeSessions/sessionAccess.ts +141 -0
  315. package/src/realtimeSessions/sessionConfigStamps.ts +123 -0
  316. package/src/realtimeSessions/subscriptionAuthorization.ts +116 -0
  317. package/src/realtimeSessions/verificationCore.ts +502 -0
  318. package/src/realtimeSessions/verificationPages.ts +149 -0
  319. package/src/realtimeSessions/verificationPolicy.ts +321 -0
  320. package/src/realtimeSessions/verificationWorkflow.ts +702 -0
  321. package/src/realtimeWidget/widgetGuestElevation.ts +34 -2
  322. package/src/resolvers/ActionResolver.ts +84 -21
  323. package/src/resolvers/AdhocQueryResolver.ts +24 -6
  324. package/src/resolvers/CacheStatsResolver.ts +89 -2
  325. package/src/resolvers/ClientToolRequestResolver.ts +25 -7
  326. package/src/resolvers/FetchEntityVectorsResolver.ts +97 -2
  327. package/src/resolvers/HumanHandoffResolver.ts +8 -0
  328. package/src/resolvers/MCPResolver.ts +8 -5
  329. package/src/resolvers/RealtimeBridgeResolver.ts +163 -11
  330. package/src/resolvers/RealtimeClientSessionResolver.ts +501 -96
  331. package/src/resolvers/RealtimeSessionEventsResolver.ts +133 -0
  332. package/src/resolvers/RealtimeSessionVerificationResolver.ts +174 -0
  333. package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
  334. package/src/resolvers/RunAIAgentResolver.ts +78 -11
  335. package/src/resolvers/RunTemplateResolver.ts +13 -12
  336. package/src/resolvers/SyncDataResolver.ts +6 -2
  337. package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
  338. package/src/resolvers/UserAvatarResolver.ts +111 -0
  339. package/src/resolvers/UserResolver.ts +2 -16
  340. package/src/resolvers/agentVisionConsentAudit.ts +82 -0
  341. package/src/resolvers/avatarInputValidation.ts +123 -0
  342. package/src/resolvers/currentUserRoles.ts +32 -1
  343. package/src/resolvers/meetings/meetingMutations.ts +21 -0
  344. package/src/services/TaskGraphContinuationDeliverer.ts +13 -7
  345. package/src/sharedCache.ts +302 -0
  346. package/src/types.ts +7 -0
@@ -0,0 +1,111 @@
1
+ /**
2
+ * @fileoverview Self-service avatar mutation: `UpdateMyAvatar(ImageURL, IconClass)`.
3
+ *
4
+ * WHY THIS EXISTS. Explorer's My Profile photo used to be a plain entity save of the caller's own
5
+ * `MJ: Users` row, which needs Update permission on `MJ: Users`. A deployment that locks its
6
+ * security model down cannot give ordinary users that permission: a row-level "own row only"
7
+ * filter restricts ROWS, not COLUMNS, so a user who may update their own row may also rewrite
8
+ * their own `Email` — and many read filters key on `{{UserEmail}}`, so the new value would match
9
+ * another person's memberships. MJ has no per-role field permissions (`MJUserEntityServer` in
10
+ * core-entities-server documents the same limit for `Type`). On such deployments every avatar save
11
+ * failed, and the login avatar sync failed silently.
12
+ *
13
+ * WHAT IT DOES. The caller comes ONLY from the request context — there is no user-ID argument. The
14
+ * input is validated at this trust boundary (`ValidateAvatarInput`). The caller's own row is then
15
+ * loaded AS THE SYSTEM USER, any state other than the two avatar columns is discarded, and only
16
+ * `UserImageURL` and `UserImageIconClass` are set before saving. Because the write is made as the
17
+ * system user, the caller's ID is logged. It works the same whether or not the caller holds Update
18
+ * on `MJ: Users`.
19
+ *
20
+ * Scope-limited sessions are refused (`IsScopeLimitedPrincipal`). An anonymous magic-link guest IS
21
+ * the shared Anonymous principal, so "their own row" belongs to every guest, and a system-user save
22
+ * goes around the entity permissions and RLS that confine any magic-link session.
23
+ *
24
+ * `MJUserEntityServer` accepts this save: the system user is the seeded `Type='Owner'` user, which
25
+ * is exempt from its invariants, and the save changes neither `Type` nor `Name` in any case.
26
+ *
27
+ * @module @memberjunction/server/resolvers/UserAvatarResolver
28
+ */
29
+ import { Arg, Ctx, Field, Mutation, ObjectType, Resolver } from 'type-graphql';
30
+ import { LogError, LogStatus, UserInfo } from '@memberjunction/core';
31
+ import type { MJUserEntity } from '@memberjunction/core-entities';
32
+ import { AppContext } from '../types.js';
33
+ import { GetReadWriteProvider } from '../util.js';
34
+ import { GetSystemUser } from '../auth/index.js';
35
+ import { IsScopeLimitedPrincipal } from '../auth/scopeLimitedPrincipal.js';
36
+ import { ResolverBase } from '../generic/ResolverBase.js';
37
+ import { ValidateAvatarInput } from './avatarInputValidation.js';
38
+
39
+ /** What the browser sees when the save fails. The server's detail goes to the log only. */
40
+ const SAVE_FAILED_MESSAGE = 'Could not save your avatar. Please try again or contact your administrator.';
41
+
42
+ @ObjectType()
43
+ export class UpdateMyAvatarResult {
44
+ @Field()
45
+ Success: boolean;
46
+
47
+ @Field({ nullable: true })
48
+ ErrorMessage?: string;
49
+ }
50
+
51
+ @Resolver()
52
+ export class UserAvatarResolver extends ResolverBase {
53
+ /**
54
+ * Sets the CALLER's avatar. Null or empty clears a column; both null reverts to the default.
55
+ * Validation failures and save failures are returned as `Success: false` with a message.
56
+ */
57
+ @Mutation(() => UpdateMyAvatarResult)
58
+ async UpdateMyAvatar(
59
+ @Ctx() ctx: AppContext,
60
+ @Arg('ImageURL', () => String, { nullable: true }) imageURL?: string | null,
61
+ @Arg('IconClass', () => String, { nullable: true }) iconClass?: string | null,
62
+ ): Promise<UpdateMyAvatarResult> {
63
+ const caller = ctx.userPayload?.userRecord as UserInfo | undefined;
64
+ if (!caller?.ID) {
65
+ throw new Error('User is not authenticated');
66
+ }
67
+ if (IsScopeLimitedPrincipal(caller)) {
68
+ return { Success: false, ErrorMessage: 'Changing your avatar is not permitted for scope-limited sessions.' };
69
+ }
70
+ await this.CheckAPIKeyScopeAuthorization('entity:update', 'MJ: Users', ctx.userPayload);
71
+
72
+ const input = ValidateAvatarInput(imageURL, iconClass);
73
+ if (!input.Valid) {
74
+ return { Success: false, ErrorMessage: input.ErrorMessage };
75
+ }
76
+
77
+ try {
78
+ return await this.saveAvatar(ctx, caller, input.ImageURL, input.IconClass);
79
+ } catch (e) {
80
+ LogError(`UpdateMyAvatar: failed for user ${caller.ID}: ${e instanceof Error ? e.message : String(e)}`);
81
+ return { Success: false, ErrorMessage: SAVE_FAILED_MESSAGE };
82
+ }
83
+ }
84
+
85
+ /** Loads the caller's own row as the system user and writes ONLY the two avatar columns. */
86
+ private async saveAvatar(
87
+ ctx: AppContext,
88
+ caller: UserInfo,
89
+ imageURL: string | null,
90
+ iconClass: string | null,
91
+ ): Promise<UpdateMyAvatarResult> {
92
+ const systemUser = await GetSystemUser();
93
+ const provider = GetReadWriteProvider(ctx.providers);
94
+ const user = await provider.GetEntityObject<MJUserEntity>('MJ: Users', systemUser);
95
+ if (!(await user.Load(caller.ID))) {
96
+ LogError(`UpdateMyAvatar: could not load the MJ: Users row for user ${caller.ID}`);
97
+ return { Success: false, ErrorMessage: 'Could not load your user record.' };
98
+ }
99
+
100
+ user.Revert(); // nothing but the two avatar columns may reach the write
101
+ user.UserImageURL = imageURL;
102
+ user.UserImageIconClass = iconClass;
103
+ LogStatus(`UpdateMyAvatar: user ${caller.ID} requested an avatar change; saving as the system user`);
104
+ if (!(await user.Save())) {
105
+ const detail = user.LatestResult?.CompleteMessage ?? 'unknown error';
106
+ LogError(`UpdateMyAvatar: save failed for user ${caller.ID}: ${detail}`);
107
+ return { Success: false, ErrorMessage: SAVE_FAILED_MESSAGE };
108
+ }
109
+ return { Success: true };
110
+ }
111
+ }
@@ -8,6 +8,7 @@ import {
8
8
  RequireContextUser,
9
9
  RequireLoadedUser,
10
10
  RequireRoleProvider,
11
+ SessionUserFields,
11
12
  UserIdExtraFilter,
12
13
  } from './currentUserRoles.js';
13
14
 
@@ -42,22 +43,7 @@ export class UserResolver extends MJUserResolverBase {
42
43
 
43
44
  if (context.userPayload.userRecord) {
44
45
  const userRecord = context.userPayload.userRecord;
45
- const userData = {
46
- ID: userRecord.ID,
47
- Name: userRecord.Name,
48
- FirstName: userRecord.FirstName,
49
- LastName: userRecord.LastName,
50
- Title: userRecord.Title,
51
- Email: userRecord.Email,
52
- Type: userRecord.Type,
53
- IsActive: userRecord.IsActive,
54
- LinkedRecordType: userRecord.LinkedRecordType,
55
- EmployeeID: userRecord.EmployeeID,
56
- LinkedEntityID: userRecord.LinkedEntityID,
57
- LinkedEntityRecordID: userRecord.LinkedEntityRecordID,
58
- __mj_CreatedAt: userRecord.__mj_CreatedAt,
59
- __mj_UpdatedAt: userRecord.__mj_UpdatedAt,
60
- };
46
+ const userData = SessionUserFields(userRecord);
61
47
  console.log('CurrentUser (from userRecord)', userData.Email);
62
48
  const provider = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
63
49
  const mapped = await this.MapFieldNamesToCodeNames('MJ: Users', userData, userRecord, provider) as MJUser_;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @fileoverview The audit trail for agent-vision consent: one `MJ: Audit Logs` row each time a person allows, or stops
3
+ * letting, agents see their camera and shared screen in a meeting room, including when LiveKit refuses the change.
4
+ *
5
+ * The row is saved by the system user, as MJ's other server-written audit rows are, because the person may not be
6
+ * allowed to create audit rows themselves; `UserID` and `RecordID` are the person. When the audit log type has not
7
+ * been seeded, the entry goes to the server log instead, so a choice is never left unrecorded.
8
+ *
9
+ * Invoked from `RealtimeBridgeResolver.SetLiveKitAgentVision`.
10
+ *
11
+ * @module @memberjunction/server
12
+ */
13
+
14
+ import { LogError, type IMetadataProvider, type UserInfo } from '@memberjunction/core';
15
+ import type { MJAuditLogEntity } from '@memberjunction/core-entities';
16
+ import { UserCache } from '@memberjunction/generic-database-provider';
17
+
18
+ /** The seeded audit log type (see `metadata/audit-log-types/.realtime-session-audit-types.json`). */
19
+ export const REALTIME_AGENT_VISION_CONSENT_AUDIT_TYPE = 'Realtime Agent Vision Consent';
20
+
21
+ const AUDIT_LOG_ENTITY = 'MJ: Audit Logs';
22
+ const USERS_ENTITY = 'MJ: Users';
23
+
24
+ /** One consent change to record. */
25
+ export interface AgentVisionConsentEntry {
26
+ /** The person who chose. */
27
+ User: UserInfo;
28
+ /** The room they chose for. */
29
+ RoomName: string;
30
+ /** `true` to let agents see their camera and shared screen, `false` to stop. */
31
+ Allow: boolean;
32
+ /** Whether LiveKit applied the change. */
33
+ Applied: boolean;
34
+ /** Why it was not applied. */
35
+ ErrorMessage?: string;
36
+ }
37
+
38
+ /**
39
+ * Writes the audit row for one consent change. Never throws: a failure to audit is logged, and the person's choice
40
+ * stands as LiveKit applied it.
41
+ *
42
+ * @param entry The change to record.
43
+ * @param provider The request's provider.
44
+ */
45
+ export async function WriteAgentVisionConsentAudit(entry: AgentVisionConsentEntry, provider: IMetadataProvider | null): Promise<void> {
46
+ try {
47
+ const writer = UserCache.Instance.GetSystemUser();
48
+ const type = provider?.AuditLogTypes?.find((t) => t.Name?.trim().toLowerCase() === REALTIME_AGENT_VISION_CONSENT_AUDIT_TYPE.toLowerCase());
49
+ if (!provider || !writer || !type) {
50
+ LogError(`Agent vision consent not audited (${missingReason(provider, writer)}): user ${entry.User.ID}: ${describeChange(entry)}`);
51
+ return;
52
+ }
53
+ const row = await provider.GetEntityObject<MJAuditLogEntity>(AUDIT_LOG_ENTITY, writer);
54
+ row.NewRecord();
55
+ row.UserID = entry.User.ID;
56
+ row.AuditLogTypeID = type.ID;
57
+ row.Status = entry.Applied ? 'Success' : 'Failed';
58
+ row.EntityID = provider.EntityByName(USERS_ENTITY)?.ID ?? null;
59
+ row.RecordID = entry.User.ID;
60
+ row.Description = describeChange(entry);
61
+ row.Details = JSON.stringify({ roomName: entry.RoomName, allow: entry.Allow, error: entry.ErrorMessage ?? null });
62
+ if (!(await row.Save())) {
63
+ LogError(`Agent vision consent audit row not saved for user ${entry.User.ID}: ${row.LatestResult?.CompleteMessage ?? 'unknown error'}`);
64
+ }
65
+ } catch (error) {
66
+ LogError(`Agent vision consent audit write threw for user ${entry.User.ID}: ${error instanceof Error ? error.message : String(error)}`);
67
+ }
68
+ }
69
+
70
+ /** Why the audit row can't be written: no provider, no system user, or the audit log type isn't seeded. */
71
+ function missingReason(provider: IMetadataProvider | null, writer: UserInfo | undefined | null): string {
72
+ if (!provider) {
73
+ return 'no provider';
74
+ }
75
+ return writer ? `audit log type '${REALTIME_AGENT_VISION_CONSENT_AUDIT_TYPE}' is not seeded` : 'no system user';
76
+ }
77
+
78
+ /** One line saying what the person chose, and whether it was applied. */
79
+ function describeChange(entry: AgentVisionConsentEntry): string {
80
+ const choice = entry.Allow ? 'Let agents see their camera and shared screen' : 'Stopped letting agents see their camera and shared screen';
81
+ return `${choice} in room ${entry.RoomName}${entry.Applied ? '' : ' (not applied)'}.`;
82
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @fileoverview Trust-boundary validation for the self-service avatar mutation
3
+ * (`UpdateMyAvatar` in `UserAvatarResolver`).
4
+ *
5
+ * The browser checks these values too, but its URL check is `new URL(...)`, which happily accepts
6
+ * `javascript:` and every `data:` type. The server is the boundary, so everything that reaches
7
+ * `MJ: Users.UserImageURL` / `UserImageIconClass` through the mutation passes through here first.
8
+ *
9
+ * Pure: no I/O, no provider, no entity — so it is unit-testable on its own.
10
+ *
11
+ * @module @memberjunction/server/resolvers/avatarInputValidation
12
+ */
13
+
14
+ /** Largest DECODED image accepted in a `data:` URI. Matches the Explorer upload cap. */
15
+ export const AVATAR_MAX_IMAGE_BYTES = 200 * 1024;
16
+
17
+ /** Longest `http:` / `https:` image URL accepted. */
18
+ export const AVATAR_MAX_URL_LENGTH = 2048;
19
+
20
+ /** Longest icon class string accepted. */
21
+ export const AVATAR_MAX_ICON_CLASS_LENGTH = 100;
22
+
23
+ /**
24
+ * Longest `data:` value that can still decode to {@link AVATAR_MAX_IMAGE_BYTES}: the longest accepted
25
+ * prefix plus the padded base64 payload. Anything longer is refused before the pattern scans it.
26
+ */
27
+ const AVATAR_MAX_DATA_URI_LENGTH = 'data:image/jpeg;base64,'.length + Math.ceil(AVATAR_MAX_IMAGE_BYTES / 3) * 4;
28
+
29
+ /** `data:image/<raster type>;base64,<payload>` — SVG is deliberately absent (it can carry script). */
30
+ const DATA_URI_PATTERN = /^data:image\/(png|jpeg|jpg|gif|webp);base64,([A-Za-z0-9+/]+={0,2})$/;
31
+
32
+ /** Lower-case Font Awesome class lists only, e.g. `fa-solid fa-user`. */
33
+ const ICON_CLASS_PATTERN = /^[a-z0-9 -]+$/;
34
+ const FA_TOKEN_PATTERN = /^fa-[a-z0-9-]+$/;
35
+
36
+ /** The outcome of {@link ValidateAvatarInput}. Empty strings are normalised to `null` (clear). */
37
+ export interface AvatarInputValidationResult {
38
+ Valid: boolean;
39
+ /** Why the input was refused. Set only when `Valid` is false. */
40
+ ErrorMessage?: string;
41
+ /** The value to store in `UserImageURL`; `null` clears it. */
42
+ ImageURL: string | null;
43
+ /** The value to store in `UserImageIconClass`; `null` clears it. */
44
+ IconClass: string | null;
45
+ }
46
+
47
+ /**
48
+ * Validates a requested avatar. Null or empty values clear the corresponding column, so both
49
+ * null is a valid "revert to default".
50
+ *
51
+ * - `imageURL`: a `data:image/(png|jpeg|jpg|gif|webp);base64,...` URI whose decoded size is at most
52
+ * {@link AVATAR_MAX_IMAGE_BYTES}, or an absolute `http:`/`https:` URL of at most
53
+ * {@link AVATAR_MAX_URL_LENGTH} characters with no whitespace. Everything else is refused.
54
+ * - `iconClass`: at most {@link AVATAR_MAX_ICON_CLASS_LENGTH} characters of `[a-z0-9 -]`,
55
+ * containing at least one `fa-` token.
56
+ */
57
+ export function ValidateAvatarInput(
58
+ imageURL: string | null | undefined,
59
+ iconClass: string | null | undefined
60
+ ): AvatarInputValidationResult {
61
+ const image = imageURL ? imageURL : null;
62
+ const icon = iconClass ? iconClass : null;
63
+ const error = (image === null ? undefined : imageUrlError(image)) ?? (icon === null ? undefined : iconClassError(icon));
64
+ return error ? { Valid: false, ErrorMessage: error, ImageURL: null, IconClass: null } : { Valid: true, ImageURL: image, IconClass: icon };
65
+ }
66
+
67
+ /** Returns why an image value is refused, or `undefined` when it is acceptable. */
68
+ function imageUrlError(value: string): string | undefined {
69
+ if (value.startsWith('data:')) {
70
+ return dataUriError(value);
71
+ }
72
+ return httpUrlError(value);
73
+ }
74
+
75
+ function dataUriError(value: string): string | undefined {
76
+ if (value.length > AVATAR_MAX_DATA_URI_LENGTH) {
77
+ return tooLargeError(Math.floor((value.length * 3) / 4));
78
+ }
79
+ const match = DATA_URI_PATTERN.exec(value);
80
+ if (!match || match[2].length % 4 !== 0) {
81
+ return 'Avatar image must be a base64 PNG, JPEG, GIF or WEBP data URI.';
82
+ }
83
+ const decodedBytes = decodedBase64Length(match[2]);
84
+ return decodedBytes > AVATAR_MAX_IMAGE_BYTES ? tooLargeError(decodedBytes) : undefined;
85
+ }
86
+
87
+ function tooLargeError(bytes: number): string {
88
+ return `Avatar image must be ${AVATAR_MAX_IMAGE_BYTES / 1024}KB or smaller (this one is about ${Math.ceil(bytes / 1024)}KB).`;
89
+ }
90
+
91
+ /** Decoded byte length of a padded base64 string, without decoding it. */
92
+ function decodedBase64Length(base64: string): number {
93
+ const padding = base64.endsWith('==') ? 2 : base64.endsWith('=') ? 1 : 0;
94
+ return (base64.length / 4) * 3 - padding;
95
+ }
96
+
97
+ function httpUrlError(value: string): string | undefined {
98
+ const refusal = 'Avatar image URL must be an absolute http or https URL.';
99
+ if (value.length > AVATAR_MAX_URL_LENGTH) {
100
+ return `Avatar image URL must be ${AVATAR_MAX_URL_LENGTH} characters or fewer.`;
101
+ }
102
+ if (/\s/.test(value) || !/^https?:\/\//i.test(value)) {
103
+ return refusal;
104
+ }
105
+ try {
106
+ const url = new URL(value);
107
+ return url.protocol === 'http:' || url.protocol === 'https:' ? undefined : refusal;
108
+ } catch {
109
+ return refusal;
110
+ }
111
+ }
112
+
113
+ /** Returns why an icon class is refused, or `undefined` when it is acceptable. */
114
+ function iconClassError(value: string): string | undefined {
115
+ const valid =
116
+ value.length <= AVATAR_MAX_ICON_CLASS_LENGTH &&
117
+ ICON_CLASS_PATTERN.test(value) &&
118
+ value.split(' ').some((token) => FA_TOKEN_PATTERN.test(token));
119
+ return valid
120
+ ? undefined
121
+ : `Avatar icon must be a Font Awesome class list (lower-case letters, digits, spaces and hyphens, ` +
122
+ `${AVATAR_MAX_ICON_CLASS_LENGTH} characters or fewer, including an fa- class).`;
123
+ }
@@ -1,4 +1,4 @@
1
- import { LogError } from '@memberjunction/core';
1
+ import { LogError, UserInfo } from '@memberjunction/core';
2
2
 
3
3
  /**
4
4
  * Guards and filter builders for CurrentUser role loading.
@@ -6,6 +6,37 @@ import { LogError } from '@memberjunction/core';
6
6
  * without importing generated GraphQL types or the Apollo config bootstrap.
7
7
  */
8
8
 
9
+ /**
10
+ * The session user's own fields, as CurrentUser returns them. The client asks for every field of
11
+ * `UserInfo` (GraphQLDataProvider builds its query from it), so each one belongs here; anything
12
+ * left out reaches the client as null (as IsTemplate once did, hiding the template-user banner).
13
+ */
14
+ export function SessionUserFields(userRecord: UserInfo) {
15
+ return {
16
+ ID: userRecord.ID,
17
+ Name: userRecord.Name,
18
+ FirstName: userRecord.FirstName,
19
+ LastName: userRecord.LastName,
20
+ Title: userRecord.Title,
21
+ Email: userRecord.Email,
22
+ Type: userRecord.Type,
23
+ IsActive: userRecord.IsActive,
24
+ IsTemplate: userRecord.IsTemplate,
25
+ LinkedRecordType: userRecord.LinkedRecordType,
26
+ EmployeeID: userRecord.EmployeeID,
27
+ LinkedEntityID: userRecord.LinkedEntityID,
28
+ LinkedEntityRecordID: userRecord.LinkedEntityRecordID,
29
+ __mj_CreatedAt: userRecord.__mj_CreatedAt,
30
+ __mj_UpdatedAt: userRecord.__mj_UpdatedAt,
31
+ FirstLast: userRecord.FirstLast,
32
+ EmployeeFirstLast: userRecord.EmployeeFirstLast,
33
+ EmployeeEmail: userRecord.EmployeeEmail,
34
+ EmployeeTitle: userRecord.EmployeeTitle,
35
+ EmployeeSupervisor: userRecord.EmployeeSupervisor,
36
+ EmployeeSupervisorEmail: userRecord.EmployeeSupervisorEmail,
37
+ };
38
+ }
39
+
9
40
  export function RequireLoadedUser<T extends { ID?: string }>(
10
41
  user: T | null | undefined,
11
42
  email: string | undefined,
@@ -305,6 +305,12 @@ export async function ExecuteUpdateMeeting(
305
305
  }
306
306
  }
307
307
 
308
+ /**
309
+ * Starts a meeting for the `StartMeeting` mutation (`MeetingResolver`): marks it `Live`, ensures its Interaction, starts
310
+ * each agent participant in the room under an agent session created for it, starts an automatic recording, and mints the
311
+ * caller's client token. An agent that fails to start doesn't fail the meeting; the session created for it is closed
312
+ * (`Error`).
313
+ */
308
314
  export async function ExecuteStartMeeting(
309
315
  provider: IMetadataProvider,
310
316
  user: UserInfo,
@@ -343,12 +349,15 @@ export async function ExecuteStartMeeting(
343
349
  const agentParticipants = participants.filter((p) => p.Role === 'Agent' && p.AgentID);
344
350
 
345
351
  for (const ap of agentParticipants) {
352
+ // Set once this agent's session exists, so a start that fails after it was created can close it.
353
+ let createdSessionID: string | undefined;
346
354
  try {
347
355
  const createdSession = await sessionManager.CreateSession(
348
356
  { agentID: ap.AgentID!, userID: user.ID },
349
357
  user,
350
358
  provider,
351
359
  );
360
+ createdSessionID = createdSession.ID;
352
361
  await LiveKitAgentRoomCoordinator.Instance.StartAgentRoomSession({
353
362
  AgentSessionID: createdSession.ID,
354
363
  RoomName: meeting.RoomName,
@@ -357,9 +366,21 @@ export async function ExecuteStartMeeting(
357
366
  MetadataProvider: provider,
358
367
  TurnMode: 'Active',
359
368
  TurnAddressing: 'Auto',
369
+ // The session exists for this agent's time in the room alone: it closes when the agent leaves (the meeting is
370
+ // ended, everyone leaves, the agent is stopped), not when the janitor finds it idle.
371
+ Host: {
372
+ OnSessionEnded: async (reason) => {
373
+ await sessionManager.CloseSessionForEndedBridge(createdSession.ID, reason, user, provider);
374
+ },
375
+ },
360
376
  });
361
377
  } catch (agentErr) {
362
378
  LogError(`[MeetingResolver] Failed to start agent participant ${ap.AgentID} in room ${meeting.RoomName}: ${agentErr instanceof Error ? agentErr.message : String(agentErr)}`);
379
+ // The agent never joined, so the hook above never runs: close its session now (Error), not when the janitor finds
380
+ // it idle. The coordinator has already closed any model session the start opened (#5308).
381
+ if (createdSessionID) {
382
+ await sessionManager.CloseSessionForFailedStart(createdSessionID, user, provider);
383
+ }
363
384
  }
364
385
  }
365
386
 
@@ -46,7 +46,8 @@ export class TaskGraphContinuationDeliverer implements TaskContinuationDeliverer
46
46
  ) {}
47
47
 
48
48
  /**
49
- * Posts the roll-up as an AI-role message in the graph's conversation.
49
+ * Posts the roll-up as an AI-role message in the graph's conversation, on the fork of the
50
+ * message that started the graph (Main when that message is in Main).
50
51
  *
51
52
  * Never throws. The dispatcher calls this inside the compare-and-swap that marks a completion
52
53
  * delivered; an error escaping would either abort that guard or leave the graph looking
@@ -62,8 +63,8 @@ export class TaskGraphContinuationDeliverer implements TaskContinuationDeliverer
62
63
  }
63
64
 
64
65
  const provider = await this.providerFactory.CreateProvider();
65
- const conversationID = await this.resolveConversationID(params.ConversationDetailID, provider);
66
- if (!conversationID) {
66
+ const origin = await this.resolveOrigin(params.ConversationDetailID, provider);
67
+ if (!origin) {
67
68
  LogError(
68
69
  `[TaskGraphContinuationDeliverer] Conversation detail ${params.ConversationDetailID} could not be loaded — ` +
69
70
  `"${params.WorkflowName}" has nowhere to post its outcome.`
@@ -73,7 +74,8 @@ export class TaskGraphContinuationDeliverer implements TaskContinuationDeliverer
73
74
 
74
75
  const detail = await provider.GetEntityObject<MJConversationDetailEntity>('MJ: Conversation Details', this.contextUser);
75
76
  detail.NewRecord();
76
- detail.ConversationID = conversationID;
77
+ detail.ConversationID = origin.ConversationID;
78
+ detail.BranchID = origin.BranchID;
77
79
  detail.Role = 'AI';
78
80
  detail.Status = 'Complete';
79
81
  detail.HiddenToUser = false;
@@ -125,11 +127,15 @@ export class TaskGraphContinuationDeliverer implements TaskContinuationDeliverer
125
127
  return;
126
128
  }
127
129
 
130
+ // Run on the fork of the message that started the graph, as PostMessage posts into it:
131
+ // RunAgent derives the conversation from the detail, but not the fork.
132
+ const origin = params.ConversationDetailID ? await this.resolveOrigin(params.ConversationDetailID, provider) : null;
128
133
  await new AgentRunner().RunAgent({
129
134
  agent,
130
135
  conversationMessages: [{ role: ChatMessageRole.user, content: this.renderMessage(params) }],
131
136
  contextUser: this.contextUser,
132
137
  conversationDetailId: params.ConversationDetailID ?? undefined,
138
+ ConversationBranchID: origin?.BranchID ?? undefined,
133
139
  // The load-bearing value: without it the next graph this run submits restarts the
134
140
  // chain at zero and the cap never fires.
135
141
  continuationDepth: params.ReinvokeDepth + 1,
@@ -149,10 +155,10 @@ export class TaskGraphContinuationDeliverer implements TaskContinuationDeliverer
149
155
  }
150
156
  }
151
157
 
152
- /** The conversation a detail belongs to. */
153
- private async resolveConversationID(conversationDetailID: string, provider: IMetadataProvider): Promise<string | null> {
158
+ /** The conversation and fork (null is Main) of the detail that started the graph; null when it cannot be loaded. */
159
+ private async resolveOrigin(conversationDetailID: string, provider: IMetadataProvider): Promise<{ ConversationID: string; BranchID: string | null } | null> {
154
160
  const detail = await provider.GetEntityObject<MJConversationDetailEntity>('MJ: Conversation Details', this.contextUser);
155
- return (await detail.Load(conversationDetailID)) ? detail.ConversationID : null;
161
+ return (await detail.Load(conversationDetailID)) ? { ConversationID: detail.ConversationID, BranchID: detail.BranchID ?? null } : null;
156
162
  }
157
163
 
158
164
  /**