@cabane/companion 0.6.29 → 0.6.30

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
@@ -8807,6 +8807,7 @@ var SseSubscriber = class {
8807
8807
  try {
8808
8808
  await this.connect();
8809
8809
  backoff = 500;
8810
+ if (!this.aborted) await sleep3(backoff);
8810
8811
  } catch (err) {
8811
8812
  if (this.aborted) return;
8812
8813
  if (err instanceof ApiError && (err.status === 401 || err.status === 403)) {
@@ -8832,8 +8833,8 @@ var SseSubscriber = class {
8832
8833
  }
8833
8834
  async connect() {
8834
8835
  const base = this.opts.baseUrl.endsWith("/") ? this.opts.baseUrl.slice(0, -1) : this.opts.baseUrl;
8835
- const query = this.lastEventId ? "" : "?replayPending=1";
8836
- const url = `${base}/api/workspaces/${this.opts.workspaceId}/events${query}`;
8836
+ const query = this.opts.path ? "" : this.lastEventId ? "" : "?replayPending=1";
8837
+ const url = this.opts.path ? `${base}${this.opts.path}` : `${base}/api/workspaces/${this.opts.workspaceId}/events${query}`;
8837
8838
  this.controller = new AbortController();
8838
8839
  const headers = {
8839
8840
  Authorization: `Bearer ${this.opts.token}`,
@@ -8925,6 +8926,7 @@ var CompanionSupervisor = class {
8925
8926
  reexecFn;
8926
8927
  dispatcherFactory;
8927
8928
  deviceApi = null;
8929
+ deviceSub = null;
8928
8930
  heartbeatTimer = null;
8929
8931
  inFlightHeartbeat = null;
8930
8932
  pollTimer = null;
@@ -8978,6 +8980,24 @@ var CompanionSupervisor = class {
8978
8980
  baseUrl: this.config.baseUrl,
8979
8981
  deviceToken: this.config.deviceToken
8980
8982
  });
8983
+ this.deviceSub = new SseSubscriber({
8984
+ baseUrl: this.config.baseUrl,
8985
+ workspaceId: "device-control",
8986
+ path: "/api/companion/events",
8987
+ token: this.config.deviceToken,
8988
+ log: this.log,
8989
+ lastEventId: null,
8990
+ onMessage: async (ev) => {
8991
+ if (ev.event === "assignments_changed") {
8992
+ if (this.refreshing && this.inFlightRefresh) await this.inFlightRefresh;
8993
+ await this.refreshAssignments();
8994
+ }
8995
+ },
8996
+ onAuthFailure: () => {
8997
+ this.log.error("companion: device control stream authentication failed");
8998
+ }
8999
+ });
9000
+ this.deviceSub.start();
8981
9001
  await this.refreshAssignments();
8982
9002
  this.kickHeartbeat();
8983
9003
  this.heartbeatTimer = setInterval(() => this.kickHeartbeat(), HEARTBEAT_INTERVAL_MS);
@@ -8990,7 +9010,10 @@ var CompanionSupervisor = class {
8990
9010
  // at least the loops keeping the event loop alive; the SSE promises let a
8991
9011
  // clean teardown resolve.
8992
9012
  finishedPromises() {
8993
- return [...this.workspaces.values()].flatMap((w) => w.sub ? [w.sub.finished] : []);
9013
+ return [
9014
+ ...this.deviceSub ? [this.deviceSub.finished] : [],
9015
+ ...[...this.workspaces.values()].flatMap((w) => w.sub ? [w.sub.finished] : [])
9016
+ ];
8994
9017
  }
8995
9018
  // ---- device-level loops ----
8996
9019
  kickHeartbeat() {
@@ -9674,13 +9697,15 @@ var CompanionSupervisor = class {
9674
9697
  this.stopped = true;
9675
9698
  if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
9676
9699
  if (this.pollTimer) clearInterval(this.pollTimer);
9700
+ this.deviceSub?.stop();
9677
9701
  for (const wr of this.workspaces.values()) {
9678
9702
  for (const a of wr.agents.values()) a.cancelDrain();
9679
9703
  if (wr.sub) wr.sub.stop();
9680
9704
  }
9681
- await Promise.all(
9682
- [...this.workspaces.values()].flatMap((wr) => wr.sub ? [wr.sub.finished] : [])
9683
- );
9705
+ await Promise.all([
9706
+ ...this.deviceSub ? [this.deviceSub.finished] : [],
9707
+ ...[...this.workspaces.values()].flatMap((wr) => wr.sub ? [wr.sub.finished] : [])
9708
+ ]);
9684
9709
  }
9685
9710
  async drainForRestart(graceMs) {
9686
9711
  this.restartPending = true;
package/dist/runtime.js CHANGED
@@ -7919,6 +7919,7 @@ var SseSubscriber = class {
7919
7919
  try {
7920
7920
  await this.connect();
7921
7921
  backoff = 500;
7922
+ if (!this.aborted) await sleep2(backoff);
7922
7923
  } catch (err) {
7923
7924
  if (this.aborted) return;
7924
7925
  if (err instanceof ApiError && (err.status === 401 || err.status === 403)) {
@@ -7944,8 +7945,8 @@ var SseSubscriber = class {
7944
7945
  }
7945
7946
  async connect() {
7946
7947
  const base = this.opts.baseUrl.endsWith("/") ? this.opts.baseUrl.slice(0, -1) : this.opts.baseUrl;
7947
- const query = this.lastEventId ? "" : "?replayPending=1";
7948
- const url = `${base}/api/workspaces/${this.opts.workspaceId}/events${query}`;
7948
+ const query = this.opts.path ? "" : this.lastEventId ? "" : "?replayPending=1";
7949
+ const url = this.opts.path ? `${base}${this.opts.path}` : `${base}/api/workspaces/${this.opts.workspaceId}/events${query}`;
7949
7950
  this.controller = new AbortController();
7950
7951
  const headers = {
7951
7952
  Authorization: `Bearer ${this.opts.token}`,
@@ -8037,6 +8038,7 @@ var CompanionSupervisor = class {
8037
8038
  reexecFn;
8038
8039
  dispatcherFactory;
8039
8040
  deviceApi = null;
8041
+ deviceSub = null;
8040
8042
  heartbeatTimer = null;
8041
8043
  inFlightHeartbeat = null;
8042
8044
  pollTimer = null;
@@ -8090,6 +8092,24 @@ var CompanionSupervisor = class {
8090
8092
  baseUrl: this.config.baseUrl,
8091
8093
  deviceToken: this.config.deviceToken
8092
8094
  });
8095
+ this.deviceSub = new SseSubscriber({
8096
+ baseUrl: this.config.baseUrl,
8097
+ workspaceId: "device-control",
8098
+ path: "/api/companion/events",
8099
+ token: this.config.deviceToken,
8100
+ log: this.log,
8101
+ lastEventId: null,
8102
+ onMessage: async (ev) => {
8103
+ if (ev.event === "assignments_changed") {
8104
+ if (this.refreshing && this.inFlightRefresh) await this.inFlightRefresh;
8105
+ await this.refreshAssignments();
8106
+ }
8107
+ },
8108
+ onAuthFailure: () => {
8109
+ this.log.error("companion: device control stream authentication failed");
8110
+ }
8111
+ });
8112
+ this.deviceSub.start();
8093
8113
  await this.refreshAssignments();
8094
8114
  this.kickHeartbeat();
8095
8115
  this.heartbeatTimer = setInterval(() => this.kickHeartbeat(), HEARTBEAT_INTERVAL_MS);
@@ -8102,7 +8122,10 @@ var CompanionSupervisor = class {
8102
8122
  // at least the loops keeping the event loop alive; the SSE promises let a
8103
8123
  // clean teardown resolve.
8104
8124
  finishedPromises() {
8105
- return [...this.workspaces.values()].flatMap((w) => w.sub ? [w.sub.finished] : []);
8125
+ return [
8126
+ ...this.deviceSub ? [this.deviceSub.finished] : [],
8127
+ ...[...this.workspaces.values()].flatMap((w) => w.sub ? [w.sub.finished] : [])
8128
+ ];
8106
8129
  }
8107
8130
  // ---- device-level loops ----
8108
8131
  kickHeartbeat() {
@@ -8786,13 +8809,15 @@ var CompanionSupervisor = class {
8786
8809
  this.stopped = true;
8787
8810
  if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
8788
8811
  if (this.pollTimer) clearInterval(this.pollTimer);
8812
+ this.deviceSub?.stop();
8789
8813
  for (const wr of this.workspaces.values()) {
8790
8814
  for (const a of wr.agents.values()) a.cancelDrain();
8791
8815
  if (wr.sub) wr.sub.stop();
8792
8816
  }
8793
- await Promise.all(
8794
- [...this.workspaces.values()].flatMap((wr) => wr.sub ? [wr.sub.finished] : [])
8795
- );
8817
+ await Promise.all([
8818
+ ...this.deviceSub ? [this.deviceSub.finished] : [],
8819
+ ...[...this.workspaces.values()].flatMap((wr) => wr.sub ? [wr.sub.finished] : [])
8820
+ ]);
8796
8821
  }
8797
8822
  async drainForRestart(graceMs) {
8798
8823
  this.restartPending = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabane/companion",
3
- "version": "0.6.29",
3
+ "version": "0.6.30",
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",