claude-use 5.0.0 → 5.0.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 +102 -16
- package/dist/index.cjs +71 -6
- package/dist/index.mjs +71 -6
- package/dist/types/config/schema.d.ts +39 -0
- package/dist/types/headroom/settings.d.ts +13 -0
- package/dist/types/headroom/state.d.ts +1 -0
- package/dist/types/headroom/supervisor.d.ts +9 -6
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -285659,11 +285659,36 @@ var PortableConfigSchema = ConfigProfileSchema.omit({ description: true }).exten
|
|
|
285659
285659
|
identity: external_exports.string().min(1).optional(),
|
|
285660
285660
|
when: WhenSchema.optional()
|
|
285661
285661
|
});
|
|
285662
|
-
var
|
|
285662
|
+
var HEADROOM_MODES = ["cache", "token"];
|
|
285663
|
+
var HEADROOM_CCR_MODES = ["default", "lossless", "none"];
|
|
285664
|
+
var HEADROOM_ROLLOUT_CHANNELS = ["beta", "canary", "dev"];
|
|
285665
|
+
var INTERCEPT_CHANNELS = ["canary", "dev"];
|
|
285666
|
+
var READ_MATURATION_CHANNELS = ["beta", "dev"];
|
|
285667
|
+
var HeadroomGlobalConfigObjectSchema = external_exports.strictObject({
|
|
285663
285668
|
/** Install spec handed to `uv tool install`. Defaults to HEADROOM_DEFAULT_SOURCE. */
|
|
285664
285669
|
source: external_exports.string().min(1).optional(),
|
|
285665
285670
|
/** How long the daemon may sit with no registered sessions before the supervisor stops it. Defaults to HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES. */
|
|
285666
|
-
idleShutdownMinutes: external_exports.number().int().positive().optional()
|
|
285671
|
+
idleShutdownMinutes: external_exports.number().int().positive().optional(),
|
|
285672
|
+
/** Optimisation mode, passed as `--mode`. Unset leaves headroom's own default (`cache`). */
|
|
285673
|
+
mode: external_exports.enum(HEADROOM_MODES).optional(),
|
|
285674
|
+
/** Keep-ratio for prose and code compression, passed as `--target-ratio`: lower is more aggressive. Unset lets headroom decide. */
|
|
285675
|
+
targetRatio: external_exports.number().gt(0).max(1).optional(),
|
|
285676
|
+
/** How compressed tool output stays recoverable. Unset leaves headroom's own default (`default`). */
|
|
285677
|
+
ccr: external_exports.enum(HEADROOM_CCR_MODES).optional(),
|
|
285678
|
+
/** Headroom's experimental release channel, exported as `HEADROOM_ROLLOUT_CHANNEL`. Required by `interceptToolResults` and `readMaturation`, which are refused without a channel that unlocks them. */
|
|
285679
|
+
rolloutChannel: external_exports.enum(HEADROOM_ROLLOUT_CHANNELS).optional(),
|
|
285680
|
+
/** Opt in to headroom's tool-result interceptors (`--intercept-tool-results`). Needs `rolloutChannel` canary or dev. */
|
|
285681
|
+
interceptToolResults: external_exports.boolean().optional(),
|
|
285682
|
+
/** Opt in to headroom's experimental read maturation (`--read-maturation`). Needs `rolloutChannel` beta or dev. */
|
|
285683
|
+
readMaturation: external_exports.boolean().optional()
|
|
285684
|
+
});
|
|
285685
|
+
var HeadroomGlobalConfigSchema = HeadroomGlobalConfigObjectSchema.superRefine((config2, context) => {
|
|
285686
|
+
if (config2.interceptToolResults === true && !INTERCEPT_CHANNELS.includes(config2.rolloutChannel ?? "")) {
|
|
285687
|
+
context.addIssue({ code: "custom", path: ["interceptToolResults"], message: "interceptToolResults needs rolloutChannel canary or dev" });
|
|
285688
|
+
}
|
|
285689
|
+
if (config2.readMaturation === true && !READ_MATURATION_CHANNELS.includes(config2.rolloutChannel ?? "")) {
|
|
285690
|
+
context.addIssue({ code: "custom", path: ["readMaturation"], message: "readMaturation needs rolloutChannel beta or dev" });
|
|
285691
|
+
}
|
|
285667
285692
|
});
|
|
285668
285693
|
var HEADROOM_DEFAULT_SOURCE = "headroom-ai[proxy] @ git+https://github.com/ExaDev/headroom@eb02fa4126450c9905a49394fdec19ae2e7c9c30";
|
|
285669
285694
|
var HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES = 15;
|
|
@@ -285730,6 +285755,8 @@ var HeadroomStateSchema = external_exports.strictObject({
|
|
|
285730
285755
|
version: external_exports.string().optional(),
|
|
285731
285756
|
/** Hash of the allowlist the running proxy was started with, so a provider-file change is detected as drift. */
|
|
285732
285757
|
allowlistHash: external_exports.string().optional(),
|
|
285758
|
+
/** Hash of the token-saving settings the running proxy was started with, so a settings change is detected as drift. */
|
|
285759
|
+
settingsHash: external_exports.string().optional(),
|
|
285733
285760
|
/** The install spec the running install was last installed from, so a `source` config change is detected as drift. */
|
|
285734
285761
|
installedSource: external_exports.string().optional(),
|
|
285735
285762
|
/** The last fatal error, kept across shutdowns so `headroom status` can explain a daemon that is not running. */
|
|
@@ -291234,7 +291261,7 @@ function resolveLaunchIdentity(paths, cwd) {
|
|
|
291234
291261
|
// package.json
|
|
291235
291262
|
var package_default = {
|
|
291236
291263
|
name: "claude-use",
|
|
291237
|
-
version: "5.0.
|
|
291264
|
+
version: "5.0.1",
|
|
291238
291265
|
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.",
|
|
291239
291266
|
license: "Apache-2.0",
|
|
291240
291267
|
author: "Joseph Mearman <joseph@mearman.co.uk>",
|
|
@@ -296096,11 +296123,41 @@ var import_node_fs14 = __toESM(require("node:fs"), 1);
|
|
|
296096
296123
|
var import_node_net2 = __toESM(require("node:net"), 1);
|
|
296097
296124
|
var import_node_child_process5 = require("node:child_process");
|
|
296098
296125
|
|
|
296126
|
+
// src/headroom/settings.ts
|
|
296127
|
+
var import_node_crypto12 = require("node:crypto");
|
|
296128
|
+
function settingsOf(config2) {
|
|
296129
|
+
return {
|
|
296130
|
+
...config2.mode === void 0 ? {} : { mode: config2.mode },
|
|
296131
|
+
...config2.targetRatio === void 0 ? {} : { targetRatio: config2.targetRatio },
|
|
296132
|
+
...config2.ccr === void 0 ? {} : { ccr: config2.ccr },
|
|
296133
|
+
...config2.rolloutChannel === void 0 ? {} : { rolloutChannel: config2.rolloutChannel },
|
|
296134
|
+
...config2.interceptToolResults === void 0 ? {} : { interceptToolResults: config2.interceptToolResults },
|
|
296135
|
+
...config2.readMaturation === void 0 ? {} : { readMaturation: config2.readMaturation }
|
|
296136
|
+
};
|
|
296137
|
+
}
|
|
296138
|
+
function settingsArgs(settings2) {
|
|
296139
|
+
return [
|
|
296140
|
+
...settings2.mode === void 0 ? [] : ["--mode", settings2.mode],
|
|
296141
|
+
...settings2.targetRatio === void 0 ? [] : ["--target-ratio", String(settings2.targetRatio)],
|
|
296142
|
+
...settings2.ccr === "lossless" ? ["--lossless"] : [],
|
|
296143
|
+
...settings2.ccr === "none" ? ["--no-ccr"] : [],
|
|
296144
|
+
...settings2.interceptToolResults === true ? ["--intercept-tool-results"] : [],
|
|
296145
|
+
...settings2.readMaturation === true ? ["--read-maturation"] : []
|
|
296146
|
+
];
|
|
296147
|
+
}
|
|
296148
|
+
function settingsEnv(settings2) {
|
|
296149
|
+
return settings2.rolloutChannel === void 0 ? {} : { HEADROOM_ROLLOUT_CHANNEL: settings2.rolloutChannel };
|
|
296150
|
+
}
|
|
296151
|
+
function hashSettings(settings2) {
|
|
296152
|
+
return (0, import_node_crypto12.createHash)("sha256").update(JSON.stringify([...settingsArgs(settings2), JSON.stringify(settingsEnv(settings2))])).digest("hex");
|
|
296153
|
+
}
|
|
296154
|
+
|
|
296099
296155
|
// src/headroom/supervisor.ts
|
|
296100
296156
|
function resolveSupervisorConfig(configured) {
|
|
296101
296157
|
return {
|
|
296102
296158
|
source: configured.source ?? HEADROOM_DEFAULT_SOURCE,
|
|
296103
|
-
idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES
|
|
296159
|
+
idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES,
|
|
296160
|
+
settings: settingsOf(configured)
|
|
296104
296161
|
};
|
|
296105
296162
|
}
|
|
296106
296163
|
var HEADROOM_BACKOFF_BASE_MS = 500;
|
|
@@ -296193,8 +296250,9 @@ function compareVersions2(a2, b) {
|
|
|
296193
296250
|
return 0;
|
|
296194
296251
|
}
|
|
296195
296252
|
var HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
|
|
296196
|
-
async function runSupervisor(
|
|
296253
|
+
async function runSupervisor(initialConfig, ports, options = {}) {
|
|
296197
296254
|
const { fs: fs16, paths } = ports;
|
|
296255
|
+
let config2 = initialConfig;
|
|
296198
296256
|
const previousState = readHeadroomState(fs16, paths.headroomStateFile);
|
|
296199
296257
|
let installedSource = previousState?.installedSource;
|
|
296200
296258
|
let lastPort = previousState?.lastPort;
|
|
@@ -296236,6 +296294,7 @@ async function runSupervisor(config2, ports, options = {}) {
|
|
|
296236
296294
|
}
|
|
296237
296295
|
let headroomPid;
|
|
296238
296296
|
let runningHash;
|
|
296297
|
+
let runningSettingsHash;
|
|
296239
296298
|
let consecutiveFailures = 0;
|
|
296240
296299
|
let idleSince;
|
|
296241
296300
|
let ticks = 0;
|
|
@@ -296244,6 +296303,8 @@ async function runSupervisor(config2, ports, options = {}) {
|
|
|
296244
296303
|
return HEADROOM_SUPERVISOR_STILL_RUNNING;
|
|
296245
296304
|
}
|
|
296246
296305
|
ticks += 1;
|
|
296306
|
+
config2 = ports.readConfig();
|
|
296307
|
+
const settingsHash = hashSettings(config2.settings);
|
|
296247
296308
|
const allowlist = allowlistOf(ports);
|
|
296248
296309
|
const allowlistHash = hashAllowlist(allowlist);
|
|
296249
296310
|
pruneDeadSessions(fs16, paths.headroomSessionsDir, ports.isRunning);
|
|
@@ -296253,6 +296314,7 @@ async function runSupervisor(config2, ports, options = {}) {
|
|
|
296253
296314
|
ports.log(`claude-use headroom supervisor: headroom pid ${String(headroomPid)} died`);
|
|
296254
296315
|
headroomPid = void 0;
|
|
296255
296316
|
runningHash = void 0;
|
|
296317
|
+
runningSettingsHash = void 0;
|
|
296256
296318
|
writeHeadroomState(fs16, paths.headroomStateFile, {
|
|
296257
296319
|
supervisorPid: ports.ownPid,
|
|
296258
296320
|
version: version2,
|
|
@@ -296268,12 +296330,13 @@ async function runSupervisor(config2, ports, options = {}) {
|
|
|
296268
296330
|
}
|
|
296269
296331
|
const previousSticky = lastPort;
|
|
296270
296332
|
const port = lastPort !== void 0 && await ports.isPortFree(lastPort) ? lastPort : await ports.freePort();
|
|
296271
|
-
const pid = ports.spawnHeadroom(port, allowlist);
|
|
296333
|
+
const pid = ports.spawnHeadroom(port, allowlist, config2.settings);
|
|
296272
296334
|
const ready = await waitUntilReady(ports, port);
|
|
296273
296335
|
if (ready) {
|
|
296274
296336
|
consecutiveFailures = 0;
|
|
296275
296337
|
headroomPid = pid;
|
|
296276
296338
|
runningHash = allowlistHash;
|
|
296339
|
+
runningSettingsHash = settingsHash;
|
|
296277
296340
|
lastPort = port;
|
|
296278
296341
|
if (previousSticky !== void 0 && port !== previousSticky) {
|
|
296279
296342
|
ports.log(
|
|
@@ -296286,6 +296349,7 @@ async function runSupervisor(config2, ports, options = {}) {
|
|
|
296286
296349
|
port,
|
|
296287
296350
|
version: version2,
|
|
296288
296351
|
allowlistHash,
|
|
296352
|
+
settingsHash,
|
|
296289
296353
|
installedSource: config2.source,
|
|
296290
296354
|
...sticky()
|
|
296291
296355
|
});
|
|
@@ -296302,12 +296366,13 @@ async function runSupervisor(config2, ports, options = {}) {
|
|
|
296302
296366
|
continue;
|
|
296303
296367
|
}
|
|
296304
296368
|
} else {
|
|
296305
|
-
if (runningHash !== allowlistHash || installedSource !== config2.source) {
|
|
296369
|
+
if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config2.source) {
|
|
296306
296370
|
if (listSessions(fs16, paths.headroomSessionsDir).length === 0) {
|
|
296307
296371
|
ports.log("claude-use headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
|
|
296308
296372
|
ports.stopProcess(headroomPid);
|
|
296309
296373
|
headroomPid = void 0;
|
|
296310
296374
|
runningHash = void 0;
|
|
296375
|
+
runningSettingsHash = void 0;
|
|
296311
296376
|
continue;
|
|
296312
296377
|
}
|
|
296313
296378
|
}
|
|
@@ -296357,6 +296422,7 @@ async function waitUntilReady(ports, port) {
|
|
|
296357
296422
|
function collectHeadroomStatus(fsPort, paths, isRunning) {
|
|
296358
296423
|
const state = readHeadroomState(fsPort, paths.headroomStateFile) ?? {};
|
|
296359
296424
|
const allowlist = currentHeadroomAllowlist(fsPort, paths);
|
|
296425
|
+
const settings2 = settingsOf(readGlobalConfig(paths)?.headroom ?? {});
|
|
296360
296426
|
return {
|
|
296361
296427
|
state,
|
|
296362
296428
|
supervisorAlive: state.supervisorPid !== void 0 && isRunning(state.supervisorPid),
|
|
@@ -296367,6 +296433,8 @@ function collectHeadroomStatus(fsPort, paths, isRunning) {
|
|
|
296367
296433
|
})),
|
|
296368
296434
|
allowlist,
|
|
296369
296435
|
allowlistDrifted: state.allowlistHash !== void 0 && state.allowlistHash !== hashAllowlist(allowlist),
|
|
296436
|
+
settings: settings2,
|
|
296437
|
+
settingsDrifted: state.settingsHash !== void 0 && state.settingsHash !== hashSettings(settings2),
|
|
296370
296438
|
logPath: paths.headroomLogPath,
|
|
296371
296439
|
logExists: fsPort.readFileUtf8(paths.headroomLogPath) !== void 0
|
|
296372
296440
|
};
|
|
@@ -296389,6 +296457,9 @@ function formatHeadroomStatus(status) {
|
|
|
296389
296457
|
}
|
|
296390
296458
|
const drift = status.allowlistDrifted ? " (DRIFTED: current provider files differ from the running daemon's allowlist; a restart is pending)" : "";
|
|
296391
296459
|
lines.push(`allowlist: ${status.allowlist.join(", ")}${drift}`);
|
|
296460
|
+
const configured = Object.entries(status.settings).map(([name, value]) => `${name} ${String(value)}`);
|
|
296461
|
+
const settingsDrift = status.settingsDrifted ? " (DRIFTED: the configured settings differ from the running daemon's; a restart is pending)" : "";
|
|
296462
|
+
lines.push(`settings: ${configured.length === 0 ? "headroom defaults" : configured.join(", ")}${settingsDrift}`);
|
|
296392
296463
|
if (status.sessions.length === 0) {
|
|
296393
296464
|
lines.push("sessions: none");
|
|
296394
296465
|
} else {
|
|
@@ -296425,11 +296496,12 @@ var READY_FETCH_TIMEOUT_MS = 2e3;
|
|
|
296425
296496
|
function headroomPidRunning(pid) {
|
|
296426
296497
|
return !exitedHeadroom.has(pid) && realIsProcessRunning(pid);
|
|
296427
296498
|
}
|
|
296428
|
-
function headroomSpawnEnv(parentEnv, allowlist) {
|
|
296499
|
+
function headroomSpawnEnv(parentEnv, allowlist, settings2) {
|
|
296429
296500
|
return {
|
|
296430
296501
|
...parentEnv,
|
|
296431
296502
|
HEADROOM_ALLOWED_BASE_URLS: allowlist.join(","),
|
|
296432
|
-
HEADROOM_HTTP2: parentEnv.HEADROOM_HTTP2 ?? "0"
|
|
296503
|
+
HEADROOM_HTTP2: parentEnv.HEADROOM_HTTP2 ?? "0",
|
|
296504
|
+
...settingsEnv(settings2)
|
|
296433
296505
|
};
|
|
296434
296506
|
}
|
|
296435
296507
|
function realSupervisorPorts(paths) {
|
|
@@ -296447,14 +296519,15 @@ function realSupervisorPorts(paths) {
|
|
|
296447
296519
|
isRunning: headroomPidRunning,
|
|
296448
296520
|
freePort: realFreePort,
|
|
296449
296521
|
isPortFree: realIsPortFree,
|
|
296450
|
-
|
|
296522
|
+
readConfig: () => resolveSupervisorConfig(readGlobalConfig(paths)?.headroom ?? {}),
|
|
296523
|
+
spawnHeadroom: (port, allowlist, settings2) => {
|
|
296451
296524
|
import_node_fs14.default.mkdirSync(paths.logsDir, { recursive: true });
|
|
296452
296525
|
const logFd = import_node_fs14.default.openSync(paths.headroomLogPath, "a");
|
|
296453
296526
|
try {
|
|
296454
|
-
const child = (0, import_node_child_process5.spawn)("headroom", ["proxy", "--host", "127.0.0.1", "--port", String(port)], {
|
|
296527
|
+
const child = (0, import_node_child_process5.spawn)("headroom", ["proxy", "--host", "127.0.0.1", "--port", String(port), ...settingsArgs(settings2)], {
|
|
296455
296528
|
detached: true,
|
|
296456
296529
|
stdio: ["ignore", logFd, logFd],
|
|
296457
|
-
env: headroomSpawnEnv(process.env, allowlist)
|
|
296530
|
+
env: headroomSpawnEnv(process.env, allowlist, settings2)
|
|
296458
296531
|
});
|
|
296459
296532
|
if (child.pid === void 0) {
|
|
296460
296533
|
throw new Error("spawning headroom returned no pid");
|
|
@@ -296625,10 +296698,10 @@ function buildProgram(deps) {
|
|
|
296625
296698
|
|
|
296626
296699
|
// src/runClaude.ts
|
|
296627
296700
|
var import_node_os8 = __toESM(require("node:os"), 1);
|
|
296628
|
-
var
|
|
296701
|
+
var import_node_crypto14 = require("node:crypto");
|
|
296629
296702
|
|
|
296630
296703
|
// src/launcher.ts
|
|
296631
|
-
var
|
|
296704
|
+
var import_node_crypto13 = require("node:crypto");
|
|
296632
296705
|
var import_node_path29 = __toESM(require("node:path"), 1);
|
|
296633
296706
|
|
|
296634
296707
|
// src/launcher/cliOverride.ts
|
|
@@ -296768,6 +296841,15 @@ function buildFlagArgs(flags) {
|
|
|
296768
296841
|
function buildArgv(params) {
|
|
296769
296842
|
return [...params.toolFlags, ...params.extraFlags, ...params.passthrough];
|
|
296770
296843
|
}
|
|
296844
|
+
var LOOPBACK_HOSTS = ["127.0.0.1", "localhost", "::1"];
|
|
296845
|
+
function isLoopbackUrl(baseUrl) {
|
|
296846
|
+
const host = new URL(baseUrl).hostname.replace(/^\[|\]$/g, "");
|
|
296847
|
+
return LOOPBACK_HOSTS.some((loopback) => loopback === host);
|
|
296848
|
+
}
|
|
296849
|
+
function withLoopbackHosts(existing) {
|
|
296850
|
+
const listed = (existing ?? "").split(",").map((entry) => entry.trim()).filter((entry) => entry !== "");
|
|
296851
|
+
return [...listed, ...LOOPBACK_HOSTS.filter((host) => !listed.includes(host))].join(",");
|
|
296852
|
+
}
|
|
296771
296853
|
function buildEnv(params) {
|
|
296772
296854
|
const env = { ...params.baseEnv };
|
|
296773
296855
|
const parentCustomHeaders = params.baseEnv.ANTHROPIC_CUSTOM_HEADERS;
|
|
@@ -296779,6 +296861,10 @@ function buildEnv(params) {
|
|
|
296779
296861
|
const providerEnv = params.provider.definition.env ?? {};
|
|
296780
296862
|
providerCustomHeaders = providerEnv.ANTHROPIC_CUSTOM_HEADERS;
|
|
296781
296863
|
env.ANTHROPIC_BASE_URL = params.provider.baseUrl;
|
|
296864
|
+
if (isLoopbackUrl(params.provider.baseUrl)) {
|
|
296865
|
+
env.NO_PROXY = withLoopbackHosts(params.baseEnv.NO_PROXY);
|
|
296866
|
+
env.no_proxy = withLoopbackHosts(params.baseEnv.no_proxy);
|
|
296867
|
+
}
|
|
296782
296868
|
env.CLAUDE_USE_PROVIDER = params.provider.definition.displayName;
|
|
296783
296869
|
for (const [key, value] of Object.entries(providerEnv)) {
|
|
296784
296870
|
env[key] = value;
|
|
@@ -297067,7 +297153,7 @@ function runLauncher(params) {
|
|
|
297067
297153
|
...identityCredential === void 0 ? {} : { identityCredential },
|
|
297068
297154
|
...frontDoor === void 0 ? {} : { frontdoor: frontDoor },
|
|
297069
297155
|
...headroom === void 0 ? {} : { headroom },
|
|
297070
|
-
sessionId: (0,
|
|
297156
|
+
sessionId: (0, import_node_crypto13.randomUUID)()
|
|
297071
297157
|
});
|
|
297072
297158
|
const releases = [...headroom === void 0 || headroomPort === void 0 ? [] : [headroomPort.release], ...frontDoorRelease === void 0 ? [] : [frontDoorRelease]];
|
|
297073
297159
|
if (releases.length === 0) {
|
|
@@ -297119,7 +297205,7 @@ function buildFarmRuntime(paths) {
|
|
|
297119
297205
|
...cliOverride === void 0 ? {} : { cliOverride }
|
|
297120
297206
|
}).input,
|
|
297121
297207
|
now: () => Date.now(),
|
|
297122
|
-
uniqueSuffix: `${String(process.pid)}.${(0,
|
|
297208
|
+
uniqueSuffix: `${String(process.pid)}.${(0, import_node_crypto14.randomUUID)()}`,
|
|
297123
297209
|
// Zombie-aware on purpose: a previous launcher that crashed out of a resync without releasing the lock may sit unreaped, still answering signal 0 as alive, and must read as a dead holder so this launch takes the lock over instead of timing out.
|
|
297124
297210
|
lock: { pid: process.pid, isRunning: realIsProcessRunning, sleep: realSleepSync }
|
|
297125
297211
|
},
|
package/dist/index.cjs
CHANGED
|
@@ -224,11 +224,36 @@ var PortableConfigSchema = ConfigProfileSchema.omit({ description: true }).exten
|
|
|
224
224
|
identity: import_zod.z.string().min(1).optional(),
|
|
225
225
|
when: WhenSchema.optional()
|
|
226
226
|
});
|
|
227
|
-
var
|
|
227
|
+
var HEADROOM_MODES = ["cache", "token"];
|
|
228
|
+
var HEADROOM_CCR_MODES = ["default", "lossless", "none"];
|
|
229
|
+
var HEADROOM_ROLLOUT_CHANNELS = ["beta", "canary", "dev"];
|
|
230
|
+
var INTERCEPT_CHANNELS = ["canary", "dev"];
|
|
231
|
+
var READ_MATURATION_CHANNELS = ["beta", "dev"];
|
|
232
|
+
var HeadroomGlobalConfigObjectSchema = import_zod.z.strictObject({
|
|
228
233
|
/** Install spec handed to `uv tool install`. Defaults to HEADROOM_DEFAULT_SOURCE. */
|
|
229
234
|
source: import_zod.z.string().min(1).optional(),
|
|
230
235
|
/** How long the daemon may sit with no registered sessions before the supervisor stops it. Defaults to HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES. */
|
|
231
|
-
idleShutdownMinutes: import_zod.z.number().int().positive().optional()
|
|
236
|
+
idleShutdownMinutes: import_zod.z.number().int().positive().optional(),
|
|
237
|
+
/** Optimisation mode, passed as `--mode`. Unset leaves headroom's own default (`cache`). */
|
|
238
|
+
mode: import_zod.z.enum(HEADROOM_MODES).optional(),
|
|
239
|
+
/** Keep-ratio for prose and code compression, passed as `--target-ratio`: lower is more aggressive. Unset lets headroom decide. */
|
|
240
|
+
targetRatio: import_zod.z.number().gt(0).max(1).optional(),
|
|
241
|
+
/** How compressed tool output stays recoverable. Unset leaves headroom's own default (`default`). */
|
|
242
|
+
ccr: import_zod.z.enum(HEADROOM_CCR_MODES).optional(),
|
|
243
|
+
/** Headroom's experimental release channel, exported as `HEADROOM_ROLLOUT_CHANNEL`. Required by `interceptToolResults` and `readMaturation`, which are refused without a channel that unlocks them. */
|
|
244
|
+
rolloutChannel: import_zod.z.enum(HEADROOM_ROLLOUT_CHANNELS).optional(),
|
|
245
|
+
/** Opt in to headroom's tool-result interceptors (`--intercept-tool-results`). Needs `rolloutChannel` canary or dev. */
|
|
246
|
+
interceptToolResults: import_zod.z.boolean().optional(),
|
|
247
|
+
/** Opt in to headroom's experimental read maturation (`--read-maturation`). Needs `rolloutChannel` beta or dev. */
|
|
248
|
+
readMaturation: import_zod.z.boolean().optional()
|
|
249
|
+
});
|
|
250
|
+
var HeadroomGlobalConfigSchema = HeadroomGlobalConfigObjectSchema.superRefine((config, context) => {
|
|
251
|
+
if (config.interceptToolResults === true && !INTERCEPT_CHANNELS.includes(config.rolloutChannel ?? "")) {
|
|
252
|
+
context.addIssue({ code: "custom", path: ["interceptToolResults"], message: "interceptToolResults needs rolloutChannel canary or dev" });
|
|
253
|
+
}
|
|
254
|
+
if (config.readMaturation === true && !READ_MATURATION_CHANNELS.includes(config.rolloutChannel ?? "")) {
|
|
255
|
+
context.addIssue({ code: "custom", path: ["readMaturation"], message: "readMaturation needs rolloutChannel beta or dev" });
|
|
256
|
+
}
|
|
232
257
|
});
|
|
233
258
|
var HEADROOM_DEFAULT_SOURCE = "headroom-ai[proxy] @ git+https://github.com/ExaDev/headroom@eb02fa4126450c9905a49394fdec19ae2e7c9c30";
|
|
234
259
|
var HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES = 15;
|
|
@@ -2531,6 +2556,8 @@ var HeadroomStateSchema = import_zod4.z.strictObject({
|
|
|
2531
2556
|
version: import_zod4.z.string().optional(),
|
|
2532
2557
|
/** Hash of the allowlist the running proxy was started with, so a provider-file change is detected as drift. */
|
|
2533
2558
|
allowlistHash: import_zod4.z.string().optional(),
|
|
2559
|
+
/** Hash of the token-saving settings the running proxy was started with, so a settings change is detected as drift. */
|
|
2560
|
+
settingsHash: import_zod4.z.string().optional(),
|
|
2534
2561
|
/** The install spec the running install was last installed from, so a `source` config change is detected as drift. */
|
|
2535
2562
|
installedSource: import_zod4.z.string().optional(),
|
|
2536
2563
|
/** The last fatal error, kept across shutdowns so `headroom status` can explain a daemon that is not running. */
|
|
@@ -2749,11 +2776,41 @@ function ensureHeadroom(params) {
|
|
|
2749
2776
|
}
|
|
2750
2777
|
}
|
|
2751
2778
|
|
|
2779
|
+
// src/headroom/settings.ts
|
|
2780
|
+
var import_node_crypto4 = require("node:crypto");
|
|
2781
|
+
function settingsOf(config) {
|
|
2782
|
+
return {
|
|
2783
|
+
...config.mode === void 0 ? {} : { mode: config.mode },
|
|
2784
|
+
...config.targetRatio === void 0 ? {} : { targetRatio: config.targetRatio },
|
|
2785
|
+
...config.ccr === void 0 ? {} : { ccr: config.ccr },
|
|
2786
|
+
...config.rolloutChannel === void 0 ? {} : { rolloutChannel: config.rolloutChannel },
|
|
2787
|
+
...config.interceptToolResults === void 0 ? {} : { interceptToolResults: config.interceptToolResults },
|
|
2788
|
+
...config.readMaturation === void 0 ? {} : { readMaturation: config.readMaturation }
|
|
2789
|
+
};
|
|
2790
|
+
}
|
|
2791
|
+
function settingsArgs(settings) {
|
|
2792
|
+
return [
|
|
2793
|
+
...settings.mode === void 0 ? [] : ["--mode", settings.mode],
|
|
2794
|
+
...settings.targetRatio === void 0 ? [] : ["--target-ratio", String(settings.targetRatio)],
|
|
2795
|
+
...settings.ccr === "lossless" ? ["--lossless"] : [],
|
|
2796
|
+
...settings.ccr === "none" ? ["--no-ccr"] : [],
|
|
2797
|
+
...settings.interceptToolResults === true ? ["--intercept-tool-results"] : [],
|
|
2798
|
+
...settings.readMaturation === true ? ["--read-maturation"] : []
|
|
2799
|
+
];
|
|
2800
|
+
}
|
|
2801
|
+
function settingsEnv(settings) {
|
|
2802
|
+
return settings.rolloutChannel === void 0 ? {} : { HEADROOM_ROLLOUT_CHANNEL: settings.rolloutChannel };
|
|
2803
|
+
}
|
|
2804
|
+
function hashSettings(settings) {
|
|
2805
|
+
return (0, import_node_crypto4.createHash)("sha256").update(JSON.stringify([...settingsArgs(settings), JSON.stringify(settingsEnv(settings))])).digest("hex");
|
|
2806
|
+
}
|
|
2807
|
+
|
|
2752
2808
|
// src/headroom/supervisor.ts
|
|
2753
2809
|
function resolveSupervisorConfig(configured) {
|
|
2754
2810
|
return {
|
|
2755
2811
|
source: configured.source ?? HEADROOM_DEFAULT_SOURCE,
|
|
2756
|
-
idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES
|
|
2812
|
+
idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES,
|
|
2813
|
+
settings: settingsOf(configured)
|
|
2757
2814
|
};
|
|
2758
2815
|
}
|
|
2759
2816
|
var HEADROOM_BACKOFF_BASE_MS = 500;
|
|
@@ -2816,8 +2873,9 @@ function compareVersions(a, b) {
|
|
|
2816
2873
|
return 0;
|
|
2817
2874
|
}
|
|
2818
2875
|
var HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
|
|
2819
|
-
async function runSupervisor(
|
|
2876
|
+
async function runSupervisor(initialConfig, ports, options = {}) {
|
|
2820
2877
|
const { fs: fs2, paths } = ports;
|
|
2878
|
+
let config = initialConfig;
|
|
2821
2879
|
const previousState = readHeadroomState(fs2, paths.headroomStateFile);
|
|
2822
2880
|
let installedSource = previousState?.installedSource;
|
|
2823
2881
|
let lastPort = previousState?.lastPort;
|
|
@@ -2859,6 +2917,7 @@ async function runSupervisor(config, ports, options = {}) {
|
|
|
2859
2917
|
}
|
|
2860
2918
|
let headroomPid;
|
|
2861
2919
|
let runningHash;
|
|
2920
|
+
let runningSettingsHash;
|
|
2862
2921
|
let consecutiveFailures = 0;
|
|
2863
2922
|
let idleSince;
|
|
2864
2923
|
let ticks = 0;
|
|
@@ -2867,6 +2926,8 @@ async function runSupervisor(config, ports, options = {}) {
|
|
|
2867
2926
|
return HEADROOM_SUPERVISOR_STILL_RUNNING;
|
|
2868
2927
|
}
|
|
2869
2928
|
ticks += 1;
|
|
2929
|
+
config = ports.readConfig();
|
|
2930
|
+
const settingsHash = hashSettings(config.settings);
|
|
2870
2931
|
const allowlist = allowlistOf(ports);
|
|
2871
2932
|
const allowlistHash = hashAllowlist(allowlist);
|
|
2872
2933
|
pruneDeadSessions(fs2, paths.headroomSessionsDir, ports.isRunning);
|
|
@@ -2876,6 +2937,7 @@ async function runSupervisor(config, ports, options = {}) {
|
|
|
2876
2937
|
ports.log(`claude-use headroom supervisor: headroom pid ${String(headroomPid)} died`);
|
|
2877
2938
|
headroomPid = void 0;
|
|
2878
2939
|
runningHash = void 0;
|
|
2940
|
+
runningSettingsHash = void 0;
|
|
2879
2941
|
writeHeadroomState(fs2, paths.headroomStateFile, {
|
|
2880
2942
|
supervisorPid: ports.ownPid,
|
|
2881
2943
|
version,
|
|
@@ -2891,12 +2953,13 @@ async function runSupervisor(config, ports, options = {}) {
|
|
|
2891
2953
|
}
|
|
2892
2954
|
const previousSticky = lastPort;
|
|
2893
2955
|
const port = lastPort !== void 0 && await ports.isPortFree(lastPort) ? lastPort : await ports.freePort();
|
|
2894
|
-
const pid = ports.spawnHeadroom(port, allowlist);
|
|
2956
|
+
const pid = ports.spawnHeadroom(port, allowlist, config.settings);
|
|
2895
2957
|
const ready = await waitUntilReady(ports, port);
|
|
2896
2958
|
if (ready) {
|
|
2897
2959
|
consecutiveFailures = 0;
|
|
2898
2960
|
headroomPid = pid;
|
|
2899
2961
|
runningHash = allowlistHash;
|
|
2962
|
+
runningSettingsHash = settingsHash;
|
|
2900
2963
|
lastPort = port;
|
|
2901
2964
|
if (previousSticky !== void 0 && port !== previousSticky) {
|
|
2902
2965
|
ports.log(
|
|
@@ -2909,6 +2972,7 @@ async function runSupervisor(config, ports, options = {}) {
|
|
|
2909
2972
|
port,
|
|
2910
2973
|
version,
|
|
2911
2974
|
allowlistHash,
|
|
2975
|
+
settingsHash,
|
|
2912
2976
|
installedSource: config.source,
|
|
2913
2977
|
...sticky()
|
|
2914
2978
|
});
|
|
@@ -2925,12 +2989,13 @@ async function runSupervisor(config, ports, options = {}) {
|
|
|
2925
2989
|
continue;
|
|
2926
2990
|
}
|
|
2927
2991
|
} else {
|
|
2928
|
-
if (runningHash !== allowlistHash || installedSource !== config.source) {
|
|
2992
|
+
if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
|
|
2929
2993
|
if (listSessions(fs2, paths.headroomSessionsDir).length === 0) {
|
|
2930
2994
|
ports.log("claude-use headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
|
|
2931
2995
|
ports.stopProcess(headroomPid);
|
|
2932
2996
|
headroomPid = void 0;
|
|
2933
2997
|
runningHash = void 0;
|
|
2998
|
+
runningSettingsHash = void 0;
|
|
2934
2999
|
continue;
|
|
2935
3000
|
}
|
|
2936
3001
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -155,11 +155,36 @@ var PortableConfigSchema = ConfigProfileSchema.omit({ description: true }).exten
|
|
|
155
155
|
identity: z.string().min(1).optional(),
|
|
156
156
|
when: WhenSchema.optional()
|
|
157
157
|
});
|
|
158
|
-
var
|
|
158
|
+
var HEADROOM_MODES = ["cache", "token"];
|
|
159
|
+
var HEADROOM_CCR_MODES = ["default", "lossless", "none"];
|
|
160
|
+
var HEADROOM_ROLLOUT_CHANNELS = ["beta", "canary", "dev"];
|
|
161
|
+
var INTERCEPT_CHANNELS = ["canary", "dev"];
|
|
162
|
+
var READ_MATURATION_CHANNELS = ["beta", "dev"];
|
|
163
|
+
var HeadroomGlobalConfigObjectSchema = z.strictObject({
|
|
159
164
|
/** Install spec handed to `uv tool install`. Defaults to HEADROOM_DEFAULT_SOURCE. */
|
|
160
165
|
source: z.string().min(1).optional(),
|
|
161
166
|
/** How long the daemon may sit with no registered sessions before the supervisor stops it. Defaults to HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES. */
|
|
162
|
-
idleShutdownMinutes: z.number().int().positive().optional()
|
|
167
|
+
idleShutdownMinutes: z.number().int().positive().optional(),
|
|
168
|
+
/** Optimisation mode, passed as `--mode`. Unset leaves headroom's own default (`cache`). */
|
|
169
|
+
mode: z.enum(HEADROOM_MODES).optional(),
|
|
170
|
+
/** Keep-ratio for prose and code compression, passed as `--target-ratio`: lower is more aggressive. Unset lets headroom decide. */
|
|
171
|
+
targetRatio: z.number().gt(0).max(1).optional(),
|
|
172
|
+
/** How compressed tool output stays recoverable. Unset leaves headroom's own default (`default`). */
|
|
173
|
+
ccr: z.enum(HEADROOM_CCR_MODES).optional(),
|
|
174
|
+
/** Headroom's experimental release channel, exported as `HEADROOM_ROLLOUT_CHANNEL`. Required by `interceptToolResults` and `readMaturation`, which are refused without a channel that unlocks them. */
|
|
175
|
+
rolloutChannel: z.enum(HEADROOM_ROLLOUT_CHANNELS).optional(),
|
|
176
|
+
/** Opt in to headroom's tool-result interceptors (`--intercept-tool-results`). Needs `rolloutChannel` canary or dev. */
|
|
177
|
+
interceptToolResults: z.boolean().optional(),
|
|
178
|
+
/** Opt in to headroom's experimental read maturation (`--read-maturation`). Needs `rolloutChannel` beta or dev. */
|
|
179
|
+
readMaturation: z.boolean().optional()
|
|
180
|
+
});
|
|
181
|
+
var HeadroomGlobalConfigSchema = HeadroomGlobalConfigObjectSchema.superRefine((config, context) => {
|
|
182
|
+
if (config.interceptToolResults === true && !INTERCEPT_CHANNELS.includes(config.rolloutChannel ?? "")) {
|
|
183
|
+
context.addIssue({ code: "custom", path: ["interceptToolResults"], message: "interceptToolResults needs rolloutChannel canary or dev" });
|
|
184
|
+
}
|
|
185
|
+
if (config.readMaturation === true && !READ_MATURATION_CHANNELS.includes(config.rolloutChannel ?? "")) {
|
|
186
|
+
context.addIssue({ code: "custom", path: ["readMaturation"], message: "readMaturation needs rolloutChannel beta or dev" });
|
|
187
|
+
}
|
|
163
188
|
});
|
|
164
189
|
var HEADROOM_DEFAULT_SOURCE = "headroom-ai[proxy] @ git+https://github.com/ExaDev/headroom@eb02fa4126450c9905a49394fdec19ae2e7c9c30";
|
|
165
190
|
var HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES = 15;
|
|
@@ -2462,6 +2487,8 @@ var HeadroomStateSchema = z4.strictObject({
|
|
|
2462
2487
|
version: z4.string().optional(),
|
|
2463
2488
|
/** Hash of the allowlist the running proxy was started with, so a provider-file change is detected as drift. */
|
|
2464
2489
|
allowlistHash: z4.string().optional(),
|
|
2490
|
+
/** Hash of the token-saving settings the running proxy was started with, so a settings change is detected as drift. */
|
|
2491
|
+
settingsHash: z4.string().optional(),
|
|
2465
2492
|
/** The install spec the running install was last installed from, so a `source` config change is detected as drift. */
|
|
2466
2493
|
installedSource: z4.string().optional(),
|
|
2467
2494
|
/** The last fatal error, kept across shutdowns so `headroom status` can explain a daemon that is not running. */
|
|
@@ -2680,11 +2707,41 @@ function ensureHeadroom(params) {
|
|
|
2680
2707
|
}
|
|
2681
2708
|
}
|
|
2682
2709
|
|
|
2710
|
+
// src/headroom/settings.ts
|
|
2711
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
2712
|
+
function settingsOf(config) {
|
|
2713
|
+
return {
|
|
2714
|
+
...config.mode === void 0 ? {} : { mode: config.mode },
|
|
2715
|
+
...config.targetRatio === void 0 ? {} : { targetRatio: config.targetRatio },
|
|
2716
|
+
...config.ccr === void 0 ? {} : { ccr: config.ccr },
|
|
2717
|
+
...config.rolloutChannel === void 0 ? {} : { rolloutChannel: config.rolloutChannel },
|
|
2718
|
+
...config.interceptToolResults === void 0 ? {} : { interceptToolResults: config.interceptToolResults },
|
|
2719
|
+
...config.readMaturation === void 0 ? {} : { readMaturation: config.readMaturation }
|
|
2720
|
+
};
|
|
2721
|
+
}
|
|
2722
|
+
function settingsArgs(settings) {
|
|
2723
|
+
return [
|
|
2724
|
+
...settings.mode === void 0 ? [] : ["--mode", settings.mode],
|
|
2725
|
+
...settings.targetRatio === void 0 ? [] : ["--target-ratio", String(settings.targetRatio)],
|
|
2726
|
+
...settings.ccr === "lossless" ? ["--lossless"] : [],
|
|
2727
|
+
...settings.ccr === "none" ? ["--no-ccr"] : [],
|
|
2728
|
+
...settings.interceptToolResults === true ? ["--intercept-tool-results"] : [],
|
|
2729
|
+
...settings.readMaturation === true ? ["--read-maturation"] : []
|
|
2730
|
+
];
|
|
2731
|
+
}
|
|
2732
|
+
function settingsEnv(settings) {
|
|
2733
|
+
return settings.rolloutChannel === void 0 ? {} : { HEADROOM_ROLLOUT_CHANNEL: settings.rolloutChannel };
|
|
2734
|
+
}
|
|
2735
|
+
function hashSettings(settings) {
|
|
2736
|
+
return createHash2("sha256").update(JSON.stringify([...settingsArgs(settings), JSON.stringify(settingsEnv(settings))])).digest("hex");
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2683
2739
|
// src/headroom/supervisor.ts
|
|
2684
2740
|
function resolveSupervisorConfig(configured) {
|
|
2685
2741
|
return {
|
|
2686
2742
|
source: configured.source ?? HEADROOM_DEFAULT_SOURCE,
|
|
2687
|
-
idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES
|
|
2743
|
+
idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES,
|
|
2744
|
+
settings: settingsOf(configured)
|
|
2688
2745
|
};
|
|
2689
2746
|
}
|
|
2690
2747
|
var HEADROOM_BACKOFF_BASE_MS = 500;
|
|
@@ -2747,8 +2804,9 @@ function compareVersions(a, b) {
|
|
|
2747
2804
|
return 0;
|
|
2748
2805
|
}
|
|
2749
2806
|
var HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
|
|
2750
|
-
async function runSupervisor(
|
|
2807
|
+
async function runSupervisor(initialConfig, ports, options = {}) {
|
|
2751
2808
|
const { fs: fs2, paths } = ports;
|
|
2809
|
+
let config = initialConfig;
|
|
2752
2810
|
const previousState = readHeadroomState(fs2, paths.headroomStateFile);
|
|
2753
2811
|
let installedSource = previousState?.installedSource;
|
|
2754
2812
|
let lastPort = previousState?.lastPort;
|
|
@@ -2790,6 +2848,7 @@ async function runSupervisor(config, ports, options = {}) {
|
|
|
2790
2848
|
}
|
|
2791
2849
|
let headroomPid;
|
|
2792
2850
|
let runningHash;
|
|
2851
|
+
let runningSettingsHash;
|
|
2793
2852
|
let consecutiveFailures = 0;
|
|
2794
2853
|
let idleSince;
|
|
2795
2854
|
let ticks = 0;
|
|
@@ -2798,6 +2857,8 @@ async function runSupervisor(config, ports, options = {}) {
|
|
|
2798
2857
|
return HEADROOM_SUPERVISOR_STILL_RUNNING;
|
|
2799
2858
|
}
|
|
2800
2859
|
ticks += 1;
|
|
2860
|
+
config = ports.readConfig();
|
|
2861
|
+
const settingsHash = hashSettings(config.settings);
|
|
2801
2862
|
const allowlist = allowlistOf(ports);
|
|
2802
2863
|
const allowlistHash = hashAllowlist(allowlist);
|
|
2803
2864
|
pruneDeadSessions(fs2, paths.headroomSessionsDir, ports.isRunning);
|
|
@@ -2807,6 +2868,7 @@ async function runSupervisor(config, ports, options = {}) {
|
|
|
2807
2868
|
ports.log(`claude-use headroom supervisor: headroom pid ${String(headroomPid)} died`);
|
|
2808
2869
|
headroomPid = void 0;
|
|
2809
2870
|
runningHash = void 0;
|
|
2871
|
+
runningSettingsHash = void 0;
|
|
2810
2872
|
writeHeadroomState(fs2, paths.headroomStateFile, {
|
|
2811
2873
|
supervisorPid: ports.ownPid,
|
|
2812
2874
|
version,
|
|
@@ -2822,12 +2884,13 @@ async function runSupervisor(config, ports, options = {}) {
|
|
|
2822
2884
|
}
|
|
2823
2885
|
const previousSticky = lastPort;
|
|
2824
2886
|
const port = lastPort !== void 0 && await ports.isPortFree(lastPort) ? lastPort : await ports.freePort();
|
|
2825
|
-
const pid = ports.spawnHeadroom(port, allowlist);
|
|
2887
|
+
const pid = ports.spawnHeadroom(port, allowlist, config.settings);
|
|
2826
2888
|
const ready = await waitUntilReady(ports, port);
|
|
2827
2889
|
if (ready) {
|
|
2828
2890
|
consecutiveFailures = 0;
|
|
2829
2891
|
headroomPid = pid;
|
|
2830
2892
|
runningHash = allowlistHash;
|
|
2893
|
+
runningSettingsHash = settingsHash;
|
|
2831
2894
|
lastPort = port;
|
|
2832
2895
|
if (previousSticky !== void 0 && port !== previousSticky) {
|
|
2833
2896
|
ports.log(
|
|
@@ -2840,6 +2903,7 @@ async function runSupervisor(config, ports, options = {}) {
|
|
|
2840
2903
|
port,
|
|
2841
2904
|
version,
|
|
2842
2905
|
allowlistHash,
|
|
2906
|
+
settingsHash,
|
|
2843
2907
|
installedSource: config.source,
|
|
2844
2908
|
...sticky()
|
|
2845
2909
|
});
|
|
@@ -2856,12 +2920,13 @@ async function runSupervisor(config, ports, options = {}) {
|
|
|
2856
2920
|
continue;
|
|
2857
2921
|
}
|
|
2858
2922
|
} else {
|
|
2859
|
-
if (runningHash !== allowlistHash || installedSource !== config.source) {
|
|
2923
|
+
if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
|
|
2860
2924
|
if (listSessions(fs2, paths.headroomSessionsDir).length === 0) {
|
|
2861
2925
|
ports.log("claude-use headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
|
|
2862
2926
|
ports.stopProcess(headroomPid);
|
|
2863
2927
|
headroomPid = void 0;
|
|
2864
2928
|
runningHash = void 0;
|
|
2929
|
+
runningSettingsHash = void 0;
|
|
2865
2930
|
continue;
|
|
2866
2931
|
}
|
|
2867
2932
|
}
|
|
@@ -559,6 +559,28 @@ export declare const PortableConfigSchema: z.ZodObject<{
|
|
|
559
559
|
}, z.core.$strict>>;
|
|
560
560
|
}, z.core.$strict>;
|
|
561
561
|
export type PortableConfig = z.infer<typeof PortableConfigSchema>;
|
|
562
|
+
declare const HeadroomGlobalConfigSchema: z.ZodObject<{
|
|
563
|
+
source: z.ZodOptional<z.ZodString>;
|
|
564
|
+
idleShutdownMinutes: z.ZodOptional<z.ZodNumber>;
|
|
565
|
+
mode: z.ZodOptional<z.ZodEnum<{
|
|
566
|
+
cache: "cache";
|
|
567
|
+
token: "token";
|
|
568
|
+
}>>;
|
|
569
|
+
targetRatio: z.ZodOptional<z.ZodNumber>;
|
|
570
|
+
ccr: z.ZodOptional<z.ZodEnum<{
|
|
571
|
+
default: "default";
|
|
572
|
+
none: "none";
|
|
573
|
+
lossless: "lossless";
|
|
574
|
+
}>>;
|
|
575
|
+
rolloutChannel: z.ZodOptional<z.ZodEnum<{
|
|
576
|
+
beta: "beta";
|
|
577
|
+
canary: "canary";
|
|
578
|
+
dev: "dev";
|
|
579
|
+
}>>;
|
|
580
|
+
interceptToolResults: z.ZodOptional<z.ZodBoolean>;
|
|
581
|
+
readMaturation: z.ZodOptional<z.ZodBoolean>;
|
|
582
|
+
}, z.core.$strict>;
|
|
583
|
+
export type HeadroomGlobalConfig = z.infer<typeof HeadroomGlobalConfigSchema>;
|
|
562
584
|
/**
|
|
563
585
|
* The default `headroom.source` install spec: the ExaDev headroom repository, with the `proxy` extra that provides the `headroom proxy` entry point. Pinned to a full commit SHA (the fork's `exadev-per-session-savings-1` tag), because a branch name can be rebased or deleted and would change or break every fresh install; bump it deliberately when the fork's per-session savings work changes. Kept here rather than in the supervisor because it is the schema's own documented default, referenced by `HeadroomGlobalConfigSchema`'s field docs.
|
|
564
586
|
*/
|
|
@@ -608,6 +630,23 @@ export declare const GlobalConfigSchema: z.ZodObject<{
|
|
|
608
630
|
headroom: z.ZodOptional<z.ZodObject<{
|
|
609
631
|
source: z.ZodOptional<z.ZodString>;
|
|
610
632
|
idleShutdownMinutes: z.ZodOptional<z.ZodNumber>;
|
|
633
|
+
mode: z.ZodOptional<z.ZodEnum<{
|
|
634
|
+
cache: "cache";
|
|
635
|
+
token: "token";
|
|
636
|
+
}>>;
|
|
637
|
+
targetRatio: z.ZodOptional<z.ZodNumber>;
|
|
638
|
+
ccr: z.ZodOptional<z.ZodEnum<{
|
|
639
|
+
default: "default";
|
|
640
|
+
none: "none";
|
|
641
|
+
lossless: "lossless";
|
|
642
|
+
}>>;
|
|
643
|
+
rolloutChannel: z.ZodOptional<z.ZodEnum<{
|
|
644
|
+
beta: "beta";
|
|
645
|
+
canary: "canary";
|
|
646
|
+
dev: "dev";
|
|
647
|
+
}>>;
|
|
648
|
+
interceptToolResults: z.ZodOptional<z.ZodBoolean>;
|
|
649
|
+
readMaturation: z.ZodOptional<z.ZodBoolean>;
|
|
611
650
|
}, z.core.$strict>>;
|
|
612
651
|
frontdoor: z.ZodOptional<z.ZodObject<{
|
|
613
652
|
idleShutdownMinutes: z.ZodOptional<z.ZodNumber>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { HeadroomGlobalConfig } from "../config/schema";
|
|
2
|
+
/** The token-saving settings of the `headroom` config block, without its install and lifecycle fields (`source`, `idleShutdownMinutes`). */
|
|
3
|
+
export type HeadroomSettings = Pick<HeadroomGlobalConfig, "mode" | "targetRatio" | "ccr" | "rolloutChannel" | "interceptToolResults" | "readMaturation">;
|
|
4
|
+
/** Picks the settings out of a headroom config block, leaving unset fields unset. */
|
|
5
|
+
export declare function settingsOf(config: Readonly<HeadroomGlobalConfig>): HeadroomSettings;
|
|
6
|
+
/**
|
|
7
|
+
* The `headroom proxy` arguments the settings ask for, after the supervisor's own `--host` and `--port`. Only settings that are set produce a flag, so an empty settings object leaves headroom on its own defaults. `ccr: "default"` is headroom's default and likewise adds nothing.
|
|
8
|
+
*/
|
|
9
|
+
export declare function settingsArgs(settings: Readonly<HeadroomSettings>): string[];
|
|
10
|
+
/** The environment the settings add to the daemon's: only the rollout channel, which headroom reads from `HEADROOM_ROLLOUT_CHANNEL` and has no flag for. */
|
|
11
|
+
export declare function settingsEnv(settings: Readonly<HeadroomSettings>): Record<string, string>;
|
|
12
|
+
/** A stable hash of the settings, so drift detection compares one short string against what the running daemon was started with. The same settings hash the same whatever order the config file wrote its keys in. */
|
|
13
|
+
export declare function hashSettings(settings: Readonly<HeadroomSettings>): string;
|
|
@@ -13,6 +13,7 @@ export declare const HeadroomStateSchema: z.ZodObject<{
|
|
|
13
13
|
lastPort: z.ZodOptional<z.ZodNumber>;
|
|
14
14
|
version: z.ZodOptional<z.ZodString>;
|
|
15
15
|
allowlistHash: z.ZodOptional<z.ZodString>;
|
|
16
|
+
settingsHash: z.ZodOptional<z.ZodString>;
|
|
16
17
|
installedSource: z.ZodOptional<z.ZodString>;
|
|
17
18
|
lastError: z.ZodOptional<z.ZodString>;
|
|
18
19
|
}, z.core.$strict>;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { type HeadroomGlobalConfig } from "../config/schema";
|
|
1
2
|
import type { LayoutPaths } from "../paths";
|
|
3
|
+
import { type HeadroomSettings } from "./settings";
|
|
2
4
|
import { type HeadroomFs } from "./state";
|
|
3
5
|
/** Everything the supervisor needs to run the daemon, resolved from the global config and the filesystem before it starts. */
|
|
4
6
|
export interface HeadroomSupervisorConfig {
|
|
@@ -6,12 +8,11 @@ export interface HeadroomSupervisorConfig {
|
|
|
6
8
|
readonly source: string;
|
|
7
9
|
/** `headroom.idleShutdownMinutes`, already defaulted. */
|
|
8
10
|
readonly idleShutdownMinutes: number;
|
|
11
|
+
/** The token-saving settings (`mode`, `targetRatio`, `ccr`, the rollout channel and its opt-ins), as configured; unset fields leave headroom on its own defaults. */
|
|
12
|
+
readonly settings: HeadroomSettings;
|
|
9
13
|
}
|
|
10
14
|
/** How the supervisor resolves the configured source against its defaults. */
|
|
11
|
-
export declare function resolveSupervisorConfig(configured:
|
|
12
|
-
readonly source?: string;
|
|
13
|
-
readonly idleShutdownMinutes?: number;
|
|
14
|
-
}): HeadroomSupervisorConfig;
|
|
15
|
+
export declare function resolveSupervisorConfig(configured: Readonly<HeadroomGlobalConfig>): HeadroomSupervisorConfig;
|
|
15
16
|
/**
|
|
16
17
|
* Every effect the supervisor performs, injected so the whole lifecycle is testable against fakes: no real process, port, clock, or HTTP call ever happens in a unit test.
|
|
17
18
|
*/
|
|
@@ -35,7 +36,9 @@ export interface SupervisorPorts {
|
|
|
35
36
|
/**
|
|
36
37
|
* Starts `headroom proxy` bound to `port` with the given allowlist, returning its pid. Output goes to the daemon log. The implementation must keep the ChildProcess handle and attach an exit listener (detaching the process is fine; unref'ing a child you still need events from is not, since without the listener nothing reaps it and it lingers as a zombie).
|
|
37
38
|
*/
|
|
38
|
-
readonly spawnHeadroom: (port: number, allowlist: readonly string[]) => number;
|
|
39
|
+
readonly spawnHeadroom: (port: number, allowlist: readonly string[], settings: Readonly<HeadroomSettings>) => number;
|
|
40
|
+
/** Reads the headroom config block as it stands on disk now. The supervisor calls it every tick, so a changed `source` or setting is noticed while the daemon runs and applied by the same quiet-registry restart an allowlist change gets. */
|
|
41
|
+
readonly readConfig: () => HeadroomSupervisorConfig;
|
|
39
42
|
/** Stops a process the supervisor owns, escalating SIGTERM to SIGKILL on a bounded timeout (see `stopSupervisedProcess`). */
|
|
40
43
|
readonly stopProcess: (pid: number) => void;
|
|
41
44
|
/** True once `GET /readyz` on the port succeeds. */
|
|
@@ -97,6 +100,6 @@ export declare const HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
|
|
|
97
100
|
*
|
|
98
101
|
* Every effect flows through `ports`, so the whole loop is unit-testable with a fake clock, filesystem, and processes. The loop awaits its sleeps, which in the real implementation run on a timer: between ticks the event loop turns, the spawned child's exit event is delivered (and the child thereby reaped), and the next tick's liveness check reads the truth.
|
|
99
102
|
*/
|
|
100
|
-
export declare function runSupervisor(
|
|
103
|
+
export declare function runSupervisor(initialConfig: HeadroomSupervisorConfig, ports: SupervisorPorts, options?: RunSupervisorOptions): Promise<number>;
|
|
101
104
|
/** The allowlist as it stands right now: every provider's upstream (the front door's origin for a codex provider, which headroom forwards codex traffic back to) plus Claude Code's own API. */
|
|
102
105
|
export declare function currentHeadroomAllowlist(fs: HeadroomFs, paths: LayoutPaths): readonly string[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-use",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.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>",
|