@cabane/companion 0.6.42 → 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;
@@ -9851,12 +9859,14 @@ var CompanionSupervisor = class {
9851
9859
  this.stopped = true;
9852
9860
  if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
9853
9861
  if (this.pollTimer) clearInterval(this.pollTimer);
9862
+ const heartbeat = this.inFlightHeartbeat;
9854
9863
  this.deviceSub?.stop();
9855
9864
  for (const wr of this.workspaces.values()) {
9856
9865
  for (const a of wr.agents.values()) a.cancelDrain();
9857
9866
  if (wr.sub) wr.sub.stop();
9858
9867
  }
9859
9868
  await Promise.all([
9869
+ ...heartbeat ? [waitForBoundedHeartbeat(heartbeat)] : [],
9860
9870
  ...this.deviceSub ? [this.deviceSub.finished] : [],
9861
9871
  ...[...this.workspaces.values()].flatMap((wr) => wr.sub ? [wr.sub.finished] : [])
9862
9872
  ]);
@@ -9869,6 +9879,18 @@ var CompanionSupervisor = class {
9869
9879
  this.reexecFn();
9870
9880
  }
9871
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
+ }
9872
9894
  function defaultReexec() {
9873
9895
  clearRuntimeState();
9874
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;
@@ -9360,12 +9368,14 @@ var CompanionSupervisor = class {
9360
9368
  this.stopped = true;
9361
9369
  if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
9362
9370
  if (this.pollTimer) clearInterval(this.pollTimer);
9371
+ const heartbeat = this.inFlightHeartbeat;
9363
9372
  this.deviceSub?.stop();
9364
9373
  for (const wr of this.workspaces.values()) {
9365
9374
  for (const a of wr.agents.values()) a.cancelDrain();
9366
9375
  if (wr.sub) wr.sub.stop();
9367
9376
  }
9368
9377
  await Promise.all([
9378
+ ...heartbeat ? [waitForBoundedHeartbeat(heartbeat)] : [],
9369
9379
  ...this.deviceSub ? [this.deviceSub.finished] : [],
9370
9380
  ...[...this.workspaces.values()].flatMap((wr) => wr.sub ? [wr.sub.finished] : [])
9371
9381
  ]);
@@ -9378,6 +9388,18 @@ var CompanionSupervisor = class {
9378
9388
  this.reexecFn();
9379
9389
  }
9380
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
+ }
9381
9403
  function defaultReexec() {
9382
9404
  clearRuntimeState();
9383
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.42",
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",