@buildautomaton/cli 0.1.10 → 0.1.12

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
@@ -22072,6 +22072,26 @@ function applyCliOutboundNetworkPreferences() {
22072
22072
 
22073
22073
  // src/bridge/connection/cli-ws-client.ts
22074
22074
  import https from "node:https";
22075
+ var CLI_WEBSOCKET_CLIENT_PING_MS = 25e3;
22076
+ function attachWebSocketClientPing(ws, intervalMs) {
22077
+ let timer = null;
22078
+ function clear() {
22079
+ if (timer != null) {
22080
+ clearInterval(timer);
22081
+ timer = null;
22082
+ }
22083
+ }
22084
+ clear();
22085
+ timer = setInterval(() => {
22086
+ if (ws.readyState === wrapper_default.OPEN) {
22087
+ try {
22088
+ ws.ping();
22089
+ } catch {
22090
+ }
22091
+ }
22092
+ }, intervalMs);
22093
+ return clear;
22094
+ }
22075
22095
  function buildCliWebSocketClientOptions(wsUrl) {
22076
22096
  const wsOptions = { perMessageDeflate: false, family: 4 };
22077
22097
  if (wsUrl.startsWith("wss://")) {
@@ -22100,6 +22120,15 @@ function safeCloseWebSocket(ws) {
22100
22120
  }
22101
22121
  }
22102
22122
  }
22123
+ function safeSendWebSocketBinary(ws, data) {
22124
+ if (ws.readyState !== wrapper_default.OPEN) return false;
22125
+ try {
22126
+ ws.send(data, { binary: true });
22127
+ return true;
22128
+ } catch {
22129
+ return false;
22130
+ }
22131
+ }
22103
22132
 
22104
22133
  // src/bridge/connection/create-ws-bridge.ts
22105
22134
  var BRIDGE_AUTH_ERROR_HEADER = "x-bridge-auth-error";
@@ -22108,12 +22137,10 @@ function createWsBridge(options) {
22108
22137
  const { url: url2, onMessage, onOpen, onClose, onError: onError2, onAuthInvalid, clientPingIntervalMs } = options;
22109
22138
  applyCliOutboundNetworkPreferences();
22110
22139
  const ws = new wrapper_default(url2, buildCliWebSocketClientOptions(url2));
22111
- let clientPingTimer = null;
22112
- function clearClientPing() {
22113
- if (clientPingTimer != null) {
22114
- clearInterval(clientPingTimer);
22115
- clientPingTimer = null;
22116
- }
22140
+ let clearClientPing = null;
22141
+ function disposeClientPing() {
22142
+ clearClientPing?.();
22143
+ clearClientPing = null;
22117
22144
  }
22118
22145
  ws.on("unexpected-response", (request, response) => {
22119
22146
  const status = response?.statusCode ?? 0;
@@ -22124,16 +22151,9 @@ function createWsBridge(options) {
22124
22151
  }
22125
22152
  });
22126
22153
  ws.on("open", () => {
22127
- clearClientPing();
22154
+ disposeClientPing();
22128
22155
  if (clientPingIntervalMs != null && clientPingIntervalMs > 0) {
22129
- clientPingTimer = setInterval(() => {
22130
- if (ws.readyState === wrapper_default.OPEN) {
22131
- try {
22132
- ws.ping();
22133
- } catch {
22134
- }
22135
- }
22136
- }, clientPingIntervalMs);
22156
+ clearClientPing = attachWebSocketClientPing(ws, clientPingIntervalMs);
22137
22157
  }
22138
22158
  onOpen?.();
22139
22159
  });
@@ -22154,11 +22174,11 @@ function createWsBridge(options) {
22154
22174
  }
22155
22175
  });
22156
22176
  ws.on("close", (code, reason) => {
22157
- clearClientPing();
22177
+ disposeClientPing();
22158
22178
  onClose?.(code, reason.toString());
22159
22179
  });
22160
22180
  ws.on("error", (err) => {
22161
- clearClientPing();
22181
+ disposeClientPing();
22162
22182
  onError2?.(err);
22163
22183
  });
22164
22184
  return ws;
@@ -22844,12 +22864,64 @@ async function createSdkStdioAcpClient(options) {
22844
22864
  });
22845
22865
  }
22846
22866
 
22867
+ // src/net/transient-local-fetch-retry.ts
22868
+ var LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS = [30, 100, 200, 400];
22869
+ function sleepMs(ms) {
22870
+ return new Promise((r) => setTimeout(r, ms));
22871
+ }
22872
+ function collectErrorText(err) {
22873
+ const parts = [];
22874
+ let e = err;
22875
+ for (let depth = 0; depth < 6 && e != null; depth += 1) {
22876
+ if (e instanceof Error) {
22877
+ parts.push(e.message);
22878
+ e = e.cause;
22879
+ } else {
22880
+ parts.push(String(e));
22881
+ break;
22882
+ }
22883
+ }
22884
+ return parts.join(" ").toLowerCase();
22885
+ }
22886
+ function isTransientLocalServiceError(err) {
22887
+ const text = collectErrorText(err);
22888
+ if (!text) return false;
22889
+ 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");
22890
+ }
22891
+ function isIdempotentHttpMethod(method) {
22892
+ const m = method.toUpperCase();
22893
+ return m === "GET" || m === "HEAD" || m === "OPTIONS";
22894
+ }
22895
+ async function fetchWithLocalTransientRetries(url2, init, options) {
22896
+ const method = (init?.method ?? "GET").toUpperCase();
22897
+ const allowRetry = isIdempotentHttpMethod(method);
22898
+ const delays = options?.delaysMs ?? LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS;
22899
+ const maxAttempts = options?.maxAttempts ?? (allowRetry ? Math.max(1, delays.length + 1) : 1);
22900
+ let lastErr;
22901
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
22902
+ try {
22903
+ return await fetch(url2, init);
22904
+ } catch (e) {
22905
+ lastErr = e;
22906
+ const canRetry = allowRetry && attempt < maxAttempts - 1 && isTransientLocalServiceError(e);
22907
+ if (!canRetry) throw e;
22908
+ const waitMs = delays[Math.min(attempt, delays.length - 1)] ?? 100;
22909
+ await sleepMs(waitMs);
22910
+ }
22911
+ }
22912
+ throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
22913
+ }
22914
+
22847
22915
  // src/firehose/proxy/local-proxy.ts
22848
22916
  var ALLOWED_HOSTS = ["localhost", "127.0.0.1", "::1"];
22849
22917
  function isAllowedHost(host) {
22850
22918
  const h = host.replace(/^\[|\]$/g, "");
22851
22919
  return ALLOWED_HOSTS.includes(host) || ALLOWED_HOSTS.includes(h);
22852
22920
  }
22921
+ function isIdempotentProxyMethod(method) {
22922
+ const m = method.toUpperCase();
22923
+ return m === "GET" || m === "HEAD" || m === "OPTIONS";
22924
+ }
22853
22925
  function checkUrlAndHost(request) {
22854
22926
  let url2;
22855
22927
  try {
@@ -22877,38 +22949,47 @@ async function proxyToLocal(request) {
22877
22949
  path: url2.pathname + url2.search,
22878
22950
  headers: request.headers
22879
22951
  };
22880
- return new Promise((resolve14) => {
22881
- const req = mod.request(opts, (res) => {
22882
- const chunks = [];
22883
- res.on("data", (c) => chunks.push(c));
22884
- res.on("end", () => {
22885
- const body = Buffer.concat(chunks).toString("utf8");
22886
- const headers = {};
22887
- for (const [k, v] of Object.entries(res.headers)) {
22888
- if (typeof v === "string") headers[k] = v;
22889
- else if (Array.isArray(v) && v[0]) headers[k] = v[0];
22890
- }
22952
+ const maxAttempts = isIdempotentProxyMethod(request.method) ? LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length + 1 : 1;
22953
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
22954
+ const once = await new Promise((resolve14) => {
22955
+ const req = mod.request(opts, (res) => {
22956
+ const chunks = [];
22957
+ res.on("data", (c) => chunks.push(c));
22958
+ res.on("end", () => {
22959
+ const body = Buffer.concat(chunks).toString("utf8");
22960
+ const headers = {};
22961
+ for (const [k, v] of Object.entries(res.headers)) {
22962
+ if (typeof v === "string") headers[k] = v;
22963
+ else if (Array.isArray(v) && v[0]) headers[k] = v[0];
22964
+ }
22965
+ resolve14({
22966
+ id: request.id,
22967
+ statusCode: res.statusCode ?? 0,
22968
+ headers,
22969
+ body
22970
+ });
22971
+ });
22972
+ });
22973
+ req.on("error", (err) => {
22891
22974
  resolve14({
22892
22975
  id: request.id,
22893
- statusCode: res.statusCode ?? 0,
22894
- headers,
22895
- body
22976
+ statusCode: 0,
22977
+ headers: {},
22978
+ body: "",
22979
+ error: err.message
22896
22980
  });
22897
22981
  });
22982
+ const method = request.method.toUpperCase();
22983
+ if (request.body && method !== "GET" && method !== "HEAD") req.write(request.body);
22984
+ req.end();
22898
22985
  });
22899
- req.on("error", (err) => {
22900
- resolve14({
22901
- id: request.id,
22902
- statusCode: 0,
22903
- headers: {},
22904
- body: "",
22905
- error: err.message
22906
- });
22907
- });
22908
- const method = request.method.toUpperCase();
22909
- if (request.body && method !== "GET" && method !== "HEAD") req.write(request.body);
22910
- req.end();
22911
- });
22986
+ const errMsg = once.error ?? "";
22987
+ const canRetry = attempt < maxAttempts - 1 && errMsg !== "" && isTransientLocalServiceError(new Error(errMsg));
22988
+ if (!canRetry) return once;
22989
+ const waitMs = LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS[Math.min(attempt, LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length - 1)] ?? 100;
22990
+ await sleepMs(waitMs);
22991
+ }
22992
+ throw new Error("Local proxy retry loop exited unexpectedly");
22912
22993
  }
22913
22994
  async function proxyToLocalStreaming(request, callbacks) {
22914
22995
  const checked = checkUrlAndHost(request);
@@ -22925,7 +23006,7 @@ async function proxyToLocalStreaming(request, callbacks) {
22925
23006
  if (request.body !== void 0 && request.body !== null && method !== "GET" && method !== "HEAD") {
22926
23007
  init.body = request.body;
22927
23008
  }
22928
- const res = await fetch(request.url, init);
23009
+ const res = await fetchWithLocalTransientRetries(request.url, init);
22929
23010
  const headers = {};
22930
23011
  res.headers.forEach((value, key) => {
22931
23012
  headers[key] = value;
@@ -31404,6 +31485,9 @@ var DevServerManager = class {
31404
31485
  function startStreamingProxy(ws, log2, pr) {
31405
31486
  proxyToLocalStreaming(pr, {
31406
31487
  onStart: (statusCode, headers) => {
31488
+ if (ws.readyState !== wrapper_default.OPEN) {
31489
+ throw new Error("Preview stream interrupted (firehose connection closed)");
31490
+ }
31407
31491
  const forwardedHeaders = { ...headers };
31408
31492
  const ce = "content-encoding";
31409
31493
  const cl = "content-length";
@@ -31416,7 +31500,10 @@ function startStreamingProxy(ws, log2, pr) {
31416
31500
  },
31417
31501
  onChunk: (chunk) => {
31418
31502
  const idBuf = Buffer.from(pr.id, "utf8");
31419
- ws.send(Buffer.concat([idBuf, Buffer.from(chunk)]), { binary: true });
31503
+ const buf = Buffer.concat([idBuf, Buffer.from(chunk)]);
31504
+ if (!safeSendWebSocketBinary(ws, buf)) {
31505
+ throw new Error("Preview stream interrupted (firehose connection closed)");
31506
+ }
31420
31507
  },
31421
31508
  onEnd: () => sendWsMessage(ws, { type: "proxy_result_end", id: pr.id }),
31422
31509
  onError: (error40) => {
@@ -31534,18 +31621,15 @@ function tryConsumeBinaryProxyBody(raw, deps) {
31534
31621
  }
31535
31622
 
31536
31623
  // src/firehose/connect-firehose.ts
31537
- var FIREHOSE_CLIENT_PING_MS = 25e3;
31538
31624
  function connectFirehose(options) {
31539
31625
  const { firehoseServerUrl, workspaceId, bridgeName, proxyPorts, log: log2, devServerManager, onOpen, onClose } = options;
31540
31626
  const wsUrl = buildFirehoseCliWsUrl(firehoseServerUrl);
31541
31627
  applyCliOutboundNetworkPreferences();
31542
31628
  const ws = new wrapper_default(wsUrl, buildCliWebSocketClientOptions(wsUrl));
31543
- let clientPingTimer = null;
31544
- function clearClientPing() {
31545
- if (clientPingTimer != null) {
31546
- clearInterval(clientPingTimer);
31547
- clientPingTimer = null;
31548
- }
31629
+ let clearClientPing = null;
31630
+ function disposeClientPing() {
31631
+ clearClientPing?.();
31632
+ clearClientPing = null;
31549
31633
  }
31550
31634
  const firehoseSend = (payload) => {
31551
31635
  sendWsMessage(ws, payload);
@@ -31559,15 +31643,8 @@ function connectFirehose(options) {
31559
31643
  startStreamingProxy: (pr) => startStreamingProxy(ws, log2, pr)
31560
31644
  };
31561
31645
  ws.on("open", () => {
31562
- clearClientPing();
31563
- clientPingTimer = setInterval(() => {
31564
- if (ws.readyState === wrapper_default.OPEN) {
31565
- try {
31566
- ws.ping();
31567
- } catch {
31568
- }
31569
- }
31570
- }, FIREHOSE_CLIENT_PING_MS);
31646
+ disposeClientPing();
31647
+ clearClientPing = attachWebSocketClientPing(ws, CLI_WEBSOCKET_CLIENT_PING_MS);
31571
31648
  onOpen?.();
31572
31649
  devServerManager.attachFirehose(firehoseSend);
31573
31650
  sendWsMessage(ws, { type: "identify", workspaceId, bridgeName, proxyPorts });
@@ -31583,18 +31660,21 @@ function connectFirehose(options) {
31583
31660
  }
31584
31661
  });
31585
31662
  ws.on("close", (code, reason) => {
31586
- clearClientPing();
31663
+ disposeClientPing();
31587
31664
  devServerManager.detachFirehose();
31588
31665
  const reasonStr = typeof reason === "string" ? reason : reason.toString();
31589
31666
  onClose?.(code, reasonStr);
31590
31667
  });
31591
31668
  ws.on("error", (err) => {
31592
- clearClientPing();
31669
+ disposeClientPing();
31593
31670
  logCliWebSocketError(log2, "[Proxy and log service]", err);
31671
+ if (ws.readyState === wrapper_default.CONNECTING || ws.readyState === wrapper_default.OPEN) {
31672
+ safeCloseWebSocket(ws);
31673
+ }
31594
31674
  });
31595
31675
  return {
31596
31676
  close() {
31597
- clearClientPing();
31677
+ disposeClientPing();
31598
31678
  devServerManager.detachFirehose();
31599
31679
  safeCloseWebSocket(ws);
31600
31680
  },
@@ -31650,13 +31730,6 @@ function attachFirehoseAfterIdentified(ctx, params) {
31650
31730
  if (myGen !== state.firehoseGeneration) return;
31651
31731
  state.firehoseHandle = null;
31652
31732
  if (state.closedByUser) return;
31653
- const main = state.currentWs;
31654
- if (!main || main.readyState !== wrapper_default.OPEN) {
31655
- logFn(
31656
- `${PROXY_AND_LOG_SERVICE_LABEL} Not reconnecting preview and log stream: main bridge connection is not open.`
31657
- );
31658
- return;
31659
- }
31660
31733
  beginFirehoseDeferredDisconnect(firehoseCtx(), code, reason, logFn);
31661
31734
  clearFirehoseReconnectTimer();
31662
31735
  const delay2 = reconnectDelayMs(state.firehoseReconnectAttempt);
@@ -31671,15 +31744,6 @@ function attachFirehoseAfterIdentified(ctx, params) {
31671
31744
  state.firehoseReconnectTimeout = setTimeout(() => {
31672
31745
  state.firehoseReconnectTimeout = null;
31673
31746
  if (state.closedByUser) return;
31674
- const w = state.currentWs;
31675
- if (!w || w.readyState !== wrapper_default.OPEN) {
31676
- if (state.firehoseQuiet.verboseLogs) {
31677
- logFn(
31678
- `${PROXY_AND_LOG_SERVICE_LABEL} Reconnect skipped: main bridge connection closed before preview stream could reconnect.`
31679
- );
31680
- }
31681
- return;
31682
- }
31683
31747
  const p = state.lastFirehoseParams;
31684
31748
  if (!p) {
31685
31749
  if (state.firehoseQuiet.verboseLogs) {
@@ -32081,21 +32145,29 @@ var handlePromptMessage = (msg, deps) => {
32081
32145
  };
32082
32146
 
32083
32147
  // src/agents/acp/from-bridge/handle-bridge-cancel-run.ts
32084
- function handleBridgeCancelRun(msg, { log: log2, acpManager }) {
32148
+ async function handleBridgeCancelRun(msg, { log: log2, acpManager, getWs }) {
32085
32149
  const runId = msg.runId;
32086
32150
  if (!runId) return;
32087
- void acpManager.cancelRun(runId).then((sent) => {
32088
- if (!sent) {
32089
- log2(
32090
- `[Agent] Cancel ignored for run ${runId.slice(0, 8)}\u2026 (no active run or cancel not available).`
32091
- );
32092
- }
32151
+ const sessionId = typeof msg.sessionId === "string" ? msg.sessionId.trim() : "";
32152
+ const sent = await acpManager.cancelRun(runId);
32153
+ if (sent) return;
32154
+ log2(`[Agent] Cancel: no local run for ${runId.slice(0, 8)}\u2026 \u2014 reporting stopped to cloud.`);
32155
+ const ws = getWs();
32156
+ if (!ws || ws.readyState !== 1) return;
32157
+ sendWsMessage(ws, {
32158
+ type: "prompt_result",
32159
+ id: `cancel-nack-${runId}`,
32160
+ runId,
32161
+ ...sessionId ? { sessionId } : {},
32162
+ success: false,
32163
+ error: "Stopped by user",
32164
+ stopReason: "no_local_run"
32093
32165
  });
32094
32166
  }
32095
32167
 
32096
32168
  // src/bridge/routing/handlers/cancel-run.ts
32097
32169
  var handleCancelRunMessage = (msg, deps) => {
32098
- handleBridgeCancelRun(msg, deps);
32170
+ void handleBridgeCancelRun(msg, deps);
32099
32171
  };
32100
32172
 
32101
32173
  // src/agents/acp/from-bridge/handle-bridge-cursor-request-response.ts
@@ -32775,7 +32847,6 @@ async function refreshBridgeTokens(params) {
32775
32847
  }
32776
32848
 
32777
32849
  // src/bridge/connection/main-bridge-ws-lifecycle.ts
32778
- var BRIDGE_CLIENT_PING_MS = 25e3;
32779
32850
  function createMainBridgeWebSocketLifecycle(params) {
32780
32851
  const {
32781
32852
  state,
@@ -32847,7 +32918,7 @@ function createMainBridgeWebSocketLifecycle(params) {
32847
32918
  const url2 = buildBridgeUrl(apiUrl, workspaceId, tokens.accessToken);
32848
32919
  state.currentWs = createWsBridge({
32849
32920
  url: url2,
32850
- clientPingIntervalMs: BRIDGE_CLIENT_PING_MS,
32921
+ clientPingIntervalMs: CLI_WEBSOCKET_CLIENT_PING_MS,
32851
32922
  onAuthInvalid: () => {
32852
32923
  if (authRefreshInFlight) return;
32853
32924
  void (async () => {