@cabane/companion 0.6.162 → 0.6.164
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 +370 -239
- package/dist/runtime.js +178 -67
- package/package.json +1 -1
package/dist/runtime.js
CHANGED
|
@@ -1678,7 +1678,9 @@ function clearRuntimeStateIfOurs(instanceId) {
|
|
|
1678
1678
|
}
|
|
1679
1679
|
rmSync3(path, { force: true });
|
|
1680
1680
|
}
|
|
1681
|
-
function readLiveRuntimeState(
|
|
1681
|
+
function readLiveRuntimeState({
|
|
1682
|
+
sweep = true
|
|
1683
|
+
} = {}) {
|
|
1682
1684
|
const path = runtimePath();
|
|
1683
1685
|
if (!existsSync6(path)) return null;
|
|
1684
1686
|
let parsed;
|
|
@@ -1691,7 +1693,7 @@ function readLiveRuntimeState() {
|
|
|
1691
1693
|
try {
|
|
1692
1694
|
process.kill(parsed.pid, 0);
|
|
1693
1695
|
} catch {
|
|
1694
|
-
clearRuntimeState();
|
|
1696
|
+
if (sweep) clearRuntimeState();
|
|
1695
1697
|
return null;
|
|
1696
1698
|
}
|
|
1697
1699
|
return parsed;
|
|
@@ -2543,15 +2545,20 @@ import {
|
|
|
2543
2545
|
rmSync as rmSync4,
|
|
2544
2546
|
writeFileSync as writeFileSync3
|
|
2545
2547
|
} from "fs";
|
|
2546
|
-
import {
|
|
2548
|
+
import { join as join11 } from "path";
|
|
2547
2549
|
import { z as z4 } from "zod";
|
|
2548
|
-
|
|
2550
|
+
var credentialMapSchema = z4.record(z4.string(), z4.string());
|
|
2551
|
+
function credentialsDir() {
|
|
2552
|
+
return join11(cabaneDir(), "credentials");
|
|
2553
|
+
}
|
|
2554
|
+
function legacyPath() {
|
|
2549
2555
|
return join11(cabaneDir(), "credentials.json");
|
|
2550
2556
|
}
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2557
|
+
function devicePath(deviceId) {
|
|
2558
|
+
return join11(credentialsDir(), `${deviceId.replace(/[^A-Za-z0-9_-]/g, "_")}.json`);
|
|
2559
|
+
}
|
|
2560
|
+
function read(path) {
|
|
2561
|
+
if (!existsSync7(path)) return null;
|
|
2555
2562
|
let raw;
|
|
2556
2563
|
try {
|
|
2557
2564
|
raw = readFileSync4(path, "utf8");
|
|
@@ -2560,19 +2567,25 @@ function load() {
|
|
|
2560
2567
|
}
|
|
2561
2568
|
if (raw.trim().length === 0) return {};
|
|
2562
2569
|
try {
|
|
2563
|
-
const result =
|
|
2570
|
+
const result = credentialMapSchema.safeParse(JSON.parse(raw));
|
|
2564
2571
|
return result.success ? result.data : {};
|
|
2565
2572
|
} catch {
|
|
2566
2573
|
return {};
|
|
2567
2574
|
}
|
|
2568
2575
|
}
|
|
2569
|
-
function
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2576
|
+
function load(deviceId) {
|
|
2577
|
+
return read(devicePath(deviceId)) ?? read(legacyPath()) ?? {};
|
|
2578
|
+
}
|
|
2579
|
+
function save(deviceId, map) {
|
|
2580
|
+
const dir2 = credentialsDir();
|
|
2581
|
+
mkdirSync5(dir2, { recursive: true });
|
|
2582
|
+
for (const d of [cabaneDir(), dir2]) {
|
|
2583
|
+
try {
|
|
2584
|
+
chmodSync3(d, 448);
|
|
2585
|
+
} catch {
|
|
2586
|
+
}
|
|
2575
2587
|
}
|
|
2588
|
+
const path = devicePath(deviceId);
|
|
2576
2589
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
2577
2590
|
try {
|
|
2578
2591
|
writeFileSync3(tmp, JSON.stringify(map, null, 2) + "\n", { mode: 384 });
|
|
@@ -2589,26 +2602,23 @@ function save(map) {
|
|
|
2589
2602
|
throw err;
|
|
2590
2603
|
}
|
|
2591
2604
|
}
|
|
2592
|
-
function getCredential(agentId) {
|
|
2593
|
-
return load()[agentId] ?? null;
|
|
2605
|
+
function getCredential(deviceId, agentId) {
|
|
2606
|
+
return load(deviceId)[agentId] ?? null;
|
|
2594
2607
|
}
|
|
2595
|
-
function setCredential(agentId, pat) {
|
|
2596
|
-
|
|
2597
|
-
|
|
2608
|
+
function setCredential(deviceId, agentId, pat) {
|
|
2609
|
+
if (read(devicePath(deviceId))?.[agentId] === pat) return;
|
|
2610
|
+
const map = load(deviceId);
|
|
2598
2611
|
map[agentId] = pat;
|
|
2599
|
-
save(map);
|
|
2612
|
+
save(deviceId, map);
|
|
2600
2613
|
}
|
|
2601
|
-
function pruneCredentials(keepAgentIds) {
|
|
2602
|
-
const map = load();
|
|
2614
|
+
function pruneCredentials(deviceId, keepAgentIds) {
|
|
2603
2615
|
const keep = new Set(keepAgentIds);
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
}
|
|
2611
|
-
if (removed > 0) save(map);
|
|
2616
|
+
if (keep.size === 0) return [];
|
|
2617
|
+
const own = read(devicePath(deviceId));
|
|
2618
|
+
const map = own ?? load(deviceId);
|
|
2619
|
+
const removed = Object.keys(map).filter((id) => !keep.has(id));
|
|
2620
|
+
for (const id of removed) delete map[id];
|
|
2621
|
+
if (removed.length > 0 || own === null) save(deviceId, map);
|
|
2612
2622
|
return removed;
|
|
2613
2623
|
}
|
|
2614
2624
|
|
|
@@ -3922,14 +3932,14 @@ function readRequestContextTokens(msg) {
|
|
|
3922
3932
|
return readSdkUsage(msg.message?.usage)?.inputTokens;
|
|
3923
3933
|
}
|
|
3924
3934
|
var CONTEXT_READING_TIMEOUT_MS = 2e3;
|
|
3925
|
-
async function readHarnessContext(
|
|
3926
|
-
if (!
|
|
3935
|
+
async function readHarnessContext(read2) {
|
|
3936
|
+
if (!read2) return void 0;
|
|
3927
3937
|
let timer;
|
|
3928
3938
|
const timeout = new Promise((resolve2) => {
|
|
3929
3939
|
timer = setTimeout(() => resolve2(void 0), CONTEXT_READING_TIMEOUT_MS);
|
|
3930
3940
|
});
|
|
3931
3941
|
try {
|
|
3932
|
-
return parseContextUsage(await Promise.race([
|
|
3942
|
+
return parseContextUsage(await Promise.race([read2(), timeout]));
|
|
3933
3943
|
} catch {
|
|
3934
3944
|
return void 0;
|
|
3935
3945
|
} finally {
|
|
@@ -6136,14 +6146,14 @@ function decideResume3(stored, currentCwd) {
|
|
|
6136
6146
|
function readUsageBaseline(raw) {
|
|
6137
6147
|
if (!raw || typeof raw !== "object") return null;
|
|
6138
6148
|
const rec = raw;
|
|
6139
|
-
const
|
|
6149
|
+
const read2 = (key) => {
|
|
6140
6150
|
const v = rec[key];
|
|
6141
6151
|
return typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : null;
|
|
6142
6152
|
};
|
|
6143
|
-
const inputTokens =
|
|
6144
|
-
const outputTokens =
|
|
6145
|
-
const cacheReadTokens =
|
|
6146
|
-
const cacheCreationTokens =
|
|
6153
|
+
const inputTokens = read2("inputTokens");
|
|
6154
|
+
const outputTokens = read2("outputTokens");
|
|
6155
|
+
const cacheReadTokens = read2("cacheReadTokens");
|
|
6156
|
+
const cacheCreationTokens = read2("cacheCreationTokens");
|
|
6147
6157
|
if (inputTokens === null || outputTokens === null || cacheReadTokens === null || cacheCreationTokens === null) {
|
|
6148
6158
|
return null;
|
|
6149
6159
|
}
|
|
@@ -9224,9 +9234,9 @@ function resolveSeqFloor(sources, ctx) {
|
|
|
9224
9234
|
);
|
|
9225
9235
|
return floor;
|
|
9226
9236
|
}
|
|
9227
|
-
function readOutboxFloor(
|
|
9237
|
+
function readOutboxFloor(read2, turnId, log) {
|
|
9228
9238
|
try {
|
|
9229
|
-
return
|
|
9239
|
+
return read2(turnId);
|
|
9230
9240
|
} catch (err) {
|
|
9231
9241
|
log.debug(
|
|
9232
9242
|
{ turnId, err: err instanceof Error ? err.message : String(err) },
|
|
@@ -9340,7 +9350,7 @@ function clearPrepared(workspaceId, conversationId, agentId) {
|
|
|
9340
9350
|
|
|
9341
9351
|
// src/transcript-writer.ts
|
|
9342
9352
|
import { appendFileSync, chmodSync as chmodSync4, copyFileSync, mkdirSync as mkdirSync8, readdirSync, rmSync as rmSync6 } from "fs";
|
|
9343
|
-
import { basename as basename3, dirname as
|
|
9353
|
+
import { basename as basename3, dirname as dirname7, join as join17 } from "path";
|
|
9344
9354
|
function transcriptsDir() {
|
|
9345
9355
|
return join17(runnerStateDir(), "transcripts");
|
|
9346
9356
|
}
|
|
@@ -9381,7 +9391,7 @@ var TranscriptWriter = class {
|
|
|
9381
9391
|
preserveAnomaly() {
|
|
9382
9392
|
if (this.broken) return;
|
|
9383
9393
|
try {
|
|
9384
|
-
const dir2 = join17(
|
|
9394
|
+
const dir2 = join17(dirname7(this.path), "anomalies");
|
|
9385
9395
|
mkdirSync8(dir2, { recursive: true, mode: 448 });
|
|
9386
9396
|
chmodSync4(dir2, 448);
|
|
9387
9397
|
const target = join17(dir2, basename3(this.path));
|
|
@@ -11302,7 +11312,7 @@ function publishStaged(from, to, move = atomicMove) {
|
|
|
11302
11312
|
// src/providers/runner-base.ts
|
|
11303
11313
|
import { createHash as createHash3 } from "crypto";
|
|
11304
11314
|
import { existsSync as existsSync14, realpathSync as realpathSync4 } from "fs";
|
|
11305
|
-
import { dirname as
|
|
11315
|
+
import { dirname as dirname8, join as join20, sep as sep3 } from "path";
|
|
11306
11316
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
11307
11317
|
var COMPANION_MOUNT = "/opt/cabane/companion";
|
|
11308
11318
|
var BASE_DOCKERFILE = `FROM node:22-slim
|
|
@@ -11348,10 +11358,10 @@ async function ensureBaseImage(docker, image, log) {
|
|
|
11348
11358
|
return true;
|
|
11349
11359
|
}
|
|
11350
11360
|
function ownInstall(moduleFile = fileURLToPath5(import.meta.url)) {
|
|
11351
|
-
let dir2 =
|
|
11361
|
+
let dir2 = dirname8(moduleFile);
|
|
11352
11362
|
for (let i = 0; i < 4; i++) {
|
|
11353
11363
|
if (existsSync14(join20(dir2, "dist", "cli.js")) && existsSync14(join20(dir2, "package.json"))) break;
|
|
11354
|
-
dir2 =
|
|
11364
|
+
dir2 = dirname8(dir2);
|
|
11355
11365
|
}
|
|
11356
11366
|
if (!existsSync14(join20(dir2, "dist", "cli.js"))) return null;
|
|
11357
11367
|
const sdk = join20(dir2, "node_modules", "@anthropic-ai", "claude-agent-sdk");
|
|
@@ -12411,7 +12421,7 @@ import {
|
|
|
12411
12421
|
import { createRequire as createRequire2 } from "module";
|
|
12412
12422
|
import { isIP } from "net";
|
|
12413
12423
|
import { homedir as homedir6, networkInterfaces, tmpdir as tmpdir4, userInfo as userInfo2 } from "os";
|
|
12414
|
-
import { dirname as
|
|
12424
|
+
import { dirname as dirname9, join as join22, sep as sep4 } from "path";
|
|
12415
12425
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
12416
12426
|
import { SandboxManager } from "@anthropic-ai/sandbox-runtime";
|
|
12417
12427
|
function accountHome() {
|
|
@@ -12424,9 +12434,9 @@ function accountHome() {
|
|
|
12424
12434
|
var STORE_WALK_TIMEOUT_MS = 6e4;
|
|
12425
12435
|
var STORE_WALK_MAX_AGE_MS = 5 * 6e4;
|
|
12426
12436
|
function storeWalkRoots(homes, cabaneDir2) {
|
|
12427
|
-
const companionHome =
|
|
12428
|
-
const parent =
|
|
12429
|
-
const beside = homes.includes(companionHome) ||
|
|
12437
|
+
const companionHome = dirname9(cabaneDir2);
|
|
12438
|
+
const parent = dirname9(companionHome);
|
|
12439
|
+
const beside = homes.includes(companionHome) || dirname9(parent) === parent ? [] : [parent];
|
|
12430
12440
|
const candidates = [.../* @__PURE__ */ new Set([...homes, ...beside])];
|
|
12431
12441
|
return candidates.filter(
|
|
12432
12442
|
(root) => !candidates.some((other) => other !== root && isAtOrUnder(root, other))
|
|
@@ -13244,7 +13254,7 @@ var SseSubscriber = class {
|
|
|
13244
13254
|
// src/updater.ts
|
|
13245
13255
|
import { spawn as spawn8 } from "child_process";
|
|
13246
13256
|
import { copyFileSync as copyFileSync3, existsSync as existsSync18, mkdirSync as mkdirSync16, renameSync as renameSync11, rmSync as rmSync14 } from "fs";
|
|
13247
|
-
import { dirname as
|
|
13257
|
+
import { dirname as dirname13, join as join28 } from "path";
|
|
13248
13258
|
|
|
13249
13259
|
// src/boot-rule.ts
|
|
13250
13260
|
import {
|
|
@@ -13256,7 +13266,7 @@ import {
|
|
|
13256
13266
|
symlinkSync,
|
|
13257
13267
|
writeFileSync as writeFileSync9
|
|
13258
13268
|
} from "fs";
|
|
13259
|
-
import { dirname as
|
|
13269
|
+
import { dirname as dirname10, join as join25 } from "path";
|
|
13260
13270
|
var UNCONFIRMED_WINDOW_MS = 10 * 6e4;
|
|
13261
13271
|
var MAX_STAGE_ATTEMPTS = 3;
|
|
13262
13272
|
function superseded(pending, ...running) {
|
|
@@ -13289,7 +13299,7 @@ import {
|
|
|
13289
13299
|
rmSync as rmSync13,
|
|
13290
13300
|
symlinkSync as symlinkSync2
|
|
13291
13301
|
} from "fs";
|
|
13292
|
-
import { dirname as
|
|
13302
|
+
import { dirname as dirname11, join as join26 } from "path";
|
|
13293
13303
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
13294
13304
|
function versionsDir() {
|
|
13295
13305
|
return join26(cabaneDir(), "versions");
|
|
@@ -13304,7 +13314,7 @@ function versionPackageRoot(version) {
|
|
|
13304
13314
|
return join26(versionsDir(), version, "node_modules", "@cabane", "companion");
|
|
13305
13315
|
}
|
|
13306
13316
|
function ownPackageRoot(moduleUrl = import.meta.url) {
|
|
13307
|
-
return
|
|
13317
|
+
return dirname11(dirname11(fileURLToPath7(moduleUrl)));
|
|
13308
13318
|
}
|
|
13309
13319
|
function packageVersion(root) {
|
|
13310
13320
|
try {
|
|
@@ -13324,7 +13334,7 @@ function currentRoot() {
|
|
|
13324
13334
|
}
|
|
13325
13335
|
function pointCurrentAt(root) {
|
|
13326
13336
|
const link = currentLink();
|
|
13327
|
-
mkdirSync14(
|
|
13337
|
+
mkdirSync14(dirname11(link), { recursive: true });
|
|
13328
13338
|
const fresh = `${link}.${process.pid}.${Date.now()}`;
|
|
13329
13339
|
symlinkSync2(root, fresh);
|
|
13330
13340
|
try {
|
|
@@ -13359,7 +13369,7 @@ function compareVersions(a, b) {
|
|
|
13359
13369
|
|
|
13360
13370
|
// src/update-state.ts
|
|
13361
13371
|
import { mkdirSync as mkdirSync15, readFileSync as readFileSync15, renameSync as renameSync10, writeFileSync as writeFileSync10 } from "fs";
|
|
13362
|
-
import { dirname as
|
|
13372
|
+
import { dirname as dirname12, join as join27 } from "path";
|
|
13363
13373
|
import { z as z18 } from "zod";
|
|
13364
13374
|
var resultSchema = z18.object({
|
|
13365
13375
|
kind: z18.enum(["updated", "rolled_back", "failed"]),
|
|
@@ -13394,7 +13404,7 @@ function readUpdateState(path = updateStatePath()) {
|
|
|
13394
13404
|
}
|
|
13395
13405
|
}
|
|
13396
13406
|
function writeUpdateState(state, path = updateStatePath()) {
|
|
13397
|
-
mkdirSync15(
|
|
13407
|
+
mkdirSync15(dirname12(path), { recursive: true });
|
|
13398
13408
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
13399
13409
|
writeFileSync10(tmp, `${JSON.stringify(state, null, 2)}
|
|
13400
13410
|
`, { mode: 384 });
|
|
@@ -13435,7 +13445,7 @@ function writeBootGuard(root = ownPackageRoot()) {
|
|
|
13435
13445
|
const built = join28(root, "dist", "boot-guard.js");
|
|
13436
13446
|
if (!existsSync18(built)) return false;
|
|
13437
13447
|
const target = bootGuardPath();
|
|
13438
|
-
mkdirSync16(
|
|
13448
|
+
mkdirSync16(dirname13(target), { recursive: true });
|
|
13439
13449
|
const tmp = `${target}.${process.pid}.tmp`;
|
|
13440
13450
|
copyFileSync3(built, tmp);
|
|
13441
13451
|
renameSync11(tmp, target);
|
|
@@ -13478,9 +13488,9 @@ async function stageVersion(version, harnesses, deps = {}) {
|
|
|
13478
13488
|
if (!await verifyInstall(placed, version, harnesses, { run, execPath: node }))
|
|
13479
13489
|
return { ok: true, root: placed };
|
|
13480
13490
|
}
|
|
13481
|
-
const npm = join28(
|
|
13491
|
+
const npm = join28(dirname13(node), "npm");
|
|
13482
13492
|
if (!existsSync18(npm))
|
|
13483
|
-
return { ok: false, reason: `there is no npm beside Node at ${
|
|
13493
|
+
return { ok: false, reason: `there is no npm beside Node at ${dirname13(node)}` };
|
|
13484
13494
|
mkdirSync16(versionsDir(), { recursive: true });
|
|
13485
13495
|
const staging = join28(versionsDir(), `.${version}.staging-${process.pid}`);
|
|
13486
13496
|
rmSync14(staging, { recursive: true, force: true });
|
|
@@ -13499,7 +13509,7 @@ async function stageVersion(version, harnesses, deps = {}) {
|
|
|
13499
13509
|
],
|
|
13500
13510
|
{
|
|
13501
13511
|
// npm's install scripts run on this Node, not whichever `node` is first on PATH.
|
|
13502
|
-
env: { ...process.env, PATH: `${
|
|
13512
|
+
env: { ...process.env, PATH: `${dirname13(node)}:${process.env.PATH ?? ""}` },
|
|
13503
13513
|
timeoutMs: 15 * 6e4
|
|
13504
13514
|
}
|
|
13505
13515
|
);
|
|
@@ -14162,7 +14172,7 @@ var CompanionSupervisor = class {
|
|
|
14162
14172
|
saveConfig(this.config);
|
|
14163
14173
|
}
|
|
14164
14174
|
for (const it of items) {
|
|
14165
|
-
if (it.credential) setCredential(it.agentId, it.credential);
|
|
14175
|
+
if (it.credential) setCredential(device.id, it.agentId, it.credential);
|
|
14166
14176
|
}
|
|
14167
14177
|
const byWorkspace = /* @__PURE__ */ new Map();
|
|
14168
14178
|
for (const it of items) {
|
|
@@ -14173,8 +14183,17 @@ var CompanionSupervisor = class {
|
|
|
14173
14183
|
for (const wsId of [...this.workspaces.keys()]) {
|
|
14174
14184
|
if (!byWorkspace.has(wsId)) await this.removeWorkspace(wsId);
|
|
14175
14185
|
}
|
|
14176
|
-
for (const [wsId, list] of byWorkspace) this.reconcileWorkspace(wsId, list);
|
|
14177
|
-
pruneCredentials(
|
|
14186
|
+
for (const [wsId, list] of byWorkspace) this.reconcileWorkspace(device.id, wsId, list);
|
|
14187
|
+
const pruned = pruneCredentials(
|
|
14188
|
+
device.id,
|
|
14189
|
+
items.map((i) => i.agentId)
|
|
14190
|
+
);
|
|
14191
|
+
if (pruned.length > 0) {
|
|
14192
|
+
this.log.info(
|
|
14193
|
+
{ deviceId: device.id, agentIds: pruned },
|
|
14194
|
+
`Removed the saved sign-in of ${pruned.length === 1 ? "agent" : "agents"} ${pruned.join(", ")}: no longer assigned to this device (${device.id}).`
|
|
14195
|
+
);
|
|
14196
|
+
}
|
|
14178
14197
|
void this.recoverRuns();
|
|
14179
14198
|
} finally {
|
|
14180
14199
|
this.refreshing = false;
|
|
@@ -14188,7 +14207,7 @@ var CompanionSupervisor = class {
|
|
|
14188
14207
|
}
|
|
14189
14208
|
}
|
|
14190
14209
|
// ---- reconciliation ----
|
|
14191
|
-
reconcileWorkspace(workspaceId, list) {
|
|
14210
|
+
reconcileWorkspace(deviceId, workspaceId, list) {
|
|
14192
14211
|
const first = list[0];
|
|
14193
14212
|
let wr = this.workspaces.get(workspaceId);
|
|
14194
14213
|
if (!wr) {
|
|
@@ -14214,7 +14233,7 @@ var CompanionSupervisor = class {
|
|
|
14214
14233
|
if (!desired.has(agentId)) this.removeAgent(wr, agentId);
|
|
14215
14234
|
}
|
|
14216
14235
|
for (const it of list) {
|
|
14217
|
-
const credential = it.credential ?? getCredential(it.agentId);
|
|
14236
|
+
const credential = it.credential ?? getCredential(deviceId, it.agentId);
|
|
14218
14237
|
const runConfig = parseRunConfig(
|
|
14219
14238
|
it.runConfig,
|
|
14220
14239
|
(m) => this.log.warn(
|
|
@@ -15297,6 +15316,95 @@ function clearCrash() {
|
|
|
15297
15316
|
}
|
|
15298
15317
|
}
|
|
15299
15318
|
|
|
15319
|
+
// src/stray-companion.ts
|
|
15320
|
+
import { spawnSync } from "child_process";
|
|
15321
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
15322
|
+
import { join as join30 } from "path";
|
|
15323
|
+
var COMPANION_ENTRY = /@cabane\/companion\/|cabane-companion|(^|[\s/])cabane\s|\.cabane\/(versions|current|boot-guard)/;
|
|
15324
|
+
var START = /\sstart(\s|$)/;
|
|
15325
|
+
function companionStarts(psOutput) {
|
|
15326
|
+
const found = [];
|
|
15327
|
+
for (const line of psOutput.split("\n")) {
|
|
15328
|
+
const match = /^\s*(\d+)\s+(.*)$/.exec(line);
|
|
15329
|
+
if (!match) continue;
|
|
15330
|
+
const command = match[2];
|
|
15331
|
+
if (COMPANION_ENTRY.test(command) && START.test(command)) {
|
|
15332
|
+
found.push({ pid: Number(match[1]), command });
|
|
15333
|
+
}
|
|
15334
|
+
}
|
|
15335
|
+
return found;
|
|
15336
|
+
}
|
|
15337
|
+
function stateDirOf(env) {
|
|
15338
|
+
const home = env.CABANE_COMPANION_HOME || env.HOME;
|
|
15339
|
+
return home ? join30(home, ".cabane") : null;
|
|
15340
|
+
}
|
|
15341
|
+
function envFromPsE(output, command) {
|
|
15342
|
+
const line = output.replace(/\n$/, "");
|
|
15343
|
+
if (!line.startsWith(command)) return {};
|
|
15344
|
+
const env = {};
|
|
15345
|
+
for (const entry of line.slice(command.length).trim().split(/ (?=[A-Za-z_][A-Za-z0-9_]*=)/)) {
|
|
15346
|
+
const eq = entry.indexOf("=");
|
|
15347
|
+
if (eq > 0) env[entry.slice(0, eq)] = entry.slice(eq + 1);
|
|
15348
|
+
}
|
|
15349
|
+
return env;
|
|
15350
|
+
}
|
|
15351
|
+
function envFromProc(pid) {
|
|
15352
|
+
try {
|
|
15353
|
+
const env = {};
|
|
15354
|
+
for (const entry of readFileSync17(`/proc/${pid}/environ`, "utf8").split("\0")) {
|
|
15355
|
+
const eq = entry.indexOf("=");
|
|
15356
|
+
if (eq > 0) env[entry.slice(0, eq)] = entry.slice(eq + 1);
|
|
15357
|
+
}
|
|
15358
|
+
return env;
|
|
15359
|
+
} catch {
|
|
15360
|
+
return null;
|
|
15361
|
+
}
|
|
15362
|
+
}
|
|
15363
|
+
function envOf(pid, command) {
|
|
15364
|
+
if (process.platform === "linux") return envFromProc(pid);
|
|
15365
|
+
const result = spawnSync("ps", ["-E", "-ww", "-o", "command=", "-p", String(pid)], {
|
|
15366
|
+
encoding: "utf8",
|
|
15367
|
+
timeout: 5e3
|
|
15368
|
+
});
|
|
15369
|
+
if (result.error || result.status !== 0) return null;
|
|
15370
|
+
return envFromPsE(result.stdout, command);
|
|
15371
|
+
}
|
|
15372
|
+
function findStrayCompanions(exclude = [], deps = {}) {
|
|
15373
|
+
if (!deps.ps && (process.platform === "win32" || typeof process.getuid !== "function")) {
|
|
15374
|
+
return [];
|
|
15375
|
+
}
|
|
15376
|
+
const ps = deps.ps ?? (() => {
|
|
15377
|
+
const result = spawnSync("ps", ["-U", String(process.getuid()), "-o", "pid=,command="], {
|
|
15378
|
+
encoding: "utf8",
|
|
15379
|
+
timeout: 5e3,
|
|
15380
|
+
maxBuffer: 16 * 1024 * 1024
|
|
15381
|
+
});
|
|
15382
|
+
return result.error || typeof result.stdout !== "string" ? null : result.stdout;
|
|
15383
|
+
});
|
|
15384
|
+
const listing = ps();
|
|
15385
|
+
if (!listing) return [];
|
|
15386
|
+
const dir2 = deps.dir ?? cabaneDir();
|
|
15387
|
+
const skip = /* @__PURE__ */ new Set([...deps.self ?? [process.pid, process.ppid], ...exclude]);
|
|
15388
|
+
return companionStarts(listing).filter((c) => {
|
|
15389
|
+
if (skip.has(c.pid)) return false;
|
|
15390
|
+
const env = (deps.env ?? envOf)(c.pid, c.command);
|
|
15391
|
+
return env !== null && stateDirOf(env) === dir2;
|
|
15392
|
+
});
|
|
15393
|
+
}
|
|
15394
|
+
var SERVICE_ENTRY = /\.cabane\/boot-guard/;
|
|
15395
|
+
function strayCompanionLines(strays, retry) {
|
|
15396
|
+
const one = strays.length === 1;
|
|
15397
|
+
return [
|
|
15398
|
+
`Another Cabane companion is running for this computer's ~/.cabane, and ~/.cabane has no record of it:`,
|
|
15399
|
+
...strays.map((s) => ` pid ${s.pid} ${s.command}`),
|
|
15400
|
+
`Two companions for one ~/.cabane sign each other's agents out. Stop ${one ? "it" : "them"} first:`,
|
|
15401
|
+
...strays.map(
|
|
15402
|
+
(s) => SERVICE_ENTRY.test(s.command) ? ` pid ${s.pid} is a service, which restarts if killed: cabane service uninstall` : ` kill ${s.pid}`
|
|
15403
|
+
),
|
|
15404
|
+
`then run ${retry} again.`
|
|
15405
|
+
];
|
|
15406
|
+
}
|
|
15407
|
+
|
|
15300
15408
|
// src/runtime.ts
|
|
15301
15409
|
async function createCompanionRuntime(opts = {}) {
|
|
15302
15410
|
const log = getLogger();
|
|
@@ -15323,6 +15431,8 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
15323
15431
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
15324
15432
|
const pre = readLiveRuntimeState();
|
|
15325
15433
|
if (pre && await verifyRuntime(pre) === "stale") clearRuntimeState();
|
|
15434
|
+
const strays = findStrayCompanions(pre ? [pre.pid] : []);
|
|
15435
|
+
if (strays.length > 0) return { ok: false, reason: "stray-companion", strays };
|
|
15326
15436
|
const claim = acquireRuntimeState({
|
|
15327
15437
|
pid: process.pid,
|
|
15328
15438
|
startedAt,
|
|
@@ -15434,5 +15544,6 @@ async function closeSurfaces(control, dashboard) {
|
|
|
15434
15544
|
if (dashboard) await dashboard.close();
|
|
15435
15545
|
}
|
|
15436
15546
|
export {
|
|
15437
|
-
createCompanionRuntime
|
|
15547
|
+
createCompanionRuntime,
|
|
15548
|
+
strayCompanionLines
|
|
15438
15549
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cabane/companion",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.164",
|
|
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",
|