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.mjs CHANGED
@@ -155,11 +155,36 @@ var PortableConfigSchema = ConfigProfileSchema.omit({ description: true }).exten
155
155
  identity: z.string().min(1).optional(),
156
156
  when: WhenSchema.optional()
157
157
  });
158
- var HeadroomGlobalConfigSchema = z.strictObject({
158
+ var HEADROOM_MODES = ["cache", "token"];
159
+ var HEADROOM_CCR_MODES = ["default", "lossless", "none"];
160
+ var HEADROOM_ROLLOUT_CHANNELS = ["beta", "canary", "dev"];
161
+ var INTERCEPT_CHANNELS = ["canary", "dev"];
162
+ var READ_MATURATION_CHANNELS = ["beta", "dev"];
163
+ var HeadroomGlobalConfigObjectSchema = z.strictObject({
159
164
  /** Install spec handed to `uv tool install`. Defaults to HEADROOM_DEFAULT_SOURCE. */
160
165
  source: z.string().min(1).optional(),
161
166
  /** How long the daemon may sit with no registered sessions before the supervisor stops it. Defaults to HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES. */
162
- idleShutdownMinutes: z.number().int().positive().optional()
167
+ idleShutdownMinutes: z.number().int().positive().optional(),
168
+ /** Optimisation mode, passed as `--mode`. Unset leaves headroom's own default (`cache`). */
169
+ mode: z.enum(HEADROOM_MODES).optional(),
170
+ /** Keep-ratio for prose and code compression, passed as `--target-ratio`: lower is more aggressive. Unset lets headroom decide. */
171
+ targetRatio: z.number().gt(0).max(1).optional(),
172
+ /** How compressed tool output stays recoverable. Unset leaves headroom's own default (`default`). */
173
+ ccr: z.enum(HEADROOM_CCR_MODES).optional(),
174
+ /** Headroom's experimental release channel, exported as `HEADROOM_ROLLOUT_CHANNEL`. Required by `interceptToolResults` and `readMaturation`, which are refused without a channel that unlocks them. */
175
+ rolloutChannel: z.enum(HEADROOM_ROLLOUT_CHANNELS).optional(),
176
+ /** Opt in to headroom's tool-result interceptors (`--intercept-tool-results`). Needs `rolloutChannel` canary or dev. */
177
+ interceptToolResults: z.boolean().optional(),
178
+ /** Opt in to headroom's experimental read maturation (`--read-maturation`). Needs `rolloutChannel` beta or dev. */
179
+ readMaturation: z.boolean().optional()
180
+ });
181
+ var HeadroomGlobalConfigSchema = HeadroomGlobalConfigObjectSchema.superRefine((config, context) => {
182
+ if (config.interceptToolResults === true && !INTERCEPT_CHANNELS.includes(config.rolloutChannel ?? "")) {
183
+ context.addIssue({ code: "custom", path: ["interceptToolResults"], message: "interceptToolResults needs rolloutChannel canary or dev" });
184
+ }
185
+ if (config.readMaturation === true && !READ_MATURATION_CHANNELS.includes(config.rolloutChannel ?? "")) {
186
+ context.addIssue({ code: "custom", path: ["readMaturation"], message: "readMaturation needs rolloutChannel beta or dev" });
187
+ }
163
188
  });
164
189
  var HEADROOM_DEFAULT_SOURCE = "headroom-ai[proxy] @ git+https://github.com/ExaDev/headroom@eb02fa4126450c9905a49394fdec19ae2e7c9c30";
165
190
  var HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES = 15;
@@ -1733,15 +1758,213 @@ function recoveryDiagnostics(recovery, identity) {
1733
1758
  ];
1734
1759
  }
1735
1760
 
1736
- // src/headroom/mitm.ts
1761
+ // src/frontdoor/connect.ts
1737
1762
  import * as fs from "node:fs";
1738
1763
  import * as http from "node:http";
1739
1764
  import * as https from "node:https";
1740
1765
  import * as net from "node:net";
1741
1766
  import * as tls from "node:tls";
1742
1767
  import forge from "node-forge";
1743
- var MITM_INTERCEPT_HOST = "api.anthropic.com";
1744
- var HEADROOM_SERVED_PATH_PREFIX = "/v1/";
1768
+
1769
+ // src/frontdoor/capability.ts
1770
+ var CONNECT_PROXY_REALM = "claude-use front door";
1771
+ var BASIC_SCHEME = "basic";
1772
+ function capabilityFromProxyAuthorization(value) {
1773
+ const trimmed = value.trim();
1774
+ const space = trimmed.indexOf(" ");
1775
+ if (space === -1 || trimmed.slice(0, space).toLowerCase() !== BASIC_SCHEME) {
1776
+ return void 0;
1777
+ }
1778
+ const userPass = Buffer.from(trimmed.slice(space + 1).trim(), "base64").toString("utf8");
1779
+ const colon = userPass.indexOf(":");
1780
+ if (colon === -1) {
1781
+ return void 0;
1782
+ }
1783
+ const password = userPass.slice(colon + 1);
1784
+ return password === "" ? void 0 : password;
1785
+ }
1786
+
1787
+ // src/frontdoor/state.ts
1788
+ import path5 from "node:path";
1789
+ import { z as z3 } from "zod";
1790
+ var FrontDoorSessionSchema = z3.strictObject({ pid: z3.number().int().positive(), startedAt: z3.number(), token: z3.string().min(1) });
1791
+ var FrontDoorStateSchema = z3.strictObject({
1792
+ /** The supervisor process serving the front door. Alive means both listeners are up in that process. */
1793
+ supervisorPid: z3.number().int().positive().optional(),
1794
+ /** 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. */
1795
+ port: z3.number().int().positive().optional(),
1796
+ /**
1797
+ * 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.
1798
+ */
1799
+ lastPort: z3.number().int().positive().optional(),
1800
+ /**
1801
+ * 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`.
1802
+ */
1803
+ connectPort: z3.number().int().positive().optional(),
1804
+ /**
1805
+ * 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.
1806
+ */
1807
+ lastConnectPort: z3.number().int().positive().optional(),
1808
+ /**
1809
+ * 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.
1810
+ */
1811
+ directPort: z3.number().int().positive().optional(),
1812
+ /** The sticky port preference for the direct listener, for the reason `directPort` gives. */
1813
+ lastDirectPort: z3.number().int().positive().optional(),
1814
+ /** The last fatal error, kept across shutdowns so `frontdoor status` can explain a front door that is not running. */
1815
+ lastError: z3.string().optional()
1816
+ });
1817
+ function readFrontDoorState(fs2, stateFile) {
1818
+ let raw;
1819
+ try {
1820
+ raw = fs2.readFileUtf8(stateFile);
1821
+ } catch {
1822
+ return void 0;
1823
+ }
1824
+ if (raw === void 0 || raw.trim() === "") {
1825
+ return void 0;
1826
+ }
1827
+ try {
1828
+ const parsed = FrontDoorStateSchema.safeParse(JSON.parse(raw));
1829
+ return parsed.success ? parsed.data : void 0;
1830
+ } catch {
1831
+ return void 0;
1832
+ }
1833
+ }
1834
+ function writeFrontDoorState(fs2, stateFile, state) {
1835
+ fs2.mkdirp(path5.dirname(stateFile));
1836
+ fs2.writeFileUtf8(stateFile, `${JSON.stringify(state, null, 2)}
1837
+ `);
1838
+ }
1839
+ function frontDoorOrigin(state) {
1840
+ const port = state?.directPort ?? state?.lastDirectPort;
1841
+ return port === void 0 ? void 0 : `http://127.0.0.1:${String(port)}`;
1842
+ }
1843
+ function writeFrontDoorSession(fs2, sessionsDir, session) {
1844
+ fs2.mkdirPrivate(sessionsDir);
1845
+ fs2.writeFilePrivate(path5.join(sessionsDir, `${String(session.pid)}.json`), `${JSON.stringify(session, null, 2)}
1846
+ `);
1847
+ }
1848
+ function listFrontDoorSessions(fs2, sessionsDir) {
1849
+ const sessions = [];
1850
+ for (const name of fs2.readdir(sessionsDir)) {
1851
+ if (!name.endsWith(".json")) {
1852
+ continue;
1853
+ }
1854
+ try {
1855
+ const parsed = FrontDoorSessionSchema.safeParse(JSON.parse(fs2.readFileUtf8(path5.join(sessionsDir, name)) ?? ""));
1856
+ if (parsed.success) {
1857
+ sessions.push({ pid: parsed.data.pid, startedAt: parsed.data.startedAt });
1858
+ }
1859
+ } catch {
1860
+ continue;
1861
+ }
1862
+ }
1863
+ return sessions.sort((a, b) => a.pid - b.pid);
1864
+ }
1865
+ function removeFrontDoorSession(fs2, sessionsDir, pid) {
1866
+ fs2.removeRecursive(path5.join(sessionsDir, `${String(pid)}.json`));
1867
+ }
1868
+ function pruneDeadFrontDoorSessions(fs2, sessionsDir, isRunning) {
1869
+ const removed = [];
1870
+ for (const session of listFrontDoorSessions(fs2, sessionsDir)) {
1871
+ if (!isRunning(session.pid)) {
1872
+ removeFrontDoorSession(fs2, sessionsDir, session.pid);
1873
+ removed.push(session.pid);
1874
+ }
1875
+ }
1876
+ return removed;
1877
+ }
1878
+
1879
+ // src/frontdoor/supervisor.ts
1880
+ var FRONTDOOR_SUPERVISOR_STILL_RUNNING = -1;
1881
+ var FRONTDOOR_POLL_MS = 1e3;
1882
+ var MS_PER_MINUTE = 6e4;
1883
+ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {}) {
1884
+ const { fs: fs2, paths } = ports;
1885
+ const previous = readFrontDoorState(fs2, paths.frontdoorStateFile);
1886
+ let lastPort = previous?.lastPort;
1887
+ let lastConnectPort = previous?.lastConnectPort;
1888
+ let lastDirectPort = previous?.lastDirectPort;
1889
+ const sticky = () => ({
1890
+ ...lastPort === void 0 ? {} : { lastPort },
1891
+ ...lastConnectPort === void 0 ? {} : { lastConnectPort },
1892
+ ...lastDirectPort === void 0 ? {} : { lastDirectPort }
1893
+ });
1894
+ const fail = (message) => {
1895
+ writeFrontDoorState(fs2, paths.frontdoorStateFile, { lastError: message, ...sticky() });
1896
+ fs2.removeRecursive(paths.frontdoorLockFile);
1897
+ ports.log(`claude-use frontdoor supervisor: ${message}`);
1898
+ return 1;
1899
+ };
1900
+ writeFrontDoorState(fs2, paths.frontdoorStateFile, { supervisorPid: ports.ownPid, ...sticky() });
1901
+ const previousSticky = lastPort;
1902
+ let http2;
1903
+ try {
1904
+ http2 = await ports.startProviderListener(lastPort);
1905
+ } catch (error) {
1906
+ return fail(`could not start the provider listener: ${error instanceof Error ? error.message : String(error)}`);
1907
+ }
1908
+ if (previousSticky !== void 0 && http2.port !== previousSticky) {
1909
+ 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)`);
1910
+ }
1911
+ lastPort = http2.port;
1912
+ const previousStickyConnect = lastConnectPort;
1913
+ let connect2;
1914
+ try {
1915
+ connect2 = await ports.startConnectListener(lastConnectPort);
1916
+ } catch (error) {
1917
+ await http2.close();
1918
+ return fail(`could not start the CONNECT surface: ${error instanceof Error ? error.message : String(error)}`);
1919
+ }
1920
+ if (previousStickyConnect !== void 0 && connect2.port !== previousStickyConnect) {
1921
+ 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)`);
1922
+ }
1923
+ lastConnectPort = connect2.port;
1924
+ const previousStickyDirect = lastDirectPort;
1925
+ let direct;
1926
+ try {
1927
+ direct = await ports.startDirectListener(lastDirectPort);
1928
+ } catch (error) {
1929
+ await connect2.close();
1930
+ await http2.close();
1931
+ return fail(`could not start the direct listener: ${error instanceof Error ? error.message : String(error)}`);
1932
+ }
1933
+ if (previousStickyDirect !== void 0 && direct.port !== previousStickyDirect) {
1934
+ 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)`);
1935
+ }
1936
+ lastDirectPort = direct.port;
1937
+ writeFrontDoorState(fs2, paths.frontdoorStateFile, { supervisorPid: ports.ownPid, port: http2.port, connectPort: connect2.port, directPort: direct.port, ...sticky() });
1938
+ fs2.removeRecursive(paths.frontdoorLockFile);
1939
+ 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)}`);
1940
+ let idleSince;
1941
+ let ticks = 0;
1942
+ for (; ; ) {
1943
+ if (options.tickLimit !== void 0 && ticks >= options.tickLimit) {
1944
+ return FRONTDOOR_SUPERVISOR_STILL_RUNNING;
1945
+ }
1946
+ ticks += 1;
1947
+ pruneDeadFrontDoorSessions(fs2, paths.frontdoorSessionsDir, ports.isRunning);
1948
+ if (listFrontDoorSessions(fs2, paths.frontdoorSessionsDir).length === 0) {
1949
+ idleSince ??= ports.now();
1950
+ if (ports.now() - idleSince >= idleShutdownMinutes * MS_PER_MINUTE) {
1951
+ ports.log(`claude-use frontdoor supervisor: no sessions for ${String(idleShutdownMinutes)} minute(s); closing the front door and exiting`);
1952
+ await direct.close();
1953
+ await connect2.close();
1954
+ await http2.close();
1955
+ writeFrontDoorState(fs2, paths.frontdoorStateFile, sticky());
1956
+ return 0;
1957
+ }
1958
+ } else {
1959
+ idleSince = void 0;
1960
+ }
1961
+ await ports.sleep(FRONTDOOR_POLL_MS);
1962
+ }
1963
+ }
1964
+
1965
+ // src/frontdoor/connect.ts
1966
+ var CONNECT_INTERCEPT_HOST = "api.anthropic.com";
1967
+ var ROUTED_PATH_PREFIX = "/v1/";
1745
1968
  var HTTPS_PORT = 443;
1746
1969
  var MAX_PORT = 65535;
1747
1970
  var SERIAL_NUMBER_BYTES = 16;
@@ -1751,9 +1974,22 @@ var CONNECT_LINE_TOKENS = 3;
1751
1974
  var HEAD_TERMINATOR = "\r\n\r\n";
1752
1975
  var MAX_CONNECT_HEAD_BYTES = 8192;
1753
1976
  var CONNECT_ESTABLISHED = "HTTP/1.1 200 Connection Established\r\n\r\n";
1977
+ var REFUSAL = {
1978
+ badRequest: "400 Bad Request",
1979
+ proxyAuthenticationRequired: "407 Proxy Authentication Required",
1980
+ requestTimeout: "408 Request Timeout",
1981
+ headTooLarge: "431 Request Header Fields Too Large",
1982
+ serviceUnavailable: "503 Service Unavailable"
1983
+ };
1984
+ var CONNECT_LIMITS = {
1985
+ headDeadlineMs: 1e4,
1986
+ maxPendingHeads: 64,
1987
+ maxTunnels: 1024,
1988
+ revalidateMs: FRONTDOOR_POLL_MS
1989
+ };
1754
1990
  var CA_VALIDITY_DAYS = 3650;
1755
1991
  var LEAF_VALIDITY_DAYS = 397;
1756
- var CA_SUBJECT_COMMON_NAME = "claude-use headroom CA";
1992
+ var CA_SUBJECT_COMMON_NAME = "claude-use front door CA";
1757
1993
  var MS_PER_DAY = 864e5;
1758
1994
  function parseConnectTarget(authority) {
1759
1995
  const colon = authority.lastIndexOf(":");
@@ -1771,8 +2007,8 @@ function parseConnectTarget(authority) {
1771
2007
  function isInterceptedHost(host, interceptHost) {
1772
2008
  return host === interceptHost;
1773
2009
  }
1774
- function servedByHeadroom(path6) {
1775
- return path6?.startsWith(HEADROOM_SERVED_PATH_PREFIX) === true;
2010
+ function servedByPipeline(path7) {
2011
+ return path7?.startsWith(ROUTED_PATH_PREFIX) === true;
1776
2012
  }
1777
2013
  var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
1778
2014
  "connection",
@@ -1820,7 +2056,13 @@ function generateCa(now) {
1820
2056
  cert.sign(keys.privateKey, forge.md.sha256.create());
1821
2057
  return { certPem: forge.pki.certificateToPem(cert), keyPem: forge.pki.privateKeyToPem(keys.privateKey) };
1822
2058
  }
1823
- function mintLeaf(ca, host, now) {
2059
+ var SAN_TYPE_DNS = 2;
2060
+ var SAN_TYPE_IP = 7;
2061
+ function mintLeaf(ca, names, now) {
2062
+ const [commonName] = names;
2063
+ if (commonName === void 0) {
2064
+ throw new Error("a leaf certificate needs at least one name");
2065
+ }
1824
2066
  const caCert = forge.pki.certificateFromPem(ca.certPem);
1825
2067
  const caKey = forge.pki.privateKeyFromPem(ca.keyPem);
1826
2068
  const keys = forge.pki.rsa.generateKeyPair({ bits: 2048 });
@@ -1829,13 +2071,13 @@ function mintLeaf(ca, host, now) {
1829
2071
  cert.serialNumber = forge.util.bytesToHex(forge.random.getBytesSync(SERIAL_NUMBER_BYTES));
1830
2072
  cert.validity.notBefore = new Date(now.getTime() - MS_PER_DAY);
1831
2073
  cert.validity.notAfter = new Date(now.getTime() + LEAF_VALIDITY_DAYS * MS_PER_DAY);
1832
- cert.setSubject([{ name: "commonName", value: host }]);
2074
+ cert.setSubject([{ name: "commonName", value: commonName }]);
1833
2075
  cert.setIssuer(caCert.subject.attributes);
1834
2076
  cert.setExtensions([
1835
2077
  { name: "basicConstraints", cA: false, critical: true },
1836
2078
  { name: "keyUsage", digitalSignature: true, keyEncipherment: true, critical: true },
1837
2079
  { name: "extKeyUsage", serverAuth: true },
1838
- { name: "subjectAltName", altNames: [{ type: 2, value: host }] },
2080
+ { name: "subjectAltName", altNames: names.map((name) => net.isIP(name) === 0 ? { type: SAN_TYPE_DNS, value: name } : { type: SAN_TYPE_IP, ip: name }) },
1839
2081
  { name: "subjectKeyIdentifier" },
1840
2082
  { name: "authorityKeyIdentifier", keyLocatorFields: [] }
1841
2083
  ]);
@@ -1849,7 +2091,7 @@ function createLeafCache(ca, now) {
1849
2091
  if (existing !== void 0) {
1850
2092
  return existing;
1851
2093
  }
1852
- const minted = mintLeaf(ca, host, now());
2094
+ const minted = mintLeaf(ca, [host], now());
1853
2095
  cache.set(host, minted);
1854
2096
  return minted;
1855
2097
  };
@@ -1872,17 +2114,10 @@ function caParses(ca) {
1872
2114
  return false;
1873
2115
  }
1874
2116
  }
1875
- async function startMitmServer(config, effects, preferredPort) {
2117
+ async function startConnectServer(config, effects, preferredPort) {
1876
2118
  const handler = (request3, response) => {
1877
- if (servedByHeadroom(request3.url)) {
1878
- const daemonPort = config.headroomPort();
1879
- if (daemonPort === void 0) {
1880
- const body = "claude-use headroom: the daemon is restarting";
1881
- response.writeHead(HTTP_BAD_GATEWAY, { "content-type": "text/plain", "content-length": String(body.length) });
1882
- response.end(body);
1883
- return;
1884
- }
1885
- effects.forwardHttp({ host: "127.0.0.1", port: daemonPort, tls: false }, request3, response);
2119
+ if (servedByPipeline(request3.url)) {
2120
+ config.serveRouted(request3, response);
1886
2121
  return;
1887
2122
  }
1888
2123
  effects.forwardHttp(config.upstream, request3, response);
@@ -1891,50 +2126,153 @@ async function startMitmServer(config, effects, preferredPort) {
1891
2126
  const tlsAcceptor = effects.createTlsAcceptor(config.leafFor(config.interceptHost), (secure) => {
1892
2127
  httpSession.serve(secure);
1893
2128
  });
2129
+ const ledger = createConnectionLedger(config.limits, config.isLiveCapability);
1894
2130
  const listener = await effects.listenLoopback(preferredPort, (socket) => {
1895
- handleConnect(socket, config, effects, tlsAcceptor);
2131
+ handleConnect(socket, { config, effects, tlsAcceptor, ledger });
1896
2132
  });
1897
2133
  return {
1898
2134
  port: listener.port,
1899
2135
  close: async () => {
2136
+ ledger.close();
1900
2137
  await listener.close();
1901
2138
  tlsAcceptor.close();
1902
2139
  httpSession.close();
1903
2140
  }
1904
2141
  };
1905
2142
  }
1906
- function handleConnect(socket, config, effects, tlsAcceptor) {
2143
+ function createConnectionLedger(limits, isLiveCapability) {
2144
+ const pending = /* @__PURE__ */ new Map();
2145
+ const authenticated = /* @__PURE__ */ new Map();
2146
+ let tunnels = 0;
2147
+ const leavePending = (socket) => {
2148
+ const deadline = pending.get(socket);
2149
+ if (deadline !== void 0) {
2150
+ clearTimeout(deadline);
2151
+ pending.delete(socket);
2152
+ }
2153
+ };
2154
+ const revalidation = setInterval(() => {
2155
+ for (const [capability, sockets] of authenticated) {
2156
+ if (!isLiveCapability(capability)) {
2157
+ for (const socket of sockets) {
2158
+ socket.destroy();
2159
+ }
2160
+ }
2161
+ }
2162
+ }, limits.revalidateMs);
2163
+ revalidation.unref();
2164
+ return {
2165
+ accept: (socket) => {
2166
+ const [oldest] = pending.keys();
2167
+ if (pending.size >= limits.maxPendingHeads && oldest !== void 0) {
2168
+ leavePending(oldest);
2169
+ refuse(oldest, REFUSAL.requestTimeout);
2170
+ }
2171
+ pending.set(
2172
+ socket,
2173
+ setTimeout(() => {
2174
+ leavePending(socket);
2175
+ refuse(socket, REFUSAL.requestTimeout);
2176
+ }, limits.headDeadlineMs)
2177
+ );
2178
+ socket.once("close", () => {
2179
+ leavePending(socket);
2180
+ });
2181
+ },
2182
+ headReceived: leavePending,
2183
+ establish: (socket, capability) => {
2184
+ if (tunnels >= limits.maxTunnels) {
2185
+ return false;
2186
+ }
2187
+ tunnels += 1;
2188
+ const sockets = authenticated.get(capability) ?? /* @__PURE__ */ new Set();
2189
+ sockets.add(socket);
2190
+ authenticated.set(capability, sockets);
2191
+ socket.once("close", () => {
2192
+ tunnels -= 1;
2193
+ sockets.delete(socket);
2194
+ if (sockets.size === 0 && authenticated.get(capability) === sockets) {
2195
+ authenticated.delete(capability);
2196
+ }
2197
+ });
2198
+ return true;
2199
+ },
2200
+ close: () => {
2201
+ clearInterval(revalidation);
2202
+ for (const deadline of pending.values()) {
2203
+ clearTimeout(deadline);
2204
+ }
2205
+ pending.clear();
2206
+ }
2207
+ };
2208
+ }
2209
+ function refuse(socket, status, headers = []) {
2210
+ socket.removeAllListeners("data");
2211
+ socket.pause();
2212
+ if (socket.destroyed || socket.writableEnded) {
2213
+ return;
2214
+ }
2215
+ socket.end(`HTTP/1.1 ${status}\r
2216
+ ${headers.map((header) => `${header}\r
2217
+ `).join("")}content-length: 0\r
2218
+ connection: close\r
2219
+ \r
2220
+ `, () => {
2221
+ socket.destroy();
2222
+ });
2223
+ }
2224
+ function handleConnect(socket, context) {
2225
+ socket.on("error", () => {
2226
+ socket.destroy();
2227
+ });
2228
+ context.ledger.accept(socket);
1907
2229
  let buffer = Buffer.alloc(0);
1908
2230
  const onData = (chunk) => {
1909
2231
  buffer = Buffer.concat([buffer, chunk]);
1910
2232
  const headEnd = buffer.indexOf(HEAD_TERMINATOR);
2233
+ if (headEnd === -1 ? buffer.length > MAX_CONNECT_HEAD_BYTES : headEnd > MAX_CONNECT_HEAD_BYTES) {
2234
+ context.ledger.headReceived(socket);
2235
+ refuse(socket, REFUSAL.headTooLarge);
2236
+ return;
2237
+ }
1911
2238
  if (headEnd === -1) {
1912
- if (buffer.length > MAX_CONNECT_HEAD_BYTES) {
1913
- socket.removeListener("data", onData);
1914
- socket.pause();
1915
- socket.destroy();
1916
- }
1917
2239
  return;
1918
2240
  }
1919
2241
  socket.removeListener("data", onData);
1920
2242
  socket.pause();
2243
+ context.ledger.headReceived(socket);
1921
2244
  const head = buffer.subarray(0, headEnd).toString("utf8");
1922
2245
  socket.unshift(buffer.subarray(headEnd + HEAD_TERMINATOR.length));
1923
- routeConnect(socket, head, config, effects, tlsAcceptor);
2246
+ routeConnect(socket, head, context);
1924
2247
  };
1925
2248
  socket.on("data", onData);
1926
- socket.on("error", () => {
1927
- socket.destroy();
1928
- });
1929
2249
  }
1930
- function routeConnect(socket, head, config, effects, tlsAcceptor) {
2250
+ function proxyAuthorizationOf(head) {
2251
+ const values = head.split("\r\n").slice(1).flatMap((line) => {
2252
+ const colon = line.indexOf(":");
2253
+ return colon > 0 && line.slice(0, colon).trim().toLowerCase() === "proxy-authorization" ? [line.slice(colon + 1)] : [];
2254
+ });
2255
+ const [value] = values;
2256
+ return values.length === 1 ? value : void 0;
2257
+ }
2258
+ function routeConnect(socket, head, context) {
2259
+ const { config, effects, tlsAcceptor, ledger } = context;
2260
+ const credential = proxyAuthorizationOf(head);
2261
+ const capability = credential === void 0 ? void 0 : capabilityFromProxyAuthorization(credential);
2262
+ if (capability === void 0 || !config.isLiveCapability(capability)) {
2263
+ refuse(socket, REFUSAL.proxyAuthenticationRequired, [`Proxy-Authenticate: Basic realm="${CONNECT_PROXY_REALM}"`]);
2264
+ return;
2265
+ }
1931
2266
  const requestLine = head.split("\r\n", 1)[0] ?? "";
1932
2267
  const parts = requestLine.split(" ");
1933
2268
  const authority = parts.length === CONNECT_LINE_TOKENS && parts[0]?.toUpperCase() === "CONNECT" ? parts[1] ?? "" : void 0;
1934
2269
  const target = authority === void 0 ? void 0 : parseConnectTarget(authority);
1935
2270
  if (target === void 0) {
1936
- socket.write("HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\n\r\n");
1937
- socket.destroy();
2271
+ refuse(socket, REFUSAL.badRequest);
2272
+ return;
2273
+ }
2274
+ if (!ledger.establish(socket, capability)) {
2275
+ refuse(socket, REFUSAL.serviceUnavailable);
1938
2276
  return;
1939
2277
  }
1940
2278
  socket.write(CONNECT_ESTABLISHED);
@@ -1952,6 +2290,10 @@ async function blindTunnel(socket, target, effects) {
1952
2290
  socket.destroy();
1953
2291
  return;
1954
2292
  }
2293
+ if (socket.destroyed) {
2294
+ upstream.destroy();
2295
+ return;
2296
+ }
1955
2297
  socket.pipe(upstream);
1956
2298
  upstream.pipe(socket);
1957
2299
  const drop = () => {
@@ -1967,7 +2309,7 @@ async function blindTunnel(socket, target, effects) {
1967
2309
  socket.destroy();
1968
2310
  });
1969
2311
  }
1970
- function realMitmEffects() {
2312
+ function realConnectEffects() {
1971
2313
  const plainAgent = new http.Agent({ keepAlive: true });
1972
2314
  const tlsAgent = new https.Agent({ keepAlive: true });
1973
2315
  const listenOnce = async (port, onSocket) => await new Promise((resolve, reject) => {
@@ -2096,7 +2438,7 @@ function realMitmEffects() {
2096
2438
  function isEnoent(error) {
2097
2439
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
2098
2440
  }
2099
- function realMitmCertStore(paths) {
2441
+ function realConnectCertStore(paths) {
2100
2442
  const read = (file) => {
2101
2443
  try {
2102
2444
  return fs.readFileSync(file, "utf8");
@@ -2109,54 +2451,51 @@ function realMitmCertStore(paths) {
2109
2451
  };
2110
2452
  return {
2111
2453
  loadCa: () => {
2112
- const certPem = read(paths.headroomCaCertFile);
2113
- const keyPem = read(paths.headroomCaKeyFile);
2454
+ const certPem = read(paths.frontdoorCaCertFile);
2455
+ const keyPem = read(paths.frontdoorCaKeyFile);
2114
2456
  return certPem === void 0 || keyPem === void 0 ? void 0 : { certPem, keyPem };
2115
2457
  },
2116
2458
  writeCa: (ca) => {
2117
- fs.mkdirSync(paths.headroomCaDir, { recursive: true });
2118
- fs.writeFileSync(paths.headroomCaCertFile, ca.certPem, "utf8");
2119
- fs.writeFileSync(paths.headroomCaKeyFile, ca.keyPem, { encoding: "utf8", mode: CA_KEY_FILE_MODE });
2120
- fs.chmodSync(paths.headroomCaKeyFile, CA_KEY_FILE_MODE);
2459
+ fs.mkdirSync(paths.frontdoorCaDir, { recursive: true });
2460
+ fs.writeFileSync(paths.frontdoorCaCertFile, ca.certPem, "utf8");
2461
+ fs.writeFileSync(paths.frontdoorCaKeyFile, ca.keyPem, { encoding: "utf8", mode: CA_KEY_FILE_MODE });
2462
+ fs.chmodSync(paths.frontdoorCaKeyFile, CA_KEY_FILE_MODE);
2121
2463
  }
2122
2464
  };
2123
2465
  }
2124
2466
 
2467
+ // src/frontdoor/ensure.ts
2468
+ import { randomUUID as randomUUID2 } from "node:crypto";
2469
+
2125
2470
  // src/headroom/state.ts
2126
2471
  import { createHash } from "node:crypto";
2127
- import path5 from "node:path";
2128
- import { z as z3 } from "zod";
2472
+ import path6 from "node:path";
2473
+ import { z as z4 } from "zod";
2129
2474
  var HEADROOM_ANTHROPIC_UPSTREAM = "https://api.anthropic.com";
2130
- var HeadroomStateSchema = z3.strictObject({
2475
+ var HeadroomStateSchema = z4.strictObject({
2131
2476
  /** The supervisor process owning the daemon. Alive means someone is keeping headroom running. */
2132
- supervisorPid: z3.number().int().positive().optional(),
2477
+ supervisorPid: z4.number().int().positive().optional(),
2133
2478
  /** The headroom proxy process itself. Absent while the supervisor is between restarts. */
2134
- headroomPid: z3.number().int().positive().optional(),
2479
+ headroomPid: z4.number().int().positive().optional(),
2135
2480
  /** 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. */
2136
- port: z3.number().int().positive().optional(),
2137
- /**
2138
- * 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`.
2139
- */
2140
- mitmPort: z3.number().int().positive().optional(),
2141
- /**
2142
- * 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.
2143
- */
2144
- lastMitmPort: z3.number().int().positive().optional(),
2481
+ port: z4.number().int().positive().optional(),
2145
2482
  /**
2146
- * 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.
2483
+ * 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.
2147
2484
  */
2148
- lastPort: z3.number().int().positive().optional(),
2485
+ lastPort: z4.number().int().positive().optional(),
2149
2486
  /** The `headroom --version` output of the running install. */
2150
- version: z3.string().optional(),
2487
+ version: z4.string().optional(),
2151
2488
  /** Hash of the allowlist the running proxy was started with, so a provider-file change is detected as drift. */
2152
- allowlistHash: z3.string().optional(),
2489
+ allowlistHash: z4.string().optional(),
2490
+ /** Hash of the token-saving settings the running proxy was started with, so a settings change is detected as drift. */
2491
+ settingsHash: z4.string().optional(),
2153
2492
  /** The install spec the running install was last installed from, so a `source` config change is detected as drift. */
2154
- installedSource: z3.string().optional(),
2493
+ installedSource: z4.string().optional(),
2155
2494
  /** The last fatal error, kept across shutdowns so `headroom status` can explain a daemon that is not running. */
2156
- lastError: z3.string().optional()
2495
+ lastError: z4.string().optional()
2157
2496
  });
2158
- var HeadroomSessionSchema = z3.strictObject({ pid: z3.number().int().positive(), startedAt: z3.number() });
2159
- var HeadroomLockSchema = z3.strictObject({ pid: z3.number().int().positive(), at: z3.number() });
2497
+ var HeadroomSessionSchema = z4.strictObject({ pid: z4.number().int().positive(), startedAt: z4.number() });
2498
+ var HeadroomLockSchema = z4.strictObject({ pid: z4.number().int().positive(), at: z4.number() });
2160
2499
  function parseJson(raw, schema) {
2161
2500
  if (raw === void 0 || raw.trim() === "") {
2162
2501
  return void 0;
@@ -2172,7 +2511,7 @@ function readHeadroomState(fs2, stateFile) {
2172
2511
  }
2173
2512
  }
2174
2513
  function writeHeadroomState(fs2, stateFile, state) {
2175
- fs2.mkdirp(path5.dirname(stateFile));
2514
+ fs2.mkdirp(path6.dirname(stateFile));
2176
2515
  fs2.writeFileUtf8(stateFile, `${JSON.stringify(state, null, 2)}
2177
2516
  `);
2178
2517
  }
@@ -2184,16 +2523,8 @@ function headroomAllowlist(providers) {
2184
2523
  return [...urls].sort();
2185
2524
  }
2186
2525
  function headroomUpstreams(providers, frontDoorOrigin2) {
2187
- const upstreams = [];
2188
- let hasCodex = false;
2189
- for (const provider of providers) {
2190
- if (isCodexProvider(provider)) {
2191
- hasCodex = true;
2192
- } else {
2193
- upstreams.push({ baseUrl: provider.baseUrl });
2194
- }
2195
- }
2196
- if (hasCodex && frontDoorOrigin2 !== void 0) {
2526
+ const upstreams = providers.filter((provider) => !isCodexProvider(provider)).map((provider) => ({ baseUrl: provider.baseUrl }));
2527
+ if (providers.length > 0 && frontDoorOrigin2 !== void 0) {
2197
2528
  upstreams.push({ baseUrl: frontDoorOrigin2 });
2198
2529
  }
2199
2530
  return upstreams;
@@ -2208,7 +2539,7 @@ function readAllProviders(fs2, providersDir) {
2208
2539
  continue;
2209
2540
  }
2210
2541
  try {
2211
- const parsed = parseJson(fs2.readFileUtf8(path5.join(providersDir, name)), ProviderSchema);
2542
+ const parsed = parseJson(fs2.readFileUtf8(path6.join(providersDir, name)), ProviderSchema);
2212
2543
  if (parsed !== void 0) {
2213
2544
  result.push({ name: name.slice(0, -".json".length), provider: parsed });
2214
2545
  }
@@ -2220,11 +2551,11 @@ function readAllProviders(fs2, providersDir) {
2220
2551
  }
2221
2552
  function writeSession(fs2, sessionsDir, session) {
2222
2553
  fs2.mkdirp(sessionsDir);
2223
- fs2.writeFileUtf8(path5.join(sessionsDir, `${String(session.pid)}.json`), `${JSON.stringify(session, null, 2)}
2554
+ fs2.writeFileUtf8(path6.join(sessionsDir, `${String(session.pid)}.json`), `${JSON.stringify(session, null, 2)}
2224
2555
  `);
2225
2556
  }
2226
2557
  function removeSession(fs2, sessionsDir, pid) {
2227
- fs2.removeRecursive(path5.join(sessionsDir, `${String(pid)}.json`));
2558
+ fs2.removeRecursive(path6.join(sessionsDir, `${String(pid)}.json`));
2228
2559
  }
2229
2560
  function listSessions(fs2, sessionsDir) {
2230
2561
  const sessions = [];
@@ -2233,7 +2564,7 @@ function listSessions(fs2, sessionsDir) {
2233
2564
  continue;
2234
2565
  }
2235
2566
  try {
2236
- const parsed = parseJson(fs2.readFileUtf8(path5.join(sessionsDir, name)), HeadroomSessionSchema);
2567
+ const parsed = parseJson(fs2.readFileUtf8(path6.join(sessionsDir, name)), HeadroomSessionSchema);
2237
2568
  if (parsed !== void 0) {
2238
2569
  sessions.push(parsed);
2239
2570
  }
@@ -2261,6 +2592,67 @@ function readStartLock(fs2, lockFile) {
2261
2592
  }
2262
2593
  }
2263
2594
 
2595
+ // src/frontdoor/ensure.ts
2596
+ var FRONTDOOR_START_TIMEOUT_MS = 3e4;
2597
+ var FRONTDOOR_LAUNCHER_POLL_MS = 100;
2598
+ var FrontDoorStartError = class extends CliError {
2599
+ constructor(message) {
2600
+ super(message);
2601
+ this.name = "FrontDoorStartError";
2602
+ }
2603
+ };
2604
+ function ensureFrontDoor(params) {
2605
+ const { paths, ports } = params;
2606
+ const deadline = ports.now() + FRONTDOOR_START_TIMEOUT_MS;
2607
+ let spawned = false;
2608
+ const distrusted = /* @__PURE__ */ new Set();
2609
+ let lastRejection;
2610
+ for (; ; ) {
2611
+ const state = readFrontDoorState(ports.fs, paths.frontdoorStateFile);
2612
+ const supervisorPid = state?.supervisorPid;
2613
+ if (supervisorPid !== void 0 && !distrusted.has(supervisorPid) && ports.isRunning(supervisorPid)) {
2614
+ if (state?.port !== void 0 && state.connectPort !== void 0) {
2615
+ const verdict = ports.verifyListener(state.port);
2616
+ if (verdict.ok) {
2617
+ const token = randomUUID2();
2618
+ writeFrontDoorSession(ports.fs, paths.frontdoorSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), token });
2619
+ return { port: state.port, connectPort: state.connectPort, token };
2620
+ }
2621
+ lastRejection = `the listener on 127.0.0.1:${String(state.port)} (recorded for pid ${String(supervisorPid)}) failed authentication: ${verdict.reason}`;
2622
+ if (spawned) {
2623
+ throw new FrontDoorStartError(`claude-use: ${lastRejection}; refusing to send this session's credentials to it. Daemon log: ${paths.frontdoorLogPath}`);
2624
+ }
2625
+ distrusted.add(supervisorPid);
2626
+ continue;
2627
+ }
2628
+ if (state?.lastError !== void 0) {
2629
+ throw new FrontDoorStartError(`claude-use: the front door reported a fatal error and is not serving: ${state.lastError} (daemon log: ${paths.frontdoorLogPath})`);
2630
+ }
2631
+ } else if (!spawned) {
2632
+ ports.fs.mkdirp(paths.frontdoorDir);
2633
+ const created = ports.fs.writeFileExclusive(paths.frontdoorLockFile, `${JSON.stringify({ pid: params.launcherPid, at: ports.now() })}
2634
+ `);
2635
+ if (created) {
2636
+ ports.spawnSupervisor(paths);
2637
+ spawned = true;
2638
+ } else {
2639
+ const lock = readStartLock(ports.fs, paths.frontdoorLockFile);
2640
+ if (lock === void 0 || !ports.isRunning(lock.pid)) {
2641
+ ports.fs.removeRecursive(paths.frontdoorLockFile);
2642
+ continue;
2643
+ }
2644
+ }
2645
+ }
2646
+ if (ports.now() >= deadline) {
2647
+ const lastError = state?.lastError;
2648
+ throw new FrontDoorStartError(
2649
+ `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}`
2650
+ );
2651
+ }
2652
+ ports.sleep(FRONTDOOR_LAUNCHER_POLL_MS);
2653
+ }
2654
+ }
2655
+
2264
2656
  // src/headroom/ensure.ts
2265
2657
  var HEADROOM_START_TIMEOUT_MS = 12e4;
2266
2658
  var HEADROOM_LAUNCHER_POLL_MS = 250;
@@ -2277,10 +2669,10 @@ function ensureHeadroom(params) {
2277
2669
  for (; ; ) {
2278
2670
  const state = readHeadroomState(ports.fs, paths.headroomStateFile);
2279
2671
  if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
2280
- const daemonUp = state.port !== void 0 && state.mitmPort !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
2281
- if (daemonUp && state.port !== void 0 && state.mitmPort !== void 0) {
2672
+ const daemonUp = state.port !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
2673
+ if (daemonUp && state.port !== void 0) {
2282
2674
  writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now() });
2283
- return { port: state.port, mitmPort: state.mitmPort };
2675
+ return { port: state.port };
2284
2676
  }
2285
2677
  if (state.lastError !== void 0) {
2286
2678
  throw new HeadroomStartError(
@@ -2315,47 +2707,41 @@ function ensureHeadroom(params) {
2315
2707
  }
2316
2708
  }
2317
2709
 
2318
- // src/frontdoor/state.ts
2319
- import { z as z4 } from "zod";
2320
- var FrontDoorStateSchema = z4.strictObject({
2321
- /** The supervisor process serving the front door. Alive means the listener is up in that process. */
2322
- supervisorPid: z4.number().int().positive().optional(),
2323
- /** 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. */
2324
- port: z4.number().int().positive().optional(),
2325
- /**
2326
- * 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.
2327
- */
2328
- lastPort: z4.number().int().positive().optional(),
2329
- /** The last fatal error, kept across shutdowns so `frontdoor status` can explain a front door that is not running. */
2330
- lastError: z4.string().optional()
2331
- });
2332
- function readFrontDoorState(fs2, stateFile) {
2333
- let raw;
2334
- try {
2335
- raw = fs2.readFileUtf8(stateFile);
2336
- } catch {
2337
- return void 0;
2338
- }
2339
- if (raw === void 0 || raw.trim() === "") {
2340
- return void 0;
2341
- }
2342
- try {
2343
- const parsed = FrontDoorStateSchema.safeParse(JSON.parse(raw));
2344
- return parsed.success ? parsed.data : void 0;
2345
- } catch {
2346
- return void 0;
2347
- }
2710
+ // src/headroom/settings.ts
2711
+ import { createHash as createHash2 } from "node:crypto";
2712
+ function settingsOf(config) {
2713
+ return {
2714
+ ...config.mode === void 0 ? {} : { mode: config.mode },
2715
+ ...config.targetRatio === void 0 ? {} : { targetRatio: config.targetRatio },
2716
+ ...config.ccr === void 0 ? {} : { ccr: config.ccr },
2717
+ ...config.rolloutChannel === void 0 ? {} : { rolloutChannel: config.rolloutChannel },
2718
+ ...config.interceptToolResults === void 0 ? {} : { interceptToolResults: config.interceptToolResults },
2719
+ ...config.readMaturation === void 0 ? {} : { readMaturation: config.readMaturation }
2720
+ };
2348
2721
  }
2349
- function frontDoorOrigin(state) {
2350
- const port = state?.port ?? state?.lastPort;
2351
- return port === void 0 ? void 0 : `http://127.0.0.1:${String(port)}`;
2722
+ function settingsArgs(settings) {
2723
+ return [
2724
+ ...settings.mode === void 0 ? [] : ["--mode", settings.mode],
2725
+ ...settings.targetRatio === void 0 ? [] : ["--target-ratio", String(settings.targetRatio)],
2726
+ ...settings.ccr === "lossless" ? ["--lossless"] : [],
2727
+ ...settings.ccr === "none" ? ["--no-ccr"] : [],
2728
+ ...settings.interceptToolResults === true ? ["--intercept-tool-results"] : [],
2729
+ ...settings.readMaturation === true ? ["--read-maturation"] : []
2730
+ ];
2731
+ }
2732
+ function settingsEnv(settings) {
2733
+ return settings.rolloutChannel === void 0 ? {} : { HEADROOM_ROLLOUT_CHANNEL: settings.rolloutChannel };
2734
+ }
2735
+ function hashSettings(settings) {
2736
+ return createHash2("sha256").update(JSON.stringify([...settingsArgs(settings), JSON.stringify(settingsEnv(settings))])).digest("hex");
2352
2737
  }
2353
2738
 
2354
2739
  // src/headroom/supervisor.ts
2355
2740
  function resolveSupervisorConfig(configured) {
2356
2741
  return {
2357
2742
  source: configured.source ?? HEADROOM_DEFAULT_SOURCE,
2358
- idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES
2743
+ idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES,
2744
+ settings: settingsOf(configured)
2359
2745
  };
2360
2746
  }
2361
2747
  var HEADROOM_BACKOFF_BASE_MS = 500;
@@ -2363,7 +2749,7 @@ var HEADROOM_BACKOFF_CAP_MS = 3e4;
2363
2749
  var HEADROOM_START_RETRY_BUDGET = 5;
2364
2750
  var HEADROOM_READY_TIMEOUT_MS = 15e3;
2365
2751
  var HEADROOM_POLL_MS = 1e3;
2366
- var MS_PER_MINUTE = 6e4;
2752
+ var MS_PER_MINUTE2 = 6e4;
2367
2753
  function backoffForAttempt(attempt) {
2368
2754
  return Math.min(HEADROOM_BACKOFF_CAP_MS, HEADROOM_BACKOFF_BASE_MS * 2 ** (attempt - 1));
2369
2755
  }
@@ -2418,18 +2804,13 @@ function compareVersions(a, b) {
2418
2804
  return 0;
2419
2805
  }
2420
2806
  var HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
2421
- async function runSupervisor(config, ports, options = {}) {
2807
+ async function runSupervisor(initialConfig, ports, options = {}) {
2422
2808
  const { fs: fs2, paths } = ports;
2809
+ let config = initialConfig;
2423
2810
  const previousState = readHeadroomState(fs2, paths.headroomStateFile);
2424
2811
  let installedSource = previousState?.installedSource;
2425
2812
  let lastPort = previousState?.lastPort;
2426
- let lastMitmPort = previousState?.lastMitmPort;
2427
- let daemonPort;
2428
- const sticky = () => ({
2429
- ...lastPort === void 0 ? {} : { lastPort },
2430
- ...lastMitmPort === void 0 ? {} : { lastMitmPort }
2431
- });
2432
- let mitm;
2813
+ const sticky = () => lastPort === void 0 ? {} : { lastPort };
2433
2814
  const fail = (message) => {
2434
2815
  writeHeadroomState(fs2, paths.headroomStateFile, {
2435
2816
  lastError: message,
@@ -2458,18 +2839,7 @@ async function runSupervisor(config, ports, options = {}) {
2458
2839
  return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
2459
2840
  }
2460
2841
  const version = ports.headroomVersion() ?? "unknown";
2461
- try {
2462
- mitm = await ports.startMitm(lastMitmPort, () => daemonPort);
2463
- } catch (error) {
2464
- return fail(`could not start the MITM proxy: ${error instanceof Error ? error.message : String(error)}`);
2465
- }
2466
- lastMitmPort = mitm.port;
2467
- if (previousState?.lastMitmPort !== void 0 && mitm.port !== previousState.lastMitmPort) {
2468
- ports.log(
2469
- `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)`
2470
- );
2471
- }
2472
- writeHeadroomState(fs2, paths.headroomStateFile, { supervisorPid: ports.ownPid, version, installedSource, mitmPort: mitm.port, ...sticky() });
2842
+ writeHeadroomState(fs2, paths.headroomStateFile, { supervisorPid: ports.ownPid, version, installedSource, ...sticky() });
2473
2843
  ports.log(`claude-use headroom supervisor ${String(ports.ownPid)}: managing headroom on allowlist [${allowlistOf(ports).join(", ")}]`);
2474
2844
  const orphanPid = previousState?.headroomPid;
2475
2845
  if (orphanPid !== void 0 && ports.isRunning(orphanPid)) {
@@ -2478,6 +2848,7 @@ async function runSupervisor(config, ports, options = {}) {
2478
2848
  }
2479
2849
  let headroomPid;
2480
2850
  let runningHash;
2851
+ let runningSettingsHash;
2481
2852
  let consecutiveFailures = 0;
2482
2853
  let idleSince;
2483
2854
  let ticks = 0;
@@ -2486,6 +2857,8 @@ async function runSupervisor(config, ports, options = {}) {
2486
2857
  return HEADROOM_SUPERVISOR_STILL_RUNNING;
2487
2858
  }
2488
2859
  ticks += 1;
2860
+ config = ports.readConfig();
2861
+ const settingsHash = hashSettings(config.settings);
2489
2862
  const allowlist = allowlistOf(ports);
2490
2863
  const allowlistHash = hashAllowlist(allowlist);
2491
2864
  pruneDeadSessions(fs2, paths.headroomSessionsDir, ports.isRunning);
@@ -2494,13 +2867,12 @@ async function runSupervisor(config, ports, options = {}) {
2494
2867
  if (crashed) {
2495
2868
  ports.log(`claude-use headroom supervisor: headroom pid ${String(headroomPid)} died`);
2496
2869
  headroomPid = void 0;
2497
- daemonPort = void 0;
2498
2870
  runningHash = void 0;
2871
+ runningSettingsHash = void 0;
2499
2872
  writeHeadroomState(fs2, paths.headroomStateFile, {
2500
2873
  supervisorPid: ports.ownPid,
2501
2874
  version,
2502
2875
  installedSource,
2503
- mitmPort: mitm.port,
2504
2876
  ...sticky()
2505
2877
  });
2506
2878
  }
@@ -2512,13 +2884,13 @@ async function runSupervisor(config, ports, options = {}) {
2512
2884
  }
2513
2885
  const previousSticky = lastPort;
2514
2886
  const port = lastPort !== void 0 && await ports.isPortFree(lastPort) ? lastPort : await ports.freePort();
2515
- const pid = ports.spawnHeadroom(port, allowlist);
2887
+ const pid = ports.spawnHeadroom(port, allowlist, config.settings);
2516
2888
  const ready = await waitUntilReady(ports, port);
2517
2889
  if (ready) {
2518
2890
  consecutiveFailures = 0;
2519
2891
  headroomPid = pid;
2520
- daemonPort = port;
2521
2892
  runningHash = allowlistHash;
2893
+ runningSettingsHash = settingsHash;
2522
2894
  lastPort = port;
2523
2895
  if (previousSticky !== void 0 && port !== previousSticky) {
2524
2896
  ports.log(
@@ -2529,9 +2901,9 @@ async function runSupervisor(config, ports, options = {}) {
2529
2901
  supervisorPid: ports.ownPid,
2530
2902
  headroomPid: pid,
2531
2903
  port,
2532
- mitmPort: mitm.port,
2533
2904
  version,
2534
2905
  allowlistHash,
2906
+ settingsHash,
2535
2907
  installedSource: config.source,
2536
2908
  ...sticky()
2537
2909
  });
@@ -2548,13 +2920,13 @@ async function runSupervisor(config, ports, options = {}) {
2548
2920
  continue;
2549
2921
  }
2550
2922
  } else {
2551
- if (runningHash !== allowlistHash || installedSource !== config.source) {
2923
+ if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
2552
2924
  if (listSessions(fs2, paths.headroomSessionsDir).length === 0) {
2553
2925
  ports.log("claude-use headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
2554
2926
  ports.stopProcess(headroomPid);
2555
2927
  headroomPid = void 0;
2556
- daemonPort = void 0;
2557
2928
  runningHash = void 0;
2929
+ runningSettingsHash = void 0;
2558
2930
  continue;
2559
2931
  }
2560
2932
  }
@@ -2562,13 +2934,11 @@ async function runSupervisor(config, ports, options = {}) {
2562
2934
  if (sessions.length === 0) {
2563
2935
  idleSince ??= ports.now();
2564
2936
  const idleMs = ports.now() - idleSince;
2565
- if (idleMs >= config.idleShutdownMinutes * MS_PER_MINUTE) {
2937
+ if (idleMs >= config.idleShutdownMinutes * MS_PER_MINUTE2) {
2566
2938
  ports.log(
2567
- `claude-use headroom supervisor: no sessions for ${String(config.idleShutdownMinutes)} minute(s); stopping headroom and the MITM proxy, and exiting`
2939
+ `claude-use headroom supervisor: no sessions for ${String(config.idleShutdownMinutes)} minute(s); stopping headroom and exiting`
2568
2940
  );
2569
2941
  ports.stopProcess(headroomPid);
2570
- daemonPort = void 0;
2571
- await mitm.close();
2572
2942
  writeHeadroomState(fs2, paths.headroomStateFile, {
2573
2943
  version,
2574
2944
  ...sticky()
@@ -2650,15 +3020,18 @@ function evaluateAmbientCredentialGuard(params) {
2650
3020
  }
2651
3021
  export {
2652
3022
  AMBIENT_CREDENTIAL_VARS,
3023
+ CONNECT_INTERCEPT_HOST,
3024
+ CONNECT_LIMITS,
2653
3025
  FARM_MANIFEST_FILENAME,
2654
- HEADROOM_SERVED_PATH_PREFIX,
3026
+ FrontDoorStartError,
2655
3027
  HeadroomStartError,
2656
- MITM_INTERCEPT_HOST,
3028
+ ROUTED_PATH_PREFIX,
2657
3029
  buildEntryFacts,
2658
3030
  carryOver,
2659
3031
  createLeafCache,
2660
3032
  detectAmbientCredential,
2661
3033
  ensureCa,
3034
+ ensureFrontDoor,
2662
3035
  ensureHeadroom,
2663
3036
  evaluateAmbientCredentialGuard,
2664
3037
  formatAmbientCredentialGuardMessage,
@@ -2668,15 +3041,16 @@ export {
2668
3041
  mintLeaf,
2669
3042
  parseConnectTarget,
2670
3043
  readFarmManifest,
2671
- realMitmCertStore,
2672
- realMitmEffects,
3044
+ realConnectCertStore,
3045
+ realConnectEffects,
2673
3046
  recoverFarm,
2674
3047
  recoveryDiagnostics,
2675
3048
  resolveDecisions,
2676
3049
  resolveSupervisorConfig,
2677
3050
  resyncFarm,
3051
+ runFrontDoorSupervisor,
2678
3052
  runSupervisor,
2679
- servedByHeadroom,
2680
- startMitmServer,
3053
+ servedByPipeline,
3054
+ startConnectServer,
2681
3055
  topLevelNames
2682
3056
  };