@mindstudio-ai/local-model-tunnel 0.5.61 → 0.5.63

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.
@@ -2206,6 +2206,12 @@ var ClientRegistry = class {
2206
2206
  hasConnected() {
2207
2207
  return this.clients.size > 0;
2208
2208
  }
2209
+ hasHeadless() {
2210
+ for (const client of this.clients.values()) {
2211
+ if (client.mode === "headless") return true;
2212
+ }
2213
+ return false;
2214
+ }
2209
2215
  count() {
2210
2216
  return this.clients.size;
2211
2217
  }
@@ -2245,6 +2251,96 @@ var ClientRegistry = class {
2245
2251
  }
2246
2252
  };
2247
2253
 
2254
+ // src/dev/proxy/telemetry-mock.ts
2255
+ var MAX_BODY_BYTES = 1048576;
2256
+ var SSE_KEEPALIVE_MS = 25e3;
2257
+ function tryHandleTelemetry(req, res, sseConnections) {
2258
+ const url = req.url ?? "";
2259
+ if (!url.startsWith("/_/telemetry/")) return false;
2260
+ if (!req.headers.authorization) {
2261
+ res.writeHead(401, { "Content-Type": "application/json" });
2262
+ res.end(
2263
+ JSON.stringify({
2264
+ error: "missing_authorization",
2265
+ code: "missing_authorization"
2266
+ })
2267
+ );
2268
+ return true;
2269
+ }
2270
+ if (url === "/_/telemetry/errors" && req.method === "POST") {
2271
+ handleBatchPost(req, res);
2272
+ return true;
2273
+ }
2274
+ if (url === "/_/telemetry/events" && req.method === "POST") {
2275
+ handleBatchPost(req, res);
2276
+ return true;
2277
+ }
2278
+ if (url === "/_/telemetry/presence" && req.method === "GET") {
2279
+ handlePresence(req, res, sseConnections);
2280
+ return true;
2281
+ }
2282
+ return false;
2283
+ }
2284
+ function handleBatchPost(req, res) {
2285
+ let total = 0;
2286
+ const chunks = [];
2287
+ let aborted = false;
2288
+ req.on("data", (chunk) => {
2289
+ total += chunk.length;
2290
+ if (total > MAX_BODY_BYTES) {
2291
+ aborted = true;
2292
+ req.destroy();
2293
+ return;
2294
+ }
2295
+ chunks.push(chunk);
2296
+ });
2297
+ req.on("end", () => {
2298
+ if (aborted) {
2299
+ res.writeHead(413, { "Content-Type": "application/json" });
2300
+ res.end(JSON.stringify({ accepted: 0, rejected: 0 }));
2301
+ return;
2302
+ }
2303
+ let accepted = 0;
2304
+ try {
2305
+ const body = JSON.parse(Buffer.concat(chunks).toString("utf-8"));
2306
+ if (Array.isArray(body?.events)) accepted = body.events.length;
2307
+ } catch {
2308
+ }
2309
+ res.writeHead(200, { "Content-Type": "application/json" });
2310
+ res.end(JSON.stringify({ accepted, rejected: 0 }));
2311
+ });
2312
+ req.on("error", () => {
2313
+ });
2314
+ }
2315
+ function handlePresence(req, res, sseConnections) {
2316
+ res.writeHead(200, {
2317
+ "Content-Type": "text/event-stream",
2318
+ "Cache-Control": "no-cache",
2319
+ Connection: "keep-alive",
2320
+ "X-Accel-Buffering": "no"
2321
+ });
2322
+ res.flushHeaders?.();
2323
+ sseConnections.add(res);
2324
+ const keepalive = setInterval(() => {
2325
+ try {
2326
+ res.write(": keepalive\n\n");
2327
+ } catch {
2328
+ }
2329
+ }, SSE_KEEPALIVE_MS);
2330
+ let cleaned = false;
2331
+ const cleanup = () => {
2332
+ if (cleaned) return;
2333
+ cleaned = true;
2334
+ clearInterval(keepalive);
2335
+ sseConnections.delete(res);
2336
+ log.debug("telemetry-mock", "SSE closed", { open: sseConnections.size });
2337
+ };
2338
+ req.on("close", cleanup);
2339
+ res.on("close", cleanup);
2340
+ res.on("error", cleanup);
2341
+ log.debug("telemetry-mock", "SSE opened", { open: sseConnections.size });
2342
+ }
2343
+
2248
2344
  // src/dev/stdin-commands/types.ts
2249
2345
  var CommandError = class extends Error {
2250
2346
  constructor(message, code) {
@@ -2276,6 +2372,12 @@ var DevProxy = class _DevProxy {
2276
2372
  commandQueue = [];
2277
2373
  /** Last mirror snapshot — sent to new mirror viewers so they don't wait for the next checkout. */
2278
2374
  lastMirrorSnapshot = null;
2375
+ /** Open /_/telemetry/presence SSE responses, drained on stop(). */
2376
+ sseConnections = /* @__PURE__ */ new Set();
2377
+ /** Waiters resolved when a headless (sandbox-owned) client registers via WS.
2378
+ * Lets the supervisor block on the real readiness signal instead of a
2379
+ * network-idle predicate that gets defeated by long-lived SSE responses. */
2380
+ headlessReadyWaiters = /* @__PURE__ */ new Set();
2279
2381
  /** Upstream dev server health tracking. */
2280
2382
  upstreamUp = true;
2281
2383
  healthCheckTimer = null;
@@ -2294,6 +2396,32 @@ var DevProxy = class _DevProxy {
2294
2396
  isBrowserConnected() {
2295
2397
  return this.clients.hasConnected();
2296
2398
  }
2399
+ /**
2400
+ * Resolve when a sandbox-owned headless client has registered via WS hello.
2401
+ * If one is already connected, resolves immediately. Otherwise queues a
2402
+ * one-shot waiter with a timeout. Used by `BrowserSupervisor` so it doesn't
2403
+ * declare `running` until the browser-agent is actually reachable for
2404
+ * commands — replacing the prior `networkidle0`-based readiness check
2405
+ * which is defeated by long-lived SSE responses (e.g. /_/telemetry/presence).
2406
+ */
2407
+ waitForHeadlessClient(timeoutMs = 15e3) {
2408
+ if (this.clients.hasHeadless()) return Promise.resolve();
2409
+ return new Promise((resolve3, reject) => {
2410
+ const waiter = {
2411
+ resolve: resolve3,
2412
+ reject,
2413
+ timer: setTimeout(() => {
2414
+ this.headlessReadyWaiters.delete(waiter);
2415
+ reject(
2416
+ new Error(
2417
+ `Sandbox browser-agent did not connect within ${timeoutMs}ms`
2418
+ )
2419
+ );
2420
+ }, timeoutMs)
2421
+ };
2422
+ this.headlessReadyWaiters.add(waiter);
2423
+ });
2424
+ }
2297
2425
  /**
2298
2426
  * Dispatch a command to the preferred browser client and wait for the result.
2299
2427
  * Commands are queued and executed one at a time per client (FIFO).
@@ -2473,6 +2601,18 @@ var DevProxy = class _DevProxy {
2473
2601
  this.wss.close();
2474
2602
  this.wss = null;
2475
2603
  }
2604
+ for (const sseRes of this.sseConnections) {
2605
+ try {
2606
+ sseRes.end();
2607
+ } catch {
2608
+ }
2609
+ }
2610
+ this.sseConnections.clear();
2611
+ for (const w of this.headlessReadyWaiters) {
2612
+ clearTimeout(w.timer);
2613
+ w.reject(new Error("Proxy stopped"));
2614
+ }
2615
+ this.headlessReadyWaiters.clear();
2476
2616
  if (this.server) {
2477
2617
  log.info("proxy", "Dev proxy stopping");
2478
2618
  this.server.close();
@@ -2557,6 +2697,13 @@ var DevProxy = class _DevProxy {
2557
2697
  viewport,
2558
2698
  mirror: !!msg.mirror
2559
2699
  });
2700
+ if (mode === "headless" && this.headlessReadyWaiters.size > 0) {
2701
+ for (const w of this.headlessReadyWaiters) {
2702
+ clearTimeout(w.timer);
2703
+ w.resolve();
2704
+ }
2705
+ this.headlessReadyWaiters.clear();
2706
+ }
2560
2707
  ws.send(JSON.stringify({ type: "ack", clientId }));
2561
2708
  if (mode === "mirror" && this.lastMirrorSnapshot) {
2562
2709
  try {
@@ -2791,6 +2938,9 @@ var DevProxy = class _DevProxy {
2791
2938
  clientRes.end();
2792
2939
  return;
2793
2940
  }
2941
+ if (tryHandleTelemetry(clientReq, clientRes, this.sseConnections)) {
2942
+ return;
2943
+ }
2794
2944
  if (clientReq.url?.startsWith("/_/")) {
2795
2945
  this.forwardToApi(clientReq, clientRes);
2796
2946
  return;
@@ -3541,4 +3691,4 @@ export {
3541
3691
  watchConfigFile,
3542
3692
  watchManifestFiles
3543
3693
  };
3544
- //# sourceMappingURL=chunk-45DC6K4V.js.map
3694
+ //# sourceMappingURL=chunk-7CY3GAYS.js.map