@nexrall/code-core 1.4.59 → 1.4.60

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,348 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports._peerTransportInternal = void 0;
37
+ exports.socketPathFor = socketPathFor;
38
+ exports.startPeerInbox = startPeerInbox;
39
+ exports.sendPeerMessage = sendPeerMessage;
40
+ exports.broadcastPeerMessage = broadcastPeerMessage;
41
+ /**
42
+ * Peer transport — the actual message delivery between two independent
43
+ * sessions on the same machine, once peerRegistry.ts has let them discover
44
+ * each other. Mirrors Claude Code's cross-session messaging transport:
45
+ * same-machine delivery goes over a private Unix domain socket per session,
46
+ * never through any server — "Message content is not sent to Anthropic
47
+ * unless the sessions are on different machines."
48
+ *
49
+ * ONE ENDPOINT PER SESSION, not a shared broker: `~/.nexrall/peers/<id>.sock`
50
+ * on macOS/Linux (mode 0600, owner-only), `\\.\pipe\nexrall-peer-<id>` on
51
+ * Windows (see socketPathFor's own doc comment for why the two need
52
+ * different safety-check strategies). A sender connects directly to the
53
+ * RECIPIENT's endpoint, writes one newline-terminated JSON message, and
54
+ * disconnects — no persistent connection, no broker process to keep alive
55
+ * or crash-recover.
56
+ *
57
+ * SECURITY — this is genuinely adversarial territory, not decoration
58
+ * -------------------------------------------------------------------
59
+ * A peer message is the OUTPUT OF ANOTHER LLM CALL, running under whatever
60
+ * prompted THAT session — treat it as untrusted exactly like fetched web
61
+ * content (see this repo's own prompt-injection-defense conventions). This
62
+ * module's job is to make the TRANSPORT itself hard to abuse; the CONTENT
63
+ * still has to be handled as untrusted data by whoever renders/acts on it
64
+ * (see loop.ts's renderPeerMessage, once that's wired in Priority 4c/4d):
65
+ * - Socket file must not be a SYMLINK (planting a symlink at the expected
66
+ * socket path pointing somewhere else is the exact class of bypass
67
+ * worktreeEnforcement.ts's git-redirect checks guard against for a
68
+ * different resource — same principle, applied here to sockets).
69
+ * - Socket file must be owned by the SAME uid as the connecting process
70
+ * (`fs.statSync(...).uid === process.getuid()`) — refuses connecting to
71
+ * a socket some other user on a shared machine planted at a guessed path.
72
+ * - Message size is capped (MAX_MESSAGE_BYTES) so one peer can't wedge
73
+ * another's inbox or blow its context window with a single message.
74
+ * - A message NEVER carries conversation history or file contents —
75
+ * plain text only, matching Claude Code's own "no context, just
76
+ * text" design. Context-sharing is what `--resume`/session resume is
77
+ * for, not this channel.
78
+ * - This module does not interpret message text as a command — no
79
+ * slash-command parsing, no execution. It is the receiving session's
80
+ * `renderPeerMessage` (loop.ts side, per this module's own doc header
81
+ * rationale) that must keep it fenced as untrusted data, never as an
82
+ * instruction with the same authority as the actual user.
83
+ */
84
+ const fs = __importStar(require("fs"));
85
+ const os = __importStar(require("os"));
86
+ const path = __importStar(require("path"));
87
+ const net = __importStar(require("net"));
88
+ const MAX_MESSAGE_BYTES = 8 * 1024; // 8 KB — a coordination ping, not a payload channel
89
+ const CONNECT_TIMEOUT_MS = 3000;
90
+ const isWindows = process.platform === 'win32';
91
+ // `struct sockaddr_un`'s `sun_path` field — what a Unix domain socket path
92
+ // actually gets copied into at the OS level — is a FIXED-SIZE buffer: 104
93
+ // bytes on macOS/BSD, 108 on Linux. A path longer than that makes
94
+ // `listen()`/`connect()` fail with EINVAL, and — this is the real bug this
95
+ // constant exists to prevent, caught by a genuinely failing test, not
96
+ // invented defensively — Node's `net.Server` reports that failure ONLY via
97
+ // its `'error'` event, which this module's `startPeerInbox` used to just
98
+ // swallow (`server.on('error', () => {/* best-effort */})`), so the peer
99
+ // would look successfully registered (a socketPath string was returned)
100
+ // while silently never being able to receive a single message. A deep home
101
+ // directory (`~/.nexrall/peers/<16-hex-char-id>.sock`) is enough to exceed
102
+ // this in practice, not just in a test harness using a long os.tmpdir()
103
+ // path — so this is a real production risk, not merely a test artifact.
104
+ // Kept conservatively below the tighter of the two OS limits.
105
+ const MAX_SOCKET_PATH_LENGTH = 100;
106
+ function preferredSocketDir() {
107
+ return process.env.NEXRALL_PEERS_DIR || path.join(os.homedir(), '.nexrall', 'peers');
108
+ }
109
+ /**
110
+ * Fallback used ONLY when preferredSocketDir()'s natural path would exceed
111
+ * MAX_SOCKET_PATH_LENGTH. `/tmp` is short and present on essentially every
112
+ * POSIX system — the same fallback other Unix-socket-using tools facing
113
+ * this identical `sockaddr_un` constraint use (tmux, docker compose, etc).
114
+ * Peer ids are random enough (16 hex chars, minted by registerPeer) that
115
+ * collisions between unrelated sessions sharing this flat fallback
116
+ * namespace are not a practical concern.
117
+ */
118
+ function fallbackSocketDir() {
119
+ return '/tmp';
120
+ }
121
+ /**
122
+ * Where this peer's transport endpoint lives.
123
+ *
124
+ * macOS/Linux: a real file at `~/.nexrall/peers/<id>.sock` — a Unix domain
125
+ * socket, backed by an actual filesystem entry `verifySocketSafety` can
126
+ * `lstat` and validate — UNLESS that path would exceed
127
+ * MAX_SOCKET_PATH_LENGTH, in which case fallbackSocketDir() is used instead
128
+ * (see its own doc comment).
129
+ *
130
+ * Windows: `\\.\pipe\nexrall-peer-<id>` — a NAMED PIPE, which lives in the
131
+ * NT kernel object namespace, NOT the regular NTFS filesystem tree. There is
132
+ * no directory to create and no on-disk file for `fs.lstatSync` to inspect
133
+ * (see verifySocketSafety's Windows branch for what this means for the
134
+ * safety checks), and no `sockaddr_un`-style length limit either — Windows
135
+ * named pipe paths have a much larger practical limit, so no fallback logic
136
+ * applies there. `net.createServer().listen(path)` / `net.createConnection
137
+ * (path)` both accept this exact path form natively — no separate Windows
138
+ * API surface needed, Node's own `net` module dispatches to named pipes
139
+ * automatically when the path matches this pattern.
140
+ */
141
+ function socketPathFor(peerId) {
142
+ if (isWindows)
143
+ return `\\\\.\\pipe\\nexrall-peer-${peerId}`;
144
+ const preferred = path.join(preferredSocketDir(), `${peerId}.sock`);
145
+ if (preferred.length <= MAX_SOCKET_PATH_LENGTH)
146
+ return preferred;
147
+ return path.join(fallbackSocketDir(), `nexrall-peer-${peerId}.sock`);
148
+ }
149
+ /**
150
+ * Validate that `socketPath` is safe to connect to BEFORE connecting —
151
+ * see this module's header for exactly which bypass this closes. Returns an
152
+ * error string on failure, null when safe to proceed.
153
+ *
154
+ * WINDOWS CAVEAT: named pipes are kernel objects, not filesystem entries, so
155
+ * `fs.lstatSync` (what the symlink/uid checks below are built on) does not
156
+ * apply to them — there is no on-disk path component for a symlink to spoof
157
+ * in the first place, so that specific attack class does not carry over.
158
+ * The uid-ownership check has no direct equivalent from plain Node `net`
159
+ * either (that would require reading the pipe's Windows security descriptor
160
+ * via a native call, which this module deliberately does not add). The
161
+ * actual safety boundary on Windows is therefore the OS's own named-pipe
162
+ * ACL (set by whoever created it, defaulting to the creating user + admins)
163
+ * plus the connection attempt itself failing cleanly (ENOENT/EACCES) for a
164
+ * pipe that does not exist or is not accessible — handled by sendPeerMessage's
165
+ * own error path, not by a pre-check here. This is a genuinely weaker
166
+ * guarantee than the Unix path's explicit checks, called out here rather
167
+ * than silently assumed equivalent.
168
+ */
169
+ function verifySocketSafety(socketPath) {
170
+ if (isWindows)
171
+ return null; // see this function's own doc comment for why
172
+ let st;
173
+ try {
174
+ st = fs.lstatSync(socketPath); // lstat, NOT stat — must inspect the link itself, not what it points to
175
+ }
176
+ catch {
177
+ return 'socket does not exist (peer may have exited)';
178
+ }
179
+ if (st.isSymbolicLink()) {
180
+ return 'refusing to connect: peer socket path is a symlink (possible spoofing attempt)';
181
+ }
182
+ if (!st.isSocket()) {
183
+ return 'refusing to connect: peer socket path is not actually a socket';
184
+ }
185
+ if (typeof process.getuid === 'function' && st.uid !== process.getuid()) {
186
+ return 'refusing to connect: peer socket is owned by a different user';
187
+ }
188
+ return null;
189
+ }
190
+ /**
191
+ * Start listening for incoming peer messages on this session's own
192
+ * socket/pipe. Unix domain socket on macOS/Linux, named pipe on Windows —
193
+ * see socketPathFor's own doc comment for the platform split. Not tested
194
+ * against a real Windows machine (dev/CI for this repo runs macOS/Linux);
195
+ * the implementation follows Node's own documented cross-platform `net`
196
+ * behavior for named-pipe paths, but treat the Windows path as less
197
+ * battle-tested than the Unix one until it has run on real Windows.
198
+ */
199
+ function startPeerInbox(peerId, opts) {
200
+ const socketPath = socketPathFor(peerId);
201
+ if (!isWindows) {
202
+ fs.mkdirSync(path.dirname(socketPath), { recursive: true });
203
+ // A stale socket file from a previous run of this exact peerId (extremely
204
+ // unlikely — ids are random — but a crash could theoretically leave one)
205
+ // would make `server.listen` fail with EADDRINUSE. Safe to remove
206
+ // unconditionally: peerId is freshly minted per registerPeer() call, so
207
+ // nothing legitimate could still be listening on it.
208
+ try {
209
+ fs.unlinkSync(socketPath);
210
+ }
211
+ catch { /* did not exist */ }
212
+ }
213
+ // No directory/stale-file handling needed on Windows — named pipes have no
214
+ // on-disk path component (see socketPathFor's doc comment), and a stale
215
+ // pipe from a previous instance of this exact random peerId is exactly as
216
+ // vanishingly unlikely there as the Unix case above.
217
+ const server = net.createServer((conn) => {
218
+ let buf = '';
219
+ conn.on('data', (chunk) => {
220
+ buf += chunk.toString('utf-8');
221
+ if (buf.length > MAX_MESSAGE_BYTES * 2) {
222
+ // A sender ignoring MAX_MESSAGE_BYTES (or trying to flood) gets cut
223
+ // off rather than let this session buffer unboundedly.
224
+ conn.destroy();
225
+ return;
226
+ }
227
+ const nl = buf.indexOf('\n');
228
+ if (nl === -1)
229
+ return;
230
+ const line = buf.slice(0, nl);
231
+ buf = '';
232
+ try {
233
+ const parsed = JSON.parse(line);
234
+ if (typeof parsed.text === 'string' && typeof parsed.from === 'string' && typeof parsed.fromId === 'string') {
235
+ opts.onMessage({
236
+ from: parsed.from,
237
+ fromId: parsed.fromId,
238
+ text: parsed.text.slice(0, MAX_MESSAGE_BYTES),
239
+ sentAt: typeof parsed.sentAt === 'number' ? parsed.sentAt : Date.now(),
240
+ });
241
+ }
242
+ }
243
+ catch { /* malformed message — drop silently, same as a corrupt network packet */ }
244
+ conn.end();
245
+ });
246
+ conn.on('error', () => { });
247
+ });
248
+ server.listen(socketPath, () => {
249
+ if (isWindows)
250
+ return; // chmod has no meaning for a named pipe — its access control is the OS's own pipe DACL, not POSIX file mode.
251
+ // Owner-only permissions — a message channel is exactly the kind of
252
+ // resource that must not be readable/writable by other users on a
253
+ // shared machine.
254
+ try {
255
+ fs.chmodSync(socketPath, 0o600);
256
+ }
257
+ catch { /* best-effort on platforms without POSIX chmod semantics */ }
258
+ });
259
+ // See PeerInboxOptions.onError's own comment for why this used to be a
260
+ // pure no-op (`() => {}`) and why that was a real bug, not a deliberate
261
+ // simplification: `listen()` failing (EINVAL for an over-length path,
262
+ // EADDRINUSE, EACCES, ...) used to be indistinguishable from success from
263
+ // this function's return value alone.
264
+ server.on('error', (err) => { opts.onError?.(err instanceof Error ? err : new Error(String(err))); });
265
+ return {
266
+ socketPath,
267
+ close() {
268
+ // `server.close()` itself unlinks the Unix socket file it created via
269
+ // `listen(path)` — verified directly (Node does this internally, not
270
+ // merely "documented and trusted"); the explicit unlinkSync below is
271
+ // therefore belt-and-suspenders for the case where close() fires
272
+ // asynchronously and a caller inspects the filesystem before that
273
+ // callback runs, not a correction of a leak. On Windows there is no
274
+ // socket FILE at all (see socketPathFor's doc comment — a named pipe
275
+ // is a kernel object), so this unlink is always a harmless no-op
276
+ // there (ENOENT, swallowed) rather than something that needs its own
277
+ // platform branch.
278
+ server.close();
279
+ try {
280
+ fs.unlinkSync(socketPath);
281
+ }
282
+ catch { /* already gone, or never existed (Windows) */ }
283
+ },
284
+ };
285
+ }
286
+ /**
287
+ * Send a message to a peer at `targetSocketPath`. Fire-and-forget from the
288
+ * caller's perspective (resolves once the write completes or fails) — there
289
+ * is no delivery receipt, matching Claude Code's own "best effort" framing
290
+ * for cross-session messaging.
291
+ */
292
+ function sendPeerMessage(targetSocketPath, message) {
293
+ return new Promise((resolve) => {
294
+ const safetyError = verifySocketSafety(targetSocketPath);
295
+ if (safetyError) {
296
+ resolve({ ok: false, error: safetyError });
297
+ return;
298
+ }
299
+ const payload = { ...message, text: message.text.slice(0, MAX_MESSAGE_BYTES), sentAt: Date.now() };
300
+ const line = JSON.stringify(payload) + '\n';
301
+ if (Buffer.byteLength(line, 'utf-8') > MAX_MESSAGE_BYTES * 2) {
302
+ resolve({ ok: false, error: `message too large (max ${MAX_MESSAGE_BYTES} bytes)` });
303
+ return;
304
+ }
305
+ const socket = net.createConnection(targetSocketPath);
306
+ const timeout = setTimeout(() => {
307
+ socket.destroy();
308
+ resolve({ ok: false, error: 'timed out connecting to peer — it may be busy or have just exited' });
309
+ }, CONNECT_TIMEOUT_MS);
310
+ socket.on('connect', () => {
311
+ socket.write(line, (err) => {
312
+ clearTimeout(timeout);
313
+ if (err) {
314
+ resolve({ ok: false, error: err.message });
315
+ return;
316
+ }
317
+ socket.end();
318
+ resolve({ ok: true });
319
+ });
320
+ });
321
+ socket.on('error', (err) => {
322
+ clearTimeout(timeout);
323
+ resolve({ ok: false, error: err.message });
324
+ });
325
+ });
326
+ }
327
+ /**
328
+ * Send the SAME message to every target in `targets`, in parallel. Each
329
+ * target gets its own independent sendPeerMessage call — one unreachable or
330
+ * slow peer cannot block or fail delivery to the others (no partial-failure
331
+ * cascade, no head-of-line blocking on a single slow socket).
332
+ *
333
+ * Deliberately a thin fan-out over the existing single-target
334
+ * sendPeerMessage rather than a new wire-level primitive: broadcast is a
335
+ * CALLER-SIDE concept (send to N addresses), not a transport-level one —
336
+ * there is no multicast socket underneath this, just N ordinary point-to-
337
+ * point sends, which keeps every security check (verifySocketSafety, size
338
+ * caps) shared with the single-message path instead of duplicated.
339
+ */
340
+ async function broadcastPeerMessage(targets, message) {
341
+ const results = await Promise.all(targets.map(async (t) => {
342
+ const r = await sendPeerMessage(t.socketPath, message);
343
+ return { name: t.name, ok: r.ok, error: r.error };
344
+ }));
345
+ return results;
346
+ }
347
+ exports._peerTransportInternal = { verifySocketSafety, MAX_MESSAGE_BYTES, CONNECT_TIMEOUT_MS, isWindows };
348
+ //# sourceMappingURL=peerTransport.js.map
@@ -0,0 +1,63 @@
1
+ export type TaskStatus = 'pending' | 'in_progress' | 'completed' | 'blocked';
2
+ export interface SharedTask {
3
+ id: string;
4
+ content: string;
5
+ status: TaskStatus;
6
+ /** Session name/id that currently owns this task (see peer registry, once that lands) — undefined for pending/completed. */
7
+ claimedBy?: string;
8
+ /** pid of the process that claimed it — used for the liveness check, same idea as crossProcessLock's stale-lock detection. */
9
+ claimedByPid?: number;
10
+ /** IDs of OTHER tasks in this same list that must be `completed` before this one is claimable. */
11
+ blockedBy?: string[];
12
+ createdAt: number;
13
+ updatedAt: number;
14
+ claimedAt?: number;
15
+ }
16
+ declare function tasksRoot(): string;
17
+ declare function scopeDir(workDir: string): string;
18
+ declare function taskPath(workDir: string, taskId: string): string;
19
+ /** A claimed task whose owning process is gone, or whose claim has gone stale with no update, is treated as abandoned. */
20
+ declare function isClaimAbandoned(task: SharedTask): boolean;
21
+ /** Whether every task ID in `blockedBy` is `completed` — i.e. this task is actually claimable right now. */
22
+ declare function isUnblocked(workDir: string, task: SharedTask): boolean;
23
+ /** List every task registered for `workDir`, newest first isn't guaranteed — sort by createdAt if display order matters to the caller. */
24
+ export declare function listSharedTasks(workDir: string): SharedTask[];
25
+ /** Create a new pending task. `id` auto-generated (short random) unless the caller supplies a stable one (e.g. re-registering from a static plan). */
26
+ export declare function createSharedTask(workDir: string, content: string, opts?: {
27
+ id?: string;
28
+ blockedBy?: string[];
29
+ }): SharedTask;
30
+ export type ClaimResult = {
31
+ ok: true;
32
+ task: SharedTask;
33
+ } | {
34
+ ok: false;
35
+ reason: 'not-found' | 'already-claimed' | 'blocked' | 'not-pending';
36
+ };
37
+ /**
38
+ * Atomically claim `taskId` for `claimedBy` (a peer/session name). Race-free
39
+ * against another process calling this at the same instant — see this
40
+ * module's header comment for exactly why.
41
+ */
42
+ export declare function claimTask(workDir: string, taskId: string, claimedBy: string): Promise<ClaimResult>;
43
+ /**
44
+ * Update a task's status. Used for both normal progress (in_progress →
45
+ * completed) and releasing a claim early (in_progress → pending, e.g. a
46
+ * session that claimed a task then decided not to do it after all). Also
47
+ * lock-guarded — a status update racing a claim attempt must not corrupt
48
+ * either operation's view of the task.
49
+ */
50
+ export declare function updateSharedTaskStatus(workDir: string, taskId: string, status: TaskStatus, opts?: {
51
+ claimedBy?: string;
52
+ }): Promise<ClaimResult>;
53
+ export declare const _sharedTasksInternal: {
54
+ tasksRoot: typeof tasksRoot;
55
+ scopeDir: typeof scopeDir;
56
+ taskPath: typeof taskPath;
57
+ isClaimAbandoned: typeof isClaimAbandoned;
58
+ isUnblocked: typeof isUnblocked;
59
+ CLAIM_STALE_MS: number;
60
+ MIN_CLAIM_AGE_FOR_DEATH_CHECK_MS: number;
61
+ };
62
+ export {};
63
+ //# sourceMappingURL=sharedTasks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sharedTasks.d.ts","sourceRoot":"","sources":["../../src/agent/sharedTasks.ts"],"names":[],"mappings":"AA6DA,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,GAAG,SAAS,CAAC;AAE7E,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,CAAC;IACnB,4HAA4H;IAC5H,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8HAA8H;IAC9H,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kGAAkG;IAClG,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA0BD,iBAAS,SAAS,IAAI,MAAM,CAE3B;AAED,iBAAS,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGzC;AAED,iBAAS,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEzD;AA4BD,0HAA0H;AAC1H,iBAAS,gBAAgB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAUnD;AAED,4GAA4G;AAC5G,iBAAS,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAG/D;AAED,0IAA0I;AAC1I,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAW7D;AAED,sJAAsJ;AACtJ,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GAC/C,UAAU,CAUZ;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GAC9B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,iBAAiB,GAAG,SAAS,GAAG,aAAa,CAAA;CAAE,CAAC;AAEvF;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA+BxG;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACrF,OAAO,CAAC,WAAW,CAAC,CAoBtB;AAED,eAAO,MAAM,oBAAoB;;;;;;;;CAAqH,CAAC"}