@masons/agent-network 0.4.16 → 0.4.18

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.
@@ -2,7 +2,8 @@ import { randomUUID } from "node:crypto";
2
2
  import { EventEmitter } from "node:events";
3
3
  import createDebug from "debug";
4
4
  import WebSocket from "ws";
5
- import { CURRENT_PROTOCOL_VERSION, isErrorEvent, isFlushComplete, isMessageReceived, isRegisterAck, isSessionCreated, isSessionEnded, REGISTER_ACK_TIMEOUT_MS, } from "./types.js";
5
+ import { setEnvironmentContext } from "./environment-context.js";
6
+ import { CURRENT_PROTOCOL_VERSION, isAddressedMessage, isDeliveryPending, isRegisterAck, isSendAck, isStructuredError, REGISTER_ACK_TIMEOUT_MS, SEND_ACK_TIMEOUT_MS, } from "./types.js";
6
7
  import { PLUGIN_VERSION } from "./version.js";
7
8
  const dbg = createDebug("agent-network:connector");
8
9
  const dbgMsg = createDebug("agent-network:connector:msg");
@@ -11,6 +12,21 @@ const BACKOFF_INITIAL_MS = 1_000;
11
12
  const BACKOFF_MAX_MS = 30_000;
12
13
  const ALREADY_CONNECTED_RETRY_MS = 2_000;
13
14
  const ALREADY_CONNECTED_MAX_RETRIES = 30;
15
+ // --- Error class for structured errors ---
16
+ /**
17
+ * Error with machine-readable `code` from Connector structured errors.
18
+ * Thrown by `send()` when the Connector responds with ERROR { code }.
19
+ */
20
+ export class ConnectorError extends Error {
21
+ code;
22
+ to;
23
+ constructor(message, code, to) {
24
+ super(message);
25
+ this.name = "ConnectorError";
26
+ this.code = code;
27
+ this.to = to;
28
+ }
29
+ }
14
30
  // --- ConnectorClient ---
15
31
  export class ConnectorClient extends EventEmitter {
16
32
  url;
@@ -23,6 +39,8 @@ export class ConnectorClient extends EventEmitter {
23
39
  registerResolve = null;
24
40
  registerReject = null;
25
41
  registerTimer = null;
42
+ /** Pending SEND_ACK correlation map for address-based sends. */
43
+ pendingSends = new Map();
26
44
  constructor(url, token) {
27
45
  super();
28
46
  this.url = url;
@@ -49,58 +67,54 @@ export class ConnectorClient extends EventEmitter {
49
67
  wsToClose.close(1000, "Client disconnect");
50
68
  }
51
69
  }
52
- createSession(target, opts) {
53
- const requestId = randomUUID();
54
- const event = {
55
- event: "CREATE_SESSION",
56
- requestId,
57
- target,
58
- secret: opts?.secret,
59
- metadata: opts?.metadata,
60
- };
61
- const sent = this.send(event);
62
- dbg("createSession target=%s requestId=%s sent=%s", target, requestId, sent);
63
- return { requestId, sent };
64
- }
65
- sendMessage(sessionId, content, opts) {
70
+ // --- Messaging ---
71
+ /**
72
+ * Send a message to a routable address. Returns a Promise that resolves
73
+ * with the SEND_ACK from the Connector, or rejects with a ConnectorError
74
+ * (structured error code) or a plain Error (timeout / disconnect).
75
+ *
76
+ * The messageId is generated internally and used for ACK correlation.
77
+ */
78
+ send(to, content, contentType = "text", metadata) {
79
+ const messageId = randomUUID();
66
80
  const event = {
67
81
  event: "SEND_MESSAGE",
68
- sessionId,
82
+ messageId,
83
+ to,
69
84
  content,
70
- contentType: opts?.contentType,
71
- metadata: opts?.metadata,
72
- };
73
- const sent = this.send(event);
74
- dbgMsg("sendMessage sessionId=%s contentLength=%d sent=%s", sessionId, content.length, sent);
75
- return sent;
76
- }
77
- endSession(sessionId, reason) {
78
- const event = {
79
- event: "END_SESSION",
80
- sessionId,
81
- reason,
85
+ contentType,
86
+ metadata,
82
87
  };
83
- const sent = this.send(event);
84
- dbg("endSession sessionId=%s sent=%s", sessionId, sent);
85
- return sent;
86
- }
87
- sendDeliveryAck(mailboxSessionId) {
88
- const event = {
89
- event: "DELIVERY_ACK",
90
- mailboxSessionId,
91
- };
92
- const sent = this.send(event);
93
- dbg("sendDeliveryAck mailboxSessionId=%s sent=%s", mailboxSessionId, sent);
94
- return sent;
88
+ return new Promise((resolve, reject) => {
89
+ const timer = setTimeout(() => {
90
+ this.pendingSends.delete(messageId);
91
+ reject(new Error("SEND_ACK timeout"));
92
+ }, SEND_ACK_TIMEOUT_MS);
93
+ this.pendingSends.set(messageId, { resolve, reject, timer });
94
+ const sent = this.sendEvent(event);
95
+ if (!sent) {
96
+ clearTimeout(timer);
97
+ this.pendingSends.delete(messageId);
98
+ reject(new Error("WebSocket not connected"));
99
+ }
100
+ });
95
101
  }
96
- sendTypingStart(sessionId) {
97
- const sent = this.send({ event: "TYPING_START", sessionId });
98
- dbg("sendTypingStart sessionId=%s sent=%s", sessionId, sent);
102
+ /**
103
+ * Send a typing indicator to a routable address.
104
+ */
105
+ sendTyping(to, isTyping) {
106
+ const event = { event: "TYPING", to, isTyping };
107
+ const sent = this.sendEvent(event);
108
+ dbg("sendTyping to=%s isTyping=%s sent=%s", to, isTyping, sent);
99
109
  return sent;
100
110
  }
101
- sendTypingStop(sessionId) {
102
- const sent = this.send({ event: "TYPING_STOP", sessionId });
103
- dbg("sendTypingStop sessionId=%s sent=%s", sessionId, sent);
111
+ /**
112
+ * Acknowledge delivery of stored messages up to a timestamp.
113
+ */
114
+ ackDelivery(upTo) {
115
+ const event = { event: "DELIVERY_ACK", upTo };
116
+ const sent = this.sendEvent(event);
117
+ dbg("ackDelivery upTo=%s sent=%s", upTo, sent);
104
118
  return sent;
105
119
  }
106
120
  on(event, listener) {
@@ -138,7 +152,7 @@ export class ConnectorClient extends EventEmitter {
138
152
  protocolVersion: CURRENT_PROTOCOL_VERSION,
139
153
  clientVersion: PLUGIN_VERSION,
140
154
  };
141
- this.send(event);
155
+ this.sendEvent(event);
142
156
  }
143
157
  startRegisterTimeout() {
144
158
  this.registerTimer = setTimeout(() => {
@@ -178,40 +192,49 @@ export class ConnectorClient extends EventEmitter {
178
192
  this.emit("error", new Error("Received non-JSON message from Connector"));
179
193
  return;
180
194
  }
195
+ // REGISTER_ACK is version-agnostic — always handled first
181
196
  if (isRegisterAck(parsed)) {
182
197
  this.handleRegisterAck(parsed);
183
198
  return;
184
199
  }
185
- if (isSessionCreated(parsed)) {
186
- dbg("SESSION_CREATED sessionId=%s direction=%s", parsed.sessionId, parsed.direction);
187
- this.handleSessionCreated(parsed);
200
+ this.dispatchAddressed(parsed);
201
+ };
202
+ /** Dispatch inbound address-based events. */
203
+ dispatchAddressed(parsed) {
204
+ if (isSendAck(parsed)) {
205
+ dbg("SEND_ACK messageId=%s status=%s", parsed.messageId, parsed.status);
206
+ this.handleSendAck(parsed);
188
207
  return;
189
208
  }
190
- if (isMessageReceived(parsed)) {
191
- dbgMsg("MESSAGE_RECEIVED sessionId=%s contentLength=%d", parsed.sessionId, parsed.content.length);
209
+ if (isAddressedMessage(parsed)) {
210
+ dbgMsg("MESSAGE_RECEIVED from=%s contentLength=%d", parsed.from, parsed.content.length);
192
211
  this.emit("message_received", parsed);
193
212
  return;
194
213
  }
195
- if (isSessionEnded(parsed)) {
196
- dbg("SESSION_ENDED sessionId=%s", parsed.sessionId);
197
- this.emit("session_ended", parsed);
214
+ if (isDeliveryPending(parsed)) {
215
+ dbg("DELIVERY_PENDING count=%d upTo=%s", parsed.count, parsed.upTo);
216
+ this.emit("delivery_pending", parsed);
198
217
  return;
199
218
  }
200
- if (isFlushComplete(parsed)) {
201
- dbg("FLUSH_COMPLETE mailboxSessionId=%s messageCount=%d", parsed.mailboxSessionId, parsed.messageCount);
202
- this.emit("flush_complete", parsed);
203
- return;
204
- }
205
- if (isErrorEvent(parsed)) {
206
- dbg("ERROR message=%s", parsed.message);
207
- this.handleErrorEvent(parsed);
219
+ if (isStructuredError(parsed)) {
220
+ dbg("ERROR code=%s message=%s", parsed.code, parsed.message);
221
+ this.handleStructuredError(parsed);
208
222
  return;
209
223
  }
210
224
  // Unknown event type — silently ignore (forward compatibility)
211
- };
225
+ }
212
226
  handleRegisterAck(ack) {
213
- dbg("REGISTER_ACK status=%s", ack.status);
227
+ dbg("REGISTER_ACK status=%s protocolVersion=%d", ack.status, ack.protocolVersion);
214
228
  if (ack.status === "ok") {
229
+ // Defensive: verify the negotiated version matches what we requested.
230
+ // Current Connector rejects unsupported versions outright (no downgrade),
231
+ // so this check is future-proofing against silent protocol desync.
232
+ if (ack.protocolVersion !== CURRENT_PROTOCOL_VERSION) {
233
+ dbg("WARN: server negotiated protocolVersion=%d, expected=%d", ack.protocolVersion, CURRENT_PROTOCOL_VERSION);
234
+ }
235
+ // Store agent + owner identity from enriched REGISTER_ACK (#969).
236
+ // Old Connectors omit these fields — setEnvironmentContext handles undefined.
237
+ setEnvironmentContext(ack.agent, ack.owner);
215
238
  this.backoffMs = BACKOFF_INITIAL_MS;
216
239
  this.alreadyConnectedRetries = 0;
217
240
  this.resolveRegister();
@@ -235,17 +258,38 @@ export class ConnectorClient extends EventEmitter {
235
258
  this.ws?.close(4001, reason);
236
259
  }
237
260
  }
238
- handleSessionCreated(event) {
239
- this.emit("session_created", event);
261
+ // --- Address-based handlers ---
262
+ /**
263
+ * Resolve the pending send promise matching this SEND_ACK.
264
+ * Also emits `send_ack` for listeners that want to observe all ACKs.
265
+ */
266
+ handleSendAck(ack) {
267
+ this.emit("send_ack", ack);
268
+ const pending = this.pendingSends.get(ack.messageId);
269
+ if (pending) {
270
+ clearTimeout(pending.timer);
271
+ this.pendingSends.delete(ack.messageId);
272
+ pending.resolve(ack);
273
+ }
240
274
  }
241
- handleErrorEvent(event) {
242
- // (1) Route by sessionId session-scoped error
243
- if (event.sessionId) {
244
- this.emit("session_error", event.sessionId, event.message);
245
- return;
275
+ /**
276
+ * Structured ERROR: message-scoped errors reject pending sends,
277
+ * connection-scoped errors emit generic event.
278
+ */
279
+ handleStructuredError(event) {
280
+ this.emit("structured_error", event);
281
+ // Message-scoped: reject the pending send for this messageId
282
+ if (event.messageId) {
283
+ const pending = this.pendingSends.get(event.messageId);
284
+ if (pending) {
285
+ clearTimeout(pending.timer);
286
+ this.pendingSends.delete(event.messageId);
287
+ pending.reject(new ConnectorError(event.message, event.code, event.to));
288
+ return;
289
+ }
246
290
  }
247
- // (2) Global error (includes requestId-only errors from failed CREATE_SESSION)
248
- this.emit("error", new Error(event.message));
291
+ // Connection-scoped: emit generic error
292
+ this.emit("error", new Error(`[${event.code}] ${event.message}`));
249
293
  }
250
294
  // --- Connection close ---
251
295
  handleClose = () => {
@@ -320,9 +364,16 @@ export class ConnectorClient extends EventEmitter {
320
364
  clearTimeout(this.registerTimer);
321
365
  this.registerTimer = null;
322
366
  }
367
+ // Reject all pending address-based sends on disconnect
368
+ for (const [, pending] of this.pendingSends) {
369
+ clearTimeout(pending.timer);
370
+ pending.reject(new Error("Connection closed"));
371
+ }
372
+ this.pendingSends.clear();
323
373
  }
324
- // --- Helpers ---
325
- send(event) {
374
+ // --- Wire helpers ---
375
+ /** Send an event over the WebSocket. */
376
+ sendEvent(event) {
326
377
  if (this.ws?.readyState === WebSocket.OPEN) {
327
378
  this.ws.send(JSON.stringify(event));
328
379
  return true;
@@ -2,20 +2,21 @@
2
2
  * Conversation Manager — maps contacts to conversations.
3
3
  *
4
4
  * Owns the identity-based API that the Tool Interface and Inbound Dispatcher
5
- * call. Internally delegates session management to SessionLifecycle.
5
+ * call. Sends messages directly to routable addresses via ConnectorClient.send().
6
6
  *
7
- * Contact resolution: handle -> `mstps://${connectorHost}/${handle}`
8
- * Internal key: MSTP address (same as SessionLifecycle).
9
- * One session per contact, last-write-wins (D2).
10
- * In-memory only, no persistence (D3).
7
+ * Contact resolution: handle `mstps://${connectorHost}/${handle}`
8
+ * Address schemes: MSTP (`mstps://`), handle (bare), passport (`passport:@`)
9
+ * Internal key: routable address.
10
+ * One conversation per contact, in-memory only (D3).
11
11
  *
12
- * @see docs/openclaw/session-abstraction-system-design.md §4.1
12
+ * @see docs/connector/gateway-v2-consumer-adaptation-plan.md PR 2
13
13
  */
14
- import type { ConnectorClient } from "./connector-client.js";
15
- import { SessionLifecycle } from "./session-lifecycle.js";
14
+ import { type ConnectorClient } from "./connector-client.js";
16
15
  export interface SendResult {
17
16
  status: "sent" | "failed";
18
17
  error?: string;
18
+ /** Machine-readable error code from Connector (when status === "failed"). */
19
+ errorCode?: string;
19
20
  }
20
21
  export interface ConversationEntry {
21
22
  contact: string;
@@ -31,37 +32,37 @@ export interface ConversationSummary {
31
32
  initiatedBy: "local" | "remote";
32
33
  }
33
34
  export declare class ConversationManager {
34
- readonly sessionLifecycle: SessionLifecycle;
35
35
  private readonly client;
36
36
  private readonly connectorHost;
37
- /** Conversations keyed by MSTP address */
37
+ /** Conversations keyed by routable address */
38
38
  private readonly conversations;
39
39
  constructor(client: ConnectorClient, connectorHost: string);
40
40
  /**
41
- * Send a message to a contact. Session management is transparent.
42
- * Creates a session automatically if none exists (D1 — sync-but-transparent).
41
+ * Send a message to a contact. Sends directly to the resolved address —
42
+ * no session management needed.
43
43
  *
44
- * @param contact - Handle or MSTP address of the target agent.
44
+ * Returns a structured result with status and optional error.
45
+ * ConnectorError codes (ADDRESS_NOT_FOUND, ACCESS_DENIED, etc.) are
46
+ * mapped to human-readable messages for LLM tool results.
47
+ *
48
+ * @param contact - Handle, MSTP address, or passport address of the target.
45
49
  * @param content - Message content to send.
46
50
  */
47
51
  send(contact: string, content: string): Promise<SendResult>;
48
52
  /**
49
- * Register an inbound session (remote agent initiated).
50
- * Called by the channel adapter on SESSION_CREATED with direction=inbound.
51
- */
52
- registerInbound(sessionId: string, contact: string, address: string): void;
53
- /**
54
- * Resolve a sessionId to a contact handle.
55
- * Used by the inbound dispatcher to derive sender identity from a message event.
53
+ * Register an inbound conversation (remote agent or visitor initiated).
54
+ * Called by the channel adapter when a message arrives from a new address.
56
55
  */
57
- getContactBySessionId(sessionId: string): string | undefined;
56
+ registerInbound(contact: string, address: string): void;
58
57
  /**
59
- * Resolve a sessionId to an MSTP address.
60
- * Used by the channel adapter for reverse lookup.
58
+ * Resolve a routable address to a contact handle.
59
+ * Used by the inbound dispatcher to derive sender identity from MESSAGE_RECEIVED.from.
61
60
  */
62
- getAddressBySessionId(sessionId: string): string | undefined;
61
+ getContactByAddress(address: string): string | undefined;
63
62
  /**
64
63
  * List all conversations with metadata.
64
+ * `active` is always true for address-based protocol — there is no session
65
+ * lifecycle. Conversations persist until explicitly ended or Plugin restarts.
65
66
  */
66
67
  listConversations(): ConversationSummary[];
67
68
  /**
@@ -74,25 +75,31 @@ export declare class ConversationManager {
74
75
  hasConversation(contact: string): boolean;
75
76
  /**
76
77
  * End the conversation with a contact.
77
- * Closes the active session if one exists.
78
+ * Removes the conversation entry. No END_SESSION needed — the Connector
79
+ * manages connection lifecycle (idle timeout).
78
80
  */
79
81
  endConversation(contact: string): void;
80
82
  /**
81
- * Resolve a contact (handle or MSTP address) to an MSTP address.
83
+ * Resolve a contact (handle, MSTP address, or passport address) to a
84
+ * routable address.
82
85
  *
83
86
  * Resolution order:
84
- * 1. If `contact` is already an MSTP address (starts with mstps:// or mstp://),
85
- * return it as-is.
86
- * 2. If `contact` is already a key in the conversations map (e.g., a sessionId
87
- * used as fallback address for direct MSTP clients), return it as-is.
88
- * 3. Otherwise construct: `mstps://${connectorHost}/${handle}`.
87
+ * 1. MSTP address (starts with mstps:// or mstp://) → return as-is.
88
+ * 2. Passport address (starts with passport:) → return as-is (already routable).
89
+ * 3. Already a key in the conversations map return as-is.
90
+ * 4. Reverse lookup: if any existing conversation has this handle as its
91
+ * contact, use that conversation's address. This ensures replies to a
92
+ * cross-Connector sender route to the sender's original address, not to
93
+ * a locally-constructed address.
94
+ * 5. Otherwise treat as handle → construct `mstps://${connectorHost}/${handle}`.
89
95
  */
90
96
  resolveAddress(contact: string): string;
91
97
  /**
92
- * Extract handle from an MSTP address.
93
- * `mstps://preview.masons.ai/alice` -> `alice`
98
+ * Extract a display-friendly handle from any address scheme.
99
+ * Delegates to shared utility — single source of truth.
94
100
  */
95
101
  extractHandle(address: string): string;
102
+ private ensureConversationEntry;
96
103
  /** @internal Reset for test isolation. */
97
104
  _resetForTesting(): void;
98
105
  }
@@ -1 +1 @@
1
- {"version":3,"file":"conversation-manager.d.ts","sourceRoot":"","sources":["../src/conversation-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAQ1D,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,GAAG,QAAQ,CAAC;CACjC;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,GAAG,QAAQ,CAAC;CACjC;AAMD,qBAAa,mBAAmB;IAC9B,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC,0CAA0C;IAC1C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAwC;gBAE1D,MAAM,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM;IAU1D;;;;;;OAMG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAgDjE;;;OAGG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAyB1E;;;OAGG;IACH,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAQ5D;;;OAGG;IACH,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI5D;;OAEG;IACH,iBAAiB,IAAI,mBAAmB,EAAE;IAe1C;;;;;;OAMG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAKzC;;;OAGG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAkBtC;;;;;;;;;OASG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAYvC;;;OAGG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAUtC,0CAA0C;IAC1C,gBAAgB,IAAI,IAAI;CAIzB"}
1
+ {"version":3,"file":"conversation-manager.d.ts","sourceRoot":"","sources":["../src/conversation-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,KAAK,eAAe,EAAkB,MAAM,uBAAuB,CAAC;AAS7E,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,GAAG,QAAQ,CAAC;CACjC;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,GAAG,QAAQ,CAAC;CACjC;AAwBD,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC,8CAA8C;IAC9C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAwC;gBAE1D,MAAM,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM;IAS1D;;;;;;;;;;OAUG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IA0CjE;;;OAGG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAKvD;;;OAGG;IACH,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAaxD;;;;OAIG;IACH,iBAAiB,IAAI,mBAAmB,EAAE;IAc1C;;;;;;OAMG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAKzC;;;;OAIG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAUtC;;;;;;;;;;;;;OAaG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAoBvC;;;OAGG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAQtC,OAAO,CAAC,uBAAuB;IAwB/B,0CAA0C;IAC1C,gBAAgB,IAAI,IAAI;CAGzB"}