@buildautomaton/cli 0.1.61 → 0.1.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.
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() {
@@ -23527,7 +23514,6 @@ var StoryCheckpointSummarySchema = CheckpointSummarySchema.extend({
23527
23514
 
23528
23515
  // ../types/src/sessions.ts
23529
23516
  init_zod();
23530
- var BUILTIN_SESSION_CHANGE_SUMMARY_FOLLOW_UP_CATALOG_PROMPT_ID = "__builtin_change_summary__";
23531
23517
  var SessionMetaSchema = external_exports.object({
23532
23518
  sessionId: external_exports.string(),
23533
23519
  workspaceId: external_exports.string(),
@@ -23660,146 +23646,6 @@ function buildPlanningSessionFollowUpAgentPrompt(userPrompt, existingTodos) {
23660
23646
  ].join("\n");
23661
23647
  }
23662
23648
 
23663
- // ../types/src/change-summary-path.ts
23664
- function normalizeRepoRelativePath(p) {
23665
- let t = p.trim().replace(/\\/g, "/");
23666
- while (t.startsWith("./")) t = t.slice(2);
23667
- return t.replace(/\/+/g, "/");
23668
- }
23669
- function resolveChangeSummaryPathAgainstAllowed(rawPath, allowed) {
23670
- const trimmed2 = rawPath.trim();
23671
- if (!trimmed2) return null;
23672
- if (allowed.has(trimmed2)) return trimmed2;
23673
- const n = normalizeRepoRelativePath(trimmed2);
23674
- if (allowed.has(n)) return n;
23675
- for (const a of allowed) {
23676
- if (normalizeRepoRelativePath(a) === n) return a;
23677
- }
23678
- return null;
23679
- }
23680
-
23681
- // ../types/src/parse-change-summary-json.ts
23682
- function clampSummaryToAtMostTwoLines(summary) {
23683
- const lines = summary.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
23684
- return lines.slice(0, 2).join("\n");
23685
- }
23686
- function parseChangeSummaryJson(raw, allowedPaths, options) {
23687
- if (raw == null || raw.trim() === "") return [];
23688
- let text = raw.trim();
23689
- const fence = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
23690
- if (fence?.[1]) text = fence[1].trim();
23691
- let parsed;
23692
- try {
23693
- parsed = JSON.parse(text);
23694
- } catch {
23695
- const start = text.indexOf("[");
23696
- const end = text.lastIndexOf("]");
23697
- if (start < 0 || end <= start) return [];
23698
- try {
23699
- parsed = JSON.parse(text.slice(start, end + 1));
23700
- } catch {
23701
- return [];
23702
- }
23703
- }
23704
- const rows = [];
23705
- let arr = [];
23706
- if (Array.isArray(parsed)) {
23707
- arr = parsed;
23708
- } else if (parsed && typeof parsed === "object" && Array.isArray(parsed.files)) {
23709
- arr = parsed.files;
23710
- }
23711
- const skip = options?.skipPathAllowlist === true;
23712
- for (const item of arr) {
23713
- if (!item || typeof item !== "object") continue;
23714
- const o = item;
23715
- const rawPath = typeof o.path === "string" ? o.path.trim() : "";
23716
- const summary = typeof o.summary === "string" ? o.summary.trim() : "";
23717
- if (!rawPath || !summary) continue;
23718
- const path79 = skip ? normalizeRepoRelativePath(rawPath) || rawPath : resolveChangeSummaryPathAgainstAllowed(rawPath, allowedPaths);
23719
- if (!path79) continue;
23720
- rows.push({ path: path79, summary: clampSummaryToAtMostTwoLines(summary) });
23721
- }
23722
- return rows;
23723
- }
23724
-
23725
- // ../types/src/build-change-summary-prompt.ts
23726
- var PATCH_PREVIEW_MAX = 12e3;
23727
- function clip(s, max) {
23728
- if (s.length <= max) return s;
23729
- return `${s.slice(0, max)}
23730
-
23731
- \u2026(truncated, ${s.length - max} more characters)`;
23732
- }
23733
- function buildSessionChangeSummaryPrompt(files) {
23734
- const lines = [
23735
- "You are the same agent that produced the changes below. Summarize **your own** edits so a reader can scan them quickly.",
23736
- "",
23737
- "Write in second person (you / your): what you changed in each path and why it matters.",
23738
- "",
23739
- "Each summary must be **very concise**: **one line** of plain text, or **at most two short lines** (use a single line break between the two if needed). No bullets, no paragraphs.",
23740
- "",
23741
- "## How to format your reply (machine parsing)",
23742
- "",
23743
- "- Put the machine-readable part **only** inside a **markdown fenced code block** whose opening fence is exactly ```json on its own line. Close the block with ``` on its own line after the JSON.",
23744
- "- Inside that fence: **nothing except** one valid JSON value \u2014 the array described below. No trailing commentary inside the fence.",
23745
- "- **Do not** attach prose to the JSON on the same line (wrong: `Only one file\u2026page.tsx.[{\u2026}]`). Wrong: any sentence that ends with `.` immediately before `[`. Put a blank line before the ```json line.",
23746
- "- If you add optional plain English before the fence (e.g. one short sentence), keep it **separate**: end that sentence, blank line, then ```json.",
23747
- '- In each `"summary"` string, avoid raw double-quote characters, or escape them as `\\"` so the JSON parses.',
23748
- "",
23749
- "JSON shape **inside the fence** (array only):",
23750
- '[{"path":"<file path exactly as given>","summary":"<one line, or two short lines separated by \\n>"}]',
23751
- "",
23752
- "Rules:",
23753
- "- Include **exactly one** object per file path listed below (same path strings).",
23754
- "- If a path is a removed directory, state briefly what you removed.",
23755
- "- Do not invent paths; use only the paths provided.",
23756
- "",
23757
- "## Files you changed",
23758
- ""
23759
- ];
23760
- for (const f of files) {
23761
- lines.push(`### ${f.path}`);
23762
- if (f.directoryRemoved) {
23763
- lines.push("(directory removed)");
23764
- lines.push("");
23765
- continue;
23766
- }
23767
- if (f.patchContent && f.patchContent.trim() !== "") {
23768
- lines.push("```diff");
23769
- lines.push(clip(f.patchContent.trim(), PATCH_PREVIEW_MAX));
23770
- lines.push("```");
23771
- } else if (f.oldText != null || f.newText != null) {
23772
- const oldT = (f.oldText ?? "").trim();
23773
- const newT = (f.newText ?? "").trim();
23774
- lines.push("Previous snippet:", clip(oldT, 6e3));
23775
- lines.push("New snippet:", clip(newT, 6e3));
23776
- } else {
23777
- lines.push("(no diff body stored for this path)");
23778
- }
23779
- lines.push("");
23780
- }
23781
- return lines.join("\n");
23782
- }
23783
-
23784
- // ../types/src/dedupe-session-file-changes-by-path.ts
23785
- function defaultRichness(c) {
23786
- const patch = typeof c.patchContent === "string" ? c.patchContent.length : 0;
23787
- const nt = typeof c.newText === "string" ? c.newText.length : 0;
23788
- const ot = typeof c.oldText === "string" ? c.oldText.length : 0;
23789
- const dir = c.directoryRemoved === true ? 8 : 0;
23790
- return (patch > 0 ? 4 : 0) + (nt > 0 ? 2 : 0) + (ot > 0 ? 1 : 0) + dir;
23791
- }
23792
- function dedupeSessionFileChangesByPath(items, richness = (item) => defaultRichness(item)) {
23793
- const byPath = /* @__PURE__ */ new Map();
23794
- for (const item of items) {
23795
- const p = typeof item.path === "string" ? item.path.trim() : "";
23796
- if (!p) continue;
23797
- const prev = byPath.get(p);
23798
- if (!prev || richness(item) >= richness(prev)) byPath.set(p, item);
23799
- }
23800
- return Array.from(byPath.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([, v]) => v);
23801
- }
23802
-
23803
23649
  // ../types/src/diff/line-diff.ts
23804
23650
  function normalizeDiffLineText(line) {
23805
23651
  return line.replace(/\r$/, "").replace(/\s*\\s*$/, "");
@@ -24855,6 +24701,71 @@ async function createSdkStdioAcpClient(options) {
24855
24701
  });
24856
24702
  }
24857
24703
 
24704
+ // src/firehose/proxy/constants.ts
24705
+ var LOCAL_PROXY_REQUEST_TIMEOUT_MS = 3e4;
24706
+
24707
+ // src/firehose/proxy/prepare-local-proxy-request-headers.ts
24708
+ var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
24709
+ "connection",
24710
+ "keep-alive",
24711
+ "proxy-authenticate",
24712
+ "proxy-authorization",
24713
+ "te",
24714
+ "trailers",
24715
+ "transfer-encoding",
24716
+ "upgrade"
24717
+ ]);
24718
+ var BODY_HEADERS = /* @__PURE__ */ new Set(["content-length", "content-type", "content-encoding"]);
24719
+ function methodAllowsRequestBody(method) {
24720
+ const normalized = method.toUpperCase();
24721
+ return normalized !== "GET" && normalized !== "HEAD";
24722
+ }
24723
+ function prepareLocalProxyRequestHeaders(method, headers, sendingBody) {
24724
+ const out = {};
24725
+ for (const [key, value] of Object.entries(headers ?? {})) {
24726
+ const lower = key.toLowerCase();
24727
+ if (lower === "host") continue;
24728
+ if (HOP_BY_HOP_HEADERS.has(lower)) continue;
24729
+ if (lower === "origin" || lower === "referer") continue;
24730
+ if (!sendingBody && BODY_HEADERS.has(lower)) continue;
24731
+ out[key] = value;
24732
+ }
24733
+ return out;
24734
+ }
24735
+
24736
+ // src/firehose/proxy/build-local-proxy-http-request.ts
24737
+ var ALLOWED_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
24738
+ function willSendLocalProxyBody(request) {
24739
+ if (!methodAllowsRequestBody(request.method)) return false;
24740
+ return request.body !== void 0 && request.body !== null;
24741
+ }
24742
+ function buildLocalProxyHttpRequest(request) {
24743
+ let url2;
24744
+ try {
24745
+ url2 = new URL(request.url);
24746
+ } catch {
24747
+ return { error: `Invalid URL: ${request.url}` };
24748
+ }
24749
+ const hostname2 = url2.hostname.replace(/^\[|\]$/g, "");
24750
+ if (!ALLOWED_HOSTS.has(url2.hostname) && !ALLOWED_HOSTS.has(hostname2)) {
24751
+ return { error: "Only localhost requests are allowed" };
24752
+ }
24753
+ const sendingBody = willSendLocalProxyBody(request);
24754
+ const headers = prepareLocalProxyRequestHeaders(request.method, request.headers, sendingBody);
24755
+ headers.host = url2.host;
24756
+ const isHttps = url2.protocol === "https:";
24757
+ const port = url2.port ? Number(url2.port) : isHttps ? 443 : 80;
24758
+ return {
24759
+ method: request.method,
24760
+ hostname: url2.hostname,
24761
+ port,
24762
+ path: `${url2.pathname}${url2.search}`,
24763
+ headers,
24764
+ sendingBody,
24765
+ body: sendingBody ? request.body : void 0
24766
+ };
24767
+ }
24768
+
24858
24769
  // src/net/transient-local-fetch-retry.ts
24859
24770
  var LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS = [30, 100, 200, 400];
24860
24771
  function sleepMs(ms) {
@@ -24879,148 +24790,151 @@ function isTransientLocalServiceError(err) {
24879
24790
  if (!text) return false;
24880
24791
  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
24792
  }
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
- }
24793
+
24794
+ // src/firehose/proxy/incoming-message-headers.ts
24795
+ function incomingMessageHeaders(res) {
24796
+ const headers = {};
24797
+ for (const [k, v] of Object.entries(res.headers)) {
24798
+ if (typeof v === "string") headers[k] = v;
24799
+ else if (Array.isArray(v) && v[0]) headers[k] = v[0];
24902
24800
  }
24903
- throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
24801
+ return headers;
24904
24802
  }
24905
24803
 
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 };
24804
+ // src/firehose/proxy/run-local-http-request.ts
24805
+ async function loadHttpModule(isHttps) {
24806
+ return isHttps ? import("https") : import("http");
24927
24807
  }
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
24808
+ function buildLocalHttpRequestOptions(built) {
24809
+ return {
24810
+ method: built.method,
24811
+ hostname: built.hostname,
24812
+ port: built.port,
24813
+ path: built.path,
24814
+ headers: built.headers
24942
24815
  };
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
- });
24816
+ }
24817
+ function runLocalHttpRequestOnce(mod, built) {
24818
+ const opts = buildLocalHttpRequestOptions(built);
24819
+ return new Promise((resolve35) => {
24820
+ const req = mod.request(opts, (res) => {
24821
+ const chunks = [];
24822
+ res.on("data", (chunk) => chunks.push(chunk));
24823
+ res.on("end", () => {
24824
+ resolve35({
24825
+ statusCode: res.statusCode ?? 0,
24826
+ headers: incomingMessageHeaders(res),
24827
+ body: Buffer.concat(chunks)
24962
24828
  });
24963
24829
  });
24964
- req.on("error", (err) => {
24830
+ res.on("error", (err) => {
24965
24831
  resolve35({
24966
- id: request.id,
24967
24832
  statusCode: 0,
24968
24833
  headers: {},
24969
- body: "",
24834
+ body: Buffer.alloc(0),
24970
24835
  error: err.message
24971
24836
  });
24972
24837
  });
24973
- const method = request.method.toUpperCase();
24974
- if (request.body && method !== "GET" && method !== "HEAD") req.write(request.body);
24975
- req.end();
24976
24838
  });
24839
+ req.setTimeout(LOCAL_PROXY_REQUEST_TIMEOUT_MS, () => {
24840
+ req.destroy(new Error(`Local preview request timed out after ${LOCAL_PROXY_REQUEST_TIMEOUT_MS}ms`));
24841
+ });
24842
+ req.on("error", (err) => {
24843
+ resolve35({
24844
+ statusCode: 0,
24845
+ headers: {},
24846
+ body: Buffer.alloc(0),
24847
+ error: err.message
24848
+ });
24849
+ });
24850
+ if (built.sendingBody && built.body != null) {
24851
+ req.write(built.body);
24852
+ }
24853
+ req.end();
24854
+ });
24855
+ }
24856
+
24857
+ // src/firehose/proxy/proxy-to-local.ts
24858
+ function isIdempotentProxyMethod(method) {
24859
+ const m = method.toUpperCase();
24860
+ return m === "GET" || m === "HEAD" || m === "OPTIONS";
24861
+ }
24862
+ async function proxyToLocal(request) {
24863
+ const built = buildLocalProxyHttpRequest(request);
24864
+ if ("error" in built) {
24865
+ return { id: request.id, statusCode: 0, headers: {}, body: "", error: built.error };
24866
+ }
24867
+ const isHttps = request.url.startsWith("https:");
24868
+ const mod = await loadHttpModule(isHttps);
24869
+ const maxAttempts = isIdempotentProxyMethod(request.method) ? LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length + 1 : 1;
24870
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
24871
+ const once = await runLocalHttpRequestOnce(mod, built);
24977
24872
  const errMsg = once.error ?? "";
24978
24873
  const canRetry = attempt < maxAttempts - 1 && errMsg !== "" && isTransientLocalServiceError(new Error(errMsg));
24979
- if (!canRetry) return once;
24874
+ if (!canRetry) {
24875
+ return {
24876
+ id: request.id,
24877
+ statusCode: once.statusCode,
24878
+ headers: once.headers,
24879
+ body: once.body.toString("utf8"),
24880
+ error: once.error
24881
+ };
24882
+ }
24980
24883
  const waitMs = LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS[Math.min(attempt, LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length - 1)] ?? 100;
24981
24884
  await sleepMs(waitMs);
24982
24885
  }
24983
24886
  throw new Error("Local proxy retry loop exited unexpectedly");
24984
24887
  }
24888
+
24889
+ // src/firehose/proxy/proxy-to-local-streaming.ts
24985
24890
  async function proxyToLocalStreaming(request, callbacks) {
24986
- const checked = checkUrlAndHost(request);
24987
- if ("error" in checked) {
24988
- callbacks.onError(checked.error);
24891
+ const built = buildLocalProxyHttpRequest(request);
24892
+ if ("error" in built) {
24893
+ callbacks.onError(built.error);
24989
24894
  return;
24990
24895
  }
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);
24896
+ const isHttps = request.url.startsWith("https:");
24897
+ const mod = await loadHttpModule(isHttps);
24898
+ const opts = buildLocalHttpRequestOptions(built);
24899
+ await new Promise((resolve35) => {
24900
+ const req = mod.request(opts, (res) => {
24901
+ try {
24902
+ callbacks.onStart(res.statusCode ?? 0, incomingMessageHeaders(res));
24903
+ } catch (err) {
24904
+ req.destroy();
24905
+ callbacks.onError(err instanceof Error ? err.message : String(err));
24906
+ resolve35();
24907
+ return;
25016
24908
  }
25017
- } finally {
25018
- reader.releaseLock();
24909
+ res.on("data", (chunk) => {
24910
+ try {
24911
+ callbacks.onChunk(new Uint8Array(chunk));
24912
+ } catch (err) {
24913
+ req.destroy();
24914
+ callbacks.onError(err instanceof Error ? err.message : String(err));
24915
+ }
24916
+ });
24917
+ res.on("end", () => {
24918
+ callbacks.onEnd();
24919
+ resolve35();
24920
+ });
24921
+ res.on("error", (err) => {
24922
+ callbacks.onError(err.message);
24923
+ resolve35();
24924
+ });
24925
+ });
24926
+ req.setTimeout(LOCAL_PROXY_REQUEST_TIMEOUT_MS, () => {
24927
+ req.destroy(new Error(`Local preview request timed out after ${LOCAL_PROXY_REQUEST_TIMEOUT_MS}ms`));
24928
+ });
24929
+ req.on("error", (err) => {
24930
+ callbacks.onError(err.message);
24931
+ resolve35();
24932
+ });
24933
+ if (built.sendingBody && built.body != null) {
24934
+ req.write(built.body);
25019
24935
  }
25020
- callbacks.onEnd();
25021
- } catch (err) {
25022
- callbacks.onError(err instanceof Error ? err.message : String(err));
25023
- }
24936
+ req.end();
24937
+ });
25024
24938
  }
25025
24939
 
25026
24940
  // src/skills/preview.ts
@@ -25272,7 +25186,7 @@ function installBridgeProcessResilience() {
25272
25186
  }
25273
25187
 
25274
25188
  // src/cli-version.ts
25275
- var CLI_VERSION = "0.1.61".length > 0 ? "0.1.61" : "0.0.0-dev";
25189
+ var CLI_VERSION = "0.1.63".length > 0 ? "0.1.63" : "0.0.0-dev";
25276
25190
 
25277
25191
  // src/connection/heartbeat/constants.ts
25278
25192
  var BRIDGE_APP_HEARTBEAT_INTERVAL_MS = 1e4;
@@ -26654,38 +26568,38 @@ function readCodeNavCacheMigrationSql(filename) {
26654
26568
  }
26655
26569
 
26656
26570
  // 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));
26571
+ function checkpointSatisfiedByLegacyChain(applied, replacesLegacyMigrations) {
26572
+ return Array.isArray(replacesLegacyMigrations) && replacesLegacyMigrations.length > 0 && replacesLegacyMigrations.every((n) => applied.has(n));
26659
26573
  }
26660
- function recordMigrationAndPruneCheckpointLegacy(db, migration, applied2) {
26574
+ function recordMigrationAndPruneCheckpointLegacy(db, migration, applied) {
26661
26575
  db.run("INSERT INTO __migrations (name) VALUES (?)", [migration.name]);
26662
- applied2.add(migration.name);
26576
+ applied.add(migration.name);
26663
26577
  if (migration.checkpoint !== true) return;
26664
26578
  const legacy = migration.replacesLegacyMigrations;
26665
26579
  if (!legacy?.length) return;
26666
26580
  for (const legacyName of legacy) {
26667
26581
  if (legacyName === migration.name) continue;
26668
26582
  db.run("DELETE FROM __migrations WHERE name = ?", [legacyName]);
26669
- applied2.delete(legacyName);
26583
+ applied.delete(legacyName);
26670
26584
  }
26671
26585
  }
26672
26586
  function runSqliteMigrations(db, migrations, bootstrapSql, label) {
26673
26587
  db.exec(bootstrapSql);
26674
26588
  const appliedRows = db.all("SELECT name FROM __migrations");
26675
- const applied2 = new Set(appliedRows.map((r) => r.name));
26589
+ const applied = new Set(appliedRows.map((r) => r.name));
26676
26590
  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);
26591
+ if (applied.has(migration.name)) continue;
26592
+ if (migration.checkpoint === true && checkpointSatisfiedByLegacyChain(applied, migration.replacesLegacyMigrations)) {
26593
+ recordMigrationAndPruneCheckpointLegacy(db, migration, applied);
26680
26594
  continue;
26681
26595
  }
26682
26596
  if (migration.alreadyApplied?.(db)) {
26683
- recordMigrationAndPruneCheckpointLegacy(db, migration, applied2);
26597
+ recordMigrationAndPruneCheckpointLegacy(db, migration, applied);
26684
26598
  continue;
26685
26599
  }
26686
26600
  try {
26687
26601
  migration.migrate(db);
26688
- recordMigrationAndPruneCheckpointLegacy(db, migration, applied2);
26602
+ recordMigrationAndPruneCheckpointLegacy(db, migration, applied);
26689
26603
  } catch (e) {
26690
26604
  console.error(`[${label}] Migration failed: ${migration.name}`, e);
26691
26605
  throw e;
@@ -32797,102 +32711,6 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
32797
32711
  });
32798
32712
  }
32799
32713
 
32800
- // src/agents/acp/put-summarize-change-summaries.ts
32801
- async function putEncryptedChangeSummaryRows(params) {
32802
- const base = params.apiBaseUrl.replace(/\/+$/, "");
32803
- const entries = params.rows.map(({ path: path79, summary }) => {
32804
- const enc = params.e2ee.encryptFields({ summary }, ["summary"]);
32805
- return { path: path79, summary: JSON.stringify(enc) };
32806
- });
32807
- const res = await fetch(
32808
- `${base}/internal/sessions/${encodeURIComponent(params.sessionId)}/follow-ups/summarize-changes`,
32809
- {
32810
- method: "PUT",
32811
- headers: {
32812
- Authorization: `Bearer ${params.authToken}`,
32813
- "Content-Type": "application/json"
32814
- },
32815
- body: JSON.stringify({ entries })
32816
- }
32817
- );
32818
- if (!res.ok) {
32819
- const t = await res.text();
32820
- throw new Error(`PUT summarize-changes summaries failed ${res.status}: ${t.slice(0, 500)}`);
32821
- }
32822
- }
32823
-
32824
- // src/agents/acp/maybe-upload-e2ee-session-change-summaries.ts
32825
- async function maybeUploadE2eeSessionChangeSummariesAfterAgentSuccess(params) {
32826
- const {
32827
- sessionId,
32828
- runId,
32829
- resultSuccess,
32830
- output,
32831
- e2ee,
32832
- cloudApiBaseUrl,
32833
- getCloudAccessToken,
32834
- followUpCatalogPromptId,
32835
- sessionChangeSummaryFilePaths,
32836
- log: log2
32837
- } = params;
32838
- const outputStr = typeof output === "string" ? output : "";
32839
- if (!sessionId) {
32840
- return;
32841
- }
32842
- if (!runId) {
32843
- return;
32844
- }
32845
- if (!resultSuccess) {
32846
- return;
32847
- }
32848
- if (!e2ee) {
32849
- return;
32850
- }
32851
- if (!cloudApiBaseUrl) {
32852
- return;
32853
- }
32854
- if (!getCloudAccessToken) {
32855
- return;
32856
- }
32857
- if (followUpCatalogPromptId !== BUILTIN_SESSION_CHANGE_SUMMARY_FOLLOW_UP_CATALOG_PROMPT_ID) {
32858
- return;
32859
- }
32860
- if (!sessionChangeSummaryFilePaths || sessionChangeSummaryFilePaths.length === 0) {
32861
- return;
32862
- }
32863
- if (outputStr.trim() === "") {
32864
- return;
32865
- }
32866
- const allowed = /* @__PURE__ */ new Set();
32867
- for (const p of sessionChangeSummaryFilePaths) {
32868
- const t = p.trim();
32869
- if (!t) continue;
32870
- allowed.add(t);
32871
- allowed.add(normalizeRepoRelativePath(t));
32872
- }
32873
- const rows = parseChangeSummaryJson(outputStr, allowed);
32874
- if (rows.length === 0) {
32875
- return;
32876
- }
32877
- const token = getCloudAccessToken();
32878
- if (!token) {
32879
- return;
32880
- }
32881
- try {
32882
- await putEncryptedChangeSummaryRows({
32883
- apiBaseUrl: cloudApiBaseUrl,
32884
- authToken: token,
32885
- sessionId,
32886
- e2ee,
32887
- rows
32888
- });
32889
- } catch (uploadErr) {
32890
- log2(
32891
- `[Agent] Encrypted change summary upload failed: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
32892
- );
32893
- }
32894
- }
32895
-
32896
32714
  // src/agents/planning/submit-planning-todos-for-turn.ts
32897
32715
  async function submitPlanningTodosForTurn(params) {
32898
32716
  const token = params.getCloudAccessToken();
@@ -32961,10 +32779,8 @@ async function finalizeAndSendPromptResult(params) {
32961
32779
  agentCwd,
32962
32780
  isPlanningSession,
32963
32781
  followUpCatalogPromptId,
32964
- sessionChangeSummaryFilePaths,
32965
32782
  cloudApiBaseUrl,
32966
32783
  getCloudAccessToken,
32967
- e2ee,
32968
32784
  sendResult,
32969
32785
  sendSessionUpdate,
32970
32786
  log: log2
@@ -32978,18 +32794,6 @@ async function finalizeAndSendPromptResult(params) {
32978
32794
  log: log2
32979
32795
  });
32980
32796
  }
32981
- await maybeUploadE2eeSessionChangeSummariesAfterAgentSuccess({
32982
- sessionId,
32983
- runId,
32984
- resultSuccess: result.success === true,
32985
- output: result.output,
32986
- e2ee,
32987
- cloudApiBaseUrl,
32988
- getCloudAccessToken,
32989
- followUpCatalogPromptId,
32990
- sessionChangeSummaryFilePaths,
32991
- log: log2
32992
- });
32993
32797
  const planningTodosSubmit = await maybeSubmitPlanningTodosAfterAgentSuccess({
32994
32798
  sessionId,
32995
32799
  runId,
@@ -33525,7 +33329,6 @@ async function sendPromptToAgent(options) {
33525
33329
  sendSessionUpdate,
33526
33330
  log: log2,
33527
33331
  followUpCatalogPromptId,
33528
- sessionChangeSummaryFilePaths,
33529
33332
  cloudApiBaseUrl,
33530
33333
  getCloudAccessToken,
33531
33334
  e2ee,
@@ -33569,7 +33372,6 @@ async function sendPromptToAgent(options) {
33569
33372
  agentCwd,
33570
33373
  isPlanningSession,
33571
33374
  followUpCatalogPromptId,
33572
- sessionChangeSummaryFilePaths,
33573
33375
  cloudApiBaseUrl,
33574
33376
  getCloudAccessToken,
33575
33377
  e2ee,
@@ -33640,7 +33442,6 @@ async function runAcpPrompt(ctx, runCtx, opts) {
33640
33442
  sendResult,
33641
33443
  sendSessionUpdate,
33642
33444
  followUpCatalogPromptId,
33643
- sessionChangeSummaryFilePaths,
33644
33445
  cloudApiBaseUrl,
33645
33446
  getCloudAccessToken,
33646
33447
  e2ee,
@@ -33706,7 +33507,6 @@ async function runAcpPrompt(ctx, runCtx, opts) {
33706
33507
  sendSessionUpdate,
33707
33508
  log: ctx.log,
33708
33509
  followUpCatalogPromptId,
33709
- sessionChangeSummaryFilePaths,
33710
33510
  cloudApiBaseUrl,
33711
33511
  getCloudAccessToken,
33712
33512
  e2ee,
@@ -34832,6 +34632,9 @@ function envForSpawn(base, userEnv, ports) {
34832
34632
  });
34833
34633
  if (ports.length > 0) {
34834
34634
  out.PORTS = ports.join(",");
34635
+ if (!userKeys.has("PORT")) {
34636
+ out.PORT = String(ports[0]);
34637
+ }
34835
34638
  }
34836
34639
  if (!userKeys.has("NO_COLOR")) {
34837
34640
  delete out.NO_COLOR;
@@ -35734,6 +35537,12 @@ function createBridgePreviewStack(options) {
35734
35537
  return { previewEnvironmentManager, scheduleInitialIndexBuildsOnce, identifyReportedPaths };
35735
35538
  }
35736
35539
 
35540
+ // src/firehose/build-cli-ws-url.ts
35541
+ function buildFirehoseCliWsUrl(baseUrl) {
35542
+ const base = baseUrl.startsWith("https") ? baseUrl.replace(/^https/, "wss") : baseUrl.replace(/^http/, "ws");
35543
+ return `${base.replace(/\/$/, "")}/ws`;
35544
+ }
35545
+
35737
35546
  // src/firehose/proxy/start-streaming-proxy.ts
35738
35547
  function startStreamingProxy(ws, log2, pr) {
35739
35548
  proxyToLocalStreaming(pr, {
@@ -35741,15 +35550,7 @@ function startStreamingProxy(ws, log2, pr) {
35741
35550
  if (ws.readyState !== wrapper_default.OPEN) {
35742
35551
  throw new Error("Preview stream interrupted (firehose connection closed)");
35743
35552
  }
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 });
35553
+ sendWsMessage(ws, { type: "proxy_result_start", id: pr.id, statusCode, headers });
35753
35554
  },
35754
35555
  onChunk: (chunk) => {
35755
35556
  const idBuf = Buffer.from(pr.id, "utf8");
@@ -35766,10 +35567,19 @@ function startStreamingProxy(ws, log2, pr) {
35766
35567
  });
35767
35568
  }
35768
35569
 
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`;
35570
+ // src/firehose/create-firehose-message-deps.ts
35571
+ function createFirehoseMessageDeps(ws, log2, previewEnvironmentManager) {
35572
+ const pendingProxyBody = /* @__PURE__ */ new Map();
35573
+ const earlyProxyBinaryBody = /* @__PURE__ */ new Map();
35574
+ const deps = {
35575
+ ws,
35576
+ log: log2,
35577
+ previewEnvironmentManager,
35578
+ pendingProxyBody,
35579
+ earlyProxyBinaryBody,
35580
+ startStreamingProxy: (pr) => startStreamingProxy(ws, log2, pr)
35581
+ };
35582
+ return deps;
35773
35583
  }
35774
35584
 
35775
35585
  // src/firehose/routing/firehose-message-router.ts
@@ -35810,25 +35620,27 @@ var handleProxyMessage = (msg, deps) => {
35810
35620
  if (!proxy || typeof proxy !== "object") return;
35811
35621
  const pr = proxy;
35812
35622
  if (pr.streamResponse) {
35813
- const bodyLength = pr.bodyLength ?? 0;
35623
+ const bodyLength = methodAllowsRequestBody(pr.method) ? pr.bodyLength ?? 0 : 0;
35624
+ const streamingRequest = {
35625
+ id: pr.id,
35626
+ method: pr.method,
35627
+ url: pr.url,
35628
+ headers: pr.headers,
35629
+ streamResponse: true
35630
+ };
35814
35631
  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
- });
35632
+ const earlyBody = deps.earlyProxyBinaryBody.get(pr.id);
35633
+ if (earlyBody !== void 0) {
35634
+ deps.earlyProxyBinaryBody.delete(pr.id);
35635
+ deps.startStreamingProxy({
35636
+ ...streamingRequest,
35637
+ body: earlyBody.length > 0 ? earlyBody : void 0
35638
+ });
35639
+ } else {
35640
+ deps.pendingProxyBody.set(pr.id, { pr: streamingRequest });
35641
+ }
35824
35642
  } else {
35825
- deps.startStreamingProxy({
35826
- id: pr.id,
35827
- method: pr.method,
35828
- url: pr.url,
35829
- headers: pr.headers,
35830
- streamResponse: true
35831
- });
35643
+ deps.startStreamingProxy(streamingRequest);
35832
35644
  }
35833
35645
  return;
35834
35646
  }
@@ -35859,37 +35671,63 @@ function dispatchFirehoseJsonMessage(msg, deps) {
35859
35671
 
35860
35672
  // src/firehose/routing/try-consume-binary-proxy-body.ts
35861
35673
  var PROXY_ID_BYTES = 36;
35674
+ 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
35675
  function tryConsumeBinaryProxyBody(raw, deps) {
35863
35676
  if (raw.length < PROXY_ID_BYTES) return false;
35864
35677
  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);
35678
+ if (!PROXY_ID_RE.test(id)) return false;
35868
35679
  const body = raw.slice(PROXY_ID_BYTES);
35869
- deps.startStreamingProxy({
35870
- ...pending2.pr,
35871
- body: body.length > 0 ? new Uint8Array(body) : void 0
35872
- });
35680
+ const bodyBytes = body.length > 0 ? new Uint8Array(body) : new Uint8Array(0);
35681
+ const pending2 = deps.pendingProxyBody.get(id);
35682
+ if (pending2) {
35683
+ deps.pendingProxyBody.delete(id);
35684
+ deps.startStreamingProxy({
35685
+ ...pending2.pr,
35686
+ body: bodyBytes.length > 0 ? bodyBytes : void 0
35687
+ });
35688
+ return true;
35689
+ }
35690
+ deps.earlyProxyBinaryBody.set(id, bodyBytes);
35873
35691
  return true;
35874
35692
  }
35875
35693
 
35876
- // src/firehose/connect-firehose.ts
35877
- function connectFirehose(options) {
35694
+ // src/firehose/handle-inbound-firehose-frame.ts
35695
+ function handleInboundFirehoseFrame(raw, deps) {
35696
+ if (Buffer.isBuffer(raw) && tryConsumeBinaryProxyBody(raw, deps)) {
35697
+ return;
35698
+ }
35699
+ let msg;
35700
+ try {
35701
+ const text = Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw);
35702
+ msg = JSON.parse(text);
35703
+ } catch {
35704
+ return;
35705
+ }
35706
+ if (msg.type === "proxy") {
35707
+ dispatchFirehoseJsonMessage(msg, deps);
35708
+ return;
35709
+ }
35710
+ setImmediate(() => {
35711
+ dispatchFirehoseJsonMessage(msg, deps);
35712
+ });
35713
+ }
35714
+
35715
+ // src/firehose/wire-firehose-websocket.ts
35716
+ function wireFirehoseWebSocket(options) {
35878
35717
  const {
35879
- firehoseServerUrl,
35718
+ ws,
35719
+ deps,
35720
+ log: log2,
35721
+ previewEnvironmentManager,
35880
35722
  workspaceId,
35881
35723
  bridgeName,
35882
35724
  proxyPorts,
35883
- log: log2,
35884
- previewEnvironmentManager,
35885
35725
  onOpen,
35886
35726
  onClose,
35887
35727
  suppressWebSocketErrors
35888
35728
  } = options;
35889
- const wsUrl = buildFirehoseCliWsUrl(firehoseServerUrl);
35890
- applyCliOutboundNetworkPreferences();
35891
- const ws = new wrapper_default(wsUrl, buildCliWebSocketClientOptions(wsUrl));
35892
35729
  let clearClientPing = null;
35730
+ let identifyParams = { workspaceId, bridgeName, proxyPorts };
35893
35731
  function disposeClientPing() {
35894
35732
  clearClientPing?.();
35895
35733
  clearClientPing = null;
@@ -35897,36 +35735,32 @@ function connectFirehose(options) {
35897
35735
  const firehoseSend = (payload) => {
35898
35736
  sendWsMessage(ws, payload);
35899
35737
  };
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
- };
35738
+ function sendIdentify(params) {
35739
+ identifyParams = params;
35740
+ if (ws.readyState !== wrapper_default.OPEN) return;
35741
+ sendWsMessage(ws, {
35742
+ type: "identify",
35743
+ workspaceId: params.workspaceId,
35744
+ bridgeName: params.bridgeName,
35745
+ proxyPorts: params.proxyPorts
35746
+ });
35747
+ }
35748
+ function detachPreviewEnvironmentManager() {
35749
+ previewEnvironmentManager.detachFirehose();
35750
+ }
35908
35751
  ws.on("open", () => {
35909
35752
  disposeClientPing();
35910
35753
  clearClientPing = attachWebSocketClientPing(ws, CLI_WEBSOCKET_CLIENT_PING_MS);
35911
35754
  onOpen?.();
35912
35755
  previewEnvironmentManager.attachFirehose(firehoseSend);
35913
- sendWsMessage(ws, { type: "identify", workspaceId, bridgeName, proxyPorts });
35756
+ sendIdentify(identifyParams);
35914
35757
  });
35915
35758
  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
- });
35759
+ handleInboundFirehoseFrame(raw, deps);
35926
35760
  });
35927
35761
  ws.on("close", (code, reason) => {
35928
35762
  disposeClientPing();
35929
- previewEnvironmentManager.detachFirehose();
35763
+ detachPreviewEnvironmentManager();
35930
35764
  const reasonStr = typeof reason === "string" ? reason : reason.toString();
35931
35765
  onClose?.(code, reasonStr);
35932
35766
  });
@@ -35939,25 +35773,64 @@ function connectFirehose(options) {
35939
35773
  safeCloseWebSocket(ws);
35940
35774
  }
35941
35775
  });
35776
+ return { disposeClientPing, sendIdentify, detachPreviewEnvironmentManager };
35777
+ }
35778
+
35779
+ // src/firehose/connect-firehose.ts
35780
+ function connectFirehose(options) {
35781
+ const {
35782
+ firehoseServerUrl,
35783
+ workspaceId,
35784
+ bridgeName,
35785
+ proxyPorts,
35786
+ log: log2,
35787
+ previewEnvironmentManager,
35788
+ onOpen,
35789
+ onClose,
35790
+ suppressWebSocketErrors
35791
+ } = options;
35792
+ const wsUrl = buildFirehoseCliWsUrl(firehoseServerUrl);
35793
+ const ws = new wrapper_default(wsUrl, buildCliWebSocketClientOptions(wsUrl));
35794
+ const deps = createFirehoseMessageDeps(ws, log2, previewEnvironmentManager);
35795
+ const wired = wireFirehoseWebSocket({
35796
+ ws,
35797
+ deps,
35798
+ log: log2,
35799
+ previewEnvironmentManager,
35800
+ workspaceId,
35801
+ bridgeName,
35802
+ proxyPorts,
35803
+ onOpen,
35804
+ onClose,
35805
+ suppressWebSocketErrors
35806
+ });
35942
35807
  return {
35943
35808
  close() {
35944
- disposeClientPing();
35945
- previewEnvironmentManager.detachFirehose();
35809
+ wired.disposeClientPing();
35810
+ wired.detachPreviewEnvironmentManager();
35946
35811
  safeCloseWebSocket(ws);
35947
35812
  },
35948
- isConnected: () => ws.readyState === wrapper_default.OPEN
35813
+ isConnected: () => ws.readyState === wrapper_default.OPEN,
35814
+ updateIdentify: wired.sendIdentify
35949
35815
  };
35950
35816
  }
35951
35817
 
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
- }
35818
+ // src/connection/firehose/compare-firehose-params.ts
35819
+ function proxyPortsEqual(a, b) {
35820
+ if (a.length !== b.length) return false;
35821
+ const sa = [...a].sort((x, y) => x - y);
35822
+ const sb = [...b].sort((x, y) => x - y);
35823
+ return sa.every((v, i) => v === sb[i]);
35824
+ }
35825
+ function sameFirehoseBridge(a, b) {
35826
+ return a.firehoseServerUrl === b.firehoseServerUrl && a.workspaceId === b.workspaceId && a.bridgeName === b.bridgeName;
35827
+ }
35828
+ function shouldReuseOpenFirehoseConnection(prev, next, isConnected) {
35829
+ return Boolean(prev && sameFirehoseBridge(prev, next) && isConnected());
35830
+ }
35831
+
35832
+ // src/connection/firehose/create-firehose-connect-callbacks.ts
35833
+ function createFirehoseConnectCallbacks(state, myGen, logFn, scheduleFirehoseRetryAfterDrop) {
35961
35834
  function firehoseCtx() {
35962
35835
  return {
35963
35836
  closedByUser: state.closedByUser,
@@ -35966,6 +35839,55 @@ function attachFirehoseAfterIdentified(ctx, params) {
35966
35839
  firehoseQuiet: state.firehoseQuiet
35967
35840
  };
35968
35841
  }
35842
+ return {
35843
+ onOpen: () => {
35844
+ if (myGen !== state.firehoseGeneration) return;
35845
+ try {
35846
+ clearFirehoseReconnectQuietOnOpen(
35847
+ {
35848
+ firehoseQuiet: state.firehoseQuiet,
35849
+ firehoseOutage: state.firehoseOutage,
35850
+ lastFirehoseReconnectCloseMeta: state.lastFirehoseReconnectCloseMeta
35851
+ },
35852
+ logFn
35853
+ );
35854
+ } catch {
35855
+ }
35856
+ const logOpenAsFirehoseReconnect = state.firehoseReconnectAttempt > 0;
35857
+ state.firehoseReconnectAttempt = 0;
35858
+ if (!logOpenAsFirehoseReconnect) {
35859
+ try {
35860
+ logFn("Connected to preview tunnel (local HTTP proxy and dev logs).");
35861
+ } catch {
35862
+ }
35863
+ }
35864
+ },
35865
+ onClose: (code, reason) => {
35866
+ if (myGen !== state.firehoseGeneration) return;
35867
+ state.firehoseHandle = null;
35868
+ if (state.closedByUser) return;
35869
+ state.lastFirehoseReconnectCloseMeta = { code, reason };
35870
+ try {
35871
+ beginFirehoseDeferredDisconnect(firehoseCtx(), code, reason, logFn);
35872
+ } catch {
35873
+ }
35874
+ try {
35875
+ scheduleFirehoseRetryAfterDrop({ code, reason });
35876
+ } catch {
35877
+ }
35878
+ }
35879
+ };
35880
+ }
35881
+
35882
+ // src/connection/firehose/create-firehose-reconnect-scheduler.ts
35883
+ function createFirehoseReconnectScheduler(ctx, reconnect) {
35884
+ const { state, logFn } = ctx;
35885
+ function clearFirehoseReconnectTimer() {
35886
+ if (state.firehoseReconnectTimeout != null) {
35887
+ clearTimeout(state.firehoseReconnectTimeout);
35888
+ state.firehoseReconnectTimeout = null;
35889
+ }
35890
+ }
35969
35891
  function scheduleFirehoseRetryAfterDrop(closeMeta) {
35970
35892
  if (state.closedByUser) return;
35971
35893
  const meta = closeMeta ?? state.lastFirehoseReconnectCloseMeta ?? void 0;
@@ -35987,30 +35909,60 @@ function attachFirehoseAfterIdentified(ctx, params) {
35987
35909
  state.firehoseReconnectTimeout = id;
35988
35910
  },
35989
35911
  shouldAbortBeforeRun: () => state.closedByUser,
35990
- run: () => {
35991
- const p = state.lastFirehoseParams;
35992
- if (!p) return;
35993
- attachFirehoseAfterIdentified(ctx, p);
35994
- },
35912
+ run: reconnect,
35995
35913
  reschedule: () => {
35996
35914
  scheduleFirehoseRetryAfterDrop();
35997
35915
  }
35998
35916
  });
35999
35917
  }
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
- }
35918
+ return { clearFirehoseReconnectTimer, scheduleFirehoseRetryAfterDrop };
35919
+ }
35920
+ function logInitialFirehoseConnect(state, logFn) {
35921
+ if (state.firehoseReconnectAttempt !== 0) return;
35922
+ try {
35923
+ logFn("Connecting to preview tunnel (local HTTP proxy and dev logs)\u2026");
35924
+ } catch {
36007
35925
  }
35926
+ }
35927
+ function replaceFirehoseHandle(state) {
36008
35928
  state.firehoseGeneration += 1;
36009
35929
  const myGen = state.firehoseGeneration;
36010
35930
  if (state.firehoseHandle) {
36011
35931
  state.firehoseHandle.close();
36012
35932
  state.firehoseHandle = null;
36013
35933
  }
35934
+ return myGen;
35935
+ }
35936
+
35937
+ // src/connection/firehose/attach-firehose-after-identified.ts
35938
+ function attachFirehoseAfterIdentified(ctx, params) {
35939
+ const { state, previewEnvironmentManager, logFn } = ctx;
35940
+ const prev = state.lastFirehoseParams;
35941
+ if (shouldReuseOpenFirehoseConnection(prev, params, () => state.firehoseHandle?.isConnected() ?? false)) {
35942
+ state.lastFirehoseParams = params;
35943
+ if (prev && !proxyPortsEqual(prev.proxyPorts, params.proxyPorts)) {
35944
+ state.firehoseHandle?.updateIdentify(params);
35945
+ }
35946
+ return;
35947
+ }
35948
+ const { clearFirehoseReconnectTimer, scheduleFirehoseRetryAfterDrop } = createFirehoseReconnectScheduler(
35949
+ ctx,
35950
+ () => {
35951
+ const p = state.lastFirehoseParams;
35952
+ if (!p) return;
35953
+ attachFirehoseAfterIdentified(ctx, p);
35954
+ }
35955
+ );
35956
+ state.lastFirehoseParams = params;
35957
+ clearFirehoseReconnectTimer();
35958
+ logInitialFirehoseConnect(state, logFn);
35959
+ const myGen = replaceFirehoseHandle(state);
35960
+ const { onOpen, onClose } = createFirehoseConnectCallbacks(
35961
+ state,
35962
+ myGen,
35963
+ logFn,
35964
+ scheduleFirehoseRetryAfterDrop
35965
+ );
36014
35966
  state.firehoseHandle = connectFirehose({
36015
35967
  firehoseServerUrl: params.firehoseServerUrl,
36016
35968
  workspaceId: params.workspaceId,
@@ -36019,45 +35971,17 @@ function attachFirehoseAfterIdentified(ctx, params) {
36019
35971
  log: logFn,
36020
35972
  previewEnvironmentManager,
36021
35973
  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
- }
35974
+ onOpen,
35975
+ onClose
36058
35976
  });
36059
35977
  }
36060
35978
 
35979
+ // src/connection/firehose/update-firehose-identify-on-open-connection.ts
35980
+ function updateFirehoseIdentifyOnOpenConnection(state, params) {
35981
+ state.lastFirehoseParams = params;
35982
+ state.firehoseHandle?.updateIdentify(params);
35983
+ }
35984
+
36061
35985
  // src/connection/create-bridge-identified-handler.ts
36062
35986
  function createOnBridgeIdentified(opts) {
36063
35987
  const { previewEnvironmentManager, firehoseServerUrl, workspaceId, state, logFn } = opts;
@@ -36094,6 +36018,7 @@ function createBridgeMessageDeps(params) {
36094
36018
  reportAutoDetectedAgents,
36095
36019
  warmupAgentCapabilitiesOnConnect: warmupAgentCapabilitiesOnConnect2,
36096
36020
  previewEnvironmentManager,
36021
+ updateFirehoseProxyPorts,
36097
36022
  e2ee,
36098
36023
  cloudApiBaseUrl,
36099
36024
  getCloudAccessToken
@@ -36112,6 +36037,7 @@ function createBridgeMessageDeps(params) {
36112
36037
  reportAutoDetectedAgents,
36113
36038
  warmupAgentCapabilitiesOnConnect: warmupAgentCapabilitiesOnConnect2,
36114
36039
  previewEnvironmentManager,
36040
+ updateFirehoseProxyPorts,
36115
36041
  e2ee,
36116
36042
  cloudApiBaseUrl,
36117
36043
  getCloudAccessToken
@@ -36372,6 +36298,11 @@ function createBridgeRuntimeMessageSetup(options) {
36372
36298
  getWs
36373
36299
  }),
36374
36300
  previewEnvironmentManager,
36301
+ updateFirehoseProxyPorts: (proxyPorts) => {
36302
+ const p = state.lastFirehoseParams;
36303
+ if (!p) return;
36304
+ updateFirehoseIdentifyOnOpenConnection(state, { ...p, proxyPorts });
36305
+ },
36375
36306
  e2ee,
36376
36307
  cloudApiBaseUrl: apiUrl,
36377
36308
  getCloudAccessToken: () => tokens.accessToken
@@ -43641,16 +43572,16 @@ async function applySessionWipToPreviewCheckouts(options) {
43641
43572
  return;
43642
43573
  }
43643
43574
  await gitResetWorktreeToBranch(previewCheckout, deployBranch);
43644
- const applied2 = await applyWorkingTreeWip(
43575
+ const applied = await applyWorkingTreeWip(
43645
43576
  previewCheckout,
43646
43577
  state.sessionCheckout,
43647
43578
  state.wip,
43648
43579
  log2
43649
43580
  );
43650
- if (!applied2.ok) {
43581
+ if (!applied.ok) {
43651
43582
  failure = {
43652
43583
  ok: false,
43653
- error: applied2.error ?? `Failed to apply session WIP to preview (${state.repoPathRelativeToWorkspaceRoot})`
43584
+ error: applied.error ?? `Failed to apply session WIP to preview (${state.repoPathRelativeToWorkspaceRoot})`
43654
43585
  };
43655
43586
  }
43656
43587
  });
@@ -43749,7 +43680,7 @@ async function deploySessionToPreviewEnvironment(options) {
43749
43680
  return { ok: false, error: "Failed to create preview environment worktrees" };
43750
43681
  }
43751
43682
  await yieldToEventLoop2();
43752
- const applied2 = await applySessionWipToPreviewCheckouts({
43683
+ const applied = await applySessionWipToPreviewCheckouts({
43753
43684
  previewWorktreeManager,
43754
43685
  environmentId: eid,
43755
43686
  deployBranch,
@@ -43757,8 +43688,8 @@ async function deploySessionToPreviewEnvironment(options) {
43757
43688
  repoStates,
43758
43689
  log: log2
43759
43690
  });
43760
- if (!applied2.ok) {
43761
- return { ok: false, error: applied2.error };
43691
+ if (!applied.ok) {
43692
+ return { ok: false, error: applied.error };
43762
43693
  }
43763
43694
  log2(
43764
43695
  `[worktrees] Deployed session ${sid.slice(0, 8)}\u2026 to preview ${eid.slice(0, 8)}\u2026 on branch ${deployBranch}.`
@@ -44080,8 +44011,6 @@ function dispatchLocalPrompt(next, deps) {
44080
44011
  ...sessionParentPath ? { sessionParentPath } : {},
44081
44012
  ...worktreeBaseBranches && Object.keys(worktreeBaseBranches).length > 0 ? { worktreeBaseBranches } : {},
44082
44013
  ...typeof pl.followUpCatalogPromptId === "string" ? { followUpCatalogPromptId: pl.followUpCatalogPromptId } : {},
44083
- ...Array.isArray(pl.sessionChangeSummaryFilePaths) ? { sessionChangeSummaryFilePaths: pl.sessionChangeSummaryFilePaths } : {},
44084
- ...Array.isArray(pl.sessionChangeSummaryFileSnapshots) ? { sessionChangeSummaryFileSnapshots: pl.sessionChangeSummaryFileSnapshots } : {},
44085
44014
  ...typeof pl.agentType === "string" && pl.agentType.trim() ? { agentType: pl.agentType.trim() } : {},
44086
44015
  ...pl.agentConfig != null && typeof pl.agentConfig === "object" && !Array.isArray(pl.agentConfig) && Object.keys(pl.agentConfig).length > 0 ? { agentConfig: pl.agentConfig } : {},
44087
44016
  ...Array.isArray(pl.attachments) && pl.attachments.length > 0 ? { attachments: pl.attachments } : {}
@@ -44344,8 +44273,7 @@ function createBridgePromptSenders(deps, getWs) {
44344
44273
  return true;
44345
44274
  };
44346
44275
  const sendResult = (result) => {
44347
- const skipEncryptForChangeSummaryFollowUp = result.type === "prompt_result" && result.followUpCatalogPromptId === BUILTIN_SESSION_CHANGE_SUMMARY_FOLLOW_UP_CATALOG_PROMPT_ID;
44348
- const encryptedFields = result.type === "prompt_result" && !skipEncryptForChangeSummaryFollowUp ? ["output", "error"] : [];
44276
+ const encryptedFields = result.type === "prompt_result" ? ["output", "error"] : [];
44349
44277
  sendBridgeMessage(result, encryptedFields);
44350
44278
  if (result.type === "prompt_result") {
44351
44279
  const pr = result;
@@ -44409,61 +44337,6 @@ function parseWorktreeBaseBranches(msg) {
44409
44337
  return Object.keys(out).length > 0 ? out : void 0;
44410
44338
  }
44411
44339
 
44412
- // src/agents/acp/change-summary/decrypt-change-summary-file-input.ts
44413
- function decryptChangeSummaryFileInput(row, e2ee) {
44414
- if (!e2ee) return row;
44415
- for (const field of ["path", "patchContent", "oldText", "newText"]) {
44416
- const raw = row[field];
44417
- if (typeof raw !== "string" || raw.trim() === "") continue;
44418
- let o;
44419
- try {
44420
- o = JSON.parse(raw);
44421
- } catch {
44422
- continue;
44423
- }
44424
- if (!isE2eeEnvelope(o.ee)) continue;
44425
- try {
44426
- const d = e2ee.decryptMessage(o);
44427
- const out = {
44428
- path: typeof d.path === "string" ? d.path : row.path
44429
- };
44430
- if (d.directoryRemoved === true) out.directoryRemoved = true;
44431
- else if (row.directoryRemoved === true) out.directoryRemoved = true;
44432
- if (typeof d.patchContent === "string") out.patchContent = d.patchContent;
44433
- else if (typeof row.patchContent === "string" && row.patchContent !== raw) out.patchContent = row.patchContent;
44434
- if (typeof d.oldText === "string") out.oldText = d.oldText;
44435
- else if (typeof row.oldText === "string") out.oldText = row.oldText;
44436
- if (typeof d.newText === "string") out.newText = d.newText;
44437
- else if (typeof row.newText === "string") out.newText = row.newText;
44438
- return out;
44439
- } catch {
44440
- return row;
44441
- }
44442
- }
44443
- return row;
44444
- }
44445
-
44446
- // src/agents/acp/change-summary/resolve-change-summary-prompt-for-agent.ts
44447
- function hasSummarizePayload(f) {
44448
- return f.directoryRemoved === true || f.patchContent != null && f.patchContent.trim() !== "" || f.oldText != null && f.oldText.trim() !== "" || f.newText != null && f.newText.trim() !== "";
44449
- }
44450
- function resolveChangeSummaryPromptForAgent(params) {
44451
- const isBuiltin = params.followUpCatalogPromptId === BUILTIN_SESSION_CHANGE_SUMMARY_FOLLOW_UP_CATALOG_PROMPT_ID;
44452
- const snaps = params.sessionChangeSummaryFileSnapshots;
44453
- if (!isBuiltin || !snaps || snaps.length === 0) {
44454
- return { promptText: params.bridgePromptText, sessionChangeSummaryFilePaths: void 0 };
44455
- }
44456
- const decrypted = dedupeSessionFileChangesByPath(snaps.map((row) => decryptChangeSummaryFileInput(row, params.e2ee)));
44457
- const withPayload = decrypted.filter(hasSummarizePayload);
44458
- if (withPayload.length === 0) {
44459
- return { promptText: params.bridgePromptText, sessionChangeSummaryFilePaths: void 0 };
44460
- }
44461
- return {
44462
- promptText: buildSessionChangeSummaryPrompt(withPayload),
44463
- sessionChangeSummaryFilePaths: withPayload.map((f) => f.path)
44464
- };
44465
- }
44466
-
44467
44340
  // src/agents/acp/from-bridge/bridge-prompt-preamble.ts
44468
44341
  import { execFile as execFile8 } from "node:child_process";
44469
44342
  import { promisify as promisify9 } from "node:util";
@@ -44510,29 +44383,9 @@ async function runBridgePromptPreamble(params) {
44510
44383
  });
44511
44384
  }
44512
44385
  }
44513
- function parseChangeSummarySnapshots(raw) {
44514
- if (!Array.isArray(raw) || raw.length === 0) return void 0;
44515
- const out = [];
44516
- for (const item of raw) {
44517
- if (!item || typeof item !== "object") continue;
44518
- const o = item;
44519
- const path79 = typeof o.path === "string" && o.path.trim() !== "" ? o.path.trim() : "";
44520
- if (!path79) continue;
44521
- const row = { path: path79 };
44522
- if (typeof o.patchContent === "string") row.patchContent = o.patchContent;
44523
- if (typeof o.oldText === "string") row.oldText = o.oldText;
44524
- if (typeof o.newText === "string") row.newText = o.newText;
44525
- if (o.directoryRemoved === true) row.directoryRemoved = true;
44526
- out.push(row);
44527
- }
44528
- return out.length > 0 ? out : void 0;
44529
- }
44530
44386
  function parseFollowUpFieldsFromPromptMessage(msg) {
44531
44387
  const followUpCatalogPromptId = typeof msg.followUpCatalogPromptId === "string" && msg.followUpCatalogPromptId.trim() !== "" ? msg.followUpCatalogPromptId.trim() : null;
44532
- const rawPaths = msg.sessionChangeSummaryFilePaths;
44533
- const sessionChangeSummaryFilePaths = Array.isArray(rawPaths) ? rawPaths.filter((p) => typeof p === "string" && p.trim() !== "").map((p) => p.trim()) : void 0;
44534
- const sessionChangeSummaryFileSnapshots = parseChangeSummarySnapshots(msg.sessionChangeSummaryFileSnapshots);
44535
- return { followUpCatalogPromptId, sessionChangeSummaryFilePaths, sessionChangeSummaryFileSnapshots };
44388
+ return { followUpCatalogPromptId };
44536
44389
  }
44537
44390
 
44538
44391
  // src/agents/acp/from-bridge/handle-bridge-prompt/run-preamble-and-prompt.ts
@@ -44563,25 +44416,9 @@ async function runPreambleAndPrompt(params) {
44563
44416
  runId,
44564
44417
  effectiveCwd
44565
44418
  });
44566
- const {
44567
- followUpCatalogPromptId,
44568
- sessionChangeSummaryFilePaths: pathsFromBridge,
44569
- sessionChangeSummaryFileSnapshots
44570
- } = parseFollowUpFieldsFromPromptMessage(msg);
44571
- const { promptText: resolvedPromptText, sessionChangeSummaryFilePaths } = resolveChangeSummaryPromptForAgent({
44572
- followUpCatalogPromptId,
44573
- sessionChangeSummaryFileSnapshots,
44574
- bridgePromptText: promptText,
44575
- e2ee: deps.e2ee
44576
- });
44577
- if (sessionChangeSummaryFileSnapshots && sessionChangeSummaryFileSnapshots.length > 0 && resolvedPromptText === promptText) {
44578
- deps.log(
44579
- "[Agent] Change-summary snapshots were present but the prompt was not rebuilt (decrypt failed or empty payloads); sending the bridge prompt as-is."
44580
- );
44581
- }
44582
- const pathsForUpload = sessionChangeSummaryFilePaths ?? pathsFromBridge;
44419
+ const { followUpCatalogPromptId } = parseFollowUpFieldsFromPromptMessage(msg);
44583
44420
  deps.acpManager.handlePrompt({
44584
- promptText: resolvedPromptText,
44421
+ promptText,
44585
44422
  promptId: msg.id,
44586
44423
  sessionId,
44587
44424
  runId,
@@ -44593,7 +44430,6 @@ async function runPreambleAndPrompt(params) {
44593
44430
  sendResult,
44594
44431
  sendSessionUpdate,
44595
44432
  followUpCatalogPromptId,
44596
- sessionChangeSummaryFilePaths: pathsForUpload,
44597
44433
  cloudApiBaseUrl: deps.cloudApiBaseUrl,
44598
44434
  getCloudAccessToken: deps.getCloudAccessToken,
44599
44435
  e2ee: deps.e2ee,
@@ -46179,12 +46015,17 @@ var handlePreviewEnvironmentControl = (msg, deps) => {
46179
46015
  };
46180
46016
 
46181
46017
  // src/routing/handlers/preview-environments-config.ts
46018
+ var VALID_PORT = (p) => typeof p === "number" && Number.isInteger(p) && p > 0 && p < 65536;
46182
46019
  var handlePreviewEnvironmentsConfig = (msg, deps) => {
46183
46020
  const previewEnvironments = msg.previewEnvironments;
46021
+ const proxyPorts = Array.isArray(msg.proxyPorts) ? msg.proxyPorts.filter(VALID_PORT) : void 0;
46184
46022
  setImmediate(() => {
46185
46023
  deps.previewEnvironmentManager?.applyConfig(previewEnvironments ?? []);
46186
46024
  const environmentIds = parsePreviewEnvironmentDefs(previewEnvironments ?? []).map((d) => d.environmentId);
46187
46025
  void deps.previewWorktreeManager?.syncConfiguredEnvironments(environmentIds);
46026
+ if (proxyPorts !== void 0) {
46027
+ deps.updateFirehoseProxyPorts?.(proxyPorts);
46028
+ }
46188
46029
  });
46189
46030
  };
46190
46031