@cabane/companion 0.6.39 → 0.6.40
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 +3 -88
- package/dist/runtime.js +3 -61
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2676,9 +2676,6 @@ var DeviceApi = class {
|
|
|
2676
2676
|
getAssignments() {
|
|
2677
2677
|
return this.request("GET", "/api/companion/assignments");
|
|
2678
2678
|
}
|
|
2679
|
-
beginDrain() {
|
|
2680
|
-
return this.request("POST", "/api/companion/drain", {});
|
|
2681
|
-
}
|
|
2682
2679
|
// CT1146: report that a dispatch addressed to THIS device can't be run, because
|
|
2683
2680
|
// the agent isn't one this device runs (and still wasn't after a forced
|
|
2684
2681
|
// assignments refresh). The server clears the Working flag, retires the dispatch
|
|
@@ -9018,8 +9015,6 @@ var CompanionSupervisor = class {
|
|
|
9018
9015
|
pollTimer = null;
|
|
9019
9016
|
refreshing = false;
|
|
9020
9017
|
stopped = false;
|
|
9021
|
-
draining = false;
|
|
9022
|
-
restartPending = false;
|
|
9023
9018
|
// CT484: latch so the companion/server version-skew warning is logged once, not
|
|
9024
9019
|
// on every 30s heartbeat.
|
|
9025
9020
|
versionSkewWarned = false;
|
|
@@ -9103,7 +9098,7 @@ var CompanionSupervisor = class {
|
|
|
9103
9098
|
}
|
|
9104
9099
|
// ---- device-level loops ----
|
|
9105
9100
|
kickHeartbeat() {
|
|
9106
|
-
if (this.
|
|
9101
|
+
if (this.inFlightHeartbeat) return;
|
|
9107
9102
|
const pending = this.sendHeartbeat();
|
|
9108
9103
|
this.inFlightHeartbeat = pending;
|
|
9109
9104
|
void pending.finally(() => {
|
|
@@ -9151,8 +9146,7 @@ var CompanionSupervisor = class {
|
|
|
9151
9146
|
// actually landed (`harnessSignals` non-null) — an absent field means "we
|
|
9152
9147
|
// didn't look this beat" and leaves the server's stored suggestion alone,
|
|
9153
9148
|
// the same fail-soft contract `models` keeps.
|
|
9154
|
-
...this.harnessSignals ? { detectedRuntimes: detectedRuntimesFor(deriveHarnessSnapshot(this.harnessSignals)) } : {}
|
|
9155
|
-
...this.restartPending ? { restarting: true } : {}
|
|
9149
|
+
...this.harnessSignals ? { detectedRuntimes: detectedRuntimesFor(deriveHarnessSnapshot(this.harnessSignals)) } : {}
|
|
9156
9150
|
});
|
|
9157
9151
|
this.hub.setDevice({ deviceId: res.deviceId });
|
|
9158
9152
|
this.deviceId = res.deviceId;
|
|
@@ -9508,7 +9502,6 @@ var CompanionSupervisor = class {
|
|
|
9508
9502
|
if (ev.id) wr.cursor.settle(ev.id);
|
|
9509
9503
|
return;
|
|
9510
9504
|
}
|
|
9511
|
-
if (this.draining) return;
|
|
9512
9505
|
const payload = {
|
|
9513
9506
|
type: "device:dispatch_requested",
|
|
9514
9507
|
...wire.payload
|
|
@@ -9870,50 +9863,6 @@ var CompanionSupervisor = class {
|
|
|
9870
9863
|
...[...this.workspaces.values()].flatMap((wr) => wr.sub ? [wr.sub.finished] : [])
|
|
9871
9864
|
]);
|
|
9872
9865
|
}
|
|
9873
|
-
async drainForRestart(graceMs) {
|
|
9874
|
-
this.restartPending = true;
|
|
9875
|
-
if (this.inFlightHeartbeat) await this.inFlightHeartbeat;
|
|
9876
|
-
if (!this.deviceApi) throw new Error("cannot establish deploy drain before pairing");
|
|
9877
|
-
await this.sendHeartbeat();
|
|
9878
|
-
const drainStartedAt = Date.now();
|
|
9879
|
-
const deadline = Date.now() + Math.max(0, graceMs);
|
|
9880
|
-
let timedOut = false;
|
|
9881
|
-
while (true) {
|
|
9882
|
-
const turns = [...this.workspaces.values()].flatMap((wr) => [...wr.chains.values()]);
|
|
9883
|
-
if (turns.length === 0) break;
|
|
9884
|
-
const remainingMs = deadline - Date.now();
|
|
9885
|
-
if (remainingMs <= 0) {
|
|
9886
|
-
timedOut = true;
|
|
9887
|
-
break;
|
|
9888
|
-
}
|
|
9889
|
-
let timer;
|
|
9890
|
-
await Promise.race([
|
|
9891
|
-
Promise.allSettled(turns),
|
|
9892
|
-
new Promise((resolve) => {
|
|
9893
|
-
timer = setTimeout(resolve, remainingMs);
|
|
9894
|
-
timer.unref?.();
|
|
9895
|
-
})
|
|
9896
|
-
]);
|
|
9897
|
-
if (timer) clearTimeout(timer);
|
|
9898
|
-
}
|
|
9899
|
-
this.log.info(
|
|
9900
|
-
{ waitedMs: Date.now() - drainStartedAt, timedOut },
|
|
9901
|
-
timedOut ? "companion: deploy drain grace elapsed; fencing admission for restart" : "companion: deploy drain reached a quiet point; fencing admission for restart"
|
|
9902
|
-
);
|
|
9903
|
-
this.draining = true;
|
|
9904
|
-
if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
|
|
9905
|
-
if (this.pollTimer) clearInterval(this.pollTimer);
|
|
9906
|
-
if (this.inFlightHeartbeat) await this.inFlightHeartbeat;
|
|
9907
|
-
await this.deviceApi.beginDrain();
|
|
9908
|
-
for (const wr of this.workspaces.values()) wr.sub?.stop();
|
|
9909
|
-
await Promise.allSettled(
|
|
9910
|
-
[...this.workspaces.values()].flatMap(
|
|
9911
|
-
(wr) => [...wr.agents.values()].map((agent) => agent.api.drainOutbox())
|
|
9912
|
-
)
|
|
9913
|
-
);
|
|
9914
|
-
await this.shutdown();
|
|
9915
|
-
return { drained: !timedOut };
|
|
9916
|
-
}
|
|
9917
9866
|
async requestStop() {
|
|
9918
9867
|
await this.shutdown();
|
|
9919
9868
|
this.exitFn(0);
|
|
@@ -10145,14 +10094,7 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
10145
10094
|
stop: stop2,
|
|
10146
10095
|
heartbeatNow: () => supervisor.heartbeatNow(),
|
|
10147
10096
|
harnesses: () => hub.statusJson().harnesses ?? [],
|
|
10148
|
-
connectHarness
|
|
10149
|
-
drainForRestart: async (graceMs) => {
|
|
10150
|
-
clearRuntimeStateIfOurs(instanceId);
|
|
10151
|
-
const result = await supervisor.drainForRestart(graceMs);
|
|
10152
|
-
await closeSurfaces(control, dashboard);
|
|
10153
|
-
stopped = true;
|
|
10154
|
-
return result;
|
|
10155
|
-
}
|
|
10097
|
+
connectHarness
|
|
10156
10098
|
}
|
|
10157
10099
|
};
|
|
10158
10100
|
}
|
|
@@ -10281,8 +10223,6 @@ function defaultSpawnDetached(args) {
|
|
|
10281
10223
|
|
|
10282
10224
|
// src/commands/start.ts
|
|
10283
10225
|
var FORCE_EXIT_MS = 4e3;
|
|
10284
|
-
var DEPLOY_REEXEC_EXIT = 75;
|
|
10285
|
-
var DEPLOY_GRACE_MS = 6.5 * 60 * 60 * 1e3;
|
|
10286
10226
|
var MAX_CODES = 3;
|
|
10287
10227
|
async function start(opts = {}) {
|
|
10288
10228
|
const interactive = isInteractive();
|
|
@@ -10521,31 +10461,6 @@ companion: received ${signal}, shutting down\u2026
|
|
|
10521
10461
|
};
|
|
10522
10462
|
process.on("SIGINT", () => void shutdown("SIGINT"));
|
|
10523
10463
|
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
|
10524
|
-
process.on("SIGUSR2", () => {
|
|
10525
|
-
if (shuttingDown) return;
|
|
10526
|
-
shuttingDown = true;
|
|
10527
|
-
const configuredGrace = Number.parseInt(
|
|
10528
|
-
process.env.CABANE_COMPANION_DEPLOY_GRACE_MS ?? String(DEPLOY_GRACE_MS),
|
|
10529
|
-
10
|
|
10530
|
-
);
|
|
10531
|
-
const graceMs = Number.isFinite(configuredGrace) ? configuredGrace : DEPLOY_GRACE_MS;
|
|
10532
|
-
process.stdout.write(`
|
|
10533
|
-
companion: deploy drain requested (${graceMs}ms grace)\u2026
|
|
10534
|
-
`);
|
|
10535
|
-
void runtime.drainForRestart(graceMs).then(({ drained }) => {
|
|
10536
|
-
process.stdout.write(
|
|
10537
|
-
drained ? "companion: deploy drain complete; re-execing onto the new dist.\n" : "companion: deploy grace expired; re-execing \u2014 unfinished turns resume after restart.\n"
|
|
10538
|
-
);
|
|
10539
|
-
resolve();
|
|
10540
|
-
process.exit(DEPLOY_REEXEC_EXIT);
|
|
10541
|
-
}).catch((err) => {
|
|
10542
|
-
process.stderr.write(
|
|
10543
|
-
`companion: deploy drain failed: ${err instanceof Error ? err.message : String(err)}
|
|
10544
|
-
`
|
|
10545
|
-
);
|
|
10546
|
-
process.exit(DEPLOY_REEXEC_EXIT);
|
|
10547
|
-
});
|
|
10548
|
-
});
|
|
10549
10464
|
});
|
|
10550
10465
|
}
|
|
10551
10466
|
|
package/dist/runtime.js
CHANGED
|
@@ -2104,9 +2104,6 @@ var DeviceApi = class {
|
|
|
2104
2104
|
getAssignments() {
|
|
2105
2105
|
return this.request("GET", "/api/companion/assignments");
|
|
2106
2106
|
}
|
|
2107
|
-
beginDrain() {
|
|
2108
|
-
return this.request("POST", "/api/companion/drain", {});
|
|
2109
|
-
}
|
|
2110
2107
|
// CT1146: report that a dispatch addressed to THIS device can't be run, because
|
|
2111
2108
|
// the agent isn't one this device runs (and still wasn't after a forced
|
|
2112
2109
|
// assignments refresh). The server clears the Working flag, retires the dispatch
|
|
@@ -8525,8 +8522,6 @@ var CompanionSupervisor = class {
|
|
|
8525
8522
|
pollTimer = null;
|
|
8526
8523
|
refreshing = false;
|
|
8527
8524
|
stopped = false;
|
|
8528
|
-
draining = false;
|
|
8529
|
-
restartPending = false;
|
|
8530
8525
|
// CT484: latch so the companion/server version-skew warning is logged once, not
|
|
8531
8526
|
// on every 30s heartbeat.
|
|
8532
8527
|
versionSkewWarned = false;
|
|
@@ -8610,7 +8605,7 @@ var CompanionSupervisor = class {
|
|
|
8610
8605
|
}
|
|
8611
8606
|
// ---- device-level loops ----
|
|
8612
8607
|
kickHeartbeat() {
|
|
8613
|
-
if (this.
|
|
8608
|
+
if (this.inFlightHeartbeat) return;
|
|
8614
8609
|
const pending = this.sendHeartbeat();
|
|
8615
8610
|
this.inFlightHeartbeat = pending;
|
|
8616
8611
|
void pending.finally(() => {
|
|
@@ -8658,8 +8653,7 @@ var CompanionSupervisor = class {
|
|
|
8658
8653
|
// actually landed (`harnessSignals` non-null) — an absent field means "we
|
|
8659
8654
|
// didn't look this beat" and leaves the server's stored suggestion alone,
|
|
8660
8655
|
// the same fail-soft contract `models` keeps.
|
|
8661
|
-
...this.harnessSignals ? { detectedRuntimes: detectedRuntimesFor(deriveHarnessSnapshot(this.harnessSignals)) } : {}
|
|
8662
|
-
...this.restartPending ? { restarting: true } : {}
|
|
8656
|
+
...this.harnessSignals ? { detectedRuntimes: detectedRuntimesFor(deriveHarnessSnapshot(this.harnessSignals)) } : {}
|
|
8663
8657
|
});
|
|
8664
8658
|
this.hub.setDevice({ deviceId: res.deviceId });
|
|
8665
8659
|
this.deviceId = res.deviceId;
|
|
@@ -9015,7 +9009,6 @@ var CompanionSupervisor = class {
|
|
|
9015
9009
|
if (ev.id) wr.cursor.settle(ev.id);
|
|
9016
9010
|
return;
|
|
9017
9011
|
}
|
|
9018
|
-
if (this.draining) return;
|
|
9019
9012
|
const payload = {
|
|
9020
9013
|
type: "device:dispatch_requested",
|
|
9021
9014
|
...wire.payload
|
|
@@ -9377,50 +9370,6 @@ var CompanionSupervisor = class {
|
|
|
9377
9370
|
...[...this.workspaces.values()].flatMap((wr) => wr.sub ? [wr.sub.finished] : [])
|
|
9378
9371
|
]);
|
|
9379
9372
|
}
|
|
9380
|
-
async drainForRestart(graceMs) {
|
|
9381
|
-
this.restartPending = true;
|
|
9382
|
-
if (this.inFlightHeartbeat) await this.inFlightHeartbeat;
|
|
9383
|
-
if (!this.deviceApi) throw new Error("cannot establish deploy drain before pairing");
|
|
9384
|
-
await this.sendHeartbeat();
|
|
9385
|
-
const drainStartedAt = Date.now();
|
|
9386
|
-
const deadline = Date.now() + Math.max(0, graceMs);
|
|
9387
|
-
let timedOut = false;
|
|
9388
|
-
while (true) {
|
|
9389
|
-
const turns = [...this.workspaces.values()].flatMap((wr) => [...wr.chains.values()]);
|
|
9390
|
-
if (turns.length === 0) break;
|
|
9391
|
-
const remainingMs = deadline - Date.now();
|
|
9392
|
-
if (remainingMs <= 0) {
|
|
9393
|
-
timedOut = true;
|
|
9394
|
-
break;
|
|
9395
|
-
}
|
|
9396
|
-
let timer;
|
|
9397
|
-
await Promise.race([
|
|
9398
|
-
Promise.allSettled(turns),
|
|
9399
|
-
new Promise((resolve) => {
|
|
9400
|
-
timer = setTimeout(resolve, remainingMs);
|
|
9401
|
-
timer.unref?.();
|
|
9402
|
-
})
|
|
9403
|
-
]);
|
|
9404
|
-
if (timer) clearTimeout(timer);
|
|
9405
|
-
}
|
|
9406
|
-
this.log.info(
|
|
9407
|
-
{ waitedMs: Date.now() - drainStartedAt, timedOut },
|
|
9408
|
-
timedOut ? "companion: deploy drain grace elapsed; fencing admission for restart" : "companion: deploy drain reached a quiet point; fencing admission for restart"
|
|
9409
|
-
);
|
|
9410
|
-
this.draining = true;
|
|
9411
|
-
if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
|
|
9412
|
-
if (this.pollTimer) clearInterval(this.pollTimer);
|
|
9413
|
-
if (this.inFlightHeartbeat) await this.inFlightHeartbeat;
|
|
9414
|
-
await this.deviceApi.beginDrain();
|
|
9415
|
-
for (const wr of this.workspaces.values()) wr.sub?.stop();
|
|
9416
|
-
await Promise.allSettled(
|
|
9417
|
-
[...this.workspaces.values()].flatMap(
|
|
9418
|
-
(wr) => [...wr.agents.values()].map((agent) => agent.api.drainOutbox())
|
|
9419
|
-
)
|
|
9420
|
-
);
|
|
9421
|
-
await this.shutdown();
|
|
9422
|
-
return { drained: !timedOut };
|
|
9423
|
-
}
|
|
9424
9373
|
async requestStop() {
|
|
9425
9374
|
await this.shutdown();
|
|
9426
9375
|
this.exitFn(0);
|
|
@@ -9652,14 +9601,7 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
9652
9601
|
stop,
|
|
9653
9602
|
heartbeatNow: () => supervisor.heartbeatNow(),
|
|
9654
9603
|
harnesses: () => hub.statusJson().harnesses ?? [],
|
|
9655
|
-
connectHarness
|
|
9656
|
-
drainForRestart: async (graceMs) => {
|
|
9657
|
-
clearRuntimeStateIfOurs(instanceId);
|
|
9658
|
-
const result = await supervisor.drainForRestart(graceMs);
|
|
9659
|
-
await closeSurfaces(control, dashboard);
|
|
9660
|
-
stopped = true;
|
|
9661
|
-
return result;
|
|
9662
|
-
}
|
|
9604
|
+
connectHarness
|
|
9663
9605
|
}
|
|
9664
9606
|
};
|
|
9665
9607
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cabane/companion",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.40",
|
|
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",
|