@kaltura-sdk/avatars 1.20.0 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- import { UnisphereLoggerFactory, TinyDataStore, TinyPubSub, performanceMonitor } from '@unisphere/core';
1
+ import { UnisphereLoggerFactory, TinyDataStore, TinyPubSub, performanceMonitor, getOrCreateGlobalUnisphere } from '@unisphere/core';
2
2
  import { getPartnerIdFromKs } from '@unisphere/runtime-js';
3
3
  import { DisconnectReasons, getAvatarDefaultCapabilities, resolveActiveContextType, AvatarConnectionStatus } from '@unisphere/genie-types';
4
4
  import { z as z$1 } from 'zod/mini';
@@ -1920,6 +1920,18 @@ PERFORMANCE OF THIS SOFTWARE.
1920
1920
  /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
1921
1921
 
1922
1922
 
1923
+ function __rest(s, e) {
1924
+ var t = {};
1925
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
1926
+ t[p] = s[p];
1927
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
1928
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
1929
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
1930
+ t[p[i]] = s[p[i]];
1931
+ }
1932
+ return t;
1933
+ }
1934
+
1923
1935
  function __awaiter(thisArg, _arguments, P, generator) {
1924
1936
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
1925
1937
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -23828,6 +23840,343 @@ const avatarConnectionMachine = setup({
23828
23840
  }
23829
23841
  });
23830
23842
 
23843
+ const NAMESPACE = 'unisphere-global-tiny-data-store';
23844
+ const REGISTRY_KEY = 'globalTinyDataStore';
23845
+ function getRegistry(logger) {
23846
+ return getOrCreateGlobalUnisphere(REGISTRY_KEY, logger, {
23847
+ windowId: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`,
23848
+ // null prototype: ids come off the wire, `__proto__`/`constructor` must be plain keys
23849
+ entries: Object.create(null),
23850
+ listening: false,
23851
+ lastTs: 0
23852
+ }).value;
23853
+ }
23854
+ // `length`/`frames` are readable cross-origin, so this reaches every frame
23855
+ // regardless of nesting depth or origin; we can't know which one holds an avatar.
23856
+ function collectWindows(root) {
23857
+ const result = [];
23858
+ const visit = w => {
23859
+ result.push(w);
23860
+ let count = 0;
23861
+ try {
23862
+ count = w.length;
23863
+ } catch (_a) {
23864
+ return;
23865
+ }
23866
+ for (let i = 0; i < count; i++) {
23867
+ try {
23868
+ visit(w.frames[i]);
23869
+ } catch (_b) {
23870
+ // frame navigated away or became inaccessible while enumerating
23871
+ }
23872
+ }
23873
+ };
23874
+ visit(root);
23875
+ return result;
23876
+ }
23877
+ // Cache frames seen by this window so a `pagehide` message dispatched by an iframe
23878
+ // just before removal is still recognized even after `contentWindow` detaches from the DOM.
23879
+ const knownFrames = new WeakSet();
23880
+ function getPageWindows(win) {
23881
+ var _a;
23882
+ const current = collectWindows((_a = win.top) !== null && _a !== void 0 ? _a : win);
23883
+ for (const w of current) {
23884
+ knownFrames.add(w);
23885
+ }
23886
+ return current;
23887
+ }
23888
+ function isKnownPageWindow(win, source) {
23889
+ if (getPageWindows(win).includes(source)) {
23890
+ return true;
23891
+ }
23892
+ return knownFrames.has(source);
23893
+ }
23894
+ function post(target, message) {
23895
+ try {
23896
+ // '*' because the host topology (and origins) are unknown. The payload is
23897
+ // non-sensitive and receivers only accept messages from this page's frames.
23898
+ target.postMessage(message, '*');
23899
+ } catch (_a) {
23900
+ // target is gone
23901
+ }
23902
+ }
23903
+ function broadcast(win, message) {
23904
+ for (const target of getPageWindows(win)) {
23905
+ if (target !== win) {
23906
+ post(target, message);
23907
+ }
23908
+ }
23909
+ }
23910
+ function compareRevisions(a, b) {
23911
+ if (a.ts !== b.ts) {
23912
+ return a.ts - b.ts;
23913
+ }
23914
+ return a.origin < b.origin ? -1 : a.origin > b.origin ? 1 : 0;
23915
+ }
23916
+ function isRevision(rev) {
23917
+ return !!rev && typeof rev.ts === 'number' && typeof rev.origin === 'string';
23918
+ }
23919
+ function handleMessage(win, registry, logger, event) {
23920
+ const data = event.data;
23921
+ if (!data || data.ns !== NAMESPACE || typeof data.id !== 'string') {
23922
+ return;
23923
+ }
23924
+ const source = event.source;
23925
+ if (!source || source === win || !isKnownPageWindow(win, source)) {
23926
+ logger.debug('ignored message from a window outside this page', {
23927
+ data: {
23928
+ id: data.id,
23929
+ kind: data.kind
23930
+ }
23931
+ });
23932
+ return;
23933
+ }
23934
+ const entry = registry.entries[data.id];
23935
+ if (data.kind === 'request-sync') {
23936
+ if (entry === null || entry === void 0 ? void 0 : entry.rev) {
23937
+ logger.debug('answering sync request', {
23938
+ data: {
23939
+ id: data.id
23940
+ }
23941
+ });
23942
+ post(source, {
23943
+ ns: NAMESPACE,
23944
+ from: registry.windowId,
23945
+ kind: 'update',
23946
+ id: data.id,
23947
+ value: entry.store.getData(),
23948
+ rev: entry.rev
23949
+ });
23950
+ }
23951
+ return;
23952
+ }
23953
+ if (data.kind !== 'update' || !isRevision(data.rev)) {
23954
+ return;
23955
+ }
23956
+ // Lamport-style: a write made after observing this one must sort after it,
23957
+ // even within the same millisecond.
23958
+ registry.lastTs = Math.max(registry.lastTs, data.rev.ts);
23959
+ if (!entry) {
23960
+ logger.debug('stored update for an id nobody here listens to yet', {
23961
+ data: {
23962
+ id: data.id,
23963
+ rev: data.rev
23964
+ }
23965
+ });
23966
+ registry.entries[data.id] = {
23967
+ store: new TinyDataStore({
23968
+ initialValue: data.value,
23969
+ compare: 'deep'
23970
+ }),
23971
+ rev: data.rev
23972
+ };
23973
+ return;
23974
+ }
23975
+ // Equal/older revisions are what stops echoes between frames.
23976
+ if (entry.rev && compareRevisions(data.rev, entry.rev) <= 0) {
23977
+ logger.debug('dropped stale or duplicate update', {
23978
+ data: {
23979
+ id: data.id,
23980
+ rev: data.rev
23981
+ }
23982
+ });
23983
+ return;
23984
+ }
23985
+ logger.debug('applied update from another frame', {
23986
+ data: {
23987
+ id: data.id,
23988
+ value: data.value,
23989
+ rev: data.rev
23990
+ }
23991
+ });
23992
+ entry.rev = data.rev;
23993
+ entry.store.setData(() => data.value);
23994
+ }
23995
+ // The listener is installed once per window by whichever bundle listens first,
23996
+ // so it keeps that bundle's logger.
23997
+ function ensureListener(win, registry, logger) {
23998
+ if (registry.listening) {
23999
+ return;
24000
+ }
24001
+ registry.listening = true;
24002
+ win.addEventListener('message', event => handleMessage(win, registry, logger, event));
24003
+ }
24004
+ /**
24005
+ * A `TinyDataStore` shared by id between every workspace in this window and,
24006
+ * via `postMessage`, every other frame on the page (parent, siblings, nested).
24007
+ * Last write wins (timestamp, then window id as tie-break).
24008
+ */
24009
+ function listenToGlobalTinyDataStore({
24010
+ id,
24011
+ initialValue,
24012
+ onChanges,
24013
+ logger = avatarClientLoggerFactory.getOrCreateLogger('service', 'global-tiny-data-store')
24014
+ }) {
24015
+ const win = window;
24016
+ const registry = getRegistry(logger);
24017
+ ensureListener(win, registry, logger);
24018
+ let entry = registry.entries[id];
24019
+ const isNew = !entry;
24020
+ if (!entry) {
24021
+ entry = registry.entries[id] = {
24022
+ store: new TinyDataStore({
24023
+ initialValue,
24024
+ compare: 'deep'
24025
+ }),
24026
+ rev: null
24027
+ };
24028
+ }
24029
+ const store = entry.store;
24030
+ const unsubscribe = store.onChanges(onChanges, {
24031
+ replayLastValue: false
24032
+ });
24033
+ logger.debug('listening to global store', {
24034
+ data: {
24035
+ id,
24036
+ isNew
24037
+ }
24038
+ });
24039
+ if (isNew) {
24040
+ broadcast(win, {
24041
+ ns: NAMESPACE,
24042
+ from: registry.windowId,
24043
+ kind: 'request-sync',
24044
+ id
24045
+ });
24046
+ }
24047
+ return {
24048
+ unsubscribe,
24049
+ getData: () => store.getData(),
24050
+ setData: value => {
24051
+ const rev = {
24052
+ ts: registry.lastTs = Math.max(Date.now(), registry.lastTs + 1),
24053
+ origin: registry.windowId
24054
+ };
24055
+ logger.debug('updating global store', {
24056
+ data: {
24057
+ id,
24058
+ value,
24059
+ rev
24060
+ }
24061
+ });
24062
+ entry.rev = rev;
24063
+ store.setData(() => value);
24064
+ broadcast(win, {
24065
+ ns: NAMESPACE,
24066
+ from: registry.windowId,
24067
+ kind: 'update',
24068
+ id,
24069
+ value,
24070
+ rev
24071
+ });
24072
+ }
24073
+ };
24074
+ }
24075
+
24076
+ const ACTIVE_SESSION_STORE_ID = 'agenticExperienceAvatar';
24077
+ function createId() {
24078
+ return typeof crypto !== 'undefined' && crypto.randomUUID ? crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
24079
+ }
24080
+ /**
24081
+ * Page-wide "who owns the call" record for one avatar manager. Takeover is
24082
+ * just overwriting the shared record: the previous owner sees an id that is
24083
+ * no longer its own and ends its own call (a close callback could not cross
24084
+ * frames).
24085
+ */
24086
+ class ActiveSessionCoordinator {
24087
+ constructor(_options) {
24088
+ this._options = _options;
24089
+ this.id = createId();
24090
+ this._owns = false;
24091
+ this._conflict = new TinyDataStore({
24092
+ initialValue: null,
24093
+ compare: 'deep'
24094
+ });
24095
+ this._win = window;
24096
+ this._onPageHide = () => this.release();
24097
+ const _a = listenToGlobalTinyDataStore({
24098
+ id: ACTIVE_SESSION_STORE_ID,
24099
+ initialValue: null,
24100
+ onChanges: value => this._handle(value),
24101
+ logger: _options.logger
24102
+ }),
24103
+ {
24104
+ unsubscribe
24105
+ } = _a,
24106
+ global = __rest(_a, ["unsubscribe"]);
24107
+ this._logger = _options.logger;
24108
+ this._global = global;
24109
+ this._unsubscribe = unsubscribe;
24110
+ this._handle(global.getData());
24111
+ // A closed iframe must not leave the page locked.
24112
+ this._win.addEventListener('pagehide', this._onPageHide);
24113
+ }
24114
+ /** The active session owned by another avatar, or null */
24115
+ get conflict() {
24116
+ return this._conflict.consumer;
24117
+ }
24118
+ claim(preventDisconnections) {
24119
+ var _a, _b;
24120
+ if (this._owns && ((_a = this._global.getData()) === null || _a === void 0 ? void 0 : _a.preventDisconnections) === preventDisconnections) {
24121
+ return;
24122
+ }
24123
+ this._owns = true;
24124
+ (_b = this._logger) === null || _b === void 0 ? void 0 : _b.log('claiming the active avatar session', {
24125
+ data: {
24126
+ id: this.id,
24127
+ preventDisconnections
24128
+ }
24129
+ });
24130
+ this._global.setData({
24131
+ id: this.id,
24132
+ preventDisconnections
24133
+ });
24134
+ }
24135
+ /** Republishes the flag only while this avatar owns the call; never claims an idle one */
24136
+ updateFlag(preventDisconnections) {
24137
+ var _a, _b;
24138
+ if (this._owns && ((_a = this._global.getData()) === null || _a === void 0 ? void 0 : _a.id) === this.id && ((_b = this._global.getData()) === null || _b === void 0 ? void 0 : _b.preventDisconnections) !== preventDisconnections) {
24139
+ this._global.setData({
24140
+ id: this.id,
24141
+ preventDisconnections
24142
+ });
24143
+ }
24144
+ }
24145
+ /** Clears the record only if it is still ours, so a taken-over avatar can't wipe the new owner */
24146
+ release() {
24147
+ var _a, _b;
24148
+ this._owns = false;
24149
+ if (((_a = this._global.getData()) === null || _a === void 0 ? void 0 : _a.id) === this.id) {
24150
+ (_b = this._logger) === null || _b === void 0 ? void 0 : _b.log('releasing the active avatar session', {
24151
+ data: {
24152
+ id: this.id
24153
+ }
24154
+ });
24155
+ this._global.setData(null);
24156
+ }
24157
+ }
24158
+ dispose() {
24159
+ this.release();
24160
+ this._unsubscribe();
24161
+ this._win.removeEventListener('pagehide', this._onPageHide);
24162
+ }
24163
+ _handle(value) {
24164
+ var _a;
24165
+ const other = value && value.id !== this.id ? value : null;
24166
+ this._conflict.setData(() => other);
24167
+ if (other && this._owns) {
24168
+ this._owns = false;
24169
+ (_a = this._logger) === null || _a === void 0 ? void 0 : _a.log('active avatar session taken over by another avatar', {
24170
+ data: {
24171
+ id: this.id,
24172
+ by: other.id
24173
+ }
24174
+ });
24175
+ this._options.onTakenOver();
24176
+ }
24177
+ }
24178
+ }
24179
+
23831
24180
  function setIfChanged(store, next, eq = (a, b) => a === b) {
23832
24181
  if (!eq(store.getData(), next)) {
23833
24182
  store.setData(() => next);
@@ -24108,7 +24457,20 @@ class AvatarClientManager extends ActorManager {
24108
24457
  this._contextSelectorVisible = new TinyDataStore({
24109
24458
  initialValue: true
24110
24459
  });
24460
+ this._multipleAvatarBehavior = new TinyDataStore({
24461
+ initialValue: {
24462
+ preventDisconnections: false
24463
+ },
24464
+ compare: 'deep'
24465
+ });
24466
+ this._isTakeoverPending = new TinyDataStore({
24467
+ initialValue: false
24468
+ });
24111
24469
  this._logger = (_a = this._config.logger) !== null && _a !== void 0 ? _a : avatarClientLoggerFactory.getOrCreateLogger('service', 'avatar-client');
24470
+ this._activeSession = new ActiveSessionCoordinator({
24471
+ onTakenOver: () => this.disconnect(),
24472
+ logger: this._logger
24473
+ });
24112
24474
  if (this._config.runtimeSettings.features) {
24113
24475
  this._features.setData(() => this._config.runtimeSettings.features);
24114
24476
  }
@@ -24121,6 +24483,19 @@ class AvatarClientManager extends ActorManager {
24121
24483
  return this._config.runtimeSettings.serverConfig === 'manual' ? (_a = this._config.runtimeSettings.loadingVideoURL) !== null && _a !== void 0 ? _a : null : null;
24122
24484
  });
24123
24485
  this._setupKalturaContext();
24486
+ this._multipleAvatarBehavior.setData(() => {
24487
+ var _a, _b;
24488
+ return {
24489
+ preventDisconnections: (_b = (_a = this._config.runtimeSettings.multipleAvatarBehavior) === null || _a === void 0 ? void 0 : _a.preventDisconnections) !== null && _b !== void 0 ? _b : false
24490
+ };
24491
+ });
24492
+ this._status.onChanges(status => {
24493
+ if (status === AvatarConnectionStatus.NotConnected || status === AvatarConnectionStatus.Disconnected || status === AvatarConnectionStatus.Error) {
24494
+ this._activeSession.release();
24495
+ }
24496
+ }, {
24497
+ replayLastValue: false
24498
+ });
24124
24499
  }
24125
24500
  // ────────────────────────────────────────────────────────────────────────
24126
24501
  // Private: connection engine
@@ -24327,6 +24702,18 @@ class AvatarClientManager extends ActorManager {
24327
24702
  this._logger.log(`Avatar client already connected`);
24328
24703
  return;
24329
24704
  }
24705
+ const conflict = this._activeSession.conflict.getData();
24706
+ if (conflict) {
24707
+ this._logger.log('Connect pending: another avatar is in a call', {
24708
+ data: {
24709
+ preventDisconnections: conflict.preventDisconnections
24710
+ }
24711
+ });
24712
+ this._pendingTakeoverOptions = options;
24713
+ this._isTakeoverPending.setData(() => true);
24714
+ return;
24715
+ }
24716
+ this._activeSession.claim(this._multipleAvatarBehavior.getData().preventDisconnections);
24330
24717
  const connectionId = 'main';
24331
24718
  this._logger.log(`Connecting to avatar client with connection ID: ${connectionId}`);
24332
24719
  timingTracker.runtimeConnectTime = performance.now();
@@ -24382,6 +24769,22 @@ class AvatarClientManager extends ActorManager {
24382
24769
  reconnect() {
24383
24770
  this.connect(this._lastConnectOptions);
24384
24771
  }
24772
+ confirmTakeover() {
24773
+ var _a;
24774
+ if (!this._isTakeoverPending.getData() || ((_a = this._activeSession.conflict.getData()) === null || _a === void 0 ? void 0 : _a.preventDisconnections)) {
24775
+ return;
24776
+ }
24777
+ const options = this._pendingTakeoverOptions;
24778
+ this._pendingTakeoverOptions = undefined;
24779
+ this._isTakeoverPending.setData(() => false);
24780
+ // Overwriting the page-wide record makes the other avatar end its own call.
24781
+ this._activeSession.claim(this._multipleAvatarBehavior.getData().preventDisconnections);
24782
+ this.connect(options);
24783
+ }
24784
+ cancelTakeover() {
24785
+ this._pendingTakeoverOptions = undefined;
24786
+ this._isTakeoverPending.setData(() => false);
24787
+ }
24385
24788
  // ────────────────────────────────────────────────────────────────────────
24386
24789
  // Public API: pause / resume
24387
24790
  // ────────────────────────────────────────────────────────────────────────
@@ -24584,6 +24987,23 @@ class AvatarClientManager extends ActorManager {
24584
24987
  setIsPreviewMode(value) {
24585
24988
  this._isPreviewMode.setData(() => value);
24586
24989
  }
24990
+ get multipleAvatarBehavior() {
24991
+ return this._multipleAvatarBehavior.consumer;
24992
+ }
24993
+ setMultipleAvatarBehavior(value) {
24994
+ var _a;
24995
+ const preventDisconnections = (_a = value === null || value === void 0 ? void 0 : value.preventDisconnections) !== null && _a !== void 0 ? _a : false;
24996
+ this._multipleAvatarBehavior.setData(() => ({
24997
+ preventDisconnections
24998
+ }));
24999
+ this._activeSession.updateFlag(preventDisconnections);
25000
+ }
25001
+ get activeSessionConflict() {
25002
+ return this._activeSession.conflict;
25003
+ }
25004
+ get isTakeoverPending() {
25005
+ return this._isTakeoverPending.consumer;
25006
+ }
24587
25007
  get contextSelectorVisible() {
24588
25008
  return this._contextSelectorVisible.consumer;
24589
25009
  }
@@ -24661,8 +25081,9 @@ class AvatarClientManager extends ActorManager {
24661
25081
  var _a;
24662
25082
  (_a = this._unsubscribeContextActive) === null || _a === void 0 ? void 0 : _a.call(this);
24663
25083
  this._unsubscribeContextActive = null;
25084
+ this._activeSession.dispose();
24664
25085
  this._cleanupConnection();
24665
25086
  }
24666
25087
  }
24667
25088
 
24668
- export { ApplicationFeatures, AvatarClientManager, AvatarConnectionMachineEventTypes, AvatarConnectionMachineId, AvatarNotificationCode, ButtonNames, PageNames, agentRawTextClientToolDeltaSchema, agentRawTextDeltaSchema, agentRawTextSchema, agentTurnToTalkSchema, askPermissionsSchema, avatarClientLoggerFactory, avatarConnectionMachine, avatarRTCClientManager, clientConfigurationSchema, conversationEndedSchema, conversationTimeWarningSchema, createTinyPubsubEventsHandler, createTinyPubsubEventsSource, generatingSpeechSchema, getAvatarDisconnectNotificationVariant, getQueryParam, initializeLogsCollectionIfNeeded, isMappedAvatarDisconnectReason, isMobileDevice, joinCompleteSchema, noAgentSchema, onServerConnectedSchema, pauseExpiryWarningSchema, resetWebsocketClientTeardownState, showAgentSchema, showStartSpeechSchema, stvFinishedTalkingSchema, stvNewSessionSchema, stvSpeechChunkSchema, stvStartedTalkingSchema, timingTracker, unsupportedClientSchema, websocketClientManager, websocketDebugInfo, websocketMessage, websocketNonRecoveredReconnect, websocketRecoveryState, websocketSystemMessage };
25089
+ export { ApplicationFeatures, AvatarClientManager, AvatarConnectionMachineEventTypes, AvatarConnectionMachineId, AvatarNotificationCode, ButtonNames, PageNames, agentRawTextClientToolDeltaSchema, agentRawTextDeltaSchema, agentRawTextSchema, agentTurnToTalkSchema, askPermissionsSchema, avatarClientLoggerFactory, avatarConnectionMachine, avatarRTCClientManager, clientConfigurationSchema, conversationEndedSchema, conversationTimeWarningSchema, createTinyPubsubEventsHandler, createTinyPubsubEventsSource, generatingSpeechSchema, getAvatarDisconnectNotificationVariant, getQueryParam, initializeLogsCollectionIfNeeded, isMappedAvatarDisconnectReason, isMobileDevice, joinCompleteSchema, listenToGlobalTinyDataStore, noAgentSchema, onServerConnectedSchema, pauseExpiryWarningSchema, resetWebsocketClientTeardownState, showAgentSchema, showStartSpeechSchema, stvFinishedTalkingSchema, stvNewSessionSchema, stvSpeechChunkSchema, stvStartedTalkingSchema, timingTracker, unsupportedClientSchema, websocketClientManager, websocketDebugInfo, websocketMessage, websocketNonRecoveredReconnect, websocketRecoveryState, websocketSystemMessage };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaltura-sdk/avatars",
3
- "version": "1.20.0",
3
+ "version": "1.21.0",
4
4
  "author": "kaltura",
5
5
  "license": "AGPL-3.0",
6
6
  "repository": {
@@ -13,13 +13,14 @@
13
13
  "@kaltura-sdk/rtc-core": "^1.26.2",
14
14
  "@local/genie-shared-utils": "^1.0.0",
15
15
  "@unisphere/core": "^1.99.0",
16
- "@unisphere/genie-types": "^1.46.0",
16
+ "@unisphere/genie-types": "^1.47.0",
17
17
  "@unisphere/notifications-core": "^1.25.0",
18
18
  "@unisphere/runtime": "^1.98.0",
19
19
  "@unisphere/runtime-js": "^1.98.0",
20
20
  "immer": "^10.1.1",
21
21
  "js-yaml": "^4.1.0",
22
22
  "socket.io-client": "^4.8.1",
23
+ "vitest": "^3.2.7",
23
24
  "zod": "^4.2.0"
24
25
  },
25
26
  "module": "./index.esm.js",
package/src/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './lib/avatar-client-manager';
2
2
  export * from './lib/server';
3
+ export * from './lib/global-tiny-data-store';
3
4
  export * from './lib/timing-tracker';
4
5
  export * from './lib/analytics';
5
6
  export * from './lib/utils';
@@ -1,6 +1,6 @@
1
1
  import { UnisphereLoggerType } from '@unisphere/core';
2
2
  import { KalturaAnalyticsServiceType, PubSubServiceType } from '@unisphere/runtime';
3
- import { AgentData, AgentKalturaContextData, AgentProgressBar, AgentScreenShareInfo, AgentTapToTalkInfo, AvatarClientManagerType, AvatarConnectionProxInternalType, AvatarConnectionProxyType, AvatarConnectionStatus, AvatarContentData, AvatarRuntimeSettings, CaptionConfig, Captions, ChatEntry, ClientToolCallLogEntry, DisconnectReasons, TapToTalkStartOptions } from '@unisphere/genie-types';
3
+ import { AgentData, AgentKalturaContextData, AgentProgressBar, AgentScreenShareInfo, AgentTapToTalkInfo, AvatarClientManagerType, AvatarConnectionProxInternalType, AvatarConnectionProxyType, AvatarConnectionStatus, AvatarContentData, AvatarRuntimeSettings, CaptionConfig, Captions, ChatEntry, ClientToolCallLogEntry, DisconnectReasons, MultipleAvatarBehavior, TapToTalkStartOptions } from '@unisphere/genie-types';
4
4
  import { type AvatarClientToolsInput, type AvatarConnectionActor } from './machines/avatar-connection';
5
5
  import { AvatarClientManagerActions } from './types';
6
6
  import { DeviceType } from '@kaltura-sdk/rtc-core';
@@ -58,6 +58,10 @@ export declare class AvatarClientManager extends ActorManager<AvatarConnectionAc
58
58
  private _features;
59
59
  private readonly _isPreviewMode;
60
60
  private readonly _contextSelectorVisible;
61
+ private readonly _multipleAvatarBehavior;
62
+ private readonly _isTakeoverPending;
63
+ private _pendingTakeoverOptions;
64
+ private readonly _activeSession;
61
65
  constructor(_config: {
62
66
  actions?: AvatarClientManagerActions;
63
67
  debugMode?: boolean;
@@ -113,6 +117,8 @@ export declare class AvatarClientManager extends ActorManager<AvatarConnectionAc
113
117
  }): void;
114
118
  disconnect(): void;
115
119
  reconnect(): void;
120
+ confirmTakeover(): void;
121
+ cancelTakeover(): void;
116
122
  get isPaused(): import("@unisphere/core").TinyDataStoreConsumer<boolean>;
117
123
  pause(): void;
118
124
  resume(): void;
@@ -149,6 +155,10 @@ export declare class AvatarClientManager extends ActorManager<AvatarConnectionAc
149
155
  setExternalThreadId(id: string | null): void;
150
156
  get isPreviewMode(): import("@unisphere/core").TinyDataStoreConsumer<boolean>;
151
157
  setIsPreviewMode(value: boolean): void;
158
+ get multipleAvatarBehavior(): import("@unisphere/core").TinyDataStoreConsumer<MultipleAvatarBehavior>;
159
+ setMultipleAvatarBehavior(value: MultipleAvatarBehavior | undefined): void;
160
+ get activeSessionConflict(): import("@unisphere/core").TinyDataStoreConsumer<import("@unisphere/genie-types").ActiveAvatarSession | null>;
161
+ get isTakeoverPending(): import("@unisphere/core").TinyDataStoreConsumer<boolean>;
152
162
  get contextSelectorVisible(): import("@unisphere/core").TinyDataStoreConsumer<boolean>;
153
163
  setContextSelectorVisible(value: boolean): void;
154
164
  get features(): import("@unisphere/core").TinyDataStoreConsumer<{
@@ -0,0 +1,33 @@
1
+ import { UnisphereLoggerType } from '@unisphere/core';
2
+ import type { ActiveAvatarSession } from '@unisphere/genie-types';
3
+ export declare const ACTIVE_SESSION_STORE_ID = "agenticExperienceAvatar";
4
+ /**
5
+ * Page-wide "who owns the call" record for one avatar manager. Takeover is
6
+ * just overwriting the shared record: the previous owner sees an id that is
7
+ * no longer its own and ends its own call (a close callback could not cross
8
+ * frames).
9
+ */
10
+ export declare class ActiveSessionCoordinator {
11
+ private readonly _options;
12
+ readonly id: string;
13
+ private _owns;
14
+ private readonly _conflict;
15
+ private readonly _global;
16
+ private readonly _unsubscribe;
17
+ private readonly _win;
18
+ private readonly _onPageHide;
19
+ private readonly _logger?;
20
+ constructor(_options: {
21
+ onTakenOver: () => void;
22
+ logger?: UnisphereLoggerType;
23
+ });
24
+ /** The active session owned by another avatar, or null */
25
+ get conflict(): import("@unisphere/core").TinyDataStoreConsumer<ActiveAvatarSession | null>;
26
+ claim(preventDisconnections: boolean): void;
27
+ /** Republishes the flag only while this avatar owns the call; never claims an idle one */
28
+ updateFlag(preventDisconnections: boolean): void;
29
+ /** Clears the record only if it is still ours, so a taken-over avatar can't wipe the new owner */
30
+ release(): void;
31
+ dispose(): void;
32
+ private _handle;
33
+ }
@@ -0,0 +1 @@
1
+ export * from './listen-to-global-tiny-data-store';
@@ -0,0 +1,16 @@
1
+ import { UnisphereLoggerType } from '@unisphere/core';
2
+ /**
3
+ * A `TinyDataStore` shared by id between every workspace in this window and,
4
+ * via `postMessage`, every other frame on the page (parent, siblings, nested).
5
+ * Last write wins (timestamp, then window id as tie-break).
6
+ */
7
+ export declare function listenToGlobalTinyDataStore<T>({ id, initialValue, onChanges, logger, }: {
8
+ id: string;
9
+ initialValue: T;
10
+ onChanges: (value: T) => void;
11
+ logger?: UnisphereLoggerType;
12
+ }): {
13
+ unsubscribe: () => void;
14
+ getData: () => T;
15
+ setData: (value: T) => void;
16
+ };
@@ -0,0 +1,17 @@
1
+ import type { ActiveSessionCoordinator } from '../avatar-client-manager/utils/active-session-coordinator';
2
+ import type { listenToGlobalTinyDataStore } from '../global-tiny-data-store';
3
+ type CoordinatorOptions = ConstructorParameters<typeof ActiveSessionCoordinator>[0];
4
+ export declare function getRealTopWindow(): Window;
5
+ export declare function addFrame(parent: Window): Window;
6
+ export declare function installBrowserLikePostMessage(windows: Window[]): void;
7
+ export declare function createFrameApi(win: Window): Promise<{
8
+ listen: (options: Parameters<typeof listenToGlobalTinyDataStore>[0]) => {
9
+ unsubscribe: () => void;
10
+ getData: () => unknown;
11
+ setData: (value: unknown) => void;
12
+ };
13
+ createCoordinator: (options: CoordinatorOptions) => ActiveSessionCoordinator;
14
+ cleanup: () => void;
15
+ }>;
16
+ export declare const flush: () => Promise<unknown>;
17
+ export {};