@cortexkit/opencode-magic-context 0.30.0 → 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.
@@ -1 +1 @@
1
- {"version":3,"file":"embedding-local.d.ts","sourceRoot":"","sources":["../../../../src/features/magic-context/memory/embedding-local.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AA2QhF,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAwBnE;AA0ED,qBAAa,sBAAuB,YAAW,iBAAiB;IAC5D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAEhC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAkC;IAClD,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,cAAc,CAA8B;IACpD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAyB;gBAE7C,KAAK,SAAgC,EAAE,cAAc,SAAM;IAMjE,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC;IAuKpC,OAAO,CAAC,sBAAsB;IAS9B,OAAO,CAAC,cAAc;IAShB,KAAK,CACP,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,gBAAgB,GAC5B,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAoCzB,UAAU,CACZ,KAAK,EAAE,MAAM,EAAE,EACf,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,gBAAgB,GAC5B,OAAO,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC,EAAE,CAAC;IAyC7B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA8B9B,QAAQ,IAAI,OAAO;CAGtB"}
1
+ {"version":3,"file":"embedding-local.d.ts","sourceRoot":"","sources":["../../../../src/features/magic-context/memory/embedding-local.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAiRhF,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAwBnE;AA0ED,qBAAa,sBAAuB,YAAW,iBAAiB;IAC5D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAEhC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAkC;IAClD,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,cAAc,CAA8B;IACpD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAyB;gBAE7C,KAAK,SAAgC,EAAE,cAAc,SAAM;IAMjE,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC;IAmLpC,OAAO,CAAC,sBAAsB;IAS9B,OAAO,CAAC,cAAc;IAShB,KAAK,CACP,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,gBAAgB,GAC5B,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAoCzB,UAAU,CACZ,KAAK,EAAE,MAAM,EAAE,EACf,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,gBAAgB,GAC5B,OAAO,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC,EAAE,CAAC;IAyC7B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA8B9B,QAAQ,IAAI,OAAO;CAGtB"}
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
@@ -166818,8 +166835,8 @@ async function injectWasmOrtForElectron() {
166818
166835
  try {
166819
166836
  const { createRequire: createRequireFn } = await import("node:module");
166820
166837
  const requireFn = createRequireFn(import.meta.url);
166821
- const pkgPath = requireFn.resolve("onnxruntime-web/package.json");
166822
- const distDir = join14(dirname8(pkgPath), "dist");
166838
+ const mainEntry = requireFn.resolve("onnxruntime-web");
166839
+ const distDir = dirname8(mainEntry);
166823
166840
  const wasmPathsPrefix = `${pathToFileURL(distDir).href}/`;
166824
166841
  if (ortWeb.env?.wasm) {
166825
166842
  ortWeb.env.wasm.wasmPaths = wasmPathsPrefix;
@@ -166939,7 +166956,7 @@ class LocalEmbeddingProvider {
166939
166956
  if (this.disposing) {
166940
166957
  return;
166941
166958
  }
166942
- await injectWasmOrtForElectron();
166959
+ const injectedWasmOrt = await injectWasmOrtForElectron();
166943
166960
  const transformersSpec = "@huggingface/transformers";
166944
166961
  const transformersModule = await import(transformersSpec);
166945
166962
  const env = transformersModule.env;
@@ -166969,7 +166986,8 @@ class LocalEmbeddingProvider {
166969
166986
  for (let attempt = 1;attempt <= MAX_ATTEMPTS; attempt++) {
166970
166987
  try {
166971
166988
  const pipeline = await withQuietConsole(() => createPipeline("feature-extraction", this.model, {
166972
- dtype: "fp32"
166989
+ dtype: "fp32",
166990
+ ...injectedWasmOrt ? { device: "auto" } : {}
166973
166991
  }));
166974
166992
  if (this.disposing) {
166975
166993
  await pipeline.dispose?.();
@@ -184722,13 +184740,11 @@ function shouldShowAnnouncement() {
184722
184740
  }
184723
184741
  return ordering > 0;
184724
184742
  }
184725
- var ANNOUNCEMENT_VERSION = "0.30.0", 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";
184726
184744
  var init_announcement = __esm(() => {
184727
184745
  init_data_path();
184728
184746
  ANNOUNCEMENT_FEATURES = [
184729
- "Setup and doctor now recognize an OpenCode Desktop install that has no CLI on PATH, instead of reporting OpenCode as not installed (#196). Desktop-only users continue setup with manual model entry.",
184730
- "TUI sidebar badge color follows your theme again (#198): it uses the theme background like sibling badges, with a fallback so the label can't disappear on transparent themes.",
184731
- "doctor clears a stale plugin cache more reliably (#199): it compares against the plugin's latest published version and preserves the cache when offline."
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."
184732
184748
  ];
184733
184749
  });
184734
184750
 
@@ -202627,7 +202643,6 @@ function calibrateBuckets(input) {
202627
202643
  // src/plugin/rpc-handlers.ts
202628
202644
  init_announcement();
202629
202645
  init_logger();
202630
- init_rpc_notifications();
202631
202646
  var workMetricsCarryBySession = new Map;
202632
202647
  function resolveSidebarWorkMetrics(db, sessionId, persistedNewWork, persistedTotalInput) {
202633
202648
  if (!openCodeDbExists()) {
@@ -203139,12 +203154,6 @@ function registerRpcHandlers(rpcServer, args) {
203139
203154
  const resolved = typeof config2.toast_duration_ms === "number" && Number.isFinite(config2.toast_duration_ms) ? config2.toast_duration_ms : 5000;
203140
203155
  return { toastDurationMs: resolved };
203141
203156
  });
203142
- rpcServer.handle("pending-notifications", async (params) => {
203143
- const lastReceivedId = Number(params.lastReceivedId ?? 0);
203144
- const sessionId = typeof params.sessionId === "string" && params.sessionId.length > 0 ? params.sessionId : undefined;
203145
- const notifications = drainNotifications(Number.isFinite(lastReceivedId) ? lastReceivedId : 0, sessionId);
203146
- return { messages: notifications };
203147
- });
203148
203157
  rpcServer.handle("get-announcement", async () => {
203149
203158
  if (!shouldShowAnnouncement()) {
203150
203159
  return { show: false };
@@ -204537,6 +204546,7 @@ init_models_dev_cache();
204537
204546
 
204538
204547
  // src/shared/rpc-server.ts
204539
204548
  init_logger();
204549
+ init_rpc_notifications();
204540
204550
  import { randomBytes, timingSafeEqual } from "node:crypto";
204541
204551
  import {
204542
204552
  chmodSync as chmodSync3,
@@ -204548,7 +204558,6 @@ import {
204548
204558
  unlinkSync as unlinkSync4,
204549
204559
  writeFileSync as writeFileSync11
204550
204560
  } from "node:fs";
204551
- import { createServer } from "node:http";
204552
204561
  import { dirname as dirname9 } from "node:path";
204553
204562
 
204554
204563
  // src/shared/rpc-utils.ts
@@ -204606,6 +204615,9 @@ function isValidPort(port) {
204606
204615
  }
204607
204616
 
204608
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;
204609
204621
  function tokensMatch(presented, expected) {
204610
204622
  const a = Buffer.from(presented, "utf8");
204611
204623
  const b = Buffer.from(expected, "utf8");
@@ -204621,6 +204633,7 @@ class MagicContextRpcServer {
204621
204633
  portFilePath;
204622
204634
  portDir;
204623
204635
  startedAt = Date.now();
204636
+ sockets = new Set;
204624
204637
  token = randomBytes(32).toString("hex");
204625
204638
  constructor(storageDir, directory) {
204626
204639
  this.portFilePath = rpcPortFilePath(storageDir, directory);
@@ -204630,49 +204643,77 @@ class MagicContextRpcServer {
204630
204643
  this.handlers.set(method, handler);
204631
204644
  }
204632
204645
  async start() {
204633
- return new Promise((resolve4, reject) => {
204634
- const server = createServer((req, res) => this.dispatch(req, res));
204635
- server.on("error", (err) => {
204636
- log(`[rpc] server error: ${err.message}`);
204637
- reject(err);
204638
- });
204639
- server.listen(0, "127.0.0.1", () => {
204640
- const addr = server.address();
204641
- if (!addr || typeof addr === "string") {
204642
- reject(new Error("Failed to get server address"));
204643
- return;
204644
- }
204645
- this.port = addr.port;
204646
- this.server = server;
204647
- try {
204648
- this.warnIfOtherLiveInstance();
204649
- const dir = dirname9(this.portFilePath);
204650
- mkdirSync11(dir, { recursive: true, mode: 448 });
204651
- try {
204652
- chmodSync3(dir, 448);
204653
- } catch {}
204654
- const tmpPath = `${this.portFilePath}.tmp`;
204655
- try {
204656
- rmSync3(tmpPath, { force: true });
204657
- } catch {}
204658
- writeFileSync11(tmpPath, JSON.stringify({
204659
- port: this.port,
204660
- pid: process.pid,
204661
- started_at: this.startedAt,
204662
- token: this.token
204663
- }), { encoding: "utf-8", mode: 384 });
204664
- renameSync5(tmpPath, this.portFilePath);
204665
- try {
204666
- chmodSync3(this.portFilePath, 384);
204667
- } catch {}
204668
- log(`[rpc] server listening on 127.0.0.1:${this.port}`);
204669
- } catch (err) {
204670
- 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);
204671
204668
  }
204672
- resolve4(this.port);
204673
- });
204674
- server.unref();
204669
+ }
204675
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 {}
204676
204717
  }
204677
204718
  warnIfOtherLiveInstance() {
204678
204719
  try {
@@ -204687,73 +204728,99 @@ class MagicContextRpcServer {
204687
204728
  }
204688
204729
  } catch {}
204689
204730
  }
204690
- stop() {
204691
- if (this.server) {
204692
- this.server.close();
204693
- 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 });
204694
204738
  }
204695
- try {
204696
- unlinkSync4(this.portFilePath);
204697
- } catch {}
204698
- }
204699
- dispatch(req, res) {
204700
- const url2 = req.url ?? "";
204701
- if (req.method === "GET" && url2 === "/health") {
204702
- res.writeHead(200, { "Content-Type": "application/json" });
204703
- res.end(JSON.stringify({ ok: true, pid: process.pid }));
204704
- return;
204739
+ if (req.method === "GET" && url2.pathname === "/health") {
204740
+ return json2({ ok: true, pid: process.pid });
204705
204741
  }
204706
- if (req.method !== "POST" || !url2.startsWith("/rpc/")) {
204707
- res.writeHead(404);
204708
- res.end("Not Found");
204709
- return;
204742
+ if (req.method !== "POST" || !url2.pathname.startsWith("/rpc/")) {
204743
+ return new Response("Not Found", { status: 404 });
204710
204744
  }
204711
- const auth = req.headers.authorization;
204745
+ const auth = req.headers.get("authorization");
204712
204746
  const presented = typeof auth === "string" ? auth.replace(/^Bearer\s+/i, "") : "";
204713
204747
  if (!tokensMatch(presented, this.token)) {
204714
- res.writeHead(401, { "Content-Type": "application/json" });
204715
- res.end(JSON.stringify({ error: "Unauthorized" }));
204716
- req.resume();
204717
- return;
204748
+ return json2({ error: "Unauthorized" }, 401);
204718
204749
  }
204719
- const method = url2.slice(5);
204750
+ const method = url2.pathname.slice(5);
204720
204751
  const handler = this.handlers.get(method);
204721
204752
  if (!handler) {
204722
- res.writeHead(404, { "Content-Type": "application/json" });
204723
- res.end(JSON.stringify({ error: `Unknown method: ${method}` }));
204724
- return;
204753
+ return json2({ error: `Unknown method: ${method}` }, 404);
204725
204754
  }
204726
- let body = "";
204727
- req.on("data", (chunk) => {
204728
- body += chunk.toString();
204729
- if (body.length > 1048576) {
204730
- res.writeHead(413);
204731
- res.end("Request too large");
204732
- req.destroy();
204733
- }
204734
- });
204735
- req.on("end", () => {
204736
- 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) {
204737
204761
  try {
204738
- if (body.length > 0) {
204739
- params = JSON.parse(body);
204740
- }
204762
+ params = JSON.parse(bodyText);
204741
204763
  } catch {
204742
- res.writeHead(400, { "Content-Type": "application/json" });
204743
- 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");
204744
204786
  return;
204745
204787
  }
204746
- handler(params).then((result) => {
204747
- res.writeHead(200, { "Content-Type": "application/json" });
204748
- res.end(JSON.stringify(result));
204749
- }).catch((err) => {
204750
- log(`[rpc] handler error: ${method} => ${err}`);
204751
- res.writeHead(500, { "Content-Type": "application/json" });
204752
- res.end(JSON.stringify({ error: String(err) }));
204753
- });
204754
- });
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
+ }
204755
204816
  }
204756
204817
  }
204818
+ function json2(body, status = 200) {
204819
+ return new Response(JSON.stringify(body), {
204820
+ status,
204821
+ headers: { "Content-Type": "application/json" }
204822
+ });
204823
+ }
204757
204824
 
204758
204825
  // src/index.ts
204759
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.0";
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.
@@ -1 +1 @@
1
- {"version":3,"file":"announcement.d.ts","sourceRoot":"","sources":["../../src/shared/announcement.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH;;;GAGG;AACH,eAAO,MAAM,oBAAoB,WAAW,CAAC;AAE7C;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,aAAa,CAAC,MAAM,CAIvD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,qDAAqD,CAAC;AAyBtF;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAGjD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAS1D;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CA0BhD"}
1
+ {"version":3,"file":"announcement.d.ts","sourceRoot":"","sources":["../../src/shared/announcement.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH;;;GAGG;AACH,eAAO,MAAM,oBAAoB,WAAW,CAAC;AAE7C;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,aAAa,CAAC,MAAM,CAEvD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,qDAAqD,CAAC;AAyBtF;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAGjD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAS1D;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CA0BhD"}
@@ -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"}