claude-use 9.12.0 → 9.13.0

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/index.mjs CHANGED
@@ -2573,7 +2573,7 @@ function listFrontDoorSessions(fs12, sessionsDir) {
2573
2573
  try {
2574
2574
  const parsed = FrontDoorSessionSchema.safeParse(JSON.parse(fs12.readFileUtf8(path8.join(sessionsDir, name)) ?? ""));
2575
2575
  if (parsed.success) {
2576
- sessions.push({ pid: parsed.data.pid, startedAt: parsed.data.startedAt });
2576
+ sessions.push({ pid: parsed.data.pid, startedAt: parsed.data.startedAt, ...parsed.data.supervisorPid === void 0 ? {} : { supervisorPid: parsed.data.supervisorPid } });
2577
2577
  }
2578
2578
  } catch {
2579
2579
  continue;
@@ -2599,7 +2599,7 @@ var init_state = __esm({
2599
2599
  "src/frontdoor/state.ts"() {
2600
2600
  "use strict";
2601
2601
  FRONT_DOOR_PROTOCOL = 3;
2602
- FrontDoorSessionSchema = z3.strictObject({ pid: z3.number().int().positive(), startedAt: z3.number(), token: z3.string().min(1) });
2602
+ FrontDoorSessionSchema = z3.strictObject({ pid: z3.number().int().positive(), startedAt: z3.number(), token: z3.string().min(1), supervisorPid: z3.number().int().positive().optional() });
2603
2603
  FrontDoorStateSchema = z3.strictObject({
2604
2604
  /**
2605
2605
  * The launcher-to-door wire protocol this supervisor speaks, written once it is serving; absent in state written by a release that predates the field, which `ensureFrontDoor` reads as protocol 0. A launcher replaces a door whose protocol is lower than its own (see `FRONT_DOOR_PROTOCOL`).
@@ -2639,7 +2639,7 @@ var init_package = __esm({
2639
2639
  "package.json"() {
2640
2640
  package_default = {
2641
2641
  name: "claude-use",
2642
- version: "9.12.0",
2642
+ version: "9.13.0",
2643
2643
  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.",
2644
2644
  license: "MIT",
2645
2645
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -2742,6 +2742,36 @@ var init_package = __esm({
2742
2742
  }
2743
2743
  });
2744
2744
 
2745
+ // src/frontdoor/supervisorProcesses.ts
2746
+ function parseFrontDoorSupervisorPids(psOutput) {
2747
+ const pids = [];
2748
+ for (const line of psOutput.split("\n")) {
2749
+ const [pidText, ...argumentsList] = line.trim().split(/\s+/);
2750
+ const pid = Number(pidText);
2751
+ if (Number.isInteger(pid) && pid > 0 && argumentsList.includes(FRONT_DOOR_SUPERVISOR_ARGUMENT)) {
2752
+ pids.push(pid);
2753
+ }
2754
+ }
2755
+ return pids.sort((a, b) => a - b);
2756
+ }
2757
+ function listFrontDoorSupervisors(run, uid) {
2758
+ const result = run.run("ps", ["-U", String(uid), "-o", "pid=,command="]);
2759
+ if (result.status === null) {
2760
+ return { ok: false, message: "ps could not be run" };
2761
+ }
2762
+ if (result.status !== 0) {
2763
+ return { ok: false, message: `ps exited ${String(result.status)}${result.stderr.trim() === "" ? "" : `: ${result.stderr.trim()}`}` };
2764
+ }
2765
+ return { ok: true, pids: parseFrontDoorSupervisorPids(result.stdout) };
2766
+ }
2767
+ var FRONT_DOOR_SUPERVISOR_ARGUMENT;
2768
+ var init_supervisorProcesses = __esm({
2769
+ "src/frontdoor/supervisorProcesses.ts"() {
2770
+ "use strict";
2771
+ FRONT_DOOR_SUPERVISOR_ARGUMENT = "__frontdoor-supervisor";
2772
+ }
2773
+ });
2774
+
2745
2775
  // src/headroom/state.ts
2746
2776
  import { createHash as createHash3 } from "node:crypto";
2747
2777
  import path11 from "node:path";
@@ -3338,9 +3368,15 @@ async function runSupervisor(initialConfig, ports, options = {}) {
3338
3368
  await ports.sleep(HEADROOM_POLL_MS);
3339
3369
  }
3340
3370
  }
3371
+ function currentHeadroomAllowlistInputs(fs12, paths) {
3372
+ return {
3373
+ providers: readAllProviders(fs12, paths.providersDir).map((entry) => entry.provider),
3374
+ frontDoorOrigin: frontDoorOrigin(readFrontDoorState(fs12, paths.frontdoorStateFile))
3375
+ };
3376
+ }
3341
3377
  function currentHeadroomAllowlist(fs12, paths) {
3342
- const providers = readAllProviders(fs12, paths.providersDir).map((entry) => entry.provider);
3343
- return headroomAllowlist(headroomUpstreams(providers, frontDoorOrigin(readFrontDoorState(fs12, paths.frontdoorStateFile))));
3378
+ const inputs = currentHeadroomAllowlistInputs(fs12, paths);
3379
+ return headroomAllowlist(headroomUpstreams(inputs.providers, inputs.frontDoorOrigin));
3344
3380
  }
3345
3381
  function allowlistOf(ports) {
3346
3382
  return currentHeadroomAllowlist(ports.fs, ports.paths);
@@ -3408,6 +3444,10 @@ function realIsProcessZombie(pid) {
3408
3444
  function realIsProcessRunning(pid) {
3409
3445
  return realIsProcessAlive(pid) && !realIsProcessZombie(pid);
3410
3446
  }
3447
+ function realIsFrontDoorSupervisor(pid) {
3448
+ const result = spawnSync("ps", ["-o", "pid=,command=", "-p", String(pid)], { encoding: "utf8" });
3449
+ return result.status === 0 && parseFrontDoorSupervisorPids(result.stdout).includes(pid);
3450
+ }
3411
3451
  function expandHome2(filePath) {
3412
3452
  return filePath.startsWith("~/") ? path13.join(os3.homedir(), filePath.slice(1)) : filePath;
3413
3453
  }
@@ -3611,6 +3651,7 @@ var init_realPorts = __esm({
3611
3651
  "use strict";
3612
3652
  init_load();
3613
3653
  init_versionDiscovery();
3654
+ init_supervisorProcesses();
3614
3655
  init_ensure();
3615
3656
  init_supervisor();
3616
3657
  init_state2();
@@ -4645,7 +4686,7 @@ function assessIdentity(member, nowMs) {
4645
4686
  );
4646
4687
  if (blockedUntil > nowMs) {
4647
4688
  if (unified.overageStatus === "allowed") {
4648
- return { candidate: { ...base, class: "pay-per-use", reasons: [`plan exhausted until ${new Date(blockedUntil).toISOString()}, continues as extra usage`, ...windowReasons, ...planReasons] } };
4689
+ return { candidate: { ...base, class: "pay-per-use", planReturnsAtMs: blockedUntil, reasons: [`plan exhausted until ${new Date(blockedUntil).toISOString()} (in ${formatAge(blockedUntil - nowMs)}), continues as extra usage`, ...windowReasons, ...planReasons] } };
4649
4690
  }
4650
4691
  return { candidate: { ...base, class: "ineligible", blockedUntilMs: blockedUntil, reasons: [`refused until ${new Date(blockedUntil).toISOString()} (in ${formatAge(blockedUntil - nowMs)})`, ...unified.fallbackAvailable === true ? ["a fallback is on offer, so requests for some models may still be served"] : [], ...windowReasons, ...overageReasons, ...planReasons] } };
4651
4692
  }
@@ -4760,7 +4801,8 @@ function rankPool(input) {
4760
4801
  return {
4761
4802
  candidates: ordered,
4762
4803
  ...pick === void 0 ? { ...earliestReturn === void 0 ? {} : { earliestReturn } } : { pick },
4763
- ...movedOff === void 0 || pick === void 0 ? {} : { movedOff }
4804
+ ...movedOff === void 0 || pick === void 0 ? {} : { movedOff },
4805
+ ...pick?.planReturnsAtMs === void 0 ? {} : { planReturn: { identity: pick.identity, atMs: pick.planReturnsAtMs } }
4764
4806
  };
4765
4807
  }
4766
4808
  var MS_PER_SECOND4, MS_PER_HOUR2, HOURS_PER_DAY2, FIVE_HOURS, PROMPT_CACHE_TTL_HOURS, PERCENT, SEVEN_DAYS, FIVE_HOUR_WINDOW_MS, SEVEN_DAY_WINDOW_MS, PROMPT_CACHE_TTL_MS, CLASS_ORDER;
@@ -6140,32 +6182,50 @@ function ensureFrontDoor(params) {
6140
6182
  let replaced = false;
6141
6183
  const distrusted = /* @__PURE__ */ new Set();
6142
6184
  let lastRejection;
6185
+ const verifyWithRetry = (port) => {
6186
+ let verdict = ports.verifyListener(port);
6187
+ for (let attempt = 1; !verdict.ok && attempt < FRONTDOOR_PROBE_ATTEMPTS; attempt += 1) {
6188
+ ports.sleep(FRONTDOOR_LAUNCHER_POLL_MS);
6189
+ verdict = ports.verifyListener(port);
6190
+ }
6191
+ return verdict;
6192
+ };
6193
+ const stopAndAwaitExit = (supervisorPid, failure) => {
6194
+ ports.stopSupervisor(supervisorPid);
6195
+ while (ports.isRunning(supervisorPid)) {
6196
+ if (ports.now() >= deadline) {
6197
+ throw new FrontDoorStartError(`${failure}. Daemon log: ${paths.frontdoorLogPath}`);
6198
+ }
6199
+ ports.sleep(FRONTDOOR_LAUNCHER_POLL_MS);
6200
+ }
6201
+ };
6143
6202
  for (; ; ) {
6144
6203
  const state = readFrontDoorState(ports.fs, paths.frontdoorStateFile);
6145
6204
  const supervisorPid = state?.supervisorPid;
6146
6205
  if (supervisorPid !== void 0 && !distrusted.has(supervisorPid) && ports.isRunning(supervisorPid)) {
6147
6206
  if (state?.port !== void 0 && state.connectPort !== void 0) {
6148
- const verdict = ports.verifyListener(state.port);
6207
+ const verdict = verifyWithRetry(state.port);
6149
6208
  if (verdict.ok && !replaced && !spawned && (params.replace === true || (state.protocol ?? 0) < FRONT_DOOR_PROTOCOL)) {
6150
- ports.stopSupervisor(supervisorPid);
6151
- while (ports.isRunning(supervisorPid)) {
6152
- if (ports.now() >= deadline) {
6153
- throw new FrontDoorStartError(`the front door (pid ${String(supervisorPid)}, protocol ${String(state.protocol ?? 0)}; this launcher speaks protocol ${String(FRONT_DOOR_PROTOCOL)}) was asked to be replaced and did not exit. Daemon log: ${paths.frontdoorLogPath}`);
6154
- }
6155
- ports.sleep(FRONTDOOR_LAUNCHER_POLL_MS);
6156
- }
6209
+ stopAndAwaitExit(
6210
+ supervisorPid,
6211
+ `the front door (pid ${String(supervisorPid)}, protocol ${String(state.protocol ?? 0)}; this launcher speaks protocol ${String(FRONT_DOOR_PROTOCOL)}) was asked to be replaced and did not exit`
6212
+ );
6157
6213
  replaced = true;
6158
6214
  continue;
6159
6215
  }
6160
6216
  if (verdict.ok) {
6161
6217
  const token = randomUUID3();
6162
- writeFrontDoorSession(ports.fs, paths.frontdoorSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), token });
6218
+ writeFrontDoorSession(ports.fs, paths.frontdoorSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), token, supervisorPid });
6163
6219
  return { port: state.port, connectPort: state.connectPort, token };
6164
6220
  }
6165
6221
  lastRejection = `the listener on 127.0.0.1:${String(state.port)} (recorded for pid ${String(supervisorPid)}) failed authentication: ${verdict.reason}`;
6166
6222
  if (spawned) {
6167
6223
  throw new FrontDoorStartError(`${lastRejection}; refusing to send this session's credentials to it. Daemon log: ${paths.frontdoorLogPath}`);
6168
6224
  }
6225
+ if (ports.isFrontDoorSupervisor(supervisorPid)) {
6226
+ stopAndAwaitExit(supervisorPid, `the front door (pid ${String(supervisorPid)}) stopped answering and did not exit when asked to`);
6227
+ continue;
6228
+ }
6169
6229
  distrusted.add(supervisorPid);
6170
6230
  continue;
6171
6231
  }
@@ -6196,7 +6256,7 @@ function ensureFrontDoor(params) {
6196
6256
  ports.sleep(FRONTDOOR_LAUNCHER_POLL_MS);
6197
6257
  }
6198
6258
  }
6199
- var FRONTDOOR_START_TIMEOUT_MS, FRONTDOOR_LAUNCHER_POLL_MS, FrontDoorStartError;
6259
+ var FRONTDOOR_START_TIMEOUT_MS, FRONTDOOR_LAUNCHER_POLL_MS, FRONTDOOR_PROBE_ATTEMPTS, FrontDoorStartError;
6200
6260
  var init_ensure2 = __esm({
6201
6261
  "src/frontdoor/ensure.ts"() {
6202
6262
  "use strict";
@@ -6205,6 +6265,7 @@ var init_ensure2 = __esm({
6205
6265
  init_state();
6206
6266
  FRONTDOOR_START_TIMEOUT_MS = 3e4;
6207
6267
  FRONTDOOR_LAUNCHER_POLL_MS = 100;
6268
+ FRONTDOOR_PROBE_ATTEMPTS = 3;
6208
6269
  FrontDoorStartError = class extends CliError {
6209
6270
  constructor(message) {
6210
6271
  super(message);
@@ -6688,7 +6749,12 @@ function resolvePoolLaunch(params) {
6688
6749
  if (graph.ranking.candidates.length === 0) {
6689
6750
  return { ok: false, message: `agent-shim: no member of pool "${poolName}" is an existing identity.` };
6690
6751
  }
6691
- const { pick, earliestReturn, movedOff } = graph.ranking;
6752
+ const { pick, earliestReturn, movedOff, planReturn } = graph.ranking;
6753
+ if (pick !== void 0 && params.wait && planReturn !== void 0) {
6754
+ log.info(`agent-shim: pool "${poolName}" would continue ${planReturn.identity} on extra usage; waiting until ${new Date(planReturn.atMs).toISOString()} for its plan window`);
6755
+ params.sleep(Math.max(planReturn.atMs - nowMs, 0));
6756
+ continue;
6757
+ }
6692
6758
  if (pick !== void 0) {
6693
6759
  if (params.recordPick) {
6694
6760
  recordStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd, pick.identity, nowMs);
@@ -7391,6 +7457,7 @@ function realEnsurePorts(paths, spawnDaemon) {
7391
7457
  now: () => Date.now(),
7392
7458
  sleep: realSleepSync,
7393
7459
  spawnSupervisor: (layout) => spawnDaemon(layout, "__frontdoor-supervisor", layout.frontdoorLogPath),
7460
+ isFrontDoorSupervisor: realIsFrontDoorSupervisor,
7394
7461
  stopSupervisor: (pid) => {
7395
7462
  process.kill(pid, "SIGTERM");
7396
7463
  },
@@ -8291,16 +8358,27 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
8291
8358
  ports.log(`agent-shim frontdoor supervisor ${String(ports.ownPid)}: front door on 127.0.0.1:${String(http3.port)}, CONNECT surface on 127.0.0.1:${String(connect3.port)}`);
8292
8359
  let idleSince;
8293
8360
  let ticks = 0;
8361
+ const supersedingOwner = () => {
8362
+ const owner = readFrontDoorState(fs12, paths.frontdoorStateFile)?.supervisorPid;
8363
+ return owner !== void 0 && owner !== ports.ownPid && ports.isRunning(owner) ? owner : void 0;
8364
+ };
8365
+ let supersededBy;
8366
+ let served;
8294
8367
  const binaryReplaced = () => {
8295
8368
  const current = ports.statOwnExecutable();
8296
8369
  return current !== void 0 && servingBinary !== void 0 && (current.dev !== servingBinary.dev || current.ino !== servingBinary.ino || current.size !== servingBinary.size || current.mtimeMs !== servingBinary.mtimeMs);
8297
8370
  };
8298
- const shutdown = async () => {
8371
+ const closeServing = async () => {
8299
8372
  await direct.close();
8300
8373
  await connect3.close();
8301
8374
  await http3.close();
8302
8375
  ports.closeRcStreamHub();
8303
- writeFrontDoorState(fs12, paths.frontdoorStateFile, sticky());
8376
+ };
8377
+ const shutdown = async () => {
8378
+ await closeServing();
8379
+ if (supersedingOwner() === void 0) {
8380
+ writeFrontDoorState(fs12, paths.frontdoorStateFile, sticky());
8381
+ }
8304
8382
  return 0;
8305
8383
  };
8306
8384
  for (; ; ) {
@@ -8313,7 +8391,28 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
8313
8391
  ports.observeLaunchRegistry(listed, pruned);
8314
8392
  ports.checkQuotaExpiry();
8315
8393
  const prunedPids = new Set(pruned);
8316
- if (listed.filter((session) => !prunedPids.has(session.pid)).length === 0) {
8394
+ const live = listed.filter((session) => !prunedPids.has(session.pid));
8395
+ if (supersededBy === void 0) {
8396
+ supersededBy = supersedingOwner();
8397
+ if (supersededBy !== void 0) {
8398
+ ports.log(`agent-shim frontdoor supervisor ${String(ports.ownPid)}: supervisor ${String(supersededBy)} now owns the front door; taking no new sessions and exiting once the sessions this door serves have ended`);
8399
+ ports.doorHealth.superseded(supersededBy);
8400
+ await ports.sleep(FRONTDOOR_POLL_MS);
8401
+ continue;
8402
+ }
8403
+ } else {
8404
+ const owner = supersededBy;
8405
+ served = (served ?? live.filter((session) => session.pid !== ports.ownPid && session.pid !== owner && (session.supervisorPid === void 0 || session.supervisorPid === ports.ownPid))).filter((own) => live.some((session) => session.pid === own.pid && session.startedAt === own.startedAt));
8406
+ if (served.length === 0) {
8407
+ ports.log("agent-shim frontdoor supervisor: the sessions this superseded door served have ended; closing the front door and exiting");
8408
+ ports.doorHealth.drained();
8409
+ await closeServing();
8410
+ return 0;
8411
+ }
8412
+ await ports.sleep(FRONTDOOR_POLL_MS);
8413
+ continue;
8414
+ }
8415
+ if (live.length === 0) {
8317
8416
  if (ticks > 1 && binaryReplaced()) {
8318
8417
  ports.log("agent-shim frontdoor supervisor: the installed binary changed and no sessions are live; turning the door over for the next launch");
8319
8418
  ports.doorHealth.binaryTurnover();
@@ -12467,7 +12566,55 @@ init_credential();
12467
12566
  init_credentialCache();
12468
12567
  init_realCredentialCache();
12469
12568
  init_source();
12569
+
12570
+ // src/headroom/allowlistDrift.ts
12571
+ init_state2();
12572
+ function diagnoseAllowlistDrift(params) {
12573
+ const { recordedHash, providers, frontDoorOrigin: frontDoorOrigin2 } = params;
12574
+ if (recordedHash === void 0 || recordedHash === hashAllowlist(headroomAllowlist(headroomUpstreams(providers, frontDoorOrigin2)))) {
12575
+ return void 0;
12576
+ }
12577
+ const originIsInput = providers.length > 0 && frontDoorOrigin2 !== void 0;
12578
+ if (!originIsInput) {
12579
+ return ["provider-files"];
12580
+ }
12581
+ return recordedHash === hashAllowlist(headroomAllowlist(headroomUpstreams(providers, void 0))) ? ["front-door-origin"] : ["provider-files", "front-door-origin"];
12582
+ }
12583
+
12584
+ // src/doctorReport.ts
12470
12585
  init_state2();
12586
+ init_supervisor();
12587
+ init_state();
12588
+
12589
+ // src/doctorFrontDoor.ts
12590
+ function pushFrontDoor(push, supervisors, registeredPid) {
12591
+ if (!supervisors.ok) {
12592
+ push("frontdoor", "warn", `Could not list processes to look for duplicate front door supervisors: ${supervisors.message}.`);
12593
+ return;
12594
+ }
12595
+ const { pids } = supervisors;
12596
+ if (pids.length === 0) {
12597
+ push("frontdoor", "pass", "No front door supervisor is running.");
12598
+ return;
12599
+ }
12600
+ const [only, ...rest] = pids;
12601
+ if (only !== void 0 && rest.length === 0) {
12602
+ push("frontdoor", "pass", `One front door supervisor is running (pid ${String(only)}).`);
12603
+ return;
12604
+ }
12605
+ const serving = registeredPid !== void 0 && pids.includes(registeredPid) ? `The front door state file names pid ${String(registeredPid)} as the serving supervisor.` : "The front door state file names no running supervisor.";
12606
+ for (const pid of pids.filter((candidate) => candidate !== registeredPid)) {
12607
+ push(
12608
+ "frontdoor",
12609
+ "fail",
12610
+ `Front door supervisor pid ${String(pid)} is running beside ${String(pids.length - 1)} other(s). ${serving} Stop it with \`kill ${String(pid)}\`; launches still connected to it lose their door.`,
12611
+ String(pid)
12612
+ );
12613
+ }
12614
+ }
12615
+
12616
+ // src/doctorReport.ts
12617
+ init_supervisorProcesses();
12471
12618
  init_legacy();
12472
12619
  init_providersStore();
12473
12620
  init_guard();
@@ -12490,7 +12637,8 @@ var DOCTOR_SECTIONS = [
12490
12637
  "global-config",
12491
12638
  "categories-local",
12492
12639
  "active-identity",
12493
- "headroom"
12640
+ "headroom",
12641
+ "frontdoor"
12494
12642
  ];
12495
12643
  function refinePathShadow(status, ownExecutablePath, realpath) {
12496
12644
  if (status.status !== "shadowed") {
@@ -12886,6 +13034,17 @@ function runDoctor(params) {
12886
13034
  `Headroom daemon is up on unix socket ${state.socketPath} (supervisor ${String(state.supervisorPid)}, daemon ${String(state.headroomPid)}).`
12887
13035
  );
12888
13036
  }
13037
+ const allowlist = params.headroom.allowlist;
13038
+ if (supervisorAlive && headroomAlive && state.socketPath !== void 0 && allowlist?.driftInputs?.includes("front-door-origin") === true) {
13039
+ const origin = allowlist.frontDoorOrigin ?? "";
13040
+ const certainty = allowlist.driftInputs.includes("provider-files") ? "may exclude" : "excludes";
13041
+ push(
13042
+ "headroom",
13043
+ "fail",
13044
+ `Headroom daemon ${String(state.headroomPid)} was started with an allowlist that ${certainty} the serving front door's direct origin ${origin}, so it refuses every request a provider launch sends back through the door. Its supervisor restarts it once no launch is registered against it; \`agent-shim headroom status\` lists the registered launches, and \`--no-headroom\` launches are unaffected.`,
13045
+ String(state.headroomPid)
13046
+ );
13047
+ }
12889
13048
  if (state.lastError !== void 0 && supervisorAlive) {
12890
13049
  push("headroom", "warn", `Headroom recorded a previous error: ${state.lastError}`);
12891
13050
  }
@@ -12898,6 +13057,10 @@ function runDoctor(params) {
12898
13057
  }
12899
13058
  }
12900
13059
  }
13060
+ if (params.frontDoor !== void 0) {
13061
+ const validated3 = params.frontDoor.state.raw === void 0 ? void 0 : validateJson(FrontDoorStateSchema, params.frontDoor.state);
13062
+ pushFrontDoor(push, params.frontDoor.supervisors, validated3?.ok === true ? validated3.data.supervisorPid : void 0);
13063
+ }
12901
13064
  if (params.activeIdentity.raw === void 0) {
12902
13065
  push("active-identity", "pass", "No active identity set.");
12903
13066
  } else {
@@ -12927,6 +13090,7 @@ var SECTION_TITLES = {
12927
13090
  "directory-rules": "Directory rules",
12928
13091
  "global-config": "Global config",
12929
13092
  headroom: "Headroom daemon",
13093
+ frontdoor: "Front door",
12930
13094
  "categories-local": "categories.local.json",
12931
13095
  "active-identity": "Active identity"
12932
13096
  };
@@ -12944,6 +13108,7 @@ var SECTION_ORDER = [
12944
13108
  "directory-rules",
12945
13109
  "global-config",
12946
13110
  "headroom",
13111
+ "frontdoor",
12947
13112
  "categories-local",
12948
13113
  "active-identity"
12949
13114
  ];
@@ -13021,6 +13186,9 @@ function collectDoctorReport(params) {
13021
13186
  shimState.targetPath,
13022
13187
  realpathOrSelf
13023
13188
  );
13189
+ const uid = process.getuid?.();
13190
+ const allowlistInputs = currentHeadroomAllowlistInputs(realFarmFs, paths);
13191
+ const servingHeadroom = resolveServingHeadroomState(realFarmFs, { stateFile: paths.headroomStateFile, legacyStateFile: paths.headroomLegacyStateFile }, realIsProcessRunning);
13024
13192
  const report = runDoctor({
13025
13193
  env: params.env,
13026
13194
  credentialCache: realCredentialCacheEnv(paths),
@@ -13059,8 +13227,13 @@ function collectDoctorReport(params) {
13059
13227
  const statePath = resolved?.path ?? paths.headroomStateFile;
13060
13228
  return { path: statePath, raw: realFarmFs.readFileUtf8(statePath) };
13061
13229
  })(),
13062
- isRunning: realIsProcessRunning
13063
- }
13230
+ isRunning: realIsProcessRunning,
13231
+ allowlist: {
13232
+ driftInputs: diagnoseAllowlistDrift({ recordedHash: servingHeadroom?.state.allowlistHash, ...allowlistInputs }),
13233
+ frontDoorOrigin: allowlistInputs.frontDoorOrigin
13234
+ }
13235
+ },
13236
+ ...uid === void 0 ? {} : { frontDoor: { state: { path: paths.frontdoorStateFile, raw: realFsPort.readFileUtf8(paths.frontdoorStateFile) }, supervisors: listFrontDoorSupervisors(realRunPort, uid) } }
13064
13237
  });
13065
13238
  return report;
13066
13239
  }
@@ -13762,7 +13935,9 @@ var DoorHealthEventSchema = z23.discriminatedUnion("kind", [
13762
13935
  z23.strictObject({ kind: z23.literal("generation"), pid: z23.number().int().positive(), providerPort: z23.number().int().positive(), connectPort: z23.number().int().positive(), directPort: z23.number().int().positive(), observedAt: z23.number() }),
13763
13936
  z23.strictObject({ kind: z23.literal("listenerFailed"), pid: z23.number().int().positive(), listener: z23.enum(["provider", "connect", "direct"]), message: z23.string(), observedAt: z23.number() }),
13764
13937
  z23.strictObject({ kind: z23.literal("binaryTurnover"), pid: z23.number().int().positive(), observedAt: z23.number() }),
13765
- z23.strictObject({ kind: z23.literal("idleShutdown"), pid: z23.number().int().positive(), observedAt: z23.number() })
13938
+ z23.strictObject({ kind: z23.literal("idleShutdown"), pid: z23.number().int().positive(), observedAt: z23.number() }),
13939
+ z23.strictObject({ kind: z23.literal("superseded"), pid: z23.number().int().positive(), ownerPid: z23.number().int().positive(), observedAt: z23.number() }),
13940
+ z23.strictObject({ kind: z23.literal("drained"), pid: z23.number().int().positive(), observedAt: z23.number() })
13766
13941
  ]);
13767
13942
  var QuotaExpiringEventSchema = z23.strictObject({
13768
13943
  identity: z23.string().min(1),
@@ -14259,6 +14434,12 @@ function createDoorHealthPublisher(hub, now, pid) {
14259
14434
  },
14260
14435
  binaryTurnover: () => {
14261
14436
  publisher.publish({ kind: "binaryTurnover", pid, observedAt: now() });
14437
+ },
14438
+ superseded: (ownerPid) => {
14439
+ publisher.publish({ kind: "superseded", pid, ownerPid, observedAt: now() });
14440
+ },
14441
+ drained: () => {
14442
+ publisher.publish({ kind: "drained", pid, observedAt: now() });
14262
14443
  }
14263
14444
  };
14264
14445
  }
@@ -4586,6 +4586,18 @@ type PathShadowStatus = {
4586
4586
  readonly by: string;
4587
4587
  };
4588
4588
 
4589
+ /** One input the headroom allowlist is computed from: the provider definitions on disk, or the front door's direct origin. */
4590
+ type AllowlistDriftInput = "provider-files" | "front-door-origin";
4591
+
4592
+ /** What listing the front-door supervisors found: their pids, or why the process table could not be read. */
4593
+ type FrontDoorSupervisorListing = {
4594
+ readonly ok: true;
4595
+ readonly pids: readonly number[];
4596
+ } | {
4597
+ readonly ok: false;
4598
+ readonly message: string;
4599
+ };
4600
+
4589
4601
  /** The result of a successful discovery. */
4590
4602
  interface DiscoveredClaudeBinary {
4591
4603
  /** Full path to the discovered `claude` executable. */
@@ -4604,7 +4616,7 @@ type ClaudeBinaryResolver = (request?: Readonly<ClaudeBinaryRequest>) => Discove
4604
4616
 
4605
4617
  declare const DOCTOR_SEVERITIES: readonly ["pass", "warn", "fail"];
4606
4618
  type DoctorSeverity = (typeof DOCTOR_SEVERITIES)[number];
4607
- declare const DOCTOR_SECTIONS: readonly ["ambient-credential", "binary-discovery", "claude-shim", "path-resolution", "legacy-name", "config-profile", "identity", "sign-in", "pool", "provider", "keychain", "directory-rules", "global-config", "categories-local", "active-identity", "headroom"];
4619
+ declare const DOCTOR_SECTIONS: readonly ["ambient-credential", "binary-discovery", "claude-shim", "path-resolution", "legacy-name", "config-profile", "identity", "sign-in", "pool", "provider", "keychain", "directory-rules", "global-config", "categories-local", "active-identity", "headroom", "frontdoor"];
4608
4620
  type DoctorSection = (typeof DOCTOR_SECTIONS)[number];
4609
4621
  /** One line of `agent-shim doctor`'s report. `subject` names the identity/profile/rule the finding is about, when the section has more than one of those. */
4610
4622
  interface DoctorFinding {
@@ -4700,6 +4712,18 @@ interface RunDoctorParams {
4700
4712
  readonly headroom: {
4701
4713
  readonly state: DoctorFileInput;
4702
4714
  readonly isRunning: (pid: number) => boolean;
4715
+ /** What the daemon's allowlist is measured against. Omit to skip the allowlist check; with it, a daemon whose allowlist may exclude the serving door's direct origin is a failure. */
4716
+ readonly allowlist?: {
4717
+ /** The inputs that could explain the recorded allowlist hash not matching the current allowlist, or undefined when it matches. */
4718
+ readonly driftInputs: readonly AllowlistDriftInput[] | undefined;
4719
+ /** The serving front door's direct origin, or undefined before it has ever served. */
4720
+ readonly frontDoorOrigin: string | undefined;
4721
+ };
4722
+ };
4723
+ /** The front door's state.json and the supervisor processes alive for this user. Omit to skip the front-door checks (the platform cannot list processes); with it, more than one supervisor alive at once is a failure naming each superseded one. */
4724
+ readonly frontDoor?: {
4725
+ readonly state: DoctorFileInput;
4726
+ readonly supervisors: FrontDoorSupervisorListing;
4703
4727
  };
4704
4728
  }
4705
4729
  /**
@@ -4888,11 +4912,14 @@ declare const UsageSnapshotSchema: z.ZodObject<{
4888
4912
  }, z.core.$strict>;
4889
4913
  type UsageSnapshot = z.infer<typeof UsageSnapshotSchema>;
4890
4914
 
4891
- /** The record held in one front-door session-registry file: the launcher's pid plus the per-launch capability token its child presents on every request. */
4915
+ /**
4916
+ * The record held in one front-door session-registry file: the launcher's pid plus the per-launch capability token its child presents on every request, and the supervisor of the door the launch was sent to. `supervisorPid` is absent on a record written before it existed and on a door's own transparent-surface record; such a launch cannot be attributed to a door, so a superseded door treats it as its own.
4917
+ */
4892
4918
  declare const FrontDoorSessionSchema: z.ZodObject<{
4893
4919
  pid: z.ZodNumber;
4894
4920
  startedAt: z.ZodNumber;
4895
4921
  token: z.ZodString;
4922
+ supervisorPid: z.ZodOptional<z.ZodNumber>;
4896
4923
  }, z.core.$strict>;
4897
4924
  type FrontDoorSession = z.infer<typeof FrontDoorSessionSchema>;
4898
4925
  /**
@@ -4911,7 +4938,7 @@ declare const FrontDoorStateSchema: z.ZodObject<{
4911
4938
  }, z.core.$strict>;
4912
4939
  type FrontDoorState = z.infer<typeof FrontDoorStateSchema>;
4913
4940
  /** A registered launch as status and the idle decision see it: the pid and start time, never the token, so nothing that prints or serialises a session can leak the capability. */
4914
- type FrontDoorSessionSummary = Pick<FrontDoorSession, "pid" | "startedAt">;
4941
+ type FrontDoorSessionSummary = Pick<FrontDoorSession, "pid" | "startedAt" | "supervisorPid">;
4915
4942
 
4916
4943
  /** How a sign-out went: whether OpenAI was told, so the person knows the grant is dead on its side too. */
4917
4944
  interface SiwcLogoutResult {
@@ -6283,7 +6310,7 @@ declare const LaunchLifecycleEventSchema: z.ZodObject<{
6283
6310
  /** One launch lifecycle event, as the TypeScript the publisher emits and its consumers handle. */
6284
6311
  type LaunchLifecycleEvent = z.output<typeof LaunchLifecycleEventSchema>;
6285
6312
  /**
6286
- * One door health event: the supervisor's own state transitions, observed at the moment they happened in this process. `generation` is the door fully serving (every listener bound and the state file naming it); `listenerFailed` is a start failure that ends the generation, naming which listener and why; `binaryTurnover` is the door retiring because the installed binary changed and no session is live, so the next launch serves from the new one; `idleShutdown` is the door closing itself with an empty session registry.
6313
+ * One door health event: the supervisor's own state transitions, observed at the moment they happened in this process. `generation` is the door fully serving (every listener bound and the state file naming it); `listenerFailed` is a start failure that ends the generation, naming which listener and why; `binaryTurnover` is the door retiring because the installed binary changed and no session is live, so the next launch serves from the new one; `idleShutdown` is the door closing itself with an empty session registry; `superseded` is the state file coming to name another live supervisor, `ownerPid`, after which this door takes no new sessions; `drained` is a superseded door closing itself because every session it served has ended.
6287
6314
  */
6288
6315
  declare const DoorHealthEventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
6289
6316
  kind: z.ZodLiteral<"generation">;
@@ -6310,6 +6337,15 @@ declare const DoorHealthEventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
6310
6337
  kind: z.ZodLiteral<"idleShutdown">;
6311
6338
  pid: z.ZodNumber;
6312
6339
  observedAt: z.ZodNumber;
6340
+ }, z.core.$strict>, z.ZodObject<{
6341
+ kind: z.ZodLiteral<"superseded">;
6342
+ pid: z.ZodNumber;
6343
+ ownerPid: z.ZodNumber;
6344
+ observedAt: z.ZodNumber;
6345
+ }, z.core.$strict>, z.ZodObject<{
6346
+ kind: z.ZodLiteral<"drained">;
6347
+ pid: z.ZodNumber;
6348
+ observedAt: z.ZodNumber;
6313
6349
  }, z.core.$strict>], "kind">;
6314
6350
  /** One door health event, as the TypeScript the publisher emits and its consumers handle. */
6315
6351
  type DoorHealthEvent = z.output<typeof DoorHealthEventSchema>;
@@ -7387,6 +7423,7 @@ declare function createControlApiRouter(deps: ControlApiDeps): {
7387
7423
  "config-profile": "config-profile";
7388
7424
  identity: "identity";
7389
7425
  provider: "provider";
7426
+ frontdoor: "frontdoor";
7390
7427
  pool: "pool";
7391
7428
  keychain: "keychain";
7392
7429
  "ambient-credential": "ambient-credential";
@@ -9369,6 +9406,7 @@ declare function createDoorApiRouter(deps: DoorApiDeps): {
9369
9406
  "config-profile": "config-profile";
9370
9407
  identity: "identity";
9371
9408
  provider: "provider";
9409
+ frontdoor: "frontdoor";
9372
9410
  pool: "pool";
9373
9411
  keychain: "keychain";
9374
9412
  "ambient-credential": "ambient-credential";
@@ -11175,6 +11213,7 @@ declare const DoctorFindingSchema: z.ZodObject<{
11175
11213
  "config-profile": "config-profile";
11176
11214
  identity: "identity";
11177
11215
  provider: "provider";
11216
+ frontdoor: "frontdoor";
11178
11217
  pool: "pool";
11179
11218
  keychain: "keychain";
11180
11219
  "ambient-credential": "ambient-credential";
@@ -11205,6 +11244,7 @@ declare const DoctorRunOutputSchema: z.ZodObject<{
11205
11244
  "config-profile": "config-profile";
11206
11245
  identity: "identity";
11207
11246
  provider: "provider";
11247
+ frontdoor: "frontdoor";
11208
11248
  pool: "pool";
11209
11249
  keychain: "keychain";
11210
11250
  "ambient-credential": "ambient-credential";
@@ -11397,7 +11437,7 @@ interface LaunchEventPublisher {
11397
11437
  declare function createLaunchEventPublisher(hub: DoorEventHub, now: () => number): LaunchEventPublisher;
11398
11438
 
11399
11439
  /**
11400
- * The door health publisher: the supervisor's own state transitions as backbone events, so a consumer of the door learns the door itself changed and not only its sessions. The publisher emits nothing of its own: the supervisor tells it the moments it already lives through (every listener bound, a start failure that ends the generation, the binary turnover, the idle shutdown), each published the moment it happens rather than at a tick, because these are process facts, not registry facts diffed between observations.
11440
+ * The door health publisher: the supervisor's own state transitions as backbone events, so a consumer of the door learns the door itself changed and not only its sessions. The publisher emits nothing of its own: the supervisor tells it the moments it already lives through (every listener bound, a start failure that ends the generation, the binary turnover, the idle shutdown, the supersession and the drained exit that follows it), each published the moment it happens rather than at a tick, because these are process facts, not registry facts diffed between observations.
11401
11441
  */
11402
11442
  /** Everything the door health publisher needs: the backbone it publishes on and the clock its events are observed at. */
11403
11443
  interface DoorHealthPublisher {
@@ -11413,6 +11453,10 @@ interface DoorHealthPublisher {
11413
11453
  readonly binaryTurnover: () => void;
11414
11454
  /** Publishes the idle shutdown: the door closing itself with an empty session registry. */
11415
11455
  readonly idleShutdown: () => void;
11456
+ /** Publishes the supersession: the state file naming another live supervisor, `ownerPid`, so this door takes no new sessions and serves only those it already had. */
11457
+ readonly superseded: (ownerPid: number) => void;
11458
+ /** Publishes the drained exit: a superseded door closing itself because every session it served has ended. */
11459
+ readonly drained: () => void;
11416
11460
  }
11417
11461
  /** Creates the door health publisher. One per door process, fed by the supervisor. */
11418
11462
  declare function createDoorHealthPublisher(hub: DoorEventHub, now: () => number, pid: number): DoorHealthPublisher;
@@ -11662,8 +11706,10 @@ interface EnsureFrontDoorPorts {
11662
11706
  readonly spawnSupervisor: (paths: LayoutPaths) => number;
11663
11707
  /** Authenticates the provider listener on `port`: a TLS handshake chaining to agent-shim's CA and a healthy answer, with no capability sent (see `probeFrontDoorSync`). */
11664
11708
  readonly verifyListener: (port: number) => ListenerVerdict;
11665
- /** Asks a running front-door supervisor to exit, without waiting for it to: the caller polls `isRunning`. Only ever called for a pid whose recorded listener has just authenticated as agent-shim's own. */
11709
+ /** Asks a running front-door supervisor to exit, without waiting for it to: the caller polls `isRunning`. Only ever called for a pid whose recorded listener has just authenticated as agent-shim's own, or whose process is agent-shim's front-door supervisor (`isFrontDoorSupervisor`). */
11666
11710
  readonly stopSupervisor: (pid: number) => void;
11711
+ /** Whether `pid` is a live agent-shim front-door supervisor process, read from the process's own command line. This is what separates a wedged door of this tool, which a replacement must retire so the two never coexist, from an unrelated process that reuses the recorded pid, which must never be signalled. */
11712
+ readonly isFrontDoorSupervisor: (pid: number) => boolean;
11667
11713
  }
11668
11714
  /**
11669
11715
  * Brings the front door up for this launch, or finds it serving, and registers this launcher pid in its session registry: the fact that keeps the door from idling out while this session lives, and the record that holds this launch's capability token. The same lock-and-poll coordination as headroom's and the old codex daemon's ensure: the exclusive-create start lock decides which of several concurrent launches spawns the one supervisor, everyone waits on state.json, and a lock whose holder died is removed and retried. Ready means state names a live supervisor and both listeners' ports, which the supervisor writes only after both have bound (and the provider listener has answered its own health probe).
@@ -11751,7 +11797,7 @@ interface RunFrontDoorSupervisorOptions {
11751
11797
  readonly tickLimit?: number;
11752
11798
  }
11753
11799
  /**
11754
- * Runs the front-door supervisor: binds all three listeners (the HTTPS provider listener, the CONNECT surface and the plain-HTTP direct listener, each on its sticky port), records the serving state, and shuts down with an empty session registry, either at the first such tick after the executable it serves as has been replaced on disk (so an applied update reaches the door without waiting out the idle window) or after `idleShutdownMinutes` of it. Returns the exit code: 0 for either retirement, 1 for a fatal start failure.
11800
+ * Runs the front-door supervisor: binds all three listeners (the HTTPS provider listener, the CONNECT surface and the plain-HTTP direct listener, each on its sticky port), records the serving state, and shuts down with an empty session registry, either at the first such tick after the executable it serves as has been replaced on disk (so an applied update reaches the door without waiting out the idle window) or after `idleShutdownMinutes` of it. A door whose state file comes to name another live supervisor is superseded: from then on it takes no new sessions, never writes the state file again, and exits as soon as the sessions it was serving have ended. Returns the exit code: 0 for any of these retirements, 1 for a fatal start failure.
11755
11801
  *
11756
11802
  * The listeners live in this process, so there is no child to keep alive: a crash of this process is a crash of every routed session's door at once, and recovery is the next launch's ensure spawning a replacement on the same sticky ports, which is exactly the failure model the issue asks to settle with tests. Keeping the headroom daemon alive is the headroom supervisor's job, not this one's; the two daemons idle out independently.
11757
11803
  */
@@ -11902,6 +11948,8 @@ interface Candidate {
11902
11948
  readonly headroom?: number;
11903
11949
  /** When an `ineligible` candidate can be used again. */
11904
11950
  readonly blockedUntilMs?: number;
11951
+ /** When the exhausted plan window of a `pay-per-use` candidate that is only on extra usage because its plan ran out resets, so a launch can hold for it rather than spend. Absent for a candidate that bills by use with no plan allowance. */
11952
+ readonly planReturnsAtMs?: number;
11905
11953
  readonly plan: PlanClass;
11906
11954
  /** True when the member's policy condition could not be decided and it is ranked last within its class, naming the missing fact in its reasons. */
11907
11955
  readonly policyDemoted?: boolean;
@@ -11918,6 +11966,11 @@ interface PoolRanking {
11918
11966
  readonly identity: string;
11919
11967
  readonly atMs: number;
11920
11968
  };
11969
+ /** When the pick continues on extra usage only because its plan ran out, the identity and the instant its plan window resets: what a launch that would rather wait than spend sleeps until. Absent when the pick is on plan quota, bills by use with no plan, or no member can be picked. */
11970
+ readonly planReturn?: {
11971
+ readonly identity: string;
11972
+ readonly atMs: number;
11973
+ };
11921
11974
  /** The member a resumed or continued conversation started on but moved off, and why, when a launch that resumes could not keep it. One sentence for the launch decision line and `check`. */
11922
11975
  readonly movedOff?: {
11923
11976
  readonly identity: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-use",
3
- "version": "9.12.0",
3
+ "version": "9.13.0",
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": "MIT",
6
6
  "author": "Joseph Mearman <joseph@mearman.co.uk>",