@masons/agent-network 0.3.9 → 0.4.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.
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Session Lifecycle — manages MSTP session state per contact.
3
+ *
4
+ * State machine: idle -> creating -> active -> recreating -> failed.
5
+ * Internal key: MSTP address (not handle) — supports same handle on different Connectors.
6
+ *
7
+ * Responsibilities:
8
+ * - Maps MSTP address to session state (sessionId, status, pending promise)
9
+ * - Auto-creates sessions on demand via ensureSession()
10
+ * - Deduplicates concurrent creates (second caller awaits first's promise)
11
+ * - Registers inbound sessions from remote agents
12
+ * - Invalidates all sessions on transport disconnect
13
+ * - Subscribes to ConnectorClient events: session_created, session_error, session_ended, disconnected
14
+ *
15
+ * @see docs/openclaw/session-abstraction-system-design.md §6.1
16
+ */
17
+ import type { ConnectorClient } from "./connector-client.js";
18
+ export type SessionStatus = "idle" | "creating" | "active" | "recreating" | "failed";
19
+ export interface SessionState {
20
+ status: SessionStatus;
21
+ sessionId: string | null;
22
+ pendingRequestId: string | null;
23
+ retryCount: number;
24
+ /** Promise that resolves when session creation completes. Used for dedup. */
25
+ createPromise: Promise<string> | null;
26
+ /** Resolve function for the create promise. */
27
+ createResolve: ((sessionId: string) => void) | null;
28
+ /** Reject function for the create promise. */
29
+ createReject: ((err: Error) => void) | null;
30
+ }
31
+ export declare class SessionLifecycle {
32
+ private readonly client;
33
+ /** Primary map: MSTP address -> session state */
34
+ private readonly sessions;
35
+ /** Reverse index: sessionId -> MSTP address */
36
+ private readonly sessionToAddress;
37
+ /** Configurable timeout for tests */
38
+ private timeoutMs;
39
+ constructor(client: ConnectorClient);
40
+ /**
41
+ * Ensure an active session exists for the given address.
42
+ * Returns the sessionId. Creates a new session if none exists.
43
+ * Concurrent callers get the same promise (dedup).
44
+ */
45
+ ensureSession(address: string): Promise<string>;
46
+ /**
47
+ * Get the active sessionId for an address, or null.
48
+ */
49
+ getSession(address: string): string | null;
50
+ /**
51
+ * Close the session for an address.
52
+ */
53
+ closeSession(address: string, reason?: string): void;
54
+ /**
55
+ * Register an inbound session (remote initiated).
56
+ */
57
+ registerInbound(sessionId: string, address: string): void;
58
+ /**
59
+ * Invalidate all sessions. Called on transport disconnect.
60
+ * Conversations are preserved but sessions are cleared.
61
+ */
62
+ invalidateAll(): void;
63
+ /**
64
+ * Reverse lookup: sessionId -> MSTP address.
65
+ */
66
+ getAddressBySessionId(sessionId: string): string | undefined;
67
+ /**
68
+ * Get all session entries (for listing conversations).
69
+ */
70
+ listSessions(): Array<{
71
+ address: string;
72
+ status: SessionStatus;
73
+ sessionId: string | null;
74
+ }>;
75
+ /**
76
+ * Remove a session entry entirely (for endConversation).
77
+ */
78
+ removeSession(address: string): void;
79
+ /** @internal Override timeout for tests. */
80
+ _setTimeoutForTesting(ms: number): void;
81
+ /** @internal Reset for test isolation. */
82
+ _resetForTesting(): void;
83
+ private subscribe;
84
+ private createNewSession;
85
+ private handleSessionCreated;
86
+ private handleSessionEnded;
87
+ private handleSessionError;
88
+ }
89
+ //# sourceMappingURL=session-lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-lifecycle.d.ts","sourceRoot":"","sources":["../src/session-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAQ7D,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,UAAU,GACV,QAAQ,GACR,YAAY,GACZ,QAAQ,CAAC;AAEb,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,aAAa,CAAC;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,aAAa,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IACtC,+CAA+C;IAC/C,aAAa,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACpD,8CAA8C;IAC9C,YAAY,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;CAC7C;AAaD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,iDAAiD;IACjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmC;IAC5D,+CAA+C;IAC/C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA6B;IAE9D,qCAAqC;IACrC,OAAO,CAAC,SAAS,CAA6B;gBAElC,MAAM,EAAE,eAAe;IASnC;;;;OAIG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAoB/C;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAQ1C;;OAEG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAqBpD;;OAEG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAqBzD;;;OAGG;IACH,aAAa,IAAI,IAAI;IAqBrB;;OAEG;IACH,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI5D;;OAEG;IACH,YAAY,IAAI,KAAK,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,aAAa,CAAC;QACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;KAC1B,CAAC;IAgBF;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAcpC,4CAA4C;IAC5C,qBAAqB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAIvC,0CAA0C;IAC1C,gBAAgB,IAAI,IAAI;IAmBxB,OAAO,CAAC,SAAS;IAsBjB,OAAO,CAAC,gBAAgB;IA+GxB,OAAO,CAAC,oBAAoB;IAuC5B,OAAO,CAAC,kBAAkB;IAiB1B,OAAO,CAAC,kBAAkB;CA0B3B"}
@@ -0,0 +1,348 @@
1
+ /**
2
+ * Session Lifecycle — manages MSTP session state per contact.
3
+ *
4
+ * State machine: idle -> creating -> active -> recreating -> failed.
5
+ * Internal key: MSTP address (not handle) — supports same handle on different Connectors.
6
+ *
7
+ * Responsibilities:
8
+ * - Maps MSTP address to session state (sessionId, status, pending promise)
9
+ * - Auto-creates sessions on demand via ensureSession()
10
+ * - Deduplicates concurrent creates (second caller awaits first's promise)
11
+ * - Registers inbound sessions from remote agents
12
+ * - Invalidates all sessions on transport disconnect
13
+ * - Subscribes to ConnectorClient events: session_created, session_error, session_ended, disconnected
14
+ *
15
+ * @see docs/openclaw/session-abstraction-system-design.md §6.1
16
+ */
17
+ import createDebug from "debug";
18
+ const dbg = createDebug("agent-network:session-lifecycle");
19
+ // ---------------------------------------------------------------------------
20
+ // Constants
21
+ // ---------------------------------------------------------------------------
22
+ const SESSION_CREATE_TIMEOUT_MS = 15_000;
23
+ const MAX_RETRY_COUNT = 1;
24
+ // ---------------------------------------------------------------------------
25
+ // SessionLifecycle
26
+ // ---------------------------------------------------------------------------
27
+ export class SessionLifecycle {
28
+ client;
29
+ /** Primary map: MSTP address -> session state */
30
+ sessions = new Map();
31
+ /** Reverse index: sessionId -> MSTP address */
32
+ sessionToAddress = new Map();
33
+ /** Configurable timeout for tests */
34
+ timeoutMs = SESSION_CREATE_TIMEOUT_MS;
35
+ constructor(client) {
36
+ this.client = client;
37
+ this.subscribe();
38
+ }
39
+ // -------------------------------------------------------------------------
40
+ // Public API
41
+ // -------------------------------------------------------------------------
42
+ /**
43
+ * Ensure an active session exists for the given address.
44
+ * Returns the sessionId. Creates a new session if none exists.
45
+ * Concurrent callers get the same promise (dedup).
46
+ */
47
+ ensureSession(address) {
48
+ const state = this.sessions.get(address);
49
+ // Active session — return immediately
50
+ if (state?.status === "active" && state.sessionId) {
51
+ return Promise.resolve(state.sessionId);
52
+ }
53
+ // Creating/recreating — dedup: return existing promise
54
+ if ((state?.status === "creating" || state?.status === "recreating") &&
55
+ state.createPromise) {
56
+ return state.createPromise;
57
+ }
58
+ // Idle, failed, or no state — create new session
59
+ return this.createNewSession(address, state?.retryCount ?? 0);
60
+ }
61
+ /**
62
+ * Get the active sessionId for an address, or null.
63
+ */
64
+ getSession(address) {
65
+ const state = this.sessions.get(address);
66
+ if (state?.status === "active" && state.sessionId) {
67
+ return state.sessionId;
68
+ }
69
+ return null;
70
+ }
71
+ /**
72
+ * Close the session for an address.
73
+ */
74
+ closeSession(address, reason) {
75
+ const state = this.sessions.get(address);
76
+ if (!state)
77
+ return;
78
+ if (state.sessionId) {
79
+ this.client.endSession(state.sessionId, reason);
80
+ this.sessionToAddress.delete(state.sessionId);
81
+ }
82
+ // Reject any pending create promise
83
+ if (state.createReject) {
84
+ state.createReject(new Error("Session closed by caller"));
85
+ state.createResolve = null;
86
+ state.createReject = null;
87
+ state.createPromise = null;
88
+ }
89
+ this.sessions.delete(address);
90
+ dbg("closeSession address=%s", address);
91
+ }
92
+ /**
93
+ * Register an inbound session (remote initiated).
94
+ */
95
+ registerInbound(sessionId, address) {
96
+ // If there's an existing session for this address, clean up
97
+ const existing = this.sessions.get(address);
98
+ if (existing?.sessionId && existing.sessionId !== sessionId) {
99
+ this.sessionToAddress.delete(existing.sessionId);
100
+ }
101
+ const state = {
102
+ status: "active",
103
+ sessionId,
104
+ pendingRequestId: null,
105
+ retryCount: 0,
106
+ createPromise: null,
107
+ createResolve: null,
108
+ createReject: null,
109
+ };
110
+ this.sessions.set(address, state);
111
+ this.sessionToAddress.set(sessionId, address);
112
+ dbg("registerInbound sessionId=%s address=%s", sessionId, address);
113
+ }
114
+ /**
115
+ * Invalidate all sessions. Called on transport disconnect.
116
+ * Conversations are preserved but sessions are cleared.
117
+ */
118
+ invalidateAll() {
119
+ const count = this.sessions.size;
120
+ for (const [address, state] of this.sessions) {
121
+ // Reject pending creates
122
+ if (state.createReject) {
123
+ state.createReject(new Error("Transport disconnected"));
124
+ state.createResolve = null;
125
+ state.createReject = null;
126
+ state.createPromise = null;
127
+ }
128
+ // Reset to idle — next send() will auto-create
129
+ state.status = "idle";
130
+ state.sessionId = null;
131
+ state.pendingRequestId = null;
132
+ state.retryCount = 0;
133
+ this.sessions.set(address, state);
134
+ }
135
+ this.sessionToAddress.clear();
136
+ dbg("invalidateAll cleared %d sessions", count);
137
+ }
138
+ /**
139
+ * Reverse lookup: sessionId -> MSTP address.
140
+ */
141
+ getAddressBySessionId(sessionId) {
142
+ return this.sessionToAddress.get(sessionId);
143
+ }
144
+ /**
145
+ * Get all session entries (for listing conversations).
146
+ */
147
+ listSessions() {
148
+ const entries = [];
149
+ for (const [address, state] of this.sessions) {
150
+ entries.push({
151
+ address,
152
+ status: state.status,
153
+ sessionId: state.sessionId,
154
+ });
155
+ }
156
+ return entries;
157
+ }
158
+ /**
159
+ * Remove a session entry entirely (for endConversation).
160
+ */
161
+ removeSession(address) {
162
+ const state = this.sessions.get(address);
163
+ if (state?.sessionId) {
164
+ this.sessionToAddress.delete(state.sessionId);
165
+ }
166
+ if (state?.createReject) {
167
+ state.createReject(new Error("Session removed"));
168
+ state.createResolve = null;
169
+ state.createReject = null;
170
+ state.createPromise = null;
171
+ }
172
+ this.sessions.delete(address);
173
+ }
174
+ /** @internal Override timeout for tests. */
175
+ _setTimeoutForTesting(ms) {
176
+ this.timeoutMs = ms;
177
+ }
178
+ /** @internal Reset for test isolation. */
179
+ _resetForTesting() {
180
+ // Reject pending creates silently
181
+ for (const state of this.sessions.values()) {
182
+ if (state.createReject) {
183
+ state.createReject(new Error("Reset for testing"));
184
+ state.createResolve = null;
185
+ state.createReject = null;
186
+ state.createPromise = null;
187
+ }
188
+ }
189
+ this.sessions.clear();
190
+ this.sessionToAddress.clear();
191
+ this.timeoutMs = SESSION_CREATE_TIMEOUT_MS;
192
+ }
193
+ // -------------------------------------------------------------------------
194
+ // Private
195
+ // -------------------------------------------------------------------------
196
+ subscribe() {
197
+ this.client.on("session_created", (event) => {
198
+ this.handleSessionCreated(event.sessionId, event.requestId, event.direction);
199
+ });
200
+ this.client.on("session_error", (sessionId, message) => {
201
+ this.handleSessionError(sessionId, message);
202
+ });
203
+ this.client.on("session_ended", (event) => {
204
+ this.handleSessionEnded(event.sessionId);
205
+ });
206
+ this.client.on("disconnected", () => {
207
+ this.invalidateAll();
208
+ });
209
+ }
210
+ createNewSession(address, previousRetryCount) {
211
+ const status = previousRetryCount > 0 ? "recreating" : "creating";
212
+ let createResolve = null;
213
+ let createReject = null;
214
+ const createPromise = new Promise((resolve, reject) => {
215
+ createResolve = resolve;
216
+ createReject = reject;
217
+ });
218
+ const { requestId, sent } = this.client.createSession(address);
219
+ if (!sent) {
220
+ const state = {
221
+ status: "failed",
222
+ sessionId: null,
223
+ pendingRequestId: null,
224
+ retryCount: previousRetryCount,
225
+ createPromise: null,
226
+ createResolve: null,
227
+ createReject: null,
228
+ };
229
+ this.sessions.set(address, state);
230
+ return Promise.reject(new Error("Failed to create session: transport unavailable"));
231
+ }
232
+ const state = {
233
+ status,
234
+ sessionId: null,
235
+ pendingRequestId: requestId,
236
+ retryCount: previousRetryCount,
237
+ createPromise,
238
+ createResolve,
239
+ createReject,
240
+ };
241
+ this.sessions.set(address, state);
242
+ // Timeout
243
+ const timer = setTimeout(() => {
244
+ const current = this.sessions.get(address);
245
+ if (current &&
246
+ current.pendingRequestId === requestId &&
247
+ (current.status === "creating" || current.status === "recreating")) {
248
+ dbg("session create timeout address=%s requestId=%s", address, requestId);
249
+ // Can we retry?
250
+ if (current.retryCount < MAX_RETRY_COUNT) {
251
+ // Move to failed, then retry
252
+ current.status = "failed";
253
+ current.retryCount++;
254
+ current.pendingRequestId = null;
255
+ const retryResolve = current.createResolve;
256
+ const retryReject = current.createReject;
257
+ current.createPromise = null;
258
+ current.createResolve = null;
259
+ current.createReject = null;
260
+ this.sessions.set(address, current);
261
+ // Retry: create a new session, pipe result to original promise
262
+ this.createNewSession(address, current.retryCount)
263
+ .then((sessionId) => retryResolve?.(sessionId))
264
+ .catch((err) => retryReject?.(err));
265
+ }
266
+ else {
267
+ // Max retries exhausted
268
+ current.status = "idle";
269
+ current.pendingRequestId = null;
270
+ current.retryCount = 0;
271
+ if (current.createReject) {
272
+ current.createReject(new Error("Session creation timed out. The remote agent may be unavailable."));
273
+ }
274
+ current.createPromise = null;
275
+ current.createResolve = null;
276
+ current.createReject = null;
277
+ this.sessions.set(address, current);
278
+ }
279
+ }
280
+ }, this.timeoutMs);
281
+ // Attach cleanup to the promise to clear the timer if resolved early
282
+ createPromise
283
+ .then(() => clearTimeout(timer))
284
+ .catch(() => clearTimeout(timer));
285
+ dbg("createNewSession address=%s requestId=%s status=%s retry=%d", address, requestId, status, previousRetryCount);
286
+ return createPromise;
287
+ }
288
+ handleSessionCreated(sessionId, requestId, direction) {
289
+ // Outbound: match by requestId
290
+ if (direction === "outbound" && requestId) {
291
+ for (const [address, state] of this.sessions) {
292
+ if (state.pendingRequestId === requestId &&
293
+ (state.status === "creating" || state.status === "recreating")) {
294
+ state.status = "active";
295
+ state.sessionId = sessionId;
296
+ state.pendingRequestId = null;
297
+ this.sessionToAddress.set(sessionId, address);
298
+ if (state.createResolve) {
299
+ state.createResolve(sessionId);
300
+ }
301
+ state.createPromise = null;
302
+ state.createResolve = null;
303
+ state.createReject = null;
304
+ dbg("session created (outbound) address=%s sessionId=%s", address, sessionId);
305
+ return;
306
+ }
307
+ }
308
+ }
309
+ // Inbound sessions are registered externally via registerInbound()
310
+ // from the channel adapter (which has the metadata to derive the address).
311
+ // We don't handle inbound SESSION_CREATED here.
312
+ }
313
+ handleSessionEnded(sessionId) {
314
+ const address = this.sessionToAddress.get(sessionId);
315
+ if (!address)
316
+ return;
317
+ const state = this.sessions.get(address);
318
+ if (!state)
319
+ return;
320
+ // Only clear if this session is the current one
321
+ if (state.sessionId === sessionId) {
322
+ state.status = "idle";
323
+ state.sessionId = null;
324
+ dbg("session ended address=%s sessionId=%s", address, sessionId);
325
+ }
326
+ this.sessionToAddress.delete(sessionId);
327
+ }
328
+ handleSessionError(sessionId, message) {
329
+ const address = this.sessionToAddress.get(sessionId);
330
+ if (!address)
331
+ return;
332
+ const state = this.sessions.get(address);
333
+ if (!state)
334
+ return;
335
+ dbg("session error address=%s sessionId=%s message=%s", address, sessionId, message);
336
+ // Session error on active session — mark for auto-recreate on next send().
337
+ // Reset retryCount to 0: this is a new failure context (transport error),
338
+ // not a continuation of a creation timeout. The next ensureSession() gets
339
+ // a fresh retry budget.
340
+ if (state.status === "active") {
341
+ this.sessionToAddress.delete(sessionId);
342
+ state.status = "idle";
343
+ state.sessionId = null;
344
+ state.retryCount = 0;
345
+ dbg("auto-recreate queued address=%s", address);
346
+ }
347
+ }
348
+ }
package/dist/tools.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * LLM tools — setup, connection, profile, and conversation tools.
3
3
  *
4
- * Registers 12 tools with OpenClaw's Plugin API so the LLM can
4
+ * Registers tools with OpenClaw's Plugin API so the LLM can
5
5
  * drive setup, profile completion, connection listing, connection
6
6
  * requests, request management, and real-time conversations,
7
7
  * guided by SKILL.md.
@@ -9,9 +9,14 @@
9
9
  * Two access patterns:
10
10
  * - **HTTP tools** (setup, connection): read config via `requirePlatformConfig()`,
11
11
  * call Platform API via `platform-client.ts`.
12
- * - **WebSocket tools** (conversation): read runtime client via
13
- * `requireConnectorClient()`, send/receive over the live connection.
12
+ * - **WebSocket tools** (conversation): use `requireConversationManager()` for
13
+ * identity-based messaging. Session management is fully transparent.
14
14
  *
15
+ * Session Abstraction (#741):
16
+ * - `masons_send_message(to, content)` — sends via ConversationManager
17
+ * - `masons_end_conversation(contact)` — ends via ConversationManager
18
+ * - `masons_create_session` — DEPRECATED SHIM (one release cycle)
19
+ * - `masons_end_session` — DEPRECATED SHIM (one release cycle)
15
20
  */
16
21
  interface ToolContent {
17
22
  content: Array<{
@@ -32,8 +37,6 @@ interface ToolApi {
32
37
  }
33
38
  /** @internal Reset module state for test isolation. */
34
39
  export declare function _resetToolsForTesting(): void;
35
- /** @internal Override session creation timeout for tests. */
36
- export declare function _setSessionTimeoutForTesting(ms: number): void;
37
40
  /**
38
41
  * Register agent network tools with the OpenClaw Plugin API.
39
42
  *
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAqCH,UAAU,WAAW;IACnB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,CACP,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,WAAW,CAAC,CAAC;CAC3B;AAED,UAAU,OAAO;IACf,YAAY,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CACzE;AAmBD,uDAAuD;AACvD,wBAAgB,qBAAqB,IAAI,IAAI,CAI5C;AAED,6DAA6D;AAC7D,wBAAgB,4BAA4B,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE7D;AAwJD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAurBhD"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAmCH,UAAU,WAAW;IACnB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,CACP,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,WAAW,CAAC,CAAC;CAC3B;AAED,UAAU,OAAO;IACf,YAAY,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CACzE;AAeD,uDAAuD;AACvD,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C;AAwFD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAqsBhD"}