@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/index.js CHANGED
@@ -22625,18 +22625,6 @@ var import_websocket = __toESM(require_websocket(), 1);
22625
22625
  var import_websocket_server = __toESM(require_websocket_server(), 1);
22626
22626
  var wrapper_default = import_websocket.default;
22627
22627
 
22628
- // src/net/apply-cli-outbound-network-prefs.ts
22629
- import dns from "node:dns";
22630
- var applied = false;
22631
- function applyCliOutboundNetworkPreferences() {
22632
- if (applied) return;
22633
- applied = true;
22634
- try {
22635
- dns.setDefaultResultOrder("ipv4first");
22636
- } catch {
22637
- }
22638
- }
22639
-
22640
22628
  // src/connection/cli-ws-client.ts
22641
22629
  import https from "node:https";
22642
22630
  var CLI_WEBSOCKET_CLIENT_PING_MS = 25e3;
@@ -22738,7 +22726,6 @@ function createWsBridge(options) {
22738
22726
  onCompactHeartbeatAck,
22739
22727
  isActiveSocket
22740
22728
  } = options;
22741
- applyCliOutboundNetworkPreferences();
22742
22729
  const ws = new wrapper_default(url2, buildCliWebSocketClientOptions(url2));
22743
22730
  let clearClientPing = null;
22744
22731
  function disposeClientPing() {
@@ -24855,6 +24842,71 @@ async function createSdkStdioAcpClient(options) {
24855
24842
  });
24856
24843
  }
24857
24844
 
24845
+ // src/firehose/proxy/constants.ts
24846
+ var LOCAL_PROXY_REQUEST_TIMEOUT_MS = 3e4;
24847
+
24848
+ // src/firehose/proxy/prepare-local-proxy-request-headers.ts
24849
+ var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
24850
+ "connection",
24851
+ "keep-alive",
24852
+ "proxy-authenticate",
24853
+ "proxy-authorization",
24854
+ "te",
24855
+ "trailers",
24856
+ "transfer-encoding",
24857
+ "upgrade"
24858
+ ]);
24859
+ var BODY_HEADERS = /* @__PURE__ */ new Set(["content-length", "content-type", "content-encoding"]);
24860
+ function methodAllowsRequestBody(method) {
24861
+ const normalized = method.toUpperCase();
24862
+ return normalized !== "GET" && normalized !== "HEAD";
24863
+ }
24864
+ function prepareLocalProxyRequestHeaders(method, headers, sendingBody) {
24865
+ const out = {};
24866
+ for (const [key, value] of Object.entries(headers ?? {})) {
24867
+ const lower = key.toLowerCase();
24868
+ if (lower === "host") continue;
24869
+ if (HOP_BY_HOP_HEADERS.has(lower)) continue;
24870
+ if (lower === "origin" || lower === "referer") continue;
24871
+ if (!sendingBody && BODY_HEADERS.has(lower)) continue;
24872
+ out[key] = value;
24873
+ }
24874
+ return out;
24875
+ }
24876
+
24877
+ // src/firehose/proxy/build-local-proxy-http-request.ts
24878
+ var ALLOWED_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
24879
+ function willSendLocalProxyBody(request) {
24880
+ if (!methodAllowsRequestBody(request.method)) return false;
24881
+ return request.body !== void 0 && request.body !== null;
24882
+ }
24883
+ function buildLocalProxyHttpRequest(request) {
24884
+ let url2;
24885
+ try {
24886
+ url2 = new URL(request.url);
24887
+ } catch {
24888
+ return { error: `Invalid URL: ${request.url}` };
24889
+ }
24890
+ const hostname2 = url2.hostname.replace(/^\[|\]$/g, "");
24891
+ if (!ALLOWED_HOSTS.has(url2.hostname) && !ALLOWED_HOSTS.has(hostname2)) {
24892
+ return { error: "Only localhost requests are allowed" };
24893
+ }
24894
+ const sendingBody = willSendLocalProxyBody(request);
24895
+ const headers = prepareLocalProxyRequestHeaders(request.method, request.headers, sendingBody);
24896
+ headers.host = url2.host;
24897
+ const isHttps = url2.protocol === "https:";
24898
+ const port = url2.port ? Number(url2.port) : isHttps ? 443 : 80;
24899
+ return {
24900
+ method: request.method,
24901
+ hostname: url2.hostname,
24902
+ port,
24903
+ path: `${url2.pathname}${url2.search}`,
24904
+ headers,
24905
+ sendingBody,
24906
+ body: sendingBody ? request.body : void 0
24907
+ };
24908
+ }
24909
+
24858
24910
  // src/net/transient-local-fetch-retry.ts
24859
24911
  var LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS = [30, 100, 200, 400];
24860
24912
  function sleepMs(ms) {
@@ -24879,148 +24931,151 @@ function isTransientLocalServiceError(err) {
24879
24931
  if (!text) return false;
24880
24932
  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");
24881
24933
  }
24882
- function isIdempotentHttpMethod(method) {
24883
- const m = method.toUpperCase();
24884
- return m === "GET" || m === "HEAD" || m === "OPTIONS";
24885
- }
24886
- async function fetchWithLocalTransientRetries(url2, init, options) {
24887
- const method = (init?.method ?? "GET").toUpperCase();
24888
- const allowRetry = isIdempotentHttpMethod(method);
24889
- const delays = options?.delaysMs ?? LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS;
24890
- const maxAttempts = options?.maxAttempts ?? (allowRetry ? Math.max(1, delays.length + 1) : 1);
24891
- let lastErr;
24892
- for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
24893
- try {
24894
- return await fetch(url2, init);
24895
- } catch (e) {
24896
- lastErr = e;
24897
- const canRetry = allowRetry && attempt < maxAttempts - 1 && isTransientLocalServiceError(e);
24898
- if (!canRetry) throw e;
24899
- const waitMs = delays[Math.min(attempt, delays.length - 1)] ?? 100;
24900
- await sleepMs(waitMs);
24901
- }
24934
+
24935
+ // src/firehose/proxy/incoming-message-headers.ts
24936
+ function incomingMessageHeaders(res) {
24937
+ const headers = {};
24938
+ for (const [k, v] of Object.entries(res.headers)) {
24939
+ if (typeof v === "string") headers[k] = v;
24940
+ else if (Array.isArray(v) && v[0]) headers[k] = v[0];
24902
24941
  }
24903
- throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
24942
+ return headers;
24904
24943
  }
24905
24944
 
24906
- // src/firehose/proxy/local-proxy.ts
24907
- var ALLOWED_HOSTS = ["localhost", "127.0.0.1", "::1"];
24908
- function isAllowedHost(host) {
24909
- const h = host.replace(/^\[|\]$/g, "");
24910
- return ALLOWED_HOSTS.includes(host) || ALLOWED_HOSTS.includes(h);
24911
- }
24912
- function isIdempotentProxyMethod(method) {
24913
- const m = method.toUpperCase();
24914
- return m === "GET" || m === "HEAD" || m === "OPTIONS";
24915
- }
24916
- function checkUrlAndHost(request) {
24917
- let url2;
24918
- try {
24919
- url2 = new URL(request.url);
24920
- } catch {
24921
- return { error: `Invalid URL: ${request.url}` };
24922
- }
24923
- if (!isAllowedHost(url2.hostname)) {
24924
- return { error: "Only localhost requests are allowed" };
24925
- }
24926
- return { url: url2 };
24945
+ // src/firehose/proxy/run-local-http-request.ts
24946
+ async function loadHttpModule(isHttps) {
24947
+ return isHttps ? import("https") : import("http");
24927
24948
  }
24928
- async function proxyToLocal(request) {
24929
- const checked = checkUrlAndHost(request);
24930
- if ("error" in checked) {
24931
- return { id: request.id, statusCode: 0, headers: {}, body: "", error: checked.error };
24932
- }
24933
- const { url: url2 } = checked;
24934
- const isHttps = url2.protocol === "https:";
24935
- const mod = isHttps ? await import("https") : await import("http");
24936
- const opts = {
24937
- method: request.method,
24938
- hostname: url2.hostname,
24939
- port: url2.port || (isHttps ? 443 : 80),
24940
- path: url2.pathname + url2.search,
24941
- headers: request.headers
24949
+ function buildLocalHttpRequestOptions(built) {
24950
+ return {
24951
+ method: built.method,
24952
+ hostname: built.hostname,
24953
+ port: built.port,
24954
+ path: built.path,
24955
+ headers: built.headers
24942
24956
  };
24943
- const maxAttempts = isIdempotentProxyMethod(request.method) ? LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length + 1 : 1;
24944
- for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
24945
- const once = await new Promise((resolve35) => {
24946
- const req = mod.request(opts, (res) => {
24947
- const chunks = [];
24948
- res.on("data", (c) => chunks.push(c));
24949
- res.on("end", () => {
24950
- const body = Buffer.concat(chunks).toString("utf8");
24951
- const headers = {};
24952
- for (const [k, v] of Object.entries(res.headers)) {
24953
- if (typeof v === "string") headers[k] = v;
24954
- else if (Array.isArray(v) && v[0]) headers[k] = v[0];
24955
- }
24956
- resolve35({
24957
- id: request.id,
24958
- statusCode: res.statusCode ?? 0,
24959
- headers,
24960
- body
24961
- });
24957
+ }
24958
+ function runLocalHttpRequestOnce(mod, built) {
24959
+ const opts = buildLocalHttpRequestOptions(built);
24960
+ return new Promise((resolve35) => {
24961
+ const req = mod.request(opts, (res) => {
24962
+ const chunks = [];
24963
+ res.on("data", (chunk) => chunks.push(chunk));
24964
+ res.on("end", () => {
24965
+ resolve35({
24966
+ statusCode: res.statusCode ?? 0,
24967
+ headers: incomingMessageHeaders(res),
24968
+ body: Buffer.concat(chunks)
24962
24969
  });
24963
24970
  });
24964
- req.on("error", (err) => {
24971
+ res.on("error", (err) => {
24965
24972
  resolve35({
24966
- id: request.id,
24967
24973
  statusCode: 0,
24968
24974
  headers: {},
24969
- body: "",
24975
+ body: Buffer.alloc(0),
24970
24976
  error: err.message
24971
24977
  });
24972
24978
  });
24973
- const method = request.method.toUpperCase();
24974
- if (request.body && method !== "GET" && method !== "HEAD") req.write(request.body);
24975
- req.end();
24976
24979
  });
24980
+ req.setTimeout(LOCAL_PROXY_REQUEST_TIMEOUT_MS, () => {
24981
+ req.destroy(new Error(`Local preview request timed out after ${LOCAL_PROXY_REQUEST_TIMEOUT_MS}ms`));
24982
+ });
24983
+ req.on("error", (err) => {
24984
+ resolve35({
24985
+ statusCode: 0,
24986
+ headers: {},
24987
+ body: Buffer.alloc(0),
24988
+ error: err.message
24989
+ });
24990
+ });
24991
+ if (built.sendingBody && built.body != null) {
24992
+ req.write(built.body);
24993
+ }
24994
+ req.end();
24995
+ });
24996
+ }
24997
+
24998
+ // src/firehose/proxy/proxy-to-local.ts
24999
+ function isIdempotentProxyMethod(method) {
25000
+ const m = method.toUpperCase();
25001
+ return m === "GET" || m === "HEAD" || m === "OPTIONS";
25002
+ }
25003
+ async function proxyToLocal(request) {
25004
+ const built = buildLocalProxyHttpRequest(request);
25005
+ if ("error" in built) {
25006
+ return { id: request.id, statusCode: 0, headers: {}, body: "", error: built.error };
25007
+ }
25008
+ const isHttps = request.url.startsWith("https:");
25009
+ const mod = await loadHttpModule(isHttps);
25010
+ const maxAttempts = isIdempotentProxyMethod(request.method) ? LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length + 1 : 1;
25011
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
25012
+ const once = await runLocalHttpRequestOnce(mod, built);
24977
25013
  const errMsg = once.error ?? "";
24978
25014
  const canRetry = attempt < maxAttempts - 1 && errMsg !== "" && isTransientLocalServiceError(new Error(errMsg));
24979
- if (!canRetry) return once;
25015
+ if (!canRetry) {
25016
+ return {
25017
+ id: request.id,
25018
+ statusCode: once.statusCode,
25019
+ headers: once.headers,
25020
+ body: once.body.toString("utf8"),
25021
+ error: once.error
25022
+ };
25023
+ }
24980
25024
  const waitMs = LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS[Math.min(attempt, LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length - 1)] ?? 100;
24981
25025
  await sleepMs(waitMs);
24982
25026
  }
24983
25027
  throw new Error("Local proxy retry loop exited unexpectedly");
24984
25028
  }
25029
+
25030
+ // src/firehose/proxy/proxy-to-local-streaming.ts
24985
25031
  async function proxyToLocalStreaming(request, callbacks) {
24986
- const checked = checkUrlAndHost(request);
24987
- if ("error" in checked) {
24988
- callbacks.onError(checked.error);
25032
+ const built = buildLocalProxyHttpRequest(request);
25033
+ if ("error" in built) {
25034
+ callbacks.onError(built.error);
24989
25035
  return;
24990
25036
  }
24991
- try {
24992
- const method = request.method.toUpperCase();
24993
- const init = {
24994
- method: request.method,
24995
- headers: request.headers
24996
- };
24997
- if (request.body !== void 0 && request.body !== null && method !== "GET" && method !== "HEAD") {
24998
- init.body = request.body;
24999
- }
25000
- const res = await fetchWithLocalTransientRetries(request.url, init);
25001
- const headers = {};
25002
- res.headers.forEach((value, key) => {
25003
- headers[key] = value;
25004
- });
25005
- callbacks.onStart(res.status, headers);
25006
- const reader = res.body?.getReader();
25007
- if (!reader) {
25008
- callbacks.onEnd();
25009
- return;
25010
- }
25011
- try {
25012
- for (; ; ) {
25013
- const { done, value } = await reader.read();
25014
- if (done) break;
25015
- callbacks.onChunk(value);
25037
+ const isHttps = request.url.startsWith("https:");
25038
+ const mod = await loadHttpModule(isHttps);
25039
+ const opts = buildLocalHttpRequestOptions(built);
25040
+ await new Promise((resolve35) => {
25041
+ const req = mod.request(opts, (res) => {
25042
+ try {
25043
+ callbacks.onStart(res.statusCode ?? 0, incomingMessageHeaders(res));
25044
+ } catch (err) {
25045
+ req.destroy();
25046
+ callbacks.onError(err instanceof Error ? err.message : String(err));
25047
+ resolve35();
25048
+ return;
25016
25049
  }
25017
- } finally {
25018
- reader.releaseLock();
25050
+ res.on("data", (chunk) => {
25051
+ try {
25052
+ callbacks.onChunk(new Uint8Array(chunk));
25053
+ } catch (err) {
25054
+ req.destroy();
25055
+ callbacks.onError(err instanceof Error ? err.message : String(err));
25056
+ }
25057
+ });
25058
+ res.on("end", () => {
25059
+ callbacks.onEnd();
25060
+ resolve35();
25061
+ });
25062
+ res.on("error", (err) => {
25063
+ callbacks.onError(err.message);
25064
+ resolve35();
25065
+ });
25066
+ });
25067
+ req.setTimeout(LOCAL_PROXY_REQUEST_TIMEOUT_MS, () => {
25068
+ req.destroy(new Error(`Local preview request timed out after ${LOCAL_PROXY_REQUEST_TIMEOUT_MS}ms`));
25069
+ });
25070
+ req.on("error", (err) => {
25071
+ callbacks.onError(err.message);
25072
+ resolve35();
25073
+ });
25074
+ if (built.sendingBody && built.body != null) {
25075
+ req.write(built.body);
25019
25076
  }
25020
- callbacks.onEnd();
25021
- } catch (err) {
25022
- callbacks.onError(err instanceof Error ? err.message : String(err));
25023
- }
25077
+ req.end();
25078
+ });
25024
25079
  }
25025
25080
 
25026
25081
  // src/skills/preview.ts
@@ -25272,7 +25327,7 @@ function installBridgeProcessResilience() {
25272
25327
  }
25273
25328
 
25274
25329
  // src/cli-version.ts
25275
- var CLI_VERSION = "0.1.61".length > 0 ? "0.1.61" : "0.0.0-dev";
25330
+ var CLI_VERSION = "0.1.62".length > 0 ? "0.1.62" : "0.0.0-dev";
25276
25331
 
25277
25332
  // src/connection/heartbeat/constants.ts
25278
25333
  var BRIDGE_APP_HEARTBEAT_INTERVAL_MS = 1e4;
@@ -26654,38 +26709,38 @@ function readCodeNavCacheMigrationSql(filename) {
26654
26709
  }
26655
26710
 
26656
26711
  // src/sqlite/run-sqlite-migrations.ts
26657
- function checkpointSatisfiedByLegacyChain(applied2, replacesLegacyMigrations) {
26658
- return Array.isArray(replacesLegacyMigrations) && replacesLegacyMigrations.length > 0 && replacesLegacyMigrations.every((n) => applied2.has(n));
26712
+ function checkpointSatisfiedByLegacyChain(applied, replacesLegacyMigrations) {
26713
+ return Array.isArray(replacesLegacyMigrations) && replacesLegacyMigrations.length > 0 && replacesLegacyMigrations.every((n) => applied.has(n));
26659
26714
  }
26660
- function recordMigrationAndPruneCheckpointLegacy(db, migration, applied2) {
26715
+ function recordMigrationAndPruneCheckpointLegacy(db, migration, applied) {
26661
26716
  db.run("INSERT INTO __migrations (name) VALUES (?)", [migration.name]);
26662
- applied2.add(migration.name);
26717
+ applied.add(migration.name);
26663
26718
  if (migration.checkpoint !== true) return;
26664
26719
  const legacy = migration.replacesLegacyMigrations;
26665
26720
  if (!legacy?.length) return;
26666
26721
  for (const legacyName of legacy) {
26667
26722
  if (legacyName === migration.name) continue;
26668
26723
  db.run("DELETE FROM __migrations WHERE name = ?", [legacyName]);
26669
- applied2.delete(legacyName);
26724
+ applied.delete(legacyName);
26670
26725
  }
26671
26726
  }
26672
26727
  function runSqliteMigrations(db, migrations, bootstrapSql, label) {
26673
26728
  db.exec(bootstrapSql);
26674
26729
  const appliedRows = db.all("SELECT name FROM __migrations");
26675
- const applied2 = new Set(appliedRows.map((r) => r.name));
26730
+ const applied = new Set(appliedRows.map((r) => r.name));
26676
26731
  for (const migration of migrations) {
26677
- if (applied2.has(migration.name)) continue;
26678
- if (migration.checkpoint === true && checkpointSatisfiedByLegacyChain(applied2, migration.replacesLegacyMigrations)) {
26679
- recordMigrationAndPruneCheckpointLegacy(db, migration, applied2);
26732
+ if (applied.has(migration.name)) continue;
26733
+ if (migration.checkpoint === true && checkpointSatisfiedByLegacyChain(applied, migration.replacesLegacyMigrations)) {
26734
+ recordMigrationAndPruneCheckpointLegacy(db, migration, applied);
26680
26735
  continue;
26681
26736
  }
26682
26737
  if (migration.alreadyApplied?.(db)) {
26683
- recordMigrationAndPruneCheckpointLegacy(db, migration, applied2);
26738
+ recordMigrationAndPruneCheckpointLegacy(db, migration, applied);
26684
26739
  continue;
26685
26740
  }
26686
26741
  try {
26687
26742
  migration.migrate(db);
26688
- recordMigrationAndPruneCheckpointLegacy(db, migration, applied2);
26743
+ recordMigrationAndPruneCheckpointLegacy(db, migration, applied);
26689
26744
  } catch (e) {
26690
26745
  console.error(`[${label}] Migration failed: ${migration.name}`, e);
26691
26746
  throw e;
@@ -34832,6 +34887,9 @@ function envForSpawn(base, userEnv, ports) {
34832
34887
  });
34833
34888
  if (ports.length > 0) {
34834
34889
  out.PORTS = ports.join(",");
34890
+ if (!userKeys.has("PORT")) {
34891
+ out.PORT = String(ports[0]);
34892
+ }
34835
34893
  }
34836
34894
  if (!userKeys.has("NO_COLOR")) {
34837
34895
  delete out.NO_COLOR;
@@ -35734,6 +35792,12 @@ function createBridgePreviewStack(options) {
35734
35792
  return { previewEnvironmentManager, scheduleInitialIndexBuildsOnce, identifyReportedPaths };
35735
35793
  }
35736
35794
 
35795
+ // src/firehose/build-cli-ws-url.ts
35796
+ function buildFirehoseCliWsUrl(baseUrl) {
35797
+ const base = baseUrl.startsWith("https") ? baseUrl.replace(/^https/, "wss") : baseUrl.replace(/^http/, "ws");
35798
+ return `${base.replace(/\/$/, "")}/ws`;
35799
+ }
35800
+
35737
35801
  // src/firehose/proxy/start-streaming-proxy.ts
35738
35802
  function startStreamingProxy(ws, log2, pr) {
35739
35803
  proxyToLocalStreaming(pr, {
@@ -35741,15 +35805,7 @@ function startStreamingProxy(ws, log2, pr) {
35741
35805
  if (ws.readyState !== wrapper_default.OPEN) {
35742
35806
  throw new Error("Preview stream interrupted (firehose connection closed)");
35743
35807
  }
35744
- const forwardedHeaders = { ...headers };
35745
- const ce = "content-encoding";
35746
- const cl = "content-length";
35747
- const keys = Object.keys(forwardedHeaders).filter(
35748
- (k) => k.toLowerCase() !== ce && k.toLowerCase() !== cl
35749
- );
35750
- const filtered = {};
35751
- for (const k of keys) if (forwardedHeaders[k] != null) filtered[k] = forwardedHeaders[k];
35752
- sendWsMessage(ws, { type: "proxy_result_start", id: pr.id, statusCode, headers: filtered });
35808
+ sendWsMessage(ws, { type: "proxy_result_start", id: pr.id, statusCode, headers });
35753
35809
  },
35754
35810
  onChunk: (chunk) => {
35755
35811
  const idBuf = Buffer.from(pr.id, "utf8");
@@ -35766,10 +35822,19 @@ function startStreamingProxy(ws, log2, pr) {
35766
35822
  });
35767
35823
  }
35768
35824
 
35769
- // src/firehose/build-cli-ws-url.ts
35770
- function buildFirehoseCliWsUrl(baseUrl) {
35771
- const base = baseUrl.startsWith("https") ? baseUrl.replace(/^https/, "wss") : baseUrl.replace(/^http/, "ws");
35772
- return `${base.replace(/\/$/, "")}/ws`;
35825
+ // src/firehose/create-firehose-message-deps.ts
35826
+ function createFirehoseMessageDeps(ws, log2, previewEnvironmentManager) {
35827
+ const pendingProxyBody = /* @__PURE__ */ new Map();
35828
+ const earlyProxyBinaryBody = /* @__PURE__ */ new Map();
35829
+ const deps = {
35830
+ ws,
35831
+ log: log2,
35832
+ previewEnvironmentManager,
35833
+ pendingProxyBody,
35834
+ earlyProxyBinaryBody,
35835
+ startStreamingProxy: (pr) => startStreamingProxy(ws, log2, pr)
35836
+ };
35837
+ return deps;
35773
35838
  }
35774
35839
 
35775
35840
  // src/firehose/routing/firehose-message-router.ts
@@ -35810,25 +35875,27 @@ var handleProxyMessage = (msg, deps) => {
35810
35875
  if (!proxy || typeof proxy !== "object") return;
35811
35876
  const pr = proxy;
35812
35877
  if (pr.streamResponse) {
35813
- const bodyLength = pr.bodyLength ?? 0;
35878
+ const bodyLength = methodAllowsRequestBody(pr.method) ? pr.bodyLength ?? 0 : 0;
35879
+ const streamingRequest = {
35880
+ id: pr.id,
35881
+ method: pr.method,
35882
+ url: pr.url,
35883
+ headers: pr.headers,
35884
+ streamResponse: true
35885
+ };
35814
35886
  if (bodyLength > 0) {
35815
- deps.pendingProxyBody.set(pr.id, {
35816
- pr: {
35817
- id: pr.id,
35818
- method: pr.method,
35819
- url: pr.url,
35820
- headers: pr.headers,
35821
- streamResponse: true
35822
- }
35823
- });
35887
+ const earlyBody = deps.earlyProxyBinaryBody.get(pr.id);
35888
+ if (earlyBody !== void 0) {
35889
+ deps.earlyProxyBinaryBody.delete(pr.id);
35890
+ deps.startStreamingProxy({
35891
+ ...streamingRequest,
35892
+ body: earlyBody.length > 0 ? earlyBody : void 0
35893
+ });
35894
+ } else {
35895
+ deps.pendingProxyBody.set(pr.id, { pr: streamingRequest });
35896
+ }
35824
35897
  } else {
35825
- deps.startStreamingProxy({
35826
- id: pr.id,
35827
- method: pr.method,
35828
- url: pr.url,
35829
- headers: pr.headers,
35830
- streamResponse: true
35831
- });
35898
+ deps.startStreamingProxy(streamingRequest);
35832
35899
  }
35833
35900
  return;
35834
35901
  }
@@ -35859,37 +35926,63 @@ function dispatchFirehoseJsonMessage(msg, deps) {
35859
35926
 
35860
35927
  // src/firehose/routing/try-consume-binary-proxy-body.ts
35861
35928
  var PROXY_ID_BYTES = 36;
35929
+ 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;
35862
35930
  function tryConsumeBinaryProxyBody(raw, deps) {
35863
35931
  if (raw.length < PROXY_ID_BYTES) return false;
35864
35932
  const id = raw.slice(0, PROXY_ID_BYTES).toString("utf8");
35865
- const pending2 = deps.pendingProxyBody.get(id);
35866
- if (!pending2) return false;
35867
- deps.pendingProxyBody.delete(id);
35933
+ if (!PROXY_ID_RE.test(id)) return false;
35868
35934
  const body = raw.slice(PROXY_ID_BYTES);
35869
- deps.startStreamingProxy({
35870
- ...pending2.pr,
35871
- body: body.length > 0 ? new Uint8Array(body) : void 0
35872
- });
35935
+ const bodyBytes = body.length > 0 ? new Uint8Array(body) : new Uint8Array(0);
35936
+ const pending2 = deps.pendingProxyBody.get(id);
35937
+ if (pending2) {
35938
+ deps.pendingProxyBody.delete(id);
35939
+ deps.startStreamingProxy({
35940
+ ...pending2.pr,
35941
+ body: bodyBytes.length > 0 ? bodyBytes : void 0
35942
+ });
35943
+ return true;
35944
+ }
35945
+ deps.earlyProxyBinaryBody.set(id, bodyBytes);
35873
35946
  return true;
35874
35947
  }
35875
35948
 
35876
- // src/firehose/connect-firehose.ts
35877
- function connectFirehose(options) {
35949
+ // src/firehose/handle-inbound-firehose-frame.ts
35950
+ function handleInboundFirehoseFrame(raw, deps) {
35951
+ if (Buffer.isBuffer(raw) && tryConsumeBinaryProxyBody(raw, deps)) {
35952
+ return;
35953
+ }
35954
+ let msg;
35955
+ try {
35956
+ const text = Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw);
35957
+ msg = JSON.parse(text);
35958
+ } catch {
35959
+ return;
35960
+ }
35961
+ if (msg.type === "proxy") {
35962
+ dispatchFirehoseJsonMessage(msg, deps);
35963
+ return;
35964
+ }
35965
+ setImmediate(() => {
35966
+ dispatchFirehoseJsonMessage(msg, deps);
35967
+ });
35968
+ }
35969
+
35970
+ // src/firehose/wire-firehose-websocket.ts
35971
+ function wireFirehoseWebSocket(options) {
35878
35972
  const {
35879
- firehoseServerUrl,
35973
+ ws,
35974
+ deps,
35975
+ log: log2,
35976
+ previewEnvironmentManager,
35880
35977
  workspaceId,
35881
35978
  bridgeName,
35882
35979
  proxyPorts,
35883
- log: log2,
35884
- previewEnvironmentManager,
35885
35980
  onOpen,
35886
35981
  onClose,
35887
35982
  suppressWebSocketErrors
35888
35983
  } = options;
35889
- const wsUrl = buildFirehoseCliWsUrl(firehoseServerUrl);
35890
- applyCliOutboundNetworkPreferences();
35891
- const ws = new wrapper_default(wsUrl, buildCliWebSocketClientOptions(wsUrl));
35892
35984
  let clearClientPing = null;
35985
+ let identifyParams = { workspaceId, bridgeName, proxyPorts };
35893
35986
  function disposeClientPing() {
35894
35987
  clearClientPing?.();
35895
35988
  clearClientPing = null;
@@ -35897,36 +35990,32 @@ function connectFirehose(options) {
35897
35990
  const firehoseSend = (payload) => {
35898
35991
  sendWsMessage(ws, payload);
35899
35992
  };
35900
- const pendingProxyBody = /* @__PURE__ */ new Map();
35901
- const deps = {
35902
- ws,
35903
- log: log2,
35904
- previewEnvironmentManager,
35905
- pendingProxyBody,
35906
- startStreamingProxy: (pr) => startStreamingProxy(ws, log2, pr)
35907
- };
35993
+ function sendIdentify(params) {
35994
+ identifyParams = params;
35995
+ if (ws.readyState !== wrapper_default.OPEN) return;
35996
+ sendWsMessage(ws, {
35997
+ type: "identify",
35998
+ workspaceId: params.workspaceId,
35999
+ bridgeName: params.bridgeName,
36000
+ proxyPorts: params.proxyPorts
36001
+ });
36002
+ }
36003
+ function detachPreviewEnvironmentManager() {
36004
+ previewEnvironmentManager.detachFirehose();
36005
+ }
35908
36006
  ws.on("open", () => {
35909
36007
  disposeClientPing();
35910
36008
  clearClientPing = attachWebSocketClientPing(ws, CLI_WEBSOCKET_CLIENT_PING_MS);
35911
36009
  onOpen?.();
35912
36010
  previewEnvironmentManager.attachFirehose(firehoseSend);
35913
- sendWsMessage(ws, { type: "identify", workspaceId, bridgeName, proxyPorts });
36011
+ sendIdentify(identifyParams);
35914
36012
  });
35915
36013
  ws.on("message", (raw) => {
35916
- if (Buffer.isBuffer(raw) && tryConsumeBinaryProxyBody(raw, deps)) {
35917
- return;
35918
- }
35919
- setImmediate(() => {
35920
- try {
35921
- const text = Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw);
35922
- dispatchFirehoseJsonMessage(JSON.parse(text), deps);
35923
- } catch {
35924
- }
35925
- });
36014
+ handleInboundFirehoseFrame(raw, deps);
35926
36015
  });
35927
36016
  ws.on("close", (code, reason) => {
35928
36017
  disposeClientPing();
35929
- previewEnvironmentManager.detachFirehose();
36018
+ detachPreviewEnvironmentManager();
35930
36019
  const reasonStr = typeof reason === "string" ? reason : reason.toString();
35931
36020
  onClose?.(code, reasonStr);
35932
36021
  });
@@ -35939,25 +36028,64 @@ function connectFirehose(options) {
35939
36028
  safeCloseWebSocket(ws);
35940
36029
  }
35941
36030
  });
36031
+ return { disposeClientPing, sendIdentify, detachPreviewEnvironmentManager };
36032
+ }
36033
+
36034
+ // src/firehose/connect-firehose.ts
36035
+ function connectFirehose(options) {
36036
+ const {
36037
+ firehoseServerUrl,
36038
+ workspaceId,
36039
+ bridgeName,
36040
+ proxyPorts,
36041
+ log: log2,
36042
+ previewEnvironmentManager,
36043
+ onOpen,
36044
+ onClose,
36045
+ suppressWebSocketErrors
36046
+ } = options;
36047
+ const wsUrl = buildFirehoseCliWsUrl(firehoseServerUrl);
36048
+ const ws = new wrapper_default(wsUrl, buildCliWebSocketClientOptions(wsUrl));
36049
+ const deps = createFirehoseMessageDeps(ws, log2, previewEnvironmentManager);
36050
+ const wired = wireFirehoseWebSocket({
36051
+ ws,
36052
+ deps,
36053
+ log: log2,
36054
+ previewEnvironmentManager,
36055
+ workspaceId,
36056
+ bridgeName,
36057
+ proxyPorts,
36058
+ onOpen,
36059
+ onClose,
36060
+ suppressWebSocketErrors
36061
+ });
35942
36062
  return {
35943
36063
  close() {
35944
- disposeClientPing();
35945
- previewEnvironmentManager.detachFirehose();
36064
+ wired.disposeClientPing();
36065
+ wired.detachPreviewEnvironmentManager();
35946
36066
  safeCloseWebSocket(ws);
35947
36067
  },
35948
- isConnected: () => ws.readyState === wrapper_default.OPEN
36068
+ isConnected: () => ws.readyState === wrapper_default.OPEN,
36069
+ updateIdentify: wired.sendIdentify
35949
36070
  };
35950
36071
  }
35951
36072
 
35952
- // src/connection/attach-firehose-after-identified.ts
35953
- function attachFirehoseAfterIdentified(ctx, params) {
35954
- const { state, previewEnvironmentManager, logFn } = ctx;
35955
- function clearFirehoseReconnectTimer() {
35956
- if (state.firehoseReconnectTimeout != null) {
35957
- clearTimeout(state.firehoseReconnectTimeout);
35958
- state.firehoseReconnectTimeout = null;
35959
- }
35960
- }
36073
+ // src/connection/firehose/compare-firehose-params.ts
36074
+ function proxyPortsEqual(a, b) {
36075
+ if (a.length !== b.length) return false;
36076
+ const sa = [...a].sort((x, y) => x - y);
36077
+ const sb = [...b].sort((x, y) => x - y);
36078
+ return sa.every((v, i) => v === sb[i]);
36079
+ }
36080
+ function sameFirehoseBridge(a, b) {
36081
+ return a.firehoseServerUrl === b.firehoseServerUrl && a.workspaceId === b.workspaceId && a.bridgeName === b.bridgeName;
36082
+ }
36083
+ function shouldReuseOpenFirehoseConnection(prev, next, isConnected) {
36084
+ return Boolean(prev && sameFirehoseBridge(prev, next) && isConnected());
36085
+ }
36086
+
36087
+ // src/connection/firehose/create-firehose-connect-callbacks.ts
36088
+ function createFirehoseConnectCallbacks(state, myGen, logFn, scheduleFirehoseRetryAfterDrop) {
35961
36089
  function firehoseCtx() {
35962
36090
  return {
35963
36091
  closedByUser: state.closedByUser,
@@ -35966,6 +36094,55 @@ function attachFirehoseAfterIdentified(ctx, params) {
35966
36094
  firehoseQuiet: state.firehoseQuiet
35967
36095
  };
35968
36096
  }
36097
+ return {
36098
+ onOpen: () => {
36099
+ if (myGen !== state.firehoseGeneration) return;
36100
+ try {
36101
+ clearFirehoseReconnectQuietOnOpen(
36102
+ {
36103
+ firehoseQuiet: state.firehoseQuiet,
36104
+ firehoseOutage: state.firehoseOutage,
36105
+ lastFirehoseReconnectCloseMeta: state.lastFirehoseReconnectCloseMeta
36106
+ },
36107
+ logFn
36108
+ );
36109
+ } catch {
36110
+ }
36111
+ const logOpenAsFirehoseReconnect = state.firehoseReconnectAttempt > 0;
36112
+ state.firehoseReconnectAttempt = 0;
36113
+ if (!logOpenAsFirehoseReconnect) {
36114
+ try {
36115
+ logFn("Connected to preview tunnel (local HTTP proxy and dev logs).");
36116
+ } catch {
36117
+ }
36118
+ }
36119
+ },
36120
+ onClose: (code, reason) => {
36121
+ if (myGen !== state.firehoseGeneration) return;
36122
+ state.firehoseHandle = null;
36123
+ if (state.closedByUser) return;
36124
+ state.lastFirehoseReconnectCloseMeta = { code, reason };
36125
+ try {
36126
+ beginFirehoseDeferredDisconnect(firehoseCtx(), code, reason, logFn);
36127
+ } catch {
36128
+ }
36129
+ try {
36130
+ scheduleFirehoseRetryAfterDrop({ code, reason });
36131
+ } catch {
36132
+ }
36133
+ }
36134
+ };
36135
+ }
36136
+
36137
+ // src/connection/firehose/create-firehose-reconnect-scheduler.ts
36138
+ function createFirehoseReconnectScheduler(ctx, reconnect) {
36139
+ const { state, logFn } = ctx;
36140
+ function clearFirehoseReconnectTimer() {
36141
+ if (state.firehoseReconnectTimeout != null) {
36142
+ clearTimeout(state.firehoseReconnectTimeout);
36143
+ state.firehoseReconnectTimeout = null;
36144
+ }
36145
+ }
35969
36146
  function scheduleFirehoseRetryAfterDrop(closeMeta) {
35970
36147
  if (state.closedByUser) return;
35971
36148
  const meta = closeMeta ?? state.lastFirehoseReconnectCloseMeta ?? void 0;
@@ -35987,30 +36164,60 @@ function attachFirehoseAfterIdentified(ctx, params) {
35987
36164
  state.firehoseReconnectTimeout = id;
35988
36165
  },
35989
36166
  shouldAbortBeforeRun: () => state.closedByUser,
35990
- run: () => {
35991
- const p = state.lastFirehoseParams;
35992
- if (!p) return;
35993
- attachFirehoseAfterIdentified(ctx, p);
35994
- },
36167
+ run: reconnect,
35995
36168
  reschedule: () => {
35996
36169
  scheduleFirehoseRetryAfterDrop();
35997
36170
  }
35998
36171
  });
35999
36172
  }
36000
- state.lastFirehoseParams = params;
36001
- clearFirehoseReconnectTimer();
36002
- if (state.firehoseReconnectAttempt === 0) {
36003
- try {
36004
- logFn("Connecting to preview tunnel (local HTTP proxy and dev logs)\u2026");
36005
- } catch {
36006
- }
36173
+ return { clearFirehoseReconnectTimer, scheduleFirehoseRetryAfterDrop };
36174
+ }
36175
+ function logInitialFirehoseConnect(state, logFn) {
36176
+ if (state.firehoseReconnectAttempt !== 0) return;
36177
+ try {
36178
+ logFn("Connecting to preview tunnel (local HTTP proxy and dev logs)\u2026");
36179
+ } catch {
36007
36180
  }
36181
+ }
36182
+ function replaceFirehoseHandle(state) {
36008
36183
  state.firehoseGeneration += 1;
36009
36184
  const myGen = state.firehoseGeneration;
36010
36185
  if (state.firehoseHandle) {
36011
36186
  state.firehoseHandle.close();
36012
36187
  state.firehoseHandle = null;
36013
36188
  }
36189
+ return myGen;
36190
+ }
36191
+
36192
+ // src/connection/firehose/attach-firehose-after-identified.ts
36193
+ function attachFirehoseAfterIdentified(ctx, params) {
36194
+ const { state, previewEnvironmentManager, logFn } = ctx;
36195
+ const prev = state.lastFirehoseParams;
36196
+ if (shouldReuseOpenFirehoseConnection(prev, params, () => state.firehoseHandle?.isConnected() ?? false)) {
36197
+ state.lastFirehoseParams = params;
36198
+ if (prev && !proxyPortsEqual(prev.proxyPorts, params.proxyPorts)) {
36199
+ state.firehoseHandle?.updateIdentify(params);
36200
+ }
36201
+ return;
36202
+ }
36203
+ const { clearFirehoseReconnectTimer, scheduleFirehoseRetryAfterDrop } = createFirehoseReconnectScheduler(
36204
+ ctx,
36205
+ () => {
36206
+ const p = state.lastFirehoseParams;
36207
+ if (!p) return;
36208
+ attachFirehoseAfterIdentified(ctx, p);
36209
+ }
36210
+ );
36211
+ state.lastFirehoseParams = params;
36212
+ clearFirehoseReconnectTimer();
36213
+ logInitialFirehoseConnect(state, logFn);
36214
+ const myGen = replaceFirehoseHandle(state);
36215
+ const { onOpen, onClose } = createFirehoseConnectCallbacks(
36216
+ state,
36217
+ myGen,
36218
+ logFn,
36219
+ scheduleFirehoseRetryAfterDrop
36220
+ );
36014
36221
  state.firehoseHandle = connectFirehose({
36015
36222
  firehoseServerUrl: params.firehoseServerUrl,
36016
36223
  workspaceId: params.workspaceId,
@@ -36019,45 +36226,17 @@ function attachFirehoseAfterIdentified(ctx, params) {
36019
36226
  log: logFn,
36020
36227
  previewEnvironmentManager,
36021
36228
  suppressWebSocketErrors: () => !state.closedByUser && state.firehoseOutage.startedAt != null,
36022
- onOpen: () => {
36023
- if (myGen !== state.firehoseGeneration) return;
36024
- try {
36025
- clearFirehoseReconnectQuietOnOpen(
36026
- {
36027
- firehoseQuiet: state.firehoseQuiet,
36028
- firehoseOutage: state.firehoseOutage,
36029
- lastFirehoseReconnectCloseMeta: state.lastFirehoseReconnectCloseMeta
36030
- },
36031
- logFn
36032
- );
36033
- } catch {
36034
- }
36035
- const logOpenAsFirehoseReconnect = state.firehoseReconnectAttempt > 0;
36036
- state.firehoseReconnectAttempt = 0;
36037
- if (!logOpenAsFirehoseReconnect) {
36038
- try {
36039
- logFn("Connected to preview tunnel (local HTTP proxy and dev logs).");
36040
- } catch {
36041
- }
36042
- }
36043
- },
36044
- onClose: (code, reason) => {
36045
- if (myGen !== state.firehoseGeneration) return;
36046
- state.firehoseHandle = null;
36047
- if (state.closedByUser) return;
36048
- state.lastFirehoseReconnectCloseMeta = { code, reason };
36049
- try {
36050
- beginFirehoseDeferredDisconnect(firehoseCtx(), code, reason, logFn);
36051
- } catch {
36052
- }
36053
- try {
36054
- scheduleFirehoseRetryAfterDrop({ code, reason });
36055
- } catch {
36056
- }
36057
- }
36229
+ onOpen,
36230
+ onClose
36058
36231
  });
36059
36232
  }
36060
36233
 
36234
+ // src/connection/firehose/update-firehose-identify-on-open-connection.ts
36235
+ function updateFirehoseIdentifyOnOpenConnection(state, params) {
36236
+ state.lastFirehoseParams = params;
36237
+ state.firehoseHandle?.updateIdentify(params);
36238
+ }
36239
+
36061
36240
  // src/connection/create-bridge-identified-handler.ts
36062
36241
  function createOnBridgeIdentified(opts) {
36063
36242
  const { previewEnvironmentManager, firehoseServerUrl, workspaceId, state, logFn } = opts;
@@ -36094,6 +36273,7 @@ function createBridgeMessageDeps(params) {
36094
36273
  reportAutoDetectedAgents,
36095
36274
  warmupAgentCapabilitiesOnConnect: warmupAgentCapabilitiesOnConnect2,
36096
36275
  previewEnvironmentManager,
36276
+ updateFirehoseProxyPorts,
36097
36277
  e2ee,
36098
36278
  cloudApiBaseUrl,
36099
36279
  getCloudAccessToken
@@ -36112,6 +36292,7 @@ function createBridgeMessageDeps(params) {
36112
36292
  reportAutoDetectedAgents,
36113
36293
  warmupAgentCapabilitiesOnConnect: warmupAgentCapabilitiesOnConnect2,
36114
36294
  previewEnvironmentManager,
36295
+ updateFirehoseProxyPorts,
36115
36296
  e2ee,
36116
36297
  cloudApiBaseUrl,
36117
36298
  getCloudAccessToken
@@ -36372,6 +36553,11 @@ function createBridgeRuntimeMessageSetup(options) {
36372
36553
  getWs
36373
36554
  }),
36374
36555
  previewEnvironmentManager,
36556
+ updateFirehoseProxyPorts: (proxyPorts) => {
36557
+ const p = state.lastFirehoseParams;
36558
+ if (!p) return;
36559
+ updateFirehoseIdentifyOnOpenConnection(state, { ...p, proxyPorts });
36560
+ },
36375
36561
  e2ee,
36376
36562
  cloudApiBaseUrl: apiUrl,
36377
36563
  getCloudAccessToken: () => tokens.accessToken
@@ -43641,16 +43827,16 @@ async function applySessionWipToPreviewCheckouts(options) {
43641
43827
  return;
43642
43828
  }
43643
43829
  await gitResetWorktreeToBranch(previewCheckout, deployBranch);
43644
- const applied2 = await applyWorkingTreeWip(
43830
+ const applied = await applyWorkingTreeWip(
43645
43831
  previewCheckout,
43646
43832
  state.sessionCheckout,
43647
43833
  state.wip,
43648
43834
  log2
43649
43835
  );
43650
- if (!applied2.ok) {
43836
+ if (!applied.ok) {
43651
43837
  failure = {
43652
43838
  ok: false,
43653
- error: applied2.error ?? `Failed to apply session WIP to preview (${state.repoPathRelativeToWorkspaceRoot})`
43839
+ error: applied.error ?? `Failed to apply session WIP to preview (${state.repoPathRelativeToWorkspaceRoot})`
43654
43840
  };
43655
43841
  }
43656
43842
  });
@@ -43749,7 +43935,7 @@ async function deploySessionToPreviewEnvironment(options) {
43749
43935
  return { ok: false, error: "Failed to create preview environment worktrees" };
43750
43936
  }
43751
43937
  await yieldToEventLoop2();
43752
- const applied2 = await applySessionWipToPreviewCheckouts({
43938
+ const applied = await applySessionWipToPreviewCheckouts({
43753
43939
  previewWorktreeManager,
43754
43940
  environmentId: eid,
43755
43941
  deployBranch,
@@ -43757,8 +43943,8 @@ async function deploySessionToPreviewEnvironment(options) {
43757
43943
  repoStates,
43758
43944
  log: log2
43759
43945
  });
43760
- if (!applied2.ok) {
43761
- return { ok: false, error: applied2.error };
43946
+ if (!applied.ok) {
43947
+ return { ok: false, error: applied.error };
43762
43948
  }
43763
43949
  log2(
43764
43950
  `[worktrees] Deployed session ${sid.slice(0, 8)}\u2026 to preview ${eid.slice(0, 8)}\u2026 on branch ${deployBranch}.`
@@ -46179,12 +46365,17 @@ var handlePreviewEnvironmentControl = (msg, deps) => {
46179
46365
  };
46180
46366
 
46181
46367
  // src/routing/handlers/preview-environments-config.ts
46368
+ var VALID_PORT = (p) => typeof p === "number" && Number.isInteger(p) && p > 0 && p < 65536;
46182
46369
  var handlePreviewEnvironmentsConfig = (msg, deps) => {
46183
46370
  const previewEnvironments = msg.previewEnvironments;
46371
+ const proxyPorts = Array.isArray(msg.proxyPorts) ? msg.proxyPorts.filter(VALID_PORT) : void 0;
46184
46372
  setImmediate(() => {
46185
46373
  deps.previewEnvironmentManager?.applyConfig(previewEnvironments ?? []);
46186
46374
  const environmentIds = parsePreviewEnvironmentDefs(previewEnvironments ?? []).map((d) => d.environmentId);
46187
46375
  void deps.previewWorktreeManager?.syncConfiguredEnvironments(environmentIds);
46376
+ if (proxyPorts !== void 0) {
46377
+ deps.updateFirehoseProxyPorts?.(proxyPorts);
46378
+ }
46188
46379
  });
46189
46380
  };
46190
46381