@principal-ai/control-tower-core 0.1.12 → 0.1.14

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.
@@ -9,17 +9,40 @@
9
9
  */
10
10
  import { PresenceManager } from './PresenceManager.js';
11
11
  import type { UserPresence, DeviceInfo, PresenceStatus, PresenceConfig, PresenceChangeEvent, ActivityUpdate } from '../types/presence.js';
12
- export declare class DefaultPresenceManager extends PresenceManager {
12
+ import type { PresenceExtension, ExtendedUserPresence } from './PresenceExtension.js';
13
+ import type { BaseServer } from '../server/BaseServer.js';
14
+ export declare class DefaultPresenceManager<TExtendedData = Record<string, unknown>> extends PresenceManager {
13
15
  private userPresences;
14
16
  private deviceToUser;
15
17
  private gracePeriodEntries;
16
- constructor(config?: PresenceConfig);
18
+ private extensions;
19
+ private server?;
20
+ constructor(config?: PresenceConfig & {
21
+ extensions?: PresenceExtension<TExtendedData>[];
22
+ });
23
+ /**
24
+ * Set server reference for auto-broadcasting
25
+ *
26
+ * This is called automatically by ServerBuilder.
27
+ * @internal
28
+ */
29
+ setServer(server: BaseServer): void;
30
+ /**
31
+ * Initialize all extensions
32
+ * @internal
33
+ */
34
+ initializeExtensions(): Promise<void>;
35
+ /**
36
+ * Destroy all extensions
37
+ * @internal
38
+ */
39
+ destroyExtensions(): Promise<void>;
17
40
  connectDevice(userId: string, deviceId: string, deviceInfo?: Partial<DeviceInfo>): Promise<UserPresence>;
18
41
  disconnectDevice(userId: string, deviceId: string): Promise<UserPresence | null>;
19
42
  updateActivity(update: ActivityUpdate): Promise<UserPresence>;
20
- getUserPresence(userId: string): Promise<UserPresence | null>;
43
+ getUserPresence(userId: string): Promise<ExtendedUserPresence<TExtendedData> | null>;
21
44
  getUsersPresence(userIds: string[]): Promise<Map<string, UserPresence>>;
22
- getOnlineUsers(): Promise<UserPresence[]>;
45
+ getOnlineUsers(): Promise<ExtendedUserPresence<TExtendedData>[]>;
23
46
  getUserDevices(userId: string): Promise<DeviceInfo[]>;
24
47
  setUserStatus(userId: string, status: PresenceStatus): Promise<UserPresence>;
25
48
  processHeartbeats(): Promise<PresenceChangeEvent[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultPresenceManager.d.ts","sourceRoot":"","sources":["../../src/abstractions/DefaultPresenceManager.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EACV,YAAY,EACZ,UAAU,EACV,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,cAAc,EACf,MAAM,sBAAsB,CAAC;AAW9B,qBAAa,sBAAuB,SAAQ,eAAe;IACzD,OAAO,CAAC,aAAa,CAAmC;IACxD,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,kBAAkB,CAAuC;gBAErD,MAAM,CAAC,EAAE,cAAc;IAI7B,aAAa,CACjB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,GAC/B,OAAO,CAAC,YAAY,CAAC;IA+DlB,gBAAgB,CACpB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IA6CzB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC;IA4B7D,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAI7D,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAavE,cAAc,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAYzC,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IASrD,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC;IAU5E,iBAAiB,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;IA4EnD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5B;;OAEG;IACH,mBAAmB,IAAI,MAAM;IAI7B;;OAEG;IACH,uBAAuB,IAAI,MAAM;IAIjC;;OAEG;IACH,kBAAkB,IAAI,MAAM;CAG7B"}
1
+ {"version":3,"file":"DefaultPresenceManager.d.ts","sourceRoot":"","sources":["../../src/abstractions/DefaultPresenceManager.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EACV,YAAY,EACZ,UAAU,EACV,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,cAAc,EACf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACtF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAW1D,qBAAa,sBAAsB,CAAC,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,eAAe;IAClG,OAAO,CAAC,aAAa,CAAmC;IACxD,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,kBAAkB,CAAuC;IACjE,OAAO,CAAC,UAAU,CAA0C;IAC5D,OAAO,CAAC,MAAM,CAAC,CAAa;gBAEhB,MAAM,CAAC,EAAE,cAAc,GAAG;QACpC,UAAU,CAAC,EAAE,iBAAiB,CAAC,aAAa,CAAC,EAAE,CAAC;KACjD;IAKD;;;;;OAKG;IACH,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAInC;;;OAGG;IACG,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3C;;;OAGG;IACG,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAMlC,aAAa,CACjB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,GAC/B,OAAO,CAAC,YAAY,CAAC;IAmHlB,gBAAgB,CACpB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAuEzB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC;IAiC7D,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC;IAyBpF,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAavE,cAAc,IAAI,OAAO,CAAC,oBAAoB,CAAC,aAAa,CAAC,EAAE,CAAC;IA4BhE,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IASrD,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC;IAU5E,iBAAiB,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;IA4EnD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAS5B;;OAEG;IACH,mBAAmB,IAAI,MAAM;IAI7B;;OAEG;IACH,uBAAuB,IAAI,MAAM;IAIjC;;OAEG;IACH,kBAAkB,IAAI,MAAM;CAG7B"}
@@ -17,9 +17,39 @@ class DefaultPresenceManager extends PresenceManager_js_1.PresenceManager {
17
17
  this.userPresences = new Map();
18
18
  this.deviceToUser = new Map();
19
19
  this.gracePeriodEntries = new Map();
20
+ this.extensions = [];
21
+ this.extensions = config?.extensions || [];
22
+ }
23
+ /**
24
+ * Set server reference for auto-broadcasting
25
+ *
26
+ * This is called automatically by ServerBuilder.
27
+ * @internal
28
+ */
29
+ setServer(server) {
30
+ this.server = server;
31
+ }
32
+ /**
33
+ * Initialize all extensions
34
+ * @internal
35
+ */
36
+ async initializeExtensions() {
37
+ for (const ext of this.extensions) {
38
+ await ext.initialize?.();
39
+ }
40
+ }
41
+ /**
42
+ * Destroy all extensions
43
+ * @internal
44
+ */
45
+ async destroyExtensions() {
46
+ for (const ext of this.extensions) {
47
+ await ext.destroy?.();
48
+ }
20
49
  }
21
50
  async connectDevice(userId, deviceId, deviceInfo) {
22
51
  const now = Date.now();
52
+ const isFirstDevice = !this.userPresences.has(userId) && !this.gracePeriodEntries.has(userId);
23
53
  // Check if user is in grace period (reconnecting)
24
54
  const gracePeriodEntry = this.gracePeriodEntries.get(userId);
25
55
  if (gracePeriodEntry) {
@@ -39,10 +69,35 @@ class DefaultPresenceManager extends PresenceManager_js_1.PresenceManager {
39
69
  presence.lastActivity = now;
40
70
  this.userPresences.set(userId, presence);
41
71
  this.deviceToUser.set(deviceId, userId);
72
+ // Call extension hooks
73
+ if (isFirstDevice) {
74
+ for (const ext of this.extensions) {
75
+ await ext.onUserOnline?.(userId, deviceId, deviceInfo?.metadata || {});
76
+ }
77
+ }
78
+ else {
79
+ for (const ext of this.extensions) {
80
+ await ext.onDeviceConnected?.(userId, deviceId, deviceInfo?.metadata || {});
81
+ }
82
+ }
83
+ // Auto-broadcast
84
+ if (this.config.broadcastPresenceUpdates && this.server) {
85
+ const experimental = this.server.experimental;
86
+ await experimental?.broadcastAuthenticated({
87
+ type: 'presence:user_online',
88
+ payload: {
89
+ userId,
90
+ deviceId,
91
+ status: 'online',
92
+ timestamp: now
93
+ }
94
+ });
95
+ }
42
96
  return presence;
43
97
  }
44
98
  // Get or create user presence
45
99
  let presence = this.userPresences.get(userId);
100
+ const wasOnline = presence !== undefined;
46
101
  if (!presence) {
47
102
  // New user
48
103
  presence = {
@@ -67,6 +122,30 @@ class DefaultPresenceManager extends PresenceManager_js_1.PresenceManager {
67
122
  presence.status = this.calculatePresenceStatus(presence.devices, now);
68
123
  presence.lastActivity = now;
69
124
  this.deviceToUser.set(deviceId, userId);
125
+ // Call extension hooks
126
+ if (!wasOnline) {
127
+ for (const ext of this.extensions) {
128
+ await ext.onUserOnline?.(userId, deviceId, deviceInfo?.metadata || {});
129
+ }
130
+ }
131
+ else {
132
+ for (const ext of this.extensions) {
133
+ await ext.onDeviceConnected?.(userId, deviceId, deviceInfo?.metadata || {});
134
+ }
135
+ }
136
+ // Auto-broadcast if user came online
137
+ if (!wasOnline && this.config.broadcastPresenceUpdates && this.server) {
138
+ const experimental = this.server.experimental;
139
+ await experimental?.broadcastAuthenticated({
140
+ type: 'presence:user_online',
141
+ payload: {
142
+ userId,
143
+ deviceId,
144
+ status: 'online',
145
+ timestamp: now
146
+ }
147
+ });
148
+ }
70
149
  return presence;
71
150
  }
72
151
  async disconnectDevice(userId, deviceId) {
@@ -78,8 +157,27 @@ class DefaultPresenceManager extends PresenceManager_js_1.PresenceManager {
78
157
  presence.devices.delete(deviceId);
79
158
  this.deviceToUser.delete(deviceId);
80
159
  const now = Date.now();
160
+ const isLastDevice = presence.devices.size === 0;
81
161
  // If no devices left, start grace period
82
- if (presence.devices.size === 0) {
162
+ if (isLastDevice) {
163
+ // Call extension hooks for user going offline
164
+ for (const ext of this.extensions) {
165
+ await ext.onUserOffline?.(userId, deviceId);
166
+ }
167
+ // Auto-broadcast user offline
168
+ if (this.config.broadcastPresenceUpdates && this.server) {
169
+ const experimental = this.server.experimental;
170
+ await experimental?.broadcastAuthenticated({
171
+ type: 'presence:user_offline',
172
+ payload: {
173
+ userId,
174
+ deviceId,
175
+ status: 'offline',
176
+ timestamp: now,
177
+ gracePeriod: this.config.gracePeriod
178
+ }
179
+ });
180
+ }
83
181
  if (this.config.gracePeriod > 0) {
84
182
  // Move to grace period
85
183
  this.gracePeriodEntries.set(userId, {
@@ -96,6 +194,10 @@ class DefaultPresenceManager extends PresenceManager_js_1.PresenceManager {
96
194
  return null;
97
195
  }
98
196
  }
197
+ // Still has devices - call device disconnected hook
198
+ for (const ext of this.extensions) {
199
+ await ext.onDeviceDisconnected?.(userId, deviceId);
200
+ }
99
201
  // Still has devices, update status
100
202
  presence.status = this.calculatePresenceStatus(presence.devices, now);
101
203
  // Update lastActivity to most recent device activity
@@ -109,7 +211,7 @@ class DefaultPresenceManager extends PresenceManager_js_1.PresenceManager {
109
211
  return presence;
110
212
  }
111
213
  async updateActivity(update) {
112
- const { userId, deviceId, timestamp } = update;
214
+ const { userId, deviceId, timestamp, activityType } = update;
113
215
  const presence = this.userPresences.get(userId);
114
216
  if (!presence) {
115
217
  throw new Error(`User ${userId} not found in presence system`);
@@ -127,10 +229,32 @@ class DefaultPresenceManager extends PresenceManager_js_1.PresenceManager {
127
229
  // Recalculate status
128
230
  const previousStatus = presence.status;
129
231
  presence.status = this.calculatePresenceStatus(presence.devices, timestamp);
232
+ // Call extension hooks
233
+ for (const ext of this.extensions) {
234
+ await ext.onActivity?.(userId, deviceId, activityType || 'heartbeat');
235
+ }
130
236
  return presence;
131
237
  }
132
238
  async getUserPresence(userId) {
133
- return this.userPresences.get(userId) || null;
239
+ const basePresence = this.userPresences.get(userId);
240
+ if (!basePresence) {
241
+ return null;
242
+ }
243
+ // Merge extended data from all extensions
244
+ if (this.extensions.length > 0) {
245
+ const extendedData = {};
246
+ for (const ext of this.extensions) {
247
+ const data = await ext.getExtendedData?.(userId);
248
+ if (data) {
249
+ Object.assign(extendedData, data);
250
+ }
251
+ }
252
+ return {
253
+ ...basePresence,
254
+ extended: extendedData
255
+ };
256
+ }
257
+ return basePresence;
134
258
  }
135
259
  async getUsersPresence(userIds) {
136
260
  const result = new Map();
@@ -146,7 +270,22 @@ class DefaultPresenceManager extends PresenceManager_js_1.PresenceManager {
146
270
  const online = [];
147
271
  for (const presence of this.userPresences.values()) {
148
272
  if (presence.status === 'online') {
149
- online.push(presence);
273
+ // Check visibility with extensions
274
+ let visible = true;
275
+ for (const ext of this.extensions) {
276
+ const shouldShow = await ext.shouldBeVisible?.(presence.userId);
277
+ if (shouldShow === false) {
278
+ visible = false;
279
+ break;
280
+ }
281
+ }
282
+ if (visible) {
283
+ // Get full presence with extended data
284
+ const fullPresence = await this.getUserPresence(presence.userId);
285
+ if (fullPresence) {
286
+ online.push(fullPresence);
287
+ }
288
+ }
150
289
  }
151
290
  }
152
291
  return online;
@@ -230,6 +369,8 @@ class DefaultPresenceManager extends PresenceManager_js_1.PresenceManager {
230
369
  return changes;
231
370
  }
232
371
  async clear() {
372
+ // Destroy extensions first
373
+ await this.destroyExtensions();
233
374
  this.userPresences.clear();
234
375
  this.deviceToUser.clear();
235
376
  this.gracePeriodEntries.clear();
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Presence Extension Interface
3
+ *
4
+ * Allows applications to extend presence tracking with custom data and behavior
5
+ * without needing to create a separate presence manager.
6
+ *
7
+ * Extensions are called at key lifecycle points and can:
8
+ * - Add custom data to user presence
9
+ * - Control visibility of users
10
+ * - React to room joins/leaves
11
+ * - Track application-specific presence state
12
+ */
13
+ import type { UserPresence } from '../types/presence.js';
14
+ /**
15
+ * Extension for adding custom presence data and behavior
16
+ *
17
+ * @template TExtendedData - Type of extended presence data
18
+ */
19
+ export interface PresenceExtension<TExtendedData = Record<string, unknown>> {
20
+ /**
21
+ * Called when a user comes online (first device connects)
22
+ *
23
+ * @param userId - User identifier
24
+ * @param deviceId - Device/client identifier
25
+ * @param metadata - Connection metadata
26
+ */
27
+ onUserOnline?(userId: string, deviceId: string, metadata: Record<string, unknown>): Promise<void> | void;
28
+ /**
29
+ * Called when a user goes offline (last device disconnects)
30
+ *
31
+ * Note: This is called when the user enters grace period or fully disconnects.
32
+ *
33
+ * @param userId - User identifier
34
+ * @param deviceId - Last device that disconnected
35
+ */
36
+ onUserOffline?(userId: string, deviceId: string): Promise<void> | void;
37
+ /**
38
+ * Called when a device connects for an already-online user
39
+ *
40
+ * @param userId - User identifier
41
+ * @param deviceId - New device identifier
42
+ * @param metadata - Connection metadata
43
+ */
44
+ onDeviceConnected?(userId: string, deviceId: string, metadata: Record<string, unknown>): Promise<void> | void;
45
+ /**
46
+ * Called when a device disconnects but user remains online
47
+ *
48
+ * @param userId - User identifier
49
+ * @param deviceId - Device that disconnected
50
+ */
51
+ onDeviceDisconnected?(userId: string, deviceId: string): Promise<void> | void;
52
+ /**
53
+ * Called when a client joins a room
54
+ *
55
+ * This is useful for tracking room-specific presence data.
56
+ *
57
+ * @param userId - User identifier
58
+ * @param roomId - Room identifier
59
+ * @param deviceId - Device/client identifier
60
+ */
61
+ onRoomJoined?(userId: string, roomId: string, deviceId: string): Promise<void> | void;
62
+ /**
63
+ * Called when a client leaves a room
64
+ *
65
+ * @param userId - User identifier
66
+ * @param roomId - Room identifier
67
+ * @param deviceId - Device/client identifier
68
+ */
69
+ onRoomLeft?(userId: string, roomId: string, deviceId: string): Promise<void> | void;
70
+ /**
71
+ * Get extended presence data for a user
72
+ *
73
+ * This data will be merged into the UserPresence object.
74
+ *
75
+ * @param userId - User identifier
76
+ * @returns Extended presence data, or null if none
77
+ */
78
+ getExtendedData?(userId: string): Promise<TExtendedData | null> | TExtendedData | null;
79
+ /**
80
+ * Determine if a user should be visible to others
81
+ *
82
+ * This allows filtering users from presence lists.
83
+ * If multiple extensions return false, the user is hidden.
84
+ *
85
+ * @param userId - User identifier
86
+ * @returns true if user should be visible, false to hide
87
+ */
88
+ shouldBeVisible?(userId: string): Promise<boolean> | boolean;
89
+ /**
90
+ * Called when user activity is detected
91
+ *
92
+ * @param userId - User identifier
93
+ * @param deviceId - Device/client identifier
94
+ * @param activityType - Type of activity
95
+ */
96
+ onActivity?(userId: string, deviceId: string, activityType: 'heartbeat' | 'message' | 'user_action'): Promise<void> | void;
97
+ /**
98
+ * Lifecycle: Called when extension is initialized
99
+ *
100
+ * Use this for setup tasks like loading initial state.
101
+ */
102
+ initialize?(): Promise<void> | void;
103
+ /**
104
+ * Lifecycle: Called when extension is destroyed
105
+ *
106
+ * Use this for cleanup tasks like persisting state.
107
+ */
108
+ destroy?(): Promise<void> | void;
109
+ }
110
+ /**
111
+ * Extended user presence with custom data
112
+ *
113
+ * @template TExtendedData - Type of extended presence data
114
+ */
115
+ export interface ExtendedUserPresence<TExtendedData = Record<string, unknown>> extends UserPresence {
116
+ /**
117
+ * Extended data from presence extensions
118
+ */
119
+ extended?: TExtendedData;
120
+ }
121
+ //# sourceMappingURL=PresenceExtension.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PresenceExtension.d.ts","sourceRoot":"","sources":["../../src/abstractions/PresenceExtension.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD;;;;GAIG;AACH,MAAM,WAAW,iBAAiB,CAAC,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACxE;;;;;;OAMG;IACH,YAAY,CAAC,CACX,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAExB;;;;;;;OAOG;IACH,aAAa,CAAC,CACZ,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAExB;;;;;;OAMG;IACH,iBAAiB,CAAC,CAChB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAExB;;;;;OAKG;IACH,oBAAoB,CAAC,CACnB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAExB;;;;;;;;OAQG;IACH,YAAY,CAAC,CACX,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAExB;;;;;;OAMG;IACH,UAAU,CAAC,CACT,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAExB;;;;;;;OAOG;IACH,eAAe,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,GAAG,aAAa,GAAG,IAAI,CAAC;IAEvF;;;;;;;;OAQG;IACH,eAAe,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAE7D;;;;;;OAMG;IACH,UAAU,CAAC,CACT,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,WAAW,GAAG,SAAS,GAAG,aAAa,GACpD,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAExB;;;;OAIG;IACH,UAAU,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAEpC;;;;OAIG;IACH,OAAO,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAClC;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB,CAAC,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAC3E,SAAQ,YAAY;IACpB;;OAEG;IACH,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B"}
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ /**
3
+ * Presence Extension Interface
4
+ *
5
+ * Allows applications to extend presence tracking with custom data and behavior
6
+ * without needing to create a separate presence manager.
7
+ *
8
+ * Extensions are called at key lifecycle points and can:
9
+ * - Add custom data to user presence
10
+ * - Control visibility of users
11
+ * - React to room joins/leaves
12
+ * - Track application-specific presence state
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -8,4 +8,5 @@ export { DefaultRoomManager } from './DefaultRoomManager.js';
8
8
  export { DefaultLockManager } from './DefaultLockManager.js';
9
9
  export { PresenceManager } from './PresenceManager.js';
10
10
  export { DefaultPresenceManager } from './DefaultPresenceManager.js';
11
+ export { type PresenceExtension, type ExtendedUserPresence } from './PresenceExtension.js';
11
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/abstractions/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/abstractions/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAC1B,MAAM,wBAAwB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"WebSocketServerTransportAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/websocket/WebSocketServerTransportAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EACjB,OAAO,EACP,cAAc,EACd,YAAY,EACZ,YAAY,EAGb,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,MAAM,CAAC;AACjD,OAAO,KAAK,EAAE,MAAM,IAAI,WAAW,EAAE,MAAM,OAAO,CAAC;AAEnD,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAEhD,UAAU,gBAAgB;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,SAAS,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,8BAA8B;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,qBAAa,+BAAgC,YAAW,iBAAiB;IACvE,OAAO,CAAC,KAAK,CAAmC;IAChD,OAAO,CAAC,eAAe,CAAkC;IACzD,OAAO,CAAC,aAAa,CAAgC;IACrD,OAAO,CAAC,aAAa,CAAgC;IAGrD,OAAO,CAAC,GAAG,CAAC,CAAkB;IAC9B,OAAO,CAAC,SAAS,CAAC,CAAS;IAG3B,OAAO,CAAC,cAAc,CAAC,CAA2B;IAClD,OAAO,CAAC,WAAW,CAAC,CAAkB;IACtC,OAAO,CAAC,aAAa,CAAC,CAAS;IAG/B,OAAO,CAAC,OAAO,CAAuC;IACtD,OAAO,CAAC,IAAI,CAA8C;IAG1D,OAAO,CAAC,WAAW,CAAC,CAAe;IACnC,OAAO,CAAC,MAAM,CAAiC;gBAEnC,MAAM,CAAC,EAAE,8BAA8B;IAUnD,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAclC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAsCjE,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,WAAW,EAAE,IAAI,GAAE,MAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAiC7E,uBAAuB,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;YA2BpD,gBAAgB;YA6EhB,mBAAmB;YAuCnB,iBAAiB;IA4E/B,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,YAAY;IAMd,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAiC3B,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IA8B3C,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAIxC,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI;IAIpC,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI;IAIpC,QAAQ,IAAI,eAAe;IAI3B,WAAW,IAAI,OAAO;IAKtB,mBAAmB,IAAI,gBAAgB,EAAE;IAIzC,uBAAuB,IAAI,gBAAgB,EAAE;IAI7C,cAAc,IAAI,MAAM;IAIxB,OAAO,IAAI,YAAY,GAAG,aAAa;IAIvC,cAAc,IAAI,OAAO;IAIzB,OAAO,CAAC,UAAU;CAGnB"}
1
+ {"version":3,"file":"WebSocketServerTransportAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/websocket/WebSocketServerTransportAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EACjB,OAAO,EACP,cAAc,EACd,YAAY,EACZ,YAAY,EAGb,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,MAAM,CAAC;AACjD,OAAO,KAAK,EAAE,MAAM,IAAI,WAAW,EAAE,MAAM,OAAO,CAAC;AAEnD,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAEhD,UAAU,gBAAgB;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,SAAS,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,8BAA8B;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,qBAAa,+BAAgC,YAAW,iBAAiB;IACvE,OAAO,CAAC,KAAK,CAAmC;IAChD,OAAO,CAAC,eAAe,CAAkC;IACzD,OAAO,CAAC,aAAa,CAAgC;IACrD,OAAO,CAAC,aAAa,CAAgC;IAGrD,OAAO,CAAC,GAAG,CAAC,CAAkB;IAC9B,OAAO,CAAC,SAAS,CAAC,CAAS;IAG3B,OAAO,CAAC,cAAc,CAAC,CAA2B;IAClD,OAAO,CAAC,WAAW,CAAC,CAAkB;IACtC,OAAO,CAAC,aAAa,CAAC,CAAS;IAG/B,OAAO,CAAC,OAAO,CAAuC;IACtD,OAAO,CAAC,IAAI,CAA8C;IAG1D,OAAO,CAAC,WAAW,CAAC,CAAe;IACnC,OAAO,CAAC,MAAM,CAAiC;gBAEnC,MAAM,CAAC,EAAE,8BAA8B;IAUnD,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAclC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAsCjE,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,WAAW,EAAE,IAAI,GAAE,MAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAiC7E,uBAAuB,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;YA2BpD,gBAAgB;YA6EhB,mBAAmB;YAuCnB,iBAAiB;IA4E/B,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,YAAY;IAMd,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAiC3B,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IA4C3C,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAIxC,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI;IAIpC,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI;IAIpC,QAAQ,IAAI,eAAe;IAI3B,WAAW,IAAI,OAAO;IAKtB,mBAAmB,IAAI,gBAAgB,EAAE;IAIzC,uBAAuB,IAAI,gBAAgB,EAAE;IAI7C,cAAc,IAAI,MAAM;IAIxB,OAAO,IAAI,YAAY,GAAG,aAAa;IAIvC,cAAc,IAAI,OAAO;IAIzB,OAAO,CAAC,UAAU;CAGnB"}
@@ -342,10 +342,24 @@ class WebSocketServerTransportAdapter {
342
342
  if (client.ws.readyState !== ws_1.WebSocket.OPEN) {
343
343
  throw new Error(`Client ${clientId} connection is not open`);
344
344
  }
345
- const messageToSend = {
346
- ...message,
347
- payload: clientMessage
348
- };
345
+ // Fully unwrap the server_message envelope
346
+ // The server_message wrapper is used internally for routing between BaseServer and transport
347
+ // We need to extract the actual message from inside the wrapper before sending to the client
348
+ const messageToSend = message.type === 'server_message' && clientMessage.type
349
+ ? {
350
+ id: message.id,
351
+ type: clientMessage.type,
352
+ payload: clientMessage.payload || (() => {
353
+ // If there's no explicit payload field, use all remaining fields except 'type'
354
+ const { type, ...rest } = clientMessage;
355
+ return rest;
356
+ })(),
357
+ timestamp: message.timestamp
358
+ }
359
+ : {
360
+ ...message,
361
+ payload: clientMessage
362
+ };
349
363
  client.ws.send(JSON.stringify(messageToSend));
350
364
  }
351
365
  onMessage(handler) {
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Presence Client Helper
3
+ *
4
+ * Simplifies client-side presence integration for web/Electron applications.
5
+ * Automatically handles connection, authentication, and presence event forwarding.
6
+ */
7
+ import { BaseClient, type ClientConfig } from './BaseClient.js';
8
+ import { TypedEventEmitter } from '../abstractions/EventEmitter.js';
9
+ import type { UserPresence } from '../types/presence.js';
10
+ export interface PresenceClientConfig {
11
+ /**
12
+ * Base client configuration
13
+ * Provide the full client config including transport and auth adapters
14
+ */
15
+ clientConfig: ClientConfig;
16
+ /**
17
+ * WebSocket URL to connect to
18
+ */
19
+ wsUrl: string;
20
+ /**
21
+ * Automatically connect on construction
22
+ * @default false
23
+ */
24
+ autoConnect?: boolean;
25
+ /**
26
+ * Automatically reconnect on disconnect
27
+ * @default true
28
+ */
29
+ autoReconnect?: boolean;
30
+ /**
31
+ * Global presence room ID
32
+ * @default '__global_presence__'
33
+ */
34
+ globalRoomId?: string;
35
+ }
36
+ export interface PresenceClientEvents {
37
+ /**
38
+ * User came online
39
+ */
40
+ userOnline: {
41
+ userId: string;
42
+ devices: unknown[];
43
+ };
44
+ /**
45
+ * User went offline
46
+ */
47
+ userOffline: {
48
+ userId: string;
49
+ };
50
+ /**
51
+ * User's presence status changed
52
+ */
53
+ statusChanged: {
54
+ userId: string;
55
+ status: string;
56
+ statusMessage?: string;
57
+ };
58
+ /**
59
+ * Connected to presence system
60
+ */
61
+ connected: {};
62
+ /**
63
+ * Disconnected from presence system
64
+ */
65
+ disconnected: {};
66
+ /**
67
+ * Error occurred
68
+ */
69
+ error: {
70
+ error: Error;
71
+ };
72
+ [key: string]: unknown;
73
+ }
74
+ /**
75
+ * High-level presence client for easy integration
76
+ *
77
+ * Example:
78
+ * ```typescript
79
+ * import { PresenceClient } from '@principal-ai/control-tower-core';
80
+ * import { WebSocketClientTransportAdapter } from '@principal-ai/control-tower-core';
81
+ * import { ClientBuilder } from '@principal-ai/control-tower-core';
82
+ *
83
+ * const transport = new WebSocketClientTransportAdapter();
84
+ *
85
+ * const client = new ClientBuilder()
86
+ * .withTransport(transport)
87
+ * .build();
88
+ *
89
+ * const presence = new PresenceClient({
90
+ * clientConfig: client,
91
+ * wsUrl: 'ws://localhost:3000/ws',
92
+ * autoConnect: true,
93
+ * autoReconnect: true
94
+ * });
95
+ *
96
+ * presence.on('userOnline', (data) => {
97
+ * console.log(`${data.userId} came online`);
98
+ * });
99
+ *
100
+ * presence.on('userOffline', (data) => {
101
+ * console.log(`${data.userId} went offline`);
102
+ * });
103
+ * ```
104
+ */
105
+ export declare class PresenceClient extends TypedEventEmitter<PresenceClientEvents> {
106
+ private client;
107
+ private wsUrl;
108
+ private autoReconnectEnabled;
109
+ private globalRoomId;
110
+ private reconnectTimeout?;
111
+ constructor(config: PresenceClientConfig);
112
+ /**
113
+ * Connect to the presence system
114
+ */
115
+ connect(): Promise<void>;
116
+ /**
117
+ * Disconnect from the presence system
118
+ */
119
+ disconnect(): Promise<void>;
120
+ /**
121
+ * Get all online users
122
+ *
123
+ * Note: This requires a corresponding API endpoint on your server.
124
+ */
125
+ getOnlineUsers(): Promise<UserPresence[]>;
126
+ /**
127
+ * Set user status
128
+ *
129
+ * Note: This requires a corresponding API endpoint on your server.
130
+ */
131
+ setStatus(status: 'online' | 'away', message?: string): Promise<void>;
132
+ /**
133
+ * Set visibility
134
+ *
135
+ * Note: This requires a corresponding API endpoint on your server.
136
+ */
137
+ setVisibility(visible: boolean): Promise<void>;
138
+ /**
139
+ * Get the underlying BaseClient
140
+ */
141
+ getClient(): BaseClient;
142
+ /**
143
+ * Check if connected
144
+ */
145
+ isConnected(): boolean;
146
+ /**
147
+ * Set up event forwarding from BaseClient
148
+ * @private
149
+ */
150
+ private setupEventForwarding;
151
+ }
152
+ //# sourceMappingURL=PresenceClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PresenceClient.d.ts","sourceRoot":"","sources":["../../src/client/PresenceClient.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,YAAY,EAAE,YAAY,CAAC;IAE3B;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,UAAU,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,EAAE,CAAA;KAAE,CAAC;IAEnD;;OAEG;IACH,WAAW,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAEhC;;OAEG;IACH,aAAa,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAE1E;;OAEG;IACH,SAAS,EAAE,EAAE,CAAC;IAEd;;OAEG;IACH,YAAY,EAAE,EAAE,CAAC;IAEjB;;OAEG;IACH,KAAK,EAAE;QAAE,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC;IAExB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBAAa,cAAe,SAAQ,iBAAiB,CAAC,oBAAoB,CAAC;IACzE,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,oBAAoB,CAAU;IACtC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,gBAAgB,CAAC,CAAiB;gBAE9B,MAAM,EAAE,oBAAoB;IAiBxC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAe9B;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAuBjC;;;;OAIG;IACG,cAAc,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAS/C;;;;OAIG;IACG,SAAS,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS3E;;;;OAIG;IACG,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IASpD;;OAEG;IACH,SAAS,IAAI,UAAU;IAIvB;;OAEG;IACH,WAAW,IAAI,OAAO;IAMtB;;;OAGG;IACH,OAAO,CAAC,oBAAoB;CAoD7B"}