@cabane/companion 0.6.43 → 0.6.44

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
@@ -8974,6 +8974,7 @@ var COMPANION_VERSION = pkg.version;
8974
8974
 
8975
8975
  // src/supervisor.ts
8976
8976
  var HEARTBEAT_INTERVAL_MS = 3e4;
8977
+ var SHUTDOWN_HEARTBEAT_WAIT_MS = 1e3;
8977
8978
  function positiveIntEnv(name) {
8978
8979
  const raw = process.env[name];
8979
8980
  if (!raw) return void 0;
@@ -9054,7 +9055,8 @@ var CompanionSupervisor = class {
9054
9055
  { protocolVersion: TURN_PROTOCOL_VERSION, version: COMPANION_VERSION },
9055
9056
  "companion: starting"
9056
9057
  );
9057
- void this.refreshHarnessStatuses();
9058
+ const startupHarnessRefresh = this.refreshHarnessStatuses();
9059
+ this.trackHeartbeat(startupHarnessRefresh);
9058
9060
  if (!this.config.deviceToken) {
9059
9061
  this.log.warn("companion: not paired (no device token) \u2014 run `cabane-companion pair`");
9060
9062
  process.stdout.write(
@@ -9085,7 +9087,11 @@ var CompanionSupervisor = class {
9085
9087
  });
9086
9088
  this.deviceSub.start();
9087
9089
  await this.refreshAssignments();
9088
- this.kickHeartbeat();
9090
+ const firstHeartbeat = startupHarnessRefresh.then(async () => {
9091
+ if (this.stopped) return;
9092
+ await this.sendHeartbeat();
9093
+ });
9094
+ this.trackHeartbeat(firstHeartbeat);
9089
9095
  this.heartbeatTimer = setInterval(() => this.kickHeartbeat(), HEARTBEAT_INTERVAL_MS);
9090
9096
  this.heartbeatTimer.unref?.();
9091
9097
  this.pollTimer = setInterval(() => void this.refreshAssignments(), ASSIGNMENTS_POLL_MS);
@@ -9103,8 +9109,10 @@ var CompanionSupervisor = class {
9103
9109
  }
9104
9110
  // ---- device-level loops ----
9105
9111
  kickHeartbeat() {
9106
- if (this.inFlightHeartbeat) return;
9107
- const pending = this.sendHeartbeat();
9112
+ if (this.stopped || this.inFlightHeartbeat) return;
9113
+ this.trackHeartbeat(this.sendHeartbeat());
9114
+ }
9115
+ trackHeartbeat(pending) {
9108
9116
  this.inFlightHeartbeat = pending;
9109
9117
  void pending.finally(() => {
9110
9118
  if (this.inFlightHeartbeat === pending) this.inFlightHeartbeat = null;
@@ -9858,7 +9866,7 @@ var CompanionSupervisor = class {
9858
9866
  if (wr.sub) wr.sub.stop();
9859
9867
  }
9860
9868
  await Promise.all([
9861
- ...heartbeat ? [heartbeat] : [],
9869
+ ...heartbeat ? [waitForBoundedHeartbeat(heartbeat)] : [],
9862
9870
  ...this.deviceSub ? [this.deviceSub.finished] : [],
9863
9871
  ...[...this.workspaces.values()].flatMap((wr) => wr.sub ? [wr.sub.finished] : [])
9864
9872
  ]);
@@ -9871,6 +9879,18 @@ var CompanionSupervisor = class {
9871
9879
  this.reexecFn();
9872
9880
  }
9873
9881
  };
9882
+ async function waitForBoundedHeartbeat(heartbeat) {
9883
+ let timer = null;
9884
+ const timeout = new Promise((resolve) => {
9885
+ timer = setTimeout(resolve, SHUTDOWN_HEARTBEAT_WAIT_MS);
9886
+ timer.unref?.();
9887
+ });
9888
+ try {
9889
+ await Promise.race([heartbeat, timeout]);
9890
+ } finally {
9891
+ if (timer) clearTimeout(timer);
9892
+ }
9893
+ }
9874
9894
  function defaultReexec() {
9875
9895
  clearRuntimeState();
9876
9896
  void import("child_process").then(({ spawn: spawn6 }) => {
package/dist/runtime.js CHANGED
@@ -8483,6 +8483,7 @@ var COMPANION_VERSION = pkg.version;
8483
8483
 
8484
8484
  // src/supervisor.ts
8485
8485
  var HEARTBEAT_INTERVAL_MS = 3e4;
8486
+ var SHUTDOWN_HEARTBEAT_WAIT_MS = 1e3;
8486
8487
  function positiveIntEnv(name) {
8487
8488
  const raw = process.env[name];
8488
8489
  if (!raw) return void 0;
@@ -8563,7 +8564,8 @@ var CompanionSupervisor = class {
8563
8564
  { protocolVersion: TURN_PROTOCOL_VERSION, version: COMPANION_VERSION },
8564
8565
  "companion: starting"
8565
8566
  );
8566
- void this.refreshHarnessStatuses();
8567
+ const startupHarnessRefresh = this.refreshHarnessStatuses();
8568
+ this.trackHeartbeat(startupHarnessRefresh);
8567
8569
  if (!this.config.deviceToken) {
8568
8570
  this.log.warn("companion: not paired (no device token) \u2014 run `cabane-companion pair`");
8569
8571
  process.stdout.write(
@@ -8594,7 +8596,11 @@ var CompanionSupervisor = class {
8594
8596
  });
8595
8597
  this.deviceSub.start();
8596
8598
  await this.refreshAssignments();
8597
- this.kickHeartbeat();
8599
+ const firstHeartbeat = startupHarnessRefresh.then(async () => {
8600
+ if (this.stopped) return;
8601
+ await this.sendHeartbeat();
8602
+ });
8603
+ this.trackHeartbeat(firstHeartbeat);
8598
8604
  this.heartbeatTimer = setInterval(() => this.kickHeartbeat(), HEARTBEAT_INTERVAL_MS);
8599
8605
  this.heartbeatTimer.unref?.();
8600
8606
  this.pollTimer = setInterval(() => void this.refreshAssignments(), ASSIGNMENTS_POLL_MS);
@@ -8612,8 +8618,10 @@ var CompanionSupervisor = class {
8612
8618
  }
8613
8619
  // ---- device-level loops ----
8614
8620
  kickHeartbeat() {
8615
- if (this.inFlightHeartbeat) return;
8616
- const pending = this.sendHeartbeat();
8621
+ if (this.stopped || this.inFlightHeartbeat) return;
8622
+ this.trackHeartbeat(this.sendHeartbeat());
8623
+ }
8624
+ trackHeartbeat(pending) {
8617
8625
  this.inFlightHeartbeat = pending;
8618
8626
  void pending.finally(() => {
8619
8627
  if (this.inFlightHeartbeat === pending) this.inFlightHeartbeat = null;
@@ -9367,7 +9375,7 @@ var CompanionSupervisor = class {
9367
9375
  if (wr.sub) wr.sub.stop();
9368
9376
  }
9369
9377
  await Promise.all([
9370
- ...heartbeat ? [heartbeat] : [],
9378
+ ...heartbeat ? [waitForBoundedHeartbeat(heartbeat)] : [],
9371
9379
  ...this.deviceSub ? [this.deviceSub.finished] : [],
9372
9380
  ...[...this.workspaces.values()].flatMap((wr) => wr.sub ? [wr.sub.finished] : [])
9373
9381
  ]);
@@ -9380,6 +9388,18 @@ var CompanionSupervisor = class {
9380
9388
  this.reexecFn();
9381
9389
  }
9382
9390
  };
9391
+ async function waitForBoundedHeartbeat(heartbeat) {
9392
+ let timer = null;
9393
+ const timeout = new Promise((resolve) => {
9394
+ timer = setTimeout(resolve, SHUTDOWN_HEARTBEAT_WAIT_MS);
9395
+ timer.unref?.();
9396
+ });
9397
+ try {
9398
+ await Promise.race([heartbeat, timeout]);
9399
+ } finally {
9400
+ if (timer) clearTimeout(timer);
9401
+ }
9402
+ }
9383
9403
  function defaultReexec() {
9384
9404
  clearRuntimeState();
9385
9405
  void import("child_process").then(({ spawn: spawn5 }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabane/companion",
3
- "version": "0.6.43",
3
+ "version": "0.6.44",
4
4
  "type": "module",
5
5
  "description": "The Cabane Companion (headless): connect a coding agent on your machine to your Cabane workspace as a responder — drive work against your own codebase, files, and MCP servers without putting any of it in Cabane.",
6
6
  "license": "UNLICENSED",