@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/cli.js CHANGED
@@ -25631,7 +25631,7 @@ var {
25631
25631
  } = import_index.default;
25632
25632
 
25633
25633
  // src/cli-version.ts
25634
- var CLI_VERSION = "0.1.61".length > 0 ? "0.1.61" : "0.0.0-dev";
25634
+ var CLI_VERSION = "0.1.63".length > 0 ? "0.1.63" : "0.0.0-dev";
25635
25635
 
25636
25636
  // src/cli/defaults.ts
25637
25637
  var DEFAULT_API_URL = process.env.BUILDAUTOMATON_API_URL ?? "https://api.buildautomaton.com";
@@ -26151,18 +26151,6 @@ var import_websocket = __toESM(require_websocket(), 1);
26151
26151
  var import_websocket_server = __toESM(require_websocket_server(), 1);
26152
26152
  var wrapper_default = import_websocket.default;
26153
26153
 
26154
- // src/net/apply-cli-outbound-network-prefs.ts
26155
- import dns from "node:dns";
26156
- var applied = false;
26157
- function applyCliOutboundNetworkPreferences() {
26158
- if (applied) return;
26159
- applied = true;
26160
- try {
26161
- dns.setDefaultResultOrder("ipv4first");
26162
- } catch {
26163
- }
26164
- }
26165
-
26166
26154
  // src/connection/cli-ws-client.ts
26167
26155
  import https from "node:https";
26168
26156
  var CLI_WEBSOCKET_CLIENT_PING_MS = 25e3;
@@ -26264,7 +26252,6 @@ function createWsBridge(options) {
26264
26252
  onCompactHeartbeatAck,
26265
26253
  isActiveSocket
26266
26254
  } = options;
26267
- applyCliOutboundNetworkPreferences();
26268
26255
  const ws = new wrapper_default(url2, buildCliWebSocketClientOptions(url2));
26269
26256
  let clearClientPing = null;
26270
26257
  function disposeClientPing() {
@@ -27706,38 +27693,38 @@ function readCodeNavCacheMigrationSql(filename) {
27706
27693
  }
27707
27694
 
27708
27695
  // src/sqlite/run-sqlite-migrations.ts
27709
- function checkpointSatisfiedByLegacyChain(applied2, replacesLegacyMigrations) {
27710
- return Array.isArray(replacesLegacyMigrations) && replacesLegacyMigrations.length > 0 && replacesLegacyMigrations.every((n) => applied2.has(n));
27696
+ function checkpointSatisfiedByLegacyChain(applied, replacesLegacyMigrations) {
27697
+ return Array.isArray(replacesLegacyMigrations) && replacesLegacyMigrations.length > 0 && replacesLegacyMigrations.every((n) => applied.has(n));
27711
27698
  }
27712
- function recordMigrationAndPruneCheckpointLegacy(db, migration, applied2) {
27699
+ function recordMigrationAndPruneCheckpointLegacy(db, migration, applied) {
27713
27700
  db.run("INSERT INTO __migrations (name) VALUES (?)", [migration.name]);
27714
- applied2.add(migration.name);
27701
+ applied.add(migration.name);
27715
27702
  if (migration.checkpoint !== true) return;
27716
27703
  const legacy = migration.replacesLegacyMigrations;
27717
27704
  if (!legacy?.length) return;
27718
27705
  for (const legacyName of legacy) {
27719
27706
  if (legacyName === migration.name) continue;
27720
27707
  db.run("DELETE FROM __migrations WHERE name = ?", [legacyName]);
27721
- applied2.delete(legacyName);
27708
+ applied.delete(legacyName);
27722
27709
  }
27723
27710
  }
27724
27711
  function runSqliteMigrations(db, migrations, bootstrapSql, label) {
27725
27712
  db.exec(bootstrapSql);
27726
27713
  const appliedRows = db.all("SELECT name FROM __migrations");
27727
- const applied2 = new Set(appliedRows.map((r) => r.name));
27714
+ const applied = new Set(appliedRows.map((r) => r.name));
27728
27715
  for (const migration of migrations) {
27729
- if (applied2.has(migration.name)) continue;
27730
- if (migration.checkpoint === true && checkpointSatisfiedByLegacyChain(applied2, migration.replacesLegacyMigrations)) {
27731
- recordMigrationAndPruneCheckpointLegacy(db, migration, applied2);
27716
+ if (applied.has(migration.name)) continue;
27717
+ if (migration.checkpoint === true && checkpointSatisfiedByLegacyChain(applied, migration.replacesLegacyMigrations)) {
27718
+ recordMigrationAndPruneCheckpointLegacy(db, migration, applied);
27732
27719
  continue;
27733
27720
  }
27734
27721
  if (migration.alreadyApplied?.(db)) {
27735
- recordMigrationAndPruneCheckpointLegacy(db, migration, applied2);
27722
+ recordMigrationAndPruneCheckpointLegacy(db, migration, applied);
27736
27723
  continue;
27737
27724
  }
27738
27725
  try {
27739
27726
  migration.migrate(db);
27740
- recordMigrationAndPruneCheckpointLegacy(db, migration, applied2);
27727
+ recordMigrationAndPruneCheckpointLegacy(db, migration, applied);
27741
27728
  } catch (e) {
27742
27729
  console.error(`[${label}] Migration failed: ${migration.name}`, e);
27743
27730
  throw e;
@@ -31968,7 +31955,6 @@ var StoryCheckpointSummarySchema = CheckpointSummarySchema.extend({
31968
31955
 
31969
31956
  // ../types/src/sessions.ts
31970
31957
  init_zod();
31971
- var BUILTIN_SESSION_CHANGE_SUMMARY_FOLLOW_UP_CATALOG_PROMPT_ID = "__builtin_change_summary__";
31972
31958
  var SessionMetaSchema = external_exports.object({
31973
31959
  sessionId: external_exports.string(),
31974
31960
  workspaceId: external_exports.string(),
@@ -32101,146 +32087,6 @@ function buildPlanningSessionFollowUpAgentPrompt(userPrompt, existingTodos) {
32101
32087
  ].join("\n");
32102
32088
  }
32103
32089
 
32104
- // ../types/src/change-summary-path.ts
32105
- function normalizeRepoRelativePath(p) {
32106
- let t = p.trim().replace(/\\/g, "/");
32107
- while (t.startsWith("./")) t = t.slice(2);
32108
- return t.replace(/\/+/g, "/");
32109
- }
32110
- function resolveChangeSummaryPathAgainstAllowed(rawPath, allowed) {
32111
- const trimmed2 = rawPath.trim();
32112
- if (!trimmed2) return null;
32113
- if (allowed.has(trimmed2)) return trimmed2;
32114
- const n = normalizeRepoRelativePath(trimmed2);
32115
- if (allowed.has(n)) return n;
32116
- for (const a of allowed) {
32117
- if (normalizeRepoRelativePath(a) === n) return a;
32118
- }
32119
- return null;
32120
- }
32121
-
32122
- // ../types/src/parse-change-summary-json.ts
32123
- function clampSummaryToAtMostTwoLines(summary) {
32124
- const lines = summary.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
32125
- return lines.slice(0, 2).join("\n");
32126
- }
32127
- function parseChangeSummaryJson(raw, allowedPaths, options) {
32128
- if (raw == null || raw.trim() === "") return [];
32129
- let text = raw.trim();
32130
- const fence = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
32131
- if (fence?.[1]) text = fence[1].trim();
32132
- let parsed;
32133
- try {
32134
- parsed = JSON.parse(text);
32135
- } catch {
32136
- const start = text.indexOf("[");
32137
- const end = text.lastIndexOf("]");
32138
- if (start < 0 || end <= start) return [];
32139
- try {
32140
- parsed = JSON.parse(text.slice(start, end + 1));
32141
- } catch {
32142
- return [];
32143
- }
32144
- }
32145
- const rows = [];
32146
- let arr = [];
32147
- if (Array.isArray(parsed)) {
32148
- arr = parsed;
32149
- } else if (parsed && typeof parsed === "object" && Array.isArray(parsed.files)) {
32150
- arr = parsed.files;
32151
- }
32152
- const skip = options?.skipPathAllowlist === true;
32153
- for (const item of arr) {
32154
- if (!item || typeof item !== "object") continue;
32155
- const o = item;
32156
- const rawPath = typeof o.path === "string" ? o.path.trim() : "";
32157
- const summary = typeof o.summary === "string" ? o.summary.trim() : "";
32158
- if (!rawPath || !summary) continue;
32159
- const path81 = skip ? normalizeRepoRelativePath(rawPath) || rawPath : resolveChangeSummaryPathAgainstAllowed(rawPath, allowedPaths);
32160
- if (!path81) continue;
32161
- rows.push({ path: path81, summary: clampSummaryToAtMostTwoLines(summary) });
32162
- }
32163
- return rows;
32164
- }
32165
-
32166
- // ../types/src/build-change-summary-prompt.ts
32167
- var PATCH_PREVIEW_MAX = 12e3;
32168
- function clip(s, max) {
32169
- if (s.length <= max) return s;
32170
- return `${s.slice(0, max)}
32171
-
32172
- \u2026(truncated, ${s.length - max} more characters)`;
32173
- }
32174
- function buildSessionChangeSummaryPrompt(files) {
32175
- const lines = [
32176
- "You are the same agent that produced the changes below. Summarize **your own** edits so a reader can scan them quickly.",
32177
- "",
32178
- "Write in second person (you / your): what you changed in each path and why it matters.",
32179
- "",
32180
- "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.",
32181
- "",
32182
- "## How to format your reply (machine parsing)",
32183
- "",
32184
- "- 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.",
32185
- "- Inside that fence: **nothing except** one valid JSON value \u2014 the array described below. No trailing commentary inside the fence.",
32186
- "- **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.",
32187
- "- If you add optional plain English before the fence (e.g. one short sentence), keep it **separate**: end that sentence, blank line, then ```json.",
32188
- '- In each `"summary"` string, avoid raw double-quote characters, or escape them as `\\"` so the JSON parses.',
32189
- "",
32190
- "JSON shape **inside the fence** (array only):",
32191
- '[{"path":"<file path exactly as given>","summary":"<one line, or two short lines separated by \\n>"}]',
32192
- "",
32193
- "Rules:",
32194
- "- Include **exactly one** object per file path listed below (same path strings).",
32195
- "- If a path is a removed directory, state briefly what you removed.",
32196
- "- Do not invent paths; use only the paths provided.",
32197
- "",
32198
- "## Files you changed",
32199
- ""
32200
- ];
32201
- for (const f of files) {
32202
- lines.push(`### ${f.path}`);
32203
- if (f.directoryRemoved) {
32204
- lines.push("(directory removed)");
32205
- lines.push("");
32206
- continue;
32207
- }
32208
- if (f.patchContent && f.patchContent.trim() !== "") {
32209
- lines.push("```diff");
32210
- lines.push(clip(f.patchContent.trim(), PATCH_PREVIEW_MAX));
32211
- lines.push("```");
32212
- } else if (f.oldText != null || f.newText != null) {
32213
- const oldT = (f.oldText ?? "").trim();
32214
- const newT = (f.newText ?? "").trim();
32215
- lines.push("Previous snippet:", clip(oldT, 6e3));
32216
- lines.push("New snippet:", clip(newT, 6e3));
32217
- } else {
32218
- lines.push("(no diff body stored for this path)");
32219
- }
32220
- lines.push("");
32221
- }
32222
- return lines.join("\n");
32223
- }
32224
-
32225
- // ../types/src/dedupe-session-file-changes-by-path.ts
32226
- function defaultRichness(c) {
32227
- const patch = typeof c.patchContent === "string" ? c.patchContent.length : 0;
32228
- const nt = typeof c.newText === "string" ? c.newText.length : 0;
32229
- const ot = typeof c.oldText === "string" ? c.oldText.length : 0;
32230
- const dir = c.directoryRemoved === true ? 8 : 0;
32231
- return (patch > 0 ? 4 : 0) + (nt > 0 ? 2 : 0) + (ot > 0 ? 1 : 0) + dir;
32232
- }
32233
- function dedupeSessionFileChangesByPath(items, richness = (item) => defaultRichness(item)) {
32234
- const byPath = /* @__PURE__ */ new Map();
32235
- for (const item of items) {
32236
- const p = typeof item.path === "string" ? item.path.trim() : "";
32237
- if (!p) continue;
32238
- const prev = byPath.get(p);
32239
- if (!prev || richness(item) >= richness(prev)) byPath.set(p, item);
32240
- }
32241
- return Array.from(byPath.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([, v]) => v);
32242
- }
32243
-
32244
32090
  // ../types/src/diff/line-diff.ts
32245
32091
  function normalizeDiffLineText(line) {
32246
32092
  return line.replace(/\r$/, "").replace(/\s*\\s*$/, "");
@@ -35849,102 +35695,6 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
35849
35695
  });
35850
35696
  }
35851
35697
 
35852
- // src/agents/acp/put-summarize-change-summaries.ts
35853
- async function putEncryptedChangeSummaryRows(params) {
35854
- const base = params.apiBaseUrl.replace(/\/+$/, "");
35855
- const entries = params.rows.map(({ path: path81, summary }) => {
35856
- const enc = params.e2ee.encryptFields({ summary }, ["summary"]);
35857
- return { path: path81, summary: JSON.stringify(enc) };
35858
- });
35859
- const res = await fetch(
35860
- `${base}/internal/sessions/${encodeURIComponent(params.sessionId)}/follow-ups/summarize-changes`,
35861
- {
35862
- method: "PUT",
35863
- headers: {
35864
- Authorization: `Bearer ${params.authToken}`,
35865
- "Content-Type": "application/json"
35866
- },
35867
- body: JSON.stringify({ entries })
35868
- }
35869
- );
35870
- if (!res.ok) {
35871
- const t = await res.text();
35872
- throw new Error(`PUT summarize-changes summaries failed ${res.status}: ${t.slice(0, 500)}`);
35873
- }
35874
- }
35875
-
35876
- // src/agents/acp/maybe-upload-e2ee-session-change-summaries.ts
35877
- async function maybeUploadE2eeSessionChangeSummariesAfterAgentSuccess(params) {
35878
- const {
35879
- sessionId,
35880
- runId,
35881
- resultSuccess,
35882
- output,
35883
- e2ee,
35884
- cloudApiBaseUrl,
35885
- getCloudAccessToken,
35886
- followUpCatalogPromptId,
35887
- sessionChangeSummaryFilePaths,
35888
- log: log2
35889
- } = params;
35890
- const outputStr = typeof output === "string" ? output : "";
35891
- if (!sessionId) {
35892
- return;
35893
- }
35894
- if (!runId) {
35895
- return;
35896
- }
35897
- if (!resultSuccess) {
35898
- return;
35899
- }
35900
- if (!e2ee) {
35901
- return;
35902
- }
35903
- if (!cloudApiBaseUrl) {
35904
- return;
35905
- }
35906
- if (!getCloudAccessToken) {
35907
- return;
35908
- }
35909
- if (followUpCatalogPromptId !== BUILTIN_SESSION_CHANGE_SUMMARY_FOLLOW_UP_CATALOG_PROMPT_ID) {
35910
- return;
35911
- }
35912
- if (!sessionChangeSummaryFilePaths || sessionChangeSummaryFilePaths.length === 0) {
35913
- return;
35914
- }
35915
- if (outputStr.trim() === "") {
35916
- return;
35917
- }
35918
- const allowed = /* @__PURE__ */ new Set();
35919
- for (const p of sessionChangeSummaryFilePaths) {
35920
- const t = p.trim();
35921
- if (!t) continue;
35922
- allowed.add(t);
35923
- allowed.add(normalizeRepoRelativePath(t));
35924
- }
35925
- const rows = parseChangeSummaryJson(outputStr, allowed);
35926
- if (rows.length === 0) {
35927
- return;
35928
- }
35929
- const token = getCloudAccessToken();
35930
- if (!token) {
35931
- return;
35932
- }
35933
- try {
35934
- await putEncryptedChangeSummaryRows({
35935
- apiBaseUrl: cloudApiBaseUrl,
35936
- authToken: token,
35937
- sessionId,
35938
- e2ee,
35939
- rows
35940
- });
35941
- } catch (uploadErr) {
35942
- log2(
35943
- `[Agent] Encrypted change summary upload failed: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
35944
- );
35945
- }
35946
- }
35947
-
35948
35698
  // src/agents/planning/submit-planning-todos-for-turn.ts
35949
35699
  async function submitPlanningTodosForTurn(params) {
35950
35700
  const token = params.getCloudAccessToken();
@@ -36013,10 +35763,8 @@ async function finalizeAndSendPromptResult(params) {
36013
35763
  agentCwd,
36014
35764
  isPlanningSession,
36015
35765
  followUpCatalogPromptId,
36016
- sessionChangeSummaryFilePaths,
36017
35766
  cloudApiBaseUrl,
36018
35767
  getCloudAccessToken,
36019
- e2ee,
36020
35768
  sendResult,
36021
35769
  sendSessionUpdate,
36022
35770
  log: log2
@@ -36030,18 +35778,6 @@ async function finalizeAndSendPromptResult(params) {
36030
35778
  log: log2
36031
35779
  });
36032
35780
  }
36033
- await maybeUploadE2eeSessionChangeSummariesAfterAgentSuccess({
36034
- sessionId,
36035
- runId,
36036
- resultSuccess: result.success === true,
36037
- output: result.output,
36038
- e2ee,
36039
- cloudApiBaseUrl,
36040
- getCloudAccessToken,
36041
- followUpCatalogPromptId,
36042
- sessionChangeSummaryFilePaths,
36043
- log: log2
36044
- });
36045
35781
  const planningTodosSubmit = await maybeSubmitPlanningTodosAfterAgentSuccess({
36046
35782
  sessionId,
36047
35783
  runId,
@@ -36485,7 +36221,6 @@ async function sendPromptToAgent(options) {
36485
36221
  sendSessionUpdate,
36486
36222
  log: log2,
36487
36223
  followUpCatalogPromptId,
36488
- sessionChangeSummaryFilePaths,
36489
36224
  cloudApiBaseUrl,
36490
36225
  getCloudAccessToken,
36491
36226
  e2ee,
@@ -36529,7 +36264,6 @@ async function sendPromptToAgent(options) {
36529
36264
  agentCwd,
36530
36265
  isPlanningSession,
36531
36266
  followUpCatalogPromptId,
36532
- sessionChangeSummaryFilePaths,
36533
36267
  cloudApiBaseUrl,
36534
36268
  getCloudAccessToken,
36535
36269
  e2ee,
@@ -36600,7 +36334,6 @@ async function runAcpPrompt(ctx, runCtx, opts) {
36600
36334
  sendResult,
36601
36335
  sendSessionUpdate,
36602
36336
  followUpCatalogPromptId,
36603
- sessionChangeSummaryFilePaths,
36604
36337
  cloudApiBaseUrl,
36605
36338
  getCloudAccessToken,
36606
36339
  e2ee,
@@ -36666,7 +36399,6 @@ async function runAcpPrompt(ctx, runCtx, opts) {
36666
36399
  sendSessionUpdate,
36667
36400
  log: ctx.log,
36668
36401
  followUpCatalogPromptId,
36669
- sessionChangeSummaryFilePaths,
36670
36402
  cloudApiBaseUrl,
36671
36403
  getCloudAccessToken,
36672
36404
  e2ee,
@@ -37713,6 +37445,9 @@ function envForSpawn(base, userEnv, ports) {
37713
37445
  });
37714
37446
  if (ports.length > 0) {
37715
37447
  out.PORTS = ports.join(",");
37448
+ if (!userKeys.has("PORT")) {
37449
+ out.PORT = String(ports[0]);
37450
+ }
37716
37451
  }
37717
37452
  if (!userKeys.has("NO_COLOR")) {
37718
37453
  delete out.NO_COLOR;
@@ -38615,172 +38350,189 @@ function createBridgePreviewStack(options) {
38615
38350
  return { previewEnvironmentManager, scheduleInitialIndexBuildsOnce, identifyReportedPaths };
38616
38351
  }
38617
38352
 
38618
- // src/net/transient-local-fetch-retry.ts
38619
- var LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS = [30, 100, 200, 400];
38620
- function sleepMs(ms) {
38621
- return new Promise((r) => setTimeout(r, ms));
38622
- }
38623
- function collectErrorText(err) {
38624
- const parts = [];
38625
- let e = err;
38626
- for (let depth = 0; depth < 6 && e != null; depth += 1) {
38627
- if (e instanceof Error) {
38628
- parts.push(e.message);
38629
- e = e.cause;
38630
- } else {
38631
- parts.push(String(e));
38632
- break;
38633
- }
38634
- }
38635
- return parts.join(" ").toLowerCase();
38636
- }
38637
- function isTransientLocalServiceError(err) {
38638
- const text = collectErrorText(err);
38639
- if (!text) return false;
38640
- return text.includes("fetch failed") || text.includes("econnrefused") || text.includes("econnreset") || text.includes("epipe") || text.includes("etimedout") || text.includes("socket hang up") || text.includes("network connection lost") || text.includes("ecanceled") || text.includes("aborted") || text.includes("und_err_socket") || text.includes("other side closed") || text.includes("eai_again");
38353
+ // src/firehose/build-cli-ws-url.ts
38354
+ function buildFirehoseCliWsUrl(baseUrl) {
38355
+ const base = baseUrl.startsWith("https") ? baseUrl.replace(/^https/, "wss") : baseUrl.replace(/^http/, "ws");
38356
+ return `${base.replace(/\/$/, "")}/ws`;
38641
38357
  }
38642
- function isIdempotentHttpMethod(method) {
38643
- const m = method.toUpperCase();
38644
- return m === "GET" || m === "HEAD" || m === "OPTIONS";
38358
+
38359
+ // src/firehose/proxy/prepare-local-proxy-request-headers.ts
38360
+ var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
38361
+ "connection",
38362
+ "keep-alive",
38363
+ "proxy-authenticate",
38364
+ "proxy-authorization",
38365
+ "te",
38366
+ "trailers",
38367
+ "transfer-encoding",
38368
+ "upgrade"
38369
+ ]);
38370
+ var BODY_HEADERS = /* @__PURE__ */ new Set(["content-length", "content-type", "content-encoding"]);
38371
+ function methodAllowsRequestBody(method) {
38372
+ const normalized = method.toUpperCase();
38373
+ return normalized !== "GET" && normalized !== "HEAD";
38645
38374
  }
38646
- async function fetchWithLocalTransientRetries(url2, init, options) {
38647
- const method = (init?.method ?? "GET").toUpperCase();
38648
- const allowRetry = isIdempotentHttpMethod(method);
38649
- const delays = options?.delaysMs ?? LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS;
38650
- const maxAttempts = options?.maxAttempts ?? (allowRetry ? Math.max(1, delays.length + 1) : 1);
38651
- let lastErr;
38652
- for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
38653
- try {
38654
- return await fetch(url2, init);
38655
- } catch (e) {
38656
- lastErr = e;
38657
- const canRetry = allowRetry && attempt < maxAttempts - 1 && isTransientLocalServiceError(e);
38658
- if (!canRetry) throw e;
38659
- const waitMs = delays[Math.min(attempt, delays.length - 1)] ?? 100;
38660
- await sleepMs(waitMs);
38661
- }
38375
+ function prepareLocalProxyRequestHeaders(method, headers, sendingBody) {
38376
+ const out = {};
38377
+ for (const [key, value] of Object.entries(headers ?? {})) {
38378
+ const lower = key.toLowerCase();
38379
+ if (lower === "host") continue;
38380
+ if (HOP_BY_HOP_HEADERS.has(lower)) continue;
38381
+ if (lower === "origin" || lower === "referer") continue;
38382
+ if (!sendingBody && BODY_HEADERS.has(lower)) continue;
38383
+ out[key] = value;
38662
38384
  }
38663
- throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
38385
+ return out;
38664
38386
  }
38665
38387
 
38666
- // src/firehose/proxy/local-proxy.ts
38667
- var ALLOWED_HOSTS = ["localhost", "127.0.0.1", "::1"];
38668
- function isAllowedHost(host) {
38669
- const h = host.replace(/^\[|\]$/g, "");
38670
- return ALLOWED_HOSTS.includes(host) || ALLOWED_HOSTS.includes(h);
38671
- }
38672
- function isIdempotentProxyMethod(method) {
38673
- const m = method.toUpperCase();
38674
- return m === "GET" || m === "HEAD" || m === "OPTIONS";
38388
+ // src/firehose/proxy/build-local-proxy-http-request.ts
38389
+ var ALLOWED_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
38390
+ function willSendLocalProxyBody(request) {
38391
+ if (!methodAllowsRequestBody(request.method)) return false;
38392
+ return request.body !== void 0 && request.body !== null;
38675
38393
  }
38676
- function checkUrlAndHost(request) {
38394
+ function buildLocalProxyHttpRequest(request) {
38677
38395
  let url2;
38678
38396
  try {
38679
38397
  url2 = new URL(request.url);
38680
38398
  } catch {
38681
38399
  return { error: `Invalid URL: ${request.url}` };
38682
38400
  }
38683
- if (!isAllowedHost(url2.hostname)) {
38401
+ const hostname2 = url2.hostname.replace(/^\[|\]$/g, "");
38402
+ if (!ALLOWED_HOSTS.has(url2.hostname) && !ALLOWED_HOSTS.has(hostname2)) {
38684
38403
  return { error: "Only localhost requests are allowed" };
38685
38404
  }
38686
- return { url: url2 };
38687
- }
38688
- async function proxyToLocal(request) {
38689
- const checked = checkUrlAndHost(request);
38690
- if ("error" in checked) {
38691
- return { id: request.id, statusCode: 0, headers: {}, body: "", error: checked.error };
38692
- }
38693
- const { url: url2 } = checked;
38405
+ const sendingBody = willSendLocalProxyBody(request);
38406
+ const headers = prepareLocalProxyRequestHeaders(request.method, request.headers, sendingBody);
38407
+ headers.host = url2.host;
38694
38408
  const isHttps = url2.protocol === "https:";
38695
- const mod = isHttps ? await import("https") : await import("http");
38696
- const opts = {
38409
+ const port = url2.port ? Number(url2.port) : isHttps ? 443 : 80;
38410
+ return {
38697
38411
  method: request.method,
38698
38412
  hostname: url2.hostname,
38699
- port: url2.port || (isHttps ? 443 : 80),
38700
- path: url2.pathname + url2.search,
38701
- headers: request.headers
38413
+ port,
38414
+ path: `${url2.pathname}${url2.search}`,
38415
+ headers,
38416
+ sendingBody,
38417
+ body: sendingBody ? request.body : void 0
38702
38418
  };
38703
- const maxAttempts = isIdempotentProxyMethod(request.method) ? LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length + 1 : 1;
38704
- for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
38705
- const once = await new Promise((resolve37) => {
38706
- const req = mod.request(opts, (res) => {
38707
- const chunks = [];
38708
- res.on("data", (c) => chunks.push(c));
38709
- res.on("end", () => {
38710
- const body = Buffer.concat(chunks).toString("utf8");
38711
- const headers = {};
38712
- for (const [k, v] of Object.entries(res.headers)) {
38713
- if (typeof v === "string") headers[k] = v;
38714
- else if (Array.isArray(v) && v[0]) headers[k] = v[0];
38715
- }
38716
- resolve37({
38717
- id: request.id,
38718
- statusCode: res.statusCode ?? 0,
38719
- headers,
38720
- body
38721
- });
38419
+ }
38420
+
38421
+ // src/firehose/proxy/constants.ts
38422
+ var LOCAL_PROXY_REQUEST_TIMEOUT_MS = 3e4;
38423
+
38424
+ // src/firehose/proxy/incoming-message-headers.ts
38425
+ function incomingMessageHeaders(res) {
38426
+ const headers = {};
38427
+ for (const [k, v] of Object.entries(res.headers)) {
38428
+ if (typeof v === "string") headers[k] = v;
38429
+ else if (Array.isArray(v) && v[0]) headers[k] = v[0];
38430
+ }
38431
+ return headers;
38432
+ }
38433
+
38434
+ // src/firehose/proxy/run-local-http-request.ts
38435
+ async function loadHttpModule(isHttps) {
38436
+ return isHttps ? import("https") : import("http");
38437
+ }
38438
+ function buildLocalHttpRequestOptions(built) {
38439
+ return {
38440
+ method: built.method,
38441
+ hostname: built.hostname,
38442
+ port: built.port,
38443
+ path: built.path,
38444
+ headers: built.headers
38445
+ };
38446
+ }
38447
+ function runLocalHttpRequestOnce(mod, built) {
38448
+ const opts = buildLocalHttpRequestOptions(built);
38449
+ return new Promise((resolve37) => {
38450
+ const req = mod.request(opts, (res) => {
38451
+ const chunks = [];
38452
+ res.on("data", (chunk) => chunks.push(chunk));
38453
+ res.on("end", () => {
38454
+ resolve37({
38455
+ statusCode: res.statusCode ?? 0,
38456
+ headers: incomingMessageHeaders(res),
38457
+ body: Buffer.concat(chunks)
38722
38458
  });
38723
38459
  });
38724
- req.on("error", (err) => {
38460
+ res.on("error", (err) => {
38725
38461
  resolve37({
38726
- id: request.id,
38727
38462
  statusCode: 0,
38728
38463
  headers: {},
38729
- body: "",
38464
+ body: Buffer.alloc(0),
38730
38465
  error: err.message
38731
38466
  });
38732
38467
  });
38733
- const method = request.method.toUpperCase();
38734
- if (request.body && method !== "GET" && method !== "HEAD") req.write(request.body);
38735
- req.end();
38736
38468
  });
38737
- const errMsg = once.error ?? "";
38738
- const canRetry = attempt < maxAttempts - 1 && errMsg !== "" && isTransientLocalServiceError(new Error(errMsg));
38739
- if (!canRetry) return once;
38740
- const waitMs = LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS[Math.min(attempt, LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length - 1)] ?? 100;
38741
- await sleepMs(waitMs);
38742
- }
38743
- throw new Error("Local proxy retry loop exited unexpectedly");
38469
+ req.setTimeout(LOCAL_PROXY_REQUEST_TIMEOUT_MS, () => {
38470
+ req.destroy(new Error(`Local preview request timed out after ${LOCAL_PROXY_REQUEST_TIMEOUT_MS}ms`));
38471
+ });
38472
+ req.on("error", (err) => {
38473
+ resolve37({
38474
+ statusCode: 0,
38475
+ headers: {},
38476
+ body: Buffer.alloc(0),
38477
+ error: err.message
38478
+ });
38479
+ });
38480
+ if (built.sendingBody && built.body != null) {
38481
+ req.write(built.body);
38482
+ }
38483
+ req.end();
38484
+ });
38744
38485
  }
38486
+
38487
+ // src/firehose/proxy/proxy-to-local-streaming.ts
38745
38488
  async function proxyToLocalStreaming(request, callbacks) {
38746
- const checked = checkUrlAndHost(request);
38747
- if ("error" in checked) {
38748
- callbacks.onError(checked.error);
38489
+ const built = buildLocalProxyHttpRequest(request);
38490
+ if ("error" in built) {
38491
+ callbacks.onError(built.error);
38749
38492
  return;
38750
38493
  }
38751
- try {
38752
- const method = request.method.toUpperCase();
38753
- const init = {
38754
- method: request.method,
38755
- headers: request.headers
38756
- };
38757
- if (request.body !== void 0 && request.body !== null && method !== "GET" && method !== "HEAD") {
38758
- init.body = request.body;
38759
- }
38760
- const res = await fetchWithLocalTransientRetries(request.url, init);
38761
- const headers = {};
38762
- res.headers.forEach((value, key) => {
38763
- headers[key] = value;
38764
- });
38765
- callbacks.onStart(res.status, headers);
38766
- const reader = res.body?.getReader();
38767
- if (!reader) {
38768
- callbacks.onEnd();
38769
- return;
38770
- }
38771
- try {
38772
- for (; ; ) {
38773
- const { done, value } = await reader.read();
38774
- if (done) break;
38775
- callbacks.onChunk(value);
38494
+ const isHttps = request.url.startsWith("https:");
38495
+ const mod = await loadHttpModule(isHttps);
38496
+ const opts = buildLocalHttpRequestOptions(built);
38497
+ await new Promise((resolve37) => {
38498
+ const req = mod.request(opts, (res) => {
38499
+ try {
38500
+ callbacks.onStart(res.statusCode ?? 0, incomingMessageHeaders(res));
38501
+ } catch (err) {
38502
+ req.destroy();
38503
+ callbacks.onError(err instanceof Error ? err.message : String(err));
38504
+ resolve37();
38505
+ return;
38776
38506
  }
38777
- } finally {
38778
- reader.releaseLock();
38507
+ res.on("data", (chunk) => {
38508
+ try {
38509
+ callbacks.onChunk(new Uint8Array(chunk));
38510
+ } catch (err) {
38511
+ req.destroy();
38512
+ callbacks.onError(err instanceof Error ? err.message : String(err));
38513
+ }
38514
+ });
38515
+ res.on("end", () => {
38516
+ callbacks.onEnd();
38517
+ resolve37();
38518
+ });
38519
+ res.on("error", (err) => {
38520
+ callbacks.onError(err.message);
38521
+ resolve37();
38522
+ });
38523
+ });
38524
+ req.setTimeout(LOCAL_PROXY_REQUEST_TIMEOUT_MS, () => {
38525
+ req.destroy(new Error(`Local preview request timed out after ${LOCAL_PROXY_REQUEST_TIMEOUT_MS}ms`));
38526
+ });
38527
+ req.on("error", (err) => {
38528
+ callbacks.onError(err.message);
38529
+ resolve37();
38530
+ });
38531
+ if (built.sendingBody && built.body != null) {
38532
+ req.write(built.body);
38779
38533
  }
38780
- callbacks.onEnd();
38781
- } catch (err) {
38782
- callbacks.onError(err instanceof Error ? err.message : String(err));
38783
- }
38534
+ req.end();
38535
+ });
38784
38536
  }
38785
38537
 
38786
38538
  // src/firehose/proxy/start-streaming-proxy.ts
@@ -38790,15 +38542,7 @@ function startStreamingProxy(ws, log2, pr) {
38790
38542
  if (ws.readyState !== wrapper_default.OPEN) {
38791
38543
  throw new Error("Preview stream interrupted (firehose connection closed)");
38792
38544
  }
38793
- const forwardedHeaders = { ...headers };
38794
- const ce = "content-encoding";
38795
- const cl = "content-length";
38796
- const keys = Object.keys(forwardedHeaders).filter(
38797
- (k) => k.toLowerCase() !== ce && k.toLowerCase() !== cl
38798
- );
38799
- const filtered = {};
38800
- for (const k of keys) if (forwardedHeaders[k] != null) filtered[k] = forwardedHeaders[k];
38801
- sendWsMessage(ws, { type: "proxy_result_start", id: pr.id, statusCode, headers: filtered });
38545
+ sendWsMessage(ws, { type: "proxy_result_start", id: pr.id, statusCode, headers });
38802
38546
  },
38803
38547
  onChunk: (chunk) => {
38804
38548
  const idBuf = Buffer.from(pr.id, "utf8");
@@ -38815,10 +38559,19 @@ function startStreamingProxy(ws, log2, pr) {
38815
38559
  });
38816
38560
  }
38817
38561
 
38818
- // src/firehose/build-cli-ws-url.ts
38819
- function buildFirehoseCliWsUrl(baseUrl) {
38820
- const base = baseUrl.startsWith("https") ? baseUrl.replace(/^https/, "wss") : baseUrl.replace(/^http/, "ws");
38821
- return `${base.replace(/\/$/, "")}/ws`;
38562
+ // src/firehose/create-firehose-message-deps.ts
38563
+ function createFirehoseMessageDeps(ws, log2, previewEnvironmentManager) {
38564
+ const pendingProxyBody = /* @__PURE__ */ new Map();
38565
+ const earlyProxyBinaryBody = /* @__PURE__ */ new Map();
38566
+ const deps = {
38567
+ ws,
38568
+ log: log2,
38569
+ previewEnvironmentManager,
38570
+ pendingProxyBody,
38571
+ earlyProxyBinaryBody,
38572
+ startStreamingProxy: (pr) => startStreamingProxy(ws, log2, pr)
38573
+ };
38574
+ return deps;
38822
38575
  }
38823
38576
 
38824
38577
  // src/firehose/routing/firehose-message-router.ts
@@ -38853,36 +38606,95 @@ var handlePreviewEnvironmentLogsViewerClose = (msg, deps) => {
38853
38606
  deps.previewEnvironmentManager.handleFirehoseLogViewerClose(environmentId, viewerId);
38854
38607
  };
38855
38608
 
38856
- // src/firehose/routing/handlers/proxy-message.ts
38857
- var handleProxyMessage = (msg, deps) => {
38858
- const proxy = msg.proxy;
38859
- if (!proxy || typeof proxy !== "object") return;
38860
- const pr = proxy;
38861
- if (pr.streamResponse) {
38862
- const bodyLength = pr.bodyLength ?? 0;
38863
- if (bodyLength > 0) {
38864
- deps.pendingProxyBody.set(pr.id, {
38865
- pr: {
38866
- id: pr.id,
38867
- method: pr.method,
38868
- url: pr.url,
38869
- headers: pr.headers,
38870
- streamResponse: true
38871
- }
38872
- });
38873
- } else {
38874
- deps.startStreamingProxy({
38875
- id: pr.id,
38876
- method: pr.method,
38877
- url: pr.url,
38878
- headers: pr.headers,
38879
- streamResponse: true
38880
- });
38881
- }
38882
- return;
38883
- }
38884
- void proxyToLocal(pr).then((res) => {
38885
- if (res.error) deps.log(`[Proxy and log service] Preview proxy failed: ${res.error}`);
38609
+ // src/net/transient-local-fetch-retry.ts
38610
+ var LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS = [30, 100, 200, 400];
38611
+ function sleepMs(ms) {
38612
+ return new Promise((r) => setTimeout(r, ms));
38613
+ }
38614
+ function collectErrorText(err) {
38615
+ const parts = [];
38616
+ let e = err;
38617
+ for (let depth = 0; depth < 6 && e != null; depth += 1) {
38618
+ if (e instanceof Error) {
38619
+ parts.push(e.message);
38620
+ e = e.cause;
38621
+ } else {
38622
+ parts.push(String(e));
38623
+ break;
38624
+ }
38625
+ }
38626
+ return parts.join(" ").toLowerCase();
38627
+ }
38628
+ function isTransientLocalServiceError(err) {
38629
+ const text = collectErrorText(err);
38630
+ if (!text) return false;
38631
+ 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");
38632
+ }
38633
+
38634
+ // src/firehose/proxy/proxy-to-local.ts
38635
+ function isIdempotentProxyMethod(method) {
38636
+ const m = method.toUpperCase();
38637
+ return m === "GET" || m === "HEAD" || m === "OPTIONS";
38638
+ }
38639
+ async function proxyToLocal(request) {
38640
+ const built = buildLocalProxyHttpRequest(request);
38641
+ if ("error" in built) {
38642
+ return { id: request.id, statusCode: 0, headers: {}, body: "", error: built.error };
38643
+ }
38644
+ const isHttps = request.url.startsWith("https:");
38645
+ const mod = await loadHttpModule(isHttps);
38646
+ const maxAttempts = isIdempotentProxyMethod(request.method) ? LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length + 1 : 1;
38647
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
38648
+ const once = await runLocalHttpRequestOnce(mod, built);
38649
+ const errMsg = once.error ?? "";
38650
+ const canRetry = attempt < maxAttempts - 1 && errMsg !== "" && isTransientLocalServiceError(new Error(errMsg));
38651
+ if (!canRetry) {
38652
+ return {
38653
+ id: request.id,
38654
+ statusCode: once.statusCode,
38655
+ headers: once.headers,
38656
+ body: once.body.toString("utf8"),
38657
+ error: once.error
38658
+ };
38659
+ }
38660
+ const waitMs = LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS[Math.min(attempt, LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length - 1)] ?? 100;
38661
+ await sleepMs(waitMs);
38662
+ }
38663
+ throw new Error("Local proxy retry loop exited unexpectedly");
38664
+ }
38665
+
38666
+ // src/firehose/routing/handlers/proxy-message.ts
38667
+ var handleProxyMessage = (msg, deps) => {
38668
+ const proxy = msg.proxy;
38669
+ if (!proxy || typeof proxy !== "object") return;
38670
+ const pr = proxy;
38671
+ if (pr.streamResponse) {
38672
+ const bodyLength = methodAllowsRequestBody(pr.method) ? pr.bodyLength ?? 0 : 0;
38673
+ const streamingRequest = {
38674
+ id: pr.id,
38675
+ method: pr.method,
38676
+ url: pr.url,
38677
+ headers: pr.headers,
38678
+ streamResponse: true
38679
+ };
38680
+ if (bodyLength > 0) {
38681
+ const earlyBody = deps.earlyProxyBinaryBody.get(pr.id);
38682
+ if (earlyBody !== void 0) {
38683
+ deps.earlyProxyBinaryBody.delete(pr.id);
38684
+ deps.startStreamingProxy({
38685
+ ...streamingRequest,
38686
+ body: earlyBody.length > 0 ? earlyBody : void 0
38687
+ });
38688
+ } else {
38689
+ deps.pendingProxyBody.set(pr.id, { pr: streamingRequest });
38690
+ }
38691
+ } else {
38692
+ deps.startStreamingProxy(streamingRequest);
38693
+ }
38694
+ return;
38695
+ }
38696
+ void proxyToLocal(pr).then((res) => {
38697
+ if (res.error) deps.log(`[Proxy and log service] Preview proxy failed: ${res.error}`);
38886
38698
  sendWsMessage(deps.ws, { type: "proxy_result", ...res, id: pr.id });
38887
38699
  }).catch((err) => {
38888
38700
  deps.log(
@@ -38908,37 +38720,63 @@ function dispatchFirehoseJsonMessage(msg, deps) {
38908
38720
 
38909
38721
  // src/firehose/routing/try-consume-binary-proxy-body.ts
38910
38722
  var PROXY_ID_BYTES = 36;
38723
+ var PROXY_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
38911
38724
  function tryConsumeBinaryProxyBody(raw, deps) {
38912
38725
  if (raw.length < PROXY_ID_BYTES) return false;
38913
38726
  const id = raw.slice(0, PROXY_ID_BYTES).toString("utf8");
38914
- const pending2 = deps.pendingProxyBody.get(id);
38915
- if (!pending2) return false;
38916
- deps.pendingProxyBody.delete(id);
38727
+ if (!PROXY_ID_RE.test(id)) return false;
38917
38728
  const body = raw.slice(PROXY_ID_BYTES);
38918
- deps.startStreamingProxy({
38919
- ...pending2.pr,
38920
- body: body.length > 0 ? new Uint8Array(body) : void 0
38921
- });
38729
+ const bodyBytes = body.length > 0 ? new Uint8Array(body) : new Uint8Array(0);
38730
+ const pending2 = deps.pendingProxyBody.get(id);
38731
+ if (pending2) {
38732
+ deps.pendingProxyBody.delete(id);
38733
+ deps.startStreamingProxy({
38734
+ ...pending2.pr,
38735
+ body: bodyBytes.length > 0 ? bodyBytes : void 0
38736
+ });
38737
+ return true;
38738
+ }
38739
+ deps.earlyProxyBinaryBody.set(id, bodyBytes);
38922
38740
  return true;
38923
38741
  }
38924
38742
 
38925
- // src/firehose/connect-firehose.ts
38926
- function connectFirehose(options) {
38743
+ // src/firehose/handle-inbound-firehose-frame.ts
38744
+ function handleInboundFirehoseFrame(raw, deps) {
38745
+ if (Buffer.isBuffer(raw) && tryConsumeBinaryProxyBody(raw, deps)) {
38746
+ return;
38747
+ }
38748
+ let msg;
38749
+ try {
38750
+ const text = Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw);
38751
+ msg = JSON.parse(text);
38752
+ } catch {
38753
+ return;
38754
+ }
38755
+ if (msg.type === "proxy") {
38756
+ dispatchFirehoseJsonMessage(msg, deps);
38757
+ return;
38758
+ }
38759
+ setImmediate(() => {
38760
+ dispatchFirehoseJsonMessage(msg, deps);
38761
+ });
38762
+ }
38763
+
38764
+ // src/firehose/wire-firehose-websocket.ts
38765
+ function wireFirehoseWebSocket(options) {
38927
38766
  const {
38928
- firehoseServerUrl,
38767
+ ws,
38768
+ deps,
38769
+ log: log2,
38770
+ previewEnvironmentManager,
38929
38771
  workspaceId,
38930
38772
  bridgeName,
38931
38773
  proxyPorts,
38932
- log: log2,
38933
- previewEnvironmentManager,
38934
38774
  onOpen,
38935
38775
  onClose,
38936
38776
  suppressWebSocketErrors
38937
38777
  } = options;
38938
- const wsUrl = buildFirehoseCliWsUrl(firehoseServerUrl);
38939
- applyCliOutboundNetworkPreferences();
38940
- const ws = new wrapper_default(wsUrl, buildCliWebSocketClientOptions(wsUrl));
38941
38778
  let clearClientPing = null;
38779
+ let identifyParams = { workspaceId, bridgeName, proxyPorts };
38942
38780
  function disposeClientPing() {
38943
38781
  clearClientPing?.();
38944
38782
  clearClientPing = null;
@@ -38946,36 +38784,32 @@ function connectFirehose(options) {
38946
38784
  const firehoseSend = (payload) => {
38947
38785
  sendWsMessage(ws, payload);
38948
38786
  };
38949
- const pendingProxyBody = /* @__PURE__ */ new Map();
38950
- const deps = {
38951
- ws,
38952
- log: log2,
38953
- previewEnvironmentManager,
38954
- pendingProxyBody,
38955
- startStreamingProxy: (pr) => startStreamingProxy(ws, log2, pr)
38956
- };
38787
+ function sendIdentify(params) {
38788
+ identifyParams = params;
38789
+ if (ws.readyState !== wrapper_default.OPEN) return;
38790
+ sendWsMessage(ws, {
38791
+ type: "identify",
38792
+ workspaceId: params.workspaceId,
38793
+ bridgeName: params.bridgeName,
38794
+ proxyPorts: params.proxyPorts
38795
+ });
38796
+ }
38797
+ function detachPreviewEnvironmentManager() {
38798
+ previewEnvironmentManager.detachFirehose();
38799
+ }
38957
38800
  ws.on("open", () => {
38958
38801
  disposeClientPing();
38959
38802
  clearClientPing = attachWebSocketClientPing(ws, CLI_WEBSOCKET_CLIENT_PING_MS);
38960
38803
  onOpen?.();
38961
38804
  previewEnvironmentManager.attachFirehose(firehoseSend);
38962
- sendWsMessage(ws, { type: "identify", workspaceId, bridgeName, proxyPorts });
38805
+ sendIdentify(identifyParams);
38963
38806
  });
38964
38807
  ws.on("message", (raw) => {
38965
- if (Buffer.isBuffer(raw) && tryConsumeBinaryProxyBody(raw, deps)) {
38966
- return;
38967
- }
38968
- setImmediate(() => {
38969
- try {
38970
- const text = Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw);
38971
- dispatchFirehoseJsonMessage(JSON.parse(text), deps);
38972
- } catch {
38973
- }
38974
- });
38808
+ handleInboundFirehoseFrame(raw, deps);
38975
38809
  });
38976
38810
  ws.on("close", (code, reason) => {
38977
38811
  disposeClientPing();
38978
- previewEnvironmentManager.detachFirehose();
38812
+ detachPreviewEnvironmentManager();
38979
38813
  const reasonStr = typeof reason === "string" ? reason : reason.toString();
38980
38814
  onClose?.(code, reasonStr);
38981
38815
  });
@@ -38988,25 +38822,64 @@ function connectFirehose(options) {
38988
38822
  safeCloseWebSocket(ws);
38989
38823
  }
38990
38824
  });
38825
+ return { disposeClientPing, sendIdentify, detachPreviewEnvironmentManager };
38826
+ }
38827
+
38828
+ // src/firehose/connect-firehose.ts
38829
+ function connectFirehose(options) {
38830
+ const {
38831
+ firehoseServerUrl,
38832
+ workspaceId,
38833
+ bridgeName,
38834
+ proxyPorts,
38835
+ log: log2,
38836
+ previewEnvironmentManager,
38837
+ onOpen,
38838
+ onClose,
38839
+ suppressWebSocketErrors
38840
+ } = options;
38841
+ const wsUrl = buildFirehoseCliWsUrl(firehoseServerUrl);
38842
+ const ws = new wrapper_default(wsUrl, buildCliWebSocketClientOptions(wsUrl));
38843
+ const deps = createFirehoseMessageDeps(ws, log2, previewEnvironmentManager);
38844
+ const wired = wireFirehoseWebSocket({
38845
+ ws,
38846
+ deps,
38847
+ log: log2,
38848
+ previewEnvironmentManager,
38849
+ workspaceId,
38850
+ bridgeName,
38851
+ proxyPorts,
38852
+ onOpen,
38853
+ onClose,
38854
+ suppressWebSocketErrors
38855
+ });
38991
38856
  return {
38992
38857
  close() {
38993
- disposeClientPing();
38994
- previewEnvironmentManager.detachFirehose();
38858
+ wired.disposeClientPing();
38859
+ wired.detachPreviewEnvironmentManager();
38995
38860
  safeCloseWebSocket(ws);
38996
38861
  },
38997
- isConnected: () => ws.readyState === wrapper_default.OPEN
38862
+ isConnected: () => ws.readyState === wrapper_default.OPEN,
38863
+ updateIdentify: wired.sendIdentify
38998
38864
  };
38999
38865
  }
39000
38866
 
39001
- // src/connection/attach-firehose-after-identified.ts
39002
- function attachFirehoseAfterIdentified(ctx, params) {
39003
- const { state, previewEnvironmentManager, logFn } = ctx;
39004
- function clearFirehoseReconnectTimer() {
39005
- if (state.firehoseReconnectTimeout != null) {
39006
- clearTimeout(state.firehoseReconnectTimeout);
39007
- state.firehoseReconnectTimeout = null;
39008
- }
39009
- }
38867
+ // src/connection/firehose/compare-firehose-params.ts
38868
+ function proxyPortsEqual(a, b) {
38869
+ if (a.length !== b.length) return false;
38870
+ const sa = [...a].sort((x, y) => x - y);
38871
+ const sb = [...b].sort((x, y) => x - y);
38872
+ return sa.every((v, i) => v === sb[i]);
38873
+ }
38874
+ function sameFirehoseBridge(a, b) {
38875
+ return a.firehoseServerUrl === b.firehoseServerUrl && a.workspaceId === b.workspaceId && a.bridgeName === b.bridgeName;
38876
+ }
38877
+ function shouldReuseOpenFirehoseConnection(prev, next, isConnected) {
38878
+ return Boolean(prev && sameFirehoseBridge(prev, next) && isConnected());
38879
+ }
38880
+
38881
+ // src/connection/firehose/create-firehose-connect-callbacks.ts
38882
+ function createFirehoseConnectCallbacks(state, myGen, logFn, scheduleFirehoseRetryAfterDrop) {
39010
38883
  function firehoseCtx() {
39011
38884
  return {
39012
38885
  closedByUser: state.closedByUser,
@@ -39015,6 +38888,55 @@ function attachFirehoseAfterIdentified(ctx, params) {
39015
38888
  firehoseQuiet: state.firehoseQuiet
39016
38889
  };
39017
38890
  }
38891
+ return {
38892
+ onOpen: () => {
38893
+ if (myGen !== state.firehoseGeneration) return;
38894
+ try {
38895
+ clearFirehoseReconnectQuietOnOpen(
38896
+ {
38897
+ firehoseQuiet: state.firehoseQuiet,
38898
+ firehoseOutage: state.firehoseOutage,
38899
+ lastFirehoseReconnectCloseMeta: state.lastFirehoseReconnectCloseMeta
38900
+ },
38901
+ logFn
38902
+ );
38903
+ } catch {
38904
+ }
38905
+ const logOpenAsFirehoseReconnect = state.firehoseReconnectAttempt > 0;
38906
+ state.firehoseReconnectAttempt = 0;
38907
+ if (!logOpenAsFirehoseReconnect) {
38908
+ try {
38909
+ logFn("Connected to preview tunnel (local HTTP proxy and dev logs).");
38910
+ } catch {
38911
+ }
38912
+ }
38913
+ },
38914
+ onClose: (code, reason) => {
38915
+ if (myGen !== state.firehoseGeneration) return;
38916
+ state.firehoseHandle = null;
38917
+ if (state.closedByUser) return;
38918
+ state.lastFirehoseReconnectCloseMeta = { code, reason };
38919
+ try {
38920
+ beginFirehoseDeferredDisconnect(firehoseCtx(), code, reason, logFn);
38921
+ } catch {
38922
+ }
38923
+ try {
38924
+ scheduleFirehoseRetryAfterDrop({ code, reason });
38925
+ } catch {
38926
+ }
38927
+ }
38928
+ };
38929
+ }
38930
+
38931
+ // src/connection/firehose/create-firehose-reconnect-scheduler.ts
38932
+ function createFirehoseReconnectScheduler(ctx, reconnect) {
38933
+ const { state, logFn } = ctx;
38934
+ function clearFirehoseReconnectTimer() {
38935
+ if (state.firehoseReconnectTimeout != null) {
38936
+ clearTimeout(state.firehoseReconnectTimeout);
38937
+ state.firehoseReconnectTimeout = null;
38938
+ }
38939
+ }
39018
38940
  function scheduleFirehoseRetryAfterDrop(closeMeta) {
39019
38941
  if (state.closedByUser) return;
39020
38942
  const meta = closeMeta ?? state.lastFirehoseReconnectCloseMeta ?? void 0;
@@ -39036,30 +38958,60 @@ function attachFirehoseAfterIdentified(ctx, params) {
39036
38958
  state.firehoseReconnectTimeout = id;
39037
38959
  },
39038
38960
  shouldAbortBeforeRun: () => state.closedByUser,
39039
- run: () => {
39040
- const p = state.lastFirehoseParams;
39041
- if (!p) return;
39042
- attachFirehoseAfterIdentified(ctx, p);
39043
- },
38961
+ run: reconnect,
39044
38962
  reschedule: () => {
39045
38963
  scheduleFirehoseRetryAfterDrop();
39046
38964
  }
39047
38965
  });
39048
38966
  }
39049
- state.lastFirehoseParams = params;
39050
- clearFirehoseReconnectTimer();
39051
- if (state.firehoseReconnectAttempt === 0) {
39052
- try {
39053
- logFn("Connecting to preview tunnel (local HTTP proxy and dev logs)\u2026");
39054
- } catch {
39055
- }
38967
+ return { clearFirehoseReconnectTimer, scheduleFirehoseRetryAfterDrop };
38968
+ }
38969
+ function logInitialFirehoseConnect(state, logFn) {
38970
+ if (state.firehoseReconnectAttempt !== 0) return;
38971
+ try {
38972
+ logFn("Connecting to preview tunnel (local HTTP proxy and dev logs)\u2026");
38973
+ } catch {
39056
38974
  }
38975
+ }
38976
+ function replaceFirehoseHandle(state) {
39057
38977
  state.firehoseGeneration += 1;
39058
38978
  const myGen = state.firehoseGeneration;
39059
38979
  if (state.firehoseHandle) {
39060
38980
  state.firehoseHandle.close();
39061
38981
  state.firehoseHandle = null;
39062
38982
  }
38983
+ return myGen;
38984
+ }
38985
+
38986
+ // src/connection/firehose/attach-firehose-after-identified.ts
38987
+ function attachFirehoseAfterIdentified(ctx, params) {
38988
+ const { state, previewEnvironmentManager, logFn } = ctx;
38989
+ const prev = state.lastFirehoseParams;
38990
+ if (shouldReuseOpenFirehoseConnection(prev, params, () => state.firehoseHandle?.isConnected() ?? false)) {
38991
+ state.lastFirehoseParams = params;
38992
+ if (prev && !proxyPortsEqual(prev.proxyPorts, params.proxyPorts)) {
38993
+ state.firehoseHandle?.updateIdentify(params);
38994
+ }
38995
+ return;
38996
+ }
38997
+ const { clearFirehoseReconnectTimer, scheduleFirehoseRetryAfterDrop } = createFirehoseReconnectScheduler(
38998
+ ctx,
38999
+ () => {
39000
+ const p = state.lastFirehoseParams;
39001
+ if (!p) return;
39002
+ attachFirehoseAfterIdentified(ctx, p);
39003
+ }
39004
+ );
39005
+ state.lastFirehoseParams = params;
39006
+ clearFirehoseReconnectTimer();
39007
+ logInitialFirehoseConnect(state, logFn);
39008
+ const myGen = replaceFirehoseHandle(state);
39009
+ const { onOpen, onClose } = createFirehoseConnectCallbacks(
39010
+ state,
39011
+ myGen,
39012
+ logFn,
39013
+ scheduleFirehoseRetryAfterDrop
39014
+ );
39063
39015
  state.firehoseHandle = connectFirehose({
39064
39016
  firehoseServerUrl: params.firehoseServerUrl,
39065
39017
  workspaceId: params.workspaceId,
@@ -39068,45 +39020,17 @@ function attachFirehoseAfterIdentified(ctx, params) {
39068
39020
  log: logFn,
39069
39021
  previewEnvironmentManager,
39070
39022
  suppressWebSocketErrors: () => !state.closedByUser && state.firehoseOutage.startedAt != null,
39071
- onOpen: () => {
39072
- if (myGen !== state.firehoseGeneration) return;
39073
- try {
39074
- clearFirehoseReconnectQuietOnOpen(
39075
- {
39076
- firehoseQuiet: state.firehoseQuiet,
39077
- firehoseOutage: state.firehoseOutage,
39078
- lastFirehoseReconnectCloseMeta: state.lastFirehoseReconnectCloseMeta
39079
- },
39080
- logFn
39081
- );
39082
- } catch {
39083
- }
39084
- const logOpenAsFirehoseReconnect = state.firehoseReconnectAttempt > 0;
39085
- state.firehoseReconnectAttempt = 0;
39086
- if (!logOpenAsFirehoseReconnect) {
39087
- try {
39088
- logFn("Connected to preview tunnel (local HTTP proxy and dev logs).");
39089
- } catch {
39090
- }
39091
- }
39092
- },
39093
- onClose: (code, reason) => {
39094
- if (myGen !== state.firehoseGeneration) return;
39095
- state.firehoseHandle = null;
39096
- if (state.closedByUser) return;
39097
- state.lastFirehoseReconnectCloseMeta = { code, reason };
39098
- try {
39099
- beginFirehoseDeferredDisconnect(firehoseCtx(), code, reason, logFn);
39100
- } catch {
39101
- }
39102
- try {
39103
- scheduleFirehoseRetryAfterDrop({ code, reason });
39104
- } catch {
39105
- }
39106
- }
39023
+ onOpen,
39024
+ onClose
39107
39025
  });
39108
39026
  }
39109
39027
 
39028
+ // src/connection/firehose/update-firehose-identify-on-open-connection.ts
39029
+ function updateFirehoseIdentifyOnOpenConnection(state, params) {
39030
+ state.lastFirehoseParams = params;
39031
+ state.firehoseHandle?.updateIdentify(params);
39032
+ }
39033
+
39110
39034
  // src/connection/create-bridge-identified-handler.ts
39111
39035
  function createOnBridgeIdentified(opts) {
39112
39036
  const { previewEnvironmentManager, firehoseServerUrl, workspaceId, state, logFn } = opts;
@@ -39143,6 +39067,7 @@ function createBridgeMessageDeps(params) {
39143
39067
  reportAutoDetectedAgents,
39144
39068
  warmupAgentCapabilitiesOnConnect: warmupAgentCapabilitiesOnConnect2,
39145
39069
  previewEnvironmentManager,
39070
+ updateFirehoseProxyPorts,
39146
39071
  e2ee,
39147
39072
  cloudApiBaseUrl,
39148
39073
  getCloudAccessToken
@@ -39161,6 +39086,7 @@ function createBridgeMessageDeps(params) {
39161
39086
  reportAutoDetectedAgents,
39162
39087
  warmupAgentCapabilitiesOnConnect: warmupAgentCapabilitiesOnConnect2,
39163
39088
  previewEnvironmentManager,
39089
+ updateFirehoseProxyPorts,
39164
39090
  e2ee,
39165
39091
  cloudApiBaseUrl,
39166
39092
  getCloudAccessToken
@@ -39421,6 +39347,11 @@ function createBridgeRuntimeMessageSetup(options) {
39421
39347
  getWs
39422
39348
  }),
39423
39349
  previewEnvironmentManager,
39350
+ updateFirehoseProxyPorts: (proxyPorts) => {
39351
+ const p = state.lastFirehoseParams;
39352
+ if (!p) return;
39353
+ updateFirehoseIdentifyOnOpenConnection(state, { ...p, proxyPorts });
39354
+ },
39424
39355
  e2ee,
39425
39356
  cloudApiBaseUrl: apiUrl,
39426
39357
  getCloudAccessToken: () => tokens.accessToken
@@ -46690,16 +46621,16 @@ async function applySessionWipToPreviewCheckouts(options) {
46690
46621
  return;
46691
46622
  }
46692
46623
  await gitResetWorktreeToBranch(previewCheckout, deployBranch);
46693
- const applied2 = await applyWorkingTreeWip(
46624
+ const applied = await applyWorkingTreeWip(
46694
46625
  previewCheckout,
46695
46626
  state.sessionCheckout,
46696
46627
  state.wip,
46697
46628
  log2
46698
46629
  );
46699
- if (!applied2.ok) {
46630
+ if (!applied.ok) {
46700
46631
  failure = {
46701
46632
  ok: false,
46702
- error: applied2.error ?? `Failed to apply session WIP to preview (${state.repoPathRelativeToWorkspaceRoot})`
46633
+ error: applied.error ?? `Failed to apply session WIP to preview (${state.repoPathRelativeToWorkspaceRoot})`
46703
46634
  };
46704
46635
  }
46705
46636
  });
@@ -46798,7 +46729,7 @@ async function deploySessionToPreviewEnvironment(options) {
46798
46729
  return { ok: false, error: "Failed to create preview environment worktrees" };
46799
46730
  }
46800
46731
  await yieldToEventLoop2();
46801
- const applied2 = await applySessionWipToPreviewCheckouts({
46732
+ const applied = await applySessionWipToPreviewCheckouts({
46802
46733
  previewWorktreeManager,
46803
46734
  environmentId: eid,
46804
46735
  deployBranch,
@@ -46806,8 +46737,8 @@ async function deploySessionToPreviewEnvironment(options) {
46806
46737
  repoStates,
46807
46738
  log: log2
46808
46739
  });
46809
- if (!applied2.ok) {
46810
- return { ok: false, error: applied2.error };
46740
+ if (!applied.ok) {
46741
+ return { ok: false, error: applied.error };
46811
46742
  }
46812
46743
  log2(
46813
46744
  `[worktrees] Deployed session ${sid.slice(0, 8)}\u2026 to preview ${eid.slice(0, 8)}\u2026 on branch ${deployBranch}.`
@@ -47129,8 +47060,6 @@ function dispatchLocalPrompt(next, deps) {
47129
47060
  ...sessionParentPath ? { sessionParentPath } : {},
47130
47061
  ...worktreeBaseBranches && Object.keys(worktreeBaseBranches).length > 0 ? { worktreeBaseBranches } : {},
47131
47062
  ...typeof pl.followUpCatalogPromptId === "string" ? { followUpCatalogPromptId: pl.followUpCatalogPromptId } : {},
47132
- ...Array.isArray(pl.sessionChangeSummaryFilePaths) ? { sessionChangeSummaryFilePaths: pl.sessionChangeSummaryFilePaths } : {},
47133
- ...Array.isArray(pl.sessionChangeSummaryFileSnapshots) ? { sessionChangeSummaryFileSnapshots: pl.sessionChangeSummaryFileSnapshots } : {},
47134
47063
  ...typeof pl.agentType === "string" && pl.agentType.trim() ? { agentType: pl.agentType.trim() } : {},
47135
47064
  ...pl.agentConfig != null && typeof pl.agentConfig === "object" && !Array.isArray(pl.agentConfig) && Object.keys(pl.agentConfig).length > 0 ? { agentConfig: pl.agentConfig } : {},
47136
47065
  ...Array.isArray(pl.attachments) && pl.attachments.length > 0 ? { attachments: pl.attachments } : {}
@@ -47393,8 +47322,7 @@ function createBridgePromptSenders(deps, getWs) {
47393
47322
  return true;
47394
47323
  };
47395
47324
  const sendResult = (result) => {
47396
- const skipEncryptForChangeSummaryFollowUp = result.type === "prompt_result" && result.followUpCatalogPromptId === BUILTIN_SESSION_CHANGE_SUMMARY_FOLLOW_UP_CATALOG_PROMPT_ID;
47397
- const encryptedFields = result.type === "prompt_result" && !skipEncryptForChangeSummaryFollowUp ? ["output", "error"] : [];
47325
+ const encryptedFields = result.type === "prompt_result" ? ["output", "error"] : [];
47398
47326
  sendBridgeMessage(result, encryptedFields);
47399
47327
  if (result.type === "prompt_result") {
47400
47328
  const pr = result;
@@ -47458,61 +47386,6 @@ function parseWorktreeBaseBranches(msg) {
47458
47386
  return Object.keys(out).length > 0 ? out : void 0;
47459
47387
  }
47460
47388
 
47461
- // src/agents/acp/change-summary/decrypt-change-summary-file-input.ts
47462
- function decryptChangeSummaryFileInput(row, e2ee) {
47463
- if (!e2ee) return row;
47464
- for (const field of ["path", "patchContent", "oldText", "newText"]) {
47465
- const raw = row[field];
47466
- if (typeof raw !== "string" || raw.trim() === "") continue;
47467
- let o;
47468
- try {
47469
- o = JSON.parse(raw);
47470
- } catch {
47471
- continue;
47472
- }
47473
- if (!isE2eeEnvelope(o.ee)) continue;
47474
- try {
47475
- const d = e2ee.decryptMessage(o);
47476
- const out = {
47477
- path: typeof d.path === "string" ? d.path : row.path
47478
- };
47479
- if (d.directoryRemoved === true) out.directoryRemoved = true;
47480
- else if (row.directoryRemoved === true) out.directoryRemoved = true;
47481
- if (typeof d.patchContent === "string") out.patchContent = d.patchContent;
47482
- else if (typeof row.patchContent === "string" && row.patchContent !== raw) out.patchContent = row.patchContent;
47483
- if (typeof d.oldText === "string") out.oldText = d.oldText;
47484
- else if (typeof row.oldText === "string") out.oldText = row.oldText;
47485
- if (typeof d.newText === "string") out.newText = d.newText;
47486
- else if (typeof row.newText === "string") out.newText = row.newText;
47487
- return out;
47488
- } catch {
47489
- return row;
47490
- }
47491
- }
47492
- return row;
47493
- }
47494
-
47495
- // src/agents/acp/change-summary/resolve-change-summary-prompt-for-agent.ts
47496
- function hasSummarizePayload(f) {
47497
- return f.directoryRemoved === true || f.patchContent != null && f.patchContent.trim() !== "" || f.oldText != null && f.oldText.trim() !== "" || f.newText != null && f.newText.trim() !== "";
47498
- }
47499
- function resolveChangeSummaryPromptForAgent(params) {
47500
- const isBuiltin = params.followUpCatalogPromptId === BUILTIN_SESSION_CHANGE_SUMMARY_FOLLOW_UP_CATALOG_PROMPT_ID;
47501
- const snaps = params.sessionChangeSummaryFileSnapshots;
47502
- if (!isBuiltin || !snaps || snaps.length === 0) {
47503
- return { promptText: params.bridgePromptText, sessionChangeSummaryFilePaths: void 0 };
47504
- }
47505
- const decrypted = dedupeSessionFileChangesByPath(snaps.map((row) => decryptChangeSummaryFileInput(row, params.e2ee)));
47506
- const withPayload = decrypted.filter(hasSummarizePayload);
47507
- if (withPayload.length === 0) {
47508
- return { promptText: params.bridgePromptText, sessionChangeSummaryFilePaths: void 0 };
47509
- }
47510
- return {
47511
- promptText: buildSessionChangeSummaryPrompt(withPayload),
47512
- sessionChangeSummaryFilePaths: withPayload.map((f) => f.path)
47513
- };
47514
- }
47515
-
47516
47389
  // src/agents/acp/from-bridge/bridge-prompt-preamble.ts
47517
47390
  import { execFile as execFile8 } from "node:child_process";
47518
47391
  import { promisify as promisify9 } from "node:util";
@@ -47559,29 +47432,9 @@ async function runBridgePromptPreamble(params) {
47559
47432
  });
47560
47433
  }
47561
47434
  }
47562
- function parseChangeSummarySnapshots(raw) {
47563
- if (!Array.isArray(raw) || raw.length === 0) return void 0;
47564
- const out = [];
47565
- for (const item of raw) {
47566
- if (!item || typeof item !== "object") continue;
47567
- const o = item;
47568
- const path81 = typeof o.path === "string" && o.path.trim() !== "" ? o.path.trim() : "";
47569
- if (!path81) continue;
47570
- const row = { path: path81 };
47571
- if (typeof o.patchContent === "string") row.patchContent = o.patchContent;
47572
- if (typeof o.oldText === "string") row.oldText = o.oldText;
47573
- if (typeof o.newText === "string") row.newText = o.newText;
47574
- if (o.directoryRemoved === true) row.directoryRemoved = true;
47575
- out.push(row);
47576
- }
47577
- return out.length > 0 ? out : void 0;
47578
- }
47579
47435
  function parseFollowUpFieldsFromPromptMessage(msg) {
47580
47436
  const followUpCatalogPromptId = typeof msg.followUpCatalogPromptId === "string" && msg.followUpCatalogPromptId.trim() !== "" ? msg.followUpCatalogPromptId.trim() : null;
47581
- const rawPaths = msg.sessionChangeSummaryFilePaths;
47582
- const sessionChangeSummaryFilePaths = Array.isArray(rawPaths) ? rawPaths.filter((p) => typeof p === "string" && p.trim() !== "").map((p) => p.trim()) : void 0;
47583
- const sessionChangeSummaryFileSnapshots = parseChangeSummarySnapshots(msg.sessionChangeSummaryFileSnapshots);
47584
- return { followUpCatalogPromptId, sessionChangeSummaryFilePaths, sessionChangeSummaryFileSnapshots };
47437
+ return { followUpCatalogPromptId };
47585
47438
  }
47586
47439
 
47587
47440
  // src/agents/acp/from-bridge/handle-bridge-prompt/run-preamble-and-prompt.ts
@@ -47612,25 +47465,9 @@ async function runPreambleAndPrompt(params) {
47612
47465
  runId,
47613
47466
  effectiveCwd
47614
47467
  });
47615
- const {
47616
- followUpCatalogPromptId,
47617
- sessionChangeSummaryFilePaths: pathsFromBridge,
47618
- sessionChangeSummaryFileSnapshots
47619
- } = parseFollowUpFieldsFromPromptMessage(msg);
47620
- const { promptText: resolvedPromptText, sessionChangeSummaryFilePaths } = resolveChangeSummaryPromptForAgent({
47621
- followUpCatalogPromptId,
47622
- sessionChangeSummaryFileSnapshots,
47623
- bridgePromptText: promptText,
47624
- e2ee: deps.e2ee
47625
- });
47626
- if (sessionChangeSummaryFileSnapshots && sessionChangeSummaryFileSnapshots.length > 0 && resolvedPromptText === promptText) {
47627
- deps.log(
47628
- "[Agent] Change-summary snapshots were present but the prompt was not rebuilt (decrypt failed or empty payloads); sending the bridge prompt as-is."
47629
- );
47630
- }
47631
- const pathsForUpload = sessionChangeSummaryFilePaths ?? pathsFromBridge;
47468
+ const { followUpCatalogPromptId } = parseFollowUpFieldsFromPromptMessage(msg);
47632
47469
  deps.acpManager.handlePrompt({
47633
- promptText: resolvedPromptText,
47470
+ promptText,
47634
47471
  promptId: msg.id,
47635
47472
  sessionId,
47636
47473
  runId,
@@ -47642,7 +47479,6 @@ async function runPreambleAndPrompt(params) {
47642
47479
  sendResult,
47643
47480
  sendSessionUpdate,
47644
47481
  followUpCatalogPromptId,
47645
- sessionChangeSummaryFilePaths: pathsForUpload,
47646
47482
  cloudApiBaseUrl: deps.cloudApiBaseUrl,
47647
47483
  getCloudAccessToken: deps.getCloudAccessToken,
47648
47484
  e2ee: deps.e2ee,
@@ -49402,12 +49238,17 @@ var handlePreviewEnvironmentControl = (msg, deps) => {
49402
49238
  };
49403
49239
 
49404
49240
  // src/routing/handlers/preview-environments-config.ts
49241
+ var VALID_PORT = (p) => typeof p === "number" && Number.isInteger(p) && p > 0 && p < 65536;
49405
49242
  var handlePreviewEnvironmentsConfig = (msg, deps) => {
49406
49243
  const previewEnvironments = msg.previewEnvironments;
49244
+ const proxyPorts = Array.isArray(msg.proxyPorts) ? msg.proxyPorts.filter(VALID_PORT) : void 0;
49407
49245
  setImmediate(() => {
49408
49246
  deps.previewEnvironmentManager?.applyConfig(previewEnvironments ?? []);
49409
49247
  const environmentIds = parsePreviewEnvironmentDefs(previewEnvironments ?? []).map((d) => d.environmentId);
49410
49248
  void deps.previewWorktreeManager?.syncConfiguredEnvironments(environmentIds);
49249
+ if (proxyPorts !== void 0) {
49250
+ deps.updateFirehoseProxyPorts?.(proxyPorts);
49251
+ }
49411
49252
  });
49412
49253
  };
49413
49254