@memberjunction/ng-conversations 6.1.1 → 6.1.3
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/lib/components/realtime/channels/base-realtime-channel-client.d.ts +36 -1
- package/dist/lib/components/realtime/channels/base-realtime-channel-client.d.ts.map +1 -1
- package/dist/lib/components/realtime/channels/base-realtime-channel-client.js +23 -0
- package/dist/lib/components/realtime/channels/base-realtime-channel-client.js.map +1 -1
- package/dist/lib/components/realtime/realtime-activity-rail.component.d.ts +1 -1
- package/dist/lib/components/realtime/realtime-activity-rail.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-activity-rail.component.js +2 -2
- package/dist/lib/components/realtime/realtime-activity-rail.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-delegation-card.component.d.ts +2 -2
- package/dist/lib/components/realtime/realtime-delegation-card.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-delegation-card.component.js +104 -72
- package/dist/lib/components/realtime/realtime-delegation-card.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-session-state.d.ts +11 -4
- package/dist/lib/components/realtime/realtime-session-state.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-session-state.js +43 -1
- package/dist/lib/components/realtime/realtime-session-state.js.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts +58 -2
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js +154 -4
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js.map +1 -1
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts +51 -2
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js +285 -5
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js.map +1 -1
- package/dist/lib/services/realtime-session.service.d.ts +39 -4
- package/dist/lib/services/realtime-session.service.d.ts.map +1 -1
- package/dist/lib/services/realtime-session.service.js +128 -8
- package/dist/lib/services/realtime-session.service.js.map +1 -1
- package/package.json +31 -31
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-browser-channel.js","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/remote-browser/remote-browser-channel.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Type } from '@angular/core';\nimport type { Subscription } from 'rxjs';\nimport { RegisterClass } from '@memberjunction/global';\nimport { JSONValue, RealtimeToolDefinition } from '@memberjunction/ai';\nimport { BaseRealtimeChannelClient, ChannelOnboardingDetails } from '../channels/base-realtime-channel-client';\nimport { RemoteBrowserHumanInputEvent, RemoteBrowserSnapshotView, RemoteBrowserSurfaceComponent } from './remote-browser-surface.component';\nimport {\n MapToolToAction,\n REMOTE_BROWSER_TOOL_DEFINITIONS,\n REMOTE_BROWSER_TOOL_NAMES,\n REMOTE_BROWSER_TOOL_PREFIX,\n RemoteBrowserAction,\n RemoteBrowserToolArgError,\n} from './remote-browser-tools';\nimport { MediaSourceAudioSink, RemoteBrowserAudioChunkInput, RemoteBrowserAudioPlayer } from './remote-browser-audio-player';\n\n/**\n * GraphQL mutation that drives the SERVER-hosted browser. The channel awaits it for every\n * `browser_*` tool call and feeds the result back to the model.\n */\nconst EXECUTE_REMOTE_BROWSER_ACTION_MUTATION = `\n mutation ExecuteRemoteBrowserAction(\n $agentSessionID: String!, $kind: String!, $url: String, $selector: String,\n $x: Float, $y: Float, $text: String, $key: String,\n $deltaX: Float, $deltaY: Float, $ms: Float\n ) {\n ExecuteRemoteBrowserAction(\n agentSessionID: $agentSessionID, kind: $kind, url: $url, selector: $selector,\n x: $x, y: $y, text: $text, key: $key,\n deltaX: $deltaX, deltaY: $deltaY, ms: $ms\n ) {\n Success\n CurrentUrl\n Detail\n }\n }\n`;\n\n/** The narrow projection of the `ExecuteRemoteBrowserAction` mutation payload the channel reads. */\ninterface ExecuteRemoteBrowserActionResult {\n ExecuteRemoteBrowserAction: {\n Success: boolean;\n CurrentUrl: string | null;\n Detail: string | null;\n } | null;\n}\n\n/**\n * GOAL mutation — hands a high-level goal to the server's autonomous browser agent (computer-use loop or\n * backend native AI) instead of relaying a granular action. The server plans + executes; we await the\n * terminal outcome. (Live per-step narration is a server-bridged concern; over this client-direct mutation\n * we get the final result.)\n */\nconst EXECUTE_REMOTE_BROWSER_GOAL_MUTATION = `\n mutation ExecuteRemoteBrowserGoal($agentSessionID: String!, $goal: String!, $startUrl: String) {\n ExecuteRemoteBrowserGoal(agentSessionID: $agentSessionID, goal: $goal, startUrl: $startUrl) {\n Success\n GoalRunID\n Strategy\n CurrentUrl\n Status\n StepCount\n Detail\n }\n }\n`;\n\n/**\n * POLL query for the async goal run. `ExecuteRemoteBrowserGoal` STARTS the (potentially multi-minute)\n * goal loop and returns a `GoalRunID` with `Status: 'Running'`; the channel polls this until the run\n * reports a terminal status — so no single request is held open for the whole loop (which would die at\n * a browser/proxy/janitor transport timeout and lose the result).\n */\nconst GET_REMOTE_BROWSER_GOAL_RESULT_QUERY = `\n query GetRemoteBrowserGoalResult($agentSessionID: String!, $goalRunID: String!) {\n GetRemoteBrowserGoalResult(agentSessionID: $agentSessionID, goalRunID: $goalRunID) {\n Success\n GoalRunID\n Strategy\n CurrentUrl\n Status\n StepCount\n Detail\n }\n }\n`;\n\n/** The narrow projection of a goal-result payload (shared by the start mutation + the poll query). */\ninterface RemoteBrowserGoalPayload {\n Success: boolean;\n GoalRunID: string | null;\n Strategy: string | null;\n CurrentUrl: string | null;\n Status: string | null;\n StepCount: number | null;\n Detail: string | null;\n}\n\n/** The narrow projection of the `ExecuteRemoteBrowserGoal` (start) mutation payload the channel reads. */\ninterface ExecuteRemoteBrowserGoalResult {\n ExecuteRemoteBrowserGoal: RemoteBrowserGoalPayload | null;\n}\n\n/** The narrow projection of the `GetRemoteBrowserGoalResult` (poll) query payload the channel reads. */\ninterface GetRemoteBrowserGoalResultPayload {\n GetRemoteBrowserGoalResult: RemoteBrowserGoalPayload | null;\n}\n\n/**\n * GraphQL mutation that asks the server to start PUSHING live CDP screencast frames for the session.\n * Returns `Streaming: true` when the backend supports `ScreenStreaming` and the stream started; the\n * channel then drives the surface's canvas. `Streaming: false` (capability absent / start failed) leaves\n * the surface on its 700ms snapshot poll fallback.\n */\nconst START_SCREENCAST_MUTATION = `\n mutation StartRemoteBrowserScreencast($agentSessionID: String!) {\n StartRemoteBrowserScreencast(agentSessionID: $agentSessionID) {\n Streaming\n }\n }\n`;\n\n/** GraphQL mutation that stops the server-pushed screencast (best-effort teardown). */\nconst STOP_SCREENCAST_MUTATION = `\n mutation StopRemoteBrowserScreencast($agentSessionID: String!) {\n StopRemoteBrowserScreencast(agentSessionID: $agentSessionID)\n }\n`;\n\n/**\n * GraphQL mutation that asks the server to start PUSHING live tab-audio chunks for the session — so a\n * co-agent demoing a video/audio site is HEARD. Returns `Streaming: true` when the backend can capture\n * audio (v1 gates by backend implementation) and the stream started; `Streaming: false` means no audio\n * (capability absent / start failed) and the client simply plays nothing.\n */\nconst START_AUDIO_STREAM_MUTATION = `\n mutation StartRemoteBrowserAudioStream($agentSessionID: String!) {\n StartRemoteBrowserAudioStream(agentSessionID: $agentSessionID) {\n Streaming\n }\n }\n`;\n\n/** GraphQL mutation that stops the server-pushed tab-audio stream (best-effort teardown). */\nconst STOP_AUDIO_STREAM_MUTATION = `\n mutation StopRemoteBrowserAudioStream($agentSessionID: String!) {\n StopRemoteBrowserAudioStream(agentSessionID: $agentSessionID)\n }\n`;\n\n/** The narrow projection of the `StartRemoteBrowserAudioStream` mutation payload the channel reads. */\ninterface StartRemoteBrowserAudioStreamResult {\n StartRemoteBrowserAudioStream: {\n Streaming: boolean;\n } | null;\n}\n\n/**\n * HUMAN-TAKEOVER mutation — relays ONE pointer/keyboard input the user performed on the live canvas into the\n * server-hosted browser (Collaborative control). Best-effort: the server returns `false` when the backend\n * lacks `HumanTakeover` or no live browser exists; the channel doesn't act on the boolean.\n */\nconst RELAY_HUMAN_INPUT_MUTATION = `\n mutation RelayRemoteBrowserHumanInput(\n $agentSessionID: String!, $kind: String!, $x: Float, $y: Float, $button: String, $key: String,\n $text: String, $deltaX: Float, $deltaY: Float, $modifiers: String\n ) {\n RelayRemoteBrowserHumanInput(\n agentSessionID: $agentSessionID, kind: $kind, x: $x, y: $y, button: $button, key: $key,\n text: $text, deltaX: $deltaX, deltaY: $deltaY, modifiers: $modifiers\n )\n }\n`;\n\n/**\n * COPY-OUT query — reads the remote page's current text selection so the surface can write it to the LOCAL\n * clipboard on a `copy` / Cmd+C (the mirror of the `'text'` paste-in path). Returns `''` when nothing is\n * selected, no live browser exists, or the backend lacks `HumanTakeover`.\n */\nconst GET_SELECTION_QUERY = `\n query GetRemoteBrowserSelection($agentSessionID: String!) {\n GetRemoteBrowserSelection(agentSessionID: $agentSessionID) {\n Text\n }\n }\n`;\n\n/** The narrow projection of the `GetRemoteBrowserSelection` query payload the channel reads. */\ninterface GetRemoteBrowserSelectionResult {\n GetRemoteBrowserSelection: {\n Text: string;\n } | null;\n}\n\n/** The narrow projection of the `StartRemoteBrowserScreencast` mutation payload the channel reads. */\ninterface StartRemoteBrowserScreencastResult {\n StartRemoteBrowserScreencast: {\n Streaming: boolean;\n } | null;\n}\n\n/** GraphQL query the surface fetcher runs — the live screenshot + URL of the server browser. */\nconst REMOTE_BROWSER_SNAPSHOT_QUERY = `\n query RemoteBrowserSnapshot($agentSessionID: String!) {\n RemoteBrowserSnapshot(agentSessionID: $agentSessionID) {\n ScreenshotBase64\n CurrentUrl\n }\n }\n`;\n\n/** The narrow projection of the `RemoteBrowserSnapshot` query payload the channel reads. */\ninterface RemoteBrowserSnapshotResult {\n RemoteBrowserSnapshot: RemoteBrowserSnapshotView | null;\n}\n\n/**\n * VISION-QUERY mutation (separate from {@link EXECUTE_REMOTE_BROWSER_ACTION_MUTATION} — this OBSERVES the page,\n * it does not drive it). The server captures the live screenshot and runs a fast vision model over it, so the\n * non-omnimodal voice agent can \"see\" the page: a text description and/or localized elements with pixel\n * centroids. Backs both `browser_DescribePage` (no target) and `browser_LocateElement` (a target description).\n */\nconst INTERPRET_PAGE_MUTATION = `\n mutation InterpretRemoteBrowserPage($agentSessionID: String!, $query: String) {\n InterpretRemoteBrowserPage(agentSessionID: $agentSessionID, query: $query) {\n Description\n Elements {\n Label\n X\n Y\n Confidence\n }\n Detail\n }\n }\n`;\n\n/** One element localized by the visual interpreter — label + pixel centroid + confidence. */\ninterface RemoteBrowserInterpretedElement {\n Label: string;\n X: number;\n Y: number;\n Confidence: number;\n}\n\n/** The narrow projection of the `InterpretRemoteBrowserPage` mutation payload the channel reads. */\ninterface InterpretRemoteBrowserPageResult {\n InterpretRemoteBrowserPage: {\n Description: string | null;\n Elements: RemoteBrowserInterpretedElement[];\n Detail: string | null;\n } | null;\n}\n\n/** The result payload (serialized to JSON) the channel returns to the model per tool call. */\ninterface RemoteBrowserToolResult {\n success: boolean;\n /** The page URL after the action, when the server reported one. */\n currentUrl?: string;\n /** Human-readable detail (e.g. extracted page text for `browser_GetPageText`, or an error). */\n detail?: string;\n /** Error description when `success` is false. */\n error?: string;\n}\n\n/**\n * The REMOTE BROWSER as a pluggable interactive channel — a {@link BaseRealtimeChannelClient}\n * resolved from the `MJ: AI Agent Channels` registry row whose `ClientPluginClass` is\n * `'RealtimeRemoteBrowserChannel'`. One instance per session (ClassFactory-created at session\n * start).\n *\n * Topology (CLIENT-DIRECT, like the whiteboard — the browser just happens to live on the\n * server):\n * - **Action**: the `browser_*` client-executed tool set ({@link REMOTE_BROWSER_TOOL_DEFINITIONS}).\n * {@link ApplyAgentTool} maps a tool call → a normalized {@link RemoteBrowserAction}\n * ({@link MapToolToAction}) and awaits the `ExecuteRemoteBrowserAction` GraphQL mutation\n * (via {@link RealtimeChannelContext.ExecuteServerAction}) to drive the server browser,\n * returning a concise JSON result for the model. Never throws — argument and transport\n * failures map to a `{ success: false, error }` payload.\n * - **Perception**: after a successful action, a `[browser]` context note carries the new\n * page URL so the agent perceives where the page went; the surface independently polls a\n * live screenshot.\n * - **Surface**: {@link RemoteBrowserSurfaceComponent}, created dynamically in the channel\n * tab; the plugin hands it the session's provider + id in {@link BindSurface} so it can\n * poll `RemoteBrowserSnapshot`. HUMAN TAKEOVER is enabled by default (Collaborative): the\n * user can click/type into the live canvas and those events relay to the server browser via\n * {@link RELAY_HUMAN_INPUT_MUTATION} (server-gated on the backend's `HumanTakeover` capability).\n *\n * The channel keeps NO client-side state of record — the browser's state lives entirely on\n * the server — so {@link SerializeState} / {@link RestoreState} use the base no-op behavior.\n */\n@RegisterClass(BaseRealtimeChannelClient, 'RealtimeRemoteBrowserChannel')\nexport class RemoteBrowserChannel extends BaseRealtimeChannelClient<RemoteBrowserSurfaceComponent> {\n /** The live bound surface, when the channel tab's pane is instantiated. */\n private surface: RemoteBrowserSurfaceComponent | null = null;\n\n /** Subscription to the bound surface's `HumanInput` output, torn down on unbind/dispose. */\n private humanInputSub: Subscription | null = null;\n\n /**\n * Whether the server confirmed it is PUSHING screencast frames for this session (`ScreenStreaming`\n * supported). When `true` the surface renders pushed frames on its canvas and skips its poll, and\n * {@link OnScreencastFrame} forwards each pushed frame to it. When `false` the surface keeps polling.\n */\n private streaming = false;\n\n /**\n * Whether the server confirmed it is PUSHING tab-audio chunks for this session (the backend can capture\n * audio). When `true` {@link OnAudioChunk} feeds each pushed chunk to {@link audioPlayer}; when `false`\n * no audio plays.\n */\n private audioStreaming = false;\n\n /**\n * The last URL the agent was TOLD about, so an unchanged page is not re-announced (#3496). `null`\n * until the first sighting, which is deliberately announced as a plain \"current page\" rather than\n * as somebody else navigating — the first page of a session was nobody's takeover.\n */\n private lastNotedUrl: string | null = null;\n\n /**\n * How many browser operations the AGENT itself has in flight right now (#3496).\n *\n * Attribution cannot be read off the feed a change arrives on. A pushed frame or a perception poll\n * only says the page moved; it is `> 0` here that says the agent is the one moving it. Two cases\n * make that necessary rather than tidy:\n *\n * - **an action's frames outrun its reply.** Under streaming, frames of the new page are pushed\n * while `ExecuteRemoteBrowserAction` is still in flight, so the observation reliably lands BEFORE\n * the mutation returns the URL — the agent's own navigation announced back to it as a takeover.\n * - **a goal drives for minutes.** `browser_AchieveGoal` runs an autonomous loop server-side and\n * every page it opens is observed with nothing yet returned, so the whole run would narrate\n * itself as somebody else.\n *\n * A counter, not a flag: goals and actions overlap, and the last one to finish must not clear a\n * window another still owns.\n */\n private agentDrivingDepth = 0;\n\n /** The client-side audio player fed pushed chunks while {@link audioStreaming}; `null` until started. */\n private audioPlayer: RemoteBrowserAudioPlayer | null = null;\n\n /** Subscription to the bound surface's `AudioMutedChange` output (the speaker toggle), torn down on unbind. */\n private audioMutedSub: Subscription | null = null;\n\n /**\n * Poll cadence + deadline for {@link pollGoalResult} (the async `browser_AchieveGoal` result poll).\n * Protected so tests can shrink them; production polls every 2.5s for up to 5 minutes (a goal loop\n * past 5 min keeps running server-side, the model just gets a \"still running\" note).\n */\n protected GoalPollIntervalMs = 2500;\n protected GoalPollTimeoutMs = 5 * 60 * 1000;\n\n public get ChannelName(): string {\n return 'Remote Browser';\n }\n\n public get ToolNamePrefix(): string {\n return REMOTE_BROWSER_TOOL_PREFIX;\n }\n\n public get TabTitle(): string {\n return 'Browser';\n }\n\n public get TabIcon(): string {\n return 'fa-solid fa-globe';\n }\n\n public GetToolDefinitions(): RealtimeToolDefinition[] {\n return REMOTE_BROWSER_TOOL_DEFINITIONS;\n }\n\n public override GetSurfaceComponent(): Type<RemoteBrowserSurfaceComponent> {\n return RemoteBrowserSurfaceComponent;\n }\n\n /** First-run intro shown the first time the user opens the Browser tab (once per user). */\n public override GetOnboardingDetails(): ChannelOnboardingDetails {\n return {\n Heading: 'Remote Browser',\n Description:\n 'The agent drives a live web browser on the server and you can watch it work right here. ' +\n 'You can also grab the wheel: click and type directly on the live page and the agent picks ' +\n 'up right where you left off.',\n Tips: [\n 'Ask the agent to look something up, open a site or fill in a form — it controls the page.',\n 'Click on the live view to take over, then click and type to drive it yourself — the \"You\\'re driving\" badge shows when takeover is active.',\n 'The current page URL is shown so you always know where the agent has navigated.',\n ],\n IconClass: 'fa-solid fa-globe',\n };\n }\n\n /**\n * Wires the dynamically-created surface: hands it a snapshot fetcher closing over the\n * channel context (session id + provider live there), so the surface stays transport-\n * agnostic. Set BEFORE the surface's first change detection (the pane binds synchronously),\n * so its `ngOnInit` poll has the fetcher.\n *\n * Also asks the server to start a live screencast. When the backend supports `ScreenStreaming` the\n * server starts PUSHING frames and reports `Streaming: true`; we flip the surface to canvas mode (its\n * poll is then skipped) and {@link OnScreencastFrame} paints each pushed frame. When the backend lacks\n * the capability (`Streaming: false`) the surface keeps the snapshot poll already running — graceful\n * fallback, no further action.\n *\n * HUMAN TAKEOVER is enabled BY DEFAULT (Collaborative): the surface is flipped `Interactive = true` and\n * its `HumanInput` output subscribed, forwarding each pointer/keyboard event to the server via\n * {@link RELAY_HUMAN_INPUT_MUTATION}. Takeover only takes effect on the canvas (screencast) path; the\n * `<img>` poll fallback stays view-only. The server gates the actual routing on the backend's\n * `HumanTakeover` capability, so this is safe to enable unconditionally.\n */\n public BindSurface(instance: RemoteBrowserSurfaceComponent): void {\n this.surface = instance;\n instance.Fetch = () => this.fetchSnapshot();\n // Copy-out: the surface reads the remote selection through this on a local `copy` and writes it locally.\n instance.FetchSelection = () => this.fetchSelection();\n instance.Interactive = true;\n this.humanInputSub = instance.HumanInput.subscribe((input) => this.relayHumanInput(input));\n this.audioMutedSub = instance.AudioMutedChange.subscribe((muted) => this.audioPlayer?.SetMuted(muted));\n void this.startScreencast(instance);\n void this.startAudioStream(instance);\n }\n\n public override UnbindSurface(): void {\n this.humanInputSub?.unsubscribe();\n this.humanInputSub = null;\n this.audioMutedSub?.unsubscribe();\n this.audioMutedSub = null;\n void this.stopScreencast();\n void this.stopAudioStream();\n this.surface = null;\n }\n\n public override Dispose(): void {\n this.humanInputSub?.unsubscribe();\n this.humanInputSub = null;\n this.audioMutedSub?.unsubscribe();\n this.audioMutedSub = null;\n void this.stopScreencast();\n void this.stopAudioStream();\n super.Dispose();\n }\n\n /**\n * Forwards one PUSHED screencast frame to the bound surface's canvas. Called by the session service\n * when a `RemoteBrowserScreencastFrame` arrives on the push-status stream for THIS session. No-op when\n * the channel isn't streaming or has no bound surface (e.g. the tab pane is collapsed).\n *\n * Under streaming the surface's snapshot poll is stopped, so this is the ONLY thing that sees the\n * page while frames are being pushed — which is why the frame now carries the URL (#3496). Without\n * it, a user navigating during a screencast changed the picture and nothing else: the agent kept\n * describing the page it last opened, and the pixels proving otherwise were right there on screen.\n *\n * @param dataBase64 The frame image as raw base64 JPEG (no `data:` prefix).\n * @param currentUrl The browser's URL when the frame was captured; absent from older servers.\n */\n public OnScreencastFrame(dataBase64: string, currentUrl?: string | null): void {\n if (this.streaming) {\n this.surface?.RenderFrame(dataBase64);\n this.notePageChange(currentUrl, 'observed');\n }\n }\n\n /**\n * Feeds one PUSHED tab-audio chunk to the client-side audio player. Called by the session service when a\n * `RemoteBrowserAudioChunk` arrives on the push-status stream for THIS session. No-op when the channel\n * isn't audio-streaming or the player has been torn down.\n *\n * @param chunk The pushed audio chunk (base64 webm-opus + codec/seq metadata).\n */\n public OnAudioChunk(chunk: RemoteBrowserAudioChunkInput): void {\n if (this.audioStreaming) {\n this.audioPlayer?.Enqueue(chunk);\n }\n }\n\n /**\n * Asks the server to start the live screencast and, on success, flips the surface to canvas mode so it\n * stops polling and starts painting pushed frames. Best-effort: a `null`/`Streaming: false` result\n * (capability absent or transport failure) leaves the surface on its poll fallback.\n */\n private async startScreencast(instance: RemoteBrowserSurfaceComponent): Promise<void> {\n const sessionId = this.Context?.AgentSessionID;\n if (!sessionId) {\n return;\n }\n const data = await this.Context?.ExecuteServerAction<StartRemoteBrowserScreencastResult>(START_SCREENCAST_MUTATION, { agentSessionID: sessionId });\n if (data?.StartRemoteBrowserScreencast?.Streaming === true) {\n this.streaming = true;\n instance.Streaming = true;\n }\n }\n\n /**\n * Asks the server to start the live tab-audio stream and, on success, spins up the client-side\n * {@link RemoteBrowserAudioPlayer} (speaker ON by default — the call is the activating user gesture) so\n * pushed chunks play. Best-effort: a `null`/`Streaming: false` result (no audio capability or transport\n * failure) leaves the channel silent with no player.\n *\n * @param instance The bound surface, flipped to show the speaker toggle when audio starts.\n */\n private async startAudioStream(instance: RemoteBrowserSurfaceComponent): Promise<void> {\n const sessionId = this.Context?.AgentSessionID;\n if (!sessionId) {\n return;\n }\n const data = await this.Context?.ExecuteServerAction<StartRemoteBrowserAudioStreamResult>(START_AUDIO_STREAM_MUTATION, { agentSessionID: sessionId });\n if (data?.StartRemoteBrowserAudioStream?.Streaming === true) {\n this.audioStreaming = true;\n this.audioPlayer = new RemoteBrowserAudioPlayer(new MediaSourceAudioSink());\n // Speaker defaults ON; reflect that on the surface so the toggle renders un-muted.\n instance.AudioAvailable = true;\n instance.AudioMuted = false;\n }\n }\n\n /** Asks the server to stop the tab-audio stream (best-effort), disposes the player, and clears state. */\n private async stopAudioStream(): Promise<void> {\n const wasStreaming = this.audioStreaming;\n this.audioStreaming = false;\n this.audioPlayer?.Dispose();\n this.audioPlayer = null;\n const sessionId = this.Context?.AgentSessionID;\n if (!wasStreaming || !sessionId) {\n return;\n }\n await this.Context?.ExecuteServerAction(STOP_AUDIO_STREAM_MUTATION, { agentSessionID: sessionId });\n }\n\n /**\n * Relays ONE human-takeover input from the surface to the server's {@link RELAY_HUMAN_INPUT_MUTATION}.\n * Best-effort and fire-and-forget: never throws, and the boolean result is ignored (the server already\n * gates routing on the backend's `HumanTakeover` capability). No-op when no live session id exists.\n *\n * @param input The flattened pointer/keyboard input the user performed on the live canvas.\n */\n private relayHumanInput(input: RemoteBrowserHumanInputEvent): void {\n const sessionId = this.Context?.AgentSessionID;\n if (!sessionId) {\n return;\n }\n void this.Context?.ExecuteServerAction(RELAY_HUMAN_INPUT_MUTATION, {\n agentSessionID: sessionId,\n kind: input.kind,\n x: input.x ?? null,\n y: input.y ?? null,\n button: input.button ?? null,\n key: input.key ?? null,\n // Paste-in: the `'text'` kind carries the local clipboard text the server inserts into the page.\n text: input.text ?? null,\n deltaX: input.deltaX ?? null,\n deltaY: input.deltaY ?? null,\n // Server expects a comma-separated modifier list (e.g. \"Shift,Control\"); null when none held.\n modifiers: input.modifiers && input.modifiers.length > 0 ? input.modifiers.join(',') : null,\n });\n }\n\n /**\n * Reads the remote page's current selection for COPY-OUT — backs the surface's `FetchSelection`. Awaits\n * the `GetRemoteBrowserSelection` query and returns its text. Best-effort by contract\n * ({@link RemoteBrowserSelectionFetcher}): returns `''` when no session is live or the query fails, so a\n * best-effort copy never throws.\n */\n private async fetchSelection(): Promise<string> {\n const sessionId = this.Context?.AgentSessionID;\n if (!sessionId) {\n return '';\n }\n const data = await this.Context?.ExecuteServerAction<GetRemoteBrowserSelectionResult>(GET_SELECTION_QUERY, { agentSessionID: sessionId });\n return data?.GetRemoteBrowserSelection?.Text ?? '';\n }\n\n /** Asks the server to stop the screencast (best-effort) and clears the streaming flag. */\n private async stopScreencast(): Promise<void> {\n const wasStreaming = this.streaming;\n this.streaming = false;\n const sessionId = this.Context?.AgentSessionID;\n if (!wasStreaming || !sessionId) {\n return;\n }\n await this.Context?.ExecuteServerAction(STOP_SCREENCAST_MUTATION, { agentSessionID: sessionId });\n }\n\n /**\n * Fetches one live snapshot of the server browser for the surface — the PERCEPTION poll.\n * Best-effort by contract ({@link RemoteBrowserSnapshotFetcher}): returns `null` when no\n * session is live or the query fails (the surface keeps its last good frame).\n *\n * Also the channel's chance to NOTICE a page change it did not cause (#3496): the poll already\n * carries the URL, and until now only the surface read it.\n */\n private async fetchSnapshot(): Promise<RemoteBrowserSnapshotView | null> {\n const sessionId = this.Context?.AgentSessionID;\n if (!sessionId) {\n return null;\n }\n const data = await this.Context?.ExecuteServerAction<RemoteBrowserSnapshotResult>(REMOTE_BROWSER_SNAPSHOT_QUERY, { agentSessionID: sessionId });\n const snapshot = data?.RemoteBrowserSnapshot ?? null;\n this.notePageChange(snapshot?.CurrentUrl ?? null, 'observed');\n return snapshot;\n }\n\n /**\n * The ONE place the agent is told where the page is (#3496).\n *\n * Before this, the `[browser] current page:` note was pushed from exactly two call sites, both\n * immediately after a server action the MODEL itself initiated. That placement — not any caching —\n * was the bug: **a surface change the agent did not cause was invisible to it.** A user taking over\n * and navigating produced no note, so the agent went on confidently describing the previous page,\n * and only corrected when told to look again. Human takeover is on by default for `Collaborative`\n * providers, so the default configuration was the broken one.\n *\n * Routing every observation through here makes the rule \"the agent hears about the page whenever it\n * MOVES, whoever moved it\" — a property of this method rather than of where callers happen to be.\n *\n * `cause` is what the agent could never work out for itself. An `'agent'` change is one this\n * channel just performed; anything spotted by the perception poll or a pushed frame is\n * `'observed'` — someone else is driving, and saying so is the difference between the agent\n * knowing the page moved and knowing it is no longer the one moving it.\n *\n * Silent when the URL has not changed, which is not an optimisation but a requirement: the\n * perception poll runs every ~700ms and an unconditional note would bury the conversation in\n * hundreds of identical lines.\n */\n private notePageChange(url: string | null | undefined, cause: 'agent' | 'observed'): void {\n const next = (url ?? '').trim();\n if (next.length === 0) {\n return;\n }\n // The live-view bar is refreshed even when the URL is unchanged: it is idempotent, and in\n // streaming mode it is the only thing keeping the bar off \"No page loaded yet\".\n this.surface?.SetCurrentUrl(next);\n if (next === this.lastNotedUrl) {\n return;\n }\n const firstSighting = this.lastNotedUrl === null;\n this.lastNotedUrl = next;\n // A change seen WHILE the agent is driving is the agent's own, whichever feed spotted it first.\n const byAgent = cause === 'agent' || this.agentDrivingDepth > 0;\n this.Context?.SendContextNote(\n byAgent || firstSighting\n ? `[browser] current page: ${next}`\n : `[browser] the page changed to: ${next} — you did not navigate here, so someone else is driving. `\n + `Describe THIS page, not the one you last opened.`,\n );\n }\n\n /**\n * Runs one agent-initiated browser operation with {@link agentDrivingDepth} raised, so any page\n * change observed while it runs is attributed to the agent rather than to a phantom third party.\n *\n * `finally` and not a trailing decrement: the window must close on a thrown transport error too,\n * or one failed tool call would leave the channel permanently unable to report a real takeover.\n */\n private async whileAgentDrives<T>(work: () => Promise<T>): Promise<T> {\n this.agentDrivingDepth++;\n try {\n return await work();\n } finally {\n this.agentDrivingDepth--;\n }\n }\n\n /**\n * Executes ONE `browser_*` tool call: maps it to a server action and awaits the\n * `ExecuteRemoteBrowserAction` mutation, then returns a concise JSON result the model reads.\n * On success it also feeds the new page URL to the agent as a `[browser]` context note. Never\n * throws — bad arguments, no live session, and server failures all become a failed-result\n * payload so the model can narrate the problem.\n */\n public async ApplyAgentTool(toolName: string, argsJson: string): Promise<string> {\n // VISION-QUERY branch: the two interpreter tools OBSERVE the page (run a vision model over a screenshot)\n // rather than drive it, so they route to the separate interpret mutation, not MapToolToAction.\n if (toolName === REMOTE_BROWSER_TOOL_NAMES.DescribePage) {\n return this.interpretPage(undefined);\n }\n if (toolName === REMOTE_BROWSER_TOOL_NAMES.AchieveGoal) {\n return this.achieveGoal(argsJson);\n }\n if (toolName === REMOTE_BROWSER_TOOL_NAMES.LocateElement) {\n const description = this.asArgString(this.parseArgs(argsJson)['description']);\n if (!description) {\n return this.fail('browser_LocateElement requires a \"description\" of the element to find.');\n }\n return this.interpretPage(description);\n }\n\n let action: RemoteBrowserAction;\n try {\n action = MapToolToAction(toolName, this.parseArgs(argsJson));\n } catch (error) {\n const message = error instanceof RemoteBrowserToolArgError ? error.message : 'Invalid Remote Browser tool arguments.';\n return this.fail(message);\n }\n\n // Briefly WAIT for the session id rather than failing instantly: the model can fire a tool the\n // first beat it connects, before mintSession binds the id (the \"session id missing\" race).\n const sessionId = await this.ResolveAgentSessionId();\n if (!sessionId) {\n // Diagnostic: distinguishes \"channel never Initialized (no Context)\" from \"session id never\n // resolved within the wait window\" — the two distinct causes of a null id at tool-execution time.\n console.warn('[RemoteBrowser] browser tool invoked but AgentSessionID is null', {\n tool: toolName,\n hasContext: !!this.Context,\n agentSessionID: this.Context?.AgentSessionID ?? null,\n });\n return this.fail('No live browser session is available yet — the realtime session may still be connecting; try again in a moment.');\n }\n\n return this.whileAgentDrives(async () => {\n const data = await this.Context?.ExecuteServerAction<ExecuteRemoteBrowserActionResult>(\n EXECUTE_REMOTE_BROWSER_ACTION_MUTATION,\n this.buildVariables(sessionId, action),\n );\n const result = data?.ExecuteRemoteBrowserAction ?? null;\n if (!result) {\n return this.fail('The browser action could not be executed (no response from the server).');\n }\n if (!result.Success) {\n return this.fail(result.Detail ?? 'The browser action failed.');\n }\n\n // PERCEPTION: tell the agent where the page is now (background — no spoken reply), and refresh\n // the live-view bar, which in streaming mode has no poll feeding it.\n this.notePageChange(result.CurrentUrl, 'agent');\n return this.ok(result.CurrentUrl, result.Detail);\n });\n }\n\n /** Builds the flat mutation variables from a normalized action (omitted fields ride as null). */\n private buildVariables(sessionId: string, action: RemoteBrowserAction): Record<string, JSONValue> {\n return {\n agentSessionID: sessionId,\n kind: action.Kind,\n url: action.Url ?? null,\n selector: action.Selector ?? null,\n x: action.X ?? null,\n y: action.Y ?? null,\n text: action.Text ?? null,\n key: action.Key ?? null,\n deltaX: action.DeltaX ?? null,\n deltaY: action.DeltaY ?? null,\n ms: action.Ms ?? null,\n };\n }\n\n /**\n * GOAL path for the `browser_AchieveGoal` tool: hands a high-level goal to the server's autonomous\n * browser agent (computer-use loop / native AI) via {@link EXECUTE_REMOTE_BROWSER_GOAL_MUTATION} and\n * returns a concise result for the model. Never throws — failures map to a `{ success: false }` string.\n *\n * @param argsJson The raw tool-call arguments (`{ goal, startUrl? }`).\n * @returns The model-readable result string.\n */\n private async achieveGoal(argsJson: string): Promise<string> {\n // Wait briefly for the session id (mint/reconnect race) before giving up — see ResolveAgentSessionId.\n const sessionId = await this.ResolveAgentSessionId();\n if (!sessionId) {\n return this.fail('No live browser session is available yet — the realtime session may still be connecting; try again in a moment.');\n }\n const args = this.parseArgs(argsJson);\n const goal = this.asArgString(args['goal']);\n if (!goal) {\n return this.fail('browser_AchieveGoal requires a \"goal\" describing what to accomplish.');\n }\n const startUrl = this.asArgString(args['startUrl']) ?? null;\n\n // START the goal (returns immediately with a GoalRunID) then POLL — a goal loop can run for\n // minutes, far longer than any single request survives, so we never hold one open for the loop.\n // The whole span is agent-driven: every page the loop opens is observed with nothing returned\n // yet, so without the window the agent would narrate its own goal as somebody else's takeover.\n return this.whileAgentDrives(async () => {\n const startData = await this.Context?.ExecuteServerAction<ExecuteRemoteBrowserGoalResult>(EXECUTE_REMOTE_BROWSER_GOAL_MUTATION, {\n agentSessionID: sessionId,\n goal,\n startUrl,\n });\n const started = startData?.ExecuteRemoteBrowserGoal ?? null;\n if (!started) {\n return this.fail('The browser goal could not be started (no response from the server).');\n }\n // Defensive: if the server ever returns a terminal status synchronously, use it directly.\n const result = started.Status === 'Running' && started.GoalRunID\n ? await this.pollGoalResult(sessionId, started.GoalRunID)\n : started;\n if (!result) {\n return this.fail('The browser goal is taking longer than expected and could not be confirmed. It may still be running.');\n }\n this.notePageChange(result.CurrentUrl, 'agent');\n if (!result.Success) {\n return this.fail(result.Detail ?? `The goal could not be completed (${result.Status ?? 'unknown'}).`);\n }\n return this.ok(result.CurrentUrl, result.Detail ?? `Goal completed (${result.Status ?? 'Completed'}, ${result.StepCount ?? 0} steps).`);\n });\n }\n\n /**\n * Polls {@link GET_REMOTE_BROWSER_GOAL_RESULT_QUERY} for a started goal run until it reports a\n * terminal status (anything other than `Running`), then returns that terminal payload. Each poll is\n * a short request, so no transport boundary times out while a long goal loop runs server-side.\n *\n * Returns `null` if the goal is still `Running` after {@link GoalPollTimeoutMs} (the loop keeps\n * running server-side; the caller maps this to a \"still running\" message), the session went away, or\n * the run id is no longer known (`Unknown` — expired/superseded).\n */\n private async pollGoalResult(sessionId: string, goalRunID: string): Promise<RemoteBrowserGoalPayload | null> {\n const interval = Math.max(1, this.GoalPollIntervalMs);\n for (let waited = 0; waited < this.GoalPollTimeoutMs; waited += interval) {\n await new Promise<void>((resolve) => setTimeout(resolve, interval));\n // The session ended mid-goal — stop polling (the live browser is gone).\n if (!this.Context?.AgentSessionID) {\n return null;\n }\n const data = await this.Context.ExecuteServerAction<GetRemoteBrowserGoalResultPayload>(GET_REMOTE_BROWSER_GOAL_RESULT_QUERY, {\n agentSessionID: sessionId,\n goalRunID,\n });\n const payload = data?.GetRemoteBrowserGoalResult ?? null;\n // A transient null (one failed poll) shouldn't abort — keep polling until the deadline.\n if (payload && payload.Status !== 'Running') {\n return payload;\n }\n }\n return null;\n }\n\n /**\n * Runs the VISION-QUERY path for the `browser_DescribePage` / `browser_LocateElement` tools: awaits the\n * `InterpretRemoteBrowserPage` mutation (server captures a screenshot + runs a fast vision model) and returns\n * a concise JSON string the model reads. For a describe (no `query`) it returns `{ description }`; for a locate\n * it returns `{ found, element, all }` so the agent can `browser_Click` the centroid. Never throws — a null\n * session, no response, or a server detail-only result all map to a result string the model can narrate.\n *\n * @param query The visual target to locate, or `undefined` for a plain page description.\n * @returns A JSON string describing the interpretation for the model.\n */\n private async interpretPage(query: string | undefined): Promise<string> {\n // Wait briefly for the session id (mint/reconnect race) before giving up — see ResolveAgentSessionId.\n const sessionId = await this.ResolveAgentSessionId();\n if (!sessionId) {\n console.warn('[RemoteBrowser] interpret tool invoked but AgentSessionID is null', {\n hasContext: !!this.Context,\n agentSessionID: this.Context?.AgentSessionID ?? null,\n });\n return this.fail('No live browser session is available yet — the realtime session may still be connecting; try again in a moment.');\n }\n\n const data = await this.Context?.ExecuteServerAction<InterpretRemoteBrowserPageResult>(INTERPRET_PAGE_MUTATION, {\n agentSessionID: sessionId,\n query: query ?? null,\n });\n const result = data?.InterpretRemoteBrowserPage ?? null;\n if (!result) {\n return this.fail('The page could not be interpreted (no response from the server).');\n }\n\n if (query === undefined) {\n // DescribePage — hand back the description (or the server's detail note when nothing was interpreted).\n return JSON.stringify({ description: result.Description ?? result.Detail ?? null });\n }\n\n // LocateElement — surface whether anything matched, the best match, and all candidates.\n const elements = result.Elements ?? [];\n const best = elements[0];\n return JSON.stringify({\n found: elements.length > 0,\n element: best ? { label: best.Label, x: best.X, y: best.Y } : null,\n all: elements,\n ...(result.Detail ? { detail: result.Detail } : {}),\n });\n }\n\n /** Coerces a parsed tool-arg to a non-empty string, or `undefined` when absent / wrong-typed. */\n private asArgString(value: JSONValue | undefined): string | undefined {\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;\n }\n\n /** Parses the tool-args JSON into a plain object; returns `{}` for empty/malformed input. */\n private parseArgs(argsJson: string): Record<string, JSONValue> {\n const trimmed = argsJson?.trim();\n if (!trimmed) {\n return {};\n }\n try {\n const parsed: unknown = JSON.parse(trimmed);\n return parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed) ? (parsed as Record<string, JSONValue>) : {};\n } catch {\n return {};\n }\n }\n\n /** Builds a successful tool-result JSON string for the model. */\n private ok(currentUrl: string | null, detail: string | null): string {\n const result: RemoteBrowserToolResult = { success: true };\n if (currentUrl) {\n result.currentUrl = currentUrl;\n }\n if (detail) {\n result.detail = detail;\n }\n return JSON.stringify(result);\n }\n\n /** Builds a failed tool-result JSON string for the model. */\n private fail(error: string): string {\n const result: RemoteBrowserToolResult = { success: false, error };\n return JSON.stringify(result);\n }\n}\n\n/**\n * Tree-shaking prevention: the Remote Browser channel is resolved dynamically through the\n * ClassFactory (by the registry row's `ClientPluginClass` key), so this static call is what\n * keeps its `@RegisterClass` side effect from being eliminated by the bundler. Called from\n * `conversations.module.ts` alongside {@link LoadRealtimeWhiteboardChannel}.\n */\nexport function LoadRealtimeRemoteBrowserChannel(): void {\n // intentional no-op — the import side effect performs the registration\n}\n"]}
|
|
1
|
+
{"version":3,"file":"remote-browser-channel.js","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/remote-browser/remote-browser-channel.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Type } from '@angular/core';\nimport type { Subscription } from 'rxjs';\nimport { RegisterClass } from '@memberjunction/global';\nimport { CHANNEL_INBOUND_VIDEO_TRACK, JSONValue, RealtimeToolDefinition, RealtimeTrack, RealtimeTrackDescriptor } from '@memberjunction/ai';\nimport { ChannelInboundVideoBridge, IChannelFrameProvider } from '@memberjunction/ai-realtime-client';\nimport { BaseRealtimeChannelClient, ChannelOnboardingDetails } from '../channels/base-realtime-channel-client';\nimport { RemoteBrowserHumanInputEvent, RemoteBrowserSnapshotView, RemoteBrowserSurfaceComponent } from './remote-browser-surface.component';\nimport {\n MapToolToAction,\n REMOTE_BROWSER_TOOL_DEFINITIONS,\n REMOTE_BROWSER_TOOL_NAMES,\n REMOTE_BROWSER_TOOL_PREFIX,\n RemoteBrowserAction,\n RemoteBrowserToolArgError,\n} from './remote-browser-tools';\nimport { MediaSourceAudioSink, RemoteBrowserAudioChunkInput, RemoteBrowserAudioPlayer } from './remote-browser-audio-player';\n\n/**\n * GraphQL mutation that drives the SERVER-hosted browser. The channel awaits it for every\n * `browser_*` tool call and feeds the result back to the model.\n */\nconst EXECUTE_REMOTE_BROWSER_ACTION_MUTATION = `\n mutation ExecuteRemoteBrowserAction(\n $agentSessionID: String!, $kind: String!, $url: String, $selector: String,\n $x: Float, $y: Float, $text: String, $key: String,\n $deltaX: Float, $deltaY: Float, $ms: Float\n ) {\n ExecuteRemoteBrowserAction(\n agentSessionID: $agentSessionID, kind: $kind, url: $url, selector: $selector,\n x: $x, y: $y, text: $text, key: $key,\n deltaX: $deltaX, deltaY: $deltaY, ms: $ms\n ) {\n Success\n CurrentUrl\n Detail\n }\n }\n`;\n\n/** The narrow projection of the `ExecuteRemoteBrowserAction` mutation payload the channel reads. */\ninterface ExecuteRemoteBrowserActionResult {\n ExecuteRemoteBrowserAction: {\n Success: boolean;\n CurrentUrl: string | null;\n Detail: string | null;\n } | null;\n}\n\n/**\n * GOAL mutation — hands a high-level goal to the server's autonomous browser agent (computer-use loop or\n * backend native AI) instead of relaying a granular action. The server plans + executes; we await the\n * terminal outcome. (Live per-step narration is a server-bridged concern; over this client-direct mutation\n * we get the final result.)\n */\nconst EXECUTE_REMOTE_BROWSER_GOAL_MUTATION = `\n mutation ExecuteRemoteBrowserGoal($agentSessionID: String!, $goal: String!, $startUrl: String) {\n ExecuteRemoteBrowserGoal(agentSessionID: $agentSessionID, goal: $goal, startUrl: $startUrl) {\n Success\n GoalRunID\n Strategy\n CurrentUrl\n Status\n StepCount\n Detail\n }\n }\n`;\n\n/**\n * POLL query for the async goal run. `ExecuteRemoteBrowserGoal` STARTS the (potentially multi-minute)\n * goal loop and returns a `GoalRunID` with `Status: 'Running'`; the channel polls this until the run\n * reports a terminal status — so no single request is held open for the whole loop (which would die at\n * a browser/proxy/janitor transport timeout and lose the result).\n */\nconst GET_REMOTE_BROWSER_GOAL_RESULT_QUERY = `\n query GetRemoteBrowserGoalResult($agentSessionID: String!, $goalRunID: String!) {\n GetRemoteBrowserGoalResult(agentSessionID: $agentSessionID, goalRunID: $goalRunID) {\n Success\n GoalRunID\n Strategy\n CurrentUrl\n Status\n StepCount\n Detail\n }\n }\n`;\n\n/** The narrow projection of a goal-result payload (shared by the start mutation + the poll query). */\ninterface RemoteBrowserGoalPayload {\n Success: boolean;\n GoalRunID: string | null;\n Strategy: string | null;\n CurrentUrl: string | null;\n Status: string | null;\n StepCount: number | null;\n Detail: string | null;\n}\n\n/** The narrow projection of the `ExecuteRemoteBrowserGoal` (start) mutation payload the channel reads. */\ninterface ExecuteRemoteBrowserGoalResult {\n ExecuteRemoteBrowserGoal: RemoteBrowserGoalPayload | null;\n}\n\n/** The narrow projection of the `GetRemoteBrowserGoalResult` (poll) query payload the channel reads. */\ninterface GetRemoteBrowserGoalResultPayload {\n GetRemoteBrowserGoalResult: RemoteBrowserGoalPayload | null;\n}\n\n/**\n * GraphQL mutation that asks the server to start PUSHING live CDP screencast frames for the session.\n * Returns `Streaming: true` when the backend supports `ScreenStreaming` and the stream started; the\n * channel then drives the surface's canvas. `Streaming: false` (capability absent / start failed) leaves\n * the surface on its 700ms snapshot poll fallback.\n */\nconst START_SCREENCAST_MUTATION = `\n mutation StartRemoteBrowserScreencast($agentSessionID: String!) {\n StartRemoteBrowserScreencast(agentSessionID: $agentSessionID) {\n Streaming\n }\n }\n`;\n\n/** GraphQL mutation that stops the server-pushed screencast (best-effort teardown). */\nconst STOP_SCREENCAST_MUTATION = `\n mutation StopRemoteBrowserScreencast($agentSessionID: String!) {\n StopRemoteBrowserScreencast(agentSessionID: $agentSessionID)\n }\n`;\n\n/**\n * GraphQL mutation that asks the server to start PUSHING live tab-audio chunks for the session — so a\n * co-agent demoing a video/audio site is HEARD. Returns `Streaming: true` when the backend can capture\n * audio (v1 gates by backend implementation) and the stream started; `Streaming: false` means no audio\n * (capability absent / start failed) and the client simply plays nothing.\n */\nconst START_AUDIO_STREAM_MUTATION = `\n mutation StartRemoteBrowserAudioStream($agentSessionID: String!) {\n StartRemoteBrowserAudioStream(agentSessionID: $agentSessionID) {\n Streaming\n }\n }\n`;\n\n/** GraphQL mutation that stops the server-pushed tab-audio stream (best-effort teardown). */\nconst STOP_AUDIO_STREAM_MUTATION = `\n mutation StopRemoteBrowserAudioStream($agentSessionID: String!) {\n StopRemoteBrowserAudioStream(agentSessionID: $agentSessionID)\n }\n`;\n\n/** The narrow projection of the `StartRemoteBrowserAudioStream` mutation payload the channel reads. */\ninterface StartRemoteBrowserAudioStreamResult {\n StartRemoteBrowserAudioStream: {\n Streaming: boolean;\n } | null;\n}\n\n/**\n * HUMAN-TAKEOVER mutation — relays ONE pointer/keyboard input the user performed on the live canvas into the\n * server-hosted browser (Collaborative control). Best-effort: the server returns `false` when the backend\n * lacks `HumanTakeover` or no live browser exists; the channel doesn't act on the boolean.\n */\nconst RELAY_HUMAN_INPUT_MUTATION = `\n mutation RelayRemoteBrowserHumanInput(\n $agentSessionID: String!, $kind: String!, $x: Float, $y: Float, $button: String, $key: String,\n $text: String, $deltaX: Float, $deltaY: Float, $modifiers: String\n ) {\n RelayRemoteBrowserHumanInput(\n agentSessionID: $agentSessionID, kind: $kind, x: $x, y: $y, button: $button, key: $key,\n text: $text, deltaX: $deltaX, deltaY: $deltaY, modifiers: $modifiers\n )\n }\n`;\n\n/**\n * COPY-OUT query — reads the remote page's current text selection so the surface can write it to the LOCAL\n * clipboard on a `copy` / Cmd+C (the mirror of the `'text'` paste-in path). Returns `''` when nothing is\n * selected, no live browser exists, or the backend lacks `HumanTakeover`.\n */\nconst GET_SELECTION_QUERY = `\n query GetRemoteBrowserSelection($agentSessionID: String!) {\n GetRemoteBrowserSelection(agentSessionID: $agentSessionID) {\n Text\n }\n }\n`;\n\n/** The narrow projection of the `GetRemoteBrowserSelection` query payload the channel reads. */\ninterface GetRemoteBrowserSelectionResult {\n GetRemoteBrowserSelection: {\n Text: string;\n } | null;\n}\n\n/** The narrow projection of the `StartRemoteBrowserScreencast` mutation payload the channel reads. */\ninterface StartRemoteBrowserScreencastResult {\n StartRemoteBrowserScreencast: {\n Streaming: boolean;\n } | null;\n}\n\n/** GraphQL query the surface fetcher runs — the live screenshot + URL of the server browser. */\nconst REMOTE_BROWSER_SNAPSHOT_QUERY = `\n query RemoteBrowserSnapshot($agentSessionID: String!) {\n RemoteBrowserSnapshot(agentSessionID: $agentSessionID) {\n ScreenshotBase64\n CurrentUrl\n }\n }\n`;\n\n/** The narrow projection of the `RemoteBrowserSnapshot` query payload the channel reads. */\ninterface RemoteBrowserSnapshotResult {\n RemoteBrowserSnapshot: RemoteBrowserSnapshotView | null;\n}\n\n/**\n * VISION-QUERY mutation (separate from {@link EXECUTE_REMOTE_BROWSER_ACTION_MUTATION} — this OBSERVES the page,\n * it does not drive it). The server captures the live screenshot and runs a fast vision model over it, so the\n * non-omnimodal voice agent can \"see\" the page: a text description and/or localized elements with pixel\n * centroids. Backs both `browser_DescribePage` (no target) and `browser_LocateElement` (a target description).\n */\nconst INTERPRET_PAGE_MUTATION = `\n mutation InterpretRemoteBrowserPage($agentSessionID: String!, $query: String) {\n InterpretRemoteBrowserPage(agentSessionID: $agentSessionID, query: $query) {\n Description\n Elements {\n Label\n X\n Y\n Confidence\n }\n Detail\n }\n }\n`;\n\n/** One element localized by the visual interpreter — label + pixel centroid + confidence. */\ninterface RemoteBrowserInterpretedElement {\n Label: string;\n X: number;\n Y: number;\n Confidence: number;\n}\n\n/** The narrow projection of the `InterpretRemoteBrowserPage` mutation payload the channel reads. */\ninterface InterpretRemoteBrowserPageResult {\n InterpretRemoteBrowserPage: {\n Description: string | null;\n Elements: RemoteBrowserInterpretedElement[];\n Detail: string | null;\n } | null;\n}\n\n/** The result payload (serialized to JSON) the channel returns to the model per tool call. */\ninterface RemoteBrowserToolResult {\n success: boolean;\n /** The page URL after the action, when the server reported one. */\n currentUrl?: string;\n /** Human-readable detail (e.g. extracted page text for `browser_GetPageText`, or an error). */\n detail?: string;\n /** Error description when `success` is false. */\n error?: string;\n}\n\n/**\n * The REMOTE BROWSER as a pluggable interactive channel — a {@link BaseRealtimeChannelClient}\n * resolved from the `MJ: AI Agent Channels` registry row whose `ClientPluginClass` is\n * `'RealtimeRemoteBrowserChannel'`. One instance per session (ClassFactory-created at session\n * start).\n *\n * Topology (CLIENT-DIRECT, like the whiteboard — the browser just happens to live on the\n * server):\n * - **Action**: the `browser_*` client-executed tool set ({@link REMOTE_BROWSER_TOOL_DEFINITIONS}).\n * {@link ApplyAgentTool} maps a tool call → a normalized {@link RemoteBrowserAction}\n * ({@link MapToolToAction}) and awaits the `ExecuteRemoteBrowserAction` GraphQL mutation\n * (via {@link RealtimeChannelContext.ExecuteServerAction}) to drive the server browser,\n * returning a concise JSON result for the model. Never throws — argument and transport\n * failures map to a `{ success: false, error }` payload.\n * - **Perception**: after a successful action, a `[browser]` context note carries the new\n * page URL so the agent perceives where the page went; the surface independently polls a\n * live screenshot.\n * - **Surface**: {@link RemoteBrowserSurfaceComponent}, created dynamically in the channel\n * tab; the plugin hands it the session's provider + id in {@link BindSurface} so it can\n * poll `RemoteBrowserSnapshot`. HUMAN TAKEOVER is enabled by default (Collaborative): the\n * user can click/type into the live canvas and those events relay to the server browser via\n * {@link RELAY_HUMAN_INPUT_MUTATION} (server-gated on the backend's `HumanTakeover` capability).\n *\n * The channel keeps NO client-side state of record — the browser's state lives entirely on\n * the server — so {@link SerializeState} / {@link RestoreState} use the base no-op behavior.\n */\n@RegisterClass(BaseRealtimeChannelClient, 'RealtimeRemoteBrowserChannel')\nexport class RemoteBrowserChannel extends BaseRealtimeChannelClient<RemoteBrowserSurfaceComponent> implements IChannelFrameProvider {\n /** The live bound surface, when the channel tab's pane is instantiated. */\n private surface: RemoteBrowserSurfaceComponent | null = null;\n\n /** Shared video bridge streaming browser frames to the model when the model supports inbound video. */\n private videoBridge: ChannelInboundVideoBridge | null = null;\n\n /**\n * Sourced tracks: Remote Browser can source inbound video to the model when the model supports it.\n */\n public override GetSourcedTracks(): readonly RealtimeTrackDescriptor[] {\n return [CHANNEL_INBOUND_VIDEO_TRACK];\n }\n\n /**\n * Produces the latest browser screenshot as a base64-encoded frame for the video bridge.\n * Under active screencast streaming, returns null to avoid redundant server snapshot round-trips\n * and duplicate frame sourcing into the video bridge.\n */\n public async GetLatestFrame(): Promise<string | null> {\n this.ensureVideoBridge();\n if (this.streaming) {\n return null;\n }\n const snapshot = await this.fetchSnapshot();\n return snapshot?.ScreenshotBase64 ?? null;\n }\n\n /** Subscription to the bound surface's `HumanInput` output, torn down on unbind/dispose. */\n private humanInputSub: Subscription | null = null;\n\n /**\n * Whether the server confirmed it is PUSHING screencast frames for this session (`ScreenStreaming`\n * supported). When `true` the surface renders pushed frames on its canvas and skips its poll, and\n * {@link OnScreencastFrame} forwards each pushed frame to it. When `false` the surface keeps polling.\n */\n private streaming = false;\n\n /**\n * Whether the server confirmed it is PUSHING tab-audio chunks for this session (the backend can capture\n * audio). When `true` {@link OnAudioChunk} feeds each pushed chunk to {@link audioPlayer}; when `false`\n * no audio plays.\n */\n private audioStreaming = false;\n\n /**\n * The last URL the agent was TOLD about, so an unchanged page is not re-announced (#3496). `null`\n * until the first sighting, which is deliberately announced as a plain \"current page\" rather than\n * as somebody else navigating — the first page of a session was nobody's takeover.\n */\n private lastNotedUrl: string | null = null;\n\n /**\n * How many browser operations the AGENT itself has in flight right now (#3496).\n *\n * Attribution cannot be read off the feed a change arrives on. A pushed frame or a perception poll\n * only says the page moved; it is `> 0` here that says the agent is the one moving it. Two cases\n * make that necessary rather than tidy:\n *\n * - **an action's frames outrun its reply.** Under streaming, frames of the new page are pushed\n * while `ExecuteRemoteBrowserAction` is still in flight, so the observation reliably lands BEFORE\n * the mutation returns the URL — the agent's own navigation announced back to it as a takeover.\n * - **a goal drives for minutes.** `browser_AchieveGoal` runs an autonomous loop server-side and\n * every page it opens is observed with nothing yet returned, so the whole run would narrate\n * itself as somebody else.\n *\n * A counter, not a flag: goals and actions overlap, and the last one to finish must not clear a\n * window another still owns.\n */\n private agentDrivingDepth = 0;\n\n /** The client-side audio player fed pushed chunks while {@link audioStreaming}; `null` until started. */\n private audioPlayer: RemoteBrowserAudioPlayer | null = null;\n\n /** Subscription to the bound surface's `AudioMutedChange` output (the speaker toggle), torn down on unbind. */\n private audioMutedSub: Subscription | null = null;\n\n /**\n * Poll cadence + deadline for {@link pollGoalResult} (the async `browser_AchieveGoal` result poll).\n * Protected so tests can shrink them; production polls every 2.5s for up to 5 minutes (a goal loop\n * past 5 min keeps running server-side, the model just gets a \"still running\" note).\n */\n protected GoalPollIntervalMs = 2500;\n protected GoalPollTimeoutMs = 5 * 60 * 1000;\n\n public get ChannelName(): string {\n return 'Remote Browser';\n }\n\n public get ToolNamePrefix(): string {\n return REMOTE_BROWSER_TOOL_PREFIX;\n }\n\n public get TabTitle(): string {\n return 'Browser';\n }\n\n public get TabIcon(): string {\n return 'fa-solid fa-globe';\n }\n\n public GetToolDefinitions(): RealtimeToolDefinition[] {\n return REMOTE_BROWSER_TOOL_DEFINITIONS;\n }\n\n public override GetSurfaceComponent(): Type<RemoteBrowserSurfaceComponent> {\n return RemoteBrowserSurfaceComponent;\n }\n\n /** First-run intro shown the first time the user opens the Browser tab (once per user). */\n public override GetOnboardingDetails(): ChannelOnboardingDetails {\n return {\n Heading: 'Remote Browser',\n Description:\n 'The agent drives a live web browser on the server and you can watch it work right here. ' +\n 'You can also grab the wheel: click and type directly on the live page and the agent picks ' +\n 'up right where you left off.',\n Tips: [\n 'Ask the agent to look something up, open a site or fill in a form — it controls the page.',\n 'Click on the live view to take over, then click and type to drive it yourself — the \"You\\'re driving\" badge shows when takeover is active.',\n 'The current page URL is shown so you always know where the agent has navigated.',\n ],\n IconClass: 'fa-solid fa-globe',\n };\n }\n\n /** Starts the video bridge streaming browser frames to the model when inbound video is supported. */\n protected override OnInitialize(): void {\n this.ensureVideoBridge();\n }\n\n public override OnSessionStarted(): void {\n this.ensureVideoBridge();\n }\n\n private ensureVideoBridge(): ChannelInboundVideoBridge | null {\n if (!this.videoBridge && this.Context) {\n this.videoBridge = new ChannelInboundVideoBridge(() => this.Context?.Client, this);\n }\n if (this.videoBridge && !this.videoBridge.IsActive && this.Context?.Client?.IsTrackEstablished('video', 'inbound')) {\n this.videoBridge.Start?.();\n }\n return this.videoBridge;\n }\n\n /**\n * Wires the dynamically-created surface: hands it a snapshot fetcher closing over the\n * channel context (session id + provider live there), so the surface stays transport-\n * agnostic. Set BEFORE the surface's first change detection (the pane binds synchronously),\n * so its `ngOnInit` poll has the fetcher.\n *\n * Also asks the server to start a live screencast. When the backend supports `ScreenStreaming` the\n * server starts PUSHING frames and reports `Streaming: true`; we flip the surface to canvas mode (its\n * poll is then skipped) and {@link OnScreencastFrame} paints each pushed frame. When the backend lacks\n * the capability (`Streaming: false`) the surface keeps the snapshot poll already running — graceful\n * fallback, no further action.\n *\n * HUMAN TAKEOVER is enabled BY DEFAULT (Collaborative): the surface is flipped `Interactive = true` and\n * its `HumanInput` output subscribed, forwarding each pointer/keyboard event to the server via\n * {@link RELAY_HUMAN_INPUT_MUTATION}. Takeover only takes effect on the canvas (screencast) path; the\n * `<img>` poll fallback stays view-only. The server gates the actual routing on the backend's\n * `HumanTakeover` capability, so this is safe to enable unconditionally.\n */\n public BindSurface(instance: RemoteBrowserSurfaceComponent): void {\n this.surface = instance;\n instance.Fetch = () => this.fetchSnapshot();\n // Copy-out: the surface reads the remote selection through this on a local `copy` and writes it locally.\n instance.FetchSelection = () => this.fetchSelection();\n instance.Interactive = true;\n this.ensureVideoBridge();\n this.humanInputSub = instance.HumanInput.subscribe((input) => this.relayHumanInput(input));\n this.audioMutedSub = instance.AudioMutedChange.subscribe((muted) => this.audioPlayer?.SetMuted(muted));\n void this.startScreencast(instance);\n void this.startAudioStream(instance);\n }\n\n public override UnbindSurface(): void {\n this.humanInputSub?.unsubscribe();\n this.humanInputSub = null;\n this.audioMutedSub?.unsubscribe();\n this.audioMutedSub = null;\n void this.stopScreencast();\n void this.stopAudioStream();\n this.surface = null;\n }\n\n public override Dispose(): void {\n this.clearScreencastTrailingTimer();\n this.pendingScreencastFrame = null;\n this.videoBridge?.Stop();\n this.videoBridge = null;\n this.humanInputSub?.unsubscribe();\n this.humanInputSub = null;\n this.audioMutedSub?.unsubscribe();\n this.audioMutedSub = null;\n void this.stopScreencast();\n void this.stopAudioStream();\n super.Dispose();\n }\n\n /** Timestamp of the last screencast frame pushed to the video bridge (paces pushes to negotiated cadence). */\n private lastScreencastPushTime = 0;\n\n /** Base64 content of the last frame pushed to the video bridge (for change-driven deduplication). */\n private lastPushedScreencastFrame: string | null = null;\n\n /** URL associated with the last frame pushed to the video bridge. */\n private lastPushedUrl: string | null = null;\n\n /** Minimum allowable screencast push cadence (250ms = 4 fps debounce limit during active bursts). */\n private static readonly SCREENCAST_MIN_CADENCE_MS = 250;\n\n /** Default screencast push cadence when not otherwise negotiated (1000ms = 1 fps ceiling for Gemini Live). */\n private static readonly SCREENCAST_DEFAULT_CADENCE_MS = 1000;\n\n /** Maximum interval (ms) between visual frame pushes on a static page (heartbeat to maintain track liveness). */\n private static readonly SCREENCAST_HEARTBEAT_MS = 15_000;\n\n /** Trailing timer to deliver the settled resting frame after a burst of rapid user activity. */\n private screencastTrailingTimer: ReturnType<typeof setTimeout> | null = null;\n\n /** Pending frame buffered during an active cooldown interval, waiting to settle. */\n private pendingScreencastFrame: { dataBase64: string; currentUrl?: string | null } | null = null;\n\n /** Cancels any active trailing-edge settle timer and clears the handle. */\n private clearScreencastTrailingTimer(): void {\n if (this.screencastTrailingTimer != null) {\n clearTimeout(this.screencastTrailingTimer);\n this.screencastTrailingTimer = null;\n }\n }\n\n /**\n * Pushes a frame to the video bridge, recording timestamp, frame content, and URL.\n */\n private pushScreencastFrame(dataBase64: string, currentUrl?: string | null): void {\n this.lastScreencastPushTime = Date.now();\n this.lastPushedScreencastFrame = dataBase64;\n this.lastPushedUrl = currentUrl ?? null;\n this.ensureVideoBridge()?.PushFrame(dataBase64);\n }\n\n /**\n * Resolves the effective push cadence in milliseconds based on the negotiated inbound video track.\n * Defaults to 1000ms (1 fps ceiling for Gemini Live), but clamps down to a minimum of 250ms (4 fps)\n * if the negotiated track specifies a higher `Rate`.\n */\n private getNegotiatedVideoCadenceMs(): number {\n const client = this.Context?.Client;\n if (!client) {\n return RemoteBrowserChannel.SCREENCAST_DEFAULT_CADENCE_MS;\n }\n const tracks: readonly RealtimeTrack[] = client.EstablishedTracks;\n const videoTrack = tracks.find(\n (t: RealtimeTrack) =>\n t.Descriptor.Modality === 'video' &&\n t.Descriptor.Direction === 'inbound'\n );\n const rate = videoTrack?.Descriptor.Rate;\n if (typeof rate === 'number' && rate > 0) {\n return Math.max(\n RemoteBrowserChannel.SCREENCAST_MIN_CADENCE_MS,\n Math.floor(1000 / rate)\n );\n }\n return RemoteBrowserChannel.SCREENCAST_DEFAULT_CADENCE_MS;\n }\n\n /**\n * Forwards one PUSHED screencast frame to the bound surface's canvas. Called by the session service\n * when a `RemoteBrowserScreencastFrame` arrives on the push-status stream for THIS session. No-op when\n * the channel isn't streaming or has no bound surface (e.g. the tab pane is collapsed).\n *\n * Under streaming the surface's snapshot poll is stopped, so this is the ONLY thing that sees the\n * page while frames are being pushed — which is why the frame now carries the URL (#3496). Without\n * it, a user navigating during a screencast changed the picture and nothing else: the agent kept\n * describing the page it last opened, and the pixels proving otherwise were right there on screen.\n *\n * Pushes to the video bridge are CHANGE-DRIVEN with a TRAILING-EDGE SETTLE DEBOUNCE:\n * 1. The human user surface renders immediately at full 60fps responsiveness.\n * 2. When the page is static (same image and URL), pushes are deduplicated to avoid burning\n * ~15k tokens/min of identical visual context.\n * 3. A frame is pushed immediately on the leading edge when visual content or URL changes after\n * the negotiated cadence (default 1000ms, clamped to >= 250ms).\n * 4. During rapid bursts (scrolling, typing, clicking) within the cooldown window, subsequent frames\n * are buffered; when the cooldown expires, a trailing timer delivers the settled resting frame\n * so the model never misses the final user state.\n * 5. A 15-second heartbeat ensures visual track liveness even during extended idle periods.\n *\n * @param dataBase64 The frame image as raw base64 JPEG (no `data:` prefix).\n * @param currentUrl The browser's URL when the frame was captured; absent from older servers.\n */\n public OnScreencastFrame(dataBase64: string, currentUrl?: string | null): void {\n if (!this.streaming) {\n return;\n }\n\n this.surface?.RenderFrame(dataBase64);\n\n const now = Date.now();\n const cadenceMs = this.getNegotiatedVideoCadenceMs();\n const elapsed = now - this.lastScreencastPushTime;\n\n if (elapsed >= cadenceMs) {\n // Cooldown has expired: leading edge push.\n this.clearScreencastTrailingTimer();\n this.pendingScreencastFrame = null;\n\n const frameChanged = dataBase64 !== this.lastPushedScreencastFrame;\n const urlChanged = currentUrl != null && currentUrl !== this.lastPushedUrl;\n const heartbeatElapsed = elapsed >= RemoteBrowserChannel.SCREENCAST_HEARTBEAT_MS;\n\n if (frameChanged || urlChanged || heartbeatElapsed) {\n this.pushScreencastFrame(dataBase64, currentUrl);\n }\n } else {\n // Within cooldown window: buffer latest frame and schedule trailing settle timer.\n this.pendingScreencastFrame = { dataBase64, currentUrl };\n if (!this.screencastTrailingTimer) {\n const delay = Math.max(0, cadenceMs - elapsed);\n this.screencastTrailingTimer = setTimeout(() => {\n this.screencastTrailingTimer = null;\n if (!this.streaming || !this.pendingScreencastFrame) {\n return;\n }\n const { dataBase64: pendingData, currentUrl: pendingUrl } = this.pendingScreencastFrame;\n this.pendingScreencastFrame = null;\n\n const frameChanged = pendingData !== this.lastPushedScreencastFrame;\n const urlChanged = pendingUrl != null && pendingUrl !== this.lastPushedUrl;\n const heartbeat = (Date.now() - this.lastScreencastPushTime) >= RemoteBrowserChannel.SCREENCAST_HEARTBEAT_MS;\n\n if (frameChanged || urlChanged || heartbeat) {\n this.pushScreencastFrame(pendingData, pendingUrl);\n }\n }, delay);\n }\n }\n\n // #3496: URL perception note runs AFTER the push so the visual image lands before/with the note.\n this.notePageChange(currentUrl, 'observed');\n }\n\n /**\n * Feeds one PUSHED tab-audio chunk to the client-side audio player. Called by the session service when a\n * `RemoteBrowserAudioChunk` arrives on the push-status stream for THIS session. No-op when the channel\n * isn't audio-streaming or the player has been torn down.\n *\n * @param chunk The pushed audio chunk (base64 webm-opus + codec/seq metadata).\n */\n public OnAudioChunk(chunk: RemoteBrowserAudioChunkInput): void {\n if (this.audioStreaming) {\n this.audioPlayer?.Enqueue(chunk);\n }\n }\n\n /**\n * Asks the server to start the live screencast and, on success, flips the surface to canvas mode so it\n * stops polling and starts painting pushed frames. Best-effort: a `null`/`Streaming: false` result\n * (capability absent or transport failure) leaves the surface on its poll fallback.\n */\n private async startScreencast(instance: RemoteBrowserSurfaceComponent): Promise<void> {\n const sessionId = this.Context?.AgentSessionID;\n if (!sessionId) {\n return;\n }\n const data = await this.Context?.ExecuteServerAction<StartRemoteBrowserScreencastResult>(START_SCREENCAST_MUTATION, { agentSessionID: sessionId });\n if (data?.StartRemoteBrowserScreencast?.Streaming === true) {\n this.streaming = true;\n instance.Streaming = true;\n }\n }\n\n /**\n * Asks the server to start the live tab-audio stream and, on success, spins up the client-side\n * {@link RemoteBrowserAudioPlayer} (speaker ON by default — the call is the activating user gesture) so\n * pushed chunks play. Best-effort: a `null`/`Streaming: false` result (no audio capability or transport\n * failure) leaves the channel silent with no player.\n *\n * @param instance The bound surface, flipped to show the speaker toggle when audio starts.\n */\n private async startAudioStream(instance: RemoteBrowserSurfaceComponent): Promise<void> {\n const sessionId = this.Context?.AgentSessionID;\n if (!sessionId) {\n return;\n }\n const data = await this.Context?.ExecuteServerAction<StartRemoteBrowserAudioStreamResult>(START_AUDIO_STREAM_MUTATION, { agentSessionID: sessionId });\n if (data?.StartRemoteBrowserAudioStream?.Streaming === true) {\n this.audioStreaming = true;\n this.audioPlayer = new RemoteBrowserAudioPlayer(new MediaSourceAudioSink());\n // Speaker defaults ON; reflect that on the surface so the toggle renders un-muted.\n instance.AudioAvailable = true;\n instance.AudioMuted = false;\n }\n }\n\n /** Asks the server to stop the tab-audio stream (best-effort), disposes the player, and clears state. */\n private async stopAudioStream(): Promise<void> {\n const wasStreaming = this.audioStreaming;\n this.audioStreaming = false;\n this.audioPlayer?.Dispose();\n this.audioPlayer = null;\n const sessionId = this.Context?.AgentSessionID;\n if (!wasStreaming || !sessionId) {\n return;\n }\n await this.Context?.ExecuteServerAction(STOP_AUDIO_STREAM_MUTATION, { agentSessionID: sessionId });\n }\n\n /**\n * Relays ONE human-takeover input from the surface to the server's {@link RELAY_HUMAN_INPUT_MUTATION}.\n * Best-effort and fire-and-forget: never throws, and the boolean result is ignored (the server already\n * gates routing on the backend's `HumanTakeover` capability). No-op when no live session id exists.\n *\n * @param input The flattened pointer/keyboard input the user performed on the live canvas.\n */\n private relayHumanInput(input: RemoteBrowserHumanInputEvent): void {\n const sessionId = this.Context?.AgentSessionID;\n if (!sessionId) {\n return;\n }\n void this.Context?.ExecuteServerAction(RELAY_HUMAN_INPUT_MUTATION, {\n agentSessionID: sessionId,\n kind: input.kind,\n x: input.x ?? null,\n y: input.y ?? null,\n button: input.button ?? null,\n key: input.key ?? null,\n // Paste-in: the `'text'` kind carries the local clipboard text the server inserts into the page.\n text: input.text ?? null,\n deltaX: input.deltaX ?? null,\n deltaY: input.deltaY ?? null,\n // Server expects a comma-separated modifier list (e.g. \"Shift,Control\"); null when none held.\n modifiers: input.modifiers && input.modifiers.length > 0 ? input.modifiers.join(',') : null,\n });\n }\n\n /**\n * Reads the remote page's current selection for COPY-OUT — backs the surface's `FetchSelection`. Awaits\n * the `GetRemoteBrowserSelection` query and returns its text. Best-effort by contract\n * ({@link RemoteBrowserSelectionFetcher}): returns `''` when no session is live or the query fails, so a\n * best-effort copy never throws.\n */\n private async fetchSelection(): Promise<string> {\n const sessionId = this.Context?.AgentSessionID;\n if (!sessionId) {\n return '';\n }\n const data = await this.Context?.ExecuteServerAction<GetRemoteBrowserSelectionResult>(GET_SELECTION_QUERY, { agentSessionID: sessionId });\n return data?.GetRemoteBrowserSelection?.Text ?? '';\n }\n\n /** Asks the server to stop the screencast (best-effort) and clears the streaming flag. */\n private async stopScreencast(): Promise<void> {\n const wasStreaming = this.streaming;\n this.streaming = false;\n this.clearScreencastTrailingTimer();\n this.pendingScreencastFrame = null;\n this.lastPushedScreencastFrame = null;\n this.lastPushedUrl = null;\n this.lastScreencastPushTime = 0;\n const sessionId = this.Context?.AgentSessionID;\n if (!wasStreaming || !sessionId) {\n return;\n }\n await this.Context?.ExecuteServerAction(STOP_SCREENCAST_MUTATION, { agentSessionID: sessionId });\n }\n\n /**\n * Fetches one live snapshot of the server browser for the surface — the PERCEPTION poll.\n * Best-effort by contract ({@link RemoteBrowserSnapshotFetcher}): returns `null` when no\n * session is live or the query fails (the surface keeps its last good frame).\n *\n * Also the channel's chance to NOTICE a page change it did not cause (#3496): the poll already\n * carries the URL, and until now only the surface read it.\n */\n private async fetchSnapshot(): Promise<RemoteBrowserSnapshotView | null> {\n const sessionId = this.Context?.AgentSessionID;\n if (!sessionId) {\n return null;\n }\n const data = await this.Context?.ExecuteServerAction<RemoteBrowserSnapshotResult>(REMOTE_BROWSER_SNAPSHOT_QUERY, { agentSessionID: sessionId });\n const snapshot = data?.RemoteBrowserSnapshot ?? null;\n this.notePageChange(snapshot?.CurrentUrl ?? null, 'observed');\n return snapshot;\n }\n\n /**\n * The ONE place the agent is told where the page is (#3496).\n *\n * Before this, the `[browser] current page:` note was pushed from exactly two call sites, both\n * immediately after a server action the MODEL itself initiated. That placement — not any caching —\n * was the bug: **a surface change the agent did not cause was invisible to it.** A user taking over\n * and navigating produced no note, so the agent went on confidently describing the previous page,\n * and only corrected when told to look again. Human takeover is on by default for `Collaborative`\n * providers, so the default configuration was the broken one.\n *\n * Routing every observation through here makes the rule \"the agent hears about the page whenever it\n * MOVES, whoever moved it\" — a property of this method rather than of where callers happen to be.\n *\n * `cause` is what the agent could never work out for itself. An `'agent'` change is one this\n * channel just performed; anything spotted by the perception poll or a pushed frame is\n * `'observed'` — someone else is driving, and saying so is the difference between the agent\n * knowing the page moved and knowing it is no longer the one moving it.\n *\n * Silent when the URL has not changed, which is not an optimisation but a requirement: the\n * perception poll runs every ~700ms and an unconditional note would bury the conversation in\n * hundreds of identical lines.\n */\n private notePageChange(url: string | null | undefined, cause: 'agent' | 'observed'): void {\n const next = (url ?? '').trim();\n if (next.length === 0) {\n return;\n }\n // The live-view bar is refreshed even when the URL is unchanged: it is idempotent, and in\n // streaming mode it is the only thing keeping the bar off \"No page loaded yet\".\n this.surface?.SetCurrentUrl(next);\n if (next === this.lastNotedUrl) {\n return;\n }\n const firstSighting = this.lastNotedUrl === null;\n this.lastNotedUrl = next;\n // A change seen WHILE the agent is driving is the agent's own, whichever feed spotted it first.\n const byAgent = cause === 'agent' || this.agentDrivingDepth > 0;\n this.Context?.SendContextNote(\n byAgent || firstSighting\n ? `[browser] current page: ${next}`\n : `[browser] the page changed to: ${next} — you did not navigate here, so someone else is driving. `\n + `Describe THIS page, not the one you last opened.`,\n );\n }\n\n /**\n * Runs one agent-initiated browser operation with {@link agentDrivingDepth} raised, so any page\n * change observed while it runs is attributed to the agent rather than to a phantom third party.\n *\n * `finally` and not a trailing decrement: the window must close on a thrown transport error too,\n * or one failed tool call would leave the channel permanently unable to report a real takeover.\n */\n private async whileAgentDrives<T>(work: () => Promise<T>): Promise<T> {\n this.agentDrivingDepth++;\n try {\n return await work();\n } finally {\n this.agentDrivingDepth--;\n }\n }\n\n /**\n * Executes ONE `browser_*` tool call: maps it to a server action and awaits the\n * `ExecuteRemoteBrowserAction` mutation, then returns a concise JSON result the model reads.\n * On success it also feeds the new page URL to the agent as a `[browser]` context note. Never\n * throws — bad arguments, no live session, and server failures all become a failed-result\n * payload so the model can narrate the problem.\n */\n public async ApplyAgentTool(toolName: string, argsJson: string): Promise<string> {\n // VISION-QUERY branch: the two interpreter tools OBSERVE the page (run a vision model over a screenshot)\n // rather than drive it, so they route to the separate interpret mutation, not MapToolToAction.\n if (toolName === REMOTE_BROWSER_TOOL_NAMES.DescribePage) {\n return this.interpretPage(undefined);\n }\n if (toolName === REMOTE_BROWSER_TOOL_NAMES.AchieveGoal) {\n return this.achieveGoal(argsJson);\n }\n if (toolName === REMOTE_BROWSER_TOOL_NAMES.LocateElement) {\n const description = this.asArgString(this.parseArgs(argsJson)['description']);\n if (!description) {\n return this.fail('browser_LocateElement requires a \"description\" of the element to find.');\n }\n return this.interpretPage(description);\n }\n\n let action: RemoteBrowserAction;\n try {\n action = MapToolToAction(toolName, this.parseArgs(argsJson));\n } catch (error) {\n const message = error instanceof RemoteBrowserToolArgError ? error.message : 'Invalid Remote Browser tool arguments.';\n return this.fail(message);\n }\n\n // Briefly WAIT for the session id rather than failing instantly: the model can fire a tool the\n // first beat it connects, before mintSession binds the id (the \"session id missing\" race).\n const sessionId = await this.ResolveAgentSessionId();\n if (!sessionId) {\n // Diagnostic: distinguishes \"channel never Initialized (no Context)\" from \"session id never\n // resolved within the wait window\" — the two distinct causes of a null id at tool-execution time.\n console.warn('[RemoteBrowser] browser tool invoked but AgentSessionID is null', {\n tool: toolName,\n hasContext: !!this.Context,\n agentSessionID: this.Context?.AgentSessionID ?? null,\n });\n return this.fail('No live browser session is available yet — the realtime session may still be connecting; try again in a moment.');\n }\n\n return this.whileAgentDrives(async () => {\n const data = await this.Context?.ExecuteServerAction<ExecuteRemoteBrowserActionResult>(\n EXECUTE_REMOTE_BROWSER_ACTION_MUTATION,\n this.buildVariables(sessionId, action),\n );\n const result = data?.ExecuteRemoteBrowserAction ?? null;\n if (!result) {\n return this.fail('The browser action could not be executed (no response from the server).');\n }\n if (!result.Success) {\n return this.fail(result.Detail ?? 'The browser action failed.');\n }\n\n // PERCEPTION: tell the agent where the page is now (background — no spoken reply), and refresh\n // the live-view bar, which in streaming mode has no poll feeding it.\n this.notePageChange(result.CurrentUrl, 'agent');\n return this.ok(result.CurrentUrl, result.Detail);\n });\n }\n\n /** Builds the flat mutation variables from a normalized action (omitted fields ride as null). */\n private buildVariables(sessionId: string, action: RemoteBrowserAction): Record<string, JSONValue> {\n return {\n agentSessionID: sessionId,\n kind: action.Kind,\n url: action.Url ?? null,\n selector: action.Selector ?? null,\n x: action.X ?? null,\n y: action.Y ?? null,\n text: action.Text ?? null,\n key: action.Key ?? null,\n deltaX: action.DeltaX ?? null,\n deltaY: action.DeltaY ?? null,\n ms: action.Ms ?? null,\n };\n }\n\n /**\n * GOAL path for the `browser_AchieveGoal` tool: hands a high-level goal to the server's autonomous\n * browser agent (computer-use loop / native AI) via {@link EXECUTE_REMOTE_BROWSER_GOAL_MUTATION} and\n * returns a concise result for the model. Never throws — failures map to a `{ success: false }` string.\n *\n * @param argsJson The raw tool-call arguments (`{ goal, startUrl? }`).\n * @returns The model-readable result string.\n */\n private async achieveGoal(argsJson: string): Promise<string> {\n // Wait briefly for the session id (mint/reconnect race) before giving up — see ResolveAgentSessionId.\n const sessionId = await this.ResolveAgentSessionId();\n if (!sessionId) {\n return this.fail('No live browser session is available yet — the realtime session may still be connecting; try again in a moment.');\n }\n const args = this.parseArgs(argsJson);\n const goal = this.asArgString(args['goal']);\n if (!goal) {\n return this.fail('browser_AchieveGoal requires a \"goal\" describing what to accomplish.');\n }\n const startUrl = this.asArgString(args['startUrl']) ?? null;\n\n // START the goal (returns immediately with a GoalRunID) then POLL — a goal loop can run for\n // minutes, far longer than any single request survives, so we never hold one open for the loop.\n // The whole span is agent-driven: every page the loop opens is observed with nothing returned\n // yet, so without the window the agent would narrate its own goal as somebody else's takeover.\n return this.whileAgentDrives(async () => {\n const startData = await this.Context?.ExecuteServerAction<ExecuteRemoteBrowserGoalResult>(EXECUTE_REMOTE_BROWSER_GOAL_MUTATION, {\n agentSessionID: sessionId,\n goal,\n startUrl,\n });\n const started = startData?.ExecuteRemoteBrowserGoal ?? null;\n if (!started) {\n return this.fail('The browser goal could not be started (no response from the server).');\n }\n // Defensive: if the server ever returns a terminal status synchronously, use it directly.\n const result = started.Status === 'Running' && started.GoalRunID\n ? await this.pollGoalResult(sessionId, started.GoalRunID)\n : started;\n if (!result) {\n return this.fail('The browser goal is taking longer than expected and could not be confirmed. It may still be running.');\n }\n this.notePageChange(result.CurrentUrl, 'agent');\n if (!result.Success) {\n return this.fail(result.Detail ?? `The goal could not be completed (${result.Status ?? 'unknown'}).`);\n }\n return this.ok(result.CurrentUrl, result.Detail ?? `Goal completed (${result.Status ?? 'Completed'}, ${result.StepCount ?? 0} steps).`);\n });\n }\n\n /**\n * Polls {@link GET_REMOTE_BROWSER_GOAL_RESULT_QUERY} for a started goal run until it reports a\n * terminal status (anything other than `Running`), then returns that terminal payload. Each poll is\n * a short request, so no transport boundary times out while a long goal loop runs server-side.\n *\n * Returns `null` if the goal is still `Running` after {@link GoalPollTimeoutMs} (the loop keeps\n * running server-side; the caller maps this to a \"still running\" message), the session went away, or\n * the run id is no longer known (`Unknown` — expired/superseded).\n */\n private async pollGoalResult(sessionId: string, goalRunID: string): Promise<RemoteBrowserGoalPayload | null> {\n const interval = Math.max(1, this.GoalPollIntervalMs);\n for (let waited = 0; waited < this.GoalPollTimeoutMs; waited += interval) {\n await new Promise<void>((resolve) => setTimeout(resolve, interval));\n // The session ended mid-goal — stop polling (the live browser is gone).\n if (!this.Context?.AgentSessionID) {\n return null;\n }\n const data = await this.Context.ExecuteServerAction<GetRemoteBrowserGoalResultPayload>(GET_REMOTE_BROWSER_GOAL_RESULT_QUERY, {\n agentSessionID: sessionId,\n goalRunID,\n });\n const payload = data?.GetRemoteBrowserGoalResult ?? null;\n // A transient null (one failed poll) shouldn't abort — keep polling until the deadline.\n if (payload && payload.Status !== 'Running') {\n return payload;\n }\n }\n return null;\n }\n\n /**\n * Runs the VISION-QUERY path for the `browser_DescribePage` / `browser_LocateElement` tools: awaits the\n * `InterpretRemoteBrowserPage` mutation (server captures a screenshot + runs a fast vision model) and returns\n * a concise JSON string the model reads. For a describe (no `query`) it returns `{ description }`; for a locate\n * it returns `{ found, element, all }` so the agent can `browser_Click` the centroid. Never throws — a null\n * session, no response, or a server detail-only result all map to a result string the model can narrate.\n *\n * @param query The visual target to locate, or `undefined` for a plain page description.\n * @returns A JSON string describing the interpretation for the model.\n */\n private async interpretPage(query: string | undefined): Promise<string> {\n // Wait briefly for the session id (mint/reconnect race) before giving up — see ResolveAgentSessionId.\n const sessionId = await this.ResolveAgentSessionId();\n if (!sessionId) {\n console.warn('[RemoteBrowser] interpret tool invoked but AgentSessionID is null', {\n hasContext: !!this.Context,\n agentSessionID: this.Context?.AgentSessionID ?? null,\n });\n return this.fail('No live browser session is available yet — the realtime session may still be connecting; try again in a moment.');\n }\n\n const data = await this.Context?.ExecuteServerAction<InterpretRemoteBrowserPageResult>(INTERPRET_PAGE_MUTATION, {\n agentSessionID: sessionId,\n query: query ?? null,\n });\n const result = data?.InterpretRemoteBrowserPage ?? null;\n if (!result) {\n return this.fail('The page could not be interpreted (no response from the server).');\n }\n\n if (query === undefined) {\n // DescribePage — hand back the description (or the server's detail note when nothing was interpreted).\n return JSON.stringify({ description: result.Description ?? result.Detail ?? null });\n }\n\n // LocateElement — surface whether anything matched, the best match, and all candidates.\n const elements = result.Elements ?? [];\n const best = elements[0];\n return JSON.stringify({\n found: elements.length > 0,\n element: best ? { label: best.Label, x: best.X, y: best.Y } : null,\n all: elements,\n ...(result.Detail ? { detail: result.Detail } : {}),\n });\n }\n\n /** Coerces a parsed tool-arg to a non-empty string, or `undefined` when absent / wrong-typed. */\n private asArgString(value: JSONValue | undefined): string | undefined {\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;\n }\n\n /** Parses the tool-args JSON into a plain object; returns `{}` for empty/malformed input. */\n private parseArgs(argsJson: string): Record<string, JSONValue> {\n const trimmed = argsJson?.trim();\n if (!trimmed) {\n return {};\n }\n try {\n const parsed: unknown = JSON.parse(trimmed);\n return parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed) ? (parsed as Record<string, JSONValue>) : {};\n } catch {\n return {};\n }\n }\n\n /** Builds a successful tool-result JSON string for the model. */\n private ok(currentUrl: string | null, detail: string | null): string {\n const result: RemoteBrowserToolResult = { success: true };\n if (currentUrl) {\n result.currentUrl = currentUrl;\n }\n if (detail) {\n result.detail = detail;\n }\n return JSON.stringify(result);\n }\n\n /** Builds a failed tool-result JSON string for the model. */\n private fail(error: string): string {\n const result: RemoteBrowserToolResult = { success: false, error };\n return JSON.stringify(result);\n }\n}\n\n/**\n * Tree-shaking prevention: the Remote Browser channel is resolved dynamically through the\n * ClassFactory (by the registry row's `ClientPluginClass` key), so this static call is what\n * keeps its `@RegisterClass` side effect from being eliminated by the bundler. Called from\n * `conversations.module.ts` alongside {@link LoadRealtimeWhiteboardChannel}.\n */\nexport function LoadRealtimeRemoteBrowserChannel(): void {\n // intentional no-op — the import side effect performs the registration\n}\n"]}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import type { Type } from '@angular/core';
|
|
2
|
-
import { RealtimeToolDefinition } from '@memberjunction/ai';
|
|
2
|
+
import { RealtimeToolDefinition, RealtimeTrackDescriptor } from '@memberjunction/ai';
|
|
3
|
+
import { IChannelFrameProvider } from '@memberjunction/ai-realtime-client';
|
|
3
4
|
import { BaseRealtimeChannelClient, ChannelOnboardingDetails } from '../channels/base-realtime-channel-client';
|
|
4
5
|
import { RealtimeWhiteboardHostComponent, WhiteboardState } from '@memberjunction/ng-whiteboard';
|
|
6
|
+
/**
|
|
7
|
+
* Asynchronously rasterizes an SVG string to a JPEG base64 string (without the `data:image/jpeg;base64,` prefix)
|
|
8
|
+
* using an offscreen canvas. Returns null in non-DOM environments or when rendering fails.
|
|
9
|
+
*/
|
|
10
|
+
export declare function rasterizeSvgToJpegBase64(svg: string, width?: number, height?: number): Promise<string | null>;
|
|
5
11
|
/**
|
|
6
12
|
* Per-widget throttle window for AMBIENT interaction context notes: at most one note per
|
|
7
13
|
* widget per this many ms — within the window the LATEST summary wins (chatty widgets
|
|
@@ -38,7 +44,7 @@ export declare const WHITEBOARD_INTERACTION_NOTE_THROTTLE_MS = 4000;
|
|
|
38
44
|
* subscription and any later surface binding keep pointing at one engine. Malformed or
|
|
39
45
|
* incompatible payloads are tolerated — the board simply starts fresh.
|
|
40
46
|
*/
|
|
41
|
-
export declare class RealtimeWhiteboardChannel extends BaseRealtimeChannelClient<RealtimeWhiteboardHostComponent> {
|
|
47
|
+
export declare class RealtimeWhiteboardChannel extends BaseRealtimeChannelClient<RealtimeWhiteboardHostComponent> implements IChannelFrameProvider {
|
|
42
48
|
/** The board's state of record — created fresh with the plugin (one per session). */
|
|
43
49
|
readonly State: WhiteboardState;
|
|
44
50
|
/** The live bound surface, when the channel tab's pane is instantiated. */
|
|
@@ -49,7 +55,48 @@ export declare class RealtimeWhiteboardChannel extends BaseRealtimeChannelClient
|
|
|
49
55
|
private stateChangedSub;
|
|
50
56
|
/** Per-widget ambient-interaction note throttles (ItemID → window state). */
|
|
51
57
|
private interactionThrottles;
|
|
58
|
+
/** Shared video bridge streaming board frames to the model when the model supports inbound video. */
|
|
59
|
+
private videoBridge;
|
|
60
|
+
/** Pacing timestamp for event-driven visual scene pushes (enforces max 1 fps). */
|
|
61
|
+
private lastPushTimestamp;
|
|
52
62
|
get ChannelName(): string;
|
|
63
|
+
/**
|
|
64
|
+
* Sourced tracks: Whiteboard can source inbound video to the model when the model supports it.
|
|
65
|
+
*/
|
|
66
|
+
GetSourcedTracks(): readonly RealtimeTrackDescriptor[];
|
|
67
|
+
/**
|
|
68
|
+
* Produces the latest visual scene as a base64-encoded frame for the video bridge.
|
|
69
|
+
* Renders the whiteboard SVG into an offscreen canvas and returns base64 JPEG.
|
|
70
|
+
*/
|
|
71
|
+
GetLatestFrame(): Promise<string | null>;
|
|
72
|
+
private static readonly WHITEBOARD_DEFAULT_CADENCE_MS;
|
|
73
|
+
private static readonly WHITEBOARD_MIN_CADENCE_MS;
|
|
74
|
+
/** Trailing timer to deliver the settled resting frame after rapid user drawing/edits. */
|
|
75
|
+
private whiteboardTrailingTimer;
|
|
76
|
+
/** Last base64 JPEG frame pushed to the bridge, used for deduplication. */
|
|
77
|
+
private lastPushedWhiteboardFrame;
|
|
78
|
+
/** Cancels any active trailing-edge settle timer and clears the handle. */
|
|
79
|
+
private clearWhiteboardTrailingTimer;
|
|
80
|
+
/**
|
|
81
|
+
* Resolves the effective push cadence in milliseconds based on the negotiated inbound video track.
|
|
82
|
+
* Defaults to 1000ms (1 fps ceiling for Gemini Live), but clamps down to a minimum of 250ms (4 fps)
|
|
83
|
+
* if the negotiated track specifies a higher `Rate`.
|
|
84
|
+
*/
|
|
85
|
+
private getNegotiatedVideoCadenceMs;
|
|
86
|
+
/**
|
|
87
|
+
* Pushes a frame to the video bridge, updating timestamp and deduplication cache.
|
|
88
|
+
*/
|
|
89
|
+
private pushWhiteboardFrame;
|
|
90
|
+
/**
|
|
91
|
+
* Pushes the latest board visual scene when user mutations occur, with dynamic pacing,
|
|
92
|
+
* deduplication, and a trailing-edge settle timer so the model sees the final resting state.
|
|
93
|
+
*/
|
|
94
|
+
private onUserMutation;
|
|
95
|
+
/**
|
|
96
|
+
* Pushes exactly ONE confirmation frame after an agent tool mutates the board, and
|
|
97
|
+
* informs the model context so it does not loop narrating its own change.
|
|
98
|
+
*/
|
|
99
|
+
private pushAgentConfirmationFrame;
|
|
53
100
|
get ToolNamePrefix(): string;
|
|
54
101
|
get TabTitle(): string;
|
|
55
102
|
get TabIcon(): string;
|
|
@@ -59,6 +106,8 @@ export declare class RealtimeWhiteboardChannel extends BaseRealtimeChannelClient
|
|
|
59
106
|
GetOnboardingDetails(): ChannelOnboardingDetails;
|
|
60
107
|
/** Persist the board (host-debounced) on EVERY board mutation — user edits AND agent tools. */
|
|
61
108
|
protected OnInitialize(): void;
|
|
109
|
+
OnSessionStarted(): void;
|
|
110
|
+
private ensureVideoBridge;
|
|
62
111
|
/**
|
|
63
112
|
* Wires the dynamically-created board host: inputs (shared state engine + agent name)
|
|
64
113
|
* are set BEFORE the component's first change detection, and the perception/garnish
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"whiteboard-channel.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/whiteboard/whiteboard-channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAG1C,OAAO,
|
|
1
|
+
{"version":3,"file":"whiteboard-channel.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/whiteboard/whiteboard-channel.ts"],"names":[],"mappings":"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"}
|