@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 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
- queue.push({ id: nextNotificationId++, type, payload, sessionId });
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
- const now = Date.now();
165555
- if (sessionId !== undefined) {
165556
- const at = lastDrainAtBySession.get(sessionId) ?? 0;
165557
- return at > 0 && now - at < TUI_CONNECTED_WINDOW_MS;
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 lastDrainAtAny > 0 && now - lastDrainAtAny < TUI_CONNECTED_WINDOW_MS;
165576
+ return false;
165560
165577
  }
165561
- var queue, nextNotificationId = 1, lastDrainAtBySession, lastDrainAtAny = 0, TUI_CONNECTED_WINDOW_MS = 3000;
165578
+ var queue, nextNotificationId = 1, sinks;
165562
165579
  var init_rpc_notifications = __esm(() => {
165563
165580
  queue = [];
165564
- lastDrainAtBySession = new Map;
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.1", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
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
- "Local embeddings work on OpenCode Desktop again (#195): /ctx-embed no longer fails with 'Unsupported device: cpu' on the Desktop app."
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
- return new Promise((resolve4, reject) => {
204633
- const server = createServer((req, res) => this.dispatch(req, res));
204634
- server.on("error", (err) => {
204635
- log(`[rpc] server error: ${err.message}`);
204636
- reject(err);
204637
- });
204638
- server.listen(0, "127.0.0.1", () => {
204639
- const addr = server.address();
204640
- if (!addr || typeof addr === "string") {
204641
- reject(new Error("Failed to get server address"));
204642
- return;
204643
- }
204644
- this.port = addr.port;
204645
- this.server = server;
204646
- try {
204647
- this.warnIfOtherLiveInstance();
204648
- const dir = dirname9(this.portFilePath);
204649
- mkdirSync11(dir, { recursive: true, mode: 448 });
204650
- try {
204651
- chmodSync3(dir, 448);
204652
- } catch {}
204653
- const tmpPath = `${this.portFilePath}.tmp`;
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
- resolve4(this.port);
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
- stop() {
204690
- if (this.server) {
204691
- this.server.close();
204692
- this.server = null;
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
- try {
204695
- unlinkSync4(this.portFilePath);
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
- res.writeHead(404);
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
- res.writeHead(401, { "Content-Type": "application/json" });
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
- res.writeHead(404, { "Content-Type": "application/json" });
204722
- res.end(JSON.stringify({ error: `Unknown method: ${method}` }));
204723
- return;
204753
+ return json2({ error: `Unknown method: ${method}` }, 404);
204724
204754
  }
204725
- let body = "";
204726
- req.on("data", (chunk) => {
204727
- body += chunk.toString();
204728
- if (body.length > 1048576) {
204729
- res.writeHead(413);
204730
- res.end("Request too large");
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
- if (body.length > 0) {
204738
- params = JSON.parse(body);
204739
- }
204762
+ params = JSON.parse(bodyText);
204740
204763
  } catch {
204741
- res.writeHead(400, { "Content-Type": "application/json" });
204742
- res.end(JSON.stringify({ error: "Invalid JSON" }));
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
- handler(params).then((result) => {
204746
- res.writeHead(200, { "Content-Type": "application/json" });
204747
- res.end(JSON.stringify(result));
204748
- }).catch((err) => {
204749
- log(`[rpc] handler error: ${method} => ${err}`);
204750
- res.writeHead(500, { "Content-Type": "application/json" });
204751
- res.end(JSON.stringify({ error: String(err) }));
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;AAqBlF,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,CAyPN"}
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.1";
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;YASvB,WAAW;IAmCzB,OAAO,CAAC,YAAY;YA4BN,WAAW;YAWX,gBAAgB;IAU9B,KAAK,IAAI,IAAI;CAKhB"}
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
- /** Push a notification for TUI to pick up via polling. */
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 draining a process that also serves
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 lost poll response re-delivers on the next poll. */
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 actively polling for notifications.
37
- * Returns true only if a TUI has drained within the last 3 seconds.
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 polling FOR THAT
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. Omit it only
43
- * for legacy/global callers that genuinely have no session context; they fall
44
- * back to "any session recently drained" (the pre-per-session behavior). */
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;AAsBD,0DAA0D;AAC1D,wBAAgB,gBAAgB,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,SAAS,CAAC,EAAE,MAAM,GACnB,IAAI,CAwBN;AAED;;;;;;;;;;;;;;;;;8EAiB8E;AAC9E,wBAAgB,kBAAkB,CAAC,cAAc,SAAI,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,eAAe,EAAE,CAkB5F;AAED;;;;;;;;6EAQ6E;AAC7E,wBAAgB,cAAc,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAO1D"}
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
- private warnIfOtherLiveInstance;
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 dispatch;
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":"AAgBA,KAAK,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAexF,qBAAa,qBAAqB;IAC9B,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAc;IAK/B,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;IAiF9B,OAAO,CAAC,uBAAuB;IAgB/B,8CAA8C;IAC9C,IAAI,IAAI,IAAI;IAYZ,OAAO,CAAC,QAAQ;CA6EnB"}
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":"AAOA,OAAO,KAAK,EACR,WAAW,EAEX,eAAe,EACf,YAAY,EACf,MAAM,wBAAwB,CAAC;AAEhC,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC;AAe3D,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAOrD;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,+BAA+B;AAC/B,wBAAgB,QAAQ,IAAI,IAAI,CAO/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,MAAM,WAAW,UAAU;IACvB,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;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;AAED,yDAAyD;AACzD,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAyBjF;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,IAAI,CAWtF"}
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"}