@cortexkit/opencode-magic-context 0.30.1 → 0.30.2
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/dist/index.js +183 -115
- package/dist/plugin/rpc-handlers.d.ts.map +1 -1
- package/dist/shared/announcement.d.ts +1 -1
- package/dist/shared/rpc-client.d.ts +8 -0
- package/dist/shared/rpc-client.d.ts.map +1 -1
- package/dist/shared/rpc-notifications.d.ts +28 -10
- package/dist/shared/rpc-notifications.d.ts.map +1 -1
- package/dist/shared/rpc-server.d.ts +22 -3
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/tui/data/context-db.d.ts +4 -14
- package/dist/tui/data/context-db.d.ts.map +1 -1
- package/dist/tui/data/notification-socket.d.ts +39 -0
- package/dist/tui/data/notification-socket.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/shared/announcement.ts +2 -2
- package/src/shared/rpc-client.ts +14 -0
- package/src/shared/rpc-notifications.test.ts +68 -11
- package/src/shared/rpc-notifications.ts +75 -36
- package/src/shared/rpc-server.ts +249 -150
- package/src/tui/data/context-db.ts +10 -64
- package/src/tui/data/notification-socket.ts +229 -0
- package/src/tui/index.tsx +68 -118
package/dist/index.js
CHANGED
|
@@ -165515,12 +165515,30 @@ var init_safe_notification_target = __esm(() => {
|
|
|
165515
165515
|
// src/shared/rpc-notifications.ts
|
|
165516
165516
|
var exports_rpc_notifications = {};
|
|
165517
165517
|
__export(exports_rpc_notifications, {
|
|
165518
|
+
registerNotificationSink: () => registerNotificationSink,
|
|
165518
165519
|
pushNotification: () => pushNotification,
|
|
165519
165520
|
isTuiConnected: () => isTuiConnected,
|
|
165520
165521
|
drainNotifications: () => drainNotifications
|
|
165521
165522
|
});
|
|
165523
|
+
function registerNotificationSink(sink) {
|
|
165524
|
+
sinks.add(sink);
|
|
165525
|
+
return () => {
|
|
165526
|
+
sinks.delete(sink);
|
|
165527
|
+
};
|
|
165528
|
+
}
|
|
165529
|
+
function notificationMatchesSink(notification, sink) {
|
|
165530
|
+
return notification.sessionId === undefined || sink.sessionId === undefined || notification.sessionId === sink.sessionId;
|
|
165531
|
+
}
|
|
165522
165532
|
function pushNotification(type, payload, sessionId) {
|
|
165523
|
-
|
|
165533
|
+
const notification = { id: nextNotificationId++, type, payload, sessionId };
|
|
165534
|
+
queue.push(notification);
|
|
165535
|
+
for (const sink of sinks) {
|
|
165536
|
+
if (!notificationMatchesSink(notification, sink))
|
|
165537
|
+
continue;
|
|
165538
|
+
try {
|
|
165539
|
+
sink.send(notification);
|
|
165540
|
+
} catch {}
|
|
165541
|
+
}
|
|
165524
165542
|
if (queue.length > 100) {
|
|
165525
165543
|
const newestPerSession = new Map;
|
|
165526
165544
|
for (const n of queue) {
|
|
@@ -165540,10 +165558,6 @@ function pushNotification(type, payload, sessionId) {
|
|
|
165540
165558
|
}
|
|
165541
165559
|
}
|
|
165542
165560
|
function drainNotifications(lastReceivedId = 0, sessionId) {
|
|
165543
|
-
const now = Date.now();
|
|
165544
|
-
lastDrainAtAny = now;
|
|
165545
|
-
if (sessionId !== undefined)
|
|
165546
|
-
lastDrainAtBySession.set(sessionId, now);
|
|
165547
165561
|
const matchesClient = (notification) => sessionId === undefined || notification.sessionId === undefined || notification.sessionId === sessionId;
|
|
165548
165562
|
if (lastReceivedId > 0) {
|
|
165549
165563
|
queue = queue.filter((notification) => !(notification.id <= lastReceivedId && matchesClient(notification)));
|
|
@@ -165551,17 +165565,20 @@ function drainNotifications(lastReceivedId = 0, sessionId) {
|
|
|
165551
165565
|
return queue.filter((notification) => notification.id > lastReceivedId && matchesClient(notification));
|
|
165552
165566
|
}
|
|
165553
165567
|
function isTuiConnected(sessionId) {
|
|
165554
|
-
|
|
165555
|
-
|
|
165556
|
-
|
|
165557
|
-
return
|
|
165568
|
+
if (sinks.size === 0)
|
|
165569
|
+
return false;
|
|
165570
|
+
if (sessionId === undefined)
|
|
165571
|
+
return true;
|
|
165572
|
+
for (const sink of sinks) {
|
|
165573
|
+
if (sink.sessionId === undefined || sink.sessionId === sessionId)
|
|
165574
|
+
return true;
|
|
165558
165575
|
}
|
|
165559
|
-
return
|
|
165576
|
+
return false;
|
|
165560
165577
|
}
|
|
165561
|
-
var queue, nextNotificationId = 1,
|
|
165578
|
+
var queue, nextNotificationId = 1, sinks;
|
|
165562
165579
|
var init_rpc_notifications = __esm(() => {
|
|
165563
165580
|
queue = [];
|
|
165564
|
-
|
|
165581
|
+
sinks = new Set;
|
|
165565
165582
|
});
|
|
165566
165583
|
|
|
165567
165584
|
// src/plugin/conflict-warning-hook.ts
|
|
@@ -184723,11 +184740,11 @@ function shouldShowAnnouncement() {
|
|
|
184723
184740
|
}
|
|
184724
184741
|
return ordering > 0;
|
|
184725
184742
|
}
|
|
184726
|
-
var ANNOUNCEMENT_VERSION = "0.30.
|
|
184743
|
+
var ANNOUNCEMENT_VERSION = "0.30.2", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
|
|
184727
184744
|
var init_announcement = __esm(() => {
|
|
184728
184745
|
init_data_path();
|
|
184729
184746
|
ANNOUNCEMENT_FEATURES = [
|
|
184730
|
-
"
|
|
184747
|
+
"Fixed high idle CPU from the TUI sidebar (#200): it now uses a single persistent connection to the plugin instead of polling, so an idle session no longer burns CPU."
|
|
184731
184748
|
];
|
|
184732
184749
|
});
|
|
184733
184750
|
|
|
@@ -202626,7 +202643,6 @@ function calibrateBuckets(input) {
|
|
|
202626
202643
|
// src/plugin/rpc-handlers.ts
|
|
202627
202644
|
init_announcement();
|
|
202628
202645
|
init_logger();
|
|
202629
|
-
init_rpc_notifications();
|
|
202630
202646
|
var workMetricsCarryBySession = new Map;
|
|
202631
202647
|
function resolveSidebarWorkMetrics(db, sessionId, persistedNewWork, persistedTotalInput) {
|
|
202632
202648
|
if (!openCodeDbExists()) {
|
|
@@ -203138,12 +203154,6 @@ function registerRpcHandlers(rpcServer, args) {
|
|
|
203138
203154
|
const resolved = typeof config2.toast_duration_ms === "number" && Number.isFinite(config2.toast_duration_ms) ? config2.toast_duration_ms : 5000;
|
|
203139
203155
|
return { toastDurationMs: resolved };
|
|
203140
203156
|
});
|
|
203141
|
-
rpcServer.handle("pending-notifications", async (params) => {
|
|
203142
|
-
const lastReceivedId = Number(params.lastReceivedId ?? 0);
|
|
203143
|
-
const sessionId = typeof params.sessionId === "string" && params.sessionId.length > 0 ? params.sessionId : undefined;
|
|
203144
|
-
const notifications = drainNotifications(Number.isFinite(lastReceivedId) ? lastReceivedId : 0, sessionId);
|
|
203145
|
-
return { messages: notifications };
|
|
203146
|
-
});
|
|
203147
203157
|
rpcServer.handle("get-announcement", async () => {
|
|
203148
203158
|
if (!shouldShowAnnouncement()) {
|
|
203149
203159
|
return { show: false };
|
|
@@ -204536,6 +204546,7 @@ init_models_dev_cache();
|
|
|
204536
204546
|
|
|
204537
204547
|
// src/shared/rpc-server.ts
|
|
204538
204548
|
init_logger();
|
|
204549
|
+
init_rpc_notifications();
|
|
204539
204550
|
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
204540
204551
|
import {
|
|
204541
204552
|
chmodSync as chmodSync3,
|
|
@@ -204547,7 +204558,6 @@ import {
|
|
|
204547
204558
|
unlinkSync as unlinkSync4,
|
|
204548
204559
|
writeFileSync as writeFileSync11
|
|
204549
204560
|
} from "node:fs";
|
|
204550
|
-
import { createServer } from "node:http";
|
|
204551
204561
|
import { dirname as dirname9 } from "node:path";
|
|
204552
204562
|
|
|
204553
204563
|
// src/shared/rpc-utils.ts
|
|
@@ -204605,6 +204615,9 @@ function isValidPort(port) {
|
|
|
204605
204615
|
}
|
|
204606
204616
|
|
|
204607
204617
|
// src/shared/rpc-server.ts
|
|
204618
|
+
var MAX_BODY_BYTES = 1048576;
|
|
204619
|
+
var WS_AUTH_TIMEOUT_MS = 5000;
|
|
204620
|
+
var WS_CLOSE_UNAUTHORIZED = 4401;
|
|
204608
204621
|
function tokensMatch(presented, expected) {
|
|
204609
204622
|
const a = Buffer.from(presented, "utf8");
|
|
204610
204623
|
const b = Buffer.from(expected, "utf8");
|
|
@@ -204620,6 +204633,7 @@ class MagicContextRpcServer {
|
|
|
204620
204633
|
portFilePath;
|
|
204621
204634
|
portDir;
|
|
204622
204635
|
startedAt = Date.now();
|
|
204636
|
+
sockets = new Set;
|
|
204623
204637
|
token = randomBytes(32).toString("hex");
|
|
204624
204638
|
constructor(storageDir, directory) {
|
|
204625
204639
|
this.portFilePath = rpcPortFilePath(storageDir, directory);
|
|
@@ -204629,49 +204643,77 @@ class MagicContextRpcServer {
|
|
|
204629
204643
|
this.handlers.set(method, handler);
|
|
204630
204644
|
}
|
|
204631
204645
|
async start() {
|
|
204632
|
-
|
|
204633
|
-
|
|
204634
|
-
|
|
204635
|
-
|
|
204636
|
-
|
|
204637
|
-
|
|
204638
|
-
|
|
204639
|
-
|
|
204640
|
-
|
|
204641
|
-
|
|
204642
|
-
|
|
204643
|
-
|
|
204644
|
-
|
|
204645
|
-
|
|
204646
|
-
|
|
204647
|
-
|
|
204648
|
-
|
|
204649
|
-
|
|
204650
|
-
|
|
204651
|
-
|
|
204652
|
-
|
|
204653
|
-
|
|
204654
|
-
try {
|
|
204655
|
-
rmSync3(tmpPath, { force: true });
|
|
204656
|
-
} catch {}
|
|
204657
|
-
writeFileSync11(tmpPath, JSON.stringify({
|
|
204658
|
-
port: this.port,
|
|
204659
|
-
pid: process.pid,
|
|
204660
|
-
started_at: this.startedAt,
|
|
204661
|
-
token: this.token
|
|
204662
|
-
}), { encoding: "utf-8", mode: 384 });
|
|
204663
|
-
renameSync5(tmpPath, this.portFilePath);
|
|
204664
|
-
try {
|
|
204665
|
-
chmodSync3(this.portFilePath, 384);
|
|
204666
|
-
} catch {}
|
|
204667
|
-
log(`[rpc] server listening on 127.0.0.1:${this.port}`);
|
|
204668
|
-
} catch (err) {
|
|
204669
|
-
log(`[rpc] failed to write port file: ${err}`);
|
|
204646
|
+
const self2 = this;
|
|
204647
|
+
const server = Bun.serve({
|
|
204648
|
+
port: 0,
|
|
204649
|
+
hostname: "127.0.0.1",
|
|
204650
|
+
fetch(req, srv) {
|
|
204651
|
+
return self2.handleFetch(req, srv);
|
|
204652
|
+
},
|
|
204653
|
+
websocket: {
|
|
204654
|
+
open(ws) {
|
|
204655
|
+
ws.data.authTimer = setTimeout(() => {
|
|
204656
|
+
if (!ws.data.authed)
|
|
204657
|
+
ws.close(WS_CLOSE_UNAUTHORIZED, "auth timeout");
|
|
204658
|
+
}, WS_AUTH_TIMEOUT_MS);
|
|
204659
|
+
},
|
|
204660
|
+
message(ws, raw) {
|
|
204661
|
+
self2.handleWsMessage(ws, raw);
|
|
204662
|
+
},
|
|
204663
|
+
close(ws) {
|
|
204664
|
+
if (ws.data.authTimer)
|
|
204665
|
+
clearTimeout(ws.data.authTimer);
|
|
204666
|
+
ws.data.unregister?.();
|
|
204667
|
+
self2.sockets.delete(ws);
|
|
204670
204668
|
}
|
|
204671
|
-
|
|
204672
|
-
});
|
|
204673
|
-
server.unref();
|
|
204669
|
+
}
|
|
204674
204670
|
});
|
|
204671
|
+
this.server = server;
|
|
204672
|
+
this.port = server.port ?? 0;
|
|
204673
|
+
try {
|
|
204674
|
+
this.warnIfOtherLiveInstance();
|
|
204675
|
+
const dir = dirname9(this.portFilePath);
|
|
204676
|
+
mkdirSync11(dir, { recursive: true, mode: 448 });
|
|
204677
|
+
try {
|
|
204678
|
+
chmodSync3(dir, 448);
|
|
204679
|
+
} catch {}
|
|
204680
|
+
const tmpPath = `${this.portFilePath}.tmp`;
|
|
204681
|
+
try {
|
|
204682
|
+
rmSync3(tmpPath, { force: true });
|
|
204683
|
+
} catch {}
|
|
204684
|
+
writeFileSync11(tmpPath, JSON.stringify({
|
|
204685
|
+
port: this.port,
|
|
204686
|
+
pid: process.pid,
|
|
204687
|
+
started_at: this.startedAt,
|
|
204688
|
+
token: this.token
|
|
204689
|
+
}), { encoding: "utf-8", mode: 384 });
|
|
204690
|
+
renameSync5(tmpPath, this.portFilePath);
|
|
204691
|
+
try {
|
|
204692
|
+
chmodSync3(this.portFilePath, 384);
|
|
204693
|
+
} catch {}
|
|
204694
|
+
log(`[rpc] server listening on 127.0.0.1:${this.port}`);
|
|
204695
|
+
} catch (err) {
|
|
204696
|
+
log(`[rpc] failed to write port file: ${err}`);
|
|
204697
|
+
}
|
|
204698
|
+
return this.port;
|
|
204699
|
+
}
|
|
204700
|
+
stop() {
|
|
204701
|
+
for (const ws of this.sockets) {
|
|
204702
|
+
try {
|
|
204703
|
+
if (ws.data.authTimer)
|
|
204704
|
+
clearTimeout(ws.data.authTimer);
|
|
204705
|
+
ws.data.unregister?.();
|
|
204706
|
+
ws.close();
|
|
204707
|
+
} catch {}
|
|
204708
|
+
}
|
|
204709
|
+
this.sockets.clear();
|
|
204710
|
+
if (this.server) {
|
|
204711
|
+
this.server.stop(true);
|
|
204712
|
+
this.server = null;
|
|
204713
|
+
}
|
|
204714
|
+
try {
|
|
204715
|
+
unlinkSync4(this.portFilePath);
|
|
204716
|
+
} catch {}
|
|
204675
204717
|
}
|
|
204676
204718
|
warnIfOtherLiveInstance() {
|
|
204677
204719
|
try {
|
|
@@ -204686,73 +204728,99 @@ class MagicContextRpcServer {
|
|
|
204686
204728
|
}
|
|
204687
204729
|
} catch {}
|
|
204688
204730
|
}
|
|
204689
|
-
|
|
204690
|
-
|
|
204691
|
-
|
|
204692
|
-
|
|
204731
|
+
async handleFetch(req, srv) {
|
|
204732
|
+
const url2 = new URL(req.url);
|
|
204733
|
+
if (url2.pathname === "/ws") {
|
|
204734
|
+
const ok = srv.upgrade(req, { data: { authed: false } });
|
|
204735
|
+
if (ok)
|
|
204736
|
+
return;
|
|
204737
|
+
return new Response("upgrade failed", { status: 400 });
|
|
204693
204738
|
}
|
|
204694
|
-
|
|
204695
|
-
|
|
204696
|
-
} catch {}
|
|
204697
|
-
}
|
|
204698
|
-
dispatch(req, res) {
|
|
204699
|
-
const url2 = req.url ?? "";
|
|
204700
|
-
if (req.method === "GET" && url2 === "/health") {
|
|
204701
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
204702
|
-
res.end(JSON.stringify({ ok: true, pid: process.pid }));
|
|
204703
|
-
return;
|
|
204739
|
+
if (req.method === "GET" && url2.pathname === "/health") {
|
|
204740
|
+
return json2({ ok: true, pid: process.pid });
|
|
204704
204741
|
}
|
|
204705
|
-
if (req.method !== "POST" || !url2.startsWith("/rpc/")) {
|
|
204706
|
-
|
|
204707
|
-
res.end("Not Found");
|
|
204708
|
-
return;
|
|
204742
|
+
if (req.method !== "POST" || !url2.pathname.startsWith("/rpc/")) {
|
|
204743
|
+
return new Response("Not Found", { status: 404 });
|
|
204709
204744
|
}
|
|
204710
|
-
const auth = req.headers.authorization;
|
|
204745
|
+
const auth = req.headers.get("authorization");
|
|
204711
204746
|
const presented = typeof auth === "string" ? auth.replace(/^Bearer\s+/i, "") : "";
|
|
204712
204747
|
if (!tokensMatch(presented, this.token)) {
|
|
204713
|
-
|
|
204714
|
-
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
204715
|
-
req.resume();
|
|
204716
|
-
return;
|
|
204748
|
+
return json2({ error: "Unauthorized" }, 401);
|
|
204717
204749
|
}
|
|
204718
|
-
const method = url2.slice(5);
|
|
204750
|
+
const method = url2.pathname.slice(5);
|
|
204719
204751
|
const handler = this.handlers.get(method);
|
|
204720
204752
|
if (!handler) {
|
|
204721
|
-
|
|
204722
|
-
res.end(JSON.stringify({ error: `Unknown method: ${method}` }));
|
|
204723
|
-
return;
|
|
204753
|
+
return json2({ error: `Unknown method: ${method}` }, 404);
|
|
204724
204754
|
}
|
|
204725
|
-
|
|
204726
|
-
|
|
204727
|
-
|
|
204728
|
-
|
|
204729
|
-
|
|
204730
|
-
|
|
204731
|
-
req.destroy();
|
|
204732
|
-
}
|
|
204733
|
-
});
|
|
204734
|
-
req.on("end", () => {
|
|
204735
|
-
let params = {};
|
|
204755
|
+
const bodyText = await req.text();
|
|
204756
|
+
if (bodyText.length > MAX_BODY_BYTES) {
|
|
204757
|
+
return new Response("Request too large", { status: 413 });
|
|
204758
|
+
}
|
|
204759
|
+
let params = {};
|
|
204760
|
+
if (bodyText.length > 0) {
|
|
204736
204761
|
try {
|
|
204737
|
-
|
|
204738
|
-
params = JSON.parse(body);
|
|
204739
|
-
}
|
|
204762
|
+
params = JSON.parse(bodyText);
|
|
204740
204763
|
} catch {
|
|
204741
|
-
|
|
204742
|
-
|
|
204764
|
+
return json2({ error: "Invalid JSON" }, 400);
|
|
204765
|
+
}
|
|
204766
|
+
}
|
|
204767
|
+
try {
|
|
204768
|
+
const result = await handler(params);
|
|
204769
|
+
return json2(result);
|
|
204770
|
+
} catch (err) {
|
|
204771
|
+
log(`[rpc] handler error: ${method} => ${err}`);
|
|
204772
|
+
return json2({ error: String(err) }, 500);
|
|
204773
|
+
}
|
|
204774
|
+
}
|
|
204775
|
+
handleWsMessage(ws, raw) {
|
|
204776
|
+
let msg;
|
|
204777
|
+
try {
|
|
204778
|
+
msg = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf8"));
|
|
204779
|
+
} catch {
|
|
204780
|
+
return;
|
|
204781
|
+
}
|
|
204782
|
+
if (msg.type === "hello") {
|
|
204783
|
+
if (!tokensMatch(typeof msg.token === "string" ? msg.token : "", this.token)) {
|
|
204784
|
+
ws.send(JSON.stringify({ type: "error", error: "unauthorized" }));
|
|
204785
|
+
ws.close(WS_CLOSE_UNAUTHORIZED, "bad token");
|
|
204743
204786
|
return;
|
|
204744
204787
|
}
|
|
204745
|
-
|
|
204746
|
-
|
|
204747
|
-
|
|
204748
|
-
}
|
|
204749
|
-
|
|
204750
|
-
|
|
204751
|
-
|
|
204752
|
-
|
|
204753
|
-
|
|
204788
|
+
if (ws.data.authTimer) {
|
|
204789
|
+
clearTimeout(ws.data.authTimer);
|
|
204790
|
+
ws.data.authTimer = undefined;
|
|
204791
|
+
}
|
|
204792
|
+
ws.data.authed = true;
|
|
204793
|
+
ws.data.sessionId = typeof msg.sessionId === "string" && msg.sessionId.length > 0 ? msg.sessionId : undefined;
|
|
204794
|
+
const sink = {
|
|
204795
|
+
sessionId: ws.data.sessionId,
|
|
204796
|
+
send: (notification) => {
|
|
204797
|
+
ws.send(JSON.stringify({ type: "notification", notification }));
|
|
204798
|
+
}
|
|
204799
|
+
};
|
|
204800
|
+
ws.data.unregister = registerNotificationSink(sink);
|
|
204801
|
+
this.sockets.add(ws);
|
|
204802
|
+
const lastReceivedId = Number(msg.lastReceivedId ?? 0);
|
|
204803
|
+
const backlog = drainNotifications(Number.isFinite(lastReceivedId) ? lastReceivedId : 0, ws.data.sessionId);
|
|
204804
|
+
for (const notification of backlog) {
|
|
204805
|
+
ws.send(JSON.stringify({ type: "notification", notification }));
|
|
204806
|
+
}
|
|
204807
|
+
ws.send(JSON.stringify({ type: "hello-ack" }));
|
|
204808
|
+
return;
|
|
204809
|
+
}
|
|
204810
|
+
if (msg.type === "ack") {
|
|
204811
|
+
const lastReceivedId = Number(msg.lastReceivedId ?? 0);
|
|
204812
|
+
if (Number.isFinite(lastReceivedId) && lastReceivedId > 0) {
|
|
204813
|
+
drainNotifications(lastReceivedId, ws.data.sessionId);
|
|
204814
|
+
}
|
|
204815
|
+
}
|
|
204754
204816
|
}
|
|
204755
204817
|
}
|
|
204818
|
+
function json2(body, status = 200) {
|
|
204819
|
+
return new Response(JSON.stringify(body), {
|
|
204820
|
+
status,
|
|
204821
|
+
headers: { "Content-Type": "application/json" }
|
|
204822
|
+
});
|
|
204823
|
+
}
|
|
204756
204824
|
|
|
204757
204825
|
// src/index.ts
|
|
204758
204826
|
var server = async (ctx) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-handlers.d.ts","sourceRoot":"","sources":["../../src/plugin/rpc-handlers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAIzE,OAAO,EACH,KAAK,eAAe,IAAI,QAAQ,EAGnC,MAAM,mCAAmC,CAAC;AAc3C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;
|
|
1
|
+
{"version":3,"file":"rpc-handlers.d.ts","sourceRoot":"","sources":["../../src/plugin/rpc-handlers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAIzE,OAAO,EACH,KAAK,eAAe,IAAI,QAAQ,EAGnC,MAAM,mCAAmC,CAAC;AAc3C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAoBlF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,KAAK,EAAe,eAAe,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AA0FtF,wBAAgB,oBAAoB,CAChC,EAAE,EAAE,QAAQ,EACZ,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,gBAAgB,CAAC,EAAE,gBAAgB,EACnC,qBAAqB,CAAC,EAAE,MAAM,EAK9B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,eAAe,CAuVjB;AAED,wBAAgB,iBAAiB,CAC7B,EAAE,EAAE,QAAQ,EACZ,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,gBAAgB,CAAC,EAAE,gBAAgB,EACnC,qBAAqB,CAAC,EAAE,MAAM,GAC/B,YAAY,CAyKd;AA4BD;;GAEG;AACH,wBAAgB,mBAAmB,CAC/B,SAAS,EAAE,qBAAqB,EAChC,IAAI,EAAE;IACF,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,gBAAgB,EAAE,gBAAgB,CAAC;CACtC,GACF,IAAI,CA4ON"}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* Bump only when there are user-visible changes worth a startup dialog.
|
|
19
19
|
* Does NOT need to match the published package version.
|
|
20
20
|
*/
|
|
21
|
-
export declare const ANNOUNCEMENT_VERSION = "0.30.
|
|
21
|
+
export declare const ANNOUNCEMENT_VERSION = "0.30.2";
|
|
22
22
|
/**
|
|
23
23
|
* Short, user-facing bullet strings. Keep each line ~80 chars or shorter so the
|
|
24
24
|
* TUI dialog renders cleanly without horizontal scroll on a typical terminal.
|
|
@@ -9,6 +9,14 @@ export declare class MagicContextRpcClient {
|
|
|
9
9
|
call<T = Record<string, unknown>>(method: string, params?: Record<string, unknown>): Promise<T>;
|
|
10
10
|
/** Check if the RPC server is reachable. */
|
|
11
11
|
isAvailable(): Promise<boolean>;
|
|
12
|
+
/** Resolve the live server's port + bearer token (for opening the WS push
|
|
13
|
+
* channel). Reuses the same health-checked port-file discovery as `call`,
|
|
14
|
+
* so the WS client and the HTTP client always agree on which server instance
|
|
15
|
+
* (and token) to use. Returns null when no live server is found. */
|
|
16
|
+
resolveEndpoint(): Promise<{
|
|
17
|
+
port: number;
|
|
18
|
+
token: string | null;
|
|
19
|
+
} | null>;
|
|
12
20
|
private resolvePort;
|
|
13
21
|
private readPortFile;
|
|
14
22
|
private healthCheck;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-client.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-client.ts"],"names":[],"mappings":"AAiBA,qBAAa,qBAAqB;IAC9B,OAAO,CAAC,IAAI,CAAuB;IACnC,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,aAAa,CAAS;gBAElB,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAKjD,iFAAiF;IAC3E,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACrC,OAAO,CAAC,CAAC,CAAC;IAwDb,4CAA4C;IACtC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"rpc-client.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-client.ts"],"names":[],"mappings":"AAiBA,qBAAa,qBAAqB;IAC9B,OAAO,CAAC,IAAI,CAAuB;IACnC,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,aAAa,CAAS;gBAElB,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAKjD,iFAAiF;IAC3E,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACrC,OAAO,CAAC,CAAC,CAAC;IAwDb,4CAA4C;IACtC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IASrC;;;yEAGqE;IAC/D,eAAe,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;YAUjE,WAAW;IAmCzB,OAAO,CAAC,YAAY;YA4BN,WAAW;YAWX,gBAAgB;IAU9B,KAAK,IAAI,IAAI;CAKhB"}
|
|
@@ -12,16 +12,33 @@ export interface RpcNotification {
|
|
|
12
12
|
payload: Record<string, unknown>;
|
|
13
13
|
sessionId?: string;
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* A connected TUI notification sink — one per authenticated WebSocket. The RPC
|
|
17
|
+
* server registers a sink when a TUI socket authenticates (hello) and removes
|
|
18
|
+
* it on close. `send` is sink-agnostic (the server owns the actual WS socket)
|
|
19
|
+
* so this module stays free of Bun/WS types.
|
|
20
|
+
*/
|
|
21
|
+
export interface NotificationSink {
|
|
22
|
+
/** The TUI's active session at connect time (its hello scope). */
|
|
23
|
+
sessionId?: string;
|
|
24
|
+
/** Deliver one notification over this sink's live socket. */
|
|
25
|
+
send: (notification: RpcNotification) => void;
|
|
26
|
+
}
|
|
27
|
+
/** Register a live TUI sink. Returns an unregister fn (call on socket close). */
|
|
28
|
+
export declare function registerNotificationSink(sink: NotificationSink): () => void;
|
|
29
|
+
/** Push a notification to the TUI. Fans out to any live WS sink immediately and
|
|
30
|
+
* also enqueues it so a TUI that is momentarily disconnected (reconnecting, or
|
|
31
|
+
* not yet connected) still receives it on its next hello via the backlog drain.
|
|
32
|
+
* At-least-once: a live push that the socket drops is re-delivered from the
|
|
33
|
+
* queue on reconnect (pruned only when the client acks via `lastReceivedId`). */
|
|
16
34
|
export declare function pushNotification(type: string, payload: Record<string, unknown>, sessionId?: string): void;
|
|
17
35
|
/** Return pending notifications after acking the client's last received id.
|
|
18
|
-
* Updates lastDrainAt so isTuiConnected() reflects recent activity.
|
|
19
36
|
*
|
|
20
37
|
* Session scoping: when `sessionId` is provided, only notifications tagged for
|
|
21
38
|
* that session (or session-less/global ones) are returned and pruned — a
|
|
22
39
|
* notification tagged for a DIFFERENT session is never handed to this client
|
|
23
40
|
* and is never pruned by this client's ack. This matters because the in-memory
|
|
24
|
-
* queue is per-process but a TUI can end up
|
|
41
|
+
* queue is per-process but a TUI can end up bound to a process that also serves
|
|
25
42
|
* OTHER sessions: e.g. opening OpenCode Desktop on the same project starts a
|
|
26
43
|
* newer RPC server that the TUI's port discovery (newest-pid-wins) then selects,
|
|
27
44
|
* so a Desktop-session upgrade-dialog action would otherwise surface in an
|
|
@@ -31,16 +48,17 @@ export declare function pushNotification(type: string, payload: Record<string, u
|
|
|
31
48
|
*
|
|
32
49
|
* Delivery is at-least-once (non-destructive return + prune-on-ack): a returned
|
|
33
50
|
* notification stays queued until a later call acks it via a higher
|
|
34
|
-
* `lastReceivedId`, so a
|
|
51
|
+
* `lastReceivedId`, so a dropped WS socket re-delivers the backlog on reconnect
|
|
52
|
+
* (the client sends its `lastReceivedId` in the hello). */
|
|
35
53
|
export declare function drainNotifications(lastReceivedId?: number, sessionId?: string): RpcNotification[];
|
|
36
|
-
/** Whether a TUI client is
|
|
37
|
-
*
|
|
54
|
+
/** Whether a TUI client is connected via a live notification socket.
|
|
55
|
+
* Now exact socket liveness (a registered WS sink), not a poll-drain timestamp.
|
|
38
56
|
*
|
|
39
|
-
* Pass `sessionId` (preferred) to ask whether a TUI is
|
|
57
|
+
* Pass `sessionId` (preferred) to ask whether a TUI is connected FOR THAT
|
|
40
58
|
* SESSION — this is what producers (`/ctx-status`, `/ctx-recomp`, the upgrade
|
|
41
59
|
* reminder) must use to decide dialog-vs-message, so a TUI on a different
|
|
42
|
-
* session in the same process does not misroute their delivery.
|
|
43
|
-
*
|
|
44
|
-
*
|
|
60
|
+
* session in the same process does not misroute their delivery. A session-less
|
|
61
|
+
* sink (legacy/global) counts for any session query. Omit `sessionId` only for
|
|
62
|
+
* callers with no session context; they get "any sink connected". */
|
|
45
63
|
export declare function isTuiConnected(sessionId?: string): boolean;
|
|
46
64
|
//# sourceMappingURL=rpc-notifications.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-notifications.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,eAAe;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;
|
|
1
|
+
{"version":3,"file":"rpc-notifications.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,eAAe;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAKD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC7B,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6DAA6D;IAC7D,IAAI,EAAE,CAAC,YAAY,EAAE,eAAe,KAAK,IAAI,CAAC;CACjD;AAYD,iFAAiF;AACjF,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,IAAI,CAK3E;AAcD;;;;kFAIkF;AAClF,wBAAgB,gBAAgB,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,SAAS,CAAC,EAAE,MAAM,GACnB,IAAI,CAoCN;AAED;;;;;;;;;;;;;;;;;4DAiB4D;AAC5D,wBAAgB,kBAAkB,CAAC,cAAc,SAAI,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,eAAe,EAAE,CAe5F;AAED;;;;;;;;sEAQsE;AACtE,wBAAgB,cAAc,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAO1D"}
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
type RpcHandler = (params: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
2
|
+
/**
|
|
3
|
+
* Plugin-private localhost RPC server for TUI ↔ server-plugin communication.
|
|
4
|
+
*
|
|
5
|
+
* Runs on Bun (the OpenCode server runner is a Bun Worker), so it uses
|
|
6
|
+
* `Bun.serve` to host BOTH:
|
|
7
|
+
* - HTTP request/reply routes (`/health`, `/rpc/<method>`) — the TUI's snapshot
|
|
8
|
+
* and dialog-result calls, which are event-driven, not idle; and
|
|
9
|
+
* - a WebSocket endpoint (`/ws`) — a single persistent connection per TUI over
|
|
10
|
+
* which the server PUSHES notifications (dialog/toast actions). This replaces
|
|
11
|
+
* the old 500ms HTTP poll, whose new-connection-per-tick cost was the source
|
|
12
|
+
* of idle TUI CPU (#200). Pi never imports this module, so `Bun.serve` is safe.
|
|
13
|
+
*/
|
|
2
14
|
export declare class MagicContextRpcServer {
|
|
3
15
|
private server;
|
|
4
16
|
private port;
|
|
@@ -6,16 +18,23 @@ export declare class MagicContextRpcServer {
|
|
|
6
18
|
private portFilePath;
|
|
7
19
|
private portDir;
|
|
8
20
|
private startedAt;
|
|
21
|
+
/** Every authenticated WS socket, so dispose can close them all. */
|
|
22
|
+
private sockets;
|
|
9
23
|
private readonly token;
|
|
10
24
|
constructor(storageDir: string, directory: string);
|
|
11
25
|
/** Register an RPC method handler. */
|
|
12
26
|
handle(method: string, handler: RpcHandler): void;
|
|
13
27
|
/** Start the server on a random port, write port to disk. */
|
|
14
28
|
start(): Promise<number>;
|
|
15
|
-
|
|
16
|
-
/** Stop the server and clean up port file. */
|
|
29
|
+
/** Stop the server: close every socket, stop accepting, remove port file. */
|
|
17
30
|
stop(): void;
|
|
18
|
-
private
|
|
31
|
+
private warnIfOtherLiveInstance;
|
|
32
|
+
/** HTTP route handler (Bun fetch). Returns a Response, or undefined when the
|
|
33
|
+
* request was upgraded to a WebSocket. */
|
|
34
|
+
private handleFetch;
|
|
35
|
+
/** WS message handler: hello (auth + sink registration + backlog drain) and
|
|
36
|
+
* ack (cursor advance → queue prune). All other messages are ignored. */
|
|
37
|
+
private handleWsMessage;
|
|
19
38
|
}
|
|
20
39
|
export {};
|
|
21
40
|
//# sourceMappingURL=rpc-server.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-server.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"rpc-server.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-server.ts"],"names":[],"mappings":"AAqBA,KAAK,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAiCxF;;;;;;;;;;;GAWG;AACH,qBAAa,qBAAqB;IAC9B,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAc;IAC/B,oEAAoE;IACpE,OAAO,CAAC,OAAO,CAAsC;IAMrD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmC;gBAE7C,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAKjD,sCAAsC;IACtC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI;IAIjD,6DAA6D;IACvD,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAmF9B,6EAA6E;IAC7E,IAAI,IAAI,IAAI;IAuBZ,OAAO,CAAC,uBAAuB;IAgB/B;+CAC2C;YAC7B,WAAW;IAuDzB;8EAC0E;IAC1E,OAAO,CAAC,eAAe;CA0D1B"}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import { MagicContextRpcClient } from "../../shared/rpc-client";
|
|
1
2
|
import type { EmbedDetail, SidebarSnapshot, StatusDetail } from "../../shared/rpc-types";
|
|
2
3
|
export type { EmbedDetail, SidebarSnapshot, StatusDetail };
|
|
3
4
|
/** Initialize the RPC client. Call once on TUI startup. */
|
|
4
5
|
export declare function initRpcClient(directory: string): void;
|
|
5
6
|
export declare function getRpcGeneration(): number;
|
|
7
|
+
/** The live RPC client (for the WS notification socket's endpoint discovery).
|
|
8
|
+
* Null before init / after close. */
|
|
9
|
+
export declare function getRpcClient(): MagicContextRpcClient | null;
|
|
6
10
|
/** Clean up the RPC client. */
|
|
7
11
|
export declare function closeRpc(): void;
|
|
8
12
|
/** Fetch sidebar snapshot from the server via RPC. */
|
|
@@ -24,12 +28,6 @@ export declare function requestUpgrade(sessionId: string): Promise<boolean>;
|
|
|
24
28
|
export declare function dismissUpgradeReminder(sessionId: string): Promise<boolean>;
|
|
25
29
|
/** Resolve global toast duration from server config via RPC. */
|
|
26
30
|
export declare function loadToastDurationMs(): Promise<number>;
|
|
27
|
-
export interface TuiMessage {
|
|
28
|
-
id: number;
|
|
29
|
-
type: string;
|
|
30
|
-
payload: Record<string, unknown>;
|
|
31
|
-
sessionId?: string;
|
|
32
|
-
}
|
|
33
31
|
/**
|
|
34
32
|
* Fetch the current startup announcement from the server, if any.
|
|
35
33
|
* Returns `{show: false}` when there's nothing to announce or when the
|
|
@@ -44,12 +42,4 @@ export interface AnnouncementResponse {
|
|
|
44
42
|
export declare function getAnnouncement(): Promise<AnnouncementResponse>;
|
|
45
43
|
/** Mark the current ANNOUNCEMENT_VERSION as dismissed on the server. */
|
|
46
44
|
export declare function markAnnounced(): Promise<boolean>;
|
|
47
|
-
/** Poll for pending server→TUI notifications via RPC. */
|
|
48
|
-
export declare function consumeTuiMessages(sessionId: string): Promise<TuiMessage[]>;
|
|
49
|
-
/**
|
|
50
|
-
* Advance the delivered-message cursor for one active TUI session.
|
|
51
|
-
* Callers must pass only the contiguous handled prefix of the drained batch;
|
|
52
|
-
* this helper remains empty-safe and monotonic for that prefix.
|
|
53
|
-
*/
|
|
54
|
-
export declare function markTuiMessagesHandled(sessionId: string, messages: TuiMessage[]): void;
|
|
55
45
|
//# sourceMappingURL=context-db.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-db.d.ts","sourceRoot":"","sources":["../../../src/tui/data/context-db.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"context-db.d.ts","sourceRoot":"","sources":["../../../src/tui/data/context-db.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEzF,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC;AAc3D,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAOrD;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED;sCACsC;AACtC,wBAAgB,YAAY,IAAI,qBAAqB,GAAG,IAAI,CAE3D;AAED,+BAA+B;AAC/B,wBAAgB,QAAQ,IAAI,IAAI,CAM/B;AA4FD,sDAAsD;AACtD,wBAAsB,mBAAmB,CACrC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAClB,OAAO,CAAC,eAAe,CAAC,CA4B1B;AAED,wDAAwD;AACxD,wBAAsB,gBAAgB,CAClC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,CAAC,CA0CvB;AAYD,gEAAgE;AAChE,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAchG;AAED,qCAAqC;AACrC,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ5E;AAED,6CAA6C;AAC7C,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQvE;AAED;mFACmF;AACnF,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQxE;AAED;;iDAEiD;AACjD,wBAAsB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAUhF;AAED,gEAAgE;AAChE,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC,CAQ3D;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAkBrE;AAED,wEAAwE;AACxE,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAQtD"}
|