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