@edeng23/pines 0.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.
- package/LICENSE +202 -0
- package/README.md +37 -0
- package/dist/cli.js +83 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/ansi.js +37 -0
- package/dist/client/ansi.js.map +1 -0
- package/dist/client/app.js +1868 -0
- package/dist/client/app.js.map +1 -0
- package/dist/client/boot.js +221 -0
- package/dist/client/boot.js.map +1 -0
- package/dist/client/commands.js +133 -0
- package/dist/client/commands.js.map +1 -0
- package/dist/client/daemon-client.js +99 -0
- package/dist/client/daemon-client.js.map +1 -0
- package/dist/client/forest/camera.js +88 -0
- package/dist/client/forest/camera.js.map +1 -0
- package/dist/client/forest/canvas.js +175 -0
- package/dist/client/forest/canvas.js.map +1 -0
- package/dist/client/forest/sidebar.js +182 -0
- package/dist/client/forest/sidebar.js.map +1 -0
- package/dist/client/forest/status.js +39 -0
- package/dist/client/forest/status.js.map +1 -0
- package/dist/client/forest/style.js +70 -0
- package/dist/client/forest/style.js.map +1 -0
- package/dist/client/forest/styles.js +429 -0
- package/dist/client/forest/styles.js.map +1 -0
- package/dist/client/forest/view.js +181 -0
- package/dist/client/forest/view.js.map +1 -0
- package/dist/client/forest/wordmark.js +26 -0
- package/dist/client/forest/wordmark.js.map +1 -0
- package/dist/client/input.js +89 -0
- package/dist/client/input.js.map +1 -0
- package/dist/client/theme.js +27 -0
- package/dist/client/theme.js.map +1 -0
- package/dist/client/tree/asciitree.js +277 -0
- package/dist/client/tree/asciitree.js.map +1 -0
- package/dist/client/tree/convmap.js +260 -0
- package/dist/client/tree/convmap.js.map +1 -0
- package/dist/client/tree/lanegraph.js +260 -0
- package/dist/client/tree/lanegraph.js.map +1 -0
- package/dist/client/tree/merge.js +161 -0
- package/dist/client/tree/merge.js.map +1 -0
- package/dist/client/tree/sessionview.js +163 -0
- package/dist/client/tree/sessionview.js.map +1 -0
- package/dist/client/tree/view.js +238 -0
- package/dist/client/tree/view.js.map +1 -0
- package/dist/client/uistate.js +45 -0
- package/dist/client/uistate.js.map +1 -0
- package/dist/daemon/agents.js +102 -0
- package/dist/daemon/agents.js.map +1 -0
- package/dist/daemon/branch.js +149 -0
- package/dist/daemon/branch.js.map +1 -0
- package/dist/daemon/embed-worker.js +74 -0
- package/dist/daemon/embed-worker.js.map +1 -0
- package/dist/daemon/essence.js +194 -0
- package/dist/daemon/essence.js.map +1 -0
- package/dist/daemon/extbridge.js +138 -0
- package/dist/daemon/extbridge.js.map +1 -0
- package/dist/daemon/lifecycle.js +134 -0
- package/dist/daemon/lifecycle.js.map +1 -0
- package/dist/daemon/pty-compat.js +53 -0
- package/dist/daemon/pty-compat.js.map +1 -0
- package/dist/daemon/semantic.js +337 -0
- package/dist/daemon/semantic.js.map +1 -0
- package/dist/daemon/server.js +808 -0
- package/dist/daemon/server.js.map +1 -0
- package/dist/daemon/supervisor.js +311 -0
- package/dist/daemon/supervisor.js.map +1 -0
- package/dist/extension/pines-extension.js +191 -0
- package/dist/extension/pines-extension.js.map +1 -0
- package/dist/layout/lexical.js +104 -0
- package/dist/layout/lexical.js.map +1 -0
- package/dist/layout/pca.js +91 -0
- package/dist/layout/pca.js.map +1 -0
- package/dist/layout/relax.js +47 -0
- package/dist/layout/relax.js.map +1 -0
- package/dist/session/ingest.js +95 -0
- package/dist/session/ingest.js.map +1 -0
- package/dist/session/parser.js +254 -0
- package/dist/session/parser.js.map +1 -0
- package/dist/shared/config.js +30 -0
- package/dist/shared/config.js.map +1 -0
- package/dist/shared/paths.js +47 -0
- package/dist/shared/paths.js.map +1 -0
- package/dist/shared/protocol.js +9 -0
- package/dist/shared/protocol.js.map +1 -0
- package/dist/shared/types.js +3 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/shared/wire.js +51 -0
- package/dist/shared/wire.js.map +1 -0
- package/dist/store/db.js +203 -0
- package/dist/store/db.js.map +1 -0
- package/dist/store/watcher.js +69 -0
- package/dist/store/watcher.js.map +1 -0
- package/package.json +80 -0
- package/scripts/fix-node-pty.mjs +34 -0
- package/src/extension/pines-extension.ts +192 -0
- package/src/store/schema.sql +60 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extension bridge: accepts pi-extension connections on the daemon socket
|
|
3
|
+
* (role "extension" in the hello), turns their events into exact tree status,
|
|
4
|
+
* and provides a request/response channel for daemon → extension commands.
|
|
5
|
+
*/
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
export class ExtBridge {
|
|
8
|
+
supervisor;
|
|
9
|
+
hooks;
|
|
10
|
+
byWire = new Map();
|
|
11
|
+
pending = new Map();
|
|
12
|
+
constructor(supervisor, hooks) {
|
|
13
|
+
this.supervisor = supervisor;
|
|
14
|
+
this.hooks = hooks;
|
|
15
|
+
}
|
|
16
|
+
/** Wire this bridge into Daemon.extensionHandlers. Returns true if handled. */
|
|
17
|
+
handle = (wire, msg) => {
|
|
18
|
+
if (msg.t === "hello" && msg.role === "extension") {
|
|
19
|
+
void this.onHello(wire, msg);
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
const conn = this.byWire.get(wire);
|
|
23
|
+
if (!conn)
|
|
24
|
+
return false;
|
|
25
|
+
if (msg.t === "ev") {
|
|
26
|
+
this.onEvent(conn, msg);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
if (msg.t === "cmd_result") {
|
|
30
|
+
const p = this.pending.get(msg.re);
|
|
31
|
+
if (p) {
|
|
32
|
+
this.pending.delete(msg.re);
|
|
33
|
+
clearTimeout(p.timer);
|
|
34
|
+
p.resolve({ ok: msg.ok, err: msg.err });
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
};
|
|
40
|
+
async onHello(wire, msg) {
|
|
41
|
+
const conn = { wire, rec: undefined };
|
|
42
|
+
this.byWire.set(wire, conn);
|
|
43
|
+
wire.on("close", () => {
|
|
44
|
+
this.byWire.delete(wire);
|
|
45
|
+
if (conn.rec)
|
|
46
|
+
conn.rec.extensionConnected = false;
|
|
47
|
+
});
|
|
48
|
+
try {
|
|
49
|
+
conn.rec = await this.hooks.claimSession(msg.treeId, msg);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
this.hooks.log(`claimSession failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
53
|
+
}
|
|
54
|
+
const rec = conn.rec;
|
|
55
|
+
if (!rec) {
|
|
56
|
+
this.hooks.log(`extension hello for unknown tree ${msg.treeId}`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
rec.extensionConnected = true;
|
|
60
|
+
rec.sessionId = msg.sessionId;
|
|
61
|
+
if (msg.leafId != null)
|
|
62
|
+
rec.leafId = msg.leafId;
|
|
63
|
+
// A pi that connects already idle is waiting for input, not "running"
|
|
64
|
+
// (spawn optimistically marks running; correct it — as seen, nothing new).
|
|
65
|
+
if (msg.idle === true && rec.status === "running")
|
|
66
|
+
rec.status = "waiting";
|
|
67
|
+
this.supervisor.notify(rec); // broadcast
|
|
68
|
+
this.hooks.log(`extension connected for ${rec.treeId} (${msg.sessionPath})`);
|
|
69
|
+
}
|
|
70
|
+
onEvent(conn, ev) {
|
|
71
|
+
const rec = conn.rec;
|
|
72
|
+
if (!rec)
|
|
73
|
+
return;
|
|
74
|
+
switch (ev.type) {
|
|
75
|
+
case "agent_start":
|
|
76
|
+
this.supervisor.setStatus(rec, "running");
|
|
77
|
+
break;
|
|
78
|
+
case "agent_end":
|
|
79
|
+
// Not idle yet — retries/compaction/queued work may follow. Wait for settled.
|
|
80
|
+
break;
|
|
81
|
+
case "agent_settled": {
|
|
82
|
+
if (ev.newLeafId !== undefined)
|
|
83
|
+
rec.leafId = ev.newLeafId;
|
|
84
|
+
const attached = this.hooks.isAttached(rec.treeId);
|
|
85
|
+
this.supervisor.setStatus(rec, "waiting", { unseen: !attached });
|
|
86
|
+
if (rec.sessionPath)
|
|
87
|
+
this.hooks.onSessionActivity(rec.sessionPath, rec.treeId);
|
|
88
|
+
this.hooks.onSettled?.(rec.treeId);
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
case "session_tree":
|
|
92
|
+
if (ev.newLeafId !== undefined)
|
|
93
|
+
rec.leafId = ev.newLeafId;
|
|
94
|
+
this.supervisor.notify(rec);
|
|
95
|
+
if (rec.sessionPath)
|
|
96
|
+
this.hooks.onSessionActivity(rec.sessionPath, rec.treeId);
|
|
97
|
+
break;
|
|
98
|
+
case "session_switch":
|
|
99
|
+
case "session_shutdown":
|
|
100
|
+
if (ev.sessionPath) {
|
|
101
|
+
rec.sessionPath = ev.sessionPath;
|
|
102
|
+
this.hooks.onSessionActivity(ev.sessionPath, rec.treeId);
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
case "session_name":
|
|
106
|
+
rec.name = ev.name ?? rec.name;
|
|
107
|
+
this.supervisor.notify(rec);
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
hasExtension(treeId) {
|
|
112
|
+
for (const conn of this.byWire.values()) {
|
|
113
|
+
if (conn.rec?.treeId === treeId)
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
/** Send a command to a tree's extension; resolves with its cmd_result. */
|
|
119
|
+
command(treeId, cmd, timeoutMs = 5000) {
|
|
120
|
+
let target;
|
|
121
|
+
for (const conn of this.byWire.values()) {
|
|
122
|
+
if (conn.rec?.treeId === treeId)
|
|
123
|
+
target = conn;
|
|
124
|
+
}
|
|
125
|
+
if (!target)
|
|
126
|
+
return Promise.resolve({ ok: false, err: "no extension connected" });
|
|
127
|
+
const id = `c_${randomUUID().slice(0, 8)}`;
|
|
128
|
+
return new Promise((resolve) => {
|
|
129
|
+
const timer = setTimeout(() => {
|
|
130
|
+
this.pending.delete(id);
|
|
131
|
+
resolve({ ok: false, err: "extension command timed out" });
|
|
132
|
+
}, timeoutMs);
|
|
133
|
+
this.pending.set(id, { resolve, timer });
|
|
134
|
+
target.wire.send({ t: "cmd", id, ...cmd });
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=extbridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extbridge.js","sourceRoot":"","sources":["../../src/daemon/extbridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon lifecycle: auto-spawn as a detached background process, socket
|
|
3
|
+
* liveness probing, and shutdown.
|
|
4
|
+
*
|
|
5
|
+
* `pines` (bare) probes the socket; if nothing is listening it re-execs this
|
|
6
|
+
* same installation as `pines server` with detached:true (setsid on POSIX) and
|
|
7
|
+
* null stdio, then polls until the socket accepts connections.
|
|
8
|
+
*/
|
|
9
|
+
import { connect } from "node:net";
|
|
10
|
+
import { spawn } from "node:child_process";
|
|
11
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { dirname, join } from "node:path";
|
|
14
|
+
import { daemonPidPath, ensurePinesHome, socketPath } from "../shared/paths.js";
|
|
15
|
+
const SERVER_READY_TIMEOUT_MS = 15_000;
|
|
16
|
+
const PROBE_INTERVAL_MS = 100;
|
|
17
|
+
/** Longest we wait for a SIGTERMed daemon to actually exit before reporting failure. */
|
|
18
|
+
const TERM_TIMEOUT_MS = 5000;
|
|
19
|
+
/** True if a live server is accepting connections on the pines socket. */
|
|
20
|
+
export function isServerListening(path = socketPath()) {
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
const sock = connect(path);
|
|
23
|
+
const done = (ok) => {
|
|
24
|
+
sock.destroy();
|
|
25
|
+
resolve(ok);
|
|
26
|
+
};
|
|
27
|
+
sock.once("connect", () => done(true));
|
|
28
|
+
sock.once("error", () => done(false));
|
|
29
|
+
sock.setTimeout(1000, () => done(false));
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** Record this process as the daemon that owns the socket. */
|
|
33
|
+
export function writeDaemonPid() {
|
|
34
|
+
try {
|
|
35
|
+
writeFileSync(daemonPidPath(), `${process.pid}\n`);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// A missing pidfile only costs us the SIGTERM fallback; never fail to boot.
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function clearDaemonPid() {
|
|
42
|
+
try {
|
|
43
|
+
unlinkSync(daemonPidPath());
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
/* ignore */
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Pid recorded by the running daemon, or undefined if absent/garbage/dead. */
|
|
50
|
+
export function readDaemonPid() {
|
|
51
|
+
let pid;
|
|
52
|
+
try {
|
|
53
|
+
pid = Number(readFileSync(daemonPidPath(), "utf8").trim());
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
59
|
+
return undefined;
|
|
60
|
+
return isProcessAlive(pid) ? pid : undefined;
|
|
61
|
+
}
|
|
62
|
+
export function isProcessAlive(pid) {
|
|
63
|
+
try {
|
|
64
|
+
// Signal 0 performs the permission/existence check without delivering.
|
|
65
|
+
process.kill(pid, 0);
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* SIGTERM a daemon and wait for it to actually go. Returns false if it is still
|
|
74
|
+
* alive after the timeout — callers must not claim success on our behalf.
|
|
75
|
+
*/
|
|
76
|
+
export async function terminateDaemon(pid) {
|
|
77
|
+
try {
|
|
78
|
+
process.kill(pid, "SIGTERM");
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return !isProcessAlive(pid);
|
|
82
|
+
}
|
|
83
|
+
const deadline = Date.now() + TERM_TIMEOUT_MS;
|
|
84
|
+
while (Date.now() < deadline) {
|
|
85
|
+
if (!isProcessAlive(pid))
|
|
86
|
+
return true;
|
|
87
|
+
await new Promise((r) => setTimeout(r, PROBE_INTERVAL_MS));
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
/** Path to the built CLI entry (dist/cli.js), resolved from this module. */
|
|
92
|
+
function cliEntryPath() {
|
|
93
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
94
|
+
// dist/daemon/lifecycle.js → dist/cli.js (same layout under src/ when run via tsx)
|
|
95
|
+
return join(here, "..", "cli.js");
|
|
96
|
+
}
|
|
97
|
+
export function spawnServerDaemon() {
|
|
98
|
+
ensurePinesHome();
|
|
99
|
+
const entry = cliEntryPath();
|
|
100
|
+
const child = spawn(process.execPath, [entry, "server"], {
|
|
101
|
+
detached: true,
|
|
102
|
+
stdio: "ignore",
|
|
103
|
+
env: { ...process.env, PINES_STARTUP_CWD: process.cwd() },
|
|
104
|
+
});
|
|
105
|
+
child.unref();
|
|
106
|
+
return child.pid;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Ensure a daemon is running; spawn one if needed. Returns once the socket is
|
|
110
|
+
* accepting connections. Throws after the readiness timeout.
|
|
111
|
+
*/
|
|
112
|
+
export async function ensureDaemon() {
|
|
113
|
+
const path = socketPath();
|
|
114
|
+
if (await isServerListening(path))
|
|
115
|
+
return;
|
|
116
|
+
// A socket file with no listener is stale (daemon died without cleanup).
|
|
117
|
+
if (existsSync(path)) {
|
|
118
|
+
try {
|
|
119
|
+
unlinkSync(path);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// Racing another client doing the same cleanup is fine.
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
spawnServerDaemon();
|
|
126
|
+
const deadline = Date.now() + SERVER_READY_TIMEOUT_MS;
|
|
127
|
+
while (Date.now() < deadline) {
|
|
128
|
+
if (await isServerListening(path))
|
|
129
|
+
return;
|
|
130
|
+
await new Promise((r) => setTimeout(r, PROBE_INTERVAL_MS));
|
|
131
|
+
}
|
|
132
|
+
throw new Error(`pines daemon did not become ready within ${SERVER_READY_TIMEOUT_MS / 1000}s (socket: ${path})`);
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../src/daemon/lifecycle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* node-pty 1.1.0's published macOS prebuild ships spawn-helper without any
|
|
3
|
+
* execute bits. The native binding then fails every spawn with the opaque
|
|
4
|
+
* "posix_spawnp failed" error. Repair the helper defensively at runtime so
|
|
5
|
+
* installs still work when lifecycle scripts were disabled by the package
|
|
6
|
+
* manager.
|
|
7
|
+
*/
|
|
8
|
+
import { accessSync, chmodSync, constants, existsSync, statSync } from "node:fs";
|
|
9
|
+
import { createRequire } from "node:module";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
|
+
let checked = false;
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
/** Ensure one helper is executable. Returns true when its mode was changed. */
|
|
14
|
+
export function ensureExecutable(path) {
|
|
15
|
+
if (process.platform === "win32" || !existsSync(path))
|
|
16
|
+
return false;
|
|
17
|
+
const mode = statSync(path).mode;
|
|
18
|
+
try {
|
|
19
|
+
accessSync(path, constants.X_OK);
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// The current user cannot execute it. Add execute permission alongside
|
|
24
|
+
// each existing read permission rather than replacing other mode bits.
|
|
25
|
+
}
|
|
26
|
+
chmodSync(path, mode | 0o111);
|
|
27
|
+
accessSync(path, constants.X_OK);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
/** Repair the helper used by this installation of node-pty, once per process. */
|
|
31
|
+
export function ensureNodePtyReady() {
|
|
32
|
+
if (checked || process.platform === "win32")
|
|
33
|
+
return;
|
|
34
|
+
const entry = require.resolve("node-pty");
|
|
35
|
+
const root = join(dirname(entry), "..");
|
|
36
|
+
const candidates = [
|
|
37
|
+
join(root, "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper"),
|
|
38
|
+
join(root, "build", "Release", "spawn-helper"),
|
|
39
|
+
];
|
|
40
|
+
const helper = candidates.find(existsSync);
|
|
41
|
+
if (!helper) {
|
|
42
|
+
checked = true;
|
|
43
|
+
return; // Future node-pty versions may no longer use this helper.
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
ensureExecutable(helper);
|
|
47
|
+
checked = true;
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
throw new Error(`node-pty helper is not executable (${helper}): ${err instanceof Error ? err.message : String(err)}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=pty-compat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pty-compat.js","sourceRoot":"","sources":["../../src/daemon/pty-compat.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,IAAI,OAAO,GAAG,KAAK,CAAC;AACpB,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,+EAA+E;AAC/E,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACpE,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC;IACjC,IAAI,CAAC;QACH,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;QACjC,OAAO,KAAK,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACP,uEAAuE;QACvE,uEAAuE;IACzE,CAAC;IACD,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,KAAK,CAAC,CAAC;IAC9B,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;IACjC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,kBAAkB;IAChC,IAAI,OAAO,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO;IAEpD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;IACxC,MAAM,UAAU,GAAG;QACjB,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,cAAc,CAAC;QAC9E,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,cAAc,CAAC;KAC/C,CAAC;IACF,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC3C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,GAAG,IAAI,CAAC;QACf,OAAO,CAAC,0DAA0D;IACpE,CAAC;IAED,IAAI,CAAC;QACH,gBAAgB,CAAC,MAAM,CAAC,CAAC;QACzB,OAAO,GAAG,IAAI,CAAC;IACjB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,sCAAsC,MAAM,MAC1C,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CACjD,EAAE,CACH,CAAC;IACJ,CAAC;AACH,CAAC"}
|