@cabane/companion 0.6.181 → 0.6.183
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/README.md +28 -18
- package/dist/cli.js +30 -10
- package/dist/runtime.js +30 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,16 +44,26 @@ The script brings its own Node (checksum-checked, into `~/.cabane`) and needs no
|
|
|
44
44
|
npm i -g @cabane/companion && cabane setup CODE
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
Either way `cabane setup CODE` pairs the device to your account, joins it to that workspace,
|
|
47
|
+
Either way `cabane setup CODE` pairs the device to your account, joins it to that workspace, starts the Companion in the background, waits for its first check-in, and ends with the device's state in Cabane's words. No confirm, no name screen, no browser on the device. The same line on a device that is already paired joins it to the workspace and mints nothing.
|
|
48
48
|
|
|
49
49
|
```
|
|
50
|
-
✓
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
✓ Paired as "maya-mbp" with Acme.
|
|
51
|
+
Cabane is running in the background. This device is ready — checked in 2s ago.
|
|
52
|
+
|
|
53
|
+
Harnesses on this device
|
|
54
|
+
✓ Claude Code connected — signed in (2.1.289)
|
|
55
|
+
Codex needs sign-in — run codex login
|
|
56
|
+
OpenCode off — not running
|
|
57
|
+
|
|
58
|
+
Status cabane status Stop cabane stop Log cabane logs
|
|
59
|
+
Start at login and keep Cabane updating itself: cabane service install
|
|
60
|
+
|
|
61
|
+
In Cabane: https://app.cabane.ai/acme/settings/devices
|
|
54
62
|
```
|
|
55
63
|
|
|
56
|
-
With nothing signed in, the device still pairs and the Companion still runs; the rows say which harness needs sign-in, and the
|
|
64
|
+
The device is online the moment the Companion checks in, before any harness is checked; Cabane shows it as **Checking harnesses…** until the first detection lands, then **Ready** or **Needs attention**. With nothing signed in, the device still pairs and the Companion still runs; the rows say which harness needs sign-in. Sign in, and the next check finds it: no restart. A harness that was signed in earlier in this run stays offered through a lapsed login, so a turn reaches the device, fails with the sign-in notice, and the first reply after you sign in again runs with no wait.
|
|
65
|
+
|
|
66
|
+
Setup never installs the service. Run `cabane service install` when you want the Companion to start at login and keep itself updated; the closing block names it.
|
|
57
67
|
|
|
58
68
|
A fresh device runs nothing yet. In a workspace, open an agent's page and assign it to the device; the Companion picks the assignment up on its next poll.
|
|
59
69
|
|
|
@@ -61,23 +71,23 @@ If `cabane` isn't found afterwards, open a new terminal, or run the `source` lin
|
|
|
61
71
|
|
|
62
72
|
## Commands
|
|
63
73
|
|
|
64
|
-
| Command | What it does
|
|
65
|
-
| ------------------------------------ |
|
|
66
|
-
| `setup [CODE]` | With a code: pair or join, detect harnesses, start. Without: a re-runnable checklist that reports harnesses and starts what isn't running.
|
|
67
|
-
| `start [--foreground]` | Run in the background. Never asks. Unpaired, it names `setup`. `--foreground` stays attached, for a supervisor of your own.
|
|
68
|
-
| `stop` | Stop a running Companion cleanly. Ends any turn in progress.
|
|
69
|
-
| `status` | Version and install, the paired device by its Cabane name,
|
|
70
|
-
| `update [--auto on\|off]` | Install the version Cabane names now; with `--auto`, set whether the Companion keeps itself up to date.
|
|
71
|
-
| `service install\|uninstall\|status` | Run as a systemd user service or a LaunchAgent that starts at boot and restarts if it stops; remove it; show its state.
|
|
72
|
-
| `logs [-n N] [-f]` | Recent log lines, or follow.
|
|
73
|
-
| `transcript [file] [--last] [-f]` | Read back a full agent turn, tool calls and results included.
|
|
74
|
-
| `logout [--purge]` | Remove the device token and cached agent credentials. Doesn't remove the device from Cabane.
|
|
74
|
+
| Command | What it does |
|
|
75
|
+
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
76
|
+
| `setup [CODE]` | With a code: pair or join, detect harnesses, start. Without: a re-runnable checklist that reports harnesses and starts what isn't running. |
|
|
77
|
+
| `start [--foreground]` | Run in the background. Never asks. Unpaired, it names `setup`. `--foreground` stays attached, for a supervisor of your own. |
|
|
78
|
+
| `stop` | Stop a running Companion cleanly. Ends any turn in progress. |
|
|
79
|
+
| `status` | Version and install, the paired device by its Cabane name, its state in Cabane's words and when it last checked in, each harness's state, the sandbox posture, declared secret names, and local overrides. |
|
|
80
|
+
| `update [--auto on\|off]` | Install the version Cabane names now; with `--auto`, set whether the Companion keeps itself up to date. |
|
|
81
|
+
| `service install\|uninstall\|status` | Run as a systemd user service or a LaunchAgent that starts at boot and restarts if it stops; remove it; show its state. |
|
|
82
|
+
| `logs [-n N] [-f]` | Recent log lines, or follow. |
|
|
83
|
+
| `transcript [file] [--last] [-f]` | Read back a full agent turn, tool calls and results included. |
|
|
84
|
+
| `logout [--purge]` | Remove the device token and cached agent credentials. Doesn't remove the device from Cabane. |
|
|
75
85
|
|
|
76
86
|
Anything else about a device is done in Cabane, on the device's page.
|
|
77
87
|
|
|
78
88
|
## Keeping it running
|
|
79
89
|
|
|
80
|
-
|
|
90
|
+
Without the service, a reboot or a crash leaves the Companion stopped until the next `cabane start`, and the device shows **Offline** in Cabane. Under the service it restarts if it stops, comes back after a reboot, and follows the version Cabane names: it installs the new version under `~/.cabane/versions`, checks it starts, and swaps between turns. A version that can't start is rolled back by the boot guard. `cabane update --auto off` stops updates; without the service, `cabane update` installs and the next `cabane start` runs it.
|
|
81
91
|
|
|
82
92
|
Other supervisors: run `cabane start --foreground` with `CABANE_COMPANION_SUPERVISED=1` set, using the absolute paths `~/.cabane/node/bin/node` and `~/.cabane/current/dist/bin.js`, which don't move on an update.
|
|
83
93
|
|
package/dist/cli.js
CHANGED
|
@@ -12554,11 +12554,24 @@ function parseHarnessRuntime(raw) {
|
|
|
12554
12554
|
if (key === "opencode") return "opencode";
|
|
12555
12555
|
return null;
|
|
12556
12556
|
}
|
|
12557
|
-
function
|
|
12558
|
-
return
|
|
12557
|
+
function stillRunnable(agent) {
|
|
12558
|
+
return agent.state === "usable" || agent.state === "signed_out" || agent.state === "problem" && agent.check.status === "check_failed";
|
|
12559
|
+
}
|
|
12560
|
+
function rememberUsed(used, detection) {
|
|
12561
|
+
const next = /* @__PURE__ */ new Set();
|
|
12562
|
+
for (const runtime of ["claude-code", "codex"]) {
|
|
12563
|
+
const agent = detection[runtime];
|
|
12564
|
+
if (agent.state === "usable" || used.has(runtime) && stillRunnable(agent)) next.add(runtime);
|
|
12565
|
+
}
|
|
12566
|
+
return next;
|
|
12567
|
+
}
|
|
12568
|
+
function advertisedRuntimes(detection, used) {
|
|
12569
|
+
return HARNESS_RUNTIMES.filter(
|
|
12570
|
+
(runtime) => detection[runtime].state === "usable" || used.has(runtime) && stillRunnable(detection[runtime])
|
|
12571
|
+
);
|
|
12559
12572
|
}
|
|
12560
12573
|
function deriveHarnessSnapshot(detection) {
|
|
12561
|
-
const advertised = new Set(
|
|
12574
|
+
const advertised = new Set(usableRuntimes(detection));
|
|
12562
12575
|
const harnesses = HARNESS_RUNTIMES.map(
|
|
12563
12576
|
(runtime) => derive(runtime, detection[runtime], advertised.has(runtime))
|
|
12564
12577
|
);
|
|
@@ -16163,6 +16176,9 @@ var CompanionSupervisor = class {
|
|
|
16163
16176
|
// control socket, so none can disagree with what is advertised. Null until
|
|
16164
16177
|
// the first detection; a heartbeat spawns nothing and reads this.
|
|
16165
16178
|
detection = null;
|
|
16179
|
+
// The bundled harnesses a detection in this run found usable, which stay offered
|
|
16180
|
+
// through a lapsed sign-in (`rememberUsed`). Empty until the first detection.
|
|
16181
|
+
used = /* @__PURE__ */ new Set();
|
|
16166
16182
|
detecting = null;
|
|
16167
16183
|
// Liveness may be acknowledged before discovery. Execution waits on this
|
|
16168
16184
|
// first look only, never on a periodic refresh of an established inventory.
|
|
@@ -16407,8 +16423,7 @@ var CompanionSupervisor = class {
|
|
|
16407
16423
|
async sendHeartbeat(first = false) {
|
|
16408
16424
|
if (!this.deviceApi) return;
|
|
16409
16425
|
try {
|
|
16410
|
-
if (!first && this.detection &&
|
|
16411
|
-
await this.detect();
|
|
16426
|
+
if (!first && this.detection && this.advertised().length === 0) await this.detect();
|
|
16412
16427
|
const store = loadSecretStoreTolerant(
|
|
16413
16428
|
(m) => this.log.warn({ err: m }, "Could not read the secrets file. Check ~/.cabane/secrets.json.")
|
|
16414
16429
|
);
|
|
@@ -16742,11 +16757,15 @@ var CompanionSupervisor = class {
|
|
|
16742
16757
|
bundledResolution(runtime) {
|
|
16743
16758
|
return this.resolveBundled(runtime);
|
|
16744
16759
|
}
|
|
16745
|
-
// Does this device OFFER a runtime —
|
|
16746
|
-
// ONE signal the heartbeat manifest, the adapter registry and the
|
|
16747
|
-
// read, so what the device advertises and what it can select can't
|
|
16760
|
+
// Does this device OFFER a runtime — usable now, or used in this run and still
|
|
16761
|
+
// runnable? The ONE signal the heartbeat manifest, the adapter registry and the
|
|
16762
|
+
// dispatcher read, so what the device advertises and what it can select can't
|
|
16763
|
+
// disagree.
|
|
16748
16764
|
offered(runtime) {
|
|
16749
|
-
return this.
|
|
16765
|
+
return this.advertised().includes(runtime);
|
|
16766
|
+
}
|
|
16767
|
+
advertised() {
|
|
16768
|
+
return this.detection ? advertisedRuntimes(this.detection, this.used) : [];
|
|
16750
16769
|
}
|
|
16751
16770
|
// The address an OpenCode turn runs against: the serve detection found, else
|
|
16752
16771
|
// the configured one, for the model list a beat still asks for.
|
|
@@ -17405,6 +17424,7 @@ var CompanionSupervisor = class {
|
|
|
17405
17424
|
...this.probeOpencode ? { probeOpencode: this.probeOpencode } : {}
|
|
17406
17425
|
});
|
|
17407
17426
|
this.detection = detection;
|
|
17427
|
+
this.used = rememberUsed(this.used, detection);
|
|
17408
17428
|
this.logBundledTransitions();
|
|
17409
17429
|
this.harnessVersions = {
|
|
17410
17430
|
claudeCode: detectedVersion(detection["claude-code"]),
|
|
@@ -17459,7 +17479,7 @@ var CompanionSupervisor = class {
|
|
|
17459
17479
|
const health = this.connectorHealth.reports();
|
|
17460
17480
|
if (health.length === 0 || !this.detection) return health;
|
|
17461
17481
|
const named = new Set(health.map((r) => r.runtime));
|
|
17462
|
-
const bare =
|
|
17482
|
+
const bare = this.advertised().filter((runtime) => !named.has(runtime)).map((runtime) => ({ runtime }));
|
|
17463
17483
|
return [...health, ...bare];
|
|
17464
17484
|
}
|
|
17465
17485
|
// Beat NOW and wait for it to land. `start` calls this before asking the
|
package/dist/runtime.js
CHANGED
|
@@ -13042,11 +13042,24 @@ function withTimeout(promise, fallback, timeoutMs) {
|
|
|
13042
13042
|
}
|
|
13043
13043
|
|
|
13044
13044
|
// src/harness-status.ts
|
|
13045
|
-
function
|
|
13046
|
-
return
|
|
13045
|
+
function stillRunnable(agent) {
|
|
13046
|
+
return agent.state === "usable" || agent.state === "signed_out" || agent.state === "problem" && agent.check.status === "check_failed";
|
|
13047
|
+
}
|
|
13048
|
+
function rememberUsed(used, detection) {
|
|
13049
|
+
const next = /* @__PURE__ */ new Set();
|
|
13050
|
+
for (const runtime of ["claude-code", "codex"]) {
|
|
13051
|
+
const agent = detection[runtime];
|
|
13052
|
+
if (agent.state === "usable" || used.has(runtime) && stillRunnable(agent)) next.add(runtime);
|
|
13053
|
+
}
|
|
13054
|
+
return next;
|
|
13055
|
+
}
|
|
13056
|
+
function advertisedRuntimes(detection, used) {
|
|
13057
|
+
return HARNESS_RUNTIMES.filter(
|
|
13058
|
+
(runtime) => detection[runtime].state === "usable" || used.has(runtime) && stillRunnable(detection[runtime])
|
|
13059
|
+
);
|
|
13047
13060
|
}
|
|
13048
13061
|
function deriveHarnessSnapshot(detection) {
|
|
13049
|
-
const advertised = new Set(
|
|
13062
|
+
const advertised = new Set(usableRuntimes(detection));
|
|
13050
13063
|
const harnesses = HARNESS_RUNTIMES.map(
|
|
13051
13064
|
(runtime) => derive(runtime, detection[runtime], advertised.has(runtime))
|
|
13052
13065
|
);
|
|
@@ -14095,6 +14108,9 @@ var CompanionSupervisor = class {
|
|
|
14095
14108
|
// control socket, so none can disagree with what is advertised. Null until
|
|
14096
14109
|
// the first detection; a heartbeat spawns nothing and reads this.
|
|
14097
14110
|
detection = null;
|
|
14111
|
+
// The bundled harnesses a detection in this run found usable, which stay offered
|
|
14112
|
+
// through a lapsed sign-in (`rememberUsed`). Empty until the first detection.
|
|
14113
|
+
used = /* @__PURE__ */ new Set();
|
|
14098
14114
|
detecting = null;
|
|
14099
14115
|
// Liveness may be acknowledged before discovery. Execution waits on this
|
|
14100
14116
|
// first look only, never on a periodic refresh of an established inventory.
|
|
@@ -14339,8 +14355,7 @@ var CompanionSupervisor = class {
|
|
|
14339
14355
|
async sendHeartbeat(first = false) {
|
|
14340
14356
|
if (!this.deviceApi) return;
|
|
14341
14357
|
try {
|
|
14342
|
-
if (!first && this.detection &&
|
|
14343
|
-
await this.detect();
|
|
14358
|
+
if (!first && this.detection && this.advertised().length === 0) await this.detect();
|
|
14344
14359
|
const store = loadSecretStoreTolerant(
|
|
14345
14360
|
(m) => this.log.warn({ err: m }, "Could not read the secrets file. Check ~/.cabane/secrets.json.")
|
|
14346
14361
|
);
|
|
@@ -14674,11 +14689,15 @@ var CompanionSupervisor = class {
|
|
|
14674
14689
|
bundledResolution(runtime) {
|
|
14675
14690
|
return this.resolveBundled(runtime);
|
|
14676
14691
|
}
|
|
14677
|
-
// Does this device OFFER a runtime —
|
|
14678
|
-
// ONE signal the heartbeat manifest, the adapter registry and the
|
|
14679
|
-
// read, so what the device advertises and what it can select can't
|
|
14692
|
+
// Does this device OFFER a runtime — usable now, or used in this run and still
|
|
14693
|
+
// runnable? The ONE signal the heartbeat manifest, the adapter registry and the
|
|
14694
|
+
// dispatcher read, so what the device advertises and what it can select can't
|
|
14695
|
+
// disagree.
|
|
14680
14696
|
offered(runtime) {
|
|
14681
|
-
return this.
|
|
14697
|
+
return this.advertised().includes(runtime);
|
|
14698
|
+
}
|
|
14699
|
+
advertised() {
|
|
14700
|
+
return this.detection ? advertisedRuntimes(this.detection, this.used) : [];
|
|
14682
14701
|
}
|
|
14683
14702
|
// The address an OpenCode turn runs against: the serve detection found, else
|
|
14684
14703
|
// the configured one, for the model list a beat still asks for.
|
|
@@ -15337,6 +15356,7 @@ var CompanionSupervisor = class {
|
|
|
15337
15356
|
...this.probeOpencode ? { probeOpencode: this.probeOpencode } : {}
|
|
15338
15357
|
});
|
|
15339
15358
|
this.detection = detection;
|
|
15359
|
+
this.used = rememberUsed(this.used, detection);
|
|
15340
15360
|
this.logBundledTransitions();
|
|
15341
15361
|
this.harnessVersions = {
|
|
15342
15362
|
claudeCode: detectedVersion(detection["claude-code"]),
|
|
@@ -15391,7 +15411,7 @@ var CompanionSupervisor = class {
|
|
|
15391
15411
|
const health = this.connectorHealth.reports();
|
|
15392
15412
|
if (health.length === 0 || !this.detection) return health;
|
|
15393
15413
|
const named = new Set(health.map((r) => r.runtime));
|
|
15394
|
-
const bare =
|
|
15414
|
+
const bare = this.advertised().filter((runtime) => !named.has(runtime)).map((runtime) => ({ runtime }));
|
|
15395
15415
|
return [...health, ...bare];
|
|
15396
15416
|
}
|
|
15397
15417
|
// Beat NOW and wait for it to land. `start` calls this before asking the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cabane/companion",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.183",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The Cabane Companion (headless): connect an 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",
|