wyrm-mcp 4.0.0 → 5.1.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,72 @@
1
+ /**
2
+ * Agent daemon process manager.
3
+ *
4
+ * Lets an AI client (Claude / Codex / Cursor / etc.) bootstrap, monitor,
5
+ * and tear down the `wyrm-loop` autonomous scheduler without the
6
+ * operator running shell commands. The MCP server can spawn the daemon
7
+ * as a detached background process, write a PID + log file under
8
+ * `~/.wyrm/`, and check liveness on demand.
9
+ *
10
+ * Files (all under `~/.wyrm/`):
11
+ * wyrm-loop.pid PID of the running daemon (single-line)
12
+ * wyrm-loop.log Stdout/stderr of the daemon (rotated at 1MB)
13
+ *
14
+ * Single-instance guarantee — if a PID file exists and the process is
15
+ * alive, `start()` refuses (no fork-bomb). `restart()` is provided as
16
+ * an explicit operation.
17
+ *
18
+ * @copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
19
+ * @license Proprietary
20
+ */
21
+ import type Database from 'better-sqlite3';
22
+ export interface DaemonStatus {
23
+ running: boolean;
24
+ pid: number | null;
25
+ pid_file: string;
26
+ log_file: string;
27
+ started_at?: string;
28
+ uptime_seconds?: number;
29
+ last_action?: {
30
+ ran_at: string;
31
+ actor: string;
32
+ goal_id: number | null;
33
+ summary: string;
34
+ result_status: string | null;
35
+ };
36
+ active_goals: number;
37
+ total_iterations: number;
38
+ }
39
+ export interface StartOptions {
40
+ interval_seconds?: number;
41
+ max_steps?: number;
42
+ project_path?: string;
43
+ verbose?: boolean;
44
+ }
45
+ export interface StartResult {
46
+ ok: boolean;
47
+ pid?: number;
48
+ status?: DaemonStatus;
49
+ error?: string;
50
+ }
51
+ export declare class AgentDaemon {
52
+ private db;
53
+ constructor(db: Database.Database);
54
+ /** Current daemon status. Always safe to call. */
55
+ status(): DaemonStatus;
56
+ /** Start the daemon. No-op if already running. */
57
+ start(opts?: StartOptions): StartResult;
58
+ /** Stop the daemon. Sends SIGTERM, then SIGKILL after a grace period. */
59
+ stop(opts?: {
60
+ grace_ms?: number;
61
+ }): Promise<{
62
+ ok: boolean;
63
+ was_running: boolean;
64
+ pid?: number;
65
+ error?: string;
66
+ }>;
67
+ /** Stop + Start in sequence. */
68
+ restart(opts?: StartOptions): Promise<StartResult>;
69
+ /** Read the last N lines of the daemon log. */
70
+ recentLog(lines?: number): string;
71
+ }
72
+ //# sourceMappingURL=agent-daemon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-daemon.d.ts","sourceRoot":"","sources":["../src/agent-daemon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAOH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,OAAO,EAAE,MAAM,CAAC;QAChB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;KAC9B,CAAC;IACF,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqED,qBAAa,WAAW;IACV,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,QAAQ,CAAC,QAAQ;IAEzC,kDAAkD;IAClD,MAAM,IAAI,YAAY;IAuDtB,kDAAkD;IAClD,KAAK,CAAC,IAAI,GAAE,YAAiB,GAAG,WAAW;IA8D3C,yEAAyE;IACnE,IAAI,CAAC,IAAI,GAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAgC1H,gCAAgC;IAC1B,OAAO,CAAC,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC,WAAW,CAAC;IAK5D,+CAA+C;IAC/C,SAAS,CAAC,KAAK,SAAK,GAAG,MAAM;CAU9B"}
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Agent daemon process manager.
3
+ *
4
+ * Lets an AI client (Claude / Codex / Cursor / etc.) bootstrap, monitor,
5
+ * and tear down the `wyrm-loop` autonomous scheduler without the
6
+ * operator running shell commands. The MCP server can spawn the daemon
7
+ * as a detached background process, write a PID + log file under
8
+ * `~/.wyrm/`, and check liveness on demand.
9
+ *
10
+ * Files (all under `~/.wyrm/`):
11
+ * wyrm-loop.pid PID of the running daemon (single-line)
12
+ * wyrm-loop.log Stdout/stderr of the daemon (rotated at 1MB)
13
+ *
14
+ * Single-instance guarantee — if a PID file exists and the process is
15
+ * alive, `start()` refuses (no fork-bomb). `restart()` is provided as
16
+ * an explicit operation.
17
+ *
18
+ * @copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
19
+ * @license Proprietary
20
+ */
21
+ import { spawn } from 'child_process';
22
+ import { existsSync, readFileSync, writeFileSync, statSync, openSync, renameSync, unlinkSync, mkdirSync } from 'fs';
23
+ import { homedir } from 'os';
24
+ import { join, dirname, resolve } from 'path';
25
+ import { fileURLToPath } from 'url';
26
+ const WYRM_DIR = join(homedir(), '.wyrm');
27
+ const PID_FILE = join(WYRM_DIR, 'wyrm-loop.pid');
28
+ const LOG_FILE = join(WYRM_DIR, 'wyrm-loop.log');
29
+ const LOG_FILE_OLD = join(WYRM_DIR, 'wyrm-loop.log.1');
30
+ const MAX_LOG_BYTES = 1_000_000;
31
+ function ensureWyrmDir() {
32
+ if (!existsSync(WYRM_DIR))
33
+ mkdirSync(WYRM_DIR, { recursive: true });
34
+ }
35
+ function rotateLogIfLarge() {
36
+ try {
37
+ if (existsSync(LOG_FILE) && statSync(LOG_FILE).size > MAX_LOG_BYTES) {
38
+ if (existsSync(LOG_FILE_OLD))
39
+ unlinkSync(LOG_FILE_OLD);
40
+ renameSync(LOG_FILE, LOG_FILE_OLD);
41
+ }
42
+ }
43
+ catch { /* best-effort */ }
44
+ }
45
+ function readPid() {
46
+ try {
47
+ const raw = readFileSync(PID_FILE, 'utf-8').trim();
48
+ const pid = parseInt(raw, 10);
49
+ return Number.isFinite(pid) && pid > 0 ? pid : null;
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ }
55
+ function isProcessAlive(pid) {
56
+ try {
57
+ // signal 0 doesn't kill; just checks existence + permission
58
+ process.kill(pid, 0);
59
+ return true;
60
+ }
61
+ catch {
62
+ return false;
63
+ }
64
+ }
65
+ /** Find the bundled `wyrm-loop` script path. Resolves relative to this
66
+ * module so it works whether installed via npm, run from source, or
67
+ * invoked from a global symlink. */
68
+ function locateLoopBinary() {
69
+ // 1. import.meta.url → packages/mcp-server/dist/agent-daemon.js
70
+ try {
71
+ const here = fileURLToPath(import.meta.url);
72
+ const candidates = [
73
+ // installed alongside (dist/wyrm-loop.js)
74
+ resolve(dirname(here), 'wyrm-loop.js'),
75
+ // source layout
76
+ resolve(dirname(here), '..', 'dist', 'wyrm-loop.js'),
77
+ ];
78
+ for (const p of candidates) {
79
+ if (existsSync(p))
80
+ return p;
81
+ }
82
+ }
83
+ catch { /* fall through */ }
84
+ // 2. Look in PATH for the `wyrm-loop` binary (set up by `npm install -g`)
85
+ const pathDirs = (process.env.PATH ?? '').split(':');
86
+ for (const dir of pathDirs) {
87
+ const cand = join(dir, 'wyrm-loop');
88
+ if (existsSync(cand))
89
+ return cand;
90
+ }
91
+ return null;
92
+ }
93
+ export class AgentDaemon {
94
+ db;
95
+ constructor(db) {
96
+ this.db = db;
97
+ }
98
+ /** Current daemon status. Always safe to call. */
99
+ status() {
100
+ const pid = readPid();
101
+ const running = pid != null && isProcessAlive(pid);
102
+ let started_at;
103
+ let uptime_seconds;
104
+ try {
105
+ if (existsSync(PID_FILE)) {
106
+ const st = statSync(PID_FILE);
107
+ started_at = st.mtime.toISOString();
108
+ uptime_seconds = Math.round((Date.now() - st.mtime.getTime()) / 1000);
109
+ }
110
+ }
111
+ catch { /* best-effort */ }
112
+ // Read latest agent_actions row for "last action" summary
113
+ let last_action;
114
+ try {
115
+ const row = this.db.prepare(`
116
+ SELECT actor, goal_id, action_kind, summary, result_status, ran_at
117
+ FROM agent_actions ORDER BY id DESC LIMIT 1
118
+ `).get();
119
+ if (row)
120
+ last_action = {
121
+ ran_at: row.ran_at, actor: row.actor, goal_id: row.goal_id,
122
+ summary: `${row.action_kind}: ${row.summary}`.slice(0, 200),
123
+ result_status: row.result_status,
124
+ };
125
+ }
126
+ catch { /* table may not exist on pre-v5 DBs */ }
127
+ let active_goals = 0;
128
+ let total_iterations = 0;
129
+ try {
130
+ active_goals = this.db.prepare(`SELECT COUNT(*) as c FROM goals WHERE status = 'active'`).get().c;
131
+ total_iterations = this.db.prepare(`SELECT COALESCE(SUM(iterations_count), 0) as c FROM goals`).get().c;
132
+ }
133
+ catch { /* pre-v5 DB */ }
134
+ return {
135
+ running,
136
+ pid: running ? pid : null,
137
+ pid_file: PID_FILE,
138
+ log_file: LOG_FILE,
139
+ started_at: running ? started_at : undefined,
140
+ uptime_seconds: running ? uptime_seconds : undefined,
141
+ last_action,
142
+ active_goals,
143
+ total_iterations,
144
+ };
145
+ }
146
+ /** Start the daemon. No-op if already running. */
147
+ start(opts = {}) {
148
+ ensureWyrmDir();
149
+ rotateLogIfLarge();
150
+ const cur = this.status();
151
+ if (cur.running) {
152
+ return { ok: true, pid: cur.pid ?? undefined, status: cur };
153
+ }
154
+ // Clear stale PID file from a crashed prior run
155
+ if (existsSync(PID_FILE)) {
156
+ try {
157
+ unlinkSync(PID_FILE);
158
+ }
159
+ catch { /* ignore */ }
160
+ }
161
+ const binary = locateLoopBinary();
162
+ if (!binary) {
163
+ return { ok: false, error: 'Could not locate wyrm-loop binary. Run `npm install -g wyrm-mcp` or `npm run build` in packages/mcp-server.' };
164
+ }
165
+ const args = ['--interval', String(opts.interval_seconds ?? 600)];
166
+ if (opts.max_steps)
167
+ args.push('--max-steps', String(opts.max_steps));
168
+ if (opts.project_path)
169
+ args.push('--project', opts.project_path);
170
+ if (opts.verbose)
171
+ args.push('--verbose');
172
+ // Open the log file once, share fd with the child
173
+ let logFd;
174
+ try {
175
+ logFd = openSync(LOG_FILE, 'a');
176
+ }
177
+ catch (err) {
178
+ return { ok: false, error: `Could not open log file ${LOG_FILE}: ${err.message}` };
179
+ }
180
+ let child;
181
+ try {
182
+ child = spawn('node', [binary, ...args], {
183
+ detached: true,
184
+ stdio: ['ignore', logFd, logFd],
185
+ env: { ...process.env, WYRM_LOOP_LOG: opts.verbose ? '1' : '0' },
186
+ });
187
+ }
188
+ catch (err) {
189
+ return { ok: false, error: `spawn failed: ${err.message}` };
190
+ }
191
+ if (!child.pid) {
192
+ return { ok: false, error: 'spawn returned no PID' };
193
+ }
194
+ try {
195
+ writeFileSync(PID_FILE, String(child.pid), 'utf-8');
196
+ }
197
+ catch (err) {
198
+ // Process is up but we can't track it — kill to keep state consistent
199
+ try {
200
+ process.kill(child.pid, 'SIGTERM');
201
+ }
202
+ catch { /* ignore */ }
203
+ return { ok: false, error: `Could not write PID file: ${err.message}` };
204
+ }
205
+ // unref so this process doesn't wait on the child
206
+ child.unref();
207
+ // Brief settle delay so status() reflects the new state
208
+ return { ok: true, pid: child.pid, status: this.status() };
209
+ }
210
+ /** Stop the daemon. Sends SIGTERM, then SIGKILL after a grace period. */
211
+ async stop(opts = {}) {
212
+ const pid = readPid();
213
+ if (pid == null) {
214
+ return { ok: true, was_running: false };
215
+ }
216
+ if (!isProcessAlive(pid)) {
217
+ // Stale PID file
218
+ try {
219
+ unlinkSync(PID_FILE);
220
+ }
221
+ catch { /* ignore */ }
222
+ return { ok: true, was_running: false, pid };
223
+ }
224
+ try {
225
+ process.kill(pid, 'SIGTERM');
226
+ }
227
+ catch (err) {
228
+ return { ok: false, was_running: true, pid, error: `SIGTERM failed: ${err.message}` };
229
+ }
230
+ const grace = Math.max(100, Math.min(opts.grace_ms ?? 3000, 30_000));
231
+ const start = Date.now();
232
+ while (Date.now() - start < grace) {
233
+ if (!isProcessAlive(pid))
234
+ break;
235
+ await new Promise(r => setTimeout(r, 100));
236
+ }
237
+ if (isProcessAlive(pid)) {
238
+ try {
239
+ process.kill(pid, 'SIGKILL');
240
+ }
241
+ catch { /* may already be dead */ }
242
+ }
243
+ try {
244
+ unlinkSync(PID_FILE);
245
+ }
246
+ catch { /* ignore */ }
247
+ return { ok: true, was_running: true, pid };
248
+ }
249
+ /** Stop + Start in sequence. */
250
+ async restart(opts = {}) {
251
+ await this.stop({ grace_ms: 3000 });
252
+ return this.start(opts);
253
+ }
254
+ /** Read the last N lines of the daemon log. */
255
+ recentLog(lines = 40) {
256
+ try {
257
+ if (!existsSync(LOG_FILE))
258
+ return '(log file does not exist)';
259
+ const raw = readFileSync(LOG_FILE, 'utf-8');
260
+ const all = raw.split('\n');
261
+ return all.slice(-Math.max(1, Math.min(lines, 1000))).join('\n');
262
+ }
263
+ catch (err) {
264
+ return `(could not read log: ${err.message})`;
265
+ }
266
+ }
267
+ }
268
+ //# sourceMappingURL=agent-daemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-daemon.js","sourceRoot":"","sources":["../src/agent-daemon.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Agent loop — OODA + ReAct (Observe → Orient → Decide → Act).
3
+ *
4
+ * This is the thing that turns Wyrm from "memory tool" into "agent".
5
+ * Given a goal (or ad-hoc query), we run a multi-turn loop where:
6
+ *
7
+ * 1. Observe — assemble relevant context from Wyrm's own data
8
+ * (failures, truths, sessions, symbols, prior iterations)
9
+ * 2. Orient — LLM synthesises: "given this context, what's the state?"
10
+ * 3. Decide — LLM proposes ONE action: either a Wyrm tool call,
11
+ * an external MCP server call, or 'done' / 'block' / 'escalate'
12
+ * 4. Act — execute the proposed action, capture result
13
+ * 5. Loop — feed result back into the next Observe step
14
+ *
15
+ * Each iteration logs to `goal_iterations` if attached to a goal.
16
+ * Hard caps: `max_iterations` per goal, ~60s wall clock per iteration,
17
+ * actions matched against a whitelist (no `wyrm_audit_export` or other
18
+ * data-exfiltration tools from inside the loop).
19
+ *
20
+ * LLM protocol — JSON envelope works with both:
21
+ * - Ollama JSON mode (most models 7B+)
22
+ * - OpenAI native function calling (gpt-4o, gpt-4o-mini, ...)
23
+ *
24
+ * The response shape we ask for:
25
+ * {"thought":"...","action":"<tool_name>","args":{...}} or
26
+ * {"thought":"...","action":"done","summary":"..."} or
27
+ * {"thought":"...","action":"block","reason":"..."}
28
+ *
29
+ * @copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
30
+ * @license Proprietary
31
+ */
32
+ import type Database from 'better-sqlite3';
33
+ import { OutboundMcpClient } from './mcp-client.js';
34
+ export interface AgentAction {
35
+ thought?: string;
36
+ action: string;
37
+ args?: Record<string, unknown>;
38
+ summary?: string;
39
+ reason?: string;
40
+ }
41
+ export interface IterationResult {
42
+ iteration_num: number;
43
+ observe_summary: string;
44
+ orient_summary: string;
45
+ decided: AgentAction;
46
+ action_result: string;
47
+ outcome: 'progressed' | 'blocked' | 'done' | 'error';
48
+ latency_ms: number;
49
+ tokens_in?: number;
50
+ tokens_out?: number;
51
+ model: string;
52
+ degraded: boolean;
53
+ }
54
+ export interface ToolDispatcher {
55
+ (toolName: string, args: Record<string, unknown>): Promise<{
56
+ ok: boolean;
57
+ result?: unknown;
58
+ error?: string;
59
+ }>;
60
+ }
61
+ export declare class AgentLoop {
62
+ private db;
63
+ private externalClient;
64
+ private internalToolDispatch;
65
+ private subAgent;
66
+ private goals;
67
+ private failures;
68
+ constructor(db: Database.Database, externalClient: OutboundMcpClient, internalToolDispatch: ToolDispatcher);
69
+ /** Run ONE OODA iteration on a goal. Returns the iteration result. */
70
+ iterate(goal_id: number, opts?: {
71
+ ollama_url?: string;
72
+ openai_api_key?: string;
73
+ model_override?: string;
74
+ }): Promise<IterationResult | null>;
75
+ /** Iterate until done / blocked / cap hit. Returns the array of iterations. */
76
+ pursue(goal_id: number, opts?: {
77
+ max_steps?: number;
78
+ ollama_url?: string;
79
+ openai_api_key?: string;
80
+ model_override?: string;
81
+ }): Promise<IterationResult[]>;
82
+ private observe;
83
+ private orientAndDecide;
84
+ private act;
85
+ }
86
+ //# sourceMappingURL=agent-loop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-loop.d.ts","sourceRoot":"","sources":["../src/agent-loop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAI3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAwBpD,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,WAAW,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,YAAY,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC/G;AAED,qBAAa,SAAS;IAMlB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,oBAAoB;IAP9B,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,QAAQ,CAAkB;gBAGxB,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,cAAc,EAAE,iBAAiB,EACjC,oBAAoB,EAAE,cAAc;IAO9C,sEAAsE;IAChE,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IA+EjJ,+EAA+E;IACzE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAgB/J,OAAO,CAAC,OAAO;YAwED,eAAe;YA0Ef,GAAG;CA4BlB"}