@memberjunction/server 6.1.0-edge.5 → 6.1.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 (250) hide show
  1. package/README.md +30 -11
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/agentSessions/SessionJanitor.js +2 -2
  4. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  5. package/dist/auth/exampleNewUserSubClass.d.ts.map +1 -1
  6. package/dist/auth/exampleNewUserSubClass.js +9 -4
  7. package/dist/auth/exampleNewUserSubClass.js.map +1 -1
  8. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  9. package/dist/auth/magicLink/MagicLinkService.js +2 -8
  10. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  11. package/dist/auth/newUsers.d.ts.map +1 -1
  12. package/dist/auth/newUsers.js +4 -12
  13. package/dist/auth/newUsers.js.map +1 -1
  14. package/dist/auth/principals.d.ts +92 -0
  15. package/dist/auth/principals.d.ts.map +1 -0
  16. package/dist/auth/principals.js +193 -0
  17. package/dist/auth/principals.js.map +1 -0
  18. package/dist/config.d.ts +106 -12
  19. package/dist/config.d.ts.map +1 -1
  20. package/dist/config.js +41 -4
  21. package/dist/config.js.map +1 -1
  22. package/dist/entitySubclasses/MJEntityPermissionEntityServer.server.d.ts +30 -0
  23. package/dist/entitySubclasses/MJEntityPermissionEntityServer.server.d.ts.map +1 -1
  24. package/dist/entitySubclasses/MJEntityPermissionEntityServer.server.js +58 -5
  25. package/dist/entitySubclasses/MJEntityPermissionEntityServer.server.js.map +1 -1
  26. package/dist/generated/graphql-schemas/__mj.d.ts +7607 -6853
  27. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  28. package/dist/generated/graphql-schemas/__mj.js +25141 -21645
  29. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  30. package/dist/generic/ResolverBase.d.ts +152 -2
  31. package/dist/generic/ResolverBase.d.ts.map +1 -1
  32. package/dist/generic/ResolverBase.js +389 -10
  33. package/dist/generic/ResolverBase.js.map +1 -1
  34. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  35. package/dist/generic/RunViewResolver.js +26 -2
  36. package/dist/generic/RunViewResolver.js.map +1 -1
  37. package/dist/generic/refusalExtensions.d.ts +52 -0
  38. package/dist/generic/refusalExtensions.d.ts.map +1 -0
  39. package/dist/generic/refusalExtensions.js +16 -0
  40. package/dist/generic/refusalExtensions.js.map +1 -0
  41. package/dist/index.d.ts +11 -3
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +131 -93
  44. package/dist/index.js.map +1 -1
  45. package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
  46. package/dist/realtimeWidget/WidgetSessionService.js +2 -12
  47. package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
  48. package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
  49. package/dist/realtimeWidget/visitorIdentity.js +20 -5
  50. package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
  51. package/dist/resolvers/EntityRecordNameResolver.d.ts +34 -5
  52. package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
  53. package/dist/resolvers/EntityRecordNameResolver.js +64 -7
  54. package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
  55. package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
  56. package/dist/resolvers/FileCategoryResolver.js +1 -2
  57. package/dist/resolvers/FileCategoryResolver.js.map +1 -1
  58. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  59. package/dist/resolvers/IntegrationDiscoveryResolver.js +2 -0
  60. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  61. package/dist/resolvers/QuerySystemUserResolver.js +1 -1
  62. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
  63. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +10 -2
  64. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  65. package/dist/resolvers/RealtimeClientSessionResolver.js +80 -4
  66. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  67. package/dist/resolvers/RemoteBrowserActionResolver.d.ts +13 -8
  68. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  69. package/dist/resolvers/RemoteBrowserActionResolver.js +45 -8
  70. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  71. package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
  72. package/dist/resolvers/SyncRolesUsersResolver.js +11 -2
  73. package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
  74. package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
  75. package/dist/resolvers/UserViewResolver.js +8 -4
  76. package/dist/resolvers/UserViewResolver.js.map +1 -1
  77. package/dist/rest/EntityCRUDHandler.d.ts +2 -1
  78. package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
  79. package/dist/rest/EntityCRUDHandler.js +3 -18
  80. package/dist/rest/EntityCRUDHandler.js.map +1 -1
  81. package/dist/rest/RESTEndpointHandler.d.ts +2 -1
  82. package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
  83. package/dist/rest/RESTEndpointHandler.js +3 -16
  84. package/dist/rest/RESTEndpointHandler.js.map +1 -1
  85. package/dist/rest/RealtimeSdpBrokerHandler.d.ts +23 -0
  86. package/dist/rest/RealtimeSdpBrokerHandler.d.ts.map +1 -0
  87. package/dist/rest/RealtimeSdpBrokerHandler.js +78 -0
  88. package/dist/rest/RealtimeSdpBrokerHandler.js.map +1 -0
  89. package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
  90. package/dist/serverExtensionReservedRoots.js +2 -3
  91. package/dist/serverExtensionReservedRoots.js.map +1 -1
  92. package/package.json +92 -91
  93. package/src/__tests__/EntityCRUDHandler.test.ts +18 -5
  94. package/src/__tests__/EntityRecordNameResolver.fieldSecurity.test.ts +118 -0
  95. package/src/__tests__/RealtimeClientSessionResolver.test.ts +20 -0
  96. package/src/__tests__/RealtimeSdpBrokerHandler.test.ts +191 -0
  97. package/src/__tests__/RemoteBrowserAudioStream.test.ts +81 -2
  98. package/src/__tests__/ResolverBase.filterEscaping.test.ts +190 -3
  99. package/src/__tests__/ResolverBase.readableFieldsTransport.test.ts +106 -0
  100. package/src/__tests__/UserViewResolver.updateWhereClause.test.ts +114 -0
  101. package/src/__tests__/WidgetSessionService.test.ts +4 -1
  102. package/src/__tests__/generatedGraphQLSchema.structural.test.ts +46 -0
  103. package/src/__tests__/newUserRolesDefault.test.ts +81 -0
  104. package/src/__tests__/newUsers.test.ts +36 -14
  105. package/src/__tests__/principals.callSites.test.ts +112 -0
  106. package/src/__tests__/principals.logging.test.ts +160 -0
  107. package/src/__tests__/principals.test.ts +305 -0
  108. package/src/__tests__/resolverBase.fls.test.ts +483 -0
  109. package/src/__tests__/resolverBase.recordChangesFls.test.ts +358 -0
  110. package/src/__tests__/resolverBase.refusalExtensions.test.ts +101 -0
  111. package/src/__tests__/resolverBase.refusalMessage.test.ts +88 -0
  112. package/src/__tests__/serverExtensionReservedRoots.test.ts +79 -11
  113. package/src/__tests__/visitorIdentity.resolve.test.ts +61 -0
  114. package/src/agentSessions/SessionJanitor.ts +2 -2
  115. package/src/auth/exampleNewUserSubClass.ts +9 -6
  116. package/src/auth/magicLink/MagicLinkService.ts +2 -8
  117. package/src/auth/newUsers.ts +7 -15
  118. package/src/auth/principals.ts +257 -0
  119. package/src/config.ts +44 -4
  120. package/src/entitySubclasses/MJEntityPermissionEntityServer.server.ts +65 -6
  121. package/src/generated/graphql-schemas/__mj.ts +18966 -16452
  122. package/src/generic/ResolverBase.ts +467 -9
  123. package/src/generic/RunViewResolver.ts +28 -2
  124. package/src/generic/refusalExtensions.ts +59 -0
  125. package/src/index.ts +154 -102
  126. package/src/realtimeWidget/WidgetSessionService.ts +2 -14
  127. package/src/realtimeWidget/visitorIdentity.ts +21 -6
  128. package/src/resolvers/EntityRecordNameResolver.ts +74 -8
  129. package/src/resolvers/FileCategoryResolver.ts +2 -3
  130. package/src/resolvers/IntegrationDiscoveryResolver.ts +2 -0
  131. package/src/resolvers/QuerySystemUserResolver.ts +1 -1
  132. package/src/resolvers/RealtimeClientSessionResolver.ts +128 -2
  133. package/src/resolvers/RemoteBrowserActionResolver.ts +47 -8
  134. package/src/resolvers/SyncRolesUsersResolver.ts +11 -2
  135. package/src/resolvers/UserViewResolver.ts +8 -4
  136. package/src/rest/EntityCRUDHandler.ts +3 -19
  137. package/src/rest/RESTEndpointHandler.ts +3 -17
  138. package/src/rest/RealtimeSdpBrokerHandler.ts +101 -0
  139. package/src/serverExtensionReservedRoots.ts +2 -3
  140. package/dist/resolvers/RingCentralTelephonyResolver.d.ts +0 -27
  141. package/dist/resolvers/RingCentralTelephonyResolver.d.ts.map +0 -1
  142. package/dist/resolvers/RingCentralTelephonyResolver.js +0 -87
  143. package/dist/resolvers/RingCentralTelephonyResolver.js.map +0 -1
  144. package/dist/resolvers/TeamsMeetingsResolver.d.ts +0 -28
  145. package/dist/resolvers/TeamsMeetingsResolver.d.ts.map +0 -1
  146. package/dist/resolvers/TeamsMeetingsResolver.js +0 -91
  147. package/dist/resolvers/TeamsMeetingsResolver.js.map +0 -1
  148. package/dist/resolvers/TelephonyResolver.d.ts +0 -26
  149. package/dist/resolvers/TelephonyResolver.d.ts.map +0 -1
  150. package/dist/resolvers/TelephonyResolver.js +0 -86
  151. package/dist/resolvers/TelephonyResolver.js.map +0 -1
  152. package/dist/resolvers/VonageTelephonyResolver.d.ts +0 -26
  153. package/dist/resolvers/VonageTelephonyResolver.d.ts.map +0 -1
  154. package/dist/resolvers/VonageTelephonyResolver.js +0 -86
  155. package/dist/resolvers/VonageTelephonyResolver.js.map +0 -1
  156. package/dist/telephony/RingCentralTelephonyService.d.ts +0 -115
  157. package/dist/telephony/RingCentralTelephonyService.d.ts.map +0 -1
  158. package/dist/telephony/RingCentralTelephonyService.js +0 -262
  159. package/dist/telephony/RingCentralTelephonyService.js.map +0 -1
  160. package/dist/telephony/TeamsMeetingsRouter.d.ts +0 -40
  161. package/dist/telephony/TeamsMeetingsRouter.d.ts.map +0 -1
  162. package/dist/telephony/TeamsMeetingsRouter.js +0 -96
  163. package/dist/telephony/TeamsMeetingsRouter.js.map +0 -1
  164. package/dist/telephony/TeamsMeetingsService.d.ts +0 -113
  165. package/dist/telephony/TeamsMeetingsService.d.ts.map +0 -1
  166. package/dist/telephony/TeamsMeetingsService.js +0 -196
  167. package/dist/telephony/TeamsMeetingsService.js.map +0 -1
  168. package/dist/telephony/TwilioTelephonyRouter.d.ts +0 -36
  169. package/dist/telephony/TwilioTelephonyRouter.d.ts.map +0 -1
  170. package/dist/telephony/TwilioTelephonyRouter.js +0 -143
  171. package/dist/telephony/TwilioTelephonyRouter.js.map +0 -1
  172. package/dist/telephony/TwilioTelephonyService.d.ts +0 -95
  173. package/dist/telephony/TwilioTelephonyService.d.ts.map +0 -1
  174. package/dist/telephony/TwilioTelephonyService.js +0 -193
  175. package/dist/telephony/TwilioTelephonyService.js.map +0 -1
  176. package/dist/telephony/VonageTelephonyRouter.d.ts +0 -41
  177. package/dist/telephony/VonageTelephonyRouter.d.ts.map +0 -1
  178. package/dist/telephony/VonageTelephonyRouter.js +0 -201
  179. package/dist/telephony/VonageTelephonyRouter.js.map +0 -1
  180. package/dist/telephony/VonageTelephonyService.d.ts +0 -90
  181. package/dist/telephony/VonageTelephonyService.d.ts.map +0 -1
  182. package/dist/telephony/VonageTelephonyService.js +0 -191
  183. package/dist/telephony/VonageTelephonyService.js.map +0 -1
  184. package/dist/telephony/calendar-scheduler.d.ts +0 -67
  185. package/dist/telephony/calendar-scheduler.d.ts.map +0 -1
  186. package/dist/telephony/calendar-scheduler.js +0 -133
  187. package/dist/telephony/calendar-scheduler.js.map +0 -1
  188. package/dist/telephony/index.d.ts +0 -29
  189. package/dist/telephony/index.d.ts.map +0 -1
  190. package/dist/telephony/index.js +0 -38
  191. package/dist/telephony/index.js.map +0 -1
  192. package/dist/telephony/media-upgrade-router.d.ts +0 -47
  193. package/dist/telephony/media-upgrade-router.d.ts.map +0 -1
  194. package/dist/telephony/media-upgrade-router.js +0 -72
  195. package/dist/telephony/media-upgrade-router.js.map +0 -1
  196. package/dist/telephony/ringcentral-runtime.d.ts +0 -14
  197. package/dist/telephony/ringcentral-runtime.d.ts.map +0 -1
  198. package/dist/telephony/ringcentral-runtime.js +0 -18
  199. package/dist/telephony/ringcentral-runtime.js.map +0 -1
  200. package/dist/telephony/teams-meetings-runtime.d.ts +0 -16
  201. package/dist/telephony/teams-meetings-runtime.d.ts.map +0 -1
  202. package/dist/telephony/teams-meetings-runtime.js +0 -20
  203. package/dist/telephony/teams-meetings-runtime.js.map +0 -1
  204. package/dist/telephony/teamsAcsMediaRegistry.d.ts +0 -69
  205. package/dist/telephony/teamsAcsMediaRegistry.d.ts.map +0 -1
  206. package/dist/telephony/teamsAcsMediaRegistry.js +0 -118
  207. package/dist/telephony/teamsAcsMediaRegistry.js.map +0 -1
  208. package/dist/telephony/telephony-runtime.d.ts +0 -14
  209. package/dist/telephony/telephony-runtime.d.ts.map +0 -1
  210. package/dist/telephony/telephony-runtime.js +0 -18
  211. package/dist/telephony/telephony-runtime.js.map +0 -1
  212. package/dist/telephony/twilioMediaRegistry.d.ts +0 -60
  213. package/dist/telephony/twilioMediaRegistry.d.ts.map +0 -1
  214. package/dist/telephony/twilioMediaRegistry.js +0 -106
  215. package/dist/telephony/twilioMediaRegistry.js.map +0 -1
  216. package/dist/telephony/vonage-runtime.d.ts +0 -14
  217. package/dist/telephony/vonage-runtime.d.ts.map +0 -1
  218. package/dist/telephony/vonage-runtime.js +0 -18
  219. package/dist/telephony/vonage-runtime.js.map +0 -1
  220. package/dist/telephony/vonageMediaRegistry.d.ts +0 -78
  221. package/dist/telephony/vonageMediaRegistry.d.ts.map +0 -1
  222. package/dist/telephony/vonageMediaRegistry.js +0 -158
  223. package/dist/telephony/vonageMediaRegistry.js.map +0 -1
  224. package/src/__tests__/media-upgrade-router.test.ts +0 -43
  225. package/src/__tests__/teams-meetings-service.test.ts +0 -65
  226. package/src/__tests__/teamsAcsMediaRegistry.test.ts +0 -82
  227. package/src/__tests__/twilio-telephony-service.test.ts +0 -23
  228. package/src/__tests__/twilioMediaRegistry.test.ts +0 -103
  229. package/src/__tests__/vonageMediaRegistry.test.ts +0 -180
  230. package/src/resolvers/RingCentralTelephonyResolver.ts +0 -64
  231. package/src/resolvers/TeamsMeetingsResolver.ts +0 -68
  232. package/src/resolvers/TelephonyResolver.ts +0 -63
  233. package/src/resolvers/VonageTelephonyResolver.ts +0 -63
  234. package/src/telephony/RingCentralTelephonyService.ts +0 -342
  235. package/src/telephony/TeamsMeetingsRouter.ts +0 -124
  236. package/src/telephony/TeamsMeetingsService.ts +0 -268
  237. package/src/telephony/TwilioTelephonyRouter.ts +0 -184
  238. package/src/telephony/TwilioTelephonyService.ts +0 -261
  239. package/src/telephony/VonageTelephonyRouter.ts +0 -239
  240. package/src/telephony/VonageTelephonyService.ts +0 -259
  241. package/src/telephony/calendar-scheduler.ts +0 -176
  242. package/src/telephony/index.ts +0 -43
  243. package/src/telephony/media-upgrade-router.ts +0 -79
  244. package/src/telephony/ringcentral-runtime.ts +0 -22
  245. package/src/telephony/teams-meetings-runtime.ts +0 -24
  246. package/src/telephony/teamsAcsMediaRegistry.ts +0 -152
  247. package/src/telephony/telephony-runtime.ts +0 -22
  248. package/src/telephony/twilioMediaRegistry.ts +0 -137
  249. package/src/telephony/vonage-runtime.ts +0 -22
  250. package/src/telephony/vonageMediaRegistry.ts +0 -200
@@ -3,6 +3,7 @@ import { CompositeKey, DatabaseProviderBase, EntityInfo, LocalCacheManager, Meta
3
3
  import { GetReadOnlyProvider, GetReadWriteProvider } from "../util.js";
4
4
  import { NoLog } from "../logging/NoLog.js";
5
5
  import { UUIDsEqual } from "@memberjunction/global";
6
+ import { DrainResponseBody } from "@memberjunction/network-utils";
6
7
  import { CronExpressionHelper } from "@memberjunction/scheduling-engine";
7
8
  import {
8
9
  MJCompanyIntegrationEntity,
@@ -6472,6 +6473,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
6472
6473
  body: JSON.stringify(payload)
6473
6474
  });
6474
6475
  if (!response.ok) {
6476
+ await DrainResponseBody(response);
6475
6477
  console.error(`[Integration] Webhook POST to ${url} returned ${response.status}`);
6476
6478
  }
6477
6479
  } catch (e) {
@@ -590,7 +590,7 @@ export class MJQueryResolverExtended extends MJQueryResolver {
590
590
  }
591
591
 
592
592
  const provider = GetReadWriteProvider(context.providers);
593
- const key = new CompositeKey([{FieldName: 'ID', Value: ID}]);
593
+ const key = CompositeKey.FromID(ID); // first-pk-ok: deletes from MJ: Queries (core entity keyed by ID) — see DeleteRecord below
594
594
 
595
595
  // Provide default options if none provided
596
596
  const deleteOptions = options || {
@@ -49,6 +49,7 @@ import {
49
49
  ParseRealtimeTypeConfiguration,
50
50
  ResolveEffectiveRealtimeConfig,
51
51
  RealtimeAllowedAgent,
52
+ RealtimeDirectActionsConfig,
52
53
  REALTIME_ADVANCED_SESSION_CONTROLS_AUTHORIZATION,
53
54
  resolveRecordingStorageAccountID,
54
55
  storeRealtimeRecording,
@@ -181,6 +182,11 @@ interface RealtimeSessionConfig {
181
182
  * model-named colleague against it without re-resolving the cascade. Absent ⇒ single-target behavior.
182
183
  */
183
184
  allowedAgents?: RealtimeAllowedAgent[];
185
+ /**
186
+ * The session's effective direct actions configuration (derived from the full config cascade at start).
187
+ * Persisted so each relayed direct action call enforces against the exact same config used for projection.
188
+ */
189
+ directActions?: RealtimeDirectActionsConfig;
184
190
  /**
185
191
  * Per-session override for the agent's media kit — a `MJ: Collections` id that takes precedence
186
192
  * over the agent's `DefaultMediaCollectionID` when the server-side `MediaChannelServer` resolves
@@ -525,6 +531,10 @@ export class RealtimeClientSessionResolver extends ResolverBase {
525
531
  'under the system user (scoped-anonymous caller).',
526
532
  );
527
533
  }
534
+ LogStatus(
535
+ `ExecuteRealtimeSessionTool: dispatching relayed tool '${toolName}' (callId: ${callId}) for session ${agentSessionId}...`,
536
+ );
537
+ const startTime = Date.now();
528
538
  const { ResultJson, PausedRunID, Artifacts } = await this.clientSessionService.ExecuteRelayedTool(
529
539
  {
530
540
  AgentSessionID: agentSessionId,
@@ -532,6 +542,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
532
542
  // Multi-target (Move 4): the session's persisted allowed-agent union — a model-named
533
543
  // colleague in the call is validated against this; absent ⇒ single-target behavior.
534
544
  AllowedAgents: await this.filterAllowedAgentsByCanRun(config.allowedAgents, contextUser),
545
+ DirectActions: config.directActions,
535
546
  // Attribution follows the VISITOR even when `runUser` is elevated: the delegated run
536
547
  // row and its context-memory scope must stay the person's, not the system user's.
537
548
  AttributionUserID: contextUser.ID,
@@ -545,6 +556,23 @@ export class RealtimeClientSessionResolver extends ResolverBase {
545
556
  runUser,
546
557
  provider,
547
558
  );
559
+ const durationMs = Date.now() - startTime;
560
+ LogStatus(
561
+ `ExecuteRealtimeSessionTool: completed relayed tool '${toolName}' (callId: ${callId}) in ${durationMs}ms`,
562
+ );
563
+
564
+ if (toolName !== 'invoke-target-agent') {
565
+ await this.persistDirectActionTurn(
566
+ session,
567
+ callId,
568
+ toolName,
569
+ argsJson,
570
+ ResultJson,
571
+ durationMs,
572
+ contextUser,
573
+ provider,
574
+ );
575
+ }
548
576
 
549
577
  // Roll the paused-run id forward in the session config: clear the one we just consumed, and
550
578
  // store a new one only if the (resumed or fresh) run paused again awaiting feedback.
@@ -832,8 +860,9 @@ export class RealtimeClientSessionResolver extends ResolverBase {
832
860
 
833
861
  /**
834
862
  * Relays a co-agent CHANNEL tool-call (browser_ / Whiteboard_ etc.) onto the session's co-agent
835
- * AIPromptRun.Messages — run-only observability so the run captures what the co-agent DID, not just
836
- * what it said. Deliberately NOT a ConversationDetail turn (the chat thread stays speech-only).
863
+ * AIPromptRun.Messages and records the tool execution facts as a hidden ConversationDetail turn.
864
+ * HiddenToUser=true ensures chat components stay speech-only while allowing session review to
865
+ * reconstruct action cards dynamically on load (Option 1).
837
866
  * Ownership-gated; best-effort (a missing prompt run / save failure simply returns false).
838
867
  *
839
868
  * @returns `true` when the tool turn was recorded on the run.
@@ -848,6 +877,20 @@ export class RealtimeClientSessionResolver extends ResolverBase {
848
877
  ): Promise<boolean> {
849
878
  const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
850
879
  const session = await this.loadOwnedActiveSession(agentSessionId, contextUser, provider);
880
+
881
+ // Persist the tool turn facts into a hidden ConversationDetail turn so session review
882
+ // can reconstruct the tool card dynamically on load (Option 1). Best-effort.
883
+ await this.persistDirectActionTurn(
884
+ session,
885
+ undefined,
886
+ toolName,
887
+ argsJson,
888
+ resultJson,
889
+ undefined,
890
+ contextUser,
891
+ provider,
892
+ );
893
+
851
894
  const promptRunID = this.readPromptRunID(session);
852
895
  if (!promptRunID) {
853
896
  return false;
@@ -1701,6 +1744,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
1701
1744
  await this.persistObservabilityRunIDs(
1702
1745
  session, targetAgentId, prep.CoAgentRunID, prep.PromptRunID, prep.CoAgentRunStepID,
1703
1746
  applicationId, prep.EffectiveConfig?.realtime?.allowedAgents,
1747
+ prep.EffectiveConfig?.realtime?.directActions,
1704
1748
  );
1705
1749
 
1706
1750
  const cfg = prep.ClientConfig;
@@ -1734,6 +1778,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
1734
1778
  coAgentRunStepID?: string,
1735
1779
  applicationID?: string,
1736
1780
  allowedAgents?: RealtimeAllowedAgent[],
1781
+ directActions?: RealtimeDirectActionsConfig,
1737
1782
  ): Promise<void> {
1738
1783
  // Preserve any server-authoritative voice deadline stamped at session start (this rebuilds the
1739
1784
  // full config, so read the existing value forward rather than dropping it).
@@ -1746,6 +1791,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
1746
1791
  maxSessionDeadlineIso: existing?.maxSessionDeadlineIso,
1747
1792
  applicationID,
1748
1793
  allowedAgents: allowedAgents && allowedAgents.length > 0 ? allowedAgents : undefined,
1794
+ directActions,
1749
1795
  };
1750
1796
  session.Config_ = JSON.stringify(config);
1751
1797
  const saved = await session.Save();
@@ -2476,6 +2522,10 @@ export class RealtimeClientSessionResolver extends ResolverBase {
2476
2522
  typeof parsed.maxSessionDeadlineIso === 'string' ? parsed.maxSessionDeadlineIso : undefined,
2477
2523
  applicationID: typeof parsed.applicationID === 'string' ? parsed.applicationID : undefined,
2478
2524
  allowedAgents: Array.isArray(parsed.allowedAgents) ? parsed.allowedAgents : undefined,
2525
+ directActions:
2526
+ parsed.directActions && typeof parsed.directActions === 'object' && typeof parsed.directActions.enabled === 'boolean'
2527
+ ? (parsed.directActions as RealtimeDirectActionsConfig)
2528
+ : undefined,
2479
2529
  };
2480
2530
  }
2481
2531
  } catch {
@@ -2686,4 +2736,80 @@ export class RealtimeClientSessionResolver extends ResolverBase {
2686
2736
  private mapTranscriptRole(role: string): 'AI' | 'Error' | 'User' {
2687
2737
  return role.trim().toLowerCase() === 'user' ? 'User' : 'AI';
2688
2738
  }
2739
+
2740
+ /**
2741
+ * Persists a direct action or channel tool execution as a hidden ConversationDetail turn.
2742
+ * Stored as stable facts (Role: 'AI', HiddenToUser: true, ExternalID: callId, CompletionTime: durationMs,
2743
+ * Message: JSON-serialized tool facts) so that session review can reconstruct the action card dynamically
2744
+ * on load (Option 1) without polluting user-facing speech chat bubbles.
2745
+ */
2746
+ private async persistDirectActionTurn(
2747
+ session: MJAIAgentSessionEntity,
2748
+ callId: string | undefined,
2749
+ toolName: string,
2750
+ argsJson: string | undefined | null,
2751
+ resultJson: string | undefined | null,
2752
+ durationMs: number | undefined,
2753
+ contextUser: UserInfo,
2754
+ provider: IMetadataProvider,
2755
+ ): Promise<boolean> {
2756
+ if (!session.ConversationID) {
2757
+ return false;
2758
+ }
2759
+ try {
2760
+ const writeUser = ResolveScopedAnonymousRunUser(contextUser);
2761
+ const detail = await provider.GetEntityObject<MJConversationDetailEntity>(
2762
+ CONVERSATION_DETAIL_ENTITY,
2763
+ writeUser,
2764
+ );
2765
+ detail.NewRecord();
2766
+ detail.ConversationID = session.ConversationID;
2767
+ detail.Role = 'AI';
2768
+ detail.HiddenToUser = true;
2769
+ detail.AgentSessionID = session.ID;
2770
+ detail.UserID = contextUser.ID;
2771
+ if (callId) {
2772
+ detail.ExternalID = callId;
2773
+ }
2774
+ if (typeof durationMs === 'number' && durationMs >= 0) {
2775
+ detail.CompletionTime = durationMs;
2776
+ }
2777
+
2778
+ let success = true;
2779
+ if (resultJson) {
2780
+ try {
2781
+ const parsed = JSON.parse(resultJson) as { success?: boolean; error?: string };
2782
+ if (parsed.success === false) {
2783
+ success = false;
2784
+ }
2785
+ } catch {
2786
+ // Plain-text output treated as success
2787
+ }
2788
+ }
2789
+ detail.Status = success ? 'Complete' : 'Error';
2790
+
2791
+ const factPayload = {
2792
+ type: 'realtime_tool_execution',
2793
+ callId: callId ?? null,
2794
+ toolName,
2795
+ argsJson: argsJson ?? null,
2796
+ resultJson: resultJson ?? null,
2797
+ success,
2798
+ durationMs: durationMs ?? 0,
2799
+ };
2800
+ detail.Message = JSON.stringify(factPayload);
2801
+
2802
+ const saved = await detail.Save();
2803
+ if (!saved) {
2804
+ LogError(
2805
+ `RealtimeClientSessionResolver.persistDirectActionTurn save failed: ${detail.LatestResult?.CompleteMessage ?? 'unknown error'}`,
2806
+ );
2807
+ }
2808
+ return saved;
2809
+ } catch (error) {
2810
+ const message = error instanceof Error ? error.message : String(error);
2811
+ LogError(`RealtimeClientSessionResolver.persistDirectActionTurn unexpected error: ${message}`);
2812
+ return false;
2813
+ }
2814
+ }
2689
2815
  }
@@ -230,6 +230,20 @@ interface VisualInterpreterPayload {
230
230
  * (the process-wide singleton) backs every request; ownership is enforced per call against the session's
231
231
  * `UserID`.
232
232
  */
233
+ /**
234
+ * Safety cap on how long a {@link RemoteBrowserActionResolver.startedScreencasts} /
235
+ * {@link RemoteBrowserActionResolver.startedAudioStreams} entry is trusted without confirmation.
236
+ *
237
+ * `RemoteBrowserActionResolver` is instantiated once per process by type-graphql's default resolver
238
+ * container, so these idempotency maps live as long as the server does. `StopRemoteBrowserScreencast` /
239
+ * `StopRemoteBrowserAudioStream` are the only code paths that ever delete an entry — a session that
240
+ * crashes, disconnects, or times out before calling Stop (a normal occurrence: tab close, network drop,
241
+ * agent timeout) leaves its entry behind forever. This TTL bounds that growth the same way
242
+ * {@link RemoteBrowserGoalRegistry}'s own sweep bounds its run records, without requiring a dedicated
243
+ * session-end hook that doesn't otherwise exist for this resolver.
244
+ */
245
+ const MAX_STREAM_ENTRY_AGE_MS = 4 * 60 * 60 * 1000; // 4 hours — generous vs. SessionJanitor's 15-minute idle threshold
246
+
233
247
  @Resolver()
234
248
  export class RemoteBrowserActionResolver extends ResolverBase {
235
249
  /**
@@ -237,20 +251,43 @@ export class RemoteBrowserActionResolver extends ResolverBase {
237
251
  * {@link RemoteBrowserActionResolver.streamKey} (agent session **plus** instance) so a re-issued
238
252
  * {@link RemoteBrowserActionResolver.StartRemoteBrowserScreencast} — the surface re-binding after a
239
253
  * tab collapse — is idempotent and never stacks two screencasts on the one browser. Entries are
240
- * removed by {@link RemoteBrowserActionResolver.StopRemoteBrowserScreencast}.
254
+ * normally removed by {@link RemoteBrowserActionResolver.StopRemoteBrowserScreencast}; the value is
255
+ * the entry's start time (ms epoch) so {@link RemoteBrowserActionResolver.sweepStreamEntries} can
256
+ * reclaim ones whose session never called Stop.
241
257
  *
242
258
  * Keyed by agent session ALONE (#3531) this was the idempotency guard turning into a lockout: the
243
259
  * first surface's entry made the SECOND surface's bind a silent `Streaming: true` no-op, so a second
244
260
  * browser could be started and driven but never watched.
245
261
  */
246
- private startedScreencasts = new Set<string>();
262
+ private startedScreencasts = new Map<string, number>();
247
263
 
248
264
  /**
249
265
  * Surfaces whose live tab-audio stream this resolver has already started, keyed by
250
- * {@link RemoteBrowserActionResolver.streamKey} for the same reason the screencast set is. Entries
251
- * are removed by {@link RemoteBrowserActionResolver.StopRemoteBrowserAudioStream}.
266
+ * {@link RemoteBrowserActionResolver.streamKey} for the same reason the screencast map is. Entries
267
+ * are normally removed by {@link RemoteBrowserActionResolver.StopRemoteBrowserAudioStream}; see
268
+ * {@link RemoteBrowserActionResolver.startedScreencasts} for why the value is a timestamp.
269
+ */
270
+ private startedAudioStreams = new Map<string, number>();
271
+
272
+ /**
273
+ * Drops any {@link startedScreencasts} / {@link startedAudioStreams} entry older than
274
+ * {@link MAX_STREAM_ENTRY_AGE_MS} — the reclaim path for sessions that never called
275
+ * `StopRemoteBrowserScreencast`/`StopRemoteBrowserAudioStream` (crash, disconnect, timeout). Called
276
+ * on every Start mutation, mirroring {@link RemoteBrowserGoalRegistry.Begin}'s sweep-on-access pattern.
252
277
  */
253
- private startedAudioStreams = new Set<string>();
278
+ private sweepStreamEntries(): void {
279
+ const cutoff = Date.now() - MAX_STREAM_ENTRY_AGE_MS;
280
+ for (const [key, startedAt] of this.startedScreencasts) {
281
+ if (startedAt < cutoff) {
282
+ this.startedScreencasts.delete(key);
283
+ }
284
+ }
285
+ for (const [key, startedAt] of this.startedAudioStreams) {
286
+ if (startedAt < cutoff) {
287
+ this.startedAudioStreams.delete(key);
288
+ }
289
+ }
290
+ }
254
291
 
255
292
  /**
256
293
  * Execute ONE browser action relayed from the client-direct realtime session, returning the outcome +
@@ -569,6 +606,7 @@ export class RemoteBrowserActionResolver extends ResolverBase {
569
606
 
570
607
  // Idempotent PER SURFACE: a re-bind must not stack a second screencast on the one live browser,
571
608
  // but a SECOND surface binding for the first time is not a re-bind (#3531).
609
+ this.sweepStreamEntries();
572
610
  const streamKey = this.streamKey(agentSessionID, instanceKey);
573
611
  if (this.startedScreencasts.has(streamKey)) {
574
612
  return { Streaming: true };
@@ -580,7 +618,7 @@ export class RemoteBrowserActionResolver extends ResolverBase {
580
618
  await liveSession.StartScreencast((frame) =>
581
619
  this.publishFrame(pubSub, userPayload, agentSessionID, instanceKey, frame, liveSession.GetCurrentUrl()),
582
620
  );
583
- this.startedScreencasts.add(streamKey);
621
+ this.startedScreencasts.set(streamKey, Date.now());
584
622
  return { Streaming: true };
585
623
  } catch (err) {
586
624
  if (err instanceof RemoteBrowserCapabilityNotSupportedError) {
@@ -647,7 +685,8 @@ export class RemoteBrowserActionResolver extends ResolverBase {
647
685
  const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
648
686
  const session = await this.loadOwnedSession(agentSessionID, contextUser, provider);
649
687
 
650
- // Idempotent PER SURFACE — same reasoning as the screencast set (#3531).
688
+ // Idempotent PER SURFACE — same reasoning as the screencast map (#3531).
689
+ this.sweepStreamEntries();
651
690
  const streamKey = this.streamKey(agentSessionID, instanceKey);
652
691
  if (this.startedAudioStreams.has(streamKey)) {
653
692
  return { Streaming: true };
@@ -657,7 +696,7 @@ export class RemoteBrowserActionResolver extends ResolverBase {
657
696
  try {
658
697
  const liveSession = await RemoteBrowserEngine.Instance.StartSessionForAgentSession(agentSessionID, contextUser, providerName, instanceKey);
659
698
  await liveSession.StartAudioStream((chunk) => this.publishAudioChunk(pubSub, userPayload, agentSessionID, instanceKey, chunk));
660
- this.startedAudioStreams.add(streamKey);
699
+ this.startedAudioStreams.set(streamKey, Date.now());
661
700
  return { Streaming: true };
662
701
  } catch (err) {
663
702
  if (err instanceof RemoteBrowserCapabilityNotSupportedError) {
@@ -385,8 +385,17 @@ export class SyncRolesAndUsersResolver {
385
385
  if (await user.Delete()) {
386
386
  return;
387
387
  }
388
- // FK constraint fallback: when the user has dependent records that block hard delete,
389
- // we mark the user inactive instead. A failure to soft-delete IS a real error and rolls back.
388
+ // Fallback when the hard delete does not happen. TWO distinct causes reach here, and this
389
+ // path deliberately treats them the same way:
390
+ // 1. FK constraints — the user has dependent records that block a hard delete.
391
+ // 2. Authorization — `MJUserEntityServer` (issue #4260) refuses a delete outright for any
392
+ // caller whose `Type` is not 'Owner'. This mutation carries `@RequireSystemUser()` and
393
+ // `getSystemUser()` resolves the seeded Owner, so cause 2 does not arise on a default
394
+ // install; it does on a deployment whose system user is not an Owner.
395
+ // Soft-deleting is the right response to (1) and an acceptable one to (2) — but note the
396
+ // subsequent Save() is subject to the SAME guard, so under cause 2 it fails too and the
397
+ // error below is what the operator sees. Read `LatestResult` rather than assuming FK.
398
+ // A failure to soft-delete IS a real error and rolls back.
390
399
  user.IsActive = false;
391
400
  if (!await user.Save()) {
392
401
  throw new Error(`Failed to delete or deactivate user '${user.Email}': ${user.LatestResult?.CompleteMessage ?? 'unknown error'}`);
@@ -4,7 +4,7 @@ import { AppContext, Arg, Ctx, Int, Query, Resolver, UserPayload } from '@member
4
4
  import { MJUserView_, MJUserViewResolverBase } from '../generated/generated.js';
5
5
  import { UserResolver } from './UserResolver.js';
6
6
  import { MJUserViewEntity, MJUserViewEntityExtended } from '@memberjunction/core-entities';
7
- import { GetReadOnlyProvider } from '../util.js';
7
+ import { GetReadOnlyProvider, GetReadWriteProvider } from '../util.js';
8
8
 
9
9
  @Resolver(MJUserView_)
10
10
  export class UserViewResolver extends MJUserViewResolverBase {
@@ -52,11 +52,15 @@ export class UserViewResolver extends MJUserViewResolverBase {
52
52
  // this should normally not be a factor but we have this exposed in the GraphQL API so that
53
53
  // a dev can force the update if desired from the client. The normal path is just to update
54
54
  // filter state which in turn will be used to update the where clause in the entity sub-class.
55
- const p = GetReadOnlyProvider(providers, {allowFallbackToReadWrite: true});
55
+ // This endpoint WRITES (Save below), so it must use the read-write provider — on a read-replica
56
+ // deployment the read-only provider would route the save to the replica.
57
+ const p = GetReadWriteProvider(providers, {allowFallbackToReadOnly: true});
56
58
  const u = this.GetUserFromPayload(userPayload);
57
59
  const viewEntity = <MJUserViewEntityExtended>await p.GetEntityObject('MJ: User Views', u);
58
- await viewEntity.Load(ID);
59
- viewEntity.UpdateWhereClause();
60
+ if (!await viewEntity.Load(ID)) {
61
+ throw new Error(`User View ${ID} not found or access denied`);
62
+ }
63
+ await viewEntity.UpdateWhereClause(true); // force regeneration regardless of dirty state — that is the whole point of this endpoint
60
64
 
61
65
  if (await viewEntity.Save()) {
62
66
  return viewEntity.GetAll();
@@ -254,26 +254,10 @@ export class EntityCRUDHandler {
254
254
  }
255
255
 
256
256
  /**
257
- * Helper method to create a composite key from an ID
257
+ * Build the record key from the `:id` path segment. A single-column key (any column name) is
258
+ * the bare value; a composite key is the URL-encoded `Field1|Value1||Field2|Value2` segment.
258
259
  */
259
260
  private static createCompositeKeyFromId(entity: BaseEntity, id: string | number): CompositeKey {
260
- if (entity.EntityInfo.PrimaryKeys.length === 1) {
261
- // Single primary key
262
- const primaryKeyField = entity.EntityInfo.PrimaryKeys[0].Name;
263
- const compositeKey = new CompositeKey();
264
- const strId = id.toString();
265
-
266
- // Use key-value pairs instead of SetValue
267
- compositeKey.KeyValuePairs = [
268
- { FieldName: primaryKeyField, Value: strId }
269
- ];
270
-
271
- return compositeKey;
272
- } else {
273
- // Composite primary key - this is a simplification
274
- // In a real implementation, you would need to parse a composite ID string
275
- // or accept an object with all primary key values
276
- throw new Error('Composite primary keys are not supported in this simplified implementation');
277
- }
261
+ return CompositeKey.FromURLSegment(entity.EntityInfo, id.toString());
278
262
  }
279
263
  }
@@ -794,25 +794,11 @@ export class RESTEndpointHandler {
794
794
  }
795
795
 
796
796
  /**
797
- * Helper method to create a composite key from an ID
797
+ * Build the record key from the `:id` path segment. A single-column key (any column name) is
798
+ * the bare value; a composite key is the URL-encoded `Field1|Value1||Field2|Value2` segment.
798
799
  */
799
800
  private createCompositeKey(entityInfo: EntityInfo, id: string): CompositeKey {
800
- if (entityInfo.PrimaryKeys.length === 1) {
801
- // Single primary key
802
- const primaryKeyField = entityInfo.PrimaryKeys[0].Name;
803
- const compositeKey = new CompositeKey();
804
-
805
- // Use key-value pairs instead of SetValue
806
- compositeKey.KeyValuePairs = [
807
- { FieldName: primaryKeyField, Value: id }
808
- ];
809
-
810
- return compositeKey;
811
- } else {
812
- // Composite primary key
813
- // This is a simplification - in a real implementation, we would need to handle composite keys properly
814
- throw new Error('Composite primary keys are not supported in this implementation');
815
- }
801
+ return CompositeKey.FromURLSegment(entityInfo, id);
816
802
  }
817
803
 
818
804
  /**
@@ -0,0 +1,101 @@
1
+ import express, { type Router, type Request, type Response, type NextFunction } from 'express';
2
+ import { MJGlobal } from '@memberjunction/global';
3
+ import { BaseRealtimeModel, RealtimeProxyRegistry, type RealtimeProxyTicketEntry } from '@memberjunction/ai';
4
+ import { LogError, LogStatus } from '@memberjunction/core';
5
+
6
+ /**
7
+ * Middleware that extracts and consumes the realtime ticket from query parameters
8
+ * BEFORE any request body parsing occurs. This prevents anonymous/unauthorized callers
9
+ * from causing body-parser memory allocations.
10
+ */
11
+ export function requireTicket(req: Request, res: Response, next: NextFunction): void {
12
+ const ticketId = (req.query['ticket'] as string) || '';
13
+ if (!ticketId) {
14
+ res.status(401).json({ error: 'Unauthorized: missing ticket' });
15
+ return;
16
+ }
17
+
18
+ const entry = RealtimeProxyRegistry.Instance.Consume(ticketId);
19
+ if (!entry) {
20
+ res.status(401).json({ error: 'Unauthorized: invalid or expired ticket' });
21
+ return;
22
+ }
23
+
24
+ res.locals.ticketEntry = entry;
25
+ res.locals.ticketId = ticketId;
26
+ next();
27
+ }
28
+
29
+ /**
30
+ * Builds the Express router handling the Realtime WebRTC SDP exchange:
31
+ * `POST /realtime/sdp-exchange?ticket=<ticketId>`
32
+ *
33
+ * In the Realtime WebRTC topology (e.g. OpenAI Live), the browser never contacts the upstream
34
+ * API directly and holds no project API key. Instead, the browser sends its offer SDP here using a
35
+ * short-lived ticket minted by the provider driver. This broker validates and consumes the ticket
36
+ * BEFORE body parsing, delegates to `driver.ExchangeWebRtcSdp` to exchange the SDP offer for an
37
+ * answer with upstream using the server's API key, and returns the answer SDP.
38
+ */
39
+ export function createRealtimeSdpBrokerRouter(): Router {
40
+ const router = express.Router();
41
+
42
+ router.post(
43
+ '/',
44
+ requireTicket,
45
+ express.json({ limit: '2mb' }),
46
+ async (req: Request, res: Response): Promise<void> => {
47
+ const entry = res.locals.ticketEntry as RealtimeProxyTicketEntry;
48
+ const ticketId = (res.locals.ticketId as string) || '';
49
+
50
+ LogStatus(
51
+ `RealtimeSdpBrokerHandler: processing ticket '${ticketId}' for user '${entry.UserID ?? 'anonymous'}', driver '${entry.DriverClass ?? 'OpenAILiveRealtime'}'`
52
+ );
53
+
54
+ const body = req.body as { sdp?: string } | undefined;
55
+ const sdp = body?.sdp;
56
+ if (!sdp || typeof sdp !== 'string') {
57
+ res.status(400).json({ error: 'Missing or invalid sdp in request body' });
58
+ return;
59
+ }
60
+
61
+ const apiKey = entry.UpstreamAuthHeader;
62
+ if (!apiKey) {
63
+ LogError(`RealtimeSdpBrokerHandler: ticket '${ticketId}' has no upstream auth credentials`);
64
+ res.status(500).json({ error: 'Server configuration error: missing upstream credentials' });
65
+ return;
66
+ }
67
+
68
+ try {
69
+ const driverClass = entry.DriverClass || 'OpenAILiveRealtime';
70
+ const driver = MJGlobal.Instance.ClassFactory.CreateInstance<BaseRealtimeModel>(
71
+ BaseRealtimeModel,
72
+ driverClass,
73
+ apiKey,
74
+ entry.UpstreamUrl
75
+ );
76
+
77
+ if (!driver || !driver.ExchangeWebRtcSdp) {
78
+ LogError(`RealtimeSdpBrokerHandler: driver '${driverClass}' does not support ExchangeWebRtcSdp`);
79
+ res.status(500).json({ error: `Driver '${driverClass}' does not support WebRTC SDP exchange` });
80
+ return;
81
+ }
82
+
83
+ LogStatus(`RealtimeSdpBrokerHandler: exchanging WebRTC SDP offer via ${driverClass}`);
84
+ // Use authoritative session config recorded on ticket entry at mint time (prevents client tampering)
85
+ const result = await driver.ExchangeWebRtcSdp(sdp, entry.SessionConfig);
86
+ res.status(200).json(result);
87
+ } catch (err: unknown) {
88
+ const message = err instanceof Error ? err.message : String(err);
89
+ LogError(`RealtimeSdpBrokerHandler failed for ticket '${ticketId}': ${message}`);
90
+ res.status(502).json({ error: 'Realtime WebRTC handshake failed' });
91
+ }
92
+ }
93
+ );
94
+
95
+ return router;
96
+ }
97
+
98
+ /**
99
+ * @deprecated Use `createRealtimeSdpBrokerRouter` instead.
100
+ */
101
+ export const createOpenAILiveBrokerRouter = createRealtimeSdpBrokerRouter;
@@ -20,6 +20,8 @@ export const CORE_STATIC_RESERVED_SERVER_EXTENSION_ROOTS: readonly string[] = [
20
20
  '/media',
21
21
  '/oauth',
22
22
  '/health',
23
+ '/realtime',
24
+ '/realtime-proxy',
23
25
  ];
24
26
 
25
27
  /**
@@ -30,9 +32,6 @@ export const CORE_CONSTANT_RESERVED_SERVER_EXTENSION_ROOTS: readonly string[] =
30
32
  '/auth', // AUTH_CATALOG_MOUNT_PATH
31
33
  '/magic-link', // MAGIC_LINK_MOUNT_PATH
32
34
  '/widget', // WIDGET_MOUNT_PATH
33
- '/telephony/twilio', // TWILIO_TELEPHONY_MOUNT_PATH
34
- '/telephony/vonage', // VONAGE_TELEPHONY_MOUNT_PATH
35
- '/meetings/teams', // TEAMS_MEETINGS_MOUNT_PATH
36
35
  ];
37
36
 
38
37
  /**
@@ -1,27 +0,0 @@
1
- /**
2
- * @fileoverview GraphQL surface for OUTBOUND RingCentral telephony: `PlaceRingCentralCall` rings a real
3
- * phone from an agent identity and connects the pinned agent. Thin — it authorizes the caller, then
4
- * delegates to the startup-constructed {@link RingCentralTelephonyService} (which owns the bridge
5
- * orchestration + the shared media registry). Inbound calls arrive via the public
6
- * `POST /telephony/ringcentral/webhook` notification, not GraphQL.
7
- *
8
- * @module @memberjunction/server
9
- */
10
- import { AppContext } from '../types.js';
11
- import { ResolverBase } from '../generic/ResolverBase.js';
12
- /** Result of an outbound RingCentral place-call attempt. */
13
- export declare class PlaceRingCentralCallResult {
14
- Success: boolean;
15
- ErrorMessage?: string;
16
- /** The placed RingCentral telephony session id (the bridge's external connection id). */
17
- SessionId: string;
18
- }
19
- export declare class RingCentralTelephonyResolver extends ResolverBase {
20
- /**
21
- * Places an outbound call from the given agent identity (its phone number is the caller-id) to a
22
- * destination number, connecting the pinned agent over the realtime bridge. Returns the telephony
23
- * session id.
24
- */
25
- PlaceRingCentralCall(agentIdentityId: string, toNumber: string, context?: AppContext): Promise<PlaceRingCentralCallResult>;
26
- }
27
- //# sourceMappingURL=RingCentralTelephonyResolver.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"RingCentralTelephonyResolver.d.ts","sourceRoot":"","sources":["../../src/resolvers/RingCentralTelephonyResolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAI1D,4DAA4D;AAC5D,qBACa,0BAA0B;IAEnC,OAAO,EAAE,OAAO,CAAC;IAGjB,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,yFAAyF;IAEzF,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,qBACa,4BAA6B,SAAQ,YAAY;IAC1D;;;;OAIG;IAEG,oBAAoB,CACgB,eAAe,EAAE,MAAM,EAC9B,QAAQ,EAAE,MAAM,EACxC,OAAO,GAAE,UAA6B,GAC9C,OAAO,CAAC,0BAA0B,CAAC;CAoBzC"}