@cabane/companion 0.6.29 → 0.6.31
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 +51 -17
- package/dist/runtime.js +47 -13
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6475,7 +6475,7 @@ var ConnectorHealthStore = class {
|
|
|
6475
6475
|
|
|
6476
6476
|
// src/dispatcher.ts
|
|
6477
6477
|
import { randomUUID } from "crypto";
|
|
6478
|
-
import { appendFileSync as appendFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync11, statSync } from "fs";
|
|
6478
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync11, readdirSync as readdirSync2, statSync } from "fs";
|
|
6479
6479
|
import { join as join15 } from "path";
|
|
6480
6480
|
|
|
6481
6481
|
// src/summon.ts
|
|
@@ -7371,10 +7371,19 @@ var DEFAULT_PREPARING_ROW_DELAY_MS = 1500;
|
|
|
7371
7371
|
var DEFAULT_AGENT_IDLE_TIMEOUT_MS = 10 * 6e4;
|
|
7372
7372
|
var DEFAULT_AGENT_TOTAL_TIMEOUT_MS = 6 * 60 * 6e4;
|
|
7373
7373
|
function checkoutState(cwd) {
|
|
7374
|
-
if (!cwd) return { ok: false, reason: "no
|
|
7375
|
-
if (!existsSync11(cwd)) return { ok: false, reason: `the
|
|
7374
|
+
if (!cwd) return { ok: false, reason: "no working directory was resolved for this turn" };
|
|
7375
|
+
if (!existsSync11(cwd)) return { ok: false, reason: `the working directory is gone (${cwd})` };
|
|
7376
|
+
let entries;
|
|
7377
|
+
try {
|
|
7378
|
+
entries = readdirSync2(cwd);
|
|
7379
|
+
} catch (error) {
|
|
7380
|
+
return { ok: false, reason: `${cwd} is unreadable (${error.message})` };
|
|
7381
|
+
}
|
|
7382
|
+
if (entries.length === 0) {
|
|
7383
|
+
return { ok: false, reason: `${cwd} is empty \u2014 a recreated shell, not a prepared directory` };
|
|
7384
|
+
}
|
|
7376
7385
|
const gitPath = join15(cwd, ".git");
|
|
7377
|
-
if (!existsSync11(gitPath)) return { ok:
|
|
7386
|
+
if (!existsSync11(gitPath)) return { ok: true, reason: "usable" };
|
|
7378
7387
|
let stat;
|
|
7379
7388
|
try {
|
|
7380
7389
|
stat = statSync(gitPath);
|
|
@@ -8588,7 +8597,7 @@ async function enumerateOpencodeModels(serverUrl, fetchImpl = fetch) {
|
|
|
8588
8597
|
import {
|
|
8589
8598
|
existsSync as existsSync12,
|
|
8590
8599
|
mkdirSync as mkdirSync12,
|
|
8591
|
-
readdirSync as
|
|
8600
|
+
readdirSync as readdirSync3,
|
|
8592
8601
|
readFileSync as readFileSync9,
|
|
8593
8602
|
renameSync as renameSync3,
|
|
8594
8603
|
rmSync as rmSync7,
|
|
@@ -8645,7 +8654,7 @@ var Outbox = class {
|
|
|
8645
8654
|
if (!existsSync12(dir2)) return [];
|
|
8646
8655
|
let names;
|
|
8647
8656
|
try {
|
|
8648
|
-
names =
|
|
8657
|
+
names = readdirSync3(dir2);
|
|
8649
8658
|
} catch {
|
|
8650
8659
|
return [];
|
|
8651
8660
|
}
|
|
@@ -8680,7 +8689,7 @@ var Outbox = class {
|
|
|
8680
8689
|
const dir2 = this.dir();
|
|
8681
8690
|
if (!existsSync12(dir2)) return 0;
|
|
8682
8691
|
try {
|
|
8683
|
-
return
|
|
8692
|
+
return readdirSync3(dir2).filter((n) => n.endsWith(".json")).length;
|
|
8684
8693
|
} catch {
|
|
8685
8694
|
return 0;
|
|
8686
8695
|
}
|
|
@@ -8807,6 +8816,7 @@ var SseSubscriber = class {
|
|
|
8807
8816
|
try {
|
|
8808
8817
|
await this.connect();
|
|
8809
8818
|
backoff = 500;
|
|
8819
|
+
if (!this.aborted) await sleep3(backoff);
|
|
8810
8820
|
} catch (err) {
|
|
8811
8821
|
if (this.aborted) return;
|
|
8812
8822
|
if (err instanceof ApiError && (err.status === 401 || err.status === 403)) {
|
|
@@ -8832,8 +8842,8 @@ var SseSubscriber = class {
|
|
|
8832
8842
|
}
|
|
8833
8843
|
async connect() {
|
|
8834
8844
|
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}`;
|
|
8845
|
+
const query = this.opts.path ? "" : this.lastEventId ? "" : "?replayPending=1";
|
|
8846
|
+
const url = this.opts.path ? `${base}${this.opts.path}` : `${base}/api/workspaces/${this.opts.workspaceId}/events${query}`;
|
|
8837
8847
|
this.controller = new AbortController();
|
|
8838
8848
|
const headers = {
|
|
8839
8849
|
Authorization: `Bearer ${this.opts.token}`,
|
|
@@ -8925,6 +8935,7 @@ var CompanionSupervisor = class {
|
|
|
8925
8935
|
reexecFn;
|
|
8926
8936
|
dispatcherFactory;
|
|
8927
8937
|
deviceApi = null;
|
|
8938
|
+
deviceSub = null;
|
|
8928
8939
|
heartbeatTimer = null;
|
|
8929
8940
|
inFlightHeartbeat = null;
|
|
8930
8941
|
pollTimer = null;
|
|
@@ -8978,6 +8989,24 @@ var CompanionSupervisor = class {
|
|
|
8978
8989
|
baseUrl: this.config.baseUrl,
|
|
8979
8990
|
deviceToken: this.config.deviceToken
|
|
8980
8991
|
});
|
|
8992
|
+
this.deviceSub = new SseSubscriber({
|
|
8993
|
+
baseUrl: this.config.baseUrl,
|
|
8994
|
+
workspaceId: "device-control",
|
|
8995
|
+
path: "/api/companion/events",
|
|
8996
|
+
token: this.config.deviceToken,
|
|
8997
|
+
log: this.log,
|
|
8998
|
+
lastEventId: null,
|
|
8999
|
+
onMessage: async (ev) => {
|
|
9000
|
+
if (ev.event === "assignments_changed") {
|
|
9001
|
+
if (this.refreshing && this.inFlightRefresh) await this.inFlightRefresh;
|
|
9002
|
+
await this.refreshAssignments();
|
|
9003
|
+
}
|
|
9004
|
+
},
|
|
9005
|
+
onAuthFailure: () => {
|
|
9006
|
+
this.log.error("companion: device control stream authentication failed");
|
|
9007
|
+
}
|
|
9008
|
+
});
|
|
9009
|
+
this.deviceSub.start();
|
|
8981
9010
|
await this.refreshAssignments();
|
|
8982
9011
|
this.kickHeartbeat();
|
|
8983
9012
|
this.heartbeatTimer = setInterval(() => this.kickHeartbeat(), HEARTBEAT_INTERVAL_MS);
|
|
@@ -8990,7 +9019,10 @@ var CompanionSupervisor = class {
|
|
|
8990
9019
|
// at least the loops keeping the event loop alive; the SSE promises let a
|
|
8991
9020
|
// clean teardown resolve.
|
|
8992
9021
|
finishedPromises() {
|
|
8993
|
-
return [
|
|
9022
|
+
return [
|
|
9023
|
+
...this.deviceSub ? [this.deviceSub.finished] : [],
|
|
9024
|
+
...[...this.workspaces.values()].flatMap((w) => w.sub ? [w.sub.finished] : [])
|
|
9025
|
+
];
|
|
8994
9026
|
}
|
|
8995
9027
|
// ---- device-level loops ----
|
|
8996
9028
|
kickHeartbeat() {
|
|
@@ -9674,13 +9706,15 @@ var CompanionSupervisor = class {
|
|
|
9674
9706
|
this.stopped = true;
|
|
9675
9707
|
if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
|
|
9676
9708
|
if (this.pollTimer) clearInterval(this.pollTimer);
|
|
9709
|
+
this.deviceSub?.stop();
|
|
9677
9710
|
for (const wr of this.workspaces.values()) {
|
|
9678
9711
|
for (const a of wr.agents.values()) a.cancelDrain();
|
|
9679
9712
|
if (wr.sub) wr.sub.stop();
|
|
9680
9713
|
}
|
|
9681
|
-
await Promise.all(
|
|
9682
|
-
|
|
9683
|
-
|
|
9714
|
+
await Promise.all([
|
|
9715
|
+
...this.deviceSub ? [this.deviceSub.finished] : [],
|
|
9716
|
+
...[...this.workspaces.values()].flatMap((wr) => wr.sub ? [wr.sub.finished] : [])
|
|
9717
|
+
]);
|
|
9684
9718
|
}
|
|
9685
9719
|
async drainForRestart(graceMs) {
|
|
9686
9720
|
this.restartPending = true;
|
|
@@ -10033,7 +10067,7 @@ companion: deploy drain requested (${graceMs}ms grace)\u2026
|
|
|
10033
10067
|
}
|
|
10034
10068
|
|
|
10035
10069
|
// src/commands/status.ts
|
|
10036
|
-
import { readdirSync as
|
|
10070
|
+
import { readdirSync as readdirSync4 } from "fs";
|
|
10037
10071
|
async function status(deps = {}) {
|
|
10038
10072
|
const readService = deps.service ?? serviceStatus;
|
|
10039
10073
|
const cfg = loadConfig();
|
|
@@ -10104,7 +10138,7 @@ function transcriptsLine() {
|
|
|
10104
10138
|
let count = 0;
|
|
10105
10139
|
let newest;
|
|
10106
10140
|
try {
|
|
10107
|
-
const files =
|
|
10141
|
+
const files = readdirSync4(dir2).filter((f) => f.endsWith(".jsonl")).sort();
|
|
10108
10142
|
count = files.length;
|
|
10109
10143
|
newest = files[files.length - 1];
|
|
10110
10144
|
} catch {
|
|
@@ -10204,7 +10238,7 @@ function isAlive(kill, pid) {
|
|
|
10204
10238
|
}
|
|
10205
10239
|
|
|
10206
10240
|
// src/commands/transcript.ts
|
|
10207
|
-
import { existsSync as existsSync14, readFileSync as readFileSync11, readdirSync as
|
|
10241
|
+
import { existsSync as existsSync14, readFileSync as readFileSync11, readdirSync as readdirSync5 } from "fs";
|
|
10208
10242
|
import { isAbsolute, join as join18 } from "path";
|
|
10209
10243
|
async function transcript(opts = {}) {
|
|
10210
10244
|
const dir2 = transcriptsDir();
|
|
@@ -10330,7 +10364,7 @@ async function followTranscripts(dir2) {
|
|
|
10330
10364
|
}
|
|
10331
10365
|
function listFiles(dir2) {
|
|
10332
10366
|
try {
|
|
10333
|
-
return
|
|
10367
|
+
return readdirSync5(dir2).filter((f) => f.endsWith(".jsonl")).sort().reverse();
|
|
10334
10368
|
} catch {
|
|
10335
10369
|
return [];
|
|
10336
10370
|
}
|
package/dist/runtime.js
CHANGED
|
@@ -5587,7 +5587,7 @@ var ConnectorHealthStore = class {
|
|
|
5587
5587
|
|
|
5588
5588
|
// src/dispatcher.ts
|
|
5589
5589
|
import { randomUUID } from "crypto";
|
|
5590
|
-
import { appendFileSync as appendFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync9, statSync } from "fs";
|
|
5590
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync9, readdirSync as readdirSync2, statSync } from "fs";
|
|
5591
5591
|
import { join as join13 } from "path";
|
|
5592
5592
|
|
|
5593
5593
|
// src/summon.ts
|
|
@@ -6483,10 +6483,19 @@ var DEFAULT_PREPARING_ROW_DELAY_MS = 1500;
|
|
|
6483
6483
|
var DEFAULT_AGENT_IDLE_TIMEOUT_MS = 10 * 6e4;
|
|
6484
6484
|
var DEFAULT_AGENT_TOTAL_TIMEOUT_MS = 6 * 60 * 6e4;
|
|
6485
6485
|
function checkoutState(cwd) {
|
|
6486
|
-
if (!cwd) return { ok: false, reason: "no
|
|
6487
|
-
if (!existsSync9(cwd)) return { ok: false, reason: `the
|
|
6486
|
+
if (!cwd) return { ok: false, reason: "no working directory was resolved for this turn" };
|
|
6487
|
+
if (!existsSync9(cwd)) return { ok: false, reason: `the working directory is gone (${cwd})` };
|
|
6488
|
+
let entries;
|
|
6489
|
+
try {
|
|
6490
|
+
entries = readdirSync2(cwd);
|
|
6491
|
+
} catch (error) {
|
|
6492
|
+
return { ok: false, reason: `${cwd} is unreadable (${error.message})` };
|
|
6493
|
+
}
|
|
6494
|
+
if (entries.length === 0) {
|
|
6495
|
+
return { ok: false, reason: `${cwd} is empty \u2014 a recreated shell, not a prepared directory` };
|
|
6496
|
+
}
|
|
6488
6497
|
const gitPath = join13(cwd, ".git");
|
|
6489
|
-
if (!existsSync9(gitPath)) return { ok:
|
|
6498
|
+
if (!existsSync9(gitPath)) return { ok: true, reason: "usable" };
|
|
6490
6499
|
let stat;
|
|
6491
6500
|
try {
|
|
6492
6501
|
stat = statSync(gitPath);
|
|
@@ -7700,7 +7709,7 @@ async function enumerateOpencodeModels(serverUrl, fetchImpl = fetch) {
|
|
|
7700
7709
|
import {
|
|
7701
7710
|
existsSync as existsSync10,
|
|
7702
7711
|
mkdirSync as mkdirSync10,
|
|
7703
|
-
readdirSync as
|
|
7712
|
+
readdirSync as readdirSync3,
|
|
7704
7713
|
readFileSync as readFileSync8,
|
|
7705
7714
|
renameSync as renameSync3,
|
|
7706
7715
|
rmSync as rmSync6,
|
|
@@ -7757,7 +7766,7 @@ var Outbox = class {
|
|
|
7757
7766
|
if (!existsSync10(dir2)) return [];
|
|
7758
7767
|
let names;
|
|
7759
7768
|
try {
|
|
7760
|
-
names =
|
|
7769
|
+
names = readdirSync3(dir2);
|
|
7761
7770
|
} catch {
|
|
7762
7771
|
return [];
|
|
7763
7772
|
}
|
|
@@ -7792,7 +7801,7 @@ var Outbox = class {
|
|
|
7792
7801
|
const dir2 = this.dir();
|
|
7793
7802
|
if (!existsSync10(dir2)) return 0;
|
|
7794
7803
|
try {
|
|
7795
|
-
return
|
|
7804
|
+
return readdirSync3(dir2).filter((n) => n.endsWith(".json")).length;
|
|
7796
7805
|
} catch {
|
|
7797
7806
|
return 0;
|
|
7798
7807
|
}
|
|
@@ -7919,6 +7928,7 @@ var SseSubscriber = class {
|
|
|
7919
7928
|
try {
|
|
7920
7929
|
await this.connect();
|
|
7921
7930
|
backoff = 500;
|
|
7931
|
+
if (!this.aborted) await sleep2(backoff);
|
|
7922
7932
|
} catch (err) {
|
|
7923
7933
|
if (this.aborted) return;
|
|
7924
7934
|
if (err instanceof ApiError && (err.status === 401 || err.status === 403)) {
|
|
@@ -7944,8 +7954,8 @@ var SseSubscriber = class {
|
|
|
7944
7954
|
}
|
|
7945
7955
|
async connect() {
|
|
7946
7956
|
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}`;
|
|
7957
|
+
const query = this.opts.path ? "" : this.lastEventId ? "" : "?replayPending=1";
|
|
7958
|
+
const url = this.opts.path ? `${base}${this.opts.path}` : `${base}/api/workspaces/${this.opts.workspaceId}/events${query}`;
|
|
7949
7959
|
this.controller = new AbortController();
|
|
7950
7960
|
const headers = {
|
|
7951
7961
|
Authorization: `Bearer ${this.opts.token}`,
|
|
@@ -8037,6 +8047,7 @@ var CompanionSupervisor = class {
|
|
|
8037
8047
|
reexecFn;
|
|
8038
8048
|
dispatcherFactory;
|
|
8039
8049
|
deviceApi = null;
|
|
8050
|
+
deviceSub = null;
|
|
8040
8051
|
heartbeatTimer = null;
|
|
8041
8052
|
inFlightHeartbeat = null;
|
|
8042
8053
|
pollTimer = null;
|
|
@@ -8090,6 +8101,24 @@ var CompanionSupervisor = class {
|
|
|
8090
8101
|
baseUrl: this.config.baseUrl,
|
|
8091
8102
|
deviceToken: this.config.deviceToken
|
|
8092
8103
|
});
|
|
8104
|
+
this.deviceSub = new SseSubscriber({
|
|
8105
|
+
baseUrl: this.config.baseUrl,
|
|
8106
|
+
workspaceId: "device-control",
|
|
8107
|
+
path: "/api/companion/events",
|
|
8108
|
+
token: this.config.deviceToken,
|
|
8109
|
+
log: this.log,
|
|
8110
|
+
lastEventId: null,
|
|
8111
|
+
onMessage: async (ev) => {
|
|
8112
|
+
if (ev.event === "assignments_changed") {
|
|
8113
|
+
if (this.refreshing && this.inFlightRefresh) await this.inFlightRefresh;
|
|
8114
|
+
await this.refreshAssignments();
|
|
8115
|
+
}
|
|
8116
|
+
},
|
|
8117
|
+
onAuthFailure: () => {
|
|
8118
|
+
this.log.error("companion: device control stream authentication failed");
|
|
8119
|
+
}
|
|
8120
|
+
});
|
|
8121
|
+
this.deviceSub.start();
|
|
8093
8122
|
await this.refreshAssignments();
|
|
8094
8123
|
this.kickHeartbeat();
|
|
8095
8124
|
this.heartbeatTimer = setInterval(() => this.kickHeartbeat(), HEARTBEAT_INTERVAL_MS);
|
|
@@ -8102,7 +8131,10 @@ var CompanionSupervisor = class {
|
|
|
8102
8131
|
// at least the loops keeping the event loop alive; the SSE promises let a
|
|
8103
8132
|
// clean teardown resolve.
|
|
8104
8133
|
finishedPromises() {
|
|
8105
|
-
return [
|
|
8134
|
+
return [
|
|
8135
|
+
...this.deviceSub ? [this.deviceSub.finished] : [],
|
|
8136
|
+
...[...this.workspaces.values()].flatMap((w) => w.sub ? [w.sub.finished] : [])
|
|
8137
|
+
];
|
|
8106
8138
|
}
|
|
8107
8139
|
// ---- device-level loops ----
|
|
8108
8140
|
kickHeartbeat() {
|
|
@@ -8786,13 +8818,15 @@ var CompanionSupervisor = class {
|
|
|
8786
8818
|
this.stopped = true;
|
|
8787
8819
|
if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
|
|
8788
8820
|
if (this.pollTimer) clearInterval(this.pollTimer);
|
|
8821
|
+
this.deviceSub?.stop();
|
|
8789
8822
|
for (const wr of this.workspaces.values()) {
|
|
8790
8823
|
for (const a of wr.agents.values()) a.cancelDrain();
|
|
8791
8824
|
if (wr.sub) wr.sub.stop();
|
|
8792
8825
|
}
|
|
8793
|
-
await Promise.all(
|
|
8794
|
-
|
|
8795
|
-
|
|
8826
|
+
await Promise.all([
|
|
8827
|
+
...this.deviceSub ? [this.deviceSub.finished] : [],
|
|
8828
|
+
...[...this.workspaces.values()].flatMap((wr) => wr.sub ? [wr.sub.finished] : [])
|
|
8829
|
+
]);
|
|
8796
8830
|
}
|
|
8797
8831
|
async drainForRestart(graceMs) {
|
|
8798
8832
|
this.restartPending = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cabane/companion",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.31",
|
|
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",
|