@buildautomaton/cli 0.1.61 → 0.1.62

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/cli.js CHANGED
@@ -25631,7 +25631,7 @@ var {
25631
25631
  } = import_index.default;
25632
25632
 
25633
25633
  // src/cli-version.ts
25634
- var CLI_VERSION = "0.1.61".length > 0 ? "0.1.61" : "0.0.0-dev";
25634
+ var CLI_VERSION = "0.1.62".length > 0 ? "0.1.62" : "0.0.0-dev";
25635
25635
 
25636
25636
  // src/cli/defaults.ts
25637
25637
  var DEFAULT_API_URL = process.env.BUILDAUTOMATON_API_URL ?? "https://api.buildautomaton.com";
@@ -26151,18 +26151,6 @@ var import_websocket = __toESM(require_websocket(), 1);
26151
26151
  var import_websocket_server = __toESM(require_websocket_server(), 1);
26152
26152
  var wrapper_default = import_websocket.default;
26153
26153
 
26154
- // src/net/apply-cli-outbound-network-prefs.ts
26155
- import dns from "node:dns";
26156
- var applied = false;
26157
- function applyCliOutboundNetworkPreferences() {
26158
- if (applied) return;
26159
- applied = true;
26160
- try {
26161
- dns.setDefaultResultOrder("ipv4first");
26162
- } catch {
26163
- }
26164
- }
26165
-
26166
26154
  // src/connection/cli-ws-client.ts
26167
26155
  import https from "node:https";
26168
26156
  var CLI_WEBSOCKET_CLIENT_PING_MS = 25e3;
@@ -26264,7 +26252,6 @@ function createWsBridge(options) {
26264
26252
  onCompactHeartbeatAck,
26265
26253
  isActiveSocket
26266
26254
  } = options;
26267
- applyCliOutboundNetworkPreferences();
26268
26255
  const ws = new wrapper_default(url2, buildCliWebSocketClientOptions(url2));
26269
26256
  let clearClientPing = null;
26270
26257
  function disposeClientPing() {
@@ -27706,38 +27693,38 @@ function readCodeNavCacheMigrationSql(filename) {
27706
27693
  }
27707
27694
 
27708
27695
  // src/sqlite/run-sqlite-migrations.ts
27709
- function checkpointSatisfiedByLegacyChain(applied2, replacesLegacyMigrations) {
27710
- return Array.isArray(replacesLegacyMigrations) && replacesLegacyMigrations.length > 0 && replacesLegacyMigrations.every((n) => applied2.has(n));
27696
+ function checkpointSatisfiedByLegacyChain(applied, replacesLegacyMigrations) {
27697
+ return Array.isArray(replacesLegacyMigrations) && replacesLegacyMigrations.length > 0 && replacesLegacyMigrations.every((n) => applied.has(n));
27711
27698
  }
27712
- function recordMigrationAndPruneCheckpointLegacy(db, migration, applied2) {
27699
+ function recordMigrationAndPruneCheckpointLegacy(db, migration, applied) {
27713
27700
  db.run("INSERT INTO __migrations (name) VALUES (?)", [migration.name]);
27714
- applied2.add(migration.name);
27701
+ applied.add(migration.name);
27715
27702
  if (migration.checkpoint !== true) return;
27716
27703
  const legacy = migration.replacesLegacyMigrations;
27717
27704
  if (!legacy?.length) return;
27718
27705
  for (const legacyName of legacy) {
27719
27706
  if (legacyName === migration.name) continue;
27720
27707
  db.run("DELETE FROM __migrations WHERE name = ?", [legacyName]);
27721
- applied2.delete(legacyName);
27708
+ applied.delete(legacyName);
27722
27709
  }
27723
27710
  }
27724
27711
  function runSqliteMigrations(db, migrations, bootstrapSql, label) {
27725
27712
  db.exec(bootstrapSql);
27726
27713
  const appliedRows = db.all("SELECT name FROM __migrations");
27727
- const applied2 = new Set(appliedRows.map((r) => r.name));
27714
+ const applied = new Set(appliedRows.map((r) => r.name));
27728
27715
  for (const migration of migrations) {
27729
- if (applied2.has(migration.name)) continue;
27730
- if (migration.checkpoint === true && checkpointSatisfiedByLegacyChain(applied2, migration.replacesLegacyMigrations)) {
27731
- recordMigrationAndPruneCheckpointLegacy(db, migration, applied2);
27716
+ if (applied.has(migration.name)) continue;
27717
+ if (migration.checkpoint === true && checkpointSatisfiedByLegacyChain(applied, migration.replacesLegacyMigrations)) {
27718
+ recordMigrationAndPruneCheckpointLegacy(db, migration, applied);
27732
27719
  continue;
27733
27720
  }
27734
27721
  if (migration.alreadyApplied?.(db)) {
27735
- recordMigrationAndPruneCheckpointLegacy(db, migration, applied2);
27722
+ recordMigrationAndPruneCheckpointLegacy(db, migration, applied);
27736
27723
  continue;
27737
27724
  }
27738
27725
  try {
27739
27726
  migration.migrate(db);
27740
- recordMigrationAndPruneCheckpointLegacy(db, migration, applied2);
27727
+ recordMigrationAndPruneCheckpointLegacy(db, migration, applied);
27741
27728
  } catch (e) {
27742
27729
  console.error(`[${label}] Migration failed: ${migration.name}`, e);
27743
27730
  throw e;
@@ -37713,6 +37700,9 @@ function envForSpawn(base, userEnv, ports) {
37713
37700
  });
37714
37701
  if (ports.length > 0) {
37715
37702
  out.PORTS = ports.join(",");
37703
+ if (!userKeys.has("PORT")) {
37704
+ out.PORT = String(ports[0]);
37705
+ }
37716
37706
  }
37717
37707
  if (!userKeys.has("NO_COLOR")) {
37718
37708
  delete out.NO_COLOR;
@@ -38615,172 +38605,189 @@ function createBridgePreviewStack(options) {
38615
38605
  return { previewEnvironmentManager, scheduleInitialIndexBuildsOnce, identifyReportedPaths };
38616
38606
  }
38617
38607
 
38618
- // src/net/transient-local-fetch-retry.ts
38619
- var LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS = [30, 100, 200, 400];
38620
- function sleepMs(ms) {
38621
- return new Promise((r) => setTimeout(r, ms));
38622
- }
38623
- function collectErrorText(err) {
38624
- const parts = [];
38625
- let e = err;
38626
- for (let depth = 0; depth < 6 && e != null; depth += 1) {
38627
- if (e instanceof Error) {
38628
- parts.push(e.message);
38629
- e = e.cause;
38630
- } else {
38631
- parts.push(String(e));
38632
- break;
38633
- }
38634
- }
38635
- return parts.join(" ").toLowerCase();
38636
- }
38637
- function isTransientLocalServiceError(err) {
38638
- const text = collectErrorText(err);
38639
- if (!text) return false;
38640
- return text.includes("fetch failed") || text.includes("econnrefused") || text.includes("econnreset") || text.includes("epipe") || text.includes("etimedout") || text.includes("socket hang up") || text.includes("network connection lost") || text.includes("ecanceled") || text.includes("aborted") || text.includes("und_err_socket") || text.includes("other side closed") || text.includes("eai_again");
38608
+ // src/firehose/build-cli-ws-url.ts
38609
+ function buildFirehoseCliWsUrl(baseUrl) {
38610
+ const base = baseUrl.startsWith("https") ? baseUrl.replace(/^https/, "wss") : baseUrl.replace(/^http/, "ws");
38611
+ return `${base.replace(/\/$/, "")}/ws`;
38641
38612
  }
38642
- function isIdempotentHttpMethod(method) {
38643
- const m = method.toUpperCase();
38644
- return m === "GET" || m === "HEAD" || m === "OPTIONS";
38613
+
38614
+ // src/firehose/proxy/prepare-local-proxy-request-headers.ts
38615
+ var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
38616
+ "connection",
38617
+ "keep-alive",
38618
+ "proxy-authenticate",
38619
+ "proxy-authorization",
38620
+ "te",
38621
+ "trailers",
38622
+ "transfer-encoding",
38623
+ "upgrade"
38624
+ ]);
38625
+ var BODY_HEADERS = /* @__PURE__ */ new Set(["content-length", "content-type", "content-encoding"]);
38626
+ function methodAllowsRequestBody(method) {
38627
+ const normalized = method.toUpperCase();
38628
+ return normalized !== "GET" && normalized !== "HEAD";
38645
38629
  }
38646
- async function fetchWithLocalTransientRetries(url2, init, options) {
38647
- const method = (init?.method ?? "GET").toUpperCase();
38648
- const allowRetry = isIdempotentHttpMethod(method);
38649
- const delays = options?.delaysMs ?? LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS;
38650
- const maxAttempts = options?.maxAttempts ?? (allowRetry ? Math.max(1, delays.length + 1) : 1);
38651
- let lastErr;
38652
- for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
38653
- try {
38654
- return await fetch(url2, init);
38655
- } catch (e) {
38656
- lastErr = e;
38657
- const canRetry = allowRetry && attempt < maxAttempts - 1 && isTransientLocalServiceError(e);
38658
- if (!canRetry) throw e;
38659
- const waitMs = delays[Math.min(attempt, delays.length - 1)] ?? 100;
38660
- await sleepMs(waitMs);
38661
- }
38630
+ function prepareLocalProxyRequestHeaders(method, headers, sendingBody) {
38631
+ const out = {};
38632
+ for (const [key, value] of Object.entries(headers ?? {})) {
38633
+ const lower = key.toLowerCase();
38634
+ if (lower === "host") continue;
38635
+ if (HOP_BY_HOP_HEADERS.has(lower)) continue;
38636
+ if (lower === "origin" || lower === "referer") continue;
38637
+ if (!sendingBody && BODY_HEADERS.has(lower)) continue;
38638
+ out[key] = value;
38662
38639
  }
38663
- throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
38640
+ return out;
38664
38641
  }
38665
38642
 
38666
- // src/firehose/proxy/local-proxy.ts
38667
- var ALLOWED_HOSTS = ["localhost", "127.0.0.1", "::1"];
38668
- function isAllowedHost(host) {
38669
- const h = host.replace(/^\[|\]$/g, "");
38670
- return ALLOWED_HOSTS.includes(host) || ALLOWED_HOSTS.includes(h);
38643
+ // src/firehose/proxy/build-local-proxy-http-request.ts
38644
+ var ALLOWED_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
38645
+ function willSendLocalProxyBody(request) {
38646
+ if (!methodAllowsRequestBody(request.method)) return false;
38647
+ return request.body !== void 0 && request.body !== null;
38671
38648
  }
38672
- function isIdempotentProxyMethod(method) {
38673
- const m = method.toUpperCase();
38674
- return m === "GET" || m === "HEAD" || m === "OPTIONS";
38675
- }
38676
- function checkUrlAndHost(request) {
38649
+ function buildLocalProxyHttpRequest(request) {
38677
38650
  let url2;
38678
38651
  try {
38679
38652
  url2 = new URL(request.url);
38680
38653
  } catch {
38681
38654
  return { error: `Invalid URL: ${request.url}` };
38682
38655
  }
38683
- if (!isAllowedHost(url2.hostname)) {
38656
+ const hostname2 = url2.hostname.replace(/^\[|\]$/g, "");
38657
+ if (!ALLOWED_HOSTS.has(url2.hostname) && !ALLOWED_HOSTS.has(hostname2)) {
38684
38658
  return { error: "Only localhost requests are allowed" };
38685
38659
  }
38686
- return { url: url2 };
38687
- }
38688
- async function proxyToLocal(request) {
38689
- const checked = checkUrlAndHost(request);
38690
- if ("error" in checked) {
38691
- return { id: request.id, statusCode: 0, headers: {}, body: "", error: checked.error };
38692
- }
38693
- const { url: url2 } = checked;
38660
+ const sendingBody = willSendLocalProxyBody(request);
38661
+ const headers = prepareLocalProxyRequestHeaders(request.method, request.headers, sendingBody);
38662
+ headers.host = url2.host;
38694
38663
  const isHttps = url2.protocol === "https:";
38695
- const mod = isHttps ? await import("https") : await import("http");
38696
- const opts = {
38664
+ const port = url2.port ? Number(url2.port) : isHttps ? 443 : 80;
38665
+ return {
38697
38666
  method: request.method,
38698
38667
  hostname: url2.hostname,
38699
- port: url2.port || (isHttps ? 443 : 80),
38700
- path: url2.pathname + url2.search,
38701
- headers: request.headers
38668
+ port,
38669
+ path: `${url2.pathname}${url2.search}`,
38670
+ headers,
38671
+ sendingBody,
38672
+ body: sendingBody ? request.body : void 0
38702
38673
  };
38703
- const maxAttempts = isIdempotentProxyMethod(request.method) ? LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length + 1 : 1;
38704
- for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
38705
- const once = await new Promise((resolve37) => {
38706
- const req = mod.request(opts, (res) => {
38707
- const chunks = [];
38708
- res.on("data", (c) => chunks.push(c));
38709
- res.on("end", () => {
38710
- const body = Buffer.concat(chunks).toString("utf8");
38711
- const headers = {};
38712
- for (const [k, v] of Object.entries(res.headers)) {
38713
- if (typeof v === "string") headers[k] = v;
38714
- else if (Array.isArray(v) && v[0]) headers[k] = v[0];
38715
- }
38716
- resolve37({
38717
- id: request.id,
38718
- statusCode: res.statusCode ?? 0,
38719
- headers,
38720
- body
38721
- });
38674
+ }
38675
+
38676
+ // src/firehose/proxy/constants.ts
38677
+ var LOCAL_PROXY_REQUEST_TIMEOUT_MS = 3e4;
38678
+
38679
+ // src/firehose/proxy/incoming-message-headers.ts
38680
+ function incomingMessageHeaders(res) {
38681
+ const headers = {};
38682
+ for (const [k, v] of Object.entries(res.headers)) {
38683
+ if (typeof v === "string") headers[k] = v;
38684
+ else if (Array.isArray(v) && v[0]) headers[k] = v[0];
38685
+ }
38686
+ return headers;
38687
+ }
38688
+
38689
+ // src/firehose/proxy/run-local-http-request.ts
38690
+ async function loadHttpModule(isHttps) {
38691
+ return isHttps ? import("https") : import("http");
38692
+ }
38693
+ function buildLocalHttpRequestOptions(built) {
38694
+ return {
38695
+ method: built.method,
38696
+ hostname: built.hostname,
38697
+ port: built.port,
38698
+ path: built.path,
38699
+ headers: built.headers
38700
+ };
38701
+ }
38702
+ function runLocalHttpRequestOnce(mod, built) {
38703
+ const opts = buildLocalHttpRequestOptions(built);
38704
+ return new Promise((resolve37) => {
38705
+ const req = mod.request(opts, (res) => {
38706
+ const chunks = [];
38707
+ res.on("data", (chunk) => chunks.push(chunk));
38708
+ res.on("end", () => {
38709
+ resolve37({
38710
+ statusCode: res.statusCode ?? 0,
38711
+ headers: incomingMessageHeaders(res),
38712
+ body: Buffer.concat(chunks)
38722
38713
  });
38723
38714
  });
38724
- req.on("error", (err) => {
38715
+ res.on("error", (err) => {
38725
38716
  resolve37({
38726
- id: request.id,
38727
38717
  statusCode: 0,
38728
38718
  headers: {},
38729
- body: "",
38719
+ body: Buffer.alloc(0),
38730
38720
  error: err.message
38731
38721
  });
38732
38722
  });
38733
- const method = request.method.toUpperCase();
38734
- if (request.body && method !== "GET" && method !== "HEAD") req.write(request.body);
38735
- req.end();
38736
38723
  });
38737
- const errMsg = once.error ?? "";
38738
- const canRetry = attempt < maxAttempts - 1 && errMsg !== "" && isTransientLocalServiceError(new Error(errMsg));
38739
- if (!canRetry) return once;
38740
- const waitMs = LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS[Math.min(attempt, LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length - 1)] ?? 100;
38741
- await sleepMs(waitMs);
38742
- }
38743
- throw new Error("Local proxy retry loop exited unexpectedly");
38724
+ req.setTimeout(LOCAL_PROXY_REQUEST_TIMEOUT_MS, () => {
38725
+ req.destroy(new Error(`Local preview request timed out after ${LOCAL_PROXY_REQUEST_TIMEOUT_MS}ms`));
38726
+ });
38727
+ req.on("error", (err) => {
38728
+ resolve37({
38729
+ statusCode: 0,
38730
+ headers: {},
38731
+ body: Buffer.alloc(0),
38732
+ error: err.message
38733
+ });
38734
+ });
38735
+ if (built.sendingBody && built.body != null) {
38736
+ req.write(built.body);
38737
+ }
38738
+ req.end();
38739
+ });
38744
38740
  }
38741
+
38742
+ // src/firehose/proxy/proxy-to-local-streaming.ts
38745
38743
  async function proxyToLocalStreaming(request, callbacks) {
38746
- const checked = checkUrlAndHost(request);
38747
- if ("error" in checked) {
38748
- callbacks.onError(checked.error);
38744
+ const built = buildLocalProxyHttpRequest(request);
38745
+ if ("error" in built) {
38746
+ callbacks.onError(built.error);
38749
38747
  return;
38750
38748
  }
38751
- try {
38752
- const method = request.method.toUpperCase();
38753
- const init = {
38754
- method: request.method,
38755
- headers: request.headers
38756
- };
38757
- if (request.body !== void 0 && request.body !== null && method !== "GET" && method !== "HEAD") {
38758
- init.body = request.body;
38759
- }
38760
- const res = await fetchWithLocalTransientRetries(request.url, init);
38761
- const headers = {};
38762
- res.headers.forEach((value, key) => {
38763
- headers[key] = value;
38764
- });
38765
- callbacks.onStart(res.status, headers);
38766
- const reader = res.body?.getReader();
38767
- if (!reader) {
38768
- callbacks.onEnd();
38769
- return;
38770
- }
38771
- try {
38772
- for (; ; ) {
38773
- const { done, value } = await reader.read();
38774
- if (done) break;
38775
- callbacks.onChunk(value);
38749
+ const isHttps = request.url.startsWith("https:");
38750
+ const mod = await loadHttpModule(isHttps);
38751
+ const opts = buildLocalHttpRequestOptions(built);
38752
+ await new Promise((resolve37) => {
38753
+ const req = mod.request(opts, (res) => {
38754
+ try {
38755
+ callbacks.onStart(res.statusCode ?? 0, incomingMessageHeaders(res));
38756
+ } catch (err) {
38757
+ req.destroy();
38758
+ callbacks.onError(err instanceof Error ? err.message : String(err));
38759
+ resolve37();
38760
+ return;
38776
38761
  }
38777
- } finally {
38778
- reader.releaseLock();
38762
+ res.on("data", (chunk) => {
38763
+ try {
38764
+ callbacks.onChunk(new Uint8Array(chunk));
38765
+ } catch (err) {
38766
+ req.destroy();
38767
+ callbacks.onError(err instanceof Error ? err.message : String(err));
38768
+ }
38769
+ });
38770
+ res.on("end", () => {
38771
+ callbacks.onEnd();
38772
+ resolve37();
38773
+ });
38774
+ res.on("error", (err) => {
38775
+ callbacks.onError(err.message);
38776
+ resolve37();
38777
+ });
38778
+ });
38779
+ req.setTimeout(LOCAL_PROXY_REQUEST_TIMEOUT_MS, () => {
38780
+ req.destroy(new Error(`Local preview request timed out after ${LOCAL_PROXY_REQUEST_TIMEOUT_MS}ms`));
38781
+ });
38782
+ req.on("error", (err) => {
38783
+ callbacks.onError(err.message);
38784
+ resolve37();
38785
+ });
38786
+ if (built.sendingBody && built.body != null) {
38787
+ req.write(built.body);
38779
38788
  }
38780
- callbacks.onEnd();
38781
- } catch (err) {
38782
- callbacks.onError(err instanceof Error ? err.message : String(err));
38783
- }
38789
+ req.end();
38790
+ });
38784
38791
  }
38785
38792
 
38786
38793
  // src/firehose/proxy/start-streaming-proxy.ts
@@ -38790,15 +38797,7 @@ function startStreamingProxy(ws, log2, pr) {
38790
38797
  if (ws.readyState !== wrapper_default.OPEN) {
38791
38798
  throw new Error("Preview stream interrupted (firehose connection closed)");
38792
38799
  }
38793
- const forwardedHeaders = { ...headers };
38794
- const ce = "content-encoding";
38795
- const cl = "content-length";
38796
- const keys = Object.keys(forwardedHeaders).filter(
38797
- (k) => k.toLowerCase() !== ce && k.toLowerCase() !== cl
38798
- );
38799
- const filtered = {};
38800
- for (const k of keys) if (forwardedHeaders[k] != null) filtered[k] = forwardedHeaders[k];
38801
- sendWsMessage(ws, { type: "proxy_result_start", id: pr.id, statusCode, headers: filtered });
38800
+ sendWsMessage(ws, { type: "proxy_result_start", id: pr.id, statusCode, headers });
38802
38801
  },
38803
38802
  onChunk: (chunk) => {
38804
38803
  const idBuf = Buffer.from(pr.id, "utf8");
@@ -38815,10 +38814,19 @@ function startStreamingProxy(ws, log2, pr) {
38815
38814
  });
38816
38815
  }
38817
38816
 
38818
- // src/firehose/build-cli-ws-url.ts
38819
- function buildFirehoseCliWsUrl(baseUrl) {
38820
- const base = baseUrl.startsWith("https") ? baseUrl.replace(/^https/, "wss") : baseUrl.replace(/^http/, "ws");
38821
- return `${base.replace(/\/$/, "")}/ws`;
38817
+ // src/firehose/create-firehose-message-deps.ts
38818
+ function createFirehoseMessageDeps(ws, log2, previewEnvironmentManager) {
38819
+ const pendingProxyBody = /* @__PURE__ */ new Map();
38820
+ const earlyProxyBinaryBody = /* @__PURE__ */ new Map();
38821
+ const deps = {
38822
+ ws,
38823
+ log: log2,
38824
+ previewEnvironmentManager,
38825
+ pendingProxyBody,
38826
+ earlyProxyBinaryBody,
38827
+ startStreamingProxy: (pr) => startStreamingProxy(ws, log2, pr)
38828
+ };
38829
+ return deps;
38822
38830
  }
38823
38831
 
38824
38832
  // src/firehose/routing/firehose-message-router.ts
@@ -38853,31 +38861,90 @@ var handlePreviewEnvironmentLogsViewerClose = (msg, deps) => {
38853
38861
  deps.previewEnvironmentManager.handleFirehoseLogViewerClose(environmentId, viewerId);
38854
38862
  };
38855
38863
 
38864
+ // src/net/transient-local-fetch-retry.ts
38865
+ var LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS = [30, 100, 200, 400];
38866
+ function sleepMs(ms) {
38867
+ return new Promise((r) => setTimeout(r, ms));
38868
+ }
38869
+ function collectErrorText(err) {
38870
+ const parts = [];
38871
+ let e = err;
38872
+ for (let depth = 0; depth < 6 && e != null; depth += 1) {
38873
+ if (e instanceof Error) {
38874
+ parts.push(e.message);
38875
+ e = e.cause;
38876
+ } else {
38877
+ parts.push(String(e));
38878
+ break;
38879
+ }
38880
+ }
38881
+ return parts.join(" ").toLowerCase();
38882
+ }
38883
+ function isTransientLocalServiceError(err) {
38884
+ const text = collectErrorText(err);
38885
+ if (!text) return false;
38886
+ return text.includes("fetch failed") || text.includes("econnrefused") || text.includes("econnreset") || text.includes("epipe") || text.includes("etimedout") || text.includes("socket hang up") || text.includes("network connection lost") || text.includes("ecanceled") || text.includes("aborted") || text.includes("und_err_socket") || text.includes("other side closed") || text.includes("eai_again");
38887
+ }
38888
+
38889
+ // src/firehose/proxy/proxy-to-local.ts
38890
+ function isIdempotentProxyMethod(method) {
38891
+ const m = method.toUpperCase();
38892
+ return m === "GET" || m === "HEAD" || m === "OPTIONS";
38893
+ }
38894
+ async function proxyToLocal(request) {
38895
+ const built = buildLocalProxyHttpRequest(request);
38896
+ if ("error" in built) {
38897
+ return { id: request.id, statusCode: 0, headers: {}, body: "", error: built.error };
38898
+ }
38899
+ const isHttps = request.url.startsWith("https:");
38900
+ const mod = await loadHttpModule(isHttps);
38901
+ const maxAttempts = isIdempotentProxyMethod(request.method) ? LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length + 1 : 1;
38902
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
38903
+ const once = await runLocalHttpRequestOnce(mod, built);
38904
+ const errMsg = once.error ?? "";
38905
+ const canRetry = attempt < maxAttempts - 1 && errMsg !== "" && isTransientLocalServiceError(new Error(errMsg));
38906
+ if (!canRetry) {
38907
+ return {
38908
+ id: request.id,
38909
+ statusCode: once.statusCode,
38910
+ headers: once.headers,
38911
+ body: once.body.toString("utf8"),
38912
+ error: once.error
38913
+ };
38914
+ }
38915
+ const waitMs = LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS[Math.min(attempt, LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length - 1)] ?? 100;
38916
+ await sleepMs(waitMs);
38917
+ }
38918
+ throw new Error("Local proxy retry loop exited unexpectedly");
38919
+ }
38920
+
38856
38921
  // src/firehose/routing/handlers/proxy-message.ts
38857
38922
  var handleProxyMessage = (msg, deps) => {
38858
38923
  const proxy = msg.proxy;
38859
38924
  if (!proxy || typeof proxy !== "object") return;
38860
38925
  const pr = proxy;
38861
38926
  if (pr.streamResponse) {
38862
- const bodyLength = pr.bodyLength ?? 0;
38927
+ const bodyLength = methodAllowsRequestBody(pr.method) ? pr.bodyLength ?? 0 : 0;
38928
+ const streamingRequest = {
38929
+ id: pr.id,
38930
+ method: pr.method,
38931
+ url: pr.url,
38932
+ headers: pr.headers,
38933
+ streamResponse: true
38934
+ };
38863
38935
  if (bodyLength > 0) {
38864
- deps.pendingProxyBody.set(pr.id, {
38865
- pr: {
38866
- id: pr.id,
38867
- method: pr.method,
38868
- url: pr.url,
38869
- headers: pr.headers,
38870
- streamResponse: true
38871
- }
38872
- });
38936
+ const earlyBody = deps.earlyProxyBinaryBody.get(pr.id);
38937
+ if (earlyBody !== void 0) {
38938
+ deps.earlyProxyBinaryBody.delete(pr.id);
38939
+ deps.startStreamingProxy({
38940
+ ...streamingRequest,
38941
+ body: earlyBody.length > 0 ? earlyBody : void 0
38942
+ });
38943
+ } else {
38944
+ deps.pendingProxyBody.set(pr.id, { pr: streamingRequest });
38945
+ }
38873
38946
  } else {
38874
- deps.startStreamingProxy({
38875
- id: pr.id,
38876
- method: pr.method,
38877
- url: pr.url,
38878
- headers: pr.headers,
38879
- streamResponse: true
38880
- });
38947
+ deps.startStreamingProxy(streamingRequest);
38881
38948
  }
38882
38949
  return;
38883
38950
  }
@@ -38908,37 +38975,63 @@ function dispatchFirehoseJsonMessage(msg, deps) {
38908
38975
 
38909
38976
  // src/firehose/routing/try-consume-binary-proxy-body.ts
38910
38977
  var PROXY_ID_BYTES = 36;
38978
+ var PROXY_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
38911
38979
  function tryConsumeBinaryProxyBody(raw, deps) {
38912
38980
  if (raw.length < PROXY_ID_BYTES) return false;
38913
38981
  const id = raw.slice(0, PROXY_ID_BYTES).toString("utf8");
38914
- const pending2 = deps.pendingProxyBody.get(id);
38915
- if (!pending2) return false;
38916
- deps.pendingProxyBody.delete(id);
38982
+ if (!PROXY_ID_RE.test(id)) return false;
38917
38983
  const body = raw.slice(PROXY_ID_BYTES);
38918
- deps.startStreamingProxy({
38919
- ...pending2.pr,
38920
- body: body.length > 0 ? new Uint8Array(body) : void 0
38921
- });
38984
+ const bodyBytes = body.length > 0 ? new Uint8Array(body) : new Uint8Array(0);
38985
+ const pending2 = deps.pendingProxyBody.get(id);
38986
+ if (pending2) {
38987
+ deps.pendingProxyBody.delete(id);
38988
+ deps.startStreamingProxy({
38989
+ ...pending2.pr,
38990
+ body: bodyBytes.length > 0 ? bodyBytes : void 0
38991
+ });
38992
+ return true;
38993
+ }
38994
+ deps.earlyProxyBinaryBody.set(id, bodyBytes);
38922
38995
  return true;
38923
38996
  }
38924
38997
 
38925
- // src/firehose/connect-firehose.ts
38926
- function connectFirehose(options) {
38998
+ // src/firehose/handle-inbound-firehose-frame.ts
38999
+ function handleInboundFirehoseFrame(raw, deps) {
39000
+ if (Buffer.isBuffer(raw) && tryConsumeBinaryProxyBody(raw, deps)) {
39001
+ return;
39002
+ }
39003
+ let msg;
39004
+ try {
39005
+ const text = Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw);
39006
+ msg = JSON.parse(text);
39007
+ } catch {
39008
+ return;
39009
+ }
39010
+ if (msg.type === "proxy") {
39011
+ dispatchFirehoseJsonMessage(msg, deps);
39012
+ return;
39013
+ }
39014
+ setImmediate(() => {
39015
+ dispatchFirehoseJsonMessage(msg, deps);
39016
+ });
39017
+ }
39018
+
39019
+ // src/firehose/wire-firehose-websocket.ts
39020
+ function wireFirehoseWebSocket(options) {
38927
39021
  const {
38928
- firehoseServerUrl,
39022
+ ws,
39023
+ deps,
39024
+ log: log2,
39025
+ previewEnvironmentManager,
38929
39026
  workspaceId,
38930
39027
  bridgeName,
38931
39028
  proxyPorts,
38932
- log: log2,
38933
- previewEnvironmentManager,
38934
39029
  onOpen,
38935
39030
  onClose,
38936
39031
  suppressWebSocketErrors
38937
39032
  } = options;
38938
- const wsUrl = buildFirehoseCliWsUrl(firehoseServerUrl);
38939
- applyCliOutboundNetworkPreferences();
38940
- const ws = new wrapper_default(wsUrl, buildCliWebSocketClientOptions(wsUrl));
38941
39033
  let clearClientPing = null;
39034
+ let identifyParams = { workspaceId, bridgeName, proxyPorts };
38942
39035
  function disposeClientPing() {
38943
39036
  clearClientPing?.();
38944
39037
  clearClientPing = null;
@@ -38946,36 +39039,32 @@ function connectFirehose(options) {
38946
39039
  const firehoseSend = (payload) => {
38947
39040
  sendWsMessage(ws, payload);
38948
39041
  };
38949
- const pendingProxyBody = /* @__PURE__ */ new Map();
38950
- const deps = {
38951
- ws,
38952
- log: log2,
38953
- previewEnvironmentManager,
38954
- pendingProxyBody,
38955
- startStreamingProxy: (pr) => startStreamingProxy(ws, log2, pr)
38956
- };
39042
+ function sendIdentify(params) {
39043
+ identifyParams = params;
39044
+ if (ws.readyState !== wrapper_default.OPEN) return;
39045
+ sendWsMessage(ws, {
39046
+ type: "identify",
39047
+ workspaceId: params.workspaceId,
39048
+ bridgeName: params.bridgeName,
39049
+ proxyPorts: params.proxyPorts
39050
+ });
39051
+ }
39052
+ function detachPreviewEnvironmentManager() {
39053
+ previewEnvironmentManager.detachFirehose();
39054
+ }
38957
39055
  ws.on("open", () => {
38958
39056
  disposeClientPing();
38959
39057
  clearClientPing = attachWebSocketClientPing(ws, CLI_WEBSOCKET_CLIENT_PING_MS);
38960
39058
  onOpen?.();
38961
39059
  previewEnvironmentManager.attachFirehose(firehoseSend);
38962
- sendWsMessage(ws, { type: "identify", workspaceId, bridgeName, proxyPorts });
39060
+ sendIdentify(identifyParams);
38963
39061
  });
38964
39062
  ws.on("message", (raw) => {
38965
- if (Buffer.isBuffer(raw) && tryConsumeBinaryProxyBody(raw, deps)) {
38966
- return;
38967
- }
38968
- setImmediate(() => {
38969
- try {
38970
- const text = Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw);
38971
- dispatchFirehoseJsonMessage(JSON.parse(text), deps);
38972
- } catch {
38973
- }
38974
- });
39063
+ handleInboundFirehoseFrame(raw, deps);
38975
39064
  });
38976
39065
  ws.on("close", (code, reason) => {
38977
39066
  disposeClientPing();
38978
- previewEnvironmentManager.detachFirehose();
39067
+ detachPreviewEnvironmentManager();
38979
39068
  const reasonStr = typeof reason === "string" ? reason : reason.toString();
38980
39069
  onClose?.(code, reasonStr);
38981
39070
  });
@@ -38988,25 +39077,64 @@ function connectFirehose(options) {
38988
39077
  safeCloseWebSocket(ws);
38989
39078
  }
38990
39079
  });
39080
+ return { disposeClientPing, sendIdentify, detachPreviewEnvironmentManager };
39081
+ }
39082
+
39083
+ // src/firehose/connect-firehose.ts
39084
+ function connectFirehose(options) {
39085
+ const {
39086
+ firehoseServerUrl,
39087
+ workspaceId,
39088
+ bridgeName,
39089
+ proxyPorts,
39090
+ log: log2,
39091
+ previewEnvironmentManager,
39092
+ onOpen,
39093
+ onClose,
39094
+ suppressWebSocketErrors
39095
+ } = options;
39096
+ const wsUrl = buildFirehoseCliWsUrl(firehoseServerUrl);
39097
+ const ws = new wrapper_default(wsUrl, buildCliWebSocketClientOptions(wsUrl));
39098
+ const deps = createFirehoseMessageDeps(ws, log2, previewEnvironmentManager);
39099
+ const wired = wireFirehoseWebSocket({
39100
+ ws,
39101
+ deps,
39102
+ log: log2,
39103
+ previewEnvironmentManager,
39104
+ workspaceId,
39105
+ bridgeName,
39106
+ proxyPorts,
39107
+ onOpen,
39108
+ onClose,
39109
+ suppressWebSocketErrors
39110
+ });
38991
39111
  return {
38992
39112
  close() {
38993
- disposeClientPing();
38994
- previewEnvironmentManager.detachFirehose();
39113
+ wired.disposeClientPing();
39114
+ wired.detachPreviewEnvironmentManager();
38995
39115
  safeCloseWebSocket(ws);
38996
39116
  },
38997
- isConnected: () => ws.readyState === wrapper_default.OPEN
39117
+ isConnected: () => ws.readyState === wrapper_default.OPEN,
39118
+ updateIdentify: wired.sendIdentify
38998
39119
  };
38999
39120
  }
39000
39121
 
39001
- // src/connection/attach-firehose-after-identified.ts
39002
- function attachFirehoseAfterIdentified(ctx, params) {
39003
- const { state, previewEnvironmentManager, logFn } = ctx;
39004
- function clearFirehoseReconnectTimer() {
39005
- if (state.firehoseReconnectTimeout != null) {
39006
- clearTimeout(state.firehoseReconnectTimeout);
39007
- state.firehoseReconnectTimeout = null;
39008
- }
39009
- }
39122
+ // src/connection/firehose/compare-firehose-params.ts
39123
+ function proxyPortsEqual(a, b) {
39124
+ if (a.length !== b.length) return false;
39125
+ const sa = [...a].sort((x, y) => x - y);
39126
+ const sb = [...b].sort((x, y) => x - y);
39127
+ return sa.every((v, i) => v === sb[i]);
39128
+ }
39129
+ function sameFirehoseBridge(a, b) {
39130
+ return a.firehoseServerUrl === b.firehoseServerUrl && a.workspaceId === b.workspaceId && a.bridgeName === b.bridgeName;
39131
+ }
39132
+ function shouldReuseOpenFirehoseConnection(prev, next, isConnected) {
39133
+ return Boolean(prev && sameFirehoseBridge(prev, next) && isConnected());
39134
+ }
39135
+
39136
+ // src/connection/firehose/create-firehose-connect-callbacks.ts
39137
+ function createFirehoseConnectCallbacks(state, myGen, logFn, scheduleFirehoseRetryAfterDrop) {
39010
39138
  function firehoseCtx() {
39011
39139
  return {
39012
39140
  closedByUser: state.closedByUser,
@@ -39015,6 +39143,55 @@ function attachFirehoseAfterIdentified(ctx, params) {
39015
39143
  firehoseQuiet: state.firehoseQuiet
39016
39144
  };
39017
39145
  }
39146
+ return {
39147
+ onOpen: () => {
39148
+ if (myGen !== state.firehoseGeneration) return;
39149
+ try {
39150
+ clearFirehoseReconnectQuietOnOpen(
39151
+ {
39152
+ firehoseQuiet: state.firehoseQuiet,
39153
+ firehoseOutage: state.firehoseOutage,
39154
+ lastFirehoseReconnectCloseMeta: state.lastFirehoseReconnectCloseMeta
39155
+ },
39156
+ logFn
39157
+ );
39158
+ } catch {
39159
+ }
39160
+ const logOpenAsFirehoseReconnect = state.firehoseReconnectAttempt > 0;
39161
+ state.firehoseReconnectAttempt = 0;
39162
+ if (!logOpenAsFirehoseReconnect) {
39163
+ try {
39164
+ logFn("Connected to preview tunnel (local HTTP proxy and dev logs).");
39165
+ } catch {
39166
+ }
39167
+ }
39168
+ },
39169
+ onClose: (code, reason) => {
39170
+ if (myGen !== state.firehoseGeneration) return;
39171
+ state.firehoseHandle = null;
39172
+ if (state.closedByUser) return;
39173
+ state.lastFirehoseReconnectCloseMeta = { code, reason };
39174
+ try {
39175
+ beginFirehoseDeferredDisconnect(firehoseCtx(), code, reason, logFn);
39176
+ } catch {
39177
+ }
39178
+ try {
39179
+ scheduleFirehoseRetryAfterDrop({ code, reason });
39180
+ } catch {
39181
+ }
39182
+ }
39183
+ };
39184
+ }
39185
+
39186
+ // src/connection/firehose/create-firehose-reconnect-scheduler.ts
39187
+ function createFirehoseReconnectScheduler(ctx, reconnect) {
39188
+ const { state, logFn } = ctx;
39189
+ function clearFirehoseReconnectTimer() {
39190
+ if (state.firehoseReconnectTimeout != null) {
39191
+ clearTimeout(state.firehoseReconnectTimeout);
39192
+ state.firehoseReconnectTimeout = null;
39193
+ }
39194
+ }
39018
39195
  function scheduleFirehoseRetryAfterDrop(closeMeta) {
39019
39196
  if (state.closedByUser) return;
39020
39197
  const meta = closeMeta ?? state.lastFirehoseReconnectCloseMeta ?? void 0;
@@ -39036,30 +39213,60 @@ function attachFirehoseAfterIdentified(ctx, params) {
39036
39213
  state.firehoseReconnectTimeout = id;
39037
39214
  },
39038
39215
  shouldAbortBeforeRun: () => state.closedByUser,
39039
- run: () => {
39040
- const p = state.lastFirehoseParams;
39041
- if (!p) return;
39042
- attachFirehoseAfterIdentified(ctx, p);
39043
- },
39216
+ run: reconnect,
39044
39217
  reschedule: () => {
39045
39218
  scheduleFirehoseRetryAfterDrop();
39046
39219
  }
39047
39220
  });
39048
39221
  }
39049
- state.lastFirehoseParams = params;
39050
- clearFirehoseReconnectTimer();
39051
- if (state.firehoseReconnectAttempt === 0) {
39052
- try {
39053
- logFn("Connecting to preview tunnel (local HTTP proxy and dev logs)\u2026");
39054
- } catch {
39055
- }
39222
+ return { clearFirehoseReconnectTimer, scheduleFirehoseRetryAfterDrop };
39223
+ }
39224
+ function logInitialFirehoseConnect(state, logFn) {
39225
+ if (state.firehoseReconnectAttempt !== 0) return;
39226
+ try {
39227
+ logFn("Connecting to preview tunnel (local HTTP proxy and dev logs)\u2026");
39228
+ } catch {
39056
39229
  }
39230
+ }
39231
+ function replaceFirehoseHandle(state) {
39057
39232
  state.firehoseGeneration += 1;
39058
39233
  const myGen = state.firehoseGeneration;
39059
39234
  if (state.firehoseHandle) {
39060
39235
  state.firehoseHandle.close();
39061
39236
  state.firehoseHandle = null;
39062
39237
  }
39238
+ return myGen;
39239
+ }
39240
+
39241
+ // src/connection/firehose/attach-firehose-after-identified.ts
39242
+ function attachFirehoseAfterIdentified(ctx, params) {
39243
+ const { state, previewEnvironmentManager, logFn } = ctx;
39244
+ const prev = state.lastFirehoseParams;
39245
+ if (shouldReuseOpenFirehoseConnection(prev, params, () => state.firehoseHandle?.isConnected() ?? false)) {
39246
+ state.lastFirehoseParams = params;
39247
+ if (prev && !proxyPortsEqual(prev.proxyPorts, params.proxyPorts)) {
39248
+ state.firehoseHandle?.updateIdentify(params);
39249
+ }
39250
+ return;
39251
+ }
39252
+ const { clearFirehoseReconnectTimer, scheduleFirehoseRetryAfterDrop } = createFirehoseReconnectScheduler(
39253
+ ctx,
39254
+ () => {
39255
+ const p = state.lastFirehoseParams;
39256
+ if (!p) return;
39257
+ attachFirehoseAfterIdentified(ctx, p);
39258
+ }
39259
+ );
39260
+ state.lastFirehoseParams = params;
39261
+ clearFirehoseReconnectTimer();
39262
+ logInitialFirehoseConnect(state, logFn);
39263
+ const myGen = replaceFirehoseHandle(state);
39264
+ const { onOpen, onClose } = createFirehoseConnectCallbacks(
39265
+ state,
39266
+ myGen,
39267
+ logFn,
39268
+ scheduleFirehoseRetryAfterDrop
39269
+ );
39063
39270
  state.firehoseHandle = connectFirehose({
39064
39271
  firehoseServerUrl: params.firehoseServerUrl,
39065
39272
  workspaceId: params.workspaceId,
@@ -39068,45 +39275,17 @@ function attachFirehoseAfterIdentified(ctx, params) {
39068
39275
  log: logFn,
39069
39276
  previewEnvironmentManager,
39070
39277
  suppressWebSocketErrors: () => !state.closedByUser && state.firehoseOutage.startedAt != null,
39071
- onOpen: () => {
39072
- if (myGen !== state.firehoseGeneration) return;
39073
- try {
39074
- clearFirehoseReconnectQuietOnOpen(
39075
- {
39076
- firehoseQuiet: state.firehoseQuiet,
39077
- firehoseOutage: state.firehoseOutage,
39078
- lastFirehoseReconnectCloseMeta: state.lastFirehoseReconnectCloseMeta
39079
- },
39080
- logFn
39081
- );
39082
- } catch {
39083
- }
39084
- const logOpenAsFirehoseReconnect = state.firehoseReconnectAttempt > 0;
39085
- state.firehoseReconnectAttempt = 0;
39086
- if (!logOpenAsFirehoseReconnect) {
39087
- try {
39088
- logFn("Connected to preview tunnel (local HTTP proxy and dev logs).");
39089
- } catch {
39090
- }
39091
- }
39092
- },
39093
- onClose: (code, reason) => {
39094
- if (myGen !== state.firehoseGeneration) return;
39095
- state.firehoseHandle = null;
39096
- if (state.closedByUser) return;
39097
- state.lastFirehoseReconnectCloseMeta = { code, reason };
39098
- try {
39099
- beginFirehoseDeferredDisconnect(firehoseCtx(), code, reason, logFn);
39100
- } catch {
39101
- }
39102
- try {
39103
- scheduleFirehoseRetryAfterDrop({ code, reason });
39104
- } catch {
39105
- }
39106
- }
39278
+ onOpen,
39279
+ onClose
39107
39280
  });
39108
39281
  }
39109
39282
 
39283
+ // src/connection/firehose/update-firehose-identify-on-open-connection.ts
39284
+ function updateFirehoseIdentifyOnOpenConnection(state, params) {
39285
+ state.lastFirehoseParams = params;
39286
+ state.firehoseHandle?.updateIdentify(params);
39287
+ }
39288
+
39110
39289
  // src/connection/create-bridge-identified-handler.ts
39111
39290
  function createOnBridgeIdentified(opts) {
39112
39291
  const { previewEnvironmentManager, firehoseServerUrl, workspaceId, state, logFn } = opts;
@@ -39143,6 +39322,7 @@ function createBridgeMessageDeps(params) {
39143
39322
  reportAutoDetectedAgents,
39144
39323
  warmupAgentCapabilitiesOnConnect: warmupAgentCapabilitiesOnConnect2,
39145
39324
  previewEnvironmentManager,
39325
+ updateFirehoseProxyPorts,
39146
39326
  e2ee,
39147
39327
  cloudApiBaseUrl,
39148
39328
  getCloudAccessToken
@@ -39161,6 +39341,7 @@ function createBridgeMessageDeps(params) {
39161
39341
  reportAutoDetectedAgents,
39162
39342
  warmupAgentCapabilitiesOnConnect: warmupAgentCapabilitiesOnConnect2,
39163
39343
  previewEnvironmentManager,
39344
+ updateFirehoseProxyPorts,
39164
39345
  e2ee,
39165
39346
  cloudApiBaseUrl,
39166
39347
  getCloudAccessToken
@@ -39421,6 +39602,11 @@ function createBridgeRuntimeMessageSetup(options) {
39421
39602
  getWs
39422
39603
  }),
39423
39604
  previewEnvironmentManager,
39605
+ updateFirehoseProxyPorts: (proxyPorts) => {
39606
+ const p = state.lastFirehoseParams;
39607
+ if (!p) return;
39608
+ updateFirehoseIdentifyOnOpenConnection(state, { ...p, proxyPorts });
39609
+ },
39424
39610
  e2ee,
39425
39611
  cloudApiBaseUrl: apiUrl,
39426
39612
  getCloudAccessToken: () => tokens.accessToken
@@ -46690,16 +46876,16 @@ async function applySessionWipToPreviewCheckouts(options) {
46690
46876
  return;
46691
46877
  }
46692
46878
  await gitResetWorktreeToBranch(previewCheckout, deployBranch);
46693
- const applied2 = await applyWorkingTreeWip(
46879
+ const applied = await applyWorkingTreeWip(
46694
46880
  previewCheckout,
46695
46881
  state.sessionCheckout,
46696
46882
  state.wip,
46697
46883
  log2
46698
46884
  );
46699
- if (!applied2.ok) {
46885
+ if (!applied.ok) {
46700
46886
  failure = {
46701
46887
  ok: false,
46702
- error: applied2.error ?? `Failed to apply session WIP to preview (${state.repoPathRelativeToWorkspaceRoot})`
46888
+ error: applied.error ?? `Failed to apply session WIP to preview (${state.repoPathRelativeToWorkspaceRoot})`
46703
46889
  };
46704
46890
  }
46705
46891
  });
@@ -46798,7 +46984,7 @@ async function deploySessionToPreviewEnvironment(options) {
46798
46984
  return { ok: false, error: "Failed to create preview environment worktrees" };
46799
46985
  }
46800
46986
  await yieldToEventLoop2();
46801
- const applied2 = await applySessionWipToPreviewCheckouts({
46987
+ const applied = await applySessionWipToPreviewCheckouts({
46802
46988
  previewWorktreeManager,
46803
46989
  environmentId: eid,
46804
46990
  deployBranch,
@@ -46806,8 +46992,8 @@ async function deploySessionToPreviewEnvironment(options) {
46806
46992
  repoStates,
46807
46993
  log: log2
46808
46994
  });
46809
- if (!applied2.ok) {
46810
- return { ok: false, error: applied2.error };
46995
+ if (!applied.ok) {
46996
+ return { ok: false, error: applied.error };
46811
46997
  }
46812
46998
  log2(
46813
46999
  `[worktrees] Deployed session ${sid.slice(0, 8)}\u2026 to preview ${eid.slice(0, 8)}\u2026 on branch ${deployBranch}.`
@@ -49402,12 +49588,17 @@ var handlePreviewEnvironmentControl = (msg, deps) => {
49402
49588
  };
49403
49589
 
49404
49590
  // src/routing/handlers/preview-environments-config.ts
49591
+ var VALID_PORT = (p) => typeof p === "number" && Number.isInteger(p) && p > 0 && p < 65536;
49405
49592
  var handlePreviewEnvironmentsConfig = (msg, deps) => {
49406
49593
  const previewEnvironments = msg.previewEnvironments;
49594
+ const proxyPorts = Array.isArray(msg.proxyPorts) ? msg.proxyPorts.filter(VALID_PORT) : void 0;
49407
49595
  setImmediate(() => {
49408
49596
  deps.previewEnvironmentManager?.applyConfig(previewEnvironments ?? []);
49409
49597
  const environmentIds = parsePreviewEnvironmentDefs(previewEnvironments ?? []).map((d) => d.environmentId);
49410
49598
  void deps.previewWorktreeManager?.syncConfiguredEnvironments(environmentIds);
49599
+ if (proxyPorts !== void 0) {
49600
+ deps.updateFirehoseProxyPorts?.(proxyPorts);
49601
+ }
49411
49602
  });
49412
49603
  };
49413
49604