@buildautomaton/cli 0.1.27 → 0.1.28

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
@@ -25064,7 +25064,7 @@ var {
25064
25064
  } = import_index.default;
25065
25065
 
25066
25066
  // src/cli-version.ts
25067
- var CLI_VERSION = "0.1.27".length > 0 ? "0.1.27" : "0.0.0-dev";
25067
+ var CLI_VERSION = "0.1.28".length > 0 ? "0.1.28" : "0.0.0-dev";
25068
25068
 
25069
25069
  // src/cli/defaults.ts
25070
25070
  var DEFAULT_API_URL = process.env.BUILDAUTOMATON_API_URL ?? "https://api.buildautomaton.com";
@@ -25638,6 +25638,12 @@ function sendWsMessage(ws, payload) {
25638
25638
  }
25639
25639
  }
25640
25640
 
25641
+ // src/connection/heartbeat/constants.ts
25642
+ var BRIDGE_APP_HEARTBEAT_INTERVAL_MS = 1e4;
25643
+ var BRIDGE_HEARTBEAT_SEQ_MAX = 2147483646;
25644
+ var BRIDGE_HEARTBEAT_MISSED_ACKS_BEFORE_RECONNECT = 4;
25645
+ var BRIDGE_HEARTBEAT_RTT_SAMPLE_MAX = 5;
25646
+
25641
25647
  // ../../node_modules/.pnpm/open@10.2.0/node_modules/open/index.js
25642
25648
  import process7 from "node:process";
25643
25649
  import { Buffer as Buffer2 } from "node:buffer";
@@ -26616,14 +26622,18 @@ function runPendingAuth(options) {
26616
26622
  }
26617
26623
  function connect() {
26618
26624
  const url2 = buildPendingBridgeUrl(apiUrl, connectionId);
26625
+ let pendingHbSeq = -1;
26619
26626
  ws = createWsBridge({
26620
26627
  url: url2,
26621
26628
  onOpen: () => {
26622
26629
  clearQuietOnOpen();
26630
+ pendingHbSeq = -1;
26623
26631
  sendWsMessage(ws, { type: "identify", role: "cli", cliVersion: CLI_VERSION });
26624
26632
  keepaliveInterval = setInterval(() => {
26625
26633
  if (resolved || !ws || ws.readyState !== 1) return;
26626
- sendWsMessage(ws, { type: "ping", timestamp: Date.now() });
26634
+ pendingHbSeq = pendingHbSeq >= BRIDGE_HEARTBEAT_SEQ_MAX ? 0 : pendingHbSeq + 1;
26635
+ const hb = { t: "h", s: pendingHbSeq };
26636
+ sendWsMessage(ws, hb);
26627
26637
  }, PENDING_KEEPALIVE_MS);
26628
26638
  if (browserFallback) {
26629
26639
  clearTimeout(browserFallback);
@@ -37782,6 +37792,7 @@ function reportGitRepos(getWs, log2) {
37782
37792
  var API_TO_BRIDGE_MESSAGE_TYPES = [
37783
37793
  "auth_token",
37784
37794
  "bridge_identified",
37795
+ "ha",
37785
37796
  "dev_servers_config",
37786
37797
  "server_control",
37787
37798
  "agent_config",
@@ -37810,6 +37821,10 @@ function parseApiToBridgeMessage(data, log2) {
37810
37821
  }
37811
37822
  return null;
37812
37823
  }
37824
+ if (t === "ha") {
37825
+ const s = data.s;
37826
+ if (typeof s !== "number" || !Number.isFinite(s)) return null;
37827
+ }
37813
37828
  return data;
37814
37829
  }
37815
37830
 
@@ -37850,6 +37865,13 @@ var handleBridgeIdentified = (msg, deps) => {
37850
37865
  });
37851
37866
  };
37852
37867
 
37868
+ // src/connection/heartbeat/ack.ts
37869
+ var handleBridgeHeartbeatAck = (msg, deps) => {
37870
+ const raw = msg.s;
37871
+ if (typeof raw !== "number" || !Number.isFinite(raw)) return;
37872
+ deps.onBridgeHeartbeatAck?.(Math.trunc(raw));
37873
+ };
37874
+
37853
37875
  // src/agents/acp/from-bridge/handle-bridge-agent-config.ts
37854
37876
  function handleBridgeAgentConfig(msg, { acpManager }) {
37855
37877
  if (!Array.isArray(msg.agents) || msg.agents.length === 0) return;
@@ -39193,6 +39215,9 @@ function dispatchBridgeMessage(msg, deps) {
39193
39215
  case "bridge_identified":
39194
39216
  handleBridgeIdentified(msg, deps);
39195
39217
  break;
39218
+ case "ha":
39219
+ handleBridgeHeartbeatAck(msg, deps);
39220
+ break;
39196
39221
  case "dev_servers_config":
39197
39222
  handleDevServersConfig(msg, deps);
39198
39223
  break;
@@ -39248,9 +39273,17 @@ function dispatchBridgeMessage(msg, deps) {
39248
39273
  }
39249
39274
 
39250
39275
  // src/routing/handle-bridge-message.ts
39276
+ function normalizeInboundBridgeWebSocketJson(data) {
39277
+ if (data === null || typeof data !== "object" || Array.isArray(data)) return data;
39278
+ const o = data;
39279
+ if (o.t === "ha" && typeof o.s === "number" && Number.isFinite(o.s)) {
39280
+ return { type: "ha", s: Math.trunc(o.s) };
39281
+ }
39282
+ return data;
39283
+ }
39251
39284
  function handleBridgeMessage(data, deps) {
39252
39285
  if (!deps.getWs()) return;
39253
- const msg = parseApiToBridgeMessage(data, deps.log);
39286
+ const msg = parseApiToBridgeMessage(normalizeInboundBridgeWebSocketJson(data), deps.log);
39254
39287
  if (!msg) return;
39255
39288
  setImmediate(() => {
39256
39289
  dispatchBridgeMessage(msg, deps);
@@ -39298,7 +39331,8 @@ function createMainBridgeWebSocketLifecycle(params) {
39298
39331
  persistTokens,
39299
39332
  onAuthInvalid,
39300
39333
  e2ee,
39301
- identifyReportedPaths
39334
+ identifyReportedPaths,
39335
+ bridgeHeartbeat
39302
39336
  } = params;
39303
39337
  let authRefreshInFlight = false;
39304
39338
  function handleOpen() {
@@ -39330,6 +39364,7 @@ function createMainBridgeWebSocketLifecycle(params) {
39330
39364
  }
39331
39365
  }
39332
39366
  function handleClose(code, reason) {
39367
+ bridgeHeartbeat?.stop();
39333
39368
  try {
39334
39369
  const was = state.currentWs;
39335
39370
  state.currentWs = null;
@@ -39364,6 +39399,7 @@ function createMainBridgeWebSocketLifecycle(params) {
39364
39399
  } catch {
39365
39400
  }
39366
39401
  }
39402
+ bridgeHeartbeat?.stop();
39367
39403
  const prev = state.currentWs;
39368
39404
  if (prev) {
39369
39405
  prev.removeAllListeners();
@@ -39455,6 +39491,92 @@ function createMainBridgeWebSocketLifecycle(params) {
39455
39491
  return { connect };
39456
39492
  }
39457
39493
 
39494
+ // src/connection/heartbeat/controller.ts
39495
+ function meanRttMs(samples) {
39496
+ if (samples.length === 0) return void 0;
39497
+ let sum = 0;
39498
+ for (const x of samples) sum += x;
39499
+ return sum / samples.length;
39500
+ }
39501
+ function createBridgeHeartbeatController(params) {
39502
+ const { getWs, log: log2 } = params;
39503
+ let interval = null;
39504
+ let seqCursor = -1;
39505
+ let awaitingSeq = null;
39506
+ let sentAtMs = 0;
39507
+ let missed = 0;
39508
+ const rttSamples = [];
39509
+ function clearTimer() {
39510
+ if (interval != null) {
39511
+ clearInterval(interval);
39512
+ interval = null;
39513
+ }
39514
+ }
39515
+ function nextSeq() {
39516
+ seqCursor = seqCursor >= BRIDGE_HEARTBEAT_SEQ_MAX ? 0 : seqCursor + 1;
39517
+ return seqCursor;
39518
+ }
39519
+ function tick() {
39520
+ const ws = getWs();
39521
+ if (!ws || ws.readyState !== wrapper_default.OPEN) return;
39522
+ if (awaitingSeq !== null) {
39523
+ missed++;
39524
+ if (missed >= BRIDGE_HEARTBEAT_MISSED_ACKS_BEFORE_RECONNECT) {
39525
+ try {
39526
+ log2("[Bridge service] Heartbeat missed repeatedly; reconnecting\u2026");
39527
+ } catch {
39528
+ }
39529
+ clearTimer();
39530
+ awaitingSeq = null;
39531
+ missed = 0;
39532
+ rttSamples.length = 0;
39533
+ safeCloseWebSocket(ws);
39534
+ return;
39535
+ }
39536
+ }
39537
+ const seq = nextSeq();
39538
+ const mean = meanRttMs(rttSamples);
39539
+ const payload = mean !== void 0 && Number.isFinite(mean) ? { t: "h", s: seq, m: Math.round(mean) } : { t: "h", s: seq };
39540
+ sendWsMessage(ws, payload);
39541
+ awaitingSeq = seq;
39542
+ sentAtMs = Date.now();
39543
+ }
39544
+ return {
39545
+ start() {
39546
+ clearTimer();
39547
+ awaitingSeq = null;
39548
+ missed = 0;
39549
+ seqCursor = -1;
39550
+ rttSamples.length = 0;
39551
+ interval = setInterval(tick, BRIDGE_APP_HEARTBEAT_INTERVAL_MS);
39552
+ },
39553
+ stop() {
39554
+ clearTimer();
39555
+ awaitingSeq = null;
39556
+ missed = 0;
39557
+ rttSamples.length = 0;
39558
+ },
39559
+ onAck(seq) {
39560
+ if (awaitingSeq === null) return;
39561
+ if (!Number.isFinite(seq)) return;
39562
+ const ack = Math.trunc(seq);
39563
+ const pending = awaitingSeq;
39564
+ if (ack < pending) return;
39565
+ if (ack === pending) {
39566
+ const rtt = Date.now() - sentAtMs;
39567
+ if (Number.isFinite(rtt) && rtt >= 0 && rtt < 6e5) {
39568
+ rttSamples.push(rtt);
39569
+ if (rttSamples.length > BRIDGE_HEARTBEAT_RTT_SAMPLE_MAX) {
39570
+ rttSamples.splice(0, rttSamples.length - BRIDGE_HEARTBEAT_RTT_SAMPLE_MAX);
39571
+ }
39572
+ }
39573
+ }
39574
+ awaitingSeq = null;
39575
+ missed = 0;
39576
+ }
39577
+ };
39578
+ }
39579
+
39458
39580
  // src/connection/create-bridge-connection.ts
39459
39581
  async function createBridgeConnection(options) {
39460
39582
  const { apiUrl, workspaceId, justAuthenticated, onAuthInvalid, persistTokens } = options;
@@ -39494,13 +39616,18 @@ async function createBridgeConnection(options) {
39494
39616
  }
39495
39617
  const e2ee = options.e2eCertificate ? createCliE2eeRuntime(options.e2eCertificate) : void 0;
39496
39618
  const devServerManager = new DevServerManager({ getWs, log: logFn, getBridgeRoot, e2ee });
39497
- const onBridgeIdentified = createOnBridgeIdentified({
39619
+ const bridgeHeartbeat = createBridgeHeartbeatController({ getWs, log: logFn });
39620
+ const baseOnBridgeIdentified = createOnBridgeIdentified({
39498
39621
  devServerManager,
39499
39622
  firehoseServerUrl,
39500
39623
  workspaceId,
39501
39624
  state,
39502
39625
  logFn
39503
39626
  });
39627
+ const onBridgeIdentified = (msg) => {
39628
+ baseOnBridgeIdentified(msg);
39629
+ bridgeHeartbeat.start();
39630
+ };
39504
39631
  const sendLocalSkillsReport = createSendLocalSkillsReport(getWs, logFn);
39505
39632
  const reportAutoDetectedAgents = createReportAutoDetectedAgents(getWs, logFn);
39506
39633
  const messageDeps = {
@@ -39509,6 +39636,9 @@ async function createBridgeConnection(options) {
39509
39636
  acpManager,
39510
39637
  sessionWorktreeManager,
39511
39638
  onBridgeIdentified,
39639
+ onBridgeHeartbeatAck: (seq) => {
39640
+ bridgeHeartbeat.onAck(seq);
39641
+ },
39512
39642
  sendLocalSkillsReport,
39513
39643
  reportAutoDetectedAgents,
39514
39644
  devServerManager,
@@ -39532,13 +39662,15 @@ async function createBridgeConnection(options) {
39532
39662
  persistTokens,
39533
39663
  onAuthInvalid,
39534
39664
  e2ee,
39535
- identifyReportedPaths
39665
+ identifyReportedPaths,
39666
+ bridgeHeartbeat
39536
39667
  });
39537
39668
  connect();
39538
39669
  const stopFileIndexWatcher = startFileIndexWatcher(getBridgeRoot());
39539
39670
  return {
39540
39671
  close: async () => {
39541
39672
  stopFileIndexWatcher();
39673
+ bridgeHeartbeat.stop();
39542
39674
  await closeBridgeConnection(state, acpManager, devServerManager, logFn);
39543
39675
  }
39544
39676
  };