@memberjunction/server 5.41.0 → 5.42.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.
- package/dist/agentSessions/index.d.ts +1 -0
- package/dist/agentSessions/index.d.ts.map +1 -1
- package/dist/agentSessions/index.js +5 -0
- package/dist/agentSessions/index.js.map +1 -1
- package/dist/agentSessions/remoteBrowserGoalEngine.d.ts +102 -0
- package/dist/agentSessions/remoteBrowserGoalEngine.d.ts.map +1 -0
- package/dist/agentSessions/remoteBrowserGoalEngine.js +164 -0
- package/dist/agentSessions/remoteBrowserGoalEngine.js.map +1 -0
- package/dist/agentSessions/remoteBrowserGoalRegistry.d.ts +66 -0
- package/dist/agentSessions/remoteBrowserGoalRegistry.d.ts.map +1 -0
- package/dist/agentSessions/remoteBrowserGoalRegistry.js +86 -0
- package/dist/agentSessions/remoteBrowserGoalRegistry.js.map +1 -0
- package/dist/auth/initializeProviders.d.ts.map +1 -1
- package/dist/auth/initializeProviders.js +4 -7
- package/dist/auth/initializeProviders.js.map +1 -1
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +12 -2
- package/dist/config.js.map +1 -1
- package/dist/generated/generated.d.ts +591 -0
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +6832 -3610
- package/dist/generated/generated.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -16
- package/dist/index.js.map +1 -1
- package/dist/integration/CustomColumnPromoter.d.ts +108 -0
- package/dist/integration/CustomColumnPromoter.d.ts.map +1 -0
- package/dist/integration/CustomColumnPromoter.js +508 -0
- package/dist/integration/CustomColumnPromoter.js.map +1 -0
- package/dist/logging/StartupLogger.d.ts +57 -1
- package/dist/logging/StartupLogger.d.ts.map +1 -1
- package/dist/logging/StartupLogger.js +115 -6
- package/dist/logging/StartupLogger.js.map +1 -1
- package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts +27 -0
- package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts.map +1 -0
- package/dist/resolvers/ExecuteRemoteOperationResolver.js +136 -0
- package/dist/resolvers/ExecuteRemoteOperationResolver.js.map +1 -0
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +101 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +544 -42
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.d.ts +121 -0
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeBridgeResolver.js +533 -0
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +78 -5
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +227 -15
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +0 -7
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
- package/dist/rest/setupRESTEndpoints.js +5 -3
- package/dist/rest/setupRESTEndpoints.js.map +1 -1
- package/package.json +83 -78
- package/src/__tests__/RealtimeBridgeResolver.test.ts +100 -0
- package/src/__tests__/RemoteBrowserAudioStream.test.ts +8 -1
- package/src/__tests__/RemoteBrowserGoalEngine.test.ts +144 -0
- package/src/__tests__/remoteBrowserGoalRegistry.test.ts +81 -0
- package/src/agentSessions/index.ts +5 -0
- package/src/agentSessions/remoteBrowserGoalEngine.ts +191 -0
- package/src/agentSessions/remoteBrowserGoalRegistry.ts +122 -0
- package/src/auth/initializeProviders.ts +4 -7
- package/src/config.ts +13 -2
- package/src/generated/generated.ts +2444 -205
- package/src/index.ts +33 -16
- package/src/integration/CustomColumnPromoter.ts +597 -0
- package/src/logging/StartupLogger.ts +130 -6
- package/src/resolvers/ExecuteRemoteOperationResolver.ts +108 -0
- package/src/resolvers/IntegrationDiscoveryResolver.ts +396 -37
- package/src/resolvers/RealtimeBridgeResolver.ts +450 -0
- package/src/resolvers/RemoteBrowserActionResolver.ts +232 -17
- package/src/resolvers/RunAIAgentResolver.ts +1 -9
- 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 {
|
|
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`;
|
|
848
|
-
* finite `x`,`y`,`deltaX`,`deltaY`. The `button` is clamped to the allowed
|
|
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: {
|
|
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
|
-
? {
|
|
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
|
-
? {
|
|
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
|
-
? {
|
|
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,
|
|
@@ -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
|
-
|
|
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
|
-
|
|
92
|
+
LogStatusEx({ message: `REST API endpoints have been set up at ${basePath}`, verboseOnly: true });
|
|
91
93
|
}
|