@memberjunction/server 6.1.0-edge.1 → 6.1.0-edge.2

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 (136) hide show
  1. package/dist/auth/exampleNewUserSubClass.js +1 -1
  2. package/dist/auth/exampleNewUserSubClass.js.map +1 -1
  3. package/dist/auth/index.d.ts.map +1 -1
  4. package/dist/auth/index.js +9 -5
  5. package/dist/auth/index.js.map +1 -1
  6. package/dist/auth/magicLink/MagicLinkService.js +1 -1
  7. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  8. package/dist/auth/newUsers.js +1 -1
  9. package/dist/auth/newUsers.js.map +1 -1
  10. package/dist/context.js +2 -2
  11. package/dist/context.js.map +1 -1
  12. package/dist/generated/generated.d.ts +45 -4
  13. package/dist/generated/generated.d.ts.map +1 -1
  14. package/dist/generated/generated.js +229 -21
  15. package/dist/generated/generated.js.map +1 -1
  16. package/dist/generic/ResolverBase.d.ts +19 -3
  17. package/dist/generic/ResolverBase.d.ts.map +1 -1
  18. package/dist/generic/ResolverBase.js +51 -17
  19. package/dist/generic/ResolverBase.js.map +1 -1
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +85 -41
  23. package/dist/index.js.map +1 -1
  24. package/dist/logging/secretRedactor.d.ts +9 -0
  25. package/dist/logging/secretRedactor.d.ts.map +1 -1
  26. package/dist/logging/secretRedactor.js +39 -0
  27. package/dist/logging/secretRedactor.js.map +1 -1
  28. package/dist/middleware/BaseServerMiddleware.d.ts +5 -0
  29. package/dist/middleware/BaseServerMiddleware.d.ts.map +1 -1
  30. package/dist/middleware/BaseServerMiddleware.js +5 -0
  31. package/dist/middleware/BaseServerMiddleware.js.map +1 -1
  32. package/dist/realtimeWidget/WidgetSessionService.js +1 -1
  33. package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
  34. package/dist/realtimeWidget/widgetGuestElevation.js +1 -1
  35. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  36. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  37. package/dist/resolvers/ActionResolver.js +12 -0
  38. package/dist/resolvers/ActionResolver.js.map +1 -1
  39. package/dist/resolvers/ComponentRegistryResolver.js +1 -1
  40. package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
  41. package/dist/resolvers/EntityPermissionResolver.js +1 -1
  42. package/dist/resolvers/EntityPermissionResolver.js.map +1 -1
  43. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +9 -0
  44. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  45. package/dist/resolvers/IntegrationDiscoveryResolver.js +3 -1
  46. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  47. package/dist/resolvers/PotentialDuplicateRecordResolver.js +1 -1
  48. package/dist/resolvers/PotentialDuplicateRecordResolver.js.map +1 -1
  49. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +10 -0
  50. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  51. package/dist/resolvers/RealtimeClientSessionResolver.js +5 -0
  52. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  53. package/dist/resolvers/SearchEntitiesResolver.js +1 -1
  54. package/dist/resolvers/SearchEntitiesResolver.js.map +1 -1
  55. package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
  56. package/dist/resolvers/SqlLoggingConfigResolver.js +1 -1
  57. package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
  58. package/dist/resolvers/SyncRolesUsersResolver.js +2 -2
  59. package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
  60. package/dist/resolvers/UserFavoriteResolver.js +1 -1
  61. package/dist/resolvers/UserFavoriteResolver.js.map +1 -1
  62. package/dist/rest/OAuthCallbackHandler.js +1 -1
  63. package/dist/rest/OAuthCallbackHandler.js.map +1 -1
  64. package/dist/rest/SignatureWebhookHandler.js +1 -1
  65. package/dist/rest/SignatureWebhookHandler.js.map +1 -1
  66. package/dist/services/DurableEntityActionTaskSubmitter.d.ts +26 -0
  67. package/dist/services/DurableEntityActionTaskSubmitter.d.ts.map +1 -0
  68. package/dist/services/DurableEntityActionTaskSubmitter.js +55 -0
  69. package/dist/services/DurableEntityActionTaskSubmitter.js.map +1 -0
  70. package/dist/services/ScheduledJobsService.js +1 -1
  71. package/dist/services/ScheduledJobsService.js.map +1 -1
  72. package/dist/services/StartTaskGraphDispatcher.d.ts.map +1 -1
  73. package/dist/services/StartTaskGraphDispatcher.js +16 -1
  74. package/dist/services/StartTaskGraphDispatcher.js.map +1 -1
  75. package/dist/services/TaskGraphActionRunner.d.ts +34 -0
  76. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -0
  77. package/dist/services/TaskGraphActionRunner.js +95 -0
  78. package/dist/services/TaskGraphActionRunner.js.map +1 -0
  79. package/dist/services/TaskGraphAgentRunner.d.ts +13 -0
  80. package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
  81. package/dist/services/TaskGraphAgentRunner.js +30 -0
  82. package/dist/services/TaskGraphAgentRunner.js.map +1 -1
  83. package/dist/services/TaskGraphPromptRunner.d.ts +31 -0
  84. package/dist/services/TaskGraphPromptRunner.d.ts.map +1 -0
  85. package/dist/services/TaskGraphPromptRunner.js +134 -0
  86. package/dist/services/TaskGraphPromptRunner.js.map +1 -0
  87. package/dist/telephony/RingCentralTelephonyService.js +1 -1
  88. package/dist/telephony/RingCentralTelephonyService.js.map +1 -1
  89. package/dist/telephony/TwilioTelephonyRouter.js +1 -1
  90. package/dist/telephony/TwilioTelephonyRouter.js.map +1 -1
  91. package/dist/telephony/VonageTelephonyRouter.js +1 -1
  92. package/dist/telephony/VonageTelephonyRouter.js.map +1 -1
  93. package/package.json +90 -90
  94. package/src/__tests__/RealtimeClientSessionResolver.test.ts +1 -1
  95. package/src/__tests__/ResolverBase.frozenRecordMapping.test.ts +133 -0
  96. package/src/__tests__/ResolverBase.transportMapping.test.ts +289 -0
  97. package/src/__tests__/SessionManager.test.ts +1 -1
  98. package/src/__tests__/WidgetSessionService.test.ts +1 -1
  99. package/src/__tests__/databaseAbstraction.test.ts +1 -1
  100. package/src/__tests__/magicLinkService.provisionLog.test.ts +1 -1
  101. package/src/__tests__/resolverBase.rls.test.ts +3 -0
  102. package/src/__tests__/secretRedactor.failOpen.test.ts +135 -0
  103. package/src/__tests__/widgetGuestElevation.test.ts +1 -1
  104. package/src/auth/exampleNewUserSubClass.ts +1 -1
  105. package/src/auth/index.ts +10 -6
  106. package/src/auth/magicLink/MagicLinkService.ts +1 -1
  107. package/src/auth/newUsers.ts +1 -1
  108. package/src/context.ts +2 -2
  109. package/src/generated/generated.ts +166 -17
  110. package/src/generic/ResolverBase.ts +52 -17
  111. package/src/index.ts +92 -42
  112. package/src/logging/secretRedactor.ts +43 -0
  113. package/src/middleware/BaseServerMiddleware.ts +5 -0
  114. package/src/realtimeWidget/WidgetSessionService.ts +1 -1
  115. package/src/realtimeWidget/widgetGuestElevation.ts +1 -1
  116. package/src/resolvers/ActionResolver.ts +13 -0
  117. package/src/resolvers/ComponentRegistryResolver.ts +1 -1
  118. package/src/resolvers/EntityPermissionResolver.ts +1 -1
  119. package/src/resolvers/IntegrationDiscoveryResolver.ts +12 -2
  120. package/src/resolvers/PotentialDuplicateRecordResolver.ts +1 -1
  121. package/src/resolvers/RealtimeClientSessionResolver.ts +13 -0
  122. package/src/resolvers/SearchEntitiesResolver.ts +1 -1
  123. package/src/resolvers/SqlLoggingConfigResolver.ts +1 -2
  124. package/src/resolvers/SyncRolesUsersResolver.ts +2 -2
  125. package/src/resolvers/UserFavoriteResolver.ts +1 -1
  126. package/src/rest/OAuthCallbackHandler.ts +1 -1
  127. package/src/rest/SignatureWebhookHandler.ts +1 -1
  128. package/src/services/DurableEntityActionTaskSubmitter.ts +78 -0
  129. package/src/services/ScheduledJobsService.ts +1 -1
  130. package/src/services/StartTaskGraphDispatcher.ts +17 -0
  131. package/src/services/TaskGraphActionRunner.ts +99 -0
  132. package/src/services/TaskGraphAgentRunner.ts +35 -1
  133. package/src/services/TaskGraphPromptRunner.ts +144 -0
  134. package/src/telephony/RingCentralTelephonyService.ts +1 -1
  135. package/src/telephony/TwilioTelephonyRouter.ts +1 -1
  136. package/src/telephony/VonageTelephonyRouter.ts +1 -1
@@ -0,0 +1,99 @@
1
+ /**
2
+ * @fileoverview MJServer's implementation of the task-graph `TaskActionRunner` seam.
3
+ *
4
+ * The counterpart to {@link TaskGraphAgentRunner}: the dispatcher knows *when* an action-assigned
5
+ * node should run and nothing about how an action executes, so execution is injected. Keeping it
6
+ * here rather than in `@memberjunction/task-graph` is not tidiness — the action engine depends on
7
+ * the entity layer the dispatcher also builds on, and importing it there would make every consumer
8
+ * of durable execution load the action engine as well.
9
+ *
10
+ * @module @memberjunction/server
11
+ */
12
+ import { ActionEngineServer } from '@memberjunction/actions';
13
+ import { ActionParam } from '@memberjunction/actions-base';
14
+ import { LogError } from '@memberjunction/core';
15
+ import { UUIDsEqual } from '@memberjunction/global';
16
+ import type { TaskActionRunner, TaskActionRunParams, TaskActionRunResult } from '@memberjunction/task-graph';
17
+
18
+ export class TaskGraphActionRunner implements TaskActionRunner {
19
+ public async RunActionForTask(params: TaskActionRunParams): Promise<TaskActionRunResult> {
20
+ try {
21
+ await ActionEngineServer.Instance.Config(false, params.ContextUser);
22
+ const action = ActionEngineServer.Instance.Actions.find((a) => UUIDsEqual(a.ID, params.ActionID));
23
+ if (!action) {
24
+ return { Success: false, ErrorMessage: `Action ${params.ActionID} is not in the engine's metadata.` };
25
+ }
26
+
27
+ const result = await ActionEngineServer.Instance.RunAction({
28
+ Action: action,
29
+ ContextUser: params.ContextUser,
30
+ Params: this.buildParams(params),
31
+ // No Filters. A durable node is work that a filter ALREADY let through — the entity
32
+ // action's gate ran at dispatch time, on the change context that no longer exists by
33
+ // the time the dispatcher picks the task up. Re-evaluating here would ask a
34
+ // transition question with nothing to answer it, and fail closed on every retry.
35
+ Filters: [],
36
+ });
37
+
38
+ return {
39
+ Success: result.Success,
40
+ Output: this.buildOutput(result.Params),
41
+ ErrorMessage: result.Success ? undefined : result.Message,
42
+ // The engine already wrote the log; this is the only place its id is in hand. Without
43
+ // carrying it out, a workflow's action step has no path back to its own execution
44
+ // record — the one thing anyone wants when an action misbehaves.
45
+ ActionLogID: result.LogEntry?.ID,
46
+ };
47
+ } catch (e) {
48
+ const message = e instanceof Error ? e.message : String(e);
49
+ LogError(`[TaskGraphActionRunner] Task ${params.TaskID} failed: ${message}`);
50
+ return { Success: false, ErrorMessage: message };
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Projects the action's parameters onto the flat result a workflow step is expected to produce.
56
+ *
57
+ * **Why not return `result.Params` as-is.** That is an `ActionParam[]`, and every consumer of a
58
+ * step's output addresses fields BY NAME: an output mapping says `{"CurrentPrice": "stockPrice"}`,
59
+ * and a branch condition then reads `payload.stockPrice`. Handed an array, the mapping finds no
60
+ * `CurrentPrice`, writes nothing, and the condition evaluates against `undefined` — which is
61
+ * falsy rather than erroneous, so the workflow takes the other branch and reports success. That
62
+ * is exactly what the Demo workflow did: step 1 completed with an output payload of `{}` and
63
+ * every downstream branch was skipped, with nothing anywhere reporting a problem.
64
+ *
65
+ * Only `Output` and `Both` params are included. Echoing the inputs back would let a step's own
66
+ * parameters masquerade as its results, so an output mapping could "succeed" by reading the
67
+ * value the step was given rather than the one it produced.
68
+ */
69
+ private buildOutput(actionParams: ActionParam[]): Record<string, unknown> {
70
+ const output: Record<string, unknown> = {};
71
+ for (const p of actionParams ?? []) {
72
+ if (p.Type === 'Output' || p.Type === 'Both') output[p.Name] = p.Value;
73
+ }
74
+ return output;
75
+ }
76
+
77
+ /**
78
+ * Rebuilds the action's parameters from the task's stored payload.
79
+ *
80
+ * **The values here have been through redaction**, because `Task.InputPayload` is persistent,
81
+ * user-visible storage and nothing writes a raw `ActionParam[]` there. A parameter the binding
82
+ * marked as not-logged therefore arrives absent rather than secret — the action sees a missing
83
+ * value, which is the honest consequence of choosing not to persist it, and the reason durable
84
+ * dispatch is opt-in per binding rather than the default.
85
+ *
86
+ * Dependency outputs are merged underneath the task's own input so a node's explicit parameters
87
+ * always win over an upstream node that happened to emit the same key.
88
+ */
89
+ private buildParams(params: TaskActionRunParams): ActionParam[] {
90
+ const merged: Record<string, unknown> = {};
91
+ for (const [name, value] of params.DependencyOutputs) {
92
+ merged[name] = value;
93
+ }
94
+ if (params.InputPayload && typeof params.InputPayload === 'object') {
95
+ Object.assign(merged, params.InputPayload as Record<string, unknown>);
96
+ }
97
+ return Object.entries(merged).map(([Name, Value]) => ({ Name, Value, Type: 'Input' }));
98
+ }
99
+ }
@@ -15,7 +15,7 @@
15
15
  import { AgentRunner } from '@memberjunction/ai-agents';
16
16
  import { ChatMessageRole } from '@memberjunction/ai';
17
17
  import { LogError } from '@memberjunction/core';
18
- import { MJAIAgentEntityExtended } from '@memberjunction/ai-core-plus';
18
+ import { MJAIAgentEntityExtended, MJAIAgentRunEntityExtended } from '@memberjunction/ai-core-plus';
19
19
  import type { TaskAgentRunner, TaskAgentRunParams, TaskAgentRunResult } from '@memberjunction/task-graph';
20
20
 
21
21
  export class TaskGraphAgentRunner implements TaskAgentRunner {
@@ -30,6 +30,10 @@ export class TaskGraphAgentRunner implements TaskAgentRunner {
30
30
  agent,
31
31
  conversationMessages: [{ role: ChatMessageRole.user, content: this.buildPrompt(params) }],
32
32
  contextUser: params.ContextUser,
33
+ // Inherited from the graph, so a graph this run goes on to emit is one hop deeper
34
+ // rather than starting the chain over. Without it MAX_REINVOKE_DEPTH never fires.
35
+ continuationDepth: params.ContinuationDepth ?? 0,
36
+ parentRun: await this.loadSubmittingRun(params),
33
37
  });
34
38
 
35
39
  const success = result?.success === true;
@@ -46,6 +50,36 @@ export class TaskGraphAgentRunner implements TaskAgentRunner {
46
50
  }
47
51
  }
48
52
 
53
+ /**
54
+ * The run that submitted this graph, as the entity `RunAgent` wants for `parentRun`.
55
+ *
56
+ * **Why this link is worth a load.** It sets `ParentRunID` on the spawned run, which turns a
57
+ * workflow's total cost into one indexed sum over `RootParentRunID` rather than a walk of the
58
+ * graph, and gives the run a "started by" trail someone can follow back. Without it every
59
+ * dispatched run looks like it began from nowhere.
60
+ *
61
+ * Undefined — never a throw — when there is no submitting run or it cannot be read. A graph
62
+ * submitted by a schedule, by MCP, or by a person genuinely has no parent run, and failing the
63
+ * step because its provenance is unavailable would trade real work for a bookkeeping detail.
64
+ */
65
+ private async loadSubmittingRun(params: TaskAgentRunParams): Promise<MJAIAgentRunEntityExtended | undefined> {
66
+ if (!params.SubmittingAgentRunID) return undefined;
67
+ try {
68
+ const run = await params.Provider.GetEntityObject<MJAIAgentRunEntityExtended>(
69
+ 'MJ: AI Agent Runs',
70
+ params.ContextUser,
71
+ );
72
+ return (await run.Load(params.SubmittingAgentRunID)) ? run : undefined;
73
+ } catch (e) {
74
+ LogError(
75
+ `[TaskGraphAgentRunner] Task ${params.TaskID}: submitting run ` +
76
+ `${params.SubmittingAgentRunID} could not be loaded; the spawned run will have no ` +
77
+ `parent link. ${e instanceof Error ? e.message : String(e)}`,
78
+ );
79
+ return undefined;
80
+ }
81
+ }
82
+
49
83
  /**
50
84
  * Builds the agent's instruction from the task's own input plus its dependencies' outputs.
51
85
  *
@@ -0,0 +1,144 @@
1
+ /**
2
+ * @fileoverview MJServer's implementation of the task-graph `TaskPromptRunner` seam.
3
+ *
4
+ * The third sibling of {@link TaskGraphAgentRunner} and {@link TaskGraphActionRunner}, and here for
5
+ * the same reason: the dispatcher knows *when* a prompt-assigned node should run and nothing about
6
+ * how a prompt executes. The AI engine depends on the entity layer the dispatcher also builds on, so
7
+ * importing it into `@memberjunction/task-graph` would make every consumer of durable execution load
8
+ * the prompt engine too.
9
+ *
10
+ * **Why prompt nodes matter enough to have their own runner.** The `User Onboarding Flow Agent`
11
+ * ships with six of them. Until this existed, every one of its steps was refused at submission and
12
+ * the agent could not run at all — the refusal was correct (a persisted step nothing can execute
13
+ * waits forever) but it made a shipped workflow unusable rather than merely unfinished.
14
+ *
15
+ * @module @memberjunction/server
16
+ */
17
+ import { AIPromptRunner } from '@memberjunction/ai-prompts';
18
+ import { AIEngine } from '@memberjunction/aiengine';
19
+ import { AIPromptParams } from '@memberjunction/ai-core-plus';
20
+ import { LogError } from '@memberjunction/core';
21
+ import { UUIDsEqual } from '@memberjunction/global';
22
+ import type { TaskPromptRunner, TaskPromptRunParams, TaskPromptRunResult } from '@memberjunction/task-graph';
23
+
24
+ /** The placeholder a workflow prompt uses to receive the payload (plan §5.7). */
25
+ const CURRENT_PAYLOAD_PLACEHOLDER = '_CURRENT_PAYLOAD';
26
+
27
+ export class TaskGraphPromptRunner implements TaskPromptRunner {
28
+ public async RunPromptForTask(params: TaskPromptRunParams): Promise<TaskPromptRunResult> {
29
+ try {
30
+ await AIEngine.Instance.Config(false, params.ContextUser);
31
+ const prompt = AIEngine.Instance.Prompts.find((p) => UUIDsEqual(p.ID, params.PromptID));
32
+ if (!prompt) {
33
+ return { Success: false, ErrorMessage: `Prompt ${params.PromptID} is not in the engine's metadata.` };
34
+ }
35
+
36
+ const promptParams = new AIPromptParams();
37
+ promptParams.prompt = prompt;
38
+ promptParams.contextUser = params.ContextUser;
39
+ promptParams.data = this.buildData(params);
40
+
41
+ const result = await new AIPromptRunner().ExecutePrompt(promptParams);
42
+ if (!result?.success) {
43
+ return {
44
+ Success: false,
45
+ ErrorMessage: result?.errorMessage ?? 'The prompt did not return a result.',
46
+ PromptRunID: result?.promptRun?.ID,
47
+ };
48
+ }
49
+
50
+ const parsed = this.parseResponse(result.result);
51
+ return {
52
+ Success: true,
53
+ Output: parsed.payload,
54
+ PromptRunID: result.promptRun?.ID,
55
+ ChatMessage: parsed.chatMessage,
56
+ };
57
+ } catch (e) {
58
+ const message = e instanceof Error ? e.message : String(e);
59
+ LogError(`[TaskGraphPromptRunner] Task ${params.TaskID} failed: ${message}`);
60
+ return { Success: false, ErrorMessage: message };
61
+ }
62
+ }
63
+
64
+ /**
65
+ * What the prompt's template can see.
66
+ *
67
+ * The payload arrives under a **placeholder name** rather than being spread across the top level,
68
+ * so a workflow author writes `{{ _CURRENT_PAYLOAD }}` and gets the whole thing — and a payload
69
+ * key can never collide with a template parameter and silently win.
70
+ *
71
+ * `flowContext` carries what the step knows about its own position: what its prerequisites
72
+ * produced. Without it a prompt is reasoning about the payload alone and cannot refer to the step
73
+ * immediately before it.
74
+ */
75
+ private buildData(params: TaskPromptRunParams): Record<string, unknown> {
76
+ const payload = params.InputPayload && typeof params.InputPayload === 'object'
77
+ ? params.InputPayload as Record<string, unknown>
78
+ : {};
79
+
80
+ const dependencyOutputs: Record<string, unknown> = {};
81
+ for (const [taskID, output] of params.DependencyOutputs) dependencyOutputs[taskID] = output;
82
+
83
+ return {
84
+ ...(params.TemplateParameters ?? {}),
85
+ // JSON, not the object. A template writes `{{ _CURRENT_PAYLOAD }}` and expects to SEE
86
+ // the payload; handing the renderer an object yields "[object Object]", and the model
87
+ // then answers about a value it was never shown. That failure is invisible from the
88
+ // outside — the prompt run succeeds, the response is well-formed, and it is about
89
+ // nothing. It is what kept the Content Pipeline reviewer rejecting a draft it could not
90
+ // read, on every iteration, forever.
91
+ [CURRENT_PAYLOAD_PLACEHOLDER]: JSON.stringify(payload, null, 2),
92
+ flowContext: JSON.stringify({ dependencyOutputs }, null, 2),
93
+ };
94
+ }
95
+
96
+ /**
97
+ * Reads the model's response as a payload update, and notices when it wants to stop.
98
+ *
99
+ * **A prompt is the only node kind that can end the workflow early**, because it is the only one
100
+ * doing open-ended reasoning: it can conclude the remaining steps are unnecessary. That is
101
+ * expressed as a Chat-shaped response, and the dispatcher honours it by skipping what remains and
102
+ * settling the parent Complete — rather than treating an early finish as an abandoned graph.
103
+ *
104
+ * Non-JSON is not an error. A prompt that answers in prose has still answered; it simply carries
105
+ * no structured update, so it becomes a message rather than a payload merge.
106
+ */
107
+ private parseResponse(raw: unknown): { payload?: Record<string, unknown>; chatMessage?: string } {
108
+ if (raw == null) return {};
109
+
110
+ if (typeof raw === 'object') {
111
+ return this.interpret(raw as Record<string, unknown>);
112
+ }
113
+
114
+ if (typeof raw === 'string') {
115
+ const text = raw.trim();
116
+ const fenced = text.replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/, '');
117
+ try {
118
+ const parsed: unknown = JSON.parse(fenced);
119
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
120
+ return this.interpret(parsed as Record<string, unknown>);
121
+ }
122
+ } catch {
123
+ // Prose, not JSON — see above.
124
+ }
125
+ return { chatMessage: text };
126
+ }
127
+
128
+ return {};
129
+ }
130
+
131
+ /** Separates "here is an update" from "we are done, tell them this". */
132
+ private interpret(obj: Record<string, unknown>): { payload?: Record<string, unknown>; chatMessage?: string } {
133
+ const nextStep = obj['nextStep'] as { type?: string } | undefined;
134
+ const wantsChat = nextStep?.type === 'Chat' || obj['taskComplete'] === true;
135
+ const message = typeof obj['message'] === 'string' ? obj['message'] : undefined;
136
+
137
+ if (wantsChat && message) {
138
+ // The payload still merges: a prompt that both concluded and produced data should not
139
+ // lose the data because it also had something to say.
140
+ return { payload: obj, chatMessage: message };
141
+ }
142
+ return { payload: obj };
143
+ }
144
+ }
@@ -19,7 +19,7 @@
19
19
 
20
20
  import { RunView, UserInfo, IMetadataProvider, Metadata, LogError, LogStatus } from '@memberjunction/core';
21
21
  import type { MJAIBridgeAgentIdentityEntity, MJAIBridgeProviderEntity } from '@memberjunction/core-entities';
22
- import { UserCache } from '@memberjunction/sqlserver-dataprovider';
22
+ import { UserCache } from '@memberjunction/generic-database-provider';
23
23
  import { AIBridgeEngine } from '@memberjunction/ai-bridge-server';
24
24
  import { CreateBridgeRealtimeSession } from '@memberjunction/ai-agents';
25
25
  import {
@@ -20,7 +20,7 @@ import { Router, urlencoded, type Request, type Response } from 'express';
20
20
  import { WebSocketServer, type WebSocket } from 'ws';
21
21
  import { RegisterMediaUpgradeRoute } from './media-upgrade-router.js';
22
22
  import { LogError, LogStatus, UserInfo, IMetadataProvider, Metadata } from '@memberjunction/core';
23
- import { UserCache } from '@memberjunction/sqlserver-dataprovider';
23
+ import { UserCache } from '@memberjunction/generic-database-provider';
24
24
  import {
25
25
  verifyTwilioSignature,
26
26
  resolveInboundCall,
@@ -26,7 +26,7 @@ import { WebSocketServer, type WebSocket } from 'ws';
26
26
  import type { IncomingMessage } from 'node:http';
27
27
  import { RegisterMediaUpgradeRoute } from './media-upgrade-router.js';
28
28
  import { LogError, LogStatus, UserInfo, IMetadataProvider, Metadata } from '@memberjunction/core';
29
- import { UserCache } from '@memberjunction/sqlserver-dataprovider';
29
+ import { UserCache } from '@memberjunction/generic-database-provider';
30
30
  import {
31
31
  verifyVonageSignature,
32
32
  verifyVonageJwt,