@memberjunction/server 5.41.0 → 5.43.0

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 (90) hide show
  1. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  2. package/dist/agentSessions/SessionJanitor.js +5 -1
  3. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  4. package/dist/agentSessions/SessionManager.d.ts +15 -2
  5. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  6. package/dist/agentSessions/SessionManager.js +50 -11
  7. package/dist/agentSessions/SessionManager.js.map +1 -1
  8. package/dist/agentSessions/index.d.ts +1 -0
  9. package/dist/agentSessions/index.d.ts.map +1 -1
  10. package/dist/agentSessions/index.js +5 -0
  11. package/dist/agentSessions/index.js.map +1 -1
  12. package/dist/agentSessions/remoteBrowserGoalEngine.d.ts +102 -0
  13. package/dist/agentSessions/remoteBrowserGoalEngine.d.ts.map +1 -0
  14. package/dist/agentSessions/remoteBrowserGoalEngine.js +164 -0
  15. package/dist/agentSessions/remoteBrowserGoalEngine.js.map +1 -0
  16. package/dist/agentSessions/remoteBrowserGoalRegistry.d.ts +66 -0
  17. package/dist/agentSessions/remoteBrowserGoalRegistry.d.ts.map +1 -0
  18. package/dist/agentSessions/remoteBrowserGoalRegistry.js +86 -0
  19. package/dist/agentSessions/remoteBrowserGoalRegistry.js.map +1 -0
  20. package/dist/auth/initializeProviders.d.ts.map +1 -1
  21. package/dist/auth/initializeProviders.js +4 -7
  22. package/dist/auth/initializeProviders.js.map +1 -1
  23. package/dist/config.d.ts +7 -0
  24. package/dist/config.d.ts.map +1 -1
  25. package/dist/config.js +12 -2
  26. package/dist/config.js.map +1 -1
  27. package/dist/generated/generated.d.ts +612 -0
  28. package/dist/generated/generated.d.ts.map +1 -1
  29. package/dist/generated/generated.js +6934 -3626
  30. package/dist/generated/generated.js.map +1 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +38 -17
  33. package/dist/index.js.map +1 -1
  34. package/dist/integration/CustomColumnPromoter.d.ts +108 -0
  35. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -0
  36. package/dist/integration/CustomColumnPromoter.js +508 -0
  37. package/dist/integration/CustomColumnPromoter.js.map +1 -0
  38. package/dist/logging/StartupLogger.d.ts +57 -1
  39. package/dist/logging/StartupLogger.d.ts.map +1 -1
  40. package/dist/logging/StartupLogger.js +115 -6
  41. package/dist/logging/StartupLogger.js.map +1 -1
  42. package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts +46 -0
  43. package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts.map +1 -0
  44. package/dist/resolvers/ExecuteRemoteOperationResolver.js +192 -0
  45. package/dist/resolvers/ExecuteRemoteOperationResolver.js.map +1 -0
  46. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +101 -1
  47. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  48. package/dist/resolvers/IntegrationDiscoveryResolver.js +629 -58
  49. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  50. package/dist/resolvers/RealtimeBridgeResolver.d.ts +130 -0
  51. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -0
  52. package/dist/resolvers/RealtimeBridgeResolver.js +580 -0
  53. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -0
  54. package/dist/resolvers/RemoteBrowserActionResolver.d.ts +78 -5
  55. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  56. package/dist/resolvers/RemoteBrowserActionResolver.js +227 -15
  57. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  58. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  59. package/dist/resolvers/RunAIAgentResolver.js +0 -7
  60. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  61. package/dist/resolvers/RunTemplateResolver.d.ts +8 -0
  62. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  63. package/dist/resolvers/RunTemplateResolver.js +8 -0
  64. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  65. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
  66. package/dist/rest/setupRESTEndpoints.js +5 -3
  67. package/dist/rest/setupRESTEndpoints.js.map +1 -1
  68. package/package.json +83 -78
  69. package/src/__tests__/RealtimeBridgeResolver.test.ts +100 -0
  70. package/src/__tests__/RemoteBrowserAudioStream.test.ts +8 -1
  71. package/src/__tests__/RemoteBrowserGoalEngine.test.ts +144 -0
  72. package/src/__tests__/remoteBrowserGoalRegistry.test.ts +81 -0
  73. package/src/agentSessions/SessionJanitor.ts +15 -1
  74. package/src/agentSessions/SessionManager.ts +59 -10
  75. package/src/agentSessions/index.ts +5 -0
  76. package/src/agentSessions/remoteBrowserGoalEngine.ts +191 -0
  77. package/src/agentSessions/remoteBrowserGoalRegistry.ts +122 -0
  78. package/src/auth/initializeProviders.ts +4 -7
  79. package/src/config.ts +13 -2
  80. package/src/generated/generated.ts +2502 -198
  81. package/src/index.ts +38 -17
  82. package/src/integration/CustomColumnPromoter.ts +597 -0
  83. package/src/logging/StartupLogger.ts +130 -6
  84. package/src/resolvers/ExecuteRemoteOperationResolver.ts +164 -0
  85. package/src/resolvers/IntegrationDiscoveryResolver.ts +490 -53
  86. package/src/resolvers/RealtimeBridgeResolver.ts +493 -0
  87. package/src/resolvers/RemoteBrowserActionResolver.ts +232 -17
  88. package/src/resolvers/RunAIAgentResolver.ts +1 -9
  89. package/src/resolvers/RunTemplateResolver.ts +8 -0
  90. package/src/rest/setupRESTEndpoints.ts +5 -3
@@ -16,13 +16,22 @@
16
16
  *
17
17
  * @module @memberjunction/server
18
18
  */
19
- import { Resolver, Mutation, Query, Arg, Ctx, Float, ObjectType, Field, PubSub, PubSubEngine } from 'type-graphql';
19
+ import { Resolver, Mutation, Query, Arg, Ctx, Float, Int, ObjectType, Field, PubSub, PubSubEngine } from 'type-graphql';
20
20
  import { AppContext, UserPayload } from '../types.js';
21
21
  import { UserInfo, IMetadataProvider, LogError } from '@memberjunction/core';
22
22
  import { UUIDsEqual } from '@memberjunction/global';
23
23
  import { MJAIAgentSessionEntity, MJAIAgentEntity } from '@memberjunction/core-entities';
24
24
  import { RemoteBrowserEngine } from '@memberjunction/remote-browser-server';
25
- import { IRemoteBrowserSession, RemoteBrowserAction, RemoteBrowserAudioChunk, RemoteBrowserHumanInput, RemoteBrowserModifierKey, RemoteBrowserCapabilityNotSupportedError } from '@memberjunction/remote-browser-base';
25
+ import { beginBrowserGoalStep, finalizeBrowserGoalStep, extractCoAgentRunID } from '../agentSessions/remoteBrowserGoalEngine.js';
26
+ import { RemoteBrowserGoalRegistry } from '../agentSessions/remoteBrowserGoalRegistry.js';
27
+ import { randomUUID } from 'node:crypto';
28
+ import {
29
+ RemoteBrowserAction,
30
+ RemoteBrowserAudioChunk,
31
+ RemoteBrowserHumanInput,
32
+ RemoteBrowserModifierKey,
33
+ RemoteBrowserCapabilityNotSupportedError,
34
+ } from '@memberjunction/remote-browser-base';
26
35
  import { ResolverBase } from '../generic/ResolverBase.js';
27
36
  import { GetReadWriteProvider } from '../util.js';
28
37
  import { PUSH_STATUS_UPDATES_TOPIC } from '../generic/PushStatusResolver.js';
@@ -72,6 +81,45 @@ export class RemoteBrowserActionResult {
72
81
  Detail?: string;
73
82
  }
74
83
 
84
+ /**
85
+ * Result of {@link RemoteBrowserActionResolver.ExecuteRemoteBrowserGoal} — the outcome of an autonomous,
86
+ * goal-driven browser run (computer-use loop or backend native AI).
87
+ */
88
+ @ObjectType()
89
+ export class RemoteBrowserGoalResultType {
90
+ /** Whether the goal was achieved. */
91
+ @Field(() => Boolean)
92
+ Success: boolean;
93
+
94
+ /**
95
+ * Handle for the async goal run. `ExecuteRemoteBrowserGoal` STARTS the goal and returns this with
96
+ * `Status: 'Running'`; the client polls {@link RemoteBrowserActionResolver.GetRemoteBrowserGoalResult}
97
+ * with it until the run reports a terminal status.
98
+ */
99
+ @Field(() => String, { nullable: true })
100
+ GoalRunID?: string;
101
+
102
+ /** Which control strategy executed the goal (`ComputerUse` / `NativeAI`). */
103
+ @Field(() => String, { nullable: true })
104
+ Strategy?: string;
105
+
106
+ /** The page URL when the run ended, when known. */
107
+ @Field(() => String, { nullable: true })
108
+ CurrentUrl?: string;
109
+
110
+ /** Terminal status label (`Completed` / `MaxStepsReached` / `Impossible` / `Error` / …). */
111
+ @Field(() => String, { nullable: true })
112
+ Status?: string;
113
+
114
+ /** Number of perceive-act steps executed (computer-use strategy). */
115
+ @Field(() => Int, { nullable: true })
116
+ StepCount?: number;
117
+
118
+ /** Human-readable detail (judge feedback / error message). */
119
+ @Field(() => String, { nullable: true })
120
+ Detail?: string;
121
+ }
122
+
75
123
  /**
76
124
  * Result of {@link RemoteBrowserActionResolver.RemoteBrowserSnapshot} — the current viewport screenshot +
77
125
  * URL for the client's live view. Both fields are null when the session holds no live browser.
@@ -113,6 +161,18 @@ export class RemoteBrowserAudioStreamResult {
113
161
  Streaming: boolean;
114
162
  }
115
163
 
164
+ /**
165
+ * Result of {@link RemoteBrowserActionResolver.GetRemoteBrowserSelection} — the remote page's current text
166
+ * selection, the copy-out half of human clipboard support. `Text` is `''` when nothing is selected, no live
167
+ * browser exists, or the backend can't read the selection (the client then writes nothing to the clipboard).
168
+ */
169
+ @ObjectType()
170
+ export class RemoteBrowserSelection {
171
+ /** The remote page's current selection text, or `''` when nothing is selected / unavailable. */
172
+ @Field(() => String)
173
+ Text: string;
174
+ }
175
+
116
176
  /**
117
177
  * One UI element the visual interpreter localized in the screenshot — a label plus the pixel centroid the
118
178
  * voice agent can feed straight into `browser_Click(x, y)`. Coordinates are in the SCREENSHOT's own pixel
@@ -241,6 +301,93 @@ export class RemoteBrowserActionResolver extends ResolverBase {
241
301
  }
242
302
  }
243
303
 
304
+ /**
305
+ * Execute a high-level GOAL against the session's browser — the agent sets an intent ("log in and open
306
+ * the latest invoice") and the resolved control strategy (computer-use loop or backend native AI) plans
307
+ * + executes it autonomously, instead of relaying granular actions. Ownership-gated; lazily starts the
308
+ * browser. Returns the terminal outcome (this is a request/response mutation — for live progress
309
+ * narration in a server-bridged realtime session the broker calls {@link RemoteBrowserEngine.AchieveGoal}
310
+ * in-process with an `OnProgress` callback).
311
+ *
312
+ * @param agentSessionID The `AIAgentSession` id the browser is bound to.
313
+ * @param goal The natural-language goal.
314
+ * @returns The goal outcome (success, strategy, status, step count, url, detail).
315
+ */
316
+ @Mutation(() => RemoteBrowserGoalResultType)
317
+ async ExecuteRemoteBrowserGoal(
318
+ @Arg('agentSessionID', () => String) agentSessionID: string,
319
+ @Arg('goal', () => String) goal: string,
320
+ @Ctx() { userPayload, providers }: AppContext,
321
+ @Arg('startUrl', () => String, { nullable: true }) startUrl?: string,
322
+ @Arg('maxSteps', () => Int, { nullable: true }) maxSteps?: number,
323
+ @Arg('preferredStrategy', () => String, { nullable: true }) preferredStrategy?: string,
324
+ ): Promise<RemoteBrowserGoalResultType> {
325
+ const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
326
+ const session = await this.loadOwnedSession(agentSessionID, contextUser, provider);
327
+ const providerName = await this.resolveProviderName(session, contextUser, provider);
328
+ // Observability: nest this goal's many prompt runs under ONE "Browser goal" step on the realtime
329
+ // co-agent run (when the session has one). Best-effort — a null step just means the goal runs unlinked.
330
+ const coAgentRunID = extractCoAgentRunID(session.Config_);
331
+ const goalStep = await beginBrowserGoalStep(provider, contextUser, coAgentRunID, goal);
332
+
333
+ // ASYNC START: a goal loop can run for minutes; do NOT hold this request open for it (browser
334
+ // fetch / proxy / janitor timeouts would kill the request while the loop runs on, and the agent
335
+ // would get "no response from the server" despite a successful run). Register the run, kick the
336
+ // loop off WITHOUT awaiting, and return a GoalRunID the client polls via GetRemoteBrowserGoalResult.
337
+ const goalRunID = randomUUID();
338
+ RemoteBrowserGoalRegistry.Instance.Begin(agentSessionID, goalRunID);
339
+ void RemoteBrowserEngine.Instance.AchieveGoal(agentSessionID, goal, {
340
+ ContextUser: contextUser,
341
+ ProviderName: providerName,
342
+ StartUrl: startUrl,
343
+ MaxSteps: maxSteps,
344
+ PreferredStrategy: preferredStrategy === 'NativeAI' || preferredStrategy === 'ComputerUse' ? preferredStrategy : undefined,
345
+ AgentRunID: coAgentRunID,
346
+ AgentRunStepID: goalStep?.ID,
347
+ })
348
+ .then(async (result) => {
349
+ await finalizeBrowserGoalStep(goalStep, result);
350
+ RemoteBrowserGoalRegistry.Instance.Complete(agentSessionID, goalRunID, result);
351
+ })
352
+ .catch(async (err) => {
353
+ const message = err instanceof Error ? err.message : String(err);
354
+ LogError(`ExecuteRemoteBrowserGoal failed (provider='${providerName}'): ${message}`);
355
+ const failure = { Success: false, Status: 'Error', Detail: `Remote browser error (${providerName}): ${message}` };
356
+ await finalizeBrowserGoalStep(goalStep, failure);
357
+ RemoteBrowserGoalRegistry.Instance.Complete(agentSessionID, goalRunID, failure);
358
+ });
359
+
360
+ return { Success: true, Status: 'Running', GoalRunID: goalRunID, Detail: 'Goal started.' };
361
+ }
362
+
363
+ /**
364
+ * Poll the outcome of a goal STARTED by {@link RemoteBrowserActionResolver.ExecuteRemoteBrowserGoal}.
365
+ * Returns `Status: 'Running'` while the loop is in flight, then the terminal outcome (success,
366
+ * strategy, status, step count, url, detail) once it finishes. Ownership-gated. A `Status: 'Unknown'`
367
+ * result means the run id is unrecognized — it expired (results are retained briefly) or never existed.
368
+ *
369
+ * @param agentSessionID The `AIAgentSession` id the goal runs against.
370
+ * @param goalRunID The handle returned by `ExecuteRemoteBrowserGoal`.
371
+ * @returns The current/terminal goal outcome.
372
+ */
373
+ @Query(() => RemoteBrowserGoalResultType)
374
+ async GetRemoteBrowserGoalResult(
375
+ @Arg('agentSessionID', () => String) agentSessionID: string,
376
+ @Arg('goalRunID', () => String) goalRunID: string,
377
+ @Ctx() { userPayload, providers }: AppContext,
378
+ ): Promise<RemoteBrowserGoalResultType> {
379
+ const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
380
+ await this.loadOwnedSession(agentSessionID, contextUser, provider);
381
+ const record = RemoteBrowserGoalRegistry.Instance.Get(agentSessionID, goalRunID);
382
+ if (!record) {
383
+ return { Success: false, Status: 'Unknown', GoalRunID: goalRunID, Detail: 'No such goal run (it may have expired).' };
384
+ }
385
+ if (record.Status === 'Running' || !record.Outcome) {
386
+ return { Success: false, Status: 'Running', GoalRunID: goalRunID };
387
+ }
388
+ return { ...record.Outcome, GoalRunID: goalRunID };
389
+ }
390
+
244
391
  /**
245
392
  * Return the current viewport screenshot + URL for the session's live browser — the client's live view.
246
393
  * Ownership-gated. When the session holds no live browser (never started, or already torn down), the
@@ -481,7 +628,8 @@ export class RemoteBrowserActionResolver extends ResolverBase {
481
628
  * agent⇄human floor, so we just relay. Finer floor / `AgentOnly` gating is a follow-up.
482
629
  *
483
630
  * @param agentSessionID The `AIAgentSession` id the browser is bound to.
484
- * @param kind The input kind (`'pointer-move' | 'pointer-click' | 'pointer-down' | 'pointer-up' | 'key' | 'scroll'`).
631
+ * @param kind The input kind (`'pointer-move' | 'pointer-click' | 'pointer-down' | 'pointer-up' | 'key' | 'text' | 'scroll'`).
632
+ * @param text The pasted text (the `'text'` paste-in kind only) — inserted into the page's focused element.
485
633
  * @param modifiers Optional comma-separated modifier keys held during the input (`'Shift'`, `'Control'`,
486
634
  * `'Alt'`, `'Meta'`) — carries Shift-click selection and Ctrl/Cmd+key chords faithfully.
487
635
  * @returns `true` when the input was routed, else `false`.
@@ -495,6 +643,7 @@ export class RemoteBrowserActionResolver extends ResolverBase {
495
643
  @Arg('y', () => Float, { nullable: true }) y?: number,
496
644
  @Arg('button', () => String, { nullable: true }) button?: string,
497
645
  @Arg('key', () => String, { nullable: true }) key?: string,
646
+ @Arg('text', () => String, { nullable: true }) text?: string,
498
647
  @Arg('deltaX', () => Float, { nullable: true }) deltaX?: number,
499
648
  @Arg('deltaY', () => Float, { nullable: true }) deltaY?: number,
500
649
  @Arg('modifiers', () => String, { nullable: true }) modifiers?: string,
@@ -507,7 +656,7 @@ export class RemoteBrowserActionResolver extends ResolverBase {
507
656
  return false;
508
657
  }
509
658
 
510
- const input = this.buildHumanInput({ kind, x, y, button, key, deltaX, deltaY, modifiers });
659
+ const input = this.buildHumanInput({ kind, x, y, button, key, text, deltaX, deltaY, modifiers });
511
660
  if (!input) {
512
661
  return false;
513
662
  }
@@ -526,6 +675,46 @@ export class RemoteBrowserActionResolver extends ResolverBase {
526
675
  }
527
676
  }
528
677
 
678
+ /**
679
+ * Returns the remote page's CURRENT text selection — the copy-out half of human clipboard support. The
680
+ * viewer captures a local `copy` / Cmd+C, calls this to read what the human selected on the live page, and
681
+ * writes the result to the LOCAL clipboard (sidestepping the isolated remote clipboard, the mirror of the
682
+ * `'text'` paste-in path). Ownership-gated.
683
+ *
684
+ * Gracefully best-effort, never throws past the ownership gate (mirrors {@link RemoteBrowserSnapshot}):
685
+ * - No live browser for the session → `{ Text: '' }`.
686
+ * - Backend lacks `HumanTakeover` ({@link RemoteBrowserCapabilityNotSupportedError}) → `{ Text: '' }`.
687
+ * - Nothing selected / any other read failure → `{ Text: '' }` (logged).
688
+ *
689
+ * @param agentSessionID The `AIAgentSession` id the browser is bound to.
690
+ * @returns The selection text, or `{ Text: '' }` when none is readable.
691
+ */
692
+ @Query(() => RemoteBrowserSelection)
693
+ async GetRemoteBrowserSelection(
694
+ @Arg('agentSessionID', () => String) agentSessionID: string,
695
+ @Ctx() { userPayload, providers }: AppContext,
696
+ ): Promise<RemoteBrowserSelection> {
697
+ const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
698
+ await this.loadOwnedSession(agentSessionID, contextUser, provider);
699
+
700
+ const liveSession = RemoteBrowserEngine.Instance.GetSessionForAgentSession(agentSessionID);
701
+ if (!liveSession) {
702
+ return { Text: '' };
703
+ }
704
+
705
+ try {
706
+ return { Text: await liveSession.GetSelectionText() };
707
+ } catch (err) {
708
+ if (err instanceof RemoteBrowserCapabilityNotSupportedError) {
709
+ // Backend can't read the selection — copy-out is simply unavailable. Not an error condition.
710
+ return { Text: '' };
711
+ }
712
+ const message = err instanceof Error ? err.message : String(err);
713
+ LogError(`GetRemoteBrowserSelection failed for session ${agentSessionID}: ${message}`);
714
+ return { Text: '' };
715
+ }
716
+ }
717
+
529
718
  // ----- internals -------------------------------------------------------------------------
530
719
 
531
720
  /**
@@ -569,12 +758,7 @@ export class RemoteBrowserActionResolver extends ResolverBase {
569
758
  * @param agentSessionID The `AIAgentSession` id the chunk belongs to.
570
759
  * @param chunk The encoded audio chunk.
571
760
  */
572
- private publishAudioChunk(
573
- pubSub: PubSubEngine,
574
- userPayload: UserPayload,
575
- agentSessionID: string,
576
- chunk: RemoteBrowserAudioChunk,
577
- ): void {
761
+ private publishAudioChunk(pubSub: PubSubEngine, userPayload: UserPayload, agentSessionID: string, chunk: RemoteBrowserAudioChunk): void {
578
762
  pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
579
763
  message: JSON.stringify({
580
764
  resolver: 'RemoteBrowserActionResolver',
@@ -844,15 +1028,25 @@ export class RemoteBrowserActionResolver extends ResolverBase {
844
1028
  /**
845
1029
  * Builds a strongly-typed {@link RemoteBrowserHumanInput} from the relayed `kind` + fields, validating
846
1030
  * each kind's required field(s). Returns `null` for an unknown kind or a kind missing its required
847
- * field(s): pointer-move/click/down/up need finite `x`,`y`; key needs a non-empty `key`; scroll needs
848
- * finite `x`,`y`,`deltaX`,`deltaY`. The `button` is clamped to the allowed union
849
- * (`'left' | 'middle' | 'right'`), defaulting unknown/absent values to `'left'`. Held `modifiers`
1031
+ * field(s): pointer-move/click/down/up need finite `x`,`y`; key needs a non-empty `key`; text needs a
1032
+ * non-empty `text`; scroll needs finite `x`,`y`,`deltaX`,`deltaY`. The `button` is clamped to the allowed
1033
+ * union (`'left' | 'middle' | 'right'`), defaulting unknown/absent values to `'left'`. Held `modifiers`
850
1034
  * (e.g. Shift-click, Ctrl/Cmd+key) ride on pointer clicks/presses and key presses.
851
1035
  *
852
1036
  * @param input The relayed input kind + all optional fields.
853
1037
  * @returns The built human input, or `null` when the kind is unknown / incomplete.
854
1038
  */
855
- private buildHumanInput(input: { kind: string; x?: number; y?: number; button?: string; key?: string; deltaX?: number; deltaY?: number; modifiers?: string }): RemoteBrowserHumanInput | null {
1039
+ private buildHumanInput(input: {
1040
+ kind: string;
1041
+ x?: number;
1042
+ y?: number;
1043
+ button?: string;
1044
+ key?: string;
1045
+ text?: string;
1046
+ deltaX?: number;
1047
+ deltaY?: number;
1048
+ modifiers?: string;
1049
+ }): RemoteBrowserHumanInput | null {
856
1050
  const modifiers = this.parseModifiers(input.modifiers);
857
1051
  const hasXy = Number.isFinite(input.x) && Number.isFinite(input.y);
858
1052
  switch (input.kind) {
@@ -860,18 +1054,39 @@ export class RemoteBrowserActionResolver extends ResolverBase {
860
1054
  return hasXy ? { Kind: 'pointer-move', X: input.x as number, Y: input.y as number } : null;
861
1055
  case 'pointer-click':
862
1056
  return hasXy
863
- ? { Kind: 'pointer-click', X: input.x as number, Y: input.y as number, Button: this.clampButton(input.button), ...(modifiers.length ? { Modifiers: modifiers } : {}) }
1057
+ ? {
1058
+ Kind: 'pointer-click',
1059
+ X: input.x as number,
1060
+ Y: input.y as number,
1061
+ Button: this.clampButton(input.button),
1062
+ ...(modifiers.length ? { Modifiers: modifiers } : {}),
1063
+ }
864
1064
  : null;
865
1065
  case 'pointer-down':
866
1066
  return hasXy
867
- ? { Kind: 'pointer-down', X: input.x as number, Y: input.y as number, Button: this.clampButton(input.button), ...(modifiers.length ? { Modifiers: modifiers } : {}) }
1067
+ ? {
1068
+ Kind: 'pointer-down',
1069
+ X: input.x as number,
1070
+ Y: input.y as number,
1071
+ Button: this.clampButton(input.button),
1072
+ ...(modifiers.length ? { Modifiers: modifiers } : {}),
1073
+ }
868
1074
  : null;
869
1075
  case 'pointer-up':
870
1076
  return hasXy
871
- ? { Kind: 'pointer-up', X: input.x as number, Y: input.y as number, Button: this.clampButton(input.button), ...(modifiers.length ? { Modifiers: modifiers } : {}) }
1077
+ ? {
1078
+ Kind: 'pointer-up',
1079
+ X: input.x as number,
1080
+ Y: input.y as number,
1081
+ Button: this.clampButton(input.button),
1082
+ ...(modifiers.length ? { Modifiers: modifiers } : {}),
1083
+ }
872
1084
  : null;
873
1085
  case 'key':
874
1086
  return input.key && input.key.length > 0 ? { Kind: 'key', Key: input.key, ...(modifiers.length ? { Modifiers: modifiers } : {}) } : null;
1087
+ case 'text':
1088
+ // Human paste — insert the relayed clipboard text into the focused element. Empty text is a no-op.
1089
+ return typeof input.text === 'string' && input.text.length > 0 ? { Kind: 'text', Text: input.text } : null;
875
1090
  case 'scroll':
876
1091
  return hasXy && Number.isFinite(input.deltaX) && Number.isFinite(input.deltaY)
877
1092
  ? { Kind: 'scroll', X: input.x as number, Y: input.y as number, DeltaX: input.deltaX as number, DeltaY: input.deltaY as number }
@@ -329,15 +329,7 @@ export class RunAIAgentResolver extends ResolverBase {
329
329
  console.error('❌ No agent run available for streaming callback');
330
330
  return;
331
331
  }
332
-
333
- console.log('💬 Publishing streaming content:', {
334
- content: chunk.content.substring(0, 50) + '...',
335
- isComplete: chunk.isComplete,
336
- stepType: chunk.stepType,
337
- sessionId,
338
- agentRunId: agentRun.ID
339
- });
340
-
332
+
341
333
  // Publish streaming content with the full serialized agent run
342
334
  const streamMsg: AgentExecutionStreamMessage = {
343
335
  sessionId,
@@ -21,8 +21,16 @@ export class TemplateRunResult {
21
21
  executionTimeMs?: number;
22
22
  }
23
23
 
24
+ /**
25
+ * @deprecated Prefer the `Template.Run` Remote Operation (`@memberjunction/templates-base-types`
26
+ * `TemplateRunOperation`) — one typed call site that routes over the generic `ExecuteRemoteOperation`
27
+ * transport on the client and in-process on the server. This bespoke `RunTemplate` mutation is retained
28
+ * only for backward compatibility with existing external API consumers and may be removed in a future
29
+ * major version.
30
+ */
24
31
  @Resolver()
25
32
  export class RunTemplateResolver extends ResolverBase {
33
+ /** @deprecated Use the `Template.Run` Remote Operation instead. Retained for backcompat. */
26
34
  @Mutation(() => TemplateRunResult)
27
35
  async RunTemplate(
28
36
  @Arg('templateId') templateId: string,
@@ -1,5 +1,6 @@
1
1
  import express from 'express';
2
2
  import BodyParser from 'body-parser';
3
+ import { LogStatusEx } from '@memberjunction/core';
3
4
  import { RESTEndpointHandler } from './RESTEndpointHandler.js';
4
5
 
5
6
  export const ___REST_API_BASE_PATH = '/api/v1';
@@ -63,9 +64,10 @@ export function setupRESTEndpoints(
63
64
  // Merge with default options
64
65
  const config = { ...DEFAULT_REST_API_OPTIONS, ...options };
65
66
 
66
- // Skip setup if REST API is disabled
67
+ // Skip setup if REST API is disabled. REST on/off state is surfaced in the startup
68
+ // summary `Auth` line at standard level, so this detail is verbose-only.
67
69
  if (!config.enabled) {
68
- console.log('REST API endpoints are disabled');
70
+ LogStatusEx({ message: 'REST API endpoints are disabled', verboseOnly: true });
69
71
  return;
70
72
  }
71
73
 
@@ -87,5 +89,5 @@ export function setupRESTEndpoints(
87
89
  app.use(basePath, BodyParser.json({ limit: '50mb' }), restHandler.getRouter());
88
90
  }
89
91
 
90
- console.log(`REST API endpoints have been set up at ${basePath}`);
92
+ LogStatusEx({ message: `REST API endpoints have been set up at ${basePath}`, verboseOnly: true });
91
93
  }