claude-use 6.2.0 → 6.2.1
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.cjs +42 -16
- package/dist/index.cjs +38 -14
- package/dist/index.mjs +38 -14
- package/dist/types/headroom/state.d.ts +6 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -55390,7 +55390,7 @@ var HeadroomStateSchema = external_exports.strictObject({
|
|
|
55390
55390
|
/** The last fatal error, kept across shutdowns so `headroom status` can explain a daemon that is not running. */
|
|
55391
55391
|
lastError: external_exports.string().optional()
|
|
55392
55392
|
});
|
|
55393
|
-
var HeadroomSessionSchema = external_exports.strictObject({ pid: external_exports.number().int().positive(), startedAt: external_exports.number() });
|
|
55393
|
+
var HeadroomSessionSchema = external_exports.strictObject({ pid: external_exports.number().int().positive(), startedAt: external_exports.number(), supervisorPid: external_exports.number().int().positive() });
|
|
55394
55394
|
var HeadroomLockSchema = external_exports.strictObject({ pid: external_exports.number().int().positive(), at: external_exports.number() });
|
|
55395
55395
|
function parseJson(raw, schema) {
|
|
55396
55396
|
if (raw === void 0 || raw.trim() === "") {
|
|
@@ -55470,6 +55470,9 @@ function listSessions(fs16, sessionsDir) {
|
|
|
55470
55470
|
}
|
|
55471
55471
|
return sessions.sort((a2, b) => a2.pid - b.pid);
|
|
55472
55472
|
}
|
|
55473
|
+
function sessionsForSupervisor(fs16, sessionsDir, supervisorPid) {
|
|
55474
|
+
return listSessions(fs16, sessionsDir).filter((session) => session.supervisorPid === supervisorPid);
|
|
55475
|
+
}
|
|
55473
55476
|
function pruneDeadSessions(fs16, sessionsDir, isRunning) {
|
|
55474
55477
|
const removed = [];
|
|
55475
55478
|
for (const session of listSessions(fs16, sessionsDir)) {
|
|
@@ -55506,7 +55509,7 @@ function ensureHeadroom(params) {
|
|
|
55506
55509
|
if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
|
|
55507
55510
|
const daemonUp = state.port !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
|
|
55508
55511
|
if (daemonUp && state.port !== void 0) {
|
|
55509
|
-
writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now() });
|
|
55512
|
+
writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
|
|
55510
55513
|
return { port: state.port };
|
|
55511
55514
|
}
|
|
55512
55515
|
if (state.lastError !== void 0) {
|
|
@@ -60994,7 +60997,7 @@ function resolveLaunchIdentity(paths, cwd) {
|
|
|
60994
60997
|
// package.json
|
|
60995
60998
|
var package_default = {
|
|
60996
60999
|
name: "claude-use",
|
|
60997
|
-
version: "6.2.
|
|
61000
|
+
version: "6.2.1",
|
|
60998
61001
|
description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
|
|
60999
61002
|
license: "Apache-2.0",
|
|
61000
61003
|
author: "Joseph Mearman <joseph@mearman.co.uk>",
|
|
@@ -68074,15 +68077,28 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
68074
68077
|
const { fs: fs16, paths } = ports;
|
|
68075
68078
|
let config2 = initialConfig;
|
|
68076
68079
|
const previousState = readHeadroomState(fs16, paths.headroomStateFile);
|
|
68080
|
+
if (previousState?.supervisorPid !== void 0 && previousState.supervisorPid !== ports.ownPid && ports.isRunning(previousState.supervisorPid)) {
|
|
68081
|
+
ports.log(`claude-use headroom supervisor ${String(ports.ownPid)}: supervisor ${String(previousState.supervisorPid)} already owns the daemon; exiting`);
|
|
68082
|
+
return 0;
|
|
68083
|
+
}
|
|
68077
68084
|
let installedSource = previousState?.installedSource;
|
|
68078
68085
|
let lastPort = previousState?.lastPort;
|
|
68079
68086
|
const sticky = () => lastPort === void 0 ? {} : { lastPort };
|
|
68087
|
+
const ownsState = () => {
|
|
68088
|
+
const owner = readHeadroomState(fs16, paths.headroomStateFile)?.supervisorPid;
|
|
68089
|
+
return owner === void 0 || owner === ports.ownPid || !ports.isRunning(owner);
|
|
68090
|
+
};
|
|
68091
|
+
const writeOwnState = (state) => {
|
|
68092
|
+
if (!ownsState()) {
|
|
68093
|
+
return false;
|
|
68094
|
+
}
|
|
68095
|
+
writeHeadroomState(fs16, paths.headroomStateFile, state);
|
|
68096
|
+
return true;
|
|
68097
|
+
};
|
|
68080
68098
|
const fail = (message) => {
|
|
68081
|
-
|
|
68082
|
-
|
|
68083
|
-
|
|
68084
|
-
});
|
|
68085
|
-
fs16.removeRecursive(paths.headroomLockFile);
|
|
68099
|
+
if (writeOwnState({ lastError: message, ...sticky() })) {
|
|
68100
|
+
fs16.removeRecursive(paths.headroomLockFile);
|
|
68101
|
+
}
|
|
68086
68102
|
ports.log(`claude-use headroom supervisor: ${message}`);
|
|
68087
68103
|
return 1;
|
|
68088
68104
|
};
|
|
@@ -68105,7 +68121,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
68105
68121
|
return fail(`could not install headroom from ${config2.source}: install failed (is uv installed and on PATH?)`);
|
|
68106
68122
|
}
|
|
68107
68123
|
const version2 = ports.headroomVersion() ?? "unknown";
|
|
68108
|
-
|
|
68124
|
+
writeOwnState({ supervisorPid: ports.ownPid, version: version2, installedSource, ...sticky() });
|
|
68109
68125
|
ports.log(`claude-use headroom supervisor ${String(ports.ownPid)}: managing headroom on allowlist [${allowlistOf(ports).join(", ")}]`);
|
|
68110
68126
|
const orphanPid = previousState?.headroomPid;
|
|
68111
68127
|
if (orphanPid !== void 0 && ports.isRunning(orphanPid)) {
|
|
@@ -68117,6 +68133,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
68117
68133
|
let runningSettingsHash;
|
|
68118
68134
|
let consecutiveFailures = 0;
|
|
68119
68135
|
let idleSince;
|
|
68136
|
+
let superseded = false;
|
|
68120
68137
|
let ticks = 0;
|
|
68121
68138
|
for (; ; ) {
|
|
68122
68139
|
if (options.tickLimit !== void 0 && ticks >= options.tickLimit) {
|
|
@@ -68128,6 +68145,11 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
68128
68145
|
const allowlist = allowlistOf(ports);
|
|
68129
68146
|
const allowlistHash = hashAllowlist(allowlist);
|
|
68130
68147
|
pruneDeadSessions(fs16, paths.headroomSessionsDir, ports.isRunning);
|
|
68148
|
+
const ownsNow = ownsState();
|
|
68149
|
+
if (!ownsNow && !superseded) {
|
|
68150
|
+
ports.log("claude-use headroom supervisor: another supervisor now owns state.json; serving this daemon's own sessions until they end");
|
|
68151
|
+
}
|
|
68152
|
+
superseded = !ownsNow;
|
|
68131
68153
|
const crashed = headroomPid !== void 0 && !ports.isRunning(headroomPid);
|
|
68132
68154
|
if (headroomPid === void 0 || crashed) {
|
|
68133
68155
|
if (crashed) {
|
|
@@ -68135,7 +68157,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
68135
68157
|
headroomPid = void 0;
|
|
68136
68158
|
runningHash = void 0;
|
|
68137
68159
|
runningSettingsHash = void 0;
|
|
68138
|
-
|
|
68160
|
+
writeOwnState({
|
|
68139
68161
|
supervisorPid: ports.ownPid,
|
|
68140
68162
|
version: version2,
|
|
68141
68163
|
installedSource,
|
|
@@ -68163,7 +68185,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
68163
68185
|
`claude-use headroom supervisor: sticky port ${String(previousSticky)} was occupied; moving to ${String(port)} (sessions launched against the old port are stale until they relaunch)`
|
|
68164
68186
|
);
|
|
68165
68187
|
}
|
|
68166
|
-
|
|
68188
|
+
const recorded = writeOwnState({
|
|
68167
68189
|
supervisorPid: ports.ownPid,
|
|
68168
68190
|
headroomPid: pid,
|
|
68169
68191
|
port,
|
|
@@ -68173,7 +68195,9 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
68173
68195
|
installedSource: config2.source,
|
|
68174
68196
|
...sticky()
|
|
68175
68197
|
});
|
|
68176
|
-
|
|
68198
|
+
if (recorded) {
|
|
68199
|
+
fs16.removeRecursive(paths.headroomLockFile);
|
|
68200
|
+
}
|
|
68177
68201
|
idleSince = void 0;
|
|
68178
68202
|
ports.log(`claude-use headroom supervisor: headroom pid ${String(pid)} ready on 127.0.0.1:${String(port)}`);
|
|
68179
68203
|
} else {
|
|
@@ -68187,7 +68211,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
68187
68211
|
}
|
|
68188
68212
|
} else {
|
|
68189
68213
|
if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config2.source) {
|
|
68190
|
-
if (
|
|
68214
|
+
if (sessionsForSupervisor(fs16, paths.headroomSessionsDir, ports.ownPid).length === 0) {
|
|
68191
68215
|
ports.log("claude-use headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
|
|
68192
68216
|
ports.stopProcess(headroomPid);
|
|
68193
68217
|
headroomPid = void 0;
|
|
@@ -68196,7 +68220,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
68196
68220
|
continue;
|
|
68197
68221
|
}
|
|
68198
68222
|
}
|
|
68199
|
-
const sessions =
|
|
68223
|
+
const sessions = sessionsForSupervisor(fs16, paths.headroomSessionsDir, ports.ownPid);
|
|
68200
68224
|
if (sessions.length === 0) {
|
|
68201
68225
|
idleSince ??= ports.now();
|
|
68202
68226
|
const idleMs = ports.now() - idleSince;
|
|
@@ -68205,7 +68229,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
68205
68229
|
`claude-use headroom supervisor: no sessions for ${String(config2.idleShutdownMinutes)} minute(s); stopping headroom and exiting`
|
|
68206
68230
|
);
|
|
68207
68231
|
ports.stopProcess(headroomPid);
|
|
68208
|
-
|
|
68232
|
+
writeOwnState({
|
|
68209
68233
|
version: version2,
|
|
68210
68234
|
...sticky()
|
|
68211
68235
|
});
|
|
@@ -68283,7 +68307,9 @@ function formatHeadroomStatus(status) {
|
|
|
68283
68307
|
if (status.sessions.length === 0) {
|
|
68284
68308
|
lines.push("sessions: none");
|
|
68285
68309
|
} else {
|
|
68286
|
-
const pids = status.sessions.map(
|
|
68310
|
+
const pids = status.sessions.map(
|
|
68311
|
+
(session) => `${String(session.pid)}${session.alive ? "" : " (dead)"}` + (session.supervisorPid === status.state.supervisorPid ? "" : ` on superseded supervisor ${String(session.supervisorPid)}`)
|
|
68312
|
+
);
|
|
68287
68313
|
lines.push(`sessions: ${String(status.sessions.length)} registered (${pids.join(", ")})`);
|
|
68288
68314
|
}
|
|
68289
68315
|
if (status.state.lastError !== void 0) {
|
package/dist/index.cjs
CHANGED
|
@@ -2811,7 +2811,7 @@ var HeadroomStateSchema = import_zod4.z.strictObject({
|
|
|
2811
2811
|
/** The last fatal error, kept across shutdowns so `headroom status` can explain a daemon that is not running. */
|
|
2812
2812
|
lastError: import_zod4.z.string().optional()
|
|
2813
2813
|
});
|
|
2814
|
-
var HeadroomSessionSchema = import_zod4.z.strictObject({ pid: import_zod4.z.number().int().positive(), startedAt: import_zod4.z.number() });
|
|
2814
|
+
var HeadroomSessionSchema = import_zod4.z.strictObject({ pid: import_zod4.z.number().int().positive(), startedAt: import_zod4.z.number(), supervisorPid: import_zod4.z.number().int().positive() });
|
|
2815
2815
|
var HeadroomLockSchema = import_zod4.z.strictObject({ pid: import_zod4.z.number().int().positive(), at: import_zod4.z.number() });
|
|
2816
2816
|
function parseJson(raw, schema) {
|
|
2817
2817
|
if (raw === void 0 || raw.trim() === "") {
|
|
@@ -2891,6 +2891,9 @@ function listSessions(fs2, sessionsDir) {
|
|
|
2891
2891
|
}
|
|
2892
2892
|
return sessions.sort((a, b) => a.pid - b.pid);
|
|
2893
2893
|
}
|
|
2894
|
+
function sessionsForSupervisor(fs2, sessionsDir, supervisorPid) {
|
|
2895
|
+
return listSessions(fs2, sessionsDir).filter((session) => session.supervisorPid === supervisorPid);
|
|
2896
|
+
}
|
|
2894
2897
|
function pruneDeadSessions(fs2, sessionsDir, isRunning) {
|
|
2895
2898
|
const removed = [];
|
|
2896
2899
|
for (const session of listSessions(fs2, sessionsDir)) {
|
|
@@ -3190,7 +3193,7 @@ function ensureHeadroom(params) {
|
|
|
3190
3193
|
if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
|
|
3191
3194
|
const daemonUp = state.port !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
|
|
3192
3195
|
if (daemonUp && state.port !== void 0) {
|
|
3193
|
-
writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now() });
|
|
3196
|
+
writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
|
|
3194
3197
|
return { port: state.port };
|
|
3195
3198
|
}
|
|
3196
3199
|
if (state.lastError !== void 0) {
|
|
@@ -3327,15 +3330,28 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3327
3330
|
const { fs: fs2, paths } = ports;
|
|
3328
3331
|
let config = initialConfig;
|
|
3329
3332
|
const previousState = readHeadroomState(fs2, paths.headroomStateFile);
|
|
3333
|
+
if (previousState?.supervisorPid !== void 0 && previousState.supervisorPid !== ports.ownPid && ports.isRunning(previousState.supervisorPid)) {
|
|
3334
|
+
ports.log(`claude-use headroom supervisor ${String(ports.ownPid)}: supervisor ${String(previousState.supervisorPid)} already owns the daemon; exiting`);
|
|
3335
|
+
return 0;
|
|
3336
|
+
}
|
|
3330
3337
|
let installedSource = previousState?.installedSource;
|
|
3331
3338
|
let lastPort = previousState?.lastPort;
|
|
3332
3339
|
const sticky = () => lastPort === void 0 ? {} : { lastPort };
|
|
3340
|
+
const ownsState = () => {
|
|
3341
|
+
const owner = readHeadroomState(fs2, paths.headroomStateFile)?.supervisorPid;
|
|
3342
|
+
return owner === void 0 || owner === ports.ownPid || !ports.isRunning(owner);
|
|
3343
|
+
};
|
|
3344
|
+
const writeOwnState = (state) => {
|
|
3345
|
+
if (!ownsState()) {
|
|
3346
|
+
return false;
|
|
3347
|
+
}
|
|
3348
|
+
writeHeadroomState(fs2, paths.headroomStateFile, state);
|
|
3349
|
+
return true;
|
|
3350
|
+
};
|
|
3333
3351
|
const fail = (message) => {
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
});
|
|
3338
|
-
fs2.removeRecursive(paths.headroomLockFile);
|
|
3352
|
+
if (writeOwnState({ lastError: message, ...sticky() })) {
|
|
3353
|
+
fs2.removeRecursive(paths.headroomLockFile);
|
|
3354
|
+
}
|
|
3339
3355
|
ports.log(`claude-use headroom supervisor: ${message}`);
|
|
3340
3356
|
return 1;
|
|
3341
3357
|
};
|
|
@@ -3358,7 +3374,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3358
3374
|
return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
|
|
3359
3375
|
}
|
|
3360
3376
|
const version = ports.headroomVersion() ?? "unknown";
|
|
3361
|
-
|
|
3377
|
+
writeOwnState({ supervisorPid: ports.ownPid, version, installedSource, ...sticky() });
|
|
3362
3378
|
ports.log(`claude-use headroom supervisor ${String(ports.ownPid)}: managing headroom on allowlist [${allowlistOf(ports).join(", ")}]`);
|
|
3363
3379
|
const orphanPid = previousState?.headroomPid;
|
|
3364
3380
|
if (orphanPid !== void 0 && ports.isRunning(orphanPid)) {
|
|
@@ -3370,6 +3386,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3370
3386
|
let runningSettingsHash;
|
|
3371
3387
|
let consecutiveFailures = 0;
|
|
3372
3388
|
let idleSince;
|
|
3389
|
+
let superseded = false;
|
|
3373
3390
|
let ticks = 0;
|
|
3374
3391
|
for (; ; ) {
|
|
3375
3392
|
if (options.tickLimit !== void 0 && ticks >= options.tickLimit) {
|
|
@@ -3381,6 +3398,11 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3381
3398
|
const allowlist = allowlistOf(ports);
|
|
3382
3399
|
const allowlistHash = hashAllowlist(allowlist);
|
|
3383
3400
|
pruneDeadSessions(fs2, paths.headroomSessionsDir, ports.isRunning);
|
|
3401
|
+
const ownsNow = ownsState();
|
|
3402
|
+
if (!ownsNow && !superseded) {
|
|
3403
|
+
ports.log("claude-use headroom supervisor: another supervisor now owns state.json; serving this daemon's own sessions until they end");
|
|
3404
|
+
}
|
|
3405
|
+
superseded = !ownsNow;
|
|
3384
3406
|
const crashed = headroomPid !== void 0 && !ports.isRunning(headroomPid);
|
|
3385
3407
|
if (headroomPid === void 0 || crashed) {
|
|
3386
3408
|
if (crashed) {
|
|
@@ -3388,7 +3410,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3388
3410
|
headroomPid = void 0;
|
|
3389
3411
|
runningHash = void 0;
|
|
3390
3412
|
runningSettingsHash = void 0;
|
|
3391
|
-
|
|
3413
|
+
writeOwnState({
|
|
3392
3414
|
supervisorPid: ports.ownPid,
|
|
3393
3415
|
version,
|
|
3394
3416
|
installedSource,
|
|
@@ -3416,7 +3438,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3416
3438
|
`claude-use headroom supervisor: sticky port ${String(previousSticky)} was occupied; moving to ${String(port)} (sessions launched against the old port are stale until they relaunch)`
|
|
3417
3439
|
);
|
|
3418
3440
|
}
|
|
3419
|
-
|
|
3441
|
+
const recorded = writeOwnState({
|
|
3420
3442
|
supervisorPid: ports.ownPid,
|
|
3421
3443
|
headroomPid: pid,
|
|
3422
3444
|
port,
|
|
@@ -3426,7 +3448,9 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3426
3448
|
installedSource: config.source,
|
|
3427
3449
|
...sticky()
|
|
3428
3450
|
});
|
|
3429
|
-
|
|
3451
|
+
if (recorded) {
|
|
3452
|
+
fs2.removeRecursive(paths.headroomLockFile);
|
|
3453
|
+
}
|
|
3430
3454
|
idleSince = void 0;
|
|
3431
3455
|
ports.log(`claude-use headroom supervisor: headroom pid ${String(pid)} ready on 127.0.0.1:${String(port)}`);
|
|
3432
3456
|
} else {
|
|
@@ -3440,7 +3464,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3440
3464
|
}
|
|
3441
3465
|
} else {
|
|
3442
3466
|
if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
|
|
3443
|
-
if (
|
|
3467
|
+
if (sessionsForSupervisor(fs2, paths.headroomSessionsDir, ports.ownPid).length === 0) {
|
|
3444
3468
|
ports.log("claude-use headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
|
|
3445
3469
|
ports.stopProcess(headroomPid);
|
|
3446
3470
|
headroomPid = void 0;
|
|
@@ -3449,7 +3473,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3449
3473
|
continue;
|
|
3450
3474
|
}
|
|
3451
3475
|
}
|
|
3452
|
-
const sessions =
|
|
3476
|
+
const sessions = sessionsForSupervisor(fs2, paths.headroomSessionsDir, ports.ownPid);
|
|
3453
3477
|
if (sessions.length === 0) {
|
|
3454
3478
|
idleSince ??= ports.now();
|
|
3455
3479
|
const idleMs = ports.now() - idleSince;
|
|
@@ -3458,7 +3482,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3458
3482
|
`claude-use headroom supervisor: no sessions for ${String(config.idleShutdownMinutes)} minute(s); stopping headroom and exiting`
|
|
3459
3483
|
);
|
|
3460
3484
|
ports.stopProcess(headroomPid);
|
|
3461
|
-
|
|
3485
|
+
writeOwnState({
|
|
3462
3486
|
version,
|
|
3463
3487
|
...sticky()
|
|
3464
3488
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -2737,7 +2737,7 @@ var HeadroomStateSchema = z4.strictObject({
|
|
|
2737
2737
|
/** The last fatal error, kept across shutdowns so `headroom status` can explain a daemon that is not running. */
|
|
2738
2738
|
lastError: z4.string().optional()
|
|
2739
2739
|
});
|
|
2740
|
-
var HeadroomSessionSchema = z4.strictObject({ pid: z4.number().int().positive(), startedAt: z4.number() });
|
|
2740
|
+
var HeadroomSessionSchema = z4.strictObject({ pid: z4.number().int().positive(), startedAt: z4.number(), supervisorPid: z4.number().int().positive() });
|
|
2741
2741
|
var HeadroomLockSchema = z4.strictObject({ pid: z4.number().int().positive(), at: z4.number() });
|
|
2742
2742
|
function parseJson(raw, schema) {
|
|
2743
2743
|
if (raw === void 0 || raw.trim() === "") {
|
|
@@ -2817,6 +2817,9 @@ function listSessions(fs2, sessionsDir) {
|
|
|
2817
2817
|
}
|
|
2818
2818
|
return sessions.sort((a, b) => a.pid - b.pid);
|
|
2819
2819
|
}
|
|
2820
|
+
function sessionsForSupervisor(fs2, sessionsDir, supervisorPid) {
|
|
2821
|
+
return listSessions(fs2, sessionsDir).filter((session) => session.supervisorPid === supervisorPid);
|
|
2822
|
+
}
|
|
2820
2823
|
function pruneDeadSessions(fs2, sessionsDir, isRunning) {
|
|
2821
2824
|
const removed = [];
|
|
2822
2825
|
for (const session of listSessions(fs2, sessionsDir)) {
|
|
@@ -3116,7 +3119,7 @@ function ensureHeadroom(params) {
|
|
|
3116
3119
|
if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
|
|
3117
3120
|
const daemonUp = state.port !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
|
|
3118
3121
|
if (daemonUp && state.port !== void 0) {
|
|
3119
|
-
writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now() });
|
|
3122
|
+
writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
|
|
3120
3123
|
return { port: state.port };
|
|
3121
3124
|
}
|
|
3122
3125
|
if (state.lastError !== void 0) {
|
|
@@ -3253,15 +3256,28 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3253
3256
|
const { fs: fs2, paths } = ports;
|
|
3254
3257
|
let config = initialConfig;
|
|
3255
3258
|
const previousState = readHeadroomState(fs2, paths.headroomStateFile);
|
|
3259
|
+
if (previousState?.supervisorPid !== void 0 && previousState.supervisorPid !== ports.ownPid && ports.isRunning(previousState.supervisorPid)) {
|
|
3260
|
+
ports.log(`claude-use headroom supervisor ${String(ports.ownPid)}: supervisor ${String(previousState.supervisorPid)} already owns the daemon; exiting`);
|
|
3261
|
+
return 0;
|
|
3262
|
+
}
|
|
3256
3263
|
let installedSource = previousState?.installedSource;
|
|
3257
3264
|
let lastPort = previousState?.lastPort;
|
|
3258
3265
|
const sticky = () => lastPort === void 0 ? {} : { lastPort };
|
|
3266
|
+
const ownsState = () => {
|
|
3267
|
+
const owner = readHeadroomState(fs2, paths.headroomStateFile)?.supervisorPid;
|
|
3268
|
+
return owner === void 0 || owner === ports.ownPid || !ports.isRunning(owner);
|
|
3269
|
+
};
|
|
3270
|
+
const writeOwnState = (state) => {
|
|
3271
|
+
if (!ownsState()) {
|
|
3272
|
+
return false;
|
|
3273
|
+
}
|
|
3274
|
+
writeHeadroomState(fs2, paths.headroomStateFile, state);
|
|
3275
|
+
return true;
|
|
3276
|
+
};
|
|
3259
3277
|
const fail = (message) => {
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
});
|
|
3264
|
-
fs2.removeRecursive(paths.headroomLockFile);
|
|
3278
|
+
if (writeOwnState({ lastError: message, ...sticky() })) {
|
|
3279
|
+
fs2.removeRecursive(paths.headroomLockFile);
|
|
3280
|
+
}
|
|
3265
3281
|
ports.log(`claude-use headroom supervisor: ${message}`);
|
|
3266
3282
|
return 1;
|
|
3267
3283
|
};
|
|
@@ -3284,7 +3300,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3284
3300
|
return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
|
|
3285
3301
|
}
|
|
3286
3302
|
const version = ports.headroomVersion() ?? "unknown";
|
|
3287
|
-
|
|
3303
|
+
writeOwnState({ supervisorPid: ports.ownPid, version, installedSource, ...sticky() });
|
|
3288
3304
|
ports.log(`claude-use headroom supervisor ${String(ports.ownPid)}: managing headroom on allowlist [${allowlistOf(ports).join(", ")}]`);
|
|
3289
3305
|
const orphanPid = previousState?.headroomPid;
|
|
3290
3306
|
if (orphanPid !== void 0 && ports.isRunning(orphanPid)) {
|
|
@@ -3296,6 +3312,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3296
3312
|
let runningSettingsHash;
|
|
3297
3313
|
let consecutiveFailures = 0;
|
|
3298
3314
|
let idleSince;
|
|
3315
|
+
let superseded = false;
|
|
3299
3316
|
let ticks = 0;
|
|
3300
3317
|
for (; ; ) {
|
|
3301
3318
|
if (options.tickLimit !== void 0 && ticks >= options.tickLimit) {
|
|
@@ -3307,6 +3324,11 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3307
3324
|
const allowlist = allowlistOf(ports);
|
|
3308
3325
|
const allowlistHash = hashAllowlist(allowlist);
|
|
3309
3326
|
pruneDeadSessions(fs2, paths.headroomSessionsDir, ports.isRunning);
|
|
3327
|
+
const ownsNow = ownsState();
|
|
3328
|
+
if (!ownsNow && !superseded) {
|
|
3329
|
+
ports.log("claude-use headroom supervisor: another supervisor now owns state.json; serving this daemon's own sessions until they end");
|
|
3330
|
+
}
|
|
3331
|
+
superseded = !ownsNow;
|
|
3310
3332
|
const crashed = headroomPid !== void 0 && !ports.isRunning(headroomPid);
|
|
3311
3333
|
if (headroomPid === void 0 || crashed) {
|
|
3312
3334
|
if (crashed) {
|
|
@@ -3314,7 +3336,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3314
3336
|
headroomPid = void 0;
|
|
3315
3337
|
runningHash = void 0;
|
|
3316
3338
|
runningSettingsHash = void 0;
|
|
3317
|
-
|
|
3339
|
+
writeOwnState({
|
|
3318
3340
|
supervisorPid: ports.ownPid,
|
|
3319
3341
|
version,
|
|
3320
3342
|
installedSource,
|
|
@@ -3342,7 +3364,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3342
3364
|
`claude-use headroom supervisor: sticky port ${String(previousSticky)} was occupied; moving to ${String(port)} (sessions launched against the old port are stale until they relaunch)`
|
|
3343
3365
|
);
|
|
3344
3366
|
}
|
|
3345
|
-
|
|
3367
|
+
const recorded = writeOwnState({
|
|
3346
3368
|
supervisorPid: ports.ownPid,
|
|
3347
3369
|
headroomPid: pid,
|
|
3348
3370
|
port,
|
|
@@ -3352,7 +3374,9 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3352
3374
|
installedSource: config.source,
|
|
3353
3375
|
...sticky()
|
|
3354
3376
|
});
|
|
3355
|
-
|
|
3377
|
+
if (recorded) {
|
|
3378
|
+
fs2.removeRecursive(paths.headroomLockFile);
|
|
3379
|
+
}
|
|
3356
3380
|
idleSince = void 0;
|
|
3357
3381
|
ports.log(`claude-use headroom supervisor: headroom pid ${String(pid)} ready on 127.0.0.1:${String(port)}`);
|
|
3358
3382
|
} else {
|
|
@@ -3366,7 +3390,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3366
3390
|
}
|
|
3367
3391
|
} else {
|
|
3368
3392
|
if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
|
|
3369
|
-
if (
|
|
3393
|
+
if (sessionsForSupervisor(fs2, paths.headroomSessionsDir, ports.ownPid).length === 0) {
|
|
3370
3394
|
ports.log("claude-use headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
|
|
3371
3395
|
ports.stopProcess(headroomPid);
|
|
3372
3396
|
headroomPid = void 0;
|
|
@@ -3375,7 +3399,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3375
3399
|
continue;
|
|
3376
3400
|
}
|
|
3377
3401
|
}
|
|
3378
|
-
const sessions =
|
|
3402
|
+
const sessions = sessionsForSupervisor(fs2, paths.headroomSessionsDir, ports.ownPid);
|
|
3379
3403
|
if (sessions.length === 0) {
|
|
3380
3404
|
idleSince ??= ports.now();
|
|
3381
3405
|
const idleMs = ports.now() - idleSince;
|
|
@@ -3384,7 +3408,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3384
3408
|
`claude-use headroom supervisor: no sessions for ${String(config.idleShutdownMinutes)} minute(s); stopping headroom and exiting`
|
|
3385
3409
|
);
|
|
3386
3410
|
ports.stopProcess(headroomPid);
|
|
3387
|
-
|
|
3411
|
+
writeOwnState({
|
|
3388
3412
|
version,
|
|
3389
3413
|
...sticky()
|
|
3390
3414
|
});
|
|
@@ -20,10 +20,13 @@ export declare const HeadroomStateSchema: z.ZodObject<{
|
|
|
20
20
|
export type HeadroomState = z.infer<typeof HeadroomStateSchema>;
|
|
21
21
|
/** The filesystem operations headroom coordination needs: the same shape as `FarmFs`, injected so every decision over state, sessions, and the start lock runs against an in-memory fake in tests. */
|
|
22
22
|
export type HeadroomFs = Pick<FarmFs, "mkdirp" | "mkdirPrivate" | "readFileUtf8" | "writeFileUtf8" | "writeFilePrivate" | "writeFileExclusive" | "readdir" | "removeRecursive">;
|
|
23
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* The record held in one session-registry file. `supervisorPid` names the supervisor whose daemon the launch connected to: several supervisors can be alive at once (a superseded one keeps serving the sessions it already has), and each must judge idle shutdown and drift restarts by its own sessions only, or none would ever retire.
|
|
25
|
+
*/
|
|
24
26
|
declare const HeadroomSessionSchema: z.ZodObject<{
|
|
25
27
|
pid: z.ZodNumber;
|
|
26
28
|
startedAt: z.ZodNumber;
|
|
29
|
+
supervisorPid: z.ZodNumber;
|
|
27
30
|
}, z.core.$strict>;
|
|
28
31
|
export type HeadroomSession = z.infer<typeof HeadroomSessionSchema>;
|
|
29
32
|
/** The exclusive-create start lock's recorded holder. */
|
|
@@ -63,6 +66,8 @@ export declare function writeSession(fs: HeadroomFs, sessionsDir: string, sessio
|
|
|
63
66
|
export declare function removeSession(fs: HeadroomFs, sessionsDir: string, pid: number): void;
|
|
64
67
|
/** Every registry entry on disk, unreadable files skipped, sorted by pid: what `headroom status` prints and what the supervisor's idle and drift decisions read. */
|
|
65
68
|
export declare function listSessions(fs: HeadroomFs, sessionsDir: string): readonly HeadroomSession[];
|
|
69
|
+
/** The registry entries recorded against one supervisor: the sessions whose daemon that supervisor is responsible for keeping up. */
|
|
70
|
+
export declare function sessionsForSupervisor(fs: HeadroomFs, sessionsDir: string, supervisorPid: number): readonly HeadroomSession[];
|
|
66
71
|
/**
|
|
67
72
|
* Removes every registry entry whose pid is no longer running: a launcher that died without releasing its entry must not keep the daemon awake or block a drift restart forever. The predicate must be zombie-aware (see `realIsProcessRunning`): a launcher that exited but was never reaped still answers signal 0 as alive, which would keep its session registered indefinitely.
|
|
68
73
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-use",
|
|
3
|
-
"version": "6.2.
|
|
3
|
+
"version": "6.2.1",
|
|
4
4
|
"description": "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Joseph Mearman <joseph@mearman.co.uk>",
|