@memberjunction/ng-conversations 6.1.0 → 6.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,8 +1,8 @@
|
|
|
1
1
|
import { Observable, Subject } from 'rxjs';
|
|
2
|
-
import { RealtimeCaption, RealtimeDelegationProgress, RealtimeDelegationResult, RealtimeDelegationNarration } from '../../services/realtime-session.service';
|
|
2
|
+
import { RealtimeCaption, RealtimeDelegationProgress, RealtimeDelegationResult, RealtimeDelegationNarration, RealtimeThoughtNarration } from '../../services/realtime-session.service';
|
|
3
3
|
import { ParsedDelegationArtifact } from '../../services/delegation-result-parser';
|
|
4
4
|
/**
|
|
5
|
-
* The
|
|
5
|
+
* The reactive session streams {@link RealtimeSessionState} merges — structurally
|
|
6
6
|
* satisfied by `RealtimeSessionService`. Narrowed to an interface so the state (and its unit
|
|
7
7
|
* tests) depend only on the streams, not on the full service / GraphQL import chain.
|
|
8
8
|
*/
|
|
@@ -15,6 +15,8 @@ export interface RealtimeSessionStreams {
|
|
|
15
15
|
DelegationResult$: Observable<RealtimeDelegationResult>;
|
|
16
16
|
/** Ephemeral spoken progress narrations. */
|
|
17
17
|
DelegationNarration$: Observable<RealtimeDelegationNarration>;
|
|
18
|
+
/** Model-authored thought / reasoning narrations. */
|
|
19
|
+
ThoughtNarration$?: Observable<RealtimeThoughtNarration>;
|
|
18
20
|
}
|
|
19
21
|
/**
|
|
20
22
|
* The view-model for one delegated tool/agent call in the live session. Built by
|
|
@@ -27,8 +29,8 @@ export interface RealtimeSessionStreams {
|
|
|
27
29
|
* references and in-place updates reliably re-render.
|
|
28
30
|
*/
|
|
29
31
|
export interface RealtimeDelegationCardVM {
|
|
30
|
-
/** What this card represents: a delegated agent run,
|
|
31
|
-
Kind: 'agent' | 'action';
|
|
32
|
+
/** What this card represents: a delegated agent run, a direct action, or model-authored narration. */
|
|
33
|
+
Kind: 'agent' | 'action' | 'narration';
|
|
32
34
|
/** The `invoke-target-agent` or direct-action call this card represents. */
|
|
33
35
|
CallID: string;
|
|
34
36
|
/** Display name of the delegated agent (e.g. "Sage") or formatted direct action title (e.g. "Get Weather"). */
|
|
@@ -179,6 +181,11 @@ export declare class RealtimeSessionState {
|
|
|
179
181
|
private onResult;
|
|
180
182
|
/** Latest narration REPLACES the previous one (never accumulated). */
|
|
181
183
|
private onNarration;
|
|
184
|
+
/**
|
|
185
|
+
* Creates or updates a narration delegation card representing model-authored thought / reasoning.
|
|
186
|
+
* Cards are immutable; narration cards are excluded from anchoring the live narration note (Reviewer Items 21 & 22).
|
|
187
|
+
*/
|
|
188
|
+
private onThought;
|
|
182
189
|
/**
|
|
183
190
|
* Immutable replacement of a card: new card object, new thread-item wrapper, new
|
|
184
191
|
* Items array, rebuilt Cards array — so every binding sees a fresh reference.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime-session-state.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/realtime/realtime-session-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,OAAO,EAAgB,MAAM,MAAM,CAAC;AACzD,OAAO,EACL,eAAe,EAAE,0BAA0B,EAAE,wBAAwB,EAAE,2BAA2B,
|
|
1
|
+
{"version":3,"file":"realtime-session-state.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/realtime/realtime-session-state.ts"],"names":[],"mappings":"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"}
|
|
@@ -71,6 +71,9 @@ export class RealtimeSessionState {
|
|
|
71
71
|
return; // already attached
|
|
72
72
|
}
|
|
73
73
|
this.subs.push(voice.Captions$.subscribe(c => this.onCaptions(c)), voice.DelegationProgress$.subscribe(p => this.onProgress(p)), voice.DelegationResult$.subscribe(r => this.onResult(r)), voice.DelegationNarration$.subscribe(n => this.onNarration(n)));
|
|
74
|
+
if (voice.ThoughtNarration$) {
|
|
75
|
+
this.subs.push(voice.ThoughtNarration$.subscribe(t => this.onThought(t)));
|
|
76
|
+
}
|
|
74
77
|
}
|
|
75
78
|
/** Unsubscribes from all session streams. Call from the owning shell's ngOnDestroy. */
|
|
76
79
|
Detach() {
|
|
@@ -278,6 +281,45 @@ export class RealtimeSessionState {
|
|
|
278
281
|
this.Narration = narration.Text;
|
|
279
282
|
this.Changed$.next();
|
|
280
283
|
}
|
|
284
|
+
/**
|
|
285
|
+
* Creates or updates a narration delegation card representing model-authored thought / reasoning.
|
|
286
|
+
* Cards are immutable; narration cards are excluded from anchoring the live narration note (Reviewer Items 21 & 22).
|
|
287
|
+
*/
|
|
288
|
+
onThought(thought) {
|
|
289
|
+
const callId = thought.CallID ?? 'model-thought';
|
|
290
|
+
const isDone = thought.IsFinal ?? true;
|
|
291
|
+
const existing = this.cardsByCallId.get(callId);
|
|
292
|
+
if (existing) {
|
|
293
|
+
this.replaceCard({
|
|
294
|
+
...existing,
|
|
295
|
+
LatestMessage: thought.Text,
|
|
296
|
+
Done: isDone,
|
|
297
|
+
Success: true,
|
|
298
|
+
Result: isDone ? thought.Text : existing.Result,
|
|
299
|
+
FinishedAt: isDone ? Date.now() : existing.FinishedAt,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
const card = {
|
|
304
|
+
CallID: callId,
|
|
305
|
+
Kind: 'narration',
|
|
306
|
+
AgentName: this.AgentName,
|
|
307
|
+
LatestStep: 'reasoning',
|
|
308
|
+
LatestMessage: thought.Text,
|
|
309
|
+
Done: isDone,
|
|
310
|
+
Success: true,
|
|
311
|
+
Result: isDone ? thought.Text : null,
|
|
312
|
+
RunRef: this.shortRunRef(callId),
|
|
313
|
+
StartedAt: Date.now(),
|
|
314
|
+
FinishedAt: isDone ? Date.now() : undefined,
|
|
315
|
+
};
|
|
316
|
+
this.cardsByCallId.set(callId, card);
|
|
317
|
+
this.Items = [...this.Items, { Kind: 'delegation', Card: card }];
|
|
318
|
+
this.rebuildCards();
|
|
319
|
+
this.recomputeActive();
|
|
320
|
+
}
|
|
321
|
+
this.Changed$.next();
|
|
322
|
+
}
|
|
281
323
|
/**
|
|
282
324
|
* Immutable replacement of a card: new card object, new thread-item wrapper, new
|
|
283
325
|
* Items array, rebuilt Cards array — so every binding sees a fresh reference.
|
|
@@ -299,7 +341,7 @@ export class RealtimeSessionState {
|
|
|
299
341
|
}
|
|
300
342
|
/** Recomputes which still-running delegation anchors the live narration note. */
|
|
301
343
|
recomputeActive() {
|
|
302
|
-
const running = this.Cards.find(c => !c.Done);
|
|
344
|
+
const running = this.Cards.find(c => !c.Done && c.Kind !== 'narration');
|
|
303
345
|
this.ActiveCallId = running ? running.CallID : null;
|
|
304
346
|
}
|
|
305
347
|
/** Derives a short, stable run reference (e.g. "#a3f1") from the call id. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime-session-state.js","sourceRoot":"","sources":["../../../../src/lib/components/realtime/realtime-session-state.ts"],"names":[],"mappings":"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","sourcesContent":["import { Observable, Subject, Subscription } from 'rxjs';\nimport {\n RealtimeCaption, RealtimeDelegationProgress, RealtimeDelegationResult, RealtimeDelegationNarration\n} from '../../services/realtime-session.service';\nimport { ParsedDelegationArtifact, FormatToolName } from '../../services/delegation-result-parser';\n\n/**\n * The four reactive session streams {@link RealtimeSessionState} merges — structurally\n * satisfied by `RealtimeSessionService`. Narrowed to an interface so the state (and its unit\n * tests) depend only on the streams, not on the full service / GraphQL import chain.\n */\nexport interface RealtimeSessionStreams {\n /** Growing user/assistant caption list. */\n Captions$: Observable<RealtimeCaption[]>;\n /** Progress updates from delegated agent runs. */\n DelegationProgress$: Observable<RealtimeDelegationProgress>;\n /** Terminal delegation results. */\n DelegationResult$: Observable<RealtimeDelegationResult>;\n /** Ephemeral spoken progress narrations. */\n DelegationNarration$: Observable<RealtimeDelegationNarration>;\n}\n\n/**\n * The view-model for one delegated tool/agent call in the live session. Built by\n * {@link RealtimeSessionState} from the `DelegationProgress$` / `DelegationResult$`\n * streams (correlated by `CallID`) and rendered both inline in the session thread\n * and as a compact card in the activity rail.\n *\n * Card objects are treated as IMMUTABLE — every progress/result event REPLACES the\n * card object (and the arrays containing it) so Angular bindings always see fresh\n * references and in-place updates reliably re-render.\n */\nexport interface RealtimeDelegationCardVM {\n /** What this card represents: a delegated agent run, or a direct action invoked by the co-agent. */\n Kind: 'agent' | 'action';\n /** The `invoke-target-agent` or direct-action call this card represents. */\n CallID: string;\n /** Display name of the delegated agent (e.g. \"Sage\") or formatted direct action title (e.g. \"Get Weather\"). */\n AgentName: string;\n /** The raw tool name when Kind === 'action' (e.g. \"File_Storage_List_Objects\"). */\n ToolName?: string;\n /** Latest human-readable progress message from the stream. */\n LatestMessage: string;\n /** The delegation phase (`prompt_execution` | `action_execution` | `direct_action` | …). */\n LatestStep: string;\n /** Optional completion percentage (0–100) when the server supplies it. */\n Percentage?: number;\n /** `true` once the delegation reached a terminal result. */\n Done: boolean;\n /** Whether the delegated work succeeded (meaningful once {@link Done} is true). */\n Success: boolean;\n /** Short run identifier (e.g. \"#a3f1\") if known; shown in expanded provenance detail. */\n RunRef?: string;\n /**\n * ID of the delegated agent run (`MJ: AI Agent Runs`) once the result reports it.\n * Powers the gear-gated \"Open run\" developer link on the card / rail entry.\n */\n RunID?: string;\n /** The real result text once the delegation completes. */\n Result?: string | null;\n /**\n * Artifacts the delegated run produced (set with the terminal result). Powers the\n * \"View\" affordance on done cards / rail entries and the surface panel's artifact tabs.\n */\n Artifacts?: ParsedDelegationArtifact[];\n /** Epoch ms when the first progress event for this call arrived. */\n StartedAt: number;\n /** Epoch ms when the terminal result arrived. */\n FinishedAt?: number;\n}\n\n/** A caption turn (user or assistant) in the unified thread. */\nexport interface RealtimeThreadCaptionItem {\n Kind: 'caption';\n Role: 'User' | 'Assistant';\n Text: string;\n}\n\n/** A delegation card in the unified thread (working → done chip). */\nexport interface RealtimeThreadDelegationItem {\n Kind: 'delegation';\n Card: RealtimeDelegationCardVM;\n}\n\n/**\n * A SESSION-BOUNDARY divider in the unified thread — a subtle, token-styled rule marking\n * where one session leg ends and another begins. Two producers:\n * - review→live continuation ({@link RealtimeSessionState.StartLiveContinuation}):\n * \"Resumed live session · <time>\" before the new live items;\n * - multi-leg session review (`BuildReviewThreadItems` over a `lastSessionId` chain):\n * \"Session leg · started <time>\" between legs, carrying the PREVIOUS leg's CloseReason\n * as a small chip so the reader sees why that leg ended.\n */\nexport interface RealtimeThreadDividerItem {\n Kind: 'divider';\n /** The divider's label (e.g. \"Resumed live session\", \"Session leg\"). */\n Label: string;\n /** Timestamp rendered after the label (\"· <time>\"), or null when unknown. */\n At: Date | null;\n /** Font Awesome icon class rendered before the label (e.g. `fa-solid fa-phone`). */\n Icon: string;\n /** Why the PRECEDING leg closed (review chains only) — rendered as a small chip. */\n CloseReason?: string | null;\n}\n\n/** One entry in the chronological thread: a caption bubble, a delegation card, or a leg divider. */\nexport type RealtimeThreadItem = RealtimeThreadCaptionItem | RealtimeThreadDelegationItem | RealtimeThreadDividerItem;\n\nexport { FormatToolName } from '../../services/delegation-result-parser';\n\n/**\n * Maps a raw delegation step id to a human-friendly phrase. Unknown steps fall back to\n * the raw progress message (per product direction) so the UI never shows snake_case ids.\n */\nexport function FriendlyStepLabel(step: string, message: string): string {\n switch (step) {\n case 'direct_action': return 'Looking that up';\n case 'prompt_execution': return 'Thinking it through';\n case 'action_execution': return 'Running actions';\n case 'subagent_execution': return 'Working with another agent';\n case 'decision_processing': return 'Deciding next steps';\n default: return message || 'Working';\n }\n}\n\n/** Formats an elapsed duration in ms as a compact human string (\"8s\", \"1m 04s\"). */\nexport function FormatElapsed(ms: number): string {\n const totalSec = Math.max(0, Math.round(ms / 1000));\n if (totalSec < 60) {\n return `${totalSec}s`;\n }\n const m = Math.floor(totalSec / 60);\n const s = totalSec % 60;\n return `${m}m ${s.toString().padStart(2, '0')}s`;\n}\n\n/**\n * SINGLE SOURCE of merged live-session state, owned by the overlay shell\n * (`RealtimeSessionOverlayComponent`) and passed via `@Input()` to BOTH the session\n * thread and the activity rail — so the per-CallID card bookkeeping and subscription\n * logic exists exactly once.\n *\n * Merges FOUR reactive streams from {@link RealtimeSessionService}:\n * - `Captions$` — growing user/assistant caption list → appended to {@link Items}.\n * - `DelegationProgress$` — first event per `CallID` inserts a working card at the thread\n * tail; later events REPLACE the card immutably (new object +\n * new arrays) so change detection always fires.\n * - `DelegationResult$` — flips the card to Done/Failed with the real result text.\n * - `DelegationNarration$` — the EPHEMERAL spoken progress note (latest only, never\n * accumulated), cleared when no delegation is running anymore.\n *\n * Consumers subscribe to {@link Changed$} and `markForCheck()`; all exposed collections\n * are replaced (never mutated) on change.\n */\nexport class RealtimeSessionState {\n /** Display name used for new delegation cards (set by the overlay's AgentName input). */\n public AgentName = 'Sage';\n\n /** The merged, chronological thread (captions + delegation cards), oldest first. */\n public Items: RealtimeThreadItem[] = [];\n\n /** All delegation cards of the session, NEWEST first — the activity rail's list. */\n public Cards: RealtimeDelegationCardVM[] = [];\n\n /** Latest ephemeral narration text, or null when none should be shown. */\n public Narration: string | null = null;\n\n /** CallID of the most recently started, still-running delegation (anchors the live note). */\n public ActiveCallId: string | null = null;\n\n /** Emits after every state change so views can mark themselves for check. */\n public readonly Changed$ = new Subject<void>();\n\n /** Number of captions already merged into {@link Items} (append-only bookkeeping). */\n private placedCaptionCount = 0;\n /** Live card lookup by CallID (insertion order = start order). */\n private cardsByCallId = new Map<string, RealtimeDelegationCardVM>();\n private subs: Subscription[] = [];\n\n /** True while at least one delegation is still running. */\n public get HasRunningDelegation(): boolean {\n return this.ActiveCallId !== null;\n }\n\n /** Subscribes to the session streams. Call once from the owning overlay shell. */\n public Attach(voice: RealtimeSessionStreams): void {\n if (this.subs.length > 0) {\n return; // already attached\n }\n this.subs.push(\n voice.Captions$.subscribe(c => this.onCaptions(c)),\n voice.DelegationProgress$.subscribe(p => this.onProgress(p)),\n voice.DelegationResult$.subscribe(r => this.onResult(r)),\n voice.DelegationNarration$.subscribe(n => this.onNarration(n))\n );\n }\n\n /** Unsubscribes from all session streams. Call from the owning shell's ngOnDestroy. */\n public Detach(): void {\n for (const s of this.subs) {\n s.unsubscribe();\n }\n this.subs = [];\n }\n\n /**\n * SESSION REVIEW population path: replaces ALL merged state with a pre-built\n * HISTORICAL thread (caption turns + done delegation cards, oldest first — see\n * `BuildReviewThreadItems`). Resets the live-stream bookkeeping first, so a later\n * live session starts from a clean merge baseline; delegation cards are indexed by\n * CallID so the rail's {@link Cards} list and {@link ActiveCallId} derive normally.\n */\n public LoadHistoricalItems(items: RealtimeThreadItem[]): void {\n this.reset();\n this.Items = items.map(item => {\n if (item.Kind === 'delegation') {\n const card: RealtimeDelegationCardVM = item.Card.Kind\n ? item.Card\n : { ...item.Card, Kind: 'agent' };\n this.cardsByCallId.set(card.CallID, card);\n return { Kind: 'delegation', Card: card };\n }\n return item;\n });\n this.rebuildCards();\n this.recomputeActive();\n this.Changed$.next();\n }\n\n /** Clears all merged state and notifies subscribers (e.g. leaving session review). */\n public Clear(): void {\n this.reset();\n this.Changed$.next();\n }\n\n /**\n * REVIEW→LIVE continuation path: KEEPS the historical thread (and its delegation cards\n * in the rail) and appends a \"Resumed live session\" {@link RealtimeThreadDividerItem},\n * then resets only the LIVE-merge bookkeeping so the new session's captions and cards\n * append cleanly AFTER the divider:\n * - `placedCaptionCount` → 0 (the fresh session's caption stream starts empty, so the\n * start-of-session `[]` emission is a no-op rather than a reset);\n * - the ephemeral narration is dropped (nothing is running yet);\n * - historical cards stay indexed by CallID — new live CallIDs are unique, so live\n * cards insert alongside them and `ActiveCallId` recomputes normally.\n */\n public StartLiveContinuation(at: Date = new Date()): void {\n const divider: RealtimeThreadDividerItem = {\n Kind: 'divider',\n Label: 'Resumed live session',\n At: at,\n Icon: 'fa-solid fa-phone'\n };\n this.Items = [...this.Items, divider];\n this.Narration = null;\n this.placedCaptionCount = 0;\n this.recomputeActive();\n this.Changed$.next();\n }\n\n /** Appends any newly-arrived captions, keeping order relative to delegation cards, and updates streaming captions in place. */\n private onCaptions(captions: RealtimeCaption[]): void {\n if (captions.length < this.placedCaptionCount) {\n // Captions are cleared on a fresh session — reset all merge state.\n this.reset();\n this.Changed$.next();\n return;\n }\n\n // Find the indices in this.Items of the captions already placed in the current session.\n // They are the last `placedCaptionCount` caption items in this.Items.\n const liveCaptionIndices: number[] = [];\n for (let idx = this.Items.length - 1; idx >= 0 && liveCaptionIndices.length < this.placedCaptionCount; idx--) {\n if (this.Items[idx].Kind === 'caption') {\n liveCaptionIndices.unshift(idx);\n }\n }\n\n let nextItems = this.Items;\n let hasChanges = false;\n\n // 1. Update any already-placed captions in place if their text or role changed (e.g. streaming deltas or corrections)\n const updateCount = Math.min(this.placedCaptionCount, liveCaptionIndices.length);\n for (let i = 0; i < updateCount; i++) {\n const itemIdx = liveCaptionIndices[i];\n const current = nextItems[itemIdx] as RealtimeThreadCaptionItem;\n const incoming = captions[i];\n if (current.Role !== incoming.Role || current.Text !== incoming.Text) {\n if (nextItems === this.Items) {\n nextItems = [...this.Items];\n }\n nextItems[itemIdx] = { Kind: 'caption', Role: incoming.Role, Text: incoming.Text };\n hasChanges = true;\n }\n }\n\n // 2. Append newly-arrived captions\n if (captions.length > this.placedCaptionCount) {\n const appended: RealtimeThreadItem[] = [];\n for (let i = this.placedCaptionCount; i < captions.length; i++) {\n appended.push({ Kind: 'caption', Role: captions[i].Role, Text: captions[i].Text });\n }\n if (nextItems === this.Items) {\n nextItems = [...this.Items];\n }\n nextItems = [...nextItems, ...appended];\n this.placedCaptionCount = captions.length;\n hasChanges = true;\n }\n\n if (hasChanges) {\n this.Items = nextItems;\n this.Changed$.next();\n }\n }\n\n /** Inserts a new working card, or immutably replaces the existing one for this CallID. */\n private onProgress(progress: RealtimeDelegationProgress): void {\n const existing = this.cardsByCallId.get(progress.CallID);\n if (existing) {\n this.replaceCard({\n ...existing,\n ToolName: progress.ToolName ?? existing.ToolName,\n LatestStep: progress.Step,\n LatestMessage: progress.Message,\n Percentage: progress.Percentage\n });\n } else {\n this.insertCard(progress);\n }\n this.Changed$.next();\n }\n\n /** Creates a working card for a first-seen CallID and appends it to the thread tail. */\n private insertCard(progress: RealtimeDelegationProgress): void {\n const isAction = !!progress.ToolName && progress.ToolName !== 'invoke-target-agent';\n const card: RealtimeDelegationCardVM = {\n CallID: progress.CallID,\n Kind: isAction ? 'action' : 'agent',\n ToolName: progress.ToolName,\n AgentName: isAction ? FormatToolName(progress.ToolName!) : this.AgentName,\n LatestStep: progress.Step,\n LatestMessage: progress.Message,\n Percentage: progress.Percentage,\n Done: false,\n Success: false,\n RunRef: this.shortRunRef(progress.CallID),\n StartedAt: Date.now()\n };\n this.cardsByCallId.set(progress.CallID, card);\n this.Items = [...this.Items, { Kind: 'delegation', Card: card }];\n this.rebuildCards();\n this.recomputeActive();\n }\n\n /** Flips the card for a finished delegation to Done/Failed with its real result. */\n private onResult(result: RealtimeDelegationResult): void {\n const existing = this.cardsByCallId.get(result.CallID);\n if (!existing) {\n if (!result.ToolName) {\n return; // untracked result without tool name — ignore\n }\n // Create-or-update safety net: if a terminal result arrives without prior progress\n // (e.g. fast direct action or missed stream packet), construct and insert the done card\n // rather than dropping the result.\n const isAction = result.ToolName !== 'invoke-target-agent';\n const toolName = result.ToolName;\n const card: RealtimeDelegationCardVM = {\n CallID: result.CallID,\n Kind: isAction ? 'action' : 'agent',\n ToolName: toolName,\n AgentName: isAction ? FormatToolName(toolName) : this.AgentName,\n LatestStep: isAction ? 'direct_action' : 'decision_processing',\n LatestMessage: isAction ? `Executed ${FormatToolName(toolName)}` : 'Completed',\n Done: true,\n Success: result.Success,\n Result: result.Output,\n RunRef: this.shortRunRef(result.CallID),\n RunID: result.RunID,\n Artifacts: isAction ? undefined : result.Artifacts,\n StartedAt: Date.now(),\n FinishedAt: Date.now()\n };\n this.cardsByCallId.set(result.CallID, card);\n this.Items = [...this.Items, { Kind: 'delegation', Card: card }];\n this.rebuildCards();\n this.recomputeActive();\n if (!this.HasRunningDelegation) {\n this.Narration = null;\n }\n this.Changed$.next();\n return;\n }\n this.replaceCard({\n ...existing,\n ToolName: result.ToolName ?? existing.ToolName,\n Done: true,\n Success: result.Success,\n Result: result.Output,\n RunID: result.RunID ?? existing.RunID,\n Artifacts: existing.Kind === 'action' ? undefined : (result.Artifacts ?? existing.Artifacts),\n FinishedAt: Date.now()\n });\n if (!this.HasRunningDelegation) {\n // All delegations finished — the live narration note fades away.\n this.Narration = null;\n }\n this.Changed$.next();\n }\n\n /** Latest narration REPLACES the previous one (never accumulated). */\n private onNarration(narration: RealtimeDelegationNarration): void {\n this.Narration = narration.Text;\n this.Changed$.next();\n }\n\n /**\n * Immutable replacement of a card: new card object, new thread-item wrapper, new\n * Items array, rebuilt Cards array — so every binding sees a fresh reference.\n */\n private replaceCard(card: RealtimeDelegationCardVM): void {\n this.cardsByCallId.set(card.CallID, card);\n const idx = this.Items.findIndex(i => i.Kind === 'delegation' && i.Card.CallID === card.CallID);\n if (idx >= 0) {\n const next = [...this.Items];\n next[idx] = { Kind: 'delegation', Card: card };\n this.Items = next;\n }\n this.rebuildCards();\n this.recomputeActive();\n }\n\n /** Rebuilds the rail's newest-first card list from the (start-ordered) map. */\n private rebuildCards(): void {\n this.Cards = Array.from(this.cardsByCallId.values()).reverse();\n }\n\n /** Recomputes which still-running delegation anchors the live narration note. */\n private recomputeActive(): void {\n const running = this.Cards.find(c => !c.Done);\n this.ActiveCallId = running ? running.CallID : null;\n }\n\n /** Derives a short, stable run reference (e.g. \"#a3f1\") from the call id. */\n private shortRunRef(callId: string): string {\n const compact = callId.replace(/[^a-z0-9]/gi, '');\n return compact.length >= 4 ? `#${compact.slice(-4).toLowerCase()}` : `#${compact.toLowerCase()}`;\n }\n\n /** Clears all merged state (fresh session). */\n private reset(): void {\n this.Items = [];\n this.Cards = [];\n this.Narration = null;\n this.ActiveCallId = null;\n this.placedCaptionCount = 0;\n this.cardsByCallId.clear();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"realtime-session-state.js","sourceRoot":"","sources":["../../../../src/lib/components/realtime/realtime-session-state.ts"],"names":[],"mappings":"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","sourcesContent":["import { Observable, Subject, Subscription } from 'rxjs';\nimport {\n RealtimeCaption, RealtimeDelegationProgress, RealtimeDelegationResult, RealtimeDelegationNarration, RealtimeThoughtNarration\n} from '../../services/realtime-session.service';\nimport { ParsedDelegationArtifact, FormatToolName } from '../../services/delegation-result-parser';\n\n/**\n * The reactive session streams {@link RealtimeSessionState} merges — structurally\n * satisfied by `RealtimeSessionService`. Narrowed to an interface so the state (and its unit\n * tests) depend only on the streams, not on the full service / GraphQL import chain.\n */\nexport interface RealtimeSessionStreams {\n /** Growing user/assistant caption list. */\n Captions$: Observable<RealtimeCaption[]>;\n /** Progress updates from delegated agent runs. */\n DelegationProgress$: Observable<RealtimeDelegationProgress>;\n /** Terminal delegation results. */\n DelegationResult$: Observable<RealtimeDelegationResult>;\n /** Ephemeral spoken progress narrations. */\n DelegationNarration$: Observable<RealtimeDelegationNarration>;\n /** Model-authored thought / reasoning narrations. */\n ThoughtNarration$?: Observable<RealtimeThoughtNarration>;\n}\n\n/**\n * The view-model for one delegated tool/agent call in the live session. Built by\n * {@link RealtimeSessionState} from the `DelegationProgress$` / `DelegationResult$`\n * streams (correlated by `CallID`) and rendered both inline in the session thread\n * and as a compact card in the activity rail.\n *\n * Card objects are treated as IMMUTABLE — every progress/result event REPLACES the\n * card object (and the arrays containing it) so Angular bindings always see fresh\n * references and in-place updates reliably re-render.\n */\nexport interface RealtimeDelegationCardVM {\n /** What this card represents: a delegated agent run, a direct action, or model-authored narration. */\n Kind: 'agent' | 'action' | 'narration';\n /** The `invoke-target-agent` or direct-action call this card represents. */\n CallID: string;\n /** Display name of the delegated agent (e.g. \"Sage\") or formatted direct action title (e.g. \"Get Weather\"). */\n AgentName: string;\n /** The raw tool name when Kind === 'action' (e.g. \"File_Storage_List_Objects\"). */\n ToolName?: string;\n /** Latest human-readable progress message from the stream. */\n LatestMessage: string;\n /** The delegation phase (`prompt_execution` | `action_execution` | `direct_action` | …). */\n LatestStep: string;\n /** Optional completion percentage (0–100) when the server supplies it. */\n Percentage?: number;\n /** `true` once the delegation reached a terminal result. */\n Done: boolean;\n /** Whether the delegated work succeeded (meaningful once {@link Done} is true). */\n Success: boolean;\n /** Short run identifier (e.g. \"#a3f1\") if known; shown in expanded provenance detail. */\n RunRef?: string;\n /**\n * ID of the delegated agent run (`MJ: AI Agent Runs`) once the result reports it.\n * Powers the gear-gated \"Open run\" developer link on the card / rail entry.\n */\n RunID?: string;\n /** The real result text once the delegation completes. */\n Result?: string | null;\n /**\n * Artifacts the delegated run produced (set with the terminal result). Powers the\n * \"View\" affordance on done cards / rail entries and the surface panel's artifact tabs.\n */\n Artifacts?: ParsedDelegationArtifact[];\n /** Epoch ms when the first progress event for this call arrived. */\n StartedAt: number;\n /** Epoch ms when the terminal result arrived. */\n FinishedAt?: number;\n}\n\n/** A caption turn (user or assistant) in the unified thread. */\nexport interface RealtimeThreadCaptionItem {\n Kind: 'caption';\n Role: 'User' | 'Assistant';\n Text: string;\n}\n\n/** A delegation card in the unified thread (working → done chip). */\nexport interface RealtimeThreadDelegationItem {\n Kind: 'delegation';\n Card: RealtimeDelegationCardVM;\n}\n\n/**\n * A SESSION-BOUNDARY divider in the unified thread — a subtle, token-styled rule marking\n * where one session leg ends and another begins. Two producers:\n * - review→live continuation ({@link RealtimeSessionState.StartLiveContinuation}):\n * \"Resumed live session · <time>\" before the new live items;\n * - multi-leg session review (`BuildReviewThreadItems` over a `lastSessionId` chain):\n * \"Session leg · started <time>\" between legs, carrying the PREVIOUS leg's CloseReason\n * as a small chip so the reader sees why that leg ended.\n */\nexport interface RealtimeThreadDividerItem {\n Kind: 'divider';\n /** The divider's label (e.g. \"Resumed live session\", \"Session leg\"). */\n Label: string;\n /** Timestamp rendered after the label (\"· <time>\"), or null when unknown. */\n At: Date | null;\n /** Font Awesome icon class rendered before the label (e.g. `fa-solid fa-phone`). */\n Icon: string;\n /** Why the PRECEDING leg closed (review chains only) — rendered as a small chip. */\n CloseReason?: string | null;\n}\n\n/** One entry in the chronological thread: a caption bubble, a delegation card, or a leg divider. */\nexport type RealtimeThreadItem = RealtimeThreadCaptionItem | RealtimeThreadDelegationItem | RealtimeThreadDividerItem;\n\nexport { FormatToolName } from '../../services/delegation-result-parser';\n\n/**\n * Maps a raw delegation step id to a human-friendly phrase. Unknown steps fall back to\n * the raw progress message (per product direction) so the UI never shows snake_case ids.\n */\nexport function FriendlyStepLabel(step: string, message: string): string {\n switch (step) {\n case 'direct_action': return 'Looking that up';\n case 'prompt_execution': return 'Thinking it through';\n case 'action_execution': return 'Running actions';\n case 'subagent_execution': return 'Working with another agent';\n case 'decision_processing': return 'Deciding next steps';\n default: return message || 'Working';\n }\n}\n\n/** Formats an elapsed duration in ms as a compact human string (\"8s\", \"1m 04s\"). */\nexport function FormatElapsed(ms: number): string {\n const totalSec = Math.max(0, Math.round(ms / 1000));\n if (totalSec < 60) {\n return `${totalSec}s`;\n }\n const m = Math.floor(totalSec / 60);\n const s = totalSec % 60;\n return `${m}m ${s.toString().padStart(2, '0')}s`;\n}\n\n/**\n * SINGLE SOURCE of merged live-session state, owned by the overlay shell\n * (`RealtimeSessionOverlayComponent`) and passed via `@Input()` to BOTH the session\n * thread and the activity rail — so the per-CallID card bookkeeping and subscription\n * logic exists exactly once.\n *\n * Merges FOUR reactive streams from {@link RealtimeSessionService}:\n * - `Captions$` — growing user/assistant caption list → appended to {@link Items}.\n * - `DelegationProgress$` — first event per `CallID` inserts a working card at the thread\n * tail; later events REPLACE the card immutably (new object +\n * new arrays) so change detection always fires.\n * - `DelegationResult$` — flips the card to Done/Failed with the real result text.\n * - `DelegationNarration$` — the EPHEMERAL spoken progress note (latest only, never\n * accumulated), cleared when no delegation is running anymore.\n *\n * Consumers subscribe to {@link Changed$} and `markForCheck()`; all exposed collections\n * are replaced (never mutated) on change.\n */\nexport class RealtimeSessionState {\n /** Display name used for new delegation cards (set by the overlay's AgentName input). */\n public AgentName = 'Sage';\n\n /** The merged, chronological thread (captions + delegation cards), oldest first. */\n public Items: RealtimeThreadItem[] = [];\n\n /** All delegation cards of the session, NEWEST first — the activity rail's list. */\n public Cards: RealtimeDelegationCardVM[] = [];\n\n /** Latest ephemeral narration text, or null when none should be shown. */\n public Narration: string | null = null;\n\n /** CallID of the most recently started, still-running delegation (anchors the live note). */\n public ActiveCallId: string | null = null;\n\n /** Emits after every state change so views can mark themselves for check. */\n public readonly Changed$ = new Subject<void>();\n\n /** Number of captions already merged into {@link Items} (append-only bookkeeping). */\n private placedCaptionCount = 0;\n /** Live card lookup by CallID (insertion order = start order). */\n private cardsByCallId = new Map<string, RealtimeDelegationCardVM>();\n private subs: Subscription[] = [];\n\n /** True while at least one delegation is still running. */\n public get HasRunningDelegation(): boolean {\n return this.ActiveCallId !== null;\n }\n\n /** Subscribes to the session streams. Call once from the owning overlay shell. */\n public Attach(voice: RealtimeSessionStreams): void {\n if (this.subs.length > 0) {\n return; // already attached\n }\n this.subs.push(\n voice.Captions$.subscribe(c => this.onCaptions(c)),\n voice.DelegationProgress$.subscribe(p => this.onProgress(p)),\n voice.DelegationResult$.subscribe(r => this.onResult(r)),\n voice.DelegationNarration$.subscribe(n => this.onNarration(n))\n );\n if (voice.ThoughtNarration$) {\n this.subs.push(\n voice.ThoughtNarration$.subscribe(t => this.onThought(t))\n );\n }\n }\n\n /** Unsubscribes from all session streams. Call from the owning shell's ngOnDestroy. */\n public Detach(): void {\n for (const s of this.subs) {\n s.unsubscribe();\n }\n this.subs = [];\n }\n\n /**\n * SESSION REVIEW population path: replaces ALL merged state with a pre-built\n * HISTORICAL thread (caption turns + done delegation cards, oldest first — see\n * `BuildReviewThreadItems`). Resets the live-stream bookkeeping first, so a later\n * live session starts from a clean merge baseline; delegation cards are indexed by\n * CallID so the rail's {@link Cards} list and {@link ActiveCallId} derive normally.\n */\n public LoadHistoricalItems(items: RealtimeThreadItem[]): void {\n this.reset();\n this.Items = items.map(item => {\n if (item.Kind === 'delegation') {\n const card: RealtimeDelegationCardVM = item.Card.Kind\n ? item.Card\n : { ...item.Card, Kind: 'agent' };\n this.cardsByCallId.set(card.CallID, card);\n return { Kind: 'delegation', Card: card };\n }\n return item;\n });\n this.rebuildCards();\n this.recomputeActive();\n this.Changed$.next();\n }\n\n /** Clears all merged state and notifies subscribers (e.g. leaving session review). */\n public Clear(): void {\n this.reset();\n this.Changed$.next();\n }\n\n /**\n * REVIEW→LIVE continuation path: KEEPS the historical thread (and its delegation cards\n * in the rail) and appends a \"Resumed live session\" {@link RealtimeThreadDividerItem},\n * then resets only the LIVE-merge bookkeeping so the new session's captions and cards\n * append cleanly AFTER the divider:\n * - `placedCaptionCount` → 0 (the fresh session's caption stream starts empty, so the\n * start-of-session `[]` emission is a no-op rather than a reset);\n * - the ephemeral narration is dropped (nothing is running yet);\n * - historical cards stay indexed by CallID — new live CallIDs are unique, so live\n * cards insert alongside them and `ActiveCallId` recomputes normally.\n */\n public StartLiveContinuation(at: Date = new Date()): void {\n const divider: RealtimeThreadDividerItem = {\n Kind: 'divider',\n Label: 'Resumed live session',\n At: at,\n Icon: 'fa-solid fa-phone'\n };\n this.Items = [...this.Items, divider];\n this.Narration = null;\n this.placedCaptionCount = 0;\n this.recomputeActive();\n this.Changed$.next();\n }\n\n /** Appends any newly-arrived captions, keeping order relative to delegation cards, and updates streaming captions in place. */\n private onCaptions(captions: RealtimeCaption[]): void {\n if (captions.length < this.placedCaptionCount) {\n // Captions are cleared on a fresh session — reset all merge state.\n this.reset();\n this.Changed$.next();\n return;\n }\n\n // Find the indices in this.Items of the captions already placed in the current session.\n // They are the last `placedCaptionCount` caption items in this.Items.\n const liveCaptionIndices: number[] = [];\n for (let idx = this.Items.length - 1; idx >= 0 && liveCaptionIndices.length < this.placedCaptionCount; idx--) {\n if (this.Items[idx].Kind === 'caption') {\n liveCaptionIndices.unshift(idx);\n }\n }\n\n let nextItems = this.Items;\n let hasChanges = false;\n\n // 1. Update any already-placed captions in place if their text or role changed (e.g. streaming deltas or corrections)\n const updateCount = Math.min(this.placedCaptionCount, liveCaptionIndices.length);\n for (let i = 0; i < updateCount; i++) {\n const itemIdx = liveCaptionIndices[i];\n const current = nextItems[itemIdx] as RealtimeThreadCaptionItem;\n const incoming = captions[i];\n if (current.Role !== incoming.Role || current.Text !== incoming.Text) {\n if (nextItems === this.Items) {\n nextItems = [...this.Items];\n }\n nextItems[itemIdx] = { Kind: 'caption', Role: incoming.Role, Text: incoming.Text };\n hasChanges = true;\n }\n }\n\n // 2. Append newly-arrived captions\n if (captions.length > this.placedCaptionCount) {\n const appended: RealtimeThreadItem[] = [];\n for (let i = this.placedCaptionCount; i < captions.length; i++) {\n appended.push({ Kind: 'caption', Role: captions[i].Role, Text: captions[i].Text });\n }\n if (nextItems === this.Items) {\n nextItems = [...this.Items];\n }\n nextItems = [...nextItems, ...appended];\n this.placedCaptionCount = captions.length;\n hasChanges = true;\n }\n\n if (hasChanges) {\n this.Items = nextItems;\n this.Changed$.next();\n }\n }\n\n /** Inserts a new working card, or immutably replaces the existing one for this CallID. */\n private onProgress(progress: RealtimeDelegationProgress): void {\n const existing = this.cardsByCallId.get(progress.CallID);\n if (existing) {\n this.replaceCard({\n ...existing,\n ToolName: progress.ToolName ?? existing.ToolName,\n LatestStep: progress.Step,\n LatestMessage: progress.Message,\n Percentage: progress.Percentage\n });\n } else {\n this.insertCard(progress);\n }\n this.Changed$.next();\n }\n\n /** Creates a working card for a first-seen CallID and appends it to the thread tail. */\n private insertCard(progress: RealtimeDelegationProgress): void {\n const isAction = !!progress.ToolName && progress.ToolName !== 'invoke-target-agent';\n const card: RealtimeDelegationCardVM = {\n CallID: progress.CallID,\n Kind: isAction ? 'action' : 'agent',\n ToolName: progress.ToolName,\n AgentName: isAction ? FormatToolName(progress.ToolName!) : this.AgentName,\n LatestStep: progress.Step,\n LatestMessage: progress.Message,\n Percentage: progress.Percentage,\n Done: false,\n Success: false,\n RunRef: this.shortRunRef(progress.CallID),\n StartedAt: Date.now()\n };\n this.cardsByCallId.set(progress.CallID, card);\n this.Items = [...this.Items, { Kind: 'delegation', Card: card }];\n this.rebuildCards();\n this.recomputeActive();\n }\n\n /** Flips the card for a finished delegation to Done/Failed with its real result. */\n private onResult(result: RealtimeDelegationResult): void {\n const existing = this.cardsByCallId.get(result.CallID);\n if (!existing) {\n if (!result.ToolName) {\n return; // untracked result without tool name — ignore\n }\n // Create-or-update safety net: if a terminal result arrives without prior progress\n // (e.g. fast direct action or missed stream packet), construct and insert the done card\n // rather than dropping the result.\n const isAction = result.ToolName !== 'invoke-target-agent';\n const toolName = result.ToolName;\n const card: RealtimeDelegationCardVM = {\n CallID: result.CallID,\n Kind: isAction ? 'action' : 'agent',\n ToolName: toolName,\n AgentName: isAction ? FormatToolName(toolName) : this.AgentName,\n LatestStep: isAction ? 'direct_action' : 'decision_processing',\n LatestMessage: isAction ? `Executed ${FormatToolName(toolName)}` : 'Completed',\n Done: true,\n Success: result.Success,\n Result: result.Output,\n RunRef: this.shortRunRef(result.CallID),\n RunID: result.RunID,\n Artifacts: isAction ? undefined : result.Artifacts,\n StartedAt: Date.now(),\n FinishedAt: Date.now()\n };\n this.cardsByCallId.set(result.CallID, card);\n this.Items = [...this.Items, { Kind: 'delegation', Card: card }];\n this.rebuildCards();\n this.recomputeActive();\n if (!this.HasRunningDelegation) {\n this.Narration = null;\n }\n this.Changed$.next();\n return;\n }\n this.replaceCard({\n ...existing,\n ToolName: result.ToolName ?? existing.ToolName,\n Done: true,\n Success: result.Success,\n Result: result.Output,\n RunID: result.RunID ?? existing.RunID,\n Artifacts: existing.Kind === 'action' ? undefined : (result.Artifacts ?? existing.Artifacts),\n FinishedAt: Date.now()\n });\n if (!this.HasRunningDelegation) {\n // All delegations finished — the live narration note fades away.\n this.Narration = null;\n }\n this.Changed$.next();\n }\n\n /** Latest narration REPLACES the previous one (never accumulated). */\n private onNarration(narration: RealtimeDelegationNarration): void {\n this.Narration = narration.Text;\n this.Changed$.next();\n }\n\n /**\n * Creates or updates a narration delegation card representing model-authored thought / reasoning.\n * Cards are immutable; narration cards are excluded from anchoring the live narration note (Reviewer Items 21 & 22).\n */\n private onThought(thought: RealtimeThoughtNarration): void {\n const callId = thought.CallID ?? 'model-thought';\n const isDone = thought.IsFinal ?? true;\n const existing = this.cardsByCallId.get(callId);\n if (existing) {\n this.replaceCard({\n ...existing,\n LatestMessage: thought.Text,\n Done: isDone,\n Success: true,\n Result: isDone ? thought.Text : existing.Result,\n FinishedAt: isDone ? Date.now() : existing.FinishedAt,\n });\n } else {\n const card: RealtimeDelegationCardVM = {\n CallID: callId,\n Kind: 'narration',\n AgentName: this.AgentName,\n LatestStep: 'reasoning',\n LatestMessage: thought.Text,\n Done: isDone,\n Success: true,\n Result: isDone ? thought.Text : null,\n RunRef: this.shortRunRef(callId),\n StartedAt: Date.now(),\n FinishedAt: isDone ? Date.now() : undefined,\n };\n this.cardsByCallId.set(callId, card);\n this.Items = [...this.Items, { Kind: 'delegation', Card: card }];\n this.rebuildCards();\n this.recomputeActive();\n }\n this.Changed$.next();\n }\n\n /**\n * Immutable replacement of a card: new card object, new thread-item wrapper, new\n * Items array, rebuilt Cards array — so every binding sees a fresh reference.\n */\n private replaceCard(card: RealtimeDelegationCardVM): void {\n this.cardsByCallId.set(card.CallID, card);\n const idx = this.Items.findIndex(i => i.Kind === 'delegation' && i.Card.CallID === card.CallID);\n if (idx >= 0) {\n const next = [...this.Items];\n next[idx] = { Kind: 'delegation', Card: card };\n this.Items = next;\n }\n this.rebuildCards();\n this.recomputeActive();\n }\n\n /** Rebuilds the rail's newest-first card list from the (start-ordered) map. */\n private rebuildCards(): void {\n this.Cards = Array.from(this.cardsByCallId.values()).reverse();\n }\n\n /** Recomputes which still-running delegation anchors the live narration note. */\n private recomputeActive(): void {\n const running = this.Cards.find(c => !c.Done && c.Kind !== 'narration');\n this.ActiveCallId = running ? running.CallID : null;\n }\n\n /** Derives a short, stable run reference (e.g. \"#a3f1\") from the call id. */\n private shortRunRef(callId: string): string {\n const compact = callId.replace(/[^a-z0-9]/gi, '');\n return compact.length >= 4 ? `#${compact.slice(-4).toLowerCase()}` : `#${compact.toLowerCase()}`;\n }\n\n /** Clears all merged state (fresh session). */\n private reset(): void {\n this.Items = [];\n this.Cards = [];\n this.Narration = null;\n this.ActiveCallId = null;\n this.placedCaptionCount = 0;\n this.cardsByCallId.clear();\n }\n}\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
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 { RemoteBrowserSurfaceComponent } from './remote-browser-surface.component';
|
|
5
6
|
import { RemoteBrowserAudioChunkInput } from './remote-browser-audio-player';
|
|
@@ -29,9 +30,21 @@ import { RemoteBrowserAudioChunkInput } from './remote-browser-audio-player';
|
|
|
29
30
|
* The channel keeps NO client-side state of record — the browser's state lives entirely on
|
|
30
31
|
* the server — so {@link SerializeState} / {@link RestoreState} use the base no-op behavior.
|
|
31
32
|
*/
|
|
32
|
-
export declare class RemoteBrowserChannel extends BaseRealtimeChannelClient<RemoteBrowserSurfaceComponent> {
|
|
33
|
+
export declare class RemoteBrowserChannel extends BaseRealtimeChannelClient<RemoteBrowserSurfaceComponent> implements IChannelFrameProvider {
|
|
33
34
|
/** The live bound surface, when the channel tab's pane is instantiated. */
|
|
34
35
|
private surface;
|
|
36
|
+
/** Shared video bridge streaming browser frames to the model when the model supports inbound video. */
|
|
37
|
+
private videoBridge;
|
|
38
|
+
/**
|
|
39
|
+
* Sourced tracks: Remote Browser can source inbound video to the model when the model supports it.
|
|
40
|
+
*/
|
|
41
|
+
GetSourcedTracks(): readonly RealtimeTrackDescriptor[];
|
|
42
|
+
/**
|
|
43
|
+
* Produces the latest browser screenshot as a base64-encoded frame for the video bridge.
|
|
44
|
+
* Under active screencast streaming, returns null to avoid redundant server snapshot round-trips
|
|
45
|
+
* and duplicate frame sourcing into the video bridge.
|
|
46
|
+
*/
|
|
47
|
+
GetLatestFrame(): Promise<string | null>;
|
|
35
48
|
/** Subscription to the bound surface's `HumanInput` output, torn down on unbind/dispose. */
|
|
36
49
|
private humanInputSub;
|
|
37
50
|
/**
|
|
@@ -89,6 +102,10 @@ export declare class RemoteBrowserChannel extends BaseRealtimeChannelClient<Remo
|
|
|
89
102
|
GetSurfaceComponent(): Type<RemoteBrowserSurfaceComponent>;
|
|
90
103
|
/** First-run intro shown the first time the user opens the Browser tab (once per user). */
|
|
91
104
|
GetOnboardingDetails(): ChannelOnboardingDetails;
|
|
105
|
+
/** Starts the video bridge streaming browser frames to the model when inbound video is supported. */
|
|
106
|
+
protected OnInitialize(): void;
|
|
107
|
+
OnSessionStarted(): void;
|
|
108
|
+
private ensureVideoBridge;
|
|
92
109
|
/**
|
|
93
110
|
* Wires the dynamically-created surface: hands it a snapshot fetcher closing over the
|
|
94
111
|
* channel context (session id + provider live there), so the surface stays transport-
|
|
@@ -110,6 +127,34 @@ export declare class RemoteBrowserChannel extends BaseRealtimeChannelClient<Remo
|
|
|
110
127
|
BindSurface(instance: RemoteBrowserSurfaceComponent): void;
|
|
111
128
|
UnbindSurface(): void;
|
|
112
129
|
Dispose(): void;
|
|
130
|
+
/** Timestamp of the last screencast frame pushed to the video bridge (paces pushes to negotiated cadence). */
|
|
131
|
+
private lastScreencastPushTime;
|
|
132
|
+
/** Base64 content of the last frame pushed to the video bridge (for change-driven deduplication). */
|
|
133
|
+
private lastPushedScreencastFrame;
|
|
134
|
+
/** URL associated with the last frame pushed to the video bridge. */
|
|
135
|
+
private lastPushedUrl;
|
|
136
|
+
/** Minimum allowable screencast push cadence (250ms = 4 fps debounce limit during active bursts). */
|
|
137
|
+
private static readonly SCREENCAST_MIN_CADENCE_MS;
|
|
138
|
+
/** Default screencast push cadence when not otherwise negotiated (1000ms = 1 fps ceiling for Gemini Live). */
|
|
139
|
+
private static readonly SCREENCAST_DEFAULT_CADENCE_MS;
|
|
140
|
+
/** Maximum interval (ms) between visual frame pushes on a static page (heartbeat to maintain track liveness). */
|
|
141
|
+
private static readonly SCREENCAST_HEARTBEAT_MS;
|
|
142
|
+
/** Trailing timer to deliver the settled resting frame after a burst of rapid user activity. */
|
|
143
|
+
private screencastTrailingTimer;
|
|
144
|
+
/** Pending frame buffered during an active cooldown interval, waiting to settle. */
|
|
145
|
+
private pendingScreencastFrame;
|
|
146
|
+
/** Cancels any active trailing-edge settle timer and clears the handle. */
|
|
147
|
+
private clearScreencastTrailingTimer;
|
|
148
|
+
/**
|
|
149
|
+
* Pushes a frame to the video bridge, recording timestamp, frame content, and URL.
|
|
150
|
+
*/
|
|
151
|
+
private pushScreencastFrame;
|
|
152
|
+
/**
|
|
153
|
+
* Resolves the effective push cadence in milliseconds based on the negotiated inbound video track.
|
|
154
|
+
* Defaults to 1000ms (1 fps ceiling for Gemini Live), but clamps down to a minimum of 250ms (4 fps)
|
|
155
|
+
* if the negotiated track specifies a higher `Rate`.
|
|
156
|
+
*/
|
|
157
|
+
private getNegotiatedVideoCadenceMs;
|
|
113
158
|
/**
|
|
114
159
|
* Forwards one PUSHED screencast frame to the bound surface's canvas. Called by the session service
|
|
115
160
|
* when a `RemoteBrowserScreencastFrame` arrives on the push-status stream for THIS session. No-op when
|
|
@@ -120,6 +165,17 @@ export declare class RemoteBrowserChannel extends BaseRealtimeChannelClient<Remo
|
|
|
120
165
|
* it, a user navigating during a screencast changed the picture and nothing else: the agent kept
|
|
121
166
|
* describing the page it last opened, and the pixels proving otherwise were right there on screen.
|
|
122
167
|
*
|
|
168
|
+
* Pushes to the video bridge are CHANGE-DRIVEN with a TRAILING-EDGE SETTLE DEBOUNCE:
|
|
169
|
+
* 1. The human user surface renders immediately at full 60fps responsiveness.
|
|
170
|
+
* 2. When the page is static (same image and URL), pushes are deduplicated to avoid burning
|
|
171
|
+
* ~15k tokens/min of identical visual context.
|
|
172
|
+
* 3. A frame is pushed immediately on the leading edge when visual content or URL changes after
|
|
173
|
+
* the negotiated cadence (default 1000ms, clamped to >= 250ms).
|
|
174
|
+
* 4. During rapid bursts (scrolling, typing, clicking) within the cooldown window, subsequent frames
|
|
175
|
+
* are buffered; when the cooldown expires, a trailing timer delivers the settled resting frame
|
|
176
|
+
* so the model never misses the final user state.
|
|
177
|
+
* 5. A 15-second heartbeat ensures visual track liveness even during extended idle periods.
|
|
178
|
+
*
|
|
123
179
|
* @param dataBase64 The frame image as raw base64 JPEG (no `data:` prefix).
|
|
124
180
|
* @param currentUrl The browser's URL when the frame was captured; absent from older servers.
|
|
125
181
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-browser-channel.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/remote-browser/remote-browser-channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAG1C,OAAO,
|
|
1
|
+
{"version":3,"file":"remote-browser-channel.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/remote-browser/remote-browser-channel.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,7 +4,10 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
+
var RemoteBrowserChannel_1;
|
|
7
8
|
import { RegisterClass } from '@memberjunction/global';
|
|
9
|
+
import { CHANNEL_INBOUND_VIDEO_TRACK } from '@memberjunction/ai';
|
|
10
|
+
import { ChannelInboundVideoBridge } from '@memberjunction/ai-realtime-client';
|
|
8
11
|
import { BaseRealtimeChannelClient } from '../channels/base-realtime-channel-client';
|
|
9
12
|
import { RemoteBrowserSurfaceComponent } from './remote-browser-surface.component';
|
|
10
13
|
import { MapToolToAction, REMOTE_BROWSER_TOOL_DEFINITIONS, REMOTE_BROWSER_TOOL_NAMES, REMOTE_BROWSER_TOOL_PREFIX, RemoteBrowserToolArgError, } from './remote-browser-tools';
|
|
@@ -190,8 +193,30 @@ const INTERPRET_PAGE_MUTATION = `
|
|
|
190
193
|
* the server — so {@link SerializeState} / {@link RestoreState} use the base no-op behavior.
|
|
191
194
|
*/
|
|
192
195
|
let RemoteBrowserChannel = class RemoteBrowserChannel extends BaseRealtimeChannelClient {
|
|
196
|
+
static { RemoteBrowserChannel_1 = this; }
|
|
193
197
|
/** The live bound surface, when the channel tab's pane is instantiated. */
|
|
194
198
|
surface = null;
|
|
199
|
+
/** Shared video bridge streaming browser frames to the model when the model supports inbound video. */
|
|
200
|
+
videoBridge = null;
|
|
201
|
+
/**
|
|
202
|
+
* Sourced tracks: Remote Browser can source inbound video to the model when the model supports it.
|
|
203
|
+
*/
|
|
204
|
+
GetSourcedTracks() {
|
|
205
|
+
return [CHANNEL_INBOUND_VIDEO_TRACK];
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Produces the latest browser screenshot as a base64-encoded frame for the video bridge.
|
|
209
|
+
* Under active screencast streaming, returns null to avoid redundant server snapshot round-trips
|
|
210
|
+
* and duplicate frame sourcing into the video bridge.
|
|
211
|
+
*/
|
|
212
|
+
async GetLatestFrame() {
|
|
213
|
+
this.ensureVideoBridge();
|
|
214
|
+
if (this.streaming) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
const snapshot = await this.fetchSnapshot();
|
|
218
|
+
return snapshot?.ScreenshotBase64 ?? null;
|
|
219
|
+
}
|
|
195
220
|
/** Subscription to the bound surface's `HumanInput` output, torn down on unbind/dispose. */
|
|
196
221
|
humanInputSub = null;
|
|
197
222
|
/**
|
|
@@ -274,6 +299,22 @@ let RemoteBrowserChannel = class RemoteBrowserChannel extends BaseRealtimeChanne
|
|
|
274
299
|
IconClass: 'fa-solid fa-globe',
|
|
275
300
|
};
|
|
276
301
|
}
|
|
302
|
+
/** Starts the video bridge streaming browser frames to the model when inbound video is supported. */
|
|
303
|
+
OnInitialize() {
|
|
304
|
+
this.ensureVideoBridge();
|
|
305
|
+
}
|
|
306
|
+
OnSessionStarted() {
|
|
307
|
+
this.ensureVideoBridge();
|
|
308
|
+
}
|
|
309
|
+
ensureVideoBridge() {
|
|
310
|
+
if (!this.videoBridge && this.Context) {
|
|
311
|
+
this.videoBridge = new ChannelInboundVideoBridge(() => this.Context?.Client, this);
|
|
312
|
+
}
|
|
313
|
+
if (this.videoBridge && !this.videoBridge.IsActive && this.Context?.Client?.IsTrackEstablished('video', 'inbound')) {
|
|
314
|
+
this.videoBridge.Start?.();
|
|
315
|
+
}
|
|
316
|
+
return this.videoBridge;
|
|
317
|
+
}
|
|
277
318
|
/**
|
|
278
319
|
* Wires the dynamically-created surface: hands it a snapshot fetcher closing over the
|
|
279
320
|
* channel context (session id + provider live there), so the surface stays transport-
|
|
@@ -298,6 +339,7 @@ let RemoteBrowserChannel = class RemoteBrowserChannel extends BaseRealtimeChanne
|
|
|
298
339
|
// Copy-out: the surface reads the remote selection through this on a local `copy` and writes it locally.
|
|
299
340
|
instance.FetchSelection = () => this.fetchSelection();
|
|
300
341
|
instance.Interactive = true;
|
|
342
|
+
this.ensureVideoBridge();
|
|
301
343
|
this.humanInputSub = instance.HumanInput.subscribe((input) => this.relayHumanInput(input));
|
|
302
344
|
this.audioMutedSub = instance.AudioMutedChange.subscribe((muted) => this.audioPlayer?.SetMuted(muted));
|
|
303
345
|
void this.startScreencast(instance);
|
|
@@ -313,6 +355,10 @@ let RemoteBrowserChannel = class RemoteBrowserChannel extends BaseRealtimeChanne
|
|
|
313
355
|
this.surface = null;
|
|
314
356
|
}
|
|
315
357
|
Dispose() {
|
|
358
|
+
this.clearScreencastTrailingTimer();
|
|
359
|
+
this.pendingScreencastFrame = null;
|
|
360
|
+
this.videoBridge?.Stop();
|
|
361
|
+
this.videoBridge = null;
|
|
316
362
|
this.humanInputSub?.unsubscribe();
|
|
317
363
|
this.humanInputSub = null;
|
|
318
364
|
this.audioMutedSub?.unsubscribe();
|
|
@@ -321,6 +367,57 @@ let RemoteBrowserChannel = class RemoteBrowserChannel extends BaseRealtimeChanne
|
|
|
321
367
|
void this.stopAudioStream();
|
|
322
368
|
super.Dispose();
|
|
323
369
|
}
|
|
370
|
+
/** Timestamp of the last screencast frame pushed to the video bridge (paces pushes to negotiated cadence). */
|
|
371
|
+
lastScreencastPushTime = 0;
|
|
372
|
+
/** Base64 content of the last frame pushed to the video bridge (for change-driven deduplication). */
|
|
373
|
+
lastPushedScreencastFrame = null;
|
|
374
|
+
/** URL associated with the last frame pushed to the video bridge. */
|
|
375
|
+
lastPushedUrl = null;
|
|
376
|
+
/** Minimum allowable screencast push cadence (250ms = 4 fps debounce limit during active bursts). */
|
|
377
|
+
static SCREENCAST_MIN_CADENCE_MS = 250;
|
|
378
|
+
/** Default screencast push cadence when not otherwise negotiated (1000ms = 1 fps ceiling for Gemini Live). */
|
|
379
|
+
static SCREENCAST_DEFAULT_CADENCE_MS = 1000;
|
|
380
|
+
/** Maximum interval (ms) between visual frame pushes on a static page (heartbeat to maintain track liveness). */
|
|
381
|
+
static SCREENCAST_HEARTBEAT_MS = 15_000;
|
|
382
|
+
/** Trailing timer to deliver the settled resting frame after a burst of rapid user activity. */
|
|
383
|
+
screencastTrailingTimer = null;
|
|
384
|
+
/** Pending frame buffered during an active cooldown interval, waiting to settle. */
|
|
385
|
+
pendingScreencastFrame = null;
|
|
386
|
+
/** Cancels any active trailing-edge settle timer and clears the handle. */
|
|
387
|
+
clearScreencastTrailingTimer() {
|
|
388
|
+
if (this.screencastTrailingTimer != null) {
|
|
389
|
+
clearTimeout(this.screencastTrailingTimer);
|
|
390
|
+
this.screencastTrailingTimer = null;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Pushes a frame to the video bridge, recording timestamp, frame content, and URL.
|
|
395
|
+
*/
|
|
396
|
+
pushScreencastFrame(dataBase64, currentUrl) {
|
|
397
|
+
this.lastScreencastPushTime = Date.now();
|
|
398
|
+
this.lastPushedScreencastFrame = dataBase64;
|
|
399
|
+
this.lastPushedUrl = currentUrl ?? null;
|
|
400
|
+
this.ensureVideoBridge()?.PushFrame(dataBase64);
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Resolves the effective push cadence in milliseconds based on the negotiated inbound video track.
|
|
404
|
+
* Defaults to 1000ms (1 fps ceiling for Gemini Live), but clamps down to a minimum of 250ms (4 fps)
|
|
405
|
+
* if the negotiated track specifies a higher `Rate`.
|
|
406
|
+
*/
|
|
407
|
+
getNegotiatedVideoCadenceMs() {
|
|
408
|
+
const client = this.Context?.Client;
|
|
409
|
+
if (!client) {
|
|
410
|
+
return RemoteBrowserChannel_1.SCREENCAST_DEFAULT_CADENCE_MS;
|
|
411
|
+
}
|
|
412
|
+
const tracks = client.EstablishedTracks;
|
|
413
|
+
const videoTrack = tracks.find((t) => t.Descriptor.Modality === 'video' &&
|
|
414
|
+
t.Descriptor.Direction === 'inbound');
|
|
415
|
+
const rate = videoTrack?.Descriptor.Rate;
|
|
416
|
+
if (typeof rate === 'number' && rate > 0) {
|
|
417
|
+
return Math.max(RemoteBrowserChannel_1.SCREENCAST_MIN_CADENCE_MS, Math.floor(1000 / rate));
|
|
418
|
+
}
|
|
419
|
+
return RemoteBrowserChannel_1.SCREENCAST_DEFAULT_CADENCE_MS;
|
|
420
|
+
}
|
|
324
421
|
/**
|
|
325
422
|
* Forwards one PUSHED screencast frame to the bound surface's canvas. Called by the session service
|
|
326
423
|
* when a `RemoteBrowserScreencastFrame` arrives on the push-status stream for THIS session. No-op when
|
|
@@ -331,14 +428,62 @@ let RemoteBrowserChannel = class RemoteBrowserChannel extends BaseRealtimeChanne
|
|
|
331
428
|
* it, a user navigating during a screencast changed the picture and nothing else: the agent kept
|
|
332
429
|
* describing the page it last opened, and the pixels proving otherwise were right there on screen.
|
|
333
430
|
*
|
|
431
|
+
* Pushes to the video bridge are CHANGE-DRIVEN with a TRAILING-EDGE SETTLE DEBOUNCE:
|
|
432
|
+
* 1. The human user surface renders immediately at full 60fps responsiveness.
|
|
433
|
+
* 2. When the page is static (same image and URL), pushes are deduplicated to avoid burning
|
|
434
|
+
* ~15k tokens/min of identical visual context.
|
|
435
|
+
* 3. A frame is pushed immediately on the leading edge when visual content or URL changes after
|
|
436
|
+
* the negotiated cadence (default 1000ms, clamped to >= 250ms).
|
|
437
|
+
* 4. During rapid bursts (scrolling, typing, clicking) within the cooldown window, subsequent frames
|
|
438
|
+
* are buffered; when the cooldown expires, a trailing timer delivers the settled resting frame
|
|
439
|
+
* so the model never misses the final user state.
|
|
440
|
+
* 5. A 15-second heartbeat ensures visual track liveness even during extended idle periods.
|
|
441
|
+
*
|
|
334
442
|
* @param dataBase64 The frame image as raw base64 JPEG (no `data:` prefix).
|
|
335
443
|
* @param currentUrl The browser's URL when the frame was captured; absent from older servers.
|
|
336
444
|
*/
|
|
337
445
|
OnScreencastFrame(dataBase64, currentUrl) {
|
|
338
|
-
if (this.streaming) {
|
|
339
|
-
|
|
340
|
-
|
|
446
|
+
if (!this.streaming) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
this.surface?.RenderFrame(dataBase64);
|
|
450
|
+
const now = Date.now();
|
|
451
|
+
const cadenceMs = this.getNegotiatedVideoCadenceMs();
|
|
452
|
+
const elapsed = now - this.lastScreencastPushTime;
|
|
453
|
+
if (elapsed >= cadenceMs) {
|
|
454
|
+
// Cooldown has expired: leading edge push.
|
|
455
|
+
this.clearScreencastTrailingTimer();
|
|
456
|
+
this.pendingScreencastFrame = null;
|
|
457
|
+
const frameChanged = dataBase64 !== this.lastPushedScreencastFrame;
|
|
458
|
+
const urlChanged = currentUrl != null && currentUrl !== this.lastPushedUrl;
|
|
459
|
+
const heartbeatElapsed = elapsed >= RemoteBrowserChannel_1.SCREENCAST_HEARTBEAT_MS;
|
|
460
|
+
if (frameChanged || urlChanged || heartbeatElapsed) {
|
|
461
|
+
this.pushScreencastFrame(dataBase64, currentUrl);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
// Within cooldown window: buffer latest frame and schedule trailing settle timer.
|
|
466
|
+
this.pendingScreencastFrame = { dataBase64, currentUrl };
|
|
467
|
+
if (!this.screencastTrailingTimer) {
|
|
468
|
+
const delay = Math.max(0, cadenceMs - elapsed);
|
|
469
|
+
this.screencastTrailingTimer = setTimeout(() => {
|
|
470
|
+
this.screencastTrailingTimer = null;
|
|
471
|
+
if (!this.streaming || !this.pendingScreencastFrame) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
const { dataBase64: pendingData, currentUrl: pendingUrl } = this.pendingScreencastFrame;
|
|
475
|
+
this.pendingScreencastFrame = null;
|
|
476
|
+
const frameChanged = pendingData !== this.lastPushedScreencastFrame;
|
|
477
|
+
const urlChanged = pendingUrl != null && pendingUrl !== this.lastPushedUrl;
|
|
478
|
+
const heartbeat = (Date.now() - this.lastScreencastPushTime) >= RemoteBrowserChannel_1.SCREENCAST_HEARTBEAT_MS;
|
|
479
|
+
if (frameChanged || urlChanged || heartbeat) {
|
|
480
|
+
this.pushScreencastFrame(pendingData, pendingUrl);
|
|
481
|
+
}
|
|
482
|
+
}, delay);
|
|
483
|
+
}
|
|
341
484
|
}
|
|
485
|
+
// #3496: URL perception note runs AFTER the push so the visual image lands before/with the note.
|
|
486
|
+
this.notePageChange(currentUrl, 'observed');
|
|
342
487
|
}
|
|
343
488
|
/**
|
|
344
489
|
* Feeds one PUSHED tab-audio chunk to the client-side audio player. Called by the session service when a
|
|
@@ -447,6 +592,11 @@ let RemoteBrowserChannel = class RemoteBrowserChannel extends BaseRealtimeChanne
|
|
|
447
592
|
async stopScreencast() {
|
|
448
593
|
const wasStreaming = this.streaming;
|
|
449
594
|
this.streaming = false;
|
|
595
|
+
this.clearScreencastTrailingTimer();
|
|
596
|
+
this.pendingScreencastFrame = null;
|
|
597
|
+
this.lastPushedScreencastFrame = null;
|
|
598
|
+
this.lastPushedUrl = null;
|
|
599
|
+
this.lastScreencastPushTime = 0;
|
|
450
600
|
const sessionId = this.Context?.AgentSessionID;
|
|
451
601
|
if (!wasStreaming || !sessionId) {
|
|
452
602
|
return;
|
|
@@ -758,7 +908,7 @@ let RemoteBrowserChannel = class RemoteBrowserChannel extends BaseRealtimeChanne
|
|
|
758
908
|
return JSON.stringify(result);
|
|
759
909
|
}
|
|
760
910
|
};
|
|
761
|
-
RemoteBrowserChannel = __decorate([
|
|
911
|
+
RemoteBrowserChannel = RemoteBrowserChannel_1 = __decorate([
|
|
762
912
|
RegisterClass(BaseRealtimeChannelClient, 'RealtimeRemoteBrowserChannel')
|
|
763
913
|
], RemoteBrowserChannel);
|
|
764
914
|
export { RemoteBrowserChannel };
|