claude-use 4.5.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/index.cjs CHANGED
@@ -31,15 +31,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  AMBIENT_CREDENTIAL_VARS: () => AMBIENT_CREDENTIAL_VARS,
34
+ CONNECT_INTERCEPT_HOST: () => CONNECT_INTERCEPT_HOST,
35
+ CONNECT_LIMITS: () => CONNECT_LIMITS,
34
36
  FARM_MANIFEST_FILENAME: () => FARM_MANIFEST_FILENAME,
35
- HEADROOM_SERVED_PATH_PREFIX: () => HEADROOM_SERVED_PATH_PREFIX,
37
+ FrontDoorStartError: () => FrontDoorStartError,
36
38
  HeadroomStartError: () => HeadroomStartError,
37
- MITM_INTERCEPT_HOST: () => MITM_INTERCEPT_HOST,
39
+ ROUTED_PATH_PREFIX: () => ROUTED_PATH_PREFIX,
38
40
  buildEntryFacts: () => buildEntryFacts,
39
41
  carryOver: () => carryOver,
40
42
  createLeafCache: () => createLeafCache,
41
43
  detectAmbientCredential: () => detectAmbientCredential,
42
44
  ensureCa: () => ensureCa,
45
+ ensureFrontDoor: () => ensureFrontDoor,
43
46
  ensureHeadroom: () => ensureHeadroom,
44
47
  evaluateAmbientCredentialGuard: () => evaluateAmbientCredentialGuard,
45
48
  formatAmbientCredentialGuardMessage: () => formatAmbientCredentialGuardMessage,
@@ -49,16 +52,17 @@ __export(index_exports, {
49
52
  mintLeaf: () => mintLeaf,
50
53
  parseConnectTarget: () => parseConnectTarget,
51
54
  readFarmManifest: () => readFarmManifest,
52
- realMitmCertStore: () => realMitmCertStore,
53
- realMitmEffects: () => realMitmEffects,
55
+ realConnectCertStore: () => realConnectCertStore,
56
+ realConnectEffects: () => realConnectEffects,
54
57
  recoverFarm: () => recoverFarm,
55
58
  recoveryDiagnostics: () => recoveryDiagnostics,
56
59
  resolveDecisions: () => resolveDecisions,
57
60
  resolveSupervisorConfig: () => resolveSupervisorConfig,
58
61
  resyncFarm: () => resyncFarm,
62
+ runFrontDoorSupervisor: () => runFrontDoorSupervisor,
59
63
  runSupervisor: () => runSupervisor,
60
- servedByHeadroom: () => servedByHeadroom,
61
- startMitmServer: () => startMitmServer,
64
+ servedByPipeline: () => servedByPipeline,
65
+ startConnectServer: () => startConnectServer,
62
66
  topLevelNames: () => topLevelNames
63
67
  });
64
68
  module.exports = __toCommonJS(index_exports);
@@ -220,11 +224,36 @@ var PortableConfigSchema = ConfigProfileSchema.omit({ description: true }).exten
220
224
  identity: import_zod.z.string().min(1).optional(),
221
225
  when: WhenSchema.optional()
222
226
  });
223
- var HeadroomGlobalConfigSchema = import_zod.z.strictObject({
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({
224
233
  /** Install spec handed to `uv tool install`. Defaults to HEADROOM_DEFAULT_SOURCE. */
225
234
  source: import_zod.z.string().min(1).optional(),
226
235
  /** How long the daemon may sit with no registered sessions before the supervisor stops it. Defaults to HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES. */
227
- 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
+ }
228
257
  });
229
258
  var HEADROOM_DEFAULT_SOURCE = "headroom-ai[proxy] @ git+https://github.com/ExaDev/headroom@eb02fa4126450c9905a49394fdec19ae2e7c9c30";
230
259
  var HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES = 15;
@@ -1798,15 +1827,213 @@ function recoveryDiagnostics(recovery, identity) {
1798
1827
  ];
1799
1828
  }
1800
1829
 
1801
- // src/headroom/mitm.ts
1830
+ // src/frontdoor/connect.ts
1802
1831
  var fs = __toESM(require("node:fs"), 1);
1803
1832
  var http = __toESM(require("node:http"), 1);
1804
1833
  var https = __toESM(require("node:https"), 1);
1805
1834
  var net = __toESM(require("node:net"), 1);
1806
1835
  var tls = __toESM(require("node:tls"), 1);
1807
1836
  var import_node_forge = __toESM(require("node-forge"), 1);
1808
- var MITM_INTERCEPT_HOST = "api.anthropic.com";
1809
- var HEADROOM_SERVED_PATH_PREFIX = "/v1/";
1837
+
1838
+ // src/frontdoor/capability.ts
1839
+ var CONNECT_PROXY_REALM = "claude-use front door";
1840
+ var BASIC_SCHEME = "basic";
1841
+ function capabilityFromProxyAuthorization(value) {
1842
+ const trimmed = value.trim();
1843
+ const space = trimmed.indexOf(" ");
1844
+ if (space === -1 || trimmed.slice(0, space).toLowerCase() !== BASIC_SCHEME) {
1845
+ return void 0;
1846
+ }
1847
+ const userPass = Buffer.from(trimmed.slice(space + 1).trim(), "base64").toString("utf8");
1848
+ const colon = userPass.indexOf(":");
1849
+ if (colon === -1) {
1850
+ return void 0;
1851
+ }
1852
+ const password = userPass.slice(colon + 1);
1853
+ return password === "" ? void 0 : password;
1854
+ }
1855
+
1856
+ // src/frontdoor/state.ts
1857
+ var import_node_path5 = __toESM(require("node:path"), 1);
1858
+ var import_zod3 = require("zod");
1859
+ var FrontDoorSessionSchema = import_zod3.z.strictObject({ pid: import_zod3.z.number().int().positive(), startedAt: import_zod3.z.number(), token: import_zod3.z.string().min(1) });
1860
+ var FrontDoorStateSchema = import_zod3.z.strictObject({
1861
+ /** The supervisor process serving the front door. Alive means both listeners are up in that process. */
1862
+ supervisorPid: import_zod3.z.number().int().positive().optional(),
1863
+ /** The loopback port the HTTPS provider listener serves on. Absent until the listener has bound and answered its health probe, so "port is set" is itself the ready signal a launcher polls for. */
1864
+ port: import_zod3.z.number().int().positive().optional(),
1865
+ /**
1866
+ * The sticky port preference for the provider listener: kept across crashes, restarts and idle shutdowns so every provider session, whose base URL was frozen at launch, keeps finding the front door at the same address.
1867
+ */
1868
+ lastPort: import_zod3.z.number().int().positive().optional(),
1869
+ /**
1870
+ * The loopback port the CONNECT surface listens on. Absent whenever no supervisor is serving it, so "connectPort is set" is the ready signal an OAuth launch polls for alongside `port`.
1871
+ */
1872
+ connectPort: import_zod3.z.number().int().positive().optional(),
1873
+ /**
1874
+ * The sticky port preference for the CONNECT surface, the exact analogue of `lastPort` for the second listener: it survives every shutdown, because every OAuth session's environment was frozen at launch with HTTPS_PROXY pointing at this address and a restart that moves strands them.
1875
+ */
1876
+ lastConnectPort: import_zod3.z.number().int().positive().optional(),
1877
+ /**
1878
+ * The loopback port the direct listener serves on: the listener a headroom hop tells the daemon to forward routed traffic back to. Sticky like the others, because headroom's allowlist admits its exact origin and an ephemeral port would invalidate the allowlist on every restart.
1879
+ */
1880
+ directPort: import_zod3.z.number().int().positive().optional(),
1881
+ /** The sticky port preference for the direct listener, for the reason `directPort` gives. */
1882
+ lastDirectPort: import_zod3.z.number().int().positive().optional(),
1883
+ /** The last fatal error, kept across shutdowns so `frontdoor status` can explain a front door that is not running. */
1884
+ lastError: import_zod3.z.string().optional()
1885
+ });
1886
+ function readFrontDoorState(fs2, stateFile) {
1887
+ let raw;
1888
+ try {
1889
+ raw = fs2.readFileUtf8(stateFile);
1890
+ } catch {
1891
+ return void 0;
1892
+ }
1893
+ if (raw === void 0 || raw.trim() === "") {
1894
+ return void 0;
1895
+ }
1896
+ try {
1897
+ const parsed = FrontDoorStateSchema.safeParse(JSON.parse(raw));
1898
+ return parsed.success ? parsed.data : void 0;
1899
+ } catch {
1900
+ return void 0;
1901
+ }
1902
+ }
1903
+ function writeFrontDoorState(fs2, stateFile, state) {
1904
+ fs2.mkdirp(import_node_path5.default.dirname(stateFile));
1905
+ fs2.writeFileUtf8(stateFile, `${JSON.stringify(state, null, 2)}
1906
+ `);
1907
+ }
1908
+ function frontDoorOrigin(state) {
1909
+ const port = state?.directPort ?? state?.lastDirectPort;
1910
+ return port === void 0 ? void 0 : `http://127.0.0.1:${String(port)}`;
1911
+ }
1912
+ function writeFrontDoorSession(fs2, sessionsDir, session) {
1913
+ fs2.mkdirPrivate(sessionsDir);
1914
+ fs2.writeFilePrivate(import_node_path5.default.join(sessionsDir, `${String(session.pid)}.json`), `${JSON.stringify(session, null, 2)}
1915
+ `);
1916
+ }
1917
+ function listFrontDoorSessions(fs2, sessionsDir) {
1918
+ const sessions = [];
1919
+ for (const name of fs2.readdir(sessionsDir)) {
1920
+ if (!name.endsWith(".json")) {
1921
+ continue;
1922
+ }
1923
+ try {
1924
+ const parsed = FrontDoorSessionSchema.safeParse(JSON.parse(fs2.readFileUtf8(import_node_path5.default.join(sessionsDir, name)) ?? ""));
1925
+ if (parsed.success) {
1926
+ sessions.push({ pid: parsed.data.pid, startedAt: parsed.data.startedAt });
1927
+ }
1928
+ } catch {
1929
+ continue;
1930
+ }
1931
+ }
1932
+ return sessions.sort((a, b) => a.pid - b.pid);
1933
+ }
1934
+ function removeFrontDoorSession(fs2, sessionsDir, pid) {
1935
+ fs2.removeRecursive(import_node_path5.default.join(sessionsDir, `${String(pid)}.json`));
1936
+ }
1937
+ function pruneDeadFrontDoorSessions(fs2, sessionsDir, isRunning) {
1938
+ const removed = [];
1939
+ for (const session of listFrontDoorSessions(fs2, sessionsDir)) {
1940
+ if (!isRunning(session.pid)) {
1941
+ removeFrontDoorSession(fs2, sessionsDir, session.pid);
1942
+ removed.push(session.pid);
1943
+ }
1944
+ }
1945
+ return removed;
1946
+ }
1947
+
1948
+ // src/frontdoor/supervisor.ts
1949
+ var FRONTDOOR_SUPERVISOR_STILL_RUNNING = -1;
1950
+ var FRONTDOOR_POLL_MS = 1e3;
1951
+ var MS_PER_MINUTE = 6e4;
1952
+ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {}) {
1953
+ const { fs: fs2, paths } = ports;
1954
+ const previous = readFrontDoorState(fs2, paths.frontdoorStateFile);
1955
+ let lastPort = previous?.lastPort;
1956
+ let lastConnectPort = previous?.lastConnectPort;
1957
+ let lastDirectPort = previous?.lastDirectPort;
1958
+ const sticky = () => ({
1959
+ ...lastPort === void 0 ? {} : { lastPort },
1960
+ ...lastConnectPort === void 0 ? {} : { lastConnectPort },
1961
+ ...lastDirectPort === void 0 ? {} : { lastDirectPort }
1962
+ });
1963
+ const fail = (message) => {
1964
+ writeFrontDoorState(fs2, paths.frontdoorStateFile, { lastError: message, ...sticky() });
1965
+ fs2.removeRecursive(paths.frontdoorLockFile);
1966
+ ports.log(`claude-use frontdoor supervisor: ${message}`);
1967
+ return 1;
1968
+ };
1969
+ writeFrontDoorState(fs2, paths.frontdoorStateFile, { supervisorPid: ports.ownPid, ...sticky() });
1970
+ const previousSticky = lastPort;
1971
+ let http2;
1972
+ try {
1973
+ http2 = await ports.startProviderListener(lastPort);
1974
+ } catch (error) {
1975
+ return fail(`could not start the provider listener: ${error instanceof Error ? error.message : String(error)}`);
1976
+ }
1977
+ if (previousSticky !== void 0 && http2.port !== previousSticky) {
1978
+ ports.log(`claude-use frontdoor supervisor: sticky port ${String(previousSticky)} was occupied; moving to ${String(http2.port)} (sessions launched against the old port are stale until they relaunch)`);
1979
+ }
1980
+ lastPort = http2.port;
1981
+ const previousStickyConnect = lastConnectPort;
1982
+ let connect2;
1983
+ try {
1984
+ connect2 = await ports.startConnectListener(lastConnectPort);
1985
+ } catch (error) {
1986
+ await http2.close();
1987
+ return fail(`could not start the CONNECT surface: ${error instanceof Error ? error.message : String(error)}`);
1988
+ }
1989
+ if (previousStickyConnect !== void 0 && connect2.port !== previousStickyConnect) {
1990
+ ports.log(`claude-use frontdoor supervisor: sticky CONNECT port ${String(previousStickyConnect)} was occupied; moving to ${String(connect2.port)} (OAuth sessions launched against the old port are stale until they relaunch)`);
1991
+ }
1992
+ lastConnectPort = connect2.port;
1993
+ const previousStickyDirect = lastDirectPort;
1994
+ let direct;
1995
+ try {
1996
+ direct = await ports.startDirectListener(lastDirectPort);
1997
+ } catch (error) {
1998
+ await connect2.close();
1999
+ await http2.close();
2000
+ return fail(`could not start the direct listener: ${error instanceof Error ? error.message : String(error)}`);
2001
+ }
2002
+ if (previousStickyDirect !== void 0 && direct.port !== previousStickyDirect) {
2003
+ ports.log(`claude-use frontdoor supervisor: sticky direct port ${String(previousStickyDirect)} was occupied; moving to ${String(direct.port)} (headroom must be restarted to admit the new address)`);
2004
+ }
2005
+ lastDirectPort = direct.port;
2006
+ writeFrontDoorState(fs2, paths.frontdoorStateFile, { supervisorPid: ports.ownPid, port: http2.port, connectPort: connect2.port, directPort: direct.port, ...sticky() });
2007
+ fs2.removeRecursive(paths.frontdoorLockFile);
2008
+ ports.log(`claude-use frontdoor supervisor ${String(ports.ownPid)}: front door on 127.0.0.1:${String(http2.port)}, CONNECT surface on 127.0.0.1:${String(connect2.port)}`);
2009
+ let idleSince;
2010
+ let ticks = 0;
2011
+ for (; ; ) {
2012
+ if (options.tickLimit !== void 0 && ticks >= options.tickLimit) {
2013
+ return FRONTDOOR_SUPERVISOR_STILL_RUNNING;
2014
+ }
2015
+ ticks += 1;
2016
+ pruneDeadFrontDoorSessions(fs2, paths.frontdoorSessionsDir, ports.isRunning);
2017
+ if (listFrontDoorSessions(fs2, paths.frontdoorSessionsDir).length === 0) {
2018
+ idleSince ??= ports.now();
2019
+ if (ports.now() - idleSince >= idleShutdownMinutes * MS_PER_MINUTE) {
2020
+ ports.log(`claude-use frontdoor supervisor: no sessions for ${String(idleShutdownMinutes)} minute(s); closing the front door and exiting`);
2021
+ await direct.close();
2022
+ await connect2.close();
2023
+ await http2.close();
2024
+ writeFrontDoorState(fs2, paths.frontdoorStateFile, sticky());
2025
+ return 0;
2026
+ }
2027
+ } else {
2028
+ idleSince = void 0;
2029
+ }
2030
+ await ports.sleep(FRONTDOOR_POLL_MS);
2031
+ }
2032
+ }
2033
+
2034
+ // src/frontdoor/connect.ts
2035
+ var CONNECT_INTERCEPT_HOST = "api.anthropic.com";
2036
+ var ROUTED_PATH_PREFIX = "/v1/";
1810
2037
  var HTTPS_PORT = 443;
1811
2038
  var MAX_PORT = 65535;
1812
2039
  var SERIAL_NUMBER_BYTES = 16;
@@ -1816,9 +2043,22 @@ var CONNECT_LINE_TOKENS = 3;
1816
2043
  var HEAD_TERMINATOR = "\r\n\r\n";
1817
2044
  var MAX_CONNECT_HEAD_BYTES = 8192;
1818
2045
  var CONNECT_ESTABLISHED = "HTTP/1.1 200 Connection Established\r\n\r\n";
2046
+ var REFUSAL = {
2047
+ badRequest: "400 Bad Request",
2048
+ proxyAuthenticationRequired: "407 Proxy Authentication Required",
2049
+ requestTimeout: "408 Request Timeout",
2050
+ headTooLarge: "431 Request Header Fields Too Large",
2051
+ serviceUnavailable: "503 Service Unavailable"
2052
+ };
2053
+ var CONNECT_LIMITS = {
2054
+ headDeadlineMs: 1e4,
2055
+ maxPendingHeads: 64,
2056
+ maxTunnels: 1024,
2057
+ revalidateMs: FRONTDOOR_POLL_MS
2058
+ };
1819
2059
  var CA_VALIDITY_DAYS = 3650;
1820
2060
  var LEAF_VALIDITY_DAYS = 397;
1821
- var CA_SUBJECT_COMMON_NAME = "claude-use headroom CA";
2061
+ var CA_SUBJECT_COMMON_NAME = "claude-use front door CA";
1822
2062
  var MS_PER_DAY = 864e5;
1823
2063
  function parseConnectTarget(authority) {
1824
2064
  const colon = authority.lastIndexOf(":");
@@ -1836,8 +2076,8 @@ function parseConnectTarget(authority) {
1836
2076
  function isInterceptedHost(host, interceptHost) {
1837
2077
  return host === interceptHost;
1838
2078
  }
1839
- function servedByHeadroom(path6) {
1840
- return path6?.startsWith(HEADROOM_SERVED_PATH_PREFIX) === true;
2079
+ function servedByPipeline(path7) {
2080
+ return path7?.startsWith(ROUTED_PATH_PREFIX) === true;
1841
2081
  }
1842
2082
  var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
1843
2083
  "connection",
@@ -1885,7 +2125,13 @@ function generateCa(now) {
1885
2125
  cert.sign(keys.privateKey, import_node_forge.default.md.sha256.create());
1886
2126
  return { certPem: import_node_forge.default.pki.certificateToPem(cert), keyPem: import_node_forge.default.pki.privateKeyToPem(keys.privateKey) };
1887
2127
  }
1888
- function mintLeaf(ca, host, now) {
2128
+ var SAN_TYPE_DNS = 2;
2129
+ var SAN_TYPE_IP = 7;
2130
+ function mintLeaf(ca, names, now) {
2131
+ const [commonName] = names;
2132
+ if (commonName === void 0) {
2133
+ throw new Error("a leaf certificate needs at least one name");
2134
+ }
1889
2135
  const caCert = import_node_forge.default.pki.certificateFromPem(ca.certPem);
1890
2136
  const caKey = import_node_forge.default.pki.privateKeyFromPem(ca.keyPem);
1891
2137
  const keys = import_node_forge.default.pki.rsa.generateKeyPair({ bits: 2048 });
@@ -1894,13 +2140,13 @@ function mintLeaf(ca, host, now) {
1894
2140
  cert.serialNumber = import_node_forge.default.util.bytesToHex(import_node_forge.default.random.getBytesSync(SERIAL_NUMBER_BYTES));
1895
2141
  cert.validity.notBefore = new Date(now.getTime() - MS_PER_DAY);
1896
2142
  cert.validity.notAfter = new Date(now.getTime() + LEAF_VALIDITY_DAYS * MS_PER_DAY);
1897
- cert.setSubject([{ name: "commonName", value: host }]);
2143
+ cert.setSubject([{ name: "commonName", value: commonName }]);
1898
2144
  cert.setIssuer(caCert.subject.attributes);
1899
2145
  cert.setExtensions([
1900
2146
  { name: "basicConstraints", cA: false, critical: true },
1901
2147
  { name: "keyUsage", digitalSignature: true, keyEncipherment: true, critical: true },
1902
2148
  { name: "extKeyUsage", serverAuth: true },
1903
- { name: "subjectAltName", altNames: [{ type: 2, value: host }] },
2149
+ { name: "subjectAltName", altNames: names.map((name) => net.isIP(name) === 0 ? { type: SAN_TYPE_DNS, value: name } : { type: SAN_TYPE_IP, ip: name }) },
1904
2150
  { name: "subjectKeyIdentifier" },
1905
2151
  { name: "authorityKeyIdentifier", keyLocatorFields: [] }
1906
2152
  ]);
@@ -1914,7 +2160,7 @@ function createLeafCache(ca, now) {
1914
2160
  if (existing !== void 0) {
1915
2161
  return existing;
1916
2162
  }
1917
- const minted = mintLeaf(ca, host, now());
2163
+ const minted = mintLeaf(ca, [host], now());
1918
2164
  cache.set(host, minted);
1919
2165
  return minted;
1920
2166
  };
@@ -1937,17 +2183,10 @@ function caParses(ca) {
1937
2183
  return false;
1938
2184
  }
1939
2185
  }
1940
- async function startMitmServer(config, effects, preferredPort) {
2186
+ async function startConnectServer(config, effects, preferredPort) {
1941
2187
  const handler = (request3, response) => {
1942
- if (servedByHeadroom(request3.url)) {
1943
- const daemonPort = config.headroomPort();
1944
- if (daemonPort === void 0) {
1945
- const body = "claude-use headroom: the daemon is restarting";
1946
- response.writeHead(HTTP_BAD_GATEWAY, { "content-type": "text/plain", "content-length": String(body.length) });
1947
- response.end(body);
1948
- return;
1949
- }
1950
- effects.forwardHttp({ host: "127.0.0.1", port: daemonPort, tls: false }, request3, response);
2188
+ if (servedByPipeline(request3.url)) {
2189
+ config.serveRouted(request3, response);
1951
2190
  return;
1952
2191
  }
1953
2192
  effects.forwardHttp(config.upstream, request3, response);
@@ -1956,50 +2195,153 @@ async function startMitmServer(config, effects, preferredPort) {
1956
2195
  const tlsAcceptor = effects.createTlsAcceptor(config.leafFor(config.interceptHost), (secure) => {
1957
2196
  httpSession.serve(secure);
1958
2197
  });
2198
+ const ledger = createConnectionLedger(config.limits, config.isLiveCapability);
1959
2199
  const listener = await effects.listenLoopback(preferredPort, (socket) => {
1960
- handleConnect(socket, config, effects, tlsAcceptor);
2200
+ handleConnect(socket, { config, effects, tlsAcceptor, ledger });
1961
2201
  });
1962
2202
  return {
1963
2203
  port: listener.port,
1964
2204
  close: async () => {
2205
+ ledger.close();
1965
2206
  await listener.close();
1966
2207
  tlsAcceptor.close();
1967
2208
  httpSession.close();
1968
2209
  }
1969
2210
  };
1970
2211
  }
1971
- function handleConnect(socket, config, effects, tlsAcceptor) {
2212
+ function createConnectionLedger(limits, isLiveCapability) {
2213
+ const pending = /* @__PURE__ */ new Map();
2214
+ const authenticated = /* @__PURE__ */ new Map();
2215
+ let tunnels = 0;
2216
+ const leavePending = (socket) => {
2217
+ const deadline = pending.get(socket);
2218
+ if (deadline !== void 0) {
2219
+ clearTimeout(deadline);
2220
+ pending.delete(socket);
2221
+ }
2222
+ };
2223
+ const revalidation = setInterval(() => {
2224
+ for (const [capability, sockets] of authenticated) {
2225
+ if (!isLiveCapability(capability)) {
2226
+ for (const socket of sockets) {
2227
+ socket.destroy();
2228
+ }
2229
+ }
2230
+ }
2231
+ }, limits.revalidateMs);
2232
+ revalidation.unref();
2233
+ return {
2234
+ accept: (socket) => {
2235
+ const [oldest] = pending.keys();
2236
+ if (pending.size >= limits.maxPendingHeads && oldest !== void 0) {
2237
+ leavePending(oldest);
2238
+ refuse(oldest, REFUSAL.requestTimeout);
2239
+ }
2240
+ pending.set(
2241
+ socket,
2242
+ setTimeout(() => {
2243
+ leavePending(socket);
2244
+ refuse(socket, REFUSAL.requestTimeout);
2245
+ }, limits.headDeadlineMs)
2246
+ );
2247
+ socket.once("close", () => {
2248
+ leavePending(socket);
2249
+ });
2250
+ },
2251
+ headReceived: leavePending,
2252
+ establish: (socket, capability) => {
2253
+ if (tunnels >= limits.maxTunnels) {
2254
+ return false;
2255
+ }
2256
+ tunnels += 1;
2257
+ const sockets = authenticated.get(capability) ?? /* @__PURE__ */ new Set();
2258
+ sockets.add(socket);
2259
+ authenticated.set(capability, sockets);
2260
+ socket.once("close", () => {
2261
+ tunnels -= 1;
2262
+ sockets.delete(socket);
2263
+ if (sockets.size === 0 && authenticated.get(capability) === sockets) {
2264
+ authenticated.delete(capability);
2265
+ }
2266
+ });
2267
+ return true;
2268
+ },
2269
+ close: () => {
2270
+ clearInterval(revalidation);
2271
+ for (const deadline of pending.values()) {
2272
+ clearTimeout(deadline);
2273
+ }
2274
+ pending.clear();
2275
+ }
2276
+ };
2277
+ }
2278
+ function refuse(socket, status, headers = []) {
2279
+ socket.removeAllListeners("data");
2280
+ socket.pause();
2281
+ if (socket.destroyed || socket.writableEnded) {
2282
+ return;
2283
+ }
2284
+ socket.end(`HTTP/1.1 ${status}\r
2285
+ ${headers.map((header) => `${header}\r
2286
+ `).join("")}content-length: 0\r
2287
+ connection: close\r
2288
+ \r
2289
+ `, () => {
2290
+ socket.destroy();
2291
+ });
2292
+ }
2293
+ function handleConnect(socket, context) {
2294
+ socket.on("error", () => {
2295
+ socket.destroy();
2296
+ });
2297
+ context.ledger.accept(socket);
1972
2298
  let buffer = Buffer.alloc(0);
1973
2299
  const onData = (chunk) => {
1974
2300
  buffer = Buffer.concat([buffer, chunk]);
1975
2301
  const headEnd = buffer.indexOf(HEAD_TERMINATOR);
2302
+ if (headEnd === -1 ? buffer.length > MAX_CONNECT_HEAD_BYTES : headEnd > MAX_CONNECT_HEAD_BYTES) {
2303
+ context.ledger.headReceived(socket);
2304
+ refuse(socket, REFUSAL.headTooLarge);
2305
+ return;
2306
+ }
1976
2307
  if (headEnd === -1) {
1977
- if (buffer.length > MAX_CONNECT_HEAD_BYTES) {
1978
- socket.removeListener("data", onData);
1979
- socket.pause();
1980
- socket.destroy();
1981
- }
1982
2308
  return;
1983
2309
  }
1984
2310
  socket.removeListener("data", onData);
1985
2311
  socket.pause();
2312
+ context.ledger.headReceived(socket);
1986
2313
  const head = buffer.subarray(0, headEnd).toString("utf8");
1987
2314
  socket.unshift(buffer.subarray(headEnd + HEAD_TERMINATOR.length));
1988
- routeConnect(socket, head, config, effects, tlsAcceptor);
2315
+ routeConnect(socket, head, context);
1989
2316
  };
1990
2317
  socket.on("data", onData);
1991
- socket.on("error", () => {
1992
- socket.destroy();
1993
- });
1994
2318
  }
1995
- function routeConnect(socket, head, config, effects, tlsAcceptor) {
2319
+ function proxyAuthorizationOf(head) {
2320
+ const values = head.split("\r\n").slice(1).flatMap((line) => {
2321
+ const colon = line.indexOf(":");
2322
+ return colon > 0 && line.slice(0, colon).trim().toLowerCase() === "proxy-authorization" ? [line.slice(colon + 1)] : [];
2323
+ });
2324
+ const [value] = values;
2325
+ return values.length === 1 ? value : void 0;
2326
+ }
2327
+ function routeConnect(socket, head, context) {
2328
+ const { config, effects, tlsAcceptor, ledger } = context;
2329
+ const credential = proxyAuthorizationOf(head);
2330
+ const capability = credential === void 0 ? void 0 : capabilityFromProxyAuthorization(credential);
2331
+ if (capability === void 0 || !config.isLiveCapability(capability)) {
2332
+ refuse(socket, REFUSAL.proxyAuthenticationRequired, [`Proxy-Authenticate: Basic realm="${CONNECT_PROXY_REALM}"`]);
2333
+ return;
2334
+ }
1996
2335
  const requestLine = head.split("\r\n", 1)[0] ?? "";
1997
2336
  const parts = requestLine.split(" ");
1998
2337
  const authority = parts.length === CONNECT_LINE_TOKENS && parts[0]?.toUpperCase() === "CONNECT" ? parts[1] ?? "" : void 0;
1999
2338
  const target = authority === void 0 ? void 0 : parseConnectTarget(authority);
2000
2339
  if (target === void 0) {
2001
- socket.write("HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\n\r\n");
2002
- socket.destroy();
2340
+ refuse(socket, REFUSAL.badRequest);
2341
+ return;
2342
+ }
2343
+ if (!ledger.establish(socket, capability)) {
2344
+ refuse(socket, REFUSAL.serviceUnavailable);
2003
2345
  return;
2004
2346
  }
2005
2347
  socket.write(CONNECT_ESTABLISHED);
@@ -2017,6 +2359,10 @@ async function blindTunnel(socket, target, effects) {
2017
2359
  socket.destroy();
2018
2360
  return;
2019
2361
  }
2362
+ if (socket.destroyed) {
2363
+ upstream.destroy();
2364
+ return;
2365
+ }
2020
2366
  socket.pipe(upstream);
2021
2367
  upstream.pipe(socket);
2022
2368
  const drop = () => {
@@ -2032,7 +2378,7 @@ async function blindTunnel(socket, target, effects) {
2032
2378
  socket.destroy();
2033
2379
  });
2034
2380
  }
2035
- function realMitmEffects() {
2381
+ function realConnectEffects() {
2036
2382
  const plainAgent = new http.Agent({ keepAlive: true });
2037
2383
  const tlsAgent = new https.Agent({ keepAlive: true });
2038
2384
  const listenOnce = async (port, onSocket) => await new Promise((resolve, reject) => {
@@ -2161,7 +2507,7 @@ function realMitmEffects() {
2161
2507
  function isEnoent(error) {
2162
2508
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
2163
2509
  }
2164
- function realMitmCertStore(paths) {
2510
+ function realConnectCertStore(paths) {
2165
2511
  const read = (file) => {
2166
2512
  try {
2167
2513
  return fs.readFileSync(file, "utf8");
@@ -2174,54 +2520,51 @@ function realMitmCertStore(paths) {
2174
2520
  };
2175
2521
  return {
2176
2522
  loadCa: () => {
2177
- const certPem = read(paths.headroomCaCertFile);
2178
- const keyPem = read(paths.headroomCaKeyFile);
2523
+ const certPem = read(paths.frontdoorCaCertFile);
2524
+ const keyPem = read(paths.frontdoorCaKeyFile);
2179
2525
  return certPem === void 0 || keyPem === void 0 ? void 0 : { certPem, keyPem };
2180
2526
  },
2181
2527
  writeCa: (ca) => {
2182
- fs.mkdirSync(paths.headroomCaDir, { recursive: true });
2183
- fs.writeFileSync(paths.headroomCaCertFile, ca.certPem, "utf8");
2184
- fs.writeFileSync(paths.headroomCaKeyFile, ca.keyPem, { encoding: "utf8", mode: CA_KEY_FILE_MODE });
2185
- fs.chmodSync(paths.headroomCaKeyFile, CA_KEY_FILE_MODE);
2528
+ fs.mkdirSync(paths.frontdoorCaDir, { recursive: true });
2529
+ fs.writeFileSync(paths.frontdoorCaCertFile, ca.certPem, "utf8");
2530
+ fs.writeFileSync(paths.frontdoorCaKeyFile, ca.keyPem, { encoding: "utf8", mode: CA_KEY_FILE_MODE });
2531
+ fs.chmodSync(paths.frontdoorCaKeyFile, CA_KEY_FILE_MODE);
2186
2532
  }
2187
2533
  };
2188
2534
  }
2189
2535
 
2536
+ // src/frontdoor/ensure.ts
2537
+ var import_node_crypto3 = require("node:crypto");
2538
+
2190
2539
  // src/headroom/state.ts
2191
2540
  var import_node_crypto2 = require("node:crypto");
2192
- var import_node_path5 = __toESM(require("node:path"), 1);
2193
- var import_zod3 = require("zod");
2541
+ var import_node_path6 = __toESM(require("node:path"), 1);
2542
+ var import_zod4 = require("zod");
2194
2543
  var HEADROOM_ANTHROPIC_UPSTREAM = "https://api.anthropic.com";
2195
- var HeadroomStateSchema = import_zod3.z.strictObject({
2544
+ var HeadroomStateSchema = import_zod4.z.strictObject({
2196
2545
  /** The supervisor process owning the daemon. Alive means someone is keeping headroom running. */
2197
- supervisorPid: import_zod3.z.number().int().positive().optional(),
2546
+ supervisorPid: import_zod4.z.number().int().positive().optional(),
2198
2547
  /** The headroom proxy process itself. Absent while the supervisor is between restarts. */
2199
- headroomPid: import_zod3.z.number().int().positive().optional(),
2548
+ headroomPid: import_zod4.z.number().int().positive().optional(),
2200
2549
  /** The loopback port the proxy listens on. Absent until the proxy has passed its readiness check, so "port is set" is itself the ready signal a launcher polls for. */
2201
- port: import_zod3.z.number().int().positive().optional(),
2202
- /**
2203
- * The loopback port the supervisor's in-process MITM CONNECT proxy listens on. Absent whenever no supervisor is serving it, so "mitmPort is set" is the ready signal an OAuth launch polls for alongside `port`.
2204
- */
2205
- mitmPort: import_zod3.z.number().int().positive().optional(),
2206
- /**
2207
- * The sticky MITM port preference, the exact analogue of `lastPort` for the second server: it survives every shutdown, because every OAuth session's environment was frozen at launch with HTTPS_PROXY pointing at this address and a restart that moves strands them.
2208
- */
2209
- lastMitmPort: import_zod3.z.number().int().positive().optional(),
2550
+ port: import_zod4.z.number().int().positive().optional(),
2210
2551
  /**
2211
- * The sticky port preference: the address the daemon last served on, kept across crashes, restarts, and idle shutdowns so the next start reuses it. Distinct from `port` on purpose: `port` is the ready signal (absent whenever nothing is serving), while `lastPort` survives every shutdown, because every live session's environment was frozen at launch pointing at this address and a restart that moves strands them.
2552
+ * The sticky port preference: the address the daemon last served on, kept across crashes, restarts, and idle shutdowns so the next start reuses it. Distinct from `port` on purpose: `port` is the ready signal (absent whenever nothing is serving), while `lastPort` survives every shutdown, because the front door (which routes sessions through this daemon) reads it live and follows a restart to whichever address it lands on.
2212
2553
  */
2213
- lastPort: import_zod3.z.number().int().positive().optional(),
2554
+ lastPort: import_zod4.z.number().int().positive().optional(),
2214
2555
  /** The `headroom --version` output of the running install. */
2215
- version: import_zod3.z.string().optional(),
2556
+ version: import_zod4.z.string().optional(),
2216
2557
  /** Hash of the allowlist the running proxy was started with, so a provider-file change is detected as drift. */
2217
- allowlistHash: import_zod3.z.string().optional(),
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(),
2218
2561
  /** The install spec the running install was last installed from, so a `source` config change is detected as drift. */
2219
- installedSource: import_zod3.z.string().optional(),
2562
+ installedSource: import_zod4.z.string().optional(),
2220
2563
  /** The last fatal error, kept across shutdowns so `headroom status` can explain a daemon that is not running. */
2221
- lastError: import_zod3.z.string().optional()
2564
+ lastError: import_zod4.z.string().optional()
2222
2565
  });
2223
- var HeadroomSessionSchema = import_zod3.z.strictObject({ pid: import_zod3.z.number().int().positive(), startedAt: import_zod3.z.number() });
2224
- var HeadroomLockSchema = import_zod3.z.strictObject({ pid: import_zod3.z.number().int().positive(), at: import_zod3.z.number() });
2566
+ var HeadroomSessionSchema = import_zod4.z.strictObject({ pid: import_zod4.z.number().int().positive(), startedAt: import_zod4.z.number() });
2567
+ var HeadroomLockSchema = import_zod4.z.strictObject({ pid: import_zod4.z.number().int().positive(), at: import_zod4.z.number() });
2225
2568
  function parseJson(raw, schema) {
2226
2569
  if (raw === void 0 || raw.trim() === "") {
2227
2570
  return void 0;
@@ -2237,7 +2580,7 @@ function readHeadroomState(fs2, stateFile) {
2237
2580
  }
2238
2581
  }
2239
2582
  function writeHeadroomState(fs2, stateFile, state) {
2240
- fs2.mkdirp(import_node_path5.default.dirname(stateFile));
2583
+ fs2.mkdirp(import_node_path6.default.dirname(stateFile));
2241
2584
  fs2.writeFileUtf8(stateFile, `${JSON.stringify(state, null, 2)}
2242
2585
  `);
2243
2586
  }
@@ -2249,16 +2592,8 @@ function headroomAllowlist(providers) {
2249
2592
  return [...urls].sort();
2250
2593
  }
2251
2594
  function headroomUpstreams(providers, frontDoorOrigin2) {
2252
- const upstreams = [];
2253
- let hasCodex = false;
2254
- for (const provider of providers) {
2255
- if (isCodexProvider(provider)) {
2256
- hasCodex = true;
2257
- } else {
2258
- upstreams.push({ baseUrl: provider.baseUrl });
2259
- }
2260
- }
2261
- if (hasCodex && frontDoorOrigin2 !== void 0) {
2595
+ const upstreams = providers.filter((provider) => !isCodexProvider(provider)).map((provider) => ({ baseUrl: provider.baseUrl }));
2596
+ if (providers.length > 0 && frontDoorOrigin2 !== void 0) {
2262
2597
  upstreams.push({ baseUrl: frontDoorOrigin2 });
2263
2598
  }
2264
2599
  return upstreams;
@@ -2273,7 +2608,7 @@ function readAllProviders(fs2, providersDir) {
2273
2608
  continue;
2274
2609
  }
2275
2610
  try {
2276
- const parsed = parseJson(fs2.readFileUtf8(import_node_path5.default.join(providersDir, name)), ProviderSchema);
2611
+ const parsed = parseJson(fs2.readFileUtf8(import_node_path6.default.join(providersDir, name)), ProviderSchema);
2277
2612
  if (parsed !== void 0) {
2278
2613
  result.push({ name: name.slice(0, -".json".length), provider: parsed });
2279
2614
  }
@@ -2285,11 +2620,11 @@ function readAllProviders(fs2, providersDir) {
2285
2620
  }
2286
2621
  function writeSession(fs2, sessionsDir, session) {
2287
2622
  fs2.mkdirp(sessionsDir);
2288
- fs2.writeFileUtf8(import_node_path5.default.join(sessionsDir, `${String(session.pid)}.json`), `${JSON.stringify(session, null, 2)}
2623
+ fs2.writeFileUtf8(import_node_path6.default.join(sessionsDir, `${String(session.pid)}.json`), `${JSON.stringify(session, null, 2)}
2289
2624
  `);
2290
2625
  }
2291
2626
  function removeSession(fs2, sessionsDir, pid) {
2292
- fs2.removeRecursive(import_node_path5.default.join(sessionsDir, `${String(pid)}.json`));
2627
+ fs2.removeRecursive(import_node_path6.default.join(sessionsDir, `${String(pid)}.json`));
2293
2628
  }
2294
2629
  function listSessions(fs2, sessionsDir) {
2295
2630
  const sessions = [];
@@ -2298,7 +2633,7 @@ function listSessions(fs2, sessionsDir) {
2298
2633
  continue;
2299
2634
  }
2300
2635
  try {
2301
- const parsed = parseJson(fs2.readFileUtf8(import_node_path5.default.join(sessionsDir, name)), HeadroomSessionSchema);
2636
+ const parsed = parseJson(fs2.readFileUtf8(import_node_path6.default.join(sessionsDir, name)), HeadroomSessionSchema);
2302
2637
  if (parsed !== void 0) {
2303
2638
  sessions.push(parsed);
2304
2639
  }
@@ -2326,6 +2661,67 @@ function readStartLock(fs2, lockFile) {
2326
2661
  }
2327
2662
  }
2328
2663
 
2664
+ // src/frontdoor/ensure.ts
2665
+ var FRONTDOOR_START_TIMEOUT_MS = 3e4;
2666
+ var FRONTDOOR_LAUNCHER_POLL_MS = 100;
2667
+ var FrontDoorStartError = class extends CliError {
2668
+ constructor(message) {
2669
+ super(message);
2670
+ this.name = "FrontDoorStartError";
2671
+ }
2672
+ };
2673
+ function ensureFrontDoor(params) {
2674
+ const { paths, ports } = params;
2675
+ const deadline = ports.now() + FRONTDOOR_START_TIMEOUT_MS;
2676
+ let spawned = false;
2677
+ const distrusted = /* @__PURE__ */ new Set();
2678
+ let lastRejection;
2679
+ for (; ; ) {
2680
+ const state = readFrontDoorState(ports.fs, paths.frontdoorStateFile);
2681
+ const supervisorPid = state?.supervisorPid;
2682
+ if (supervisorPid !== void 0 && !distrusted.has(supervisorPid) && ports.isRunning(supervisorPid)) {
2683
+ if (state?.port !== void 0 && state.connectPort !== void 0) {
2684
+ const verdict = ports.verifyListener(state.port);
2685
+ if (verdict.ok) {
2686
+ const token = (0, import_node_crypto3.randomUUID)();
2687
+ writeFrontDoorSession(ports.fs, paths.frontdoorSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), token });
2688
+ return { port: state.port, connectPort: state.connectPort, token };
2689
+ }
2690
+ lastRejection = `the listener on 127.0.0.1:${String(state.port)} (recorded for pid ${String(supervisorPid)}) failed authentication: ${verdict.reason}`;
2691
+ if (spawned) {
2692
+ throw new FrontDoorStartError(`claude-use: ${lastRejection}; refusing to send this session's credentials to it. Daemon log: ${paths.frontdoorLogPath}`);
2693
+ }
2694
+ distrusted.add(supervisorPid);
2695
+ continue;
2696
+ }
2697
+ if (state?.lastError !== void 0) {
2698
+ throw new FrontDoorStartError(`claude-use: the front door reported a fatal error and is not serving: ${state.lastError} (daemon log: ${paths.frontdoorLogPath})`);
2699
+ }
2700
+ } else if (!spawned) {
2701
+ ports.fs.mkdirp(paths.frontdoorDir);
2702
+ const created = ports.fs.writeFileExclusive(paths.frontdoorLockFile, `${JSON.stringify({ pid: params.launcherPid, at: ports.now() })}
2703
+ `);
2704
+ if (created) {
2705
+ ports.spawnSupervisor(paths);
2706
+ spawned = true;
2707
+ } else {
2708
+ const lock = readStartLock(ports.fs, paths.frontdoorLockFile);
2709
+ if (lock === void 0 || !ports.isRunning(lock.pid)) {
2710
+ ports.fs.removeRecursive(paths.frontdoorLockFile);
2711
+ continue;
2712
+ }
2713
+ }
2714
+ }
2715
+ if (ports.now() >= deadline) {
2716
+ const lastError = state?.lastError;
2717
+ throw new FrontDoorStartError(
2718
+ `claude-use: the front door did not become ready within ${String(FRONTDOOR_START_TIMEOUT_MS)}ms` + (lastError === void 0 ? "" : ` (last error: ${lastError})`) + (lastRejection === void 0 ? "" : ` (${lastRejection})`) + `. Daemon log: ${paths.frontdoorLogPath}`
2719
+ );
2720
+ }
2721
+ ports.sleep(FRONTDOOR_LAUNCHER_POLL_MS);
2722
+ }
2723
+ }
2724
+
2329
2725
  // src/headroom/ensure.ts
2330
2726
  var HEADROOM_START_TIMEOUT_MS = 12e4;
2331
2727
  var HEADROOM_LAUNCHER_POLL_MS = 250;
@@ -2342,10 +2738,10 @@ function ensureHeadroom(params) {
2342
2738
  for (; ; ) {
2343
2739
  const state = readHeadroomState(ports.fs, paths.headroomStateFile);
2344
2740
  if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
2345
- const daemonUp = state.port !== void 0 && state.mitmPort !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
2346
- if (daemonUp && state.port !== void 0 && state.mitmPort !== void 0) {
2741
+ const daemonUp = state.port !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
2742
+ if (daemonUp && state.port !== void 0) {
2347
2743
  writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now() });
2348
- return { port: state.port, mitmPort: state.mitmPort };
2744
+ return { port: state.port };
2349
2745
  }
2350
2746
  if (state.lastError !== void 0) {
2351
2747
  throw new HeadroomStartError(
@@ -2380,47 +2776,41 @@ function ensureHeadroom(params) {
2380
2776
  }
2381
2777
  }
2382
2778
 
2383
- // src/frontdoor/state.ts
2384
- var import_zod4 = require("zod");
2385
- var FrontDoorStateSchema = import_zod4.z.strictObject({
2386
- /** The supervisor process serving the front door. Alive means the listener is up in that process. */
2387
- supervisorPid: import_zod4.z.number().int().positive().optional(),
2388
- /** The loopback port the front-door listener serves on. Absent until the listener has bound and answered its health probe, so "port is set" is itself the ready signal a launcher polls for. */
2389
- port: import_zod4.z.number().int().positive().optional(),
2390
- /**
2391
- * The sticky port preference: kept across crashes, restarts and idle shutdowns so every session, whose base URL was frozen at launch, keeps finding the front door at the same address.
2392
- */
2393
- lastPort: import_zod4.z.number().int().positive().optional(),
2394
- /** The last fatal error, kept across shutdowns so `frontdoor status` can explain a front door that is not running. */
2395
- lastError: import_zod4.z.string().optional()
2396
- });
2397
- function readFrontDoorState(fs2, stateFile) {
2398
- let raw;
2399
- try {
2400
- raw = fs2.readFileUtf8(stateFile);
2401
- } catch {
2402
- return void 0;
2403
- }
2404
- if (raw === void 0 || raw.trim() === "") {
2405
- return void 0;
2406
- }
2407
- try {
2408
- const parsed = FrontDoorStateSchema.safeParse(JSON.parse(raw));
2409
- return parsed.success ? parsed.data : void 0;
2410
- } catch {
2411
- return void 0;
2412
- }
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
+ };
2413
2790
  }
2414
- function frontDoorOrigin(state) {
2415
- const port = state?.port ?? state?.lastPort;
2416
- return port === void 0 ? void 0 : `http://127.0.0.1:${String(port)}`;
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");
2417
2806
  }
2418
2807
 
2419
2808
  // src/headroom/supervisor.ts
2420
2809
  function resolveSupervisorConfig(configured) {
2421
2810
  return {
2422
2811
  source: configured.source ?? HEADROOM_DEFAULT_SOURCE,
2423
- idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES
2812
+ idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES,
2813
+ settings: settingsOf(configured)
2424
2814
  };
2425
2815
  }
2426
2816
  var HEADROOM_BACKOFF_BASE_MS = 500;
@@ -2428,7 +2818,7 @@ var HEADROOM_BACKOFF_CAP_MS = 3e4;
2428
2818
  var HEADROOM_START_RETRY_BUDGET = 5;
2429
2819
  var HEADROOM_READY_TIMEOUT_MS = 15e3;
2430
2820
  var HEADROOM_POLL_MS = 1e3;
2431
- var MS_PER_MINUTE = 6e4;
2821
+ var MS_PER_MINUTE2 = 6e4;
2432
2822
  function backoffForAttempt(attempt) {
2433
2823
  return Math.min(HEADROOM_BACKOFF_CAP_MS, HEADROOM_BACKOFF_BASE_MS * 2 ** (attempt - 1));
2434
2824
  }
@@ -2483,18 +2873,13 @@ function compareVersions(a, b) {
2483
2873
  return 0;
2484
2874
  }
2485
2875
  var HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
2486
- async function runSupervisor(config, ports, options = {}) {
2876
+ async function runSupervisor(initialConfig, ports, options = {}) {
2487
2877
  const { fs: fs2, paths } = ports;
2878
+ let config = initialConfig;
2488
2879
  const previousState = readHeadroomState(fs2, paths.headroomStateFile);
2489
2880
  let installedSource = previousState?.installedSource;
2490
2881
  let lastPort = previousState?.lastPort;
2491
- let lastMitmPort = previousState?.lastMitmPort;
2492
- let daemonPort;
2493
- const sticky = () => ({
2494
- ...lastPort === void 0 ? {} : { lastPort },
2495
- ...lastMitmPort === void 0 ? {} : { lastMitmPort }
2496
- });
2497
- let mitm;
2882
+ const sticky = () => lastPort === void 0 ? {} : { lastPort };
2498
2883
  const fail = (message) => {
2499
2884
  writeHeadroomState(fs2, paths.headroomStateFile, {
2500
2885
  lastError: message,
@@ -2523,18 +2908,7 @@ async function runSupervisor(config, ports, options = {}) {
2523
2908
  return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
2524
2909
  }
2525
2910
  const version = ports.headroomVersion() ?? "unknown";
2526
- try {
2527
- mitm = await ports.startMitm(lastMitmPort, () => daemonPort);
2528
- } catch (error) {
2529
- return fail(`could not start the MITM proxy: ${error instanceof Error ? error.message : String(error)}`);
2530
- }
2531
- lastMitmPort = mitm.port;
2532
- if (previousState?.lastMitmPort !== void 0 && mitm.port !== previousState.lastMitmPort) {
2533
- ports.log(
2534
- `claude-use headroom supervisor: sticky MITM port ${String(previousState.lastMitmPort)} was occupied; moving to ${String(mitm.port)} (OAuth sessions launched against the old port are stale until they relaunch)`
2535
- );
2536
- }
2537
- writeHeadroomState(fs2, paths.headroomStateFile, { supervisorPid: ports.ownPid, version, installedSource, mitmPort: mitm.port, ...sticky() });
2911
+ writeHeadroomState(fs2, paths.headroomStateFile, { supervisorPid: ports.ownPid, version, installedSource, ...sticky() });
2538
2912
  ports.log(`claude-use headroom supervisor ${String(ports.ownPid)}: managing headroom on allowlist [${allowlistOf(ports).join(", ")}]`);
2539
2913
  const orphanPid = previousState?.headroomPid;
2540
2914
  if (orphanPid !== void 0 && ports.isRunning(orphanPid)) {
@@ -2543,6 +2917,7 @@ async function runSupervisor(config, ports, options = {}) {
2543
2917
  }
2544
2918
  let headroomPid;
2545
2919
  let runningHash;
2920
+ let runningSettingsHash;
2546
2921
  let consecutiveFailures = 0;
2547
2922
  let idleSince;
2548
2923
  let ticks = 0;
@@ -2551,6 +2926,8 @@ async function runSupervisor(config, ports, options = {}) {
2551
2926
  return HEADROOM_SUPERVISOR_STILL_RUNNING;
2552
2927
  }
2553
2928
  ticks += 1;
2929
+ config = ports.readConfig();
2930
+ const settingsHash = hashSettings(config.settings);
2554
2931
  const allowlist = allowlistOf(ports);
2555
2932
  const allowlistHash = hashAllowlist(allowlist);
2556
2933
  pruneDeadSessions(fs2, paths.headroomSessionsDir, ports.isRunning);
@@ -2559,13 +2936,12 @@ async function runSupervisor(config, ports, options = {}) {
2559
2936
  if (crashed) {
2560
2937
  ports.log(`claude-use headroom supervisor: headroom pid ${String(headroomPid)} died`);
2561
2938
  headroomPid = void 0;
2562
- daemonPort = void 0;
2563
2939
  runningHash = void 0;
2940
+ runningSettingsHash = void 0;
2564
2941
  writeHeadroomState(fs2, paths.headroomStateFile, {
2565
2942
  supervisorPid: ports.ownPid,
2566
2943
  version,
2567
2944
  installedSource,
2568
- mitmPort: mitm.port,
2569
2945
  ...sticky()
2570
2946
  });
2571
2947
  }
@@ -2577,13 +2953,13 @@ async function runSupervisor(config, ports, options = {}) {
2577
2953
  }
2578
2954
  const previousSticky = lastPort;
2579
2955
  const port = lastPort !== void 0 && await ports.isPortFree(lastPort) ? lastPort : await ports.freePort();
2580
- const pid = ports.spawnHeadroom(port, allowlist);
2956
+ const pid = ports.spawnHeadroom(port, allowlist, config.settings);
2581
2957
  const ready = await waitUntilReady(ports, port);
2582
2958
  if (ready) {
2583
2959
  consecutiveFailures = 0;
2584
2960
  headroomPid = pid;
2585
- daemonPort = port;
2586
2961
  runningHash = allowlistHash;
2962
+ runningSettingsHash = settingsHash;
2587
2963
  lastPort = port;
2588
2964
  if (previousSticky !== void 0 && port !== previousSticky) {
2589
2965
  ports.log(
@@ -2594,9 +2970,9 @@ async function runSupervisor(config, ports, options = {}) {
2594
2970
  supervisorPid: ports.ownPid,
2595
2971
  headroomPid: pid,
2596
2972
  port,
2597
- mitmPort: mitm.port,
2598
2973
  version,
2599
2974
  allowlistHash,
2975
+ settingsHash,
2600
2976
  installedSource: config.source,
2601
2977
  ...sticky()
2602
2978
  });
@@ -2613,13 +2989,13 @@ async function runSupervisor(config, ports, options = {}) {
2613
2989
  continue;
2614
2990
  }
2615
2991
  } else {
2616
- if (runningHash !== allowlistHash || installedSource !== config.source) {
2992
+ if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
2617
2993
  if (listSessions(fs2, paths.headroomSessionsDir).length === 0) {
2618
2994
  ports.log("claude-use headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
2619
2995
  ports.stopProcess(headroomPid);
2620
2996
  headroomPid = void 0;
2621
- daemonPort = void 0;
2622
2997
  runningHash = void 0;
2998
+ runningSettingsHash = void 0;
2623
2999
  continue;
2624
3000
  }
2625
3001
  }
@@ -2627,13 +3003,11 @@ async function runSupervisor(config, ports, options = {}) {
2627
3003
  if (sessions.length === 0) {
2628
3004
  idleSince ??= ports.now();
2629
3005
  const idleMs = ports.now() - idleSince;
2630
- if (idleMs >= config.idleShutdownMinutes * MS_PER_MINUTE) {
3006
+ if (idleMs >= config.idleShutdownMinutes * MS_PER_MINUTE2) {
2631
3007
  ports.log(
2632
- `claude-use headroom supervisor: no sessions for ${String(config.idleShutdownMinutes)} minute(s); stopping headroom and the MITM proxy, and exiting`
3008
+ `claude-use headroom supervisor: no sessions for ${String(config.idleShutdownMinutes)} minute(s); stopping headroom and exiting`
2633
3009
  );
2634
3010
  ports.stopProcess(headroomPid);
2635
- daemonPort = void 0;
2636
- await mitm.close();
2637
3011
  writeHeadroomState(fs2, paths.headroomStateFile, {
2638
3012
  version,
2639
3013
  ...sticky()
@@ -2716,15 +3090,18 @@ function evaluateAmbientCredentialGuard(params) {
2716
3090
  // Annotate the CommonJS export names for ESM import in node:
2717
3091
  0 && (module.exports = {
2718
3092
  AMBIENT_CREDENTIAL_VARS,
3093
+ CONNECT_INTERCEPT_HOST,
3094
+ CONNECT_LIMITS,
2719
3095
  FARM_MANIFEST_FILENAME,
2720
- HEADROOM_SERVED_PATH_PREFIX,
3096
+ FrontDoorStartError,
2721
3097
  HeadroomStartError,
2722
- MITM_INTERCEPT_HOST,
3098
+ ROUTED_PATH_PREFIX,
2723
3099
  buildEntryFacts,
2724
3100
  carryOver,
2725
3101
  createLeafCache,
2726
3102
  detectAmbientCredential,
2727
3103
  ensureCa,
3104
+ ensureFrontDoor,
2728
3105
  ensureHeadroom,
2729
3106
  evaluateAmbientCredentialGuard,
2730
3107
  formatAmbientCredentialGuardMessage,
@@ -2734,15 +3111,16 @@ function evaluateAmbientCredentialGuard(params) {
2734
3111
  mintLeaf,
2735
3112
  parseConnectTarget,
2736
3113
  readFarmManifest,
2737
- realMitmCertStore,
2738
- realMitmEffects,
3114
+ realConnectCertStore,
3115
+ realConnectEffects,
2739
3116
  recoverFarm,
2740
3117
  recoveryDiagnostics,
2741
3118
  resolveDecisions,
2742
3119
  resolveSupervisorConfig,
2743
3120
  resyncFarm,
3121
+ runFrontDoorSupervisor,
2744
3122
  runSupervisor,
2745
- servedByHeadroom,
2746
- startMitmServer,
3123
+ servedByPipeline,
3124
+ startConnectServer,
2747
3125
  topLevelNames
2748
3126
  });