agent-shim 8.15.0 → 8.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -14464,7 +14464,7 @@ var require_client = __commonJS({
14464
14464
  "use strict";
14465
14465
  var assert2 = require("node:assert");
14466
14466
  var net3 = require("node:net");
14467
- var http6 = require("node:http");
14467
+ var http7 = require("node:http");
14468
14468
  var util = require_util2();
14469
14469
  var { ClientStats } = require_stats();
14470
14470
  var { channels } = require_diagnostics();
@@ -14522,7 +14522,7 @@ var require_client = __commonJS({
14522
14522
  var connectH1 = require_client_h1();
14523
14523
  var connectH2 = require_client_h2();
14524
14524
  var kClosedResolve = /* @__PURE__ */ Symbol("kClosedResolve");
14525
- var getDefaultNodeMaxHeaderSize = http6 && http6.maxHeaderSize && Number.isInteger(http6.maxHeaderSize) && http6.maxHeaderSize > 0 ? () => http6.maxHeaderSize : () => {
14525
+ var getDefaultNodeMaxHeaderSize = http7 && http7.maxHeaderSize && Number.isInteger(http7.maxHeaderSize) && http7.maxHeaderSize > 0 ? () => http7.maxHeaderSize : () => {
14526
14526
  throw new InvalidArgumentError2("http module not available or http.maxHeaderSize invalid");
14527
14527
  };
14528
14528
  var noop = () => {
@@ -55242,6 +55242,8 @@ var ProviderEnvSchema = external_exports.record(external_exports.string().min(1)
55242
55242
  var PROVIDER_KINDS = ["http", "codex"];
55243
55243
  var CODEX_TIERS = ["fable", "opus", "sonnet", "haiku"];
55244
55244
  var CODEX_EFFORTS = ["none", "low", "medium", "high"];
55245
+ var CODEX_LOGINS = ["codex-cli", "chatgpt-sign-in"];
55246
+ var CODEX_DEFAULT_LOGIN = "codex-cli";
55245
55247
  var CODEX_DEFAULT_MODEL = "gpt-5.6-sol";
55246
55248
  var CODEX_DEFAULT_TIER_MODELS = Object.freeze({
55247
55249
  fable: "gpt-5.6-sol",
@@ -55261,7 +55263,9 @@ var CodexProviderConfigSchema = external_exports.strictObject({
55261
55263
  haiku: external_exports.string().min(1).optional()
55262
55264
  }).optional(),
55263
55265
  /** The reasoning effort used when the request's own `output_config.effort` is not one the backend accepts. */
55264
- effort: external_exports.enum(CODEX_EFFORTS).optional()
55266
+ effort: external_exports.enum(CODEX_EFFORTS).optional(),
55267
+ /** Which login the upstream call is made with; see `CODEX_LOGINS`. */
55268
+ login: external_exports.enum(CODEX_LOGINS).optional()
55265
55269
  });
55266
55270
  var HttpProviderSchema = external_exports.strictObject({
55267
55271
  $schema: external_exports.string().optional(),
@@ -60278,6 +60282,7 @@ function buildLayoutPaths(root) {
60278
60282
  frontdoorCaCertFile: import_node_path19.default.join(root, "frontdoor", "ca", "ca.pem"),
60279
60283
  frontdoorCaKeyFile: import_node_path19.default.join(root, "frontdoor", "ca", "ca.key"),
60280
60284
  frontdoorCaBundlesDir: import_node_path19.default.join(root, "frontdoor", "ca", "bundles"),
60285
+ chatgptSignInFile: import_node_path19.default.join(root, "codex", "chatgpt-sign-in.json"),
60281
60286
  frontdoorStateFile: import_node_path19.default.join(root, "frontdoor", "state.json"),
60282
60287
  frontdoorLockFile: import_node_path19.default.join(root, "frontdoor", "start.lock"),
60283
60288
  frontdoorControlTokenFile: import_node_path19.default.join(root, "frontdoor", "control-token"),
@@ -61744,7 +61749,7 @@ function registerIdentityCommand(program2, deps) {
61744
61749
  // package.json
61745
61750
  var package_default = {
61746
61751
  name: "agent-shim",
61747
- version: "8.15.0",
61752
+ version: "8.16.0",
61748
61753
  description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
61749
61754
  license: "MIT",
61750
61755
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -63484,7 +63489,7 @@ var ProviderKindMismatchError = class extends UsageError {
63484
63489
  }
63485
63490
  };
63486
63491
  function isEmptyCodexConfig(config2) {
63487
- return config2 === void 0 || config2.defaultModel === void 0 && config2.effort === void 0 && (config2.models === void 0 || Object.keys(config2.models).length === 0);
63492
+ return config2 === void 0 || config2.defaultModel === void 0 && config2.effort === void 0 && config2.login === void 0 && (config2.models === void 0 || Object.keys(config2.models).length === 0);
63488
63493
  }
63489
63494
  function addProvider(paths, name, input2) {
63490
63495
  if (providerExists(paths, name)) {
@@ -64121,7 +64126,7 @@ function registerCheckCommand(program2, deps) {
64121
64126
  }
64122
64127
 
64123
64128
  // src/codex/commands.ts
64124
- var import_node_crypto7 = require("node:crypto");
64129
+ var import_node_crypto9 = require("node:crypto");
64125
64130
  var import_node_fs11 = __toESM(require("node:fs"), 1);
64126
64131
  var import_node_os6 = __toESM(require("node:os"), 1);
64127
64132
  var import_node_path31 = __toESM(require("node:path"), 1);
@@ -64272,8 +64277,273 @@ function createCodexAuthStore(authPath, ports) {
64272
64277
  };
64273
64278
  }
64274
64279
 
64275
- // src/codex/upstream.ts
64280
+ // src/codex/siwc.ts
64276
64281
  var import_node_crypto6 = require("node:crypto");
64282
+ var SIWC_AUTHORIZE_URL = "https://auth.openai.com/api/accounts/authorize";
64283
+ var SIWC_TOKEN_URL = "https://auth.openai.com/api/accounts/oauth/token";
64284
+ var SIWC_REVOKE_URL = "https://auth.openai.com/api/accounts/oauth/revoke";
64285
+ var SIWC_ISSUER = "https://auth.openai.com";
64286
+ var SIWC_JWKS_URL = "https://auth.openai.com/.well-known/jwks.json";
64287
+ var SIWC_DYNAMIC_CLIENT_ID = "dynamic_agent_client";
64288
+ var SIWC_RESOURCE = "https://api.openai.com/v1";
64289
+ var SIWC_RESPONSES_URL = `${SIWC_RESOURCE}/responses`;
64290
+ var SIWC_SCOPES = ["openid", "profile", "email", "offline_access", "resource.invoke", "chatgpt.tokens.use.direct"];
64291
+ var SIWC_PLAN_SCOPE = "chatgpt.tokens.use.direct";
64292
+ var SIWC_CALLBACK_PORT = 1455;
64293
+ var SIWC_CALLBACK_PATH = "/auth/callback";
64294
+ var SIWC_RANDOM_BYTES = 32;
64295
+ var MS_PER_SECOND4 = 1e3;
64296
+ var ID_TOKEN_ALGORITHM = "RS256";
64297
+ var SiwcError = class extends Error {
64298
+ constructor(message) {
64299
+ super(message);
64300
+ this.name = "SiwcError";
64301
+ }
64302
+ };
64303
+ var SiwcInvalidGrantError = class extends SiwcError {
64304
+ constructor(message) {
64305
+ super(message);
64306
+ this.name = "SiwcInvalidGrantError";
64307
+ }
64308
+ };
64309
+ function base64Url(bytes) {
64310
+ return Buffer.from(bytes).toString("base64url");
64311
+ }
64312
+ function newPkce(randomBytes4) {
64313
+ const verifier = base64Url(randomBytes4(SIWC_RANDOM_BYTES));
64314
+ return { verifier, challenge: (0, import_node_crypto6.createHash)("sha256").update(verifier).digest("base64url") };
64315
+ }
64316
+ function newOpaqueValue(randomBytes4) {
64317
+ return base64Url(randomBytes4(SIWC_RANDOM_BYTES));
64318
+ }
64319
+ function buildAuthorizeUrl(request7) {
64320
+ const url2 = new URL(SIWC_AUTHORIZE_URL);
64321
+ const params = url2.searchParams;
64322
+ params.set("client_id", request7.clientId ?? SIWC_DYNAMIC_CLIENT_ID);
64323
+ if (request7.clientId === void 0) {
64324
+ params.set("agent_name_hint", request7.agentName);
64325
+ }
64326
+ if (request7.returning !== void 0) {
64327
+ params.set("id_token_hint", request7.returning.idToken);
64328
+ if (request7.returning.loginHint !== void 0) {
64329
+ params.set("login_hint", request7.returning.loginHint);
64330
+ }
64331
+ }
64332
+ params.set("response_type", "code");
64333
+ params.set("redirect_uri", request7.redirectUri);
64334
+ params.set("scope", SIWC_SCOPES.join(" "));
64335
+ params.set("resource", SIWC_RESOURCE);
64336
+ params.set("state", request7.state);
64337
+ params.set("nonce", request7.nonce);
64338
+ params.set("code_challenge_method", "S256");
64339
+ params.set("code_challenge", request7.challenge);
64340
+ return url2.toString();
64341
+ }
64342
+ function parseCallback(url2, expectedState) {
64343
+ const params = url2.searchParams;
64344
+ const error62 = params.get("error");
64345
+ if (error62 !== null) {
64346
+ throw new SiwcError(`the sign-in was refused: ${error62}${params.has("error_description") ? ` (${params.get("error_description") ?? ""})` : ""}`);
64347
+ }
64348
+ if (params.get("state") !== expectedState) {
64349
+ throw new SiwcError("the sign-in callback's state did not match the request, so it was discarded");
64350
+ }
64351
+ const code = params.get("code");
64352
+ if (code === null || code === "") {
64353
+ throw new SiwcError("the sign-in callback carried no authorization code");
64354
+ }
64355
+ const scope = params.get("scope");
64356
+ return { code, clientId: params.get("client_id") ?? void 0, scopes: scope === null ? [] : scope.split(" ").filter((part) => part !== "") };
64357
+ }
64358
+ var TokenResponseSchema = external_exports.looseObject({
64359
+ access_token: external_exports.string().min(1),
64360
+ refresh_token: external_exports.string().min(1).optional(),
64361
+ id_token: external_exports.string().min(1).optional(),
64362
+ expires_in: external_exports.number().positive(),
64363
+ scope: external_exports.string().optional()
64364
+ });
64365
+ var ERROR_BODY_CHARS = 300;
64366
+ var TOKEN_CALL_TIMEOUT_MS = 3e4;
64367
+ async function postToken(ports, form, what) {
64368
+ const response = await ports.fetch(SIWC_TOKEN_URL, {
64369
+ method: "POST",
64370
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
64371
+ body: new URLSearchParams({ ...form, resource: SIWC_RESOURCE }).toString(),
64372
+ signal: AbortSignal.timeout(TOKEN_CALL_TIMEOUT_MS)
64373
+ });
64374
+ if (!response.ok) {
64375
+ const detail = (await response.text()).slice(0, ERROR_BODY_CHARS);
64376
+ const message = `the ${what} failed with HTTP ${String(response.status)}: ${detail}`;
64377
+ throw /"error"\s*:\s*"invalid_grant"/.test(detail) ? new SiwcInvalidGrantError(message) : new SiwcError(message);
64378
+ }
64379
+ const parsed = TokenResponseSchema.safeParse(await response.json());
64380
+ if (!parsed.success) {
64381
+ throw new SiwcError(`the ${what} answered without an access token and a lifetime`);
64382
+ }
64383
+ return parsed.data;
64384
+ }
64385
+ function grantTokens(ports, answer) {
64386
+ return {
64387
+ accessToken: answer.access_token,
64388
+ refreshToken: answer.refresh_token,
64389
+ idToken: answer.id_token,
64390
+ scopes: answer.scope === void 0 ? void 0 : answer.scope.split(" ").filter((part) => part !== ""),
64391
+ expiresAt: ports.now().getTime() + answer.expires_in * MS_PER_SECOND4
64392
+ };
64393
+ }
64394
+ async function exchangeCode(ports, request7) {
64395
+ return grantTokens(
64396
+ ports,
64397
+ await postToken(ports, { grant_type: "authorization_code", client_id: request7.clientId, code: request7.code, code_verifier: request7.verifier, redirect_uri: request7.redirectUri }, "authorisation code exchange")
64398
+ );
64399
+ }
64400
+ async function refreshTokens(ports, request7) {
64401
+ return grantTokens(ports, await postToken(ports, { grant_type: "refresh_token", client_id: request7.clientId, refresh_token: request7.refreshToken }, "token refresh"));
64402
+ }
64403
+ var JwtHeaderSchema = external_exports.looseObject({ alg: external_exports.string(), kid: external_exports.string().optional() });
64404
+ var IdTokenClaimsSchema = external_exports.looseObject({
64405
+ iss: external_exports.string(),
64406
+ sub: external_exports.string().min(1),
64407
+ aud: external_exports.union([external_exports.string(), external_exports.array(external_exports.string())]),
64408
+ exp: external_exports.number(),
64409
+ nonce: external_exports.string().optional(),
64410
+ email: external_exports.string().optional()
64411
+ });
64412
+ var JwksSchema = external_exports.object({ keys: external_exports.array(external_exports.looseObject({ kid: external_exports.string().optional(), kty: external_exports.string() })) });
64413
+ function decodeSegment(segment) {
64414
+ if (segment === void 0) {
64415
+ throw new SiwcError("the ID token is not a three-part JWT");
64416
+ }
64417
+ try {
64418
+ const parsed = JSON.parse(Buffer.from(segment, "base64url").toString("utf8"));
64419
+ return parsed;
64420
+ } catch {
64421
+ throw new SiwcError("the ID token has an unreadable segment");
64422
+ }
64423
+ }
64424
+ async function verifyIdToken(ports, idToken, expected) {
64425
+ const [headerSegment, payloadSegment, signatureSegment, ...rest] = idToken.split(".");
64426
+ if (rest.length > 0 || headerSegment === void 0 || payloadSegment === void 0 || signatureSegment === void 0) {
64427
+ throw new SiwcError("the ID token is not a three-part JWT");
64428
+ }
64429
+ const header = JwtHeaderSchema.parse(decodeSegment(headerSegment));
64430
+ if (header.alg !== ID_TOKEN_ALGORITHM) {
64431
+ throw new SiwcError(`the ID token is signed with ${header.alg}, and only ${ID_TOKEN_ALGORITHM} is accepted`);
64432
+ }
64433
+ const response = await ports.fetch(SIWC_JWKS_URL, { method: "GET", headers: {}, signal: AbortSignal.timeout(TOKEN_CALL_TIMEOUT_MS) });
64434
+ if (!response.ok) {
64435
+ throw new SiwcError(`the issuer's key set could not be fetched (HTTP ${String(response.status)})`);
64436
+ }
64437
+ const jwk = JwksSchema.parse(await response.json()).keys.find((key) => key.kty === "RSA" && (header.kid === void 0 || key.kid === header.kid));
64438
+ if (jwk === void 0) {
64439
+ throw new SiwcError("the issuer's key set has no key for the ID token's signature");
64440
+ }
64441
+ const signed = Buffer.from(`${headerSegment}.${payloadSegment}`);
64442
+ if (!(0, import_node_crypto6.verify)("sha256", signed, (0, import_node_crypto6.createPublicKey)({ key: jwk, format: "jwk" }), Buffer.from(signatureSegment, "base64url"))) {
64443
+ throw new SiwcError("the ID token's signature did not verify");
64444
+ }
64445
+ const claims = IdTokenClaimsSchema.parse(decodeSegment(payloadSegment));
64446
+ if (claims.iss !== SIWC_ISSUER) {
64447
+ throw new SiwcError(`the ID token was issued by ${claims.iss}, not ${SIWC_ISSUER}`);
64448
+ }
64449
+ if (!(Array.isArray(claims.aud) ? claims.aud : [claims.aud]).includes(expected.clientId)) {
64450
+ throw new SiwcError("the ID token was issued for a different client");
64451
+ }
64452
+ if (claims.exp * MS_PER_SECOND4 <= ports.now().getTime()) {
64453
+ throw new SiwcError("the ID token has already expired");
64454
+ }
64455
+ if (expected.nonce !== void 0 && claims.nonce !== expected.nonce) {
64456
+ throw new SiwcError("the ID token's nonce did not match the request");
64457
+ }
64458
+ return { sub: claims.sub, email: claims.email };
64459
+ }
64460
+
64461
+ // src/codex/siwcLogin.ts
64462
+ var SIWC_AGENT_NAME = "agent-shim";
64463
+ var SIWC_LOGIN_TIMEOUT_MS = 3e5;
64464
+ async function runSiwcLogin(ports, options) {
64465
+ const existing = ports.store.read();
64466
+ const returning = existing?.grant;
64467
+ for (let attempt = 0; ; attempt += 1) {
64468
+ const clientId = ports.store.read()?.registeredClientId ?? returning?.clientId;
64469
+ try {
64470
+ return await authorizeOnce(ports, options, clientId, attempt === 0 ? returning : void 0);
64471
+ } catch (error62) {
64472
+ if (!(error62 instanceof SiwcInvalidGrantError) || attempt > 0) {
64473
+ throw error62;
64474
+ }
64475
+ ports.print("OpenAI did not accept the first code, which is expected once after registering: authorising again with the registration it issued.");
64476
+ }
64477
+ }
64478
+ }
64479
+ async function authorizeOnce(ports, options, savedClientId, returning) {
64480
+ const pkce = newPkce(ports.siwc.randomBytes);
64481
+ const state = newOpaqueValue(ports.siwc.randomBytes);
64482
+ const nonce = newOpaqueValue(ports.siwc.randomBytes);
64483
+ const redirectUri = `http://127.0.0.1:${String(SIWC_CALLBACK_PORT)}${SIWC_CALLBACK_PATH}`;
64484
+ const url2 = buildAuthorizeUrl({
64485
+ clientId: savedClientId,
64486
+ agentName: SIWC_AGENT_NAME,
64487
+ redirectUri,
64488
+ state,
64489
+ nonce,
64490
+ challenge: pkce.challenge,
64491
+ ...returning === void 0 ? {} : { returning: { idToken: returning.idToken, loginHint: returning.email } }
64492
+ });
64493
+ const listener = await ports.listen(SIWC_CALLBACK_PORT, state);
64494
+ try {
64495
+ ports.print(`Sign in at: ${url2}`);
64496
+ if (options.open) {
64497
+ ports.openBrowser(url2);
64498
+ }
64499
+ const callback = parseCallback(await listener.waitForCallback(AbortSignal.timeout(SIWC_LOGIN_TIMEOUT_MS)), state);
64500
+ const clientId = callback.clientId ?? savedClientId;
64501
+ if (clientId === void 0) {
64502
+ throw new SiwcError("the sign-in callback carried no client id and none was stored from an earlier sign-in");
64503
+ }
64504
+ const grantBefore = ports.store.read()?.grant;
64505
+ ports.store.write({ registeredClientId: clientId, ...grantBefore === void 0 ? {} : { grant: grantBefore } });
64506
+ const tokens = await exchangeCode(ports.siwc, { clientId, code: callback.code, verifier: pkce.verifier, redirectUri });
64507
+ if (tokens.refreshToken === void 0 || tokens.idToken === void 0) {
64508
+ throw new SiwcError("the token endpoint did not return a refresh token and an ID token, so the sign-in cannot be kept");
64509
+ }
64510
+ const scopes = tokens.scopes ?? callback.scopes;
64511
+ if (!scopes.includes(SIWC_PLAN_SCOPE)) {
64512
+ throw new SiwcError("the sign-in was not granted permission to use your ChatGPT plan: run the login again and allow it");
64513
+ }
64514
+ const identity = await verifyIdToken(ports.siwc, tokens.idToken, { clientId, nonce });
64515
+ ports.store.write({
64516
+ registeredClientId: clientId,
64517
+ grant: { clientId, sub: identity.sub, ...identity.email === void 0 ? {} : { email: identity.email }, idToken: tokens.idToken, accessToken: tokens.accessToken, refreshToken: tokens.refreshToken, scopes, expiresAt: tokens.expiresAt }
64518
+ });
64519
+ return { email: identity.email, sub: identity.sub };
64520
+ } finally {
64521
+ listener.close();
64522
+ }
64523
+ }
64524
+ var REVOKE_TIMEOUT_MS = 3e4;
64525
+ async function runSiwcLogout(ports) {
64526
+ const file2 = ports.store.read();
64527
+ if (file2?.grant === void 0) {
64528
+ return { hadGrant: false, revoked: false };
64529
+ }
64530
+ const revoked = await ports.siwc.fetch(SIWC_REVOKE_URL, {
64531
+ method: "POST",
64532
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
64533
+ body: new URLSearchParams({ token: file2.grant.refreshToken, token_type_hint: "refresh_token", client_id: file2.grant.clientId }).toString(),
64534
+ signal: AbortSignal.timeout(REVOKE_TIMEOUT_MS)
64535
+ }).then(
64536
+ (response) => response.ok,
64537
+ () => false
64538
+ );
64539
+ ports.store.write({ registeredClientId: file2.grant.clientId });
64540
+ return { hadGrant: true, revoked };
64541
+ }
64542
+
64543
+ // src/codex/siwcPorts.ts
64544
+ var import_node_child_process4 = require("node:child_process");
64545
+ var import_node_crypto7 = require("node:crypto");
64546
+ var import_node_http = __toESM(require("node:http"), 1);
64277
64547
 
64278
64548
  // src/codex/http.ts
64279
64549
  var HTTP_STATUS = {
@@ -64291,7 +64561,157 @@ var HTTP_STATUS = {
64291
64561
  badGateway: 502
64292
64562
  };
64293
64563
 
64564
+ // src/codex/siwcPorts.ts
64565
+ function realSiwcPorts(fetch3) {
64566
+ return { fetch: fetch3, randomBytes: (size) => (0, import_node_crypto7.randomBytes)(size), now: () => /* @__PURE__ */ new Date() };
64567
+ }
64568
+ var CALLBACK_PAGE = "<!doctype html><meta charset=utf-8><title>Signed in</title><p>You can close this tab and return to the terminal.</p>";
64569
+ async function listenForCallback(port, state) {
64570
+ let deliver;
64571
+ const arrived = new Promise((resolve) => {
64572
+ deliver = resolve;
64573
+ });
64574
+ const server = import_node_http.default.createServer((request7, response) => {
64575
+ const url2 = new URL(request7.url ?? "/", `http://127.0.0.1:${String(port)}`);
64576
+ if (request7.method !== "GET" || url2.pathname !== SIWC_CALLBACK_PATH || url2.searchParams.getAll("state").length !== 1 || url2.searchParams.get("state") !== state) {
64577
+ response.writeHead(HTTP_STATUS.notFound).end("Not found");
64578
+ return;
64579
+ }
64580
+ response.writeHead(HTTP_STATUS.ok, { "Content-Type": "text/html; charset=utf-8" });
64581
+ response.end(CALLBACK_PAGE);
64582
+ deliver?.(url2);
64583
+ });
64584
+ await new Promise((resolve, reject) => {
64585
+ server.once("error", (error62) => {
64586
+ reject(error62.code === "EADDRINUSE" ? new Error(`port ${String(port)} is in use (another sign-in, such as \`codex login\`, may be running): finish or stop it and try again`) : error62);
64587
+ });
64588
+ server.listen(port, "127.0.0.1", () => {
64589
+ resolve();
64590
+ });
64591
+ });
64592
+ return {
64593
+ waitForCallback: async (signal) => await Promise.race([
64594
+ arrived,
64595
+ new Promise((_resolve, reject) => {
64596
+ signal.addEventListener("abort", () => {
64597
+ reject(new Error("timed out waiting for the browser sign-in to finish"));
64598
+ });
64599
+ })
64600
+ ]),
64601
+ close: () => {
64602
+ server.close();
64603
+ server.closeAllConnections();
64604
+ }
64605
+ };
64606
+ }
64607
+ function openInBrowser(url2) {
64608
+ const command = process.platform === "darwin" ? { file: "open", args: [url2] } : process.platform === "win32" ? { file: "cmd", args: ["/c", "start", "", url2] } : { file: "xdg-open", args: [url2] };
64609
+ (0, import_node_child_process4.spawn)(command.file, command.args, { detached: true, stdio: "ignore" }).unref();
64610
+ }
64611
+
64612
+ // src/codex/siwcStore.ts
64613
+ var SiwcGrantSchema = external_exports.strictObject({
64614
+ clientId: external_exports.string().min(1),
64615
+ sub: external_exports.string().min(1),
64616
+ email: external_exports.string().optional(),
64617
+ idToken: external_exports.string().min(1),
64618
+ accessToken: external_exports.string().min(1),
64619
+ refreshToken: external_exports.string().min(1),
64620
+ scopes: external_exports.array(external_exports.string()).readonly(),
64621
+ /** When the access token stops working, in milliseconds since the epoch. */
64622
+ expiresAt: external_exports.number()
64623
+ });
64624
+ var SiwcFileSchema = external_exports.strictObject({ registeredClientId: external_exports.string().min(1).optional(), grant: SiwcGrantSchema.optional() });
64625
+ var SIWC_REFRESH_MARGIN_MS = 6e4;
64626
+ function parseSiwcFile(raw, filePath) {
64627
+ let json2;
64628
+ try {
64629
+ json2 = JSON.parse(raw);
64630
+ } catch {
64631
+ throw new CodexAuthError(`${filePath} is not valid JSON: run \`agent-shim codex login\` again`);
64632
+ }
64633
+ const parsed = SiwcFileSchema.safeParse(json2);
64634
+ if (!parsed.success) {
64635
+ throw new CodexAuthError(`${filePath} is not a Sign in with ChatGPT file: run \`agent-shim codex login\` again`);
64636
+ }
64637
+ return parsed.data;
64638
+ }
64639
+ function grantFrom(previous, tokens) {
64640
+ return {
64641
+ ...previous,
64642
+ accessToken: tokens.accessToken,
64643
+ refreshToken: tokens.refreshToken ?? previous.refreshToken,
64644
+ idToken: tokens.idToken ?? previous.idToken,
64645
+ scopes: tokens.scopes ?? previous.scopes,
64646
+ expiresAt: tokens.expiresAt
64647
+ };
64648
+ }
64649
+ function createSiwcStore(filePath, ports) {
64650
+ let cached2;
64651
+ let inFlight;
64652
+ const read = () => {
64653
+ const raw = ports.fs.read(filePath);
64654
+ return raw === void 0 ? void 0 : parseSiwcFile(raw, filePath);
64655
+ };
64656
+ const write = (file2) => {
64657
+ const temp = `${filePath}.${ports.tempSuffix}.tmp`;
64658
+ ports.fs.writePrivate(temp, `${JSON.stringify(file2, null, 2)}
64659
+ `);
64660
+ ports.fs.rename(temp, filePath);
64661
+ cached2 = file2.grant;
64662
+ };
64663
+ const loadSignedIn = () => {
64664
+ const file2 = read();
64665
+ if (file2?.grant === void 0) {
64666
+ throw new CodexAuthError(`no Sign in with ChatGPT login at ${filePath}: run \`agent-shim codex login\` first`);
64667
+ }
64668
+ if (!file2.grant.scopes.includes(SIWC_PLAN_SCOPE)) {
64669
+ throw new CodexAuthError(`the Sign in with ChatGPT login at ${filePath} was not granted permission to use your ChatGPT plan: run \`agent-shim codex login\` again and allow it`);
64670
+ }
64671
+ return { file: file2, grant: file2.grant };
64672
+ };
64673
+ const credentialsOf2 = (grant) => ({ accessToken: grant.accessToken, accountId: void 0 });
64674
+ const doRefresh = async (rejected) => {
64675
+ const { file: file2, grant } = loadSignedIn();
64676
+ if (rejected !== void 0 && grant.accessToken !== rejected) {
64677
+ cached2 = grant;
64678
+ return credentialsOf2(grant);
64679
+ }
64680
+ let tokens;
64681
+ try {
64682
+ tokens = await refreshTokens(ports.siwc, { clientId: grant.clientId, refreshToken: grant.refreshToken });
64683
+ } catch (error62) {
64684
+ if (error62 instanceof SiwcError) {
64685
+ throw new CodexAuthError(`${error62.message}: run \`agent-shim codex login\` again if the login was revoked`);
64686
+ }
64687
+ throw error62;
64688
+ }
64689
+ const next = grantFrom(grant, tokens);
64690
+ write({ ...file2, grant: next });
64691
+ return credentialsOf2(next);
64692
+ };
64693
+ const refreshOnce = async (rejected) => await (inFlight ??= doRefresh(rejected).finally(() => {
64694
+ inFlight = void 0;
64695
+ }));
64696
+ return {
64697
+ read,
64698
+ write,
64699
+ auth: {
64700
+ current: async () => {
64701
+ const grant = cached2 ?? loadSignedIn().grant;
64702
+ cached2 = grant;
64703
+ if (grant.expiresAt - SIWC_REFRESH_MARGIN_MS <= ports.siwc.now().getTime()) {
64704
+ return await refreshOnce(void 0);
64705
+ }
64706
+ return credentialsOf2(grant);
64707
+ },
64708
+ refresh: async (rejected) => await refreshOnce(rejected)
64709
+ }
64710
+ };
64711
+ }
64712
+
64294
64713
  // src/codex/upstream.ts
64714
+ var import_node_crypto8 = require("node:crypto");
64295
64715
  var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
64296
64716
  var CODEX_UPSTREAM_TIMEOUT_MS = 3e5;
64297
64717
  var CODEX_HEADERS_TIMEOUT_MS = 15e3;
@@ -64327,12 +64747,17 @@ function sessionIdFor(userId, randomId) {
64327
64747
  if (userId === void 0 || userId === "") {
64328
64748
  return randomId();
64329
64749
  }
64330
- const hex3 = (0, import_node_crypto6.createHash)("sha256").update(`agent-shim codex session
64750
+ const hex3 = (0, import_node_crypto8.createHash)("sha256").update(`agent-shim codex session
64331
64751
  ${userId}`).digest("hex");
64332
64752
  let next = 0;
64333
64753
  return UUID_TEMPLATE.replace(/x/g, () => hex3.charAt(next++));
64334
64754
  }
64335
- function headersFor(credentials, sessionId) {
64755
+ var CODEX_BACKEND_TARGET = { url: CODEX_RESPONSES_URL, headers: codexBackendHeaders };
64756
+ var RESPONSES_API_TARGET = {
64757
+ url: SIWC_RESPONSES_URL,
64758
+ headers: (credentials) => ({ "Content-Type": "application/json", Accept: "text/event-stream", Authorization: `Bearer ${credentials.accessToken}` })
64759
+ };
64760
+ function codexBackendHeaders(credentials, sessionId) {
64336
64761
  return {
64337
64762
  "Content-Type": "application/json",
64338
64763
  Accept: "text/event-stream",
@@ -64344,11 +64769,11 @@ function headersFor(credentials, sessionId) {
64344
64769
  };
64345
64770
  }
64346
64771
  async function callCodex(ports, request7, sessionId, signal) {
64347
- const url2 = ports.url ?? CODEX_RESPONSES_URL;
64772
+ const target = ports.target ?? CODEX_BACKEND_TARGET;
64348
64773
  const body = JSON.stringify(request7);
64349
- const send = async (credentials2) => await fetchUntilHeaders(ports.fetch, ports.timers, url2, {
64774
+ const send = async (credentials2) => await fetchUntilHeaders(ports.fetch, ports.timers, target.url, {
64350
64775
  method: "POST",
64351
- headers: headersFor(credentials2, sessionId),
64776
+ headers: target.headers(credentials2, sessionId),
64352
64777
  body,
64353
64778
  signal: AbortSignal.any([signal, AbortSignal.timeout(CODEX_UPSTREAM_TIMEOUT_MS)])
64354
64779
  });
@@ -64384,6 +64809,7 @@ var realCodexAuthFs = {
64384
64809
  }
64385
64810
  },
64386
64811
  writePrivate: (filePath, contents) => {
64812
+ import_node_fs11.default.mkdirSync(import_node_path31.default.dirname(filePath), { recursive: true });
64387
64813
  import_node_fs11.default.writeFileSync(filePath, contents, { encoding: "utf8", mode: PRIVATE_FILE_MODE });
64388
64814
  import_node_fs11.default.chmodSync(filePath, PRIVATE_FILE_MODE);
64389
64815
  },
@@ -64403,17 +64829,22 @@ function writeUsageSnapshot(log, snapshot) {
64403
64829
  log(`usage snapshot write failed: ${error62 instanceof Error ? error62.message : String(error62)}`);
64404
64830
  }
64405
64831
  }
64406
- function createCodexRoutePorts(log) {
64832
+ function createCodexRoutePorts(log, signInFile) {
64407
64833
  const agent = createUpstreamAgent();
64408
64834
  const upstreamFetch = createUpstreamFetch(agent);
64835
+ const tempSuffix = `${String(process.pid)}.${(0, import_node_crypto9.randomUUID)()}`;
64409
64836
  const auth = createCodexAuthStore(import_node_path31.default.join(codexHome(process.env, import_node_os6.default.homedir()), "auth.json"), {
64410
64837
  fs: realCodexAuthFs,
64411
64838
  fetch: upstreamFetch,
64412
64839
  now: () => /* @__PURE__ */ new Date(),
64413
- tempSuffix: `${String(process.pid)}.${(0, import_node_crypto7.randomUUID)()}`
64840
+ tempSuffix
64414
64841
  });
64842
+ const signIn = createSiwcStore(signInFile, { fs: realCodexAuthFs, siwc: realSiwcPorts(upstreamFetch), tempSuffix });
64415
64843
  return {
64416
- upstream: { fetch: upstreamFetch, auth, timers: realTimers, randomId: import_node_crypto7.randomUUID },
64844
+ upstreams: {
64845
+ "codex-cli": { fetch: upstreamFetch, auth, timers: realTimers, randomId: import_node_crypto9.randomUUID },
64846
+ "chatgpt-sign-in": { fetch: upstreamFetch, auth: signIn.auth, timers: realTimers, randomId: import_node_crypto9.randomUUID, target: RESPONSES_API_TARGET }
64847
+ },
64417
64848
  writeUsageSnapshot: (snapshot) => {
64418
64849
  writeUsageSnapshot(log, snapshot);
64419
64850
  },
@@ -64421,6 +64852,18 @@ function createCodexRoutePorts(log) {
64421
64852
  log
64422
64853
  };
64423
64854
  }
64855
+ function readSignInStatus(fsPort, filePath) {
64856
+ const raw = fsPort.readFileUtf8(filePath);
64857
+ if (raw === void 0) {
64858
+ return { state: "none" };
64859
+ }
64860
+ try {
64861
+ const file2 = parseSiwcFile(raw, filePath);
64862
+ return file2.grant === void 0 ? { state: "none" } : { state: "signed-in", email: file2.grant.email, planScope: file2.grant.scopes.includes(SIWC_PLAN_SCOPE), accessTokenExpiresAt: file2.grant.expiresAt };
64863
+ } catch (error62) {
64864
+ return { state: "unreadable", message: error62 instanceof Error ? error62.message : String(error62) };
64865
+ }
64866
+ }
64424
64867
  function codexProviderNames(fsPort, providersDir) {
64425
64868
  return readAllProviders(fsPort, providersDir).filter((entry) => isCodexProvider(entry.provider)).map((entry) => entry.name).sort();
64426
64869
  }
@@ -64431,12 +64874,22 @@ function collectCodexStatus(fsPort, paths, isRunning) {
64431
64874
  supervisorAlive: state.supervisorPid !== void 0 && isRunning(state.supervisorPid),
64432
64875
  sessions: listFrontDoorSessions(fsPort, paths.frontdoorSessionsDir).map((session) => ({ ...session, alive: isRunning(session.pid) })),
64433
64876
  codexProviders: codexProviderNames(fsPort, paths.providersDir),
64877
+ signIn: readSignInStatus(fsPort, paths.chatgptSignInFile),
64434
64878
  usageSnapshotPath: codexUsageSnapshotPath(),
64435
64879
  usageSnapshotExists: fsPort.readFileUtf8(codexUsageSnapshotPath()) !== void 0,
64436
64880
  logPath: paths.frontdoorLogPath,
64437
64881
  logExists: fsPort.readFileUtf8(paths.frontdoorLogPath) !== void 0
64438
64882
  };
64439
64883
  }
64884
+ function formatSignIn(signIn) {
64885
+ if (signIn.state === "none") {
64886
+ return "chatgpt sign-in: not signed in (run `agent-shim codex login`)";
64887
+ }
64888
+ if (signIn.state === "unreadable") {
64889
+ return `chatgpt sign-in: ${signIn.message}`;
64890
+ }
64891
+ return `chatgpt sign-in: signed in${signIn.email === void 0 ? "" : ` as ${signIn.email}`}${signIn.planScope ? "" : ", WITHOUT permission to use your ChatGPT plan (run `agent-shim codex login` again and allow it)"}`;
64892
+ }
64440
64893
  function formatCodexStatus(status) {
64441
64894
  const lines = [];
64442
64895
  lines.push(
@@ -64457,10 +64910,24 @@ function formatCodexStatus(status) {
64457
64910
  if (status.frontDoor.lastError !== void 0) {
64458
64911
  lines.push(`last error: ${status.frontDoor.lastError}`);
64459
64912
  }
64913
+ lines.push(formatSignIn(status.signIn));
64460
64914
  lines.push(`usage snapshot: ${status.usageSnapshotPath}${status.usageSnapshotExists ? "" : " (not created yet)"}`);
64461
64915
  lines.push(`daemon log: ${status.logPath}${status.logExists ? "" : " (not created yet)"}`);
64462
64916
  return lines;
64463
64917
  }
64918
+ function realSiwcLoginPorts(paths) {
64919
+ const siwc = realSiwcPorts(createUpstreamFetch(createUpstreamAgent()));
64920
+ const store = createSiwcStore(paths.chatgptSignInFile, { fs: realCodexAuthFs, siwc, tempSuffix: `${String(process.pid)}.${(0, import_node_crypto9.randomUUID)()}` });
64921
+ return {
64922
+ store,
64923
+ siwc,
64924
+ listen: listenForCallback,
64925
+ openBrowser: openInBrowser,
64926
+ print: (line) => {
64927
+ console.log(line);
64928
+ }
64929
+ };
64930
+ }
64464
64931
  function registerCodexCommand(program2, deps) {
64465
64932
  const { paths } = deps;
64466
64933
  const codex = withExamples(program2.command("codex").description("Inspect the codex translation the front-door daemon serves for codex providers."), ["agent-shim codex status"]);
@@ -64477,10 +64944,38 @@ function registerCodexCommand(program2, deps) {
64477
64944
  }),
64478
64945
  ["agent-shim codex status", "agent-shim codex status --json"]
64479
64946
  );
64947
+ withExamples(
64948
+ codex.command("login").description("Sign in with ChatGPT so a codex provider with `codex.login` set to `chatgpt-sign-in` can spend your ChatGPT plan on OpenAI's public Responses API. Opens your browser.").option("--no-open", "Print the sign-in address instead of opening the browser.").option("--json", "Print the result as JSON.").action(async (options) => {
64949
+ const ports = realSiwcLoginPorts(paths);
64950
+ const result = await runSiwcLogin(options.json === true ? { ...ports, print: () => void 0 } : ports, { open: options.open });
64951
+ if (options.json === true) {
64952
+ printJson({ action: "login", email: result.email ?? null, sub: result.sub });
64953
+ return;
64954
+ }
64955
+ console.log(`Signed in${result.email === void 0 ? "" : ` as ${result.email}`}. Set "codex": { "login": "chatgpt-sign-in" } on a codex provider to use it.`);
64956
+ }),
64957
+ ["agent-shim codex login", "agent-shim codex login --no-open"]
64958
+ );
64959
+ withExamples(
64960
+ codex.command("logout").description("Revoke the Sign in with ChatGPT login and remove it from this machine.").option("--json", "Print the result as JSON.").action(async (options) => {
64961
+ const ports = realSiwcLoginPorts(paths);
64962
+ const result = await runSiwcLogout(ports);
64963
+ if (options.json === true) {
64964
+ printJson({ action: "logout", ...result });
64965
+ return;
64966
+ }
64967
+ if (!result.hadGrant) {
64968
+ console.log("Not signed in.");
64969
+ return;
64970
+ }
64971
+ console.log(result.revoked ? "Signed out and revoked the login at OpenAI." : "Signed out here, but OpenAI could not be told to revoke the login: revoke it in ChatGPT settings.");
64972
+ }),
64973
+ ["agent-shim codex logout"]
64974
+ );
64480
64975
  }
64481
64976
 
64482
64977
  // src/frontdoor/commands.ts
64483
- var import_node_crypto12 = require("node:crypto");
64978
+ var import_node_crypto14 = require("node:crypto");
64484
64979
  var import_node_path36 = __toESM(require("node:path"), 1);
64485
64980
  var import_node_fs13 = __toESM(require("node:fs"), 1);
64486
64981
 
@@ -64585,7 +65080,7 @@ function createDoorPipelines(deps) {
64585
65080
  }
64586
65081
 
64587
65082
  // src/frontdoor/capability.ts
64588
- var import_node_crypto8 = require("node:crypto");
65083
+ var import_node_crypto10 = require("node:crypto");
64589
65084
  var CONNECT_PROXY_USER = "agent-shim";
64590
65085
  var CONNECT_PROXY_REALM = "agent-shim front door";
64591
65086
  var BASIC_SCHEME = "basic";
@@ -64607,13 +65102,13 @@ function capabilityFromProxyAuthorization(value2) {
64607
65102
  return password === "" ? void 0 : password;
64608
65103
  }
64609
65104
  function digest(token) {
64610
- return (0, import_node_crypto8.createHash)("sha256").update(token, "utf8").digest();
65105
+ return (0, import_node_crypto10.createHash)("sha256").update(token, "utf8").digest();
64611
65106
  }
64612
65107
  function isLiveCapability(presented, live) {
64613
65108
  const presentedDigest = digest(presented);
64614
65109
  let matched = false;
64615
65110
  for (const token of live) {
64616
- const equal = (0, import_node_crypto8.timingSafeEqual)(presentedDigest, digest(token));
65111
+ const equal = (0, import_node_crypto10.timingSafeEqual)(presentedDigest, digest(token));
64617
65112
  matched = equal || matched;
64618
65113
  }
64619
65114
  return matched;
@@ -64757,11 +65252,11 @@ function createFileCapture(file2, now = () => /* @__PURE__ */ new Date()) {
64757
65252
  }
64758
65253
 
64759
65254
  // src/frontdoor/connect.ts
64760
- var import_node_crypto10 = require("node:crypto");
65255
+ var import_node_crypto12 = require("node:crypto");
64761
65256
  var fs13 = __toESM(require("node:fs"), 1);
64762
65257
 
64763
65258
  // src/frontdoor/x509.ts
64764
- var import_node_crypto9 = require("node:crypto");
65259
+ var import_node_crypto11 = require("node:crypto");
64765
65260
  var import_node_net = __toESM(require("node:net"), 1);
64766
65261
  var TAG_BOOLEAN = 1;
64767
65262
  var TAG_INTEGER = 2;
@@ -64797,7 +65292,7 @@ var HIGH_BIT = 128;
64797
65292
  var BASE_128_PAYLOAD_MASK = 127;
64798
65293
  var FIRST_ARC_WEIGHT = 40;
64799
65294
  var SHORT_FORM_MAX_LENGTH = 127;
64800
- var MS_PER_SECOND4 = 1e3;
65295
+ var MS_PER_SECOND5 = 1e3;
64801
65296
  var UTC_TIME_LAST_YEAR = 2049;
64802
65297
  var YEAR_DIGITS = 4;
64803
65298
  var TWO_DIGIT_YEAR_DIGITS = 2;
@@ -64856,7 +65351,7 @@ function wholeBitString(content) {
64856
65351
  return tlv(TAG_BIT_STRING, Buffer.concat([Buffer.from([0]), content]));
64857
65352
  }
64858
65353
  function time3(instant) {
64859
- const iso = new Date(Math.floor(instant.getTime() / MS_PER_SECOND4) * MS_PER_SECOND4).toISOString();
65354
+ const iso = new Date(Math.floor(instant.getTime() / MS_PER_SECOND5) * MS_PER_SECOND5).toISOString();
64860
65355
  const compact = `${iso.replace(/[-:T]/g, "").slice(0, COMPACT_TIME_DIGITS)}Z`;
64861
65356
  if (instant.getUTCFullYear() > UTC_TIME_LAST_YEAR) {
64862
65357
  return tlv(TAG_GENERALIZED_TIME, Buffer.from(compact, "ascii"));
@@ -64913,7 +65408,7 @@ function ipBytes(address) {
64913
65408
  return bytes;
64914
65409
  }
64915
65410
  function keyIdentifier(publicKey) {
64916
- return (0, import_node_crypto9.createHash)("sha1").update(publicKey.export({ type: "pkcs1", format: "der" })).digest();
65411
+ return (0, import_node_crypto11.createHash)("sha1").update(publicKey.export({ type: "pkcs1", format: "der" })).digest();
64917
65412
  }
64918
65413
  function readElement(buffer, offset) {
64919
65414
  const tag = buffer[offset];
@@ -64959,7 +65454,7 @@ ${body.join("\n")}
64959
65454
  `;
64960
65455
  }
64961
65456
  function randomSerial() {
64962
- const bytes = (0, import_node_crypto9.randomBytes)(SERIAL_NUMBER_BYTES);
65457
+ const bytes = (0, import_node_crypto11.randomBytes)(SERIAL_NUMBER_BYTES);
64963
65458
  bytes[0] = (bytes[0] ?? 0) & BASE_128_PAYLOAD_MASK | HIGH_BIT >> 1;
64964
65459
  return bytes;
64965
65460
  }
@@ -64974,7 +65469,7 @@ function assemble(spec) {
64974
65469
  spec.subjectPublicKey.export({ type: "spki", format: "der" }),
64975
65470
  tlv(TAG_EXTENSIONS, sequence(...spec.extensions))
64976
65471
  );
64977
- const signature = (0, import_node_crypto9.sign)("sha256", tbs, spec.signingKey);
65472
+ const signature = (0, import_node_crypto11.sign)("sha256", tbs, spec.signingKey);
64978
65473
  return sequence(tbs, SIGNATURE_ALGORITHM, wholeBitString(signature));
64979
65474
  }
64980
65475
  function subjectKeyIdentifierExtension(publicKey) {
@@ -64984,7 +65479,7 @@ function basicConstraintsExtension(isCa) {
64984
65479
  return extension(OID_BASIC_CONSTRAINTS, true, sequence(...isCa ? [boolean4(true)] : []));
64985
65480
  }
64986
65481
  function rsaKeyPair() {
64987
- return (0, import_node_crypto9.generateKeyPairSync)("rsa", { modulusLength: RSA_MODULUS_BITS });
65482
+ return (0, import_node_crypto11.generateKeyPairSync)("rsa", { modulusLength: RSA_MODULUS_BITS });
64988
65483
  }
64989
65484
  function keyPem(privateKey) {
64990
65485
  return privateKey.export({ type: "pkcs8", format: "pem" });
@@ -65008,8 +65503,8 @@ function issueLeafCertificate(ca, names, notBefore, notAfter) {
65008
65503
  if (commonName === void 0) {
65009
65504
  throw new Error("a leaf certificate needs at least one name");
65010
65505
  }
65011
- const caCertificate = new import_node_crypto9.X509Certificate(ca.certPem);
65012
- const caPrivateKey = (0, import_node_crypto9.createPrivateKey)(ca.keyPem);
65506
+ const caCertificate = new import_node_crypto11.X509Certificate(ca.certPem);
65507
+ const caPrivateKey = (0, import_node_crypto11.createPrivateKey)(ca.keyPem);
65013
65508
  const { publicKey, privateKey } = rsaKeyPair();
65014
65509
  const generalNames = names.map((name) => import_node_net.default.isIP(name) === 0 ? tlv(TAG_GENERAL_NAME_DNS, Buffer.from(name, "ascii")) : tlv(TAG_GENERAL_NAME_IP, ipBytes(name)));
65015
65510
  const der = assemble({
@@ -65106,22 +65601,22 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
65106
65601
  };
65107
65602
  writeFrontDoorState(fs19, paths.frontdoorStateFile, { supervisorPid: ports.ownPid, ...sticky() });
65108
65603
  const previousSticky = lastPort;
65109
- let http6;
65604
+ let http7;
65110
65605
  try {
65111
- http6 = await ports.startProviderListener(lastPort);
65606
+ http7 = await ports.startProviderListener(lastPort);
65112
65607
  } catch (error62) {
65113
65608
  return fail(`could not start the provider listener: ${error62 instanceof Error ? error62.message : String(error62)}`);
65114
65609
  }
65115
- if (previousSticky !== void 0 && http6.port !== previousSticky) {
65116
- ports.log(`agent-shim frontdoor supervisor: sticky port ${String(previousSticky)} was occupied; moving to ${String(http6.port)} (sessions launched against the old port are stale until they relaunch)`);
65610
+ if (previousSticky !== void 0 && http7.port !== previousSticky) {
65611
+ ports.log(`agent-shim frontdoor supervisor: sticky port ${String(previousSticky)} was occupied; moving to ${String(http7.port)} (sessions launched against the old port are stale until they relaunch)`);
65117
65612
  }
65118
- lastPort = http6.port;
65613
+ lastPort = http7.port;
65119
65614
  const previousStickyConnect = lastConnectPort;
65120
65615
  let connect3;
65121
65616
  try {
65122
65617
  connect3 = await ports.startConnectListener(lastConnectPort);
65123
65618
  } catch (error62) {
65124
- await http6.close();
65619
+ await http7.close();
65125
65620
  return fail(`could not start the CONNECT surface: ${error62 instanceof Error ? error62.message : String(error62)}`);
65126
65621
  }
65127
65622
  if (previousStickyConnect !== void 0 && connect3.port !== previousStickyConnect) {
@@ -65134,16 +65629,16 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
65134
65629
  direct = await ports.startDirectListener(lastDirectPort);
65135
65630
  } catch (error62) {
65136
65631
  await connect3.close();
65137
- await http6.close();
65632
+ await http7.close();
65138
65633
  return fail(`could not start the direct listener: ${error62 instanceof Error ? error62.message : String(error62)}`);
65139
65634
  }
65140
65635
  if (previousStickyDirect !== void 0 && direct.port !== previousStickyDirect) {
65141
65636
  ports.log(`agent-shim frontdoor supervisor: sticky direct port ${String(previousStickyDirect)} was occupied; moving to ${String(direct.port)} (headroom must be restarted to admit the new address)`);
65142
65637
  }
65143
65638
  lastDirectPort = direct.port;
65144
- writeFrontDoorState(fs19, paths.frontdoorStateFile, { protocol: FRONT_DOOR_PROTOCOL, supervisorPid: ports.ownPid, port: http6.port, connectPort: connect3.port, directPort: direct.port, ...sticky() });
65639
+ writeFrontDoorState(fs19, paths.frontdoorStateFile, { protocol: FRONT_DOOR_PROTOCOL, supervisorPid: ports.ownPid, port: http7.port, connectPort: connect3.port, directPort: direct.port, ...sticky() });
65145
65640
  fs19.removeRecursive(paths.frontdoorLockFile);
65146
- ports.log(`agent-shim frontdoor supervisor ${String(ports.ownPid)}: front door on 127.0.0.1:${String(http6.port)}, CONNECT surface on 127.0.0.1:${String(connect3.port)}`);
65641
+ ports.log(`agent-shim frontdoor supervisor ${String(ports.ownPid)}: front door on 127.0.0.1:${String(http7.port)}, CONNECT surface on 127.0.0.1:${String(connect3.port)}`);
65147
65642
  let idleSince;
65148
65643
  let ticks = 0;
65149
65644
  for (; ; ) {
@@ -65151,14 +65646,17 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
65151
65646
  return FRONTDOOR_SUPERVISOR_STILL_RUNNING;
65152
65647
  }
65153
65648
  ticks += 1;
65154
- pruneDeadFrontDoorSessions(fs19, paths.frontdoorSessionsDir, ports.isRunning);
65155
- if (listFrontDoorSessions(fs19, paths.frontdoorSessionsDir).length === 0) {
65649
+ const listed = listFrontDoorSessions(fs19, paths.frontdoorSessionsDir);
65650
+ const pruned = pruneDeadFrontDoorSessions(fs19, paths.frontdoorSessionsDir, ports.isRunning);
65651
+ ports.observeLaunchRegistry(listed, pruned);
65652
+ const prunedPids = new Set(pruned);
65653
+ if (listed.filter((session) => !prunedPids.has(session.pid)).length === 0) {
65156
65654
  idleSince ??= ports.now();
65157
65655
  if (ports.now() - idleSince >= idleShutdownMinutes * MS_PER_MINUTE2) {
65158
65656
  ports.log(`agent-shim frontdoor supervisor: no sessions for ${String(idleShutdownMinutes)} minute(s); closing the front door and exiting`);
65159
65657
  await direct.close();
65160
65658
  await connect3.close();
65161
- await http6.close();
65659
+ await http7.close();
65162
65660
  ports.closeRcStreamHub();
65163
65661
  writeFrontDoorState(fs19, paths.frontdoorStateFile, sticky());
65164
65662
  return 0;
@@ -65274,8 +65772,8 @@ function ensureCa(store, generate) {
65274
65772
  }
65275
65773
  function caParses(ca) {
65276
65774
  try {
65277
- new import_node_crypto10.X509Certificate(ca.certPem);
65278
- (0, import_node_crypto10.createPrivateKey)(ca.keyPem);
65775
+ new import_node_crypto12.X509Certificate(ca.certPem);
65776
+ (0, import_node_crypto12.createPrivateKey)(ca.keyPem);
65279
65777
  return true;
65280
65778
  } catch {
65281
65779
  return false;
@@ -65563,13 +66061,13 @@ function realConnectCertStore(paths) {
65563
66061
 
65564
66062
  // src/frontdoor/connectEffects.ts
65565
66063
  var import_node_dns = __toESM(require("node:dns"), 1);
65566
- var http2 = __toESM(require("node:http"), 1);
66064
+ var http3 = __toESM(require("node:http"), 1);
65567
66065
  var https3 = __toESM(require("node:https"), 1);
65568
66066
  var net2 = __toESM(require("node:net"), 1);
65569
66067
  var tls = __toESM(require("node:tls"), 1);
65570
66068
 
65571
66069
  // src/frontdoor/server.ts
65572
- var import_node_http = __toESM(require("node:http"), 1);
66070
+ var import_node_http2 = __toESM(require("node:http"), 1);
65573
66071
  var import_node_https = __toESM(require("node:https"), 1);
65574
66072
 
65575
66073
  // src/frontdoor/rcControl.ts
@@ -66646,7 +67144,7 @@ function createFrontDoorServer(pipeline, log, tls2, control, api) {
66646
67144
  });
66647
67145
  };
66648
67146
  if (tls2 === void 0) {
66649
- return import_node_http.default.createServer(handler);
67147
+ return import_node_http2.default.createServer(handler);
66650
67148
  }
66651
67149
  const server = import_node_https.default.createServer({ key: tls2.keyPem, cert: tls2.certPem }, handler);
66652
67150
  server.on("tlsClientError", (_error, socket) => {
@@ -66661,7 +67159,7 @@ async function frontDoorHealthy(port, ca) {
66661
67159
  resolve(response.statusCode === HTTP_STATUS.ok);
66662
67160
  };
66663
67161
  const options = { host: "127.0.0.1", port, path: "/healthz", agent: false, timeout: HEALTH_TIMEOUT_MS };
66664
- const request7 = ca === void 0 ? import_node_http.default.get(options, onResponse) : import_node_https.default.get({ ...options, ca: [ca] }, onResponse);
67162
+ const request7 = ca === void 0 ? import_node_http2.default.get(options, onResponse) : import_node_https.default.get({ ...options, ca: [ca] }, onResponse);
66665
67163
  request7.on("timeout", () => {
66666
67164
  request7.destroy();
66667
67165
  });
@@ -66816,7 +67314,7 @@ async function reservedDial(target, ports) {
66816
67314
  function requestServername(options, fallback) {
66817
67315
  return "servername" in options && typeof options.servername === "string" && options.servername !== "" ? options.servername : fallback;
66818
67316
  }
66819
- var ExemptHttpAgent = class extends http2.Agent {
67317
+ var ExemptHttpAgent = class extends http3.Agent {
66820
67318
  constructor(ports = DOOR_SOURCE_PORTS) {
66821
67319
  super();
66822
67320
  this.ports = ports;
@@ -67087,7 +67585,7 @@ function realConnectEffects(ports = DOOR_SOURCE_PORTS) {
67087
67585
  };
67088
67586
  },
67089
67587
  createHttpSession: (handler, onUpgrade) => {
67090
- const server = http2.createServer(handler);
67588
+ const server = http3.createServer(handler);
67091
67589
  server.on("clientError", (_error, socket) => {
67092
67590
  socket.destroy();
67093
67591
  });
@@ -67157,7 +67655,7 @@ function realConnectEffects(ports = DOOR_SOURCE_PORTS) {
67157
67655
  }
67158
67656
  upstreamResponse.pipe(response);
67159
67657
  };
67160
- const upstream = target.tls ? https3.request(options, onUpstreamResponse) : http2.request(options, onUpstreamResponse);
67658
+ const upstream = target.tls ? https3.request(options, onUpstreamResponse) : http3.request(options, onUpstreamResponse);
67161
67659
  upstream.on("error", (error62) => {
67162
67660
  if (response.headersSent) {
67163
67661
  response.destroy(error62);
@@ -67282,7 +67780,21 @@ function cacheClause(cacheEnv, owner, block2) {
67282
67780
  const state = describeCachedCredential({ port: cacheEnv.port, platform: cacheEnv.platform, owner, cache: block2.cache, nowMs: cacheEnv.now() });
67283
67781
  return { text: `; ${formatCachedCredentialState(state)}`, unreadable: state.status === "unreadable" };
67284
67782
  }
67285
- function pushProvider(push, entry, cacheEnv) {
67783
+ function signInProblem(signIn) {
67784
+ if (signIn.raw === void 0) {
67785
+ return "nobody is signed in: run `agent-shim codex login`.";
67786
+ }
67787
+ try {
67788
+ const grant = parseSiwcFile(signIn.raw, signIn.path).grant;
67789
+ if (grant === void 0) {
67790
+ return "nobody is signed in: run `agent-shim codex login`.";
67791
+ }
67792
+ return grant.scopes.includes(SIWC_PLAN_SCOPE) ? void 0 : "the login was not given permission to use your ChatGPT plan: run `agent-shim codex login` again and allow it.";
67793
+ } catch (error62) {
67794
+ return error62 instanceof Error ? error62.message : String(error62);
67795
+ }
67796
+ }
67797
+ function pushProvider(push, entry, cacheEnv, signIn) {
67286
67798
  if (entry.raw === void 0) {
67287
67799
  push("provider", "fail", `${entry.path} is missing.`, entry.name);
67288
67800
  return;
@@ -67309,6 +67821,12 @@ function pushProvider(push, entry, cacheEnv) {
67309
67821
  if (validated2.data.env?.ENABLE_TOOL_SEARCH === void 0) {
67310
67822
  push("provider", "warn", `${entry.name} does not set ENABLE_TOOL_SEARCH: a session on this provider loads MCP tools upfront rather than deferring them, because its base URL is the front door, which Claude Code treats as a non-first-party host. Set ENABLE_TOOL_SEARCH in the provider's env block to restore deferred loading, when the upstream backend supports the tool_reference payloads it sends.`, entry.name);
67311
67823
  }
67824
+ if (signIn !== void 0 && isCodexProvider(validated2.data) && validated2.data.codex?.login === "chatgpt-sign-in") {
67825
+ const problem = signInProblem(signIn);
67826
+ if (problem !== void 0) {
67827
+ push("provider", "warn", `${entry.name} uses the Sign in with ChatGPT login, but ${problem}`, entry.name);
67828
+ }
67829
+ }
67312
67830
  }
67313
67831
  function pushLegacyName(push, params) {
67314
67832
  const legacyVariables = Object.keys(params.env).filter((name) => name.startsWith(LEGACY_ENV_PREFIX) && params.env[name] !== void 0).sort();
@@ -67433,7 +67951,7 @@ function runDoctor(params) {
67433
67951
  return poolName === void 0 ? validIdentityNames.has(selector) : poolName in pools;
67434
67952
  };
67435
67953
  for (const entry of params.providers) {
67436
- pushProvider(push, entry, params.credentialCache);
67954
+ pushProvider(push, entry, params.credentialCache, params.chatgptSignIn);
67437
67955
  }
67438
67956
  if (params.platform !== "darwin" || params.run === void 0) {
67439
67957
  push("keychain", "pass", "Skipped (not macOS).");
@@ -67675,6 +68193,7 @@ function collectDoctorReport(params) {
67675
68193
  const report = runDoctor({
67676
68194
  env: params.env,
67677
68195
  credentialCache: realCredentialCacheEnv(paths),
68196
+ chatgptSignIn: { path: paths.chatgptSignInFile, raw: realFsPort.readFileUtf8(paths.chatgptSignInFile) },
67678
68197
  installedClaudeVersions: realInstalledClaudeVersions(),
67679
68198
  identities,
67680
68199
  configProfiles,
@@ -68870,6 +69389,56 @@ var DoctorRunOutputSchema = external_exports.strictObject({
68870
69389
  findings: external_exports.readonly(external_exports.array(DoctorFindingSchema))
68871
69390
  });
68872
69391
 
69392
+ // node_modules/.pnpm/@orpc+contract@1.15.4/node_modules/@orpc/contract/dist/shared/contract.D_dZrO__.mjs
69393
+ var ValidationError = class extends Error {
69394
+ issues;
69395
+ data;
69396
+ constructor(options) {
69397
+ super(options.message, options);
69398
+ this.issues = options.issues;
69399
+ this.data = options.data;
69400
+ }
69401
+ };
69402
+ function mergeErrorMap(errorMap1, errorMap2) {
69403
+ return { ...errorMap1, ...errorMap2 };
69404
+ }
69405
+ async function validateORPCError(map3, error62) {
69406
+ const { code, status, message, data, cause, defined } = error62;
69407
+ const config2 = map3?.[error62.code];
69408
+ if (!config2 || fallbackORPCErrorStatus(error62.code, config2.status) !== error62.status) {
69409
+ return defined ? new ORPCError(code, { defined: false, status, message, data, cause }) : error62;
69410
+ }
69411
+ if (!config2.data) {
69412
+ return defined ? error62 : new ORPCError(code, { defined: true, status, message, data, cause });
69413
+ }
69414
+ const validated2 = await config2.data["~standard"].validate(error62.data);
69415
+ if (validated2.issues) {
69416
+ return defined ? new ORPCError(code, { defined: false, status, message, data, cause }) : error62;
69417
+ }
69418
+ return new ORPCError(code, { defined: true, status, message, data: validated2.value, cause });
69419
+ }
69420
+ var ContractProcedure = class {
69421
+ /**
69422
+ * This property holds the defined options for the contract procedure.
69423
+ */
69424
+ "~orpc";
69425
+ constructor(def) {
69426
+ if (def.route?.successStatus && isORPCErrorStatus(def.route.successStatus)) {
69427
+ throw new Error("[ContractProcedure] Invalid successStatus.");
69428
+ }
69429
+ if (Object.values(def.errorMap).some((val) => val && val.status && !isORPCErrorStatus(val.status))) {
69430
+ throw new Error("[ContractProcedure] Invalid error status code.");
69431
+ }
69432
+ this["~orpc"] = def;
69433
+ }
69434
+ };
69435
+ function isContractProcedure(item) {
69436
+ if (item instanceof ContractProcedure) {
69437
+ return true;
69438
+ }
69439
+ return (typeof item === "object" || typeof item === "function") && item !== null && "~orpc" in item && typeof item["~orpc"] === "object" && item["~orpc"] !== null && "errorMap" in item["~orpc"] && "route" in item["~orpc"] && "meta" in item["~orpc"];
69440
+ }
69441
+
68873
69442
  // node_modules/.pnpm/@orpc+standard-server-fetch@1.15.4/node_modules/@orpc/standard-server-fetch/dist/index.mjs
68874
69443
  function toEventIterator(stream, options = {}) {
68875
69444
  const eventStream = stream?.pipeThrough(new TextDecoderStream()).pipeThrough(new EventDecoderStream());
@@ -69581,93 +70150,6 @@ var StandardRPCLink = class extends StandardLink {
69581
70150
  }
69582
70151
  };
69583
70152
 
69584
- // node_modules/.pnpm/@orpc+client@1.15.4/node_modules/@orpc/client/dist/adapters/fetch/index.mjs
69585
- var CompositeLinkFetchPlugin = class extends CompositeStandardLinkPlugin {
69586
- initRuntimeAdapter(options) {
69587
- for (const plugin of this.plugins) {
69588
- plugin.initRuntimeAdapter?.(options);
69589
- }
69590
- }
69591
- };
69592
- var LinkFetchClient = class {
69593
- fetch;
69594
- toFetchRequestOptions;
69595
- adapterInterceptors;
69596
- constructor(options) {
69597
- const plugin = new CompositeLinkFetchPlugin(options.plugins);
69598
- plugin.initRuntimeAdapter(options);
69599
- this.fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
69600
- this.toFetchRequestOptions = options;
69601
- this.adapterInterceptors = toArray(options.adapterInterceptors);
69602
- }
69603
- async call(standardRequest, options, path44, input2) {
69604
- const request7 = toFetchRequest(standardRequest, this.toFetchRequestOptions);
69605
- const fetchResponse = await intercept(
69606
- this.adapterInterceptors,
69607
- { ...options, request: request7, path: path44, input: input2, init: { redirect: "manual" } },
69608
- ({ request: request22, path: path210, input: input22, init, ...options2 }) => this.fetch(request22, init, options2, path210, input22)
69609
- );
69610
- const lazyResponse = toStandardLazyResponse(fetchResponse, { signal: request7.signal });
69611
- return lazyResponse;
69612
- }
69613
- };
69614
- var RPCLink = class extends StandardRPCLink {
69615
- constructor(options) {
69616
- const linkClient = new LinkFetchClient(options);
69617
- super(linkClient, options);
69618
- }
69619
- };
69620
-
69621
- // node_modules/.pnpm/@orpc+contract@1.15.4/node_modules/@orpc/contract/dist/shared/contract.D_dZrO__.mjs
69622
- var ValidationError = class extends Error {
69623
- issues;
69624
- data;
69625
- constructor(options) {
69626
- super(options.message, options);
69627
- this.issues = options.issues;
69628
- this.data = options.data;
69629
- }
69630
- };
69631
- function mergeErrorMap(errorMap1, errorMap2) {
69632
- return { ...errorMap1, ...errorMap2 };
69633
- }
69634
- async function validateORPCError(map3, error62) {
69635
- const { code, status, message, data, cause, defined } = error62;
69636
- const config2 = map3?.[error62.code];
69637
- if (!config2 || fallbackORPCErrorStatus(error62.code, config2.status) !== error62.status) {
69638
- return defined ? new ORPCError(code, { defined: false, status, message, data, cause }) : error62;
69639
- }
69640
- if (!config2.data) {
69641
- return defined ? error62 : new ORPCError(code, { defined: true, status, message, data, cause });
69642
- }
69643
- const validated2 = await config2.data["~standard"].validate(error62.data);
69644
- if (validated2.issues) {
69645
- return defined ? new ORPCError(code, { defined: false, status, message, data, cause }) : error62;
69646
- }
69647
- return new ORPCError(code, { defined: true, status, message, data: validated2.value, cause });
69648
- }
69649
- var ContractProcedure = class {
69650
- /**
69651
- * This property holds the defined options for the contract procedure.
69652
- */
69653
- "~orpc";
69654
- constructor(def) {
69655
- if (def.route?.successStatus && isORPCErrorStatus(def.route.successStatus)) {
69656
- throw new Error("[ContractProcedure] Invalid successStatus.");
69657
- }
69658
- if (Object.values(def.errorMap).some((val) => val && val.status && !isORPCErrorStatus(val.status))) {
69659
- throw new Error("[ContractProcedure] Invalid error status code.");
69660
- }
69661
- this["~orpc"] = def;
69662
- }
69663
- };
69664
- function isContractProcedure(item) {
69665
- if (item instanceof ContractProcedure) {
69666
- return true;
69667
- }
69668
- return (typeof item === "object" || typeof item === "function") && item !== null && "~orpc" in item && typeof item["~orpc"] === "object" && item["~orpc"] !== null && "errorMap" in item["~orpc"] && "route" in item["~orpc"] && "meta" in item["~orpc"];
69669
- }
69670
-
69671
70153
  // node_modules/.pnpm/@orpc+contract@1.15.4/node_modules/@orpc/contract/dist/index.mjs
69672
70154
  function mergeMeta(meta1, meta22) {
69673
70155
  return { ...meta1, ...meta22 };
@@ -70612,6 +71094,43 @@ var os7 = new Builder({
70612
71094
  dedupeLeadingMiddlewares: true
70613
71095
  });
70614
71096
 
71097
+ // node_modules/.pnpm/@orpc+client@1.15.4/node_modules/@orpc/client/dist/adapters/fetch/index.mjs
71098
+ var CompositeLinkFetchPlugin = class extends CompositeStandardLinkPlugin {
71099
+ initRuntimeAdapter(options) {
71100
+ for (const plugin of this.plugins) {
71101
+ plugin.initRuntimeAdapter?.(options);
71102
+ }
71103
+ }
71104
+ };
71105
+ var LinkFetchClient = class {
71106
+ fetch;
71107
+ toFetchRequestOptions;
71108
+ adapterInterceptors;
71109
+ constructor(options) {
71110
+ const plugin = new CompositeLinkFetchPlugin(options.plugins);
71111
+ plugin.initRuntimeAdapter(options);
71112
+ this.fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
71113
+ this.toFetchRequestOptions = options;
71114
+ this.adapterInterceptors = toArray(options.adapterInterceptors);
71115
+ }
71116
+ async call(standardRequest, options, path44, input2) {
71117
+ const request7 = toFetchRequest(standardRequest, this.toFetchRequestOptions);
71118
+ const fetchResponse = await intercept(
71119
+ this.adapterInterceptors,
71120
+ { ...options, request: request7, path: path44, input: input2, init: { redirect: "manual" } },
71121
+ ({ request: request22, path: path210, input: input22, init, ...options2 }) => this.fetch(request22, init, options2, path210, input22)
71122
+ );
71123
+ const lazyResponse = toStandardLazyResponse(fetchResponse, { signal: request7.signal });
71124
+ return lazyResponse;
71125
+ }
71126
+ };
71127
+ var RPCLink = class extends StandardRPCLink {
71128
+ constructor(options) {
71129
+ const linkClient = new LinkFetchClient(options);
71130
+ super(linkClient, options);
71131
+ }
71132
+ };
71133
+
70615
71134
  // node_modules/.pnpm/@orpc+standard-server-node@1.15.4/node_modules/@orpc/standard-server-node/dist/index.mjs
70616
71135
  var import_node_buffer = require("node:buffer");
70617
71136
  var import_node_stream = require("node:stream");
@@ -71335,6 +71854,62 @@ function frontDoorRcApiClient(port, ca, token) {
71335
71854
  return createORPCClient(frontDoorApiLink(port, ca, token));
71336
71855
  }
71337
71856
 
71857
+ // src/frontdoor/eventSchemas.ts
71858
+ var DOOR_EVENT_SOURCE_RC = "rc";
71859
+ var LAUNCH_EVENT_SOURCE = "launch";
71860
+ var DoorEventSchema = external_exports.strictObject({
71861
+ source: external_exports.string().min(1),
71862
+ /** A positive integer because the backbone's counters start at one and only ever increase. */
71863
+ sequence: external_exports.number().int().positive(),
71864
+ payload: external_exports.unknown()
71865
+ });
71866
+ var DoorEventSourceQuerySchema = external_exports.strictObject({
71867
+ sources: external_exports.array(external_exports.string().min(1)).min(1).optional()
71868
+ });
71869
+ var LaunchLifecycleEventSchema = external_exports.strictObject({
71870
+ kind: external_exports.enum(["registered", "pruned", "ended"]),
71871
+ pid: external_exports.number().int().positive(),
71872
+ startedAt: external_exports.number(),
71873
+ observedAt: external_exports.number()
71874
+ });
71875
+
71876
+ // src/frontdoor/eventsApi.ts
71877
+ function createEventsApiRouter(deps) {
71878
+ const authed = doorApiAuth(deps.expectedToken);
71879
+ return {
71880
+ events: {
71881
+ subscribe: authed.input(DoorEventSourceQuerySchema).output(eventIterator(DoorEventSchema)).handler(async function* ({ input: input2, signal }) {
71882
+ if (input2.sources !== void 0) {
71883
+ const live = deps.events.sources();
71884
+ const unknown2 = input2.sources.filter((source) => !live.includes(source));
71885
+ if (unknown2.length > 0) {
71886
+ throw new ORPCError("NOT_FOUND", { message: `the door publishes no event source named ${unknown2.map((source) => `"${source}"`).join(", ")}; the live sources are ${live.map((source) => `"${source}"`).join(", ")}` });
71887
+ }
71888
+ }
71889
+ const stream = deps.events.stream(input2.sources);
71890
+ const ended = new Promise((resolve) => {
71891
+ signal?.addEventListener("abort", () => {
71892
+ resolve();
71893
+ }, { once: true });
71894
+ });
71895
+ try {
71896
+ for (; ; ) {
71897
+ signal?.throwIfAborted();
71898
+ const next = await Promise.race([stream.next(), ended.then(() => ({ value: void 0, done: true }))]);
71899
+ if (next.done === true) {
71900
+ signal?.throwIfAborted();
71901
+ return;
71902
+ }
71903
+ yield withEventMeta(next.value, { id: String(next.value.sequence) });
71904
+ }
71905
+ } finally {
71906
+ stream.close();
71907
+ }
71908
+ })
71909
+ }
71910
+ };
71911
+ }
71912
+
71338
71913
  // src/frontdoor/controlApi.ts
71339
71914
  function createControlApiRouter(deps) {
71340
71915
  const authed = doorApiAuth(deps.expectedToken);
@@ -71373,14 +71948,156 @@ function createControlApiRouter(deps) {
71373
71948
  };
71374
71949
  }
71375
71950
  function createDoorApiRouter(deps) {
71376
- return { ...createRcApiRouter(deps), ...createControlApiRouter(deps) };
71951
+ return { ...createRcApiRouter(deps), ...createControlApiRouter(deps), ...createEventsApiRouter(deps) };
71377
71952
  }
71378
71953
  function createDoorApiNodeHandler(deps) {
71379
71954
  return doorApiNodeHandlerOf(createDoorApiRouter(deps));
71380
71955
  }
71381
71956
 
71957
+ // src/frontdoor/eventHub.ts
71958
+ var DOOR_EVENT_BUFFER_EVENTS = 100;
71959
+ function createDoorEventHub() {
71960
+ const sequences = /* @__PURE__ */ new Map();
71961
+ const registered = /* @__PURE__ */ new Set();
71962
+ const every = /* @__PURE__ */ new Set();
71963
+ const bySource = /* @__PURE__ */ new Map();
71964
+ const deliver = (event) => {
71965
+ for (const listener of every) {
71966
+ listener(event);
71967
+ }
71968
+ for (const listener of bySource.get(event.source) ?? []) {
71969
+ listener(event);
71970
+ }
71971
+ };
71972
+ const publish = (source, payload) => {
71973
+ const sequence2 = (sequences.get(source) ?? 0) + 1;
71974
+ sequences.set(source, sequence2);
71975
+ deliver({ source, sequence: sequence2, payload });
71976
+ };
71977
+ const addListener = (sources, listener) => {
71978
+ if (sources === void 0) {
71979
+ every.add(listener);
71980
+ return () => {
71981
+ every.delete(listener);
71982
+ };
71983
+ }
71984
+ const listenersFor = (source) => {
71985
+ const listeners = bySource.get(source) ?? /* @__PURE__ */ new Set();
71986
+ bySource.set(source, listeners);
71987
+ return listeners;
71988
+ };
71989
+ const detachers = sources.map((source) => {
71990
+ const listeners = listenersFor(source);
71991
+ listeners.add(listener);
71992
+ return () => {
71993
+ listeners.delete(listener);
71994
+ if (listeners.size === 0) {
71995
+ bySource.delete(source);
71996
+ }
71997
+ };
71998
+ });
71999
+ return () => {
72000
+ for (const detach of detachers) {
72001
+ detach();
72002
+ }
72003
+ };
72004
+ };
72005
+ return {
72006
+ publisher: (source) => {
72007
+ registered.add(source);
72008
+ return {
72009
+ source,
72010
+ publish: (payload) => {
72011
+ publish(source, payload);
72012
+ }
72013
+ };
72014
+ },
72015
+ subscribe: addListener,
72016
+ stream: (sources) => {
72017
+ const queue = [];
72018
+ let wake;
72019
+ let closed = false;
72020
+ const detach = addListener(sources, (event) => {
72021
+ if (queue.length >= DOOR_EVENT_BUFFER_EVENTS) {
72022
+ queue.shift();
72023
+ }
72024
+ queue.push(event);
72025
+ wake?.();
72026
+ });
72027
+ return {
72028
+ next: async () => {
72029
+ for (; ; ) {
72030
+ const event = queue.shift();
72031
+ if (event !== void 0) {
72032
+ return { value: event, done: false };
72033
+ }
72034
+ if (closed) {
72035
+ return { value: void 0, done: true };
72036
+ }
72037
+ await new Promise((resolve) => {
72038
+ wake = resolve;
72039
+ });
72040
+ }
72041
+ },
72042
+ close: () => {
72043
+ closed = true;
72044
+ detach();
72045
+ wake?.();
72046
+ }
72047
+ };
72048
+ },
72049
+ sources: () => [...registered].sort()
72050
+ };
72051
+ }
72052
+ function rcFanoutOnDoorHub(fanout, hub) {
72053
+ const publisher = hub.publisher(DOOR_EVENT_SOURCE_RC);
72054
+ return {
72055
+ publish: (event) => {
72056
+ fanout.publish(event);
72057
+ publisher.publish(event);
72058
+ },
72059
+ subscribe: (session, listener) => fanout.subscribe(session, listener)
72060
+ };
72061
+ }
72062
+
72063
+ // src/frontdoor/launchEvents.ts
72064
+ function createLaunchEventPublisher(hub, now) {
72065
+ const publisher = hub.publisher(LAUNCH_EVENT_SOURCE);
72066
+ let known;
72067
+ return {
72068
+ observe: (sessions, pruned) => {
72069
+ const previous = known;
72070
+ const listed = new Map(sessions.map((session) => [session.pid, session]));
72071
+ const prunedSet = new Set(pruned);
72072
+ known = new Map([...listed].filter(([pid]) => !prunedSet.has(pid)));
72073
+ if (previous === void 0) {
72074
+ return;
72075
+ }
72076
+ const emit = (kind, pid, startedAt) => {
72077
+ publisher.publish({ kind, pid, startedAt, observedAt: now() });
72078
+ };
72079
+ for (const pid of pruned) {
72080
+ const session = listed.get(pid);
72081
+ if (session !== void 0) {
72082
+ emit("pruned", pid, session.startedAt);
72083
+ }
72084
+ }
72085
+ for (const [pid, session] of previous) {
72086
+ if (!listed.has(pid) && !prunedSet.has(pid)) {
72087
+ emit("ended", pid, session.startedAt);
72088
+ }
72089
+ }
72090
+ for (const [pid, session] of listed) {
72091
+ if (!previous.has(pid) && !prunedSet.has(pid)) {
72092
+ emit("registered", pid, session.startedAt);
72093
+ }
72094
+ }
72095
+ }
72096
+ };
72097
+ }
72098
+
71382
72099
  // src/frontdoor/headroomHop.ts
71383
- var http3 = __toESM(require("node:http"), 1);
72100
+ var http4 = __toESM(require("node:http"), 1);
71384
72101
  var HTTP_BAD_GATEWAY2 = HTTP_STATUS.badGateway;
71385
72102
  function responseHeaders(headers) {
71386
72103
  const result = {};
@@ -71434,7 +72151,7 @@ async function answerBadGateway(response, message) {
71434
72151
  await response.write(JSON.stringify({ type: "error", error: { type: "api_error", message } }));
71435
72152
  response.end();
71436
72153
  }
71437
- var HOP_AGENT = new http3.Agent({ keepAlive: false });
72154
+ var HOP_AGENT = new http4.Agent({ keepAlive: false });
71438
72155
  async function forwardThroughHeadroom(request7, response, target) {
71439
72156
  const { socketPath, headers } = target;
71440
72157
  await new Promise((resolve) => {
@@ -71462,7 +72179,7 @@ async function forwardThroughHeadroom(request7, response, target) {
71462
72179
  };
71463
72180
  void stream();
71464
72181
  };
71465
- const hop = http3.request({ socketPath, method: request7.method, path: request7.url, headers, agent: HOP_AGENT }, onUpstreamResponse);
72182
+ const hop = http4.request({ socketPath, method: request7.method, path: request7.url, headers, agent: HOP_AGENT }, onUpstreamResponse);
71466
72183
  hop.on("error", (error62) => {
71467
72184
  const phase = response.headersSent ? "mid-stream" : "before the response started";
71468
72185
  const code = "code" in error62 && typeof error62.code === "string" ? ` ${error62.code}` : "";
@@ -71651,7 +72368,7 @@ async function serveRouted(request7, deps) {
71651
72368
  }
71652
72369
 
71653
72370
  // src/codex/translate.ts
71654
- var import_node_crypto11 = require("node:crypto");
72371
+ var import_node_crypto13 = require("node:crypto");
71655
72372
 
71656
72373
  // src/codex/anthropic.ts
71657
72374
  var TextBlockSchema = external_exports.looseObject({ type: external_exports.literal("text"), text: external_exports.string() });
@@ -71730,7 +72447,8 @@ function resolveCodexConfig(config2) {
71730
72447
  sonnet: overrides?.sonnet ?? CODEX_DEFAULT_TIER_MODELS.sonnet,
71731
72448
  haiku: overrides?.haiku ?? CODEX_DEFAULT_TIER_MODELS.haiku
71732
72449
  },
71733
- effort: config2?.effort ?? CODEX_DEFAULT_EFFORT
72450
+ effort: config2?.effort ?? CODEX_DEFAULT_EFFORT,
72451
+ login: config2?.login ?? CODEX_DEFAULT_LOGIN
71734
72452
  };
71735
72453
  }
71736
72454
  function mapModel(requested, config2) {
@@ -71763,7 +72481,7 @@ function shortToolName(name, names) {
71763
72481
  if (name.length <= CODEX_TOOL_NAME_LIMIT) {
71764
72482
  return name;
71765
72483
  }
71766
- const hash2 = (0, import_node_crypto11.createHash)("sha256").update(name).digest("hex").slice(0, TOOL_NAME_HASH_CHARS);
72484
+ const hash2 = (0, import_node_crypto13.createHash)("sha256").update(name).digest("hex").slice(0, TOOL_NAME_HASH_CHARS);
71767
72485
  const shortened = `${name.slice(0, CODEX_TOOL_NAME_LIMIT - TOOL_NAME_HASH_CHARS - 1)}_${hash2}`;
71768
72486
  names.set(shortened, name);
71769
72487
  return shortened;
@@ -72039,17 +72757,17 @@ function unifiedQuotaHeaders(upstream, rejected, quotaError) {
72039
72757
  }
72040
72758
  return headers;
72041
72759
  }
72042
- var MS_PER_SECOND5 = 1e3;
72760
+ var MS_PER_SECOND6 = 1e3;
72043
72761
  function buildUsageSnapshot(upstream, quotaError, nowMs) {
72044
72762
  const used7 = upstream.get("x-codex-primary-used-percent") ?? (quotaError === void 0 ? null : EXHAUSTED_PERCENT);
72045
72763
  const reset7 = upstream.get("x-codex-primary-reset-at") ?? (quotaError?.resets_at === void 0 ? null : String(quotaError.resets_at));
72046
- const sevenDay = used7 !== null && reset7 !== null ? { used_percentage: Number(used7), resets_at: new Date(Number(reset7) * MS_PER_SECOND5).toISOString() } : void 0;
72764
+ const sevenDay = used7 !== null && reset7 !== null ? { used_percentage: Number(used7), resets_at: new Date(Number(reset7) * MS_PER_SECOND6).toISOString() } : void 0;
72047
72765
  let fiveHour;
72048
72766
  if (Number(upstream.get("x-codex-secondary-window-minutes")) > 0) {
72049
72767
  const used5 = upstream.get("x-codex-secondary-used-percent");
72050
72768
  const reset5 = upstream.get("x-codex-secondary-reset-at");
72051
72769
  if (used5 !== null && reset5 !== null) {
72052
- fiveHour = { used_percentage: Number(used5), resets_at: new Date(Number(reset5) * MS_PER_SECOND5).toISOString() };
72770
+ fiveHour = { used_percentage: Number(used5), resets_at: new Date(Number(reset5) * MS_PER_SECOND6).toISOString() };
72053
72771
  }
72054
72772
  }
72055
72773
  if (sevenDay === void 0 && fiveHour === void 0) {
@@ -72266,12 +72984,13 @@ function createCodexRoute(ports) {
72266
72984
  }
72267
72985
  const body = parsed.data;
72268
72986
  const requestedModel = body.model ?? "";
72987
+ const upstreamPorts = ports.upstreams[config2.login];
72269
72988
  const { request: upstreamRequest, toolNames } = translateRequest(body, config2);
72270
72989
  const started = ports.now();
72271
72990
  const elapsed = () => `${String(ports.now() - started)}ms`;
72272
72991
  let upstream;
72273
72992
  try {
72274
- upstream = await callCodex(ports.upstream, upstreamRequest, sessionIdFor(body.metadata?.user_id, ports.upstream.randomId), request7.signal);
72993
+ upstream = await callCodex(upstreamPorts, upstreamRequest, sessionIdFor(body.metadata?.user_id, upstreamPorts.randomId), request7.signal);
72275
72994
  } catch (error62) {
72276
72995
  if (request7.signal.aborted) {
72277
72996
  return errorResponse(HTTP_STATUS.clientClosedRequest, "client disconnected");
@@ -72304,7 +73023,7 @@ function createCodexRoute(ports) {
72304
73023
  });
72305
73024
  }
72306
73025
  snapshot(void 0);
72307
- const relay = createRelay({ requestedModel, toolNames, fallbackId: () => `msg_${ports.upstream.randomId()}` });
73026
+ const relay = createRelay({ requestedModel, toolNames, fallbackId: () => `msg_${upstreamPorts.randomId()}` });
72308
73027
  const upstreamBody = upstream.body;
72309
73028
  const quota = { ...quotaHeaders(upstream.headers), ...unifiedQuotaHeaders(upstream.headers, false, void 0) };
72310
73029
  const logCompleted = (message, usage) => {
@@ -72469,7 +73188,7 @@ function createCodexRouteMount(ports, upstreamBase, provider) {
72469
73188
  }
72470
73189
 
72471
73190
  // src/frontdoor/passthrough.ts
72472
- var http4 = __toESM(require("node:http"), 1);
73191
+ var http5 = __toESM(require("node:http"), 1);
72473
73192
  var https4 = __toESM(require("node:https"), 1);
72474
73193
  function responseHeaders2(headers) {
72475
73194
  const result = {};
@@ -72523,7 +73242,7 @@ function createPassthroughRoute(name, target) {
72523
73242
  };
72524
73243
  void stream();
72525
73244
  };
72526
- const forward = tls2 ? https4.request(options, onUpstreamResponse) : http4.request(options, onUpstreamResponse);
73245
+ const forward = tls2 ? https4.request(options, onUpstreamResponse) : http5.request(options, onUpstreamResponse);
72527
73246
  forward.on("error", (error62) => {
72528
73247
  if (!response.headersSent) {
72529
73248
  response.start(HTTP_STATUS.badGateway, { "Content-Type": "application/json" });
@@ -72701,7 +73420,7 @@ var RC_SESSION_ID_PREFIX2 = "cse_";
72701
73420
  var RC_SELF_HOST_API_BASE_URL = "https://api.anthropic.com";
72702
73421
  var RC_SELF_HOST_OAUTH_HOST = "platform.claude.com";
72703
73422
  var HTTP_CONFLICT = 409;
72704
- var MS_PER_SECOND6 = 1e3;
73423
+ var MS_PER_SECOND7 = 1e3;
72705
73424
  var HTTP_SERVICE_UNAVAILABLE = 503;
72706
73425
  var RC_SELF_HOST_WORKER_JWT_TTL_SECONDS = 46800;
72707
73426
  var RC_SELF_HOST_OAUTH_TOKEN_TTL_SECONDS = 3600;
@@ -72776,7 +73495,7 @@ function parseJsonObject2(body) {
72776
73495
  function mintWorkerJwt(randomToken, issuedAt) {
72777
73496
  const encode3 = (value2) => Buffer.from(JSON.stringify(value2), "utf8").toString("base64url");
72778
73497
  const header = encode3({ alg: "HS256", typ: "JWT" });
72779
- const payload = encode3({ exp: Math.floor((issuedAt + RC_SELF_HOST_WORKER_JWT_TTL_SECONDS * MS_PER_SECOND6) / MS_PER_SECOND6), iat: Math.floor(issuedAt / MS_PER_SECOND6) });
73498
+ const payload = encode3({ exp: Math.floor((issuedAt + RC_SELF_HOST_WORKER_JWT_TTL_SECONDS * MS_PER_SECOND7) / MS_PER_SECOND7), iat: Math.floor(issuedAt / MS_PER_SECOND7) });
72780
73499
  return `${header}.${payload}.${Buffer.from(randomToken(), "utf8").toString("base64url")}`;
72781
73500
  }
72782
73501
  function growthBookFeatures() {
@@ -73227,7 +73946,7 @@ data: ${JSON.stringify(envelope)}
73227
73946
  }
73228
73947
  if (method === "POST" && pathname.startsWith("/api/eval/")) {
73229
73948
  await readBody3(request7, CONTROL_BODY_CAP_BYTES);
73230
- answerServerJson(response, HTTP_STATUS.ok, { features: growthBookFeatures(), dateUpdated: Math.floor(deps.now() / MS_PER_SECOND6) });
73949
+ answerServerJson(response, HTTP_STATUS.ok, { features: growthBookFeatures(), dateUpdated: Math.floor(deps.now() / MS_PER_SECOND7) });
73231
73950
  return true;
73232
73951
  }
73233
73952
  if (method === "GET" && pathname === "/api/oauth/profile") {
@@ -73715,7 +74434,7 @@ var ProviderQuotaError = class extends Error {
73715
74434
  this.name = "ProviderQuotaError";
73716
74435
  }
73717
74436
  };
73718
- var MS_PER_SECOND7 = 1e3;
74437
+ var MS_PER_SECOND8 = 1e3;
73719
74438
  var SECONDS_PER_HOUR = 3600;
73720
74439
  var HOURS_PER_DAY3 = 24;
73721
74440
  var DAYS_PER_MONTH = 30;
@@ -73727,8 +74446,8 @@ var ZAI_QUOTA_URL = `https://${ZAI_HOST}/api/monitor/usage/quota/limit`;
73727
74446
  var ZAI_UNIT_HOURS = 3;
73728
74447
  var ZAI_UNIT_MONTHS = 5;
73729
74448
  var ZAI_UNIT_MS = /* @__PURE__ */ new Map([
73730
- [ZAI_UNIT_HOURS, SECONDS_PER_HOUR * MS_PER_SECOND7],
73731
- [ZAI_UNIT_MONTHS, DAYS_PER_MONTH * HOURS_PER_DAY3 * SECONDS_PER_HOUR * MS_PER_SECOND7]
74449
+ [ZAI_UNIT_HOURS, SECONDS_PER_HOUR * MS_PER_SECOND8],
74450
+ [ZAI_UNIT_MONTHS, DAYS_PER_MONTH * HOURS_PER_DAY3 * SECONDS_PER_HOUR * MS_PER_SECOND8]
73732
74451
  ]);
73733
74452
  var ZAI_MEASURES = { TOKENS_LIMIT: "tokens", TIME_LIMIT: "tool-calls" };
73734
74453
  var ZaiLimitSchema = external_exports.object({
@@ -73761,8 +74480,8 @@ function zaiWindow(limit) {
73761
74480
  var zaiQuotaAdapter = {
73762
74481
  source: ZAI_SOURCE,
73763
74482
  matches: (baseUrl) => URL.canParse(baseUrl) && new URL(baseUrl).hostname === ZAI_HOST,
73764
- fetch: async (apiKey, http6, signal, nowMs) => {
73765
- const response = await http6(ZAI_QUOTA_URL, { authorization: apiKey, accept: "application/json" }, signal);
74483
+ fetch: async (apiKey, http7, signal, nowMs) => {
74484
+ const response = await http7(ZAI_QUOTA_URL, { authorization: apiKey, accept: "application/json" }, signal);
73766
74485
  if (response.status !== HTTP_OK) {
73767
74486
  throw new ProviderQuotaError(ZAI_SOURCE, `answered HTTP ${String(response.status)}`);
73768
74487
  }
@@ -73792,7 +74511,7 @@ var OPENROUTER_HOST = "openrouter.ai";
73792
74511
  var OPENROUTER_KEY_URL = `https://${OPENROUTER_HOST}/api/v1/key`;
73793
74512
  var OPENROUTER_CREDITS_URL = `https://${OPENROUTER_HOST}/api/v1/credits`;
73794
74513
  var HTTP_FORBIDDEN = 403;
73795
- var MS_PER_DAY2 = HOURS_PER_DAY3 * SECONDS_PER_HOUR * MS_PER_SECOND7;
74514
+ var MS_PER_DAY2 = HOURS_PER_DAY3 * SECONDS_PER_HOUR * MS_PER_SECOND8;
73796
74515
  var OpenRouterKeySchema = external_exports.object({
73797
74516
  data: external_exports.object({
73798
74517
  limit: external_exports.number().nonnegative().nullable(),
@@ -73803,8 +74522,8 @@ var OpenRouterKeySchema = external_exports.object({
73803
74522
  })
73804
74523
  });
73805
74524
  var OpenRouterCreditsSchema = external_exports.object({ data: external_exports.object({ total_credits: external_exports.number().nonnegative(), total_usage: external_exports.number().nonnegative() }) });
73806
- async function openRouterGet(url2, schema, apiKey, http6, signal) {
73807
- const response = await http6(url2, { authorization: `Bearer ${apiKey}`, accept: "application/json" }, signal);
74525
+ async function openRouterGet(url2, schema, apiKey, http7, signal) {
74526
+ const response = await http7(url2, { authorization: `Bearer ${apiKey}`, accept: "application/json" }, signal);
73808
74527
  if (response.status === HTTP_FORBIDDEN) {
73809
74528
  return "forbidden";
73810
74529
  }
@@ -73829,12 +74548,12 @@ function nextUtcMidnight(nowMs) {
73829
74548
  var openRouterQuotaAdapter = {
73830
74549
  source: OPENROUTER_SOURCE,
73831
74550
  matches: (baseUrl) => URL.canParse(baseUrl) && new URL(baseUrl).hostname === OPENROUTER_HOST,
73832
- fetch: async (apiKey, http6, signal, nowMs) => {
73833
- const key = await openRouterGet(OPENROUTER_KEY_URL, OpenRouterKeySchema, apiKey, http6, signal);
74551
+ fetch: async (apiKey, http7, signal, nowMs) => {
74552
+ const key = await openRouterGet(OPENROUTER_KEY_URL, OpenRouterKeySchema, apiKey, http7, signal);
73834
74553
  if (key === "forbidden") {
73835
74554
  throw new ProviderQuotaError(OPENROUTER_SOURCE, "/api/v1/key refused the key (HTTP 403)");
73836
74555
  }
73837
- const credits = await openRouterGet(OPENROUTER_CREDITS_URL, OpenRouterCreditsSchema, apiKey, http6, signal);
74556
+ const credits = await openRouterGet(OPENROUTER_CREDITS_URL, OpenRouterCreditsSchema, apiKey, http7, signal);
73838
74557
  const { limit, limit_remaining: limitRemaining, limit_reset: limitReset, free_model_daily_requests: free, is_free_tier: isFreeTier } = key.data;
73839
74558
  const windows = [];
73840
74559
  if (limit !== null && limit > 0) {
@@ -74010,7 +74729,7 @@ function realFrontDoorSupervisorPorts(paths) {
74010
74729
  };
74011
74730
  let directPort = 0;
74012
74731
  const rcSelfHost = rcSelfHostFromEnv(process.env);
74013
- const codexPorts = createCodexRoutePorts(log);
74732
+ const codexPorts = createCodexRoutePorts(log, paths.chatgptSignInFile);
74014
74733
  let ca;
74015
74734
  const loadCa = () => {
74016
74735
  ca ??= ensureCa(realConnectCertStore(paths), () => generateCa(/* @__PURE__ */ new Date()));
@@ -74027,14 +74746,15 @@ function realFrontDoorSupervisorPorts(paths) {
74027
74746
  };
74028
74747
  const rcSelfHostSurface = rcSelfHost ? createRcSelfHostSurface({
74029
74748
  now: () => Date.now(),
74030
- newUuid: import_node_crypto12.randomUUID,
74031
- randomToken: () => (0, import_node_crypto12.randomBytes)(MINTED_TOKEN_RANDOM_BYTES).toString("base64url"),
74749
+ newUuid: import_node_crypto14.randomUUID,
74750
+ randomToken: () => (0, import_node_crypto14.randomBytes)(MINTED_TOKEN_RANDOM_BYTES).toString("base64url"),
74032
74751
  credentialRecord: () => readRcSelfHostRecord(realFarmFs, paths.frontdoorDir),
74033
74752
  log
74034
74753
  }) : void 0;
74035
74754
  const rcCredentialStore = createRcCredentialStore(realFarmFs, paths.frontdoorRcCredentialsDir);
74036
74755
  const rcTracker = createRcSessionTracker({ now: () => Date.now(), idleMs: RC_IDLE_EXPIRY_MS, credentialStore: rcCredentialStore });
74037
- const rcFanout = createRcEventFanout();
74756
+ const doorEvents = createDoorEventHub();
74757
+ const rcFanout = rcFanoutOnDoorHub(createRcEventFanout(), doorEvents);
74038
74758
  const rcHub = createRcStreamHub({
74039
74759
  now: () => Date.now(),
74040
74760
  credentialOf: rcTracker.credentialOf,
@@ -74045,7 +74765,7 @@ function realFrontDoorSupervisorPorts(paths) {
74045
74765
  saveSequenceNum: rcCredentialStore.writeCursor,
74046
74766
  dial: rcSelfHost ? lateRcStreamDial(rcSelfHostDialTarget) : realRcStreamDial(),
74047
74767
  fanout: rcFanout,
74048
- newClientId: import_node_crypto12.randomUUID,
74768
+ newClientId: import_node_crypto14.randomUUID,
74049
74769
  backoffMs: RC_STREAM_BACKOFF_MS,
74050
74770
  sleep: async (ms) => {
74051
74771
  await new Promise((resolve) => {
@@ -74056,7 +74776,7 @@ function realFrontDoorSupervisorPorts(paths) {
74056
74776
  appendLog(paths, `frontdoor ${String(process.pid)}: ${line}`);
74057
74777
  }
74058
74778
  });
74059
- const rcControlToken = (0, import_node_crypto12.randomUUID)();
74779
+ const rcControlToken = (0, import_node_crypto14.randomUUID)();
74060
74780
  const resolveRoute = rcObservingResolver(
74061
74781
  createProviderRouteResolver({ fs: realFsPort, providersDir: paths.providersDir, codexPorts, directPort: () => directPort, ...rcSelfHostSurface === void 0 ? {} : { rcSelfHostRoute: rcSelfHostSurface.route } }),
74062
74782
  rcTracker,
@@ -74087,8 +74807,8 @@ function realFrontDoorSupervisorPorts(paths) {
74087
74807
  now: () => Date.now(),
74088
74808
  headroomSocket: () => headroomSocketTarget(realFarmFs, realHeadroomSocketTrust, paths),
74089
74809
  // The per-generation capability the direct listener demands: held only in this process's memory, so a loopback process that discovers the direct port still cannot use it.
74090
- hopSecret: (0, import_node_crypto12.randomUUID)(),
74091
- custody: createCredentialCustody(() => (0, import_node_crypto12.randomUUID)()),
74810
+ hopSecret: (0, import_node_crypto14.randomUUID)(),
74811
+ custody: createCredentialCustody(() => (0, import_node_crypto14.randomUUID)()),
74092
74812
  log
74093
74813
  });
74094
74814
  const onListenerError = (name) => (error62) => {
@@ -74109,9 +74829,9 @@ function realFrontDoorSupervisorPorts(paths) {
74109
74829
  startProviderListener: async (preferredPort) => {
74110
74830
  const authority = loadCa();
74111
74831
  const rcDial = rcSelfHost ? lateRcEventDial(rcSelfHostDialTarget) : realRcEventDial();
74112
- const rcInject = async (sessionId, text2) => await injectRcUserMessage({ credentialOf: rcTracker.credentialOf, noteSequenceNums: rcTracker.noteSequenceNums, dial: rcDial, newUuid: import_node_crypto12.randomUUID }, sessionId, text2);
74832
+ const rcInject = async (sessionId, text2) => await injectRcUserMessage({ credentialOf: rcTracker.credentialOf, noteSequenceNums: rcTracker.noteSequenceNums, dial: rcDial, newUuid: import_node_crypto14.randomUUID }, sessionId, text2);
74113
74833
  const rcAnswer = async (sessionId, requestId, decision) => await answerRcControlRequest({ credentialOf: rcTracker.credentialOf, pendingOf: (id) => rcTracker.pendingOf(id), completePending: rcTracker.completePending, noteSequenceNums: rcTracker.noteSequenceNums, dial: rcDial }, sessionId, requestId, decision);
74114
- const rcControlRequestDeps = { credentialOf: rcTracker.credentialOf, noteSequenceNums: rcTracker.noteSequenceNums, dial: rcDial, newUuid: import_node_crypto12.randomUUID };
74834
+ const rcControlRequestDeps = { credentialOf: rcTracker.credentialOf, noteSequenceNums: rcTracker.noteSequenceNums, dial: rcDial, newUuid: import_node_crypto14.randomUUID };
74115
74835
  const rcInterrupt = async (sessionId) => await interruptRcSession(rcControlRequestDeps, sessionId);
74116
74836
  const rcSetModel = async (sessionId, model) => await setRcSessionModel(rcControlRequestDeps, sessionId, model);
74117
74837
  const rcSetPermissionMode = async (sessionId, mode) => await setRcSessionPermissionMode(rcControlRequestDeps, sessionId, mode);
@@ -74144,7 +74864,8 @@ function realFrontDoorSupervisorPorts(paths) {
74144
74864
  setModel: rcSetModel,
74145
74865
  setPermissionMode: rcSetPermissionMode,
74146
74866
  fanout: rcFanout,
74147
- ...controlDeps
74867
+ ...controlDeps,
74868
+ events: doorEvents
74148
74869
  })
74149
74870
  );
74150
74871
  const handle = await listenFrontDoor(server, { ...preferredPort === void 0 ? {} : { preferredPort }, ca: authority.certPem, onError: onListenerError("provider") });
@@ -74158,7 +74879,7 @@ function realFrontDoorSupervisorPorts(paths) {
74158
74879
  },
74159
74880
  startConnectListener: async (preferredPort) => {
74160
74881
  const wantedTransparentPort = process.env.AGENT_SHIM_TRANSPARENT_SURFACE === void 0 ? void 0 : Number(process.env.AGENT_SHIM_TRANSPARENT_SURFACE);
74161
- const transparentCapability = wantedTransparentPort === void 0 || Number.isNaN(wantedTransparentPort) ? void 0 : (0, import_node_crypto12.randomUUID)();
74882
+ const transparentCapability = wantedTransparentPort === void 0 || Number.isNaN(wantedTransparentPort) ? void 0 : (0, import_node_crypto14.randomUUID)();
74162
74883
  if (transparentCapability !== void 0) {
74163
74884
  writeFrontDoorSession(realFarmFs, paths.frontdoorSessionsDir, { pid: process.pid, startedAt: Date.now(), token: transparentCapability });
74164
74885
  }
@@ -74231,6 +74952,8 @@ function realFrontDoorSupervisorPorts(paths) {
74231
74952
  return { port: handle.port, close: handle.close };
74232
74953
  },
74233
74954
  closeRcStreamHub: rcHub.close,
74955
+ // The launch lifecycle publisher over the same backbone the typed API serves: the tick's registry facts go in here, and the door's own register, prune and end moments come out as source-tagged events.
74956
+ observeLaunchRegistry: createLaunchEventPublisher(doorEvents, () => Date.now()).observe,
74234
74957
  log: (line) => {
74235
74958
  appendLog(paths, line);
74236
74959
  }
@@ -74501,8 +75224,8 @@ function registerFrontDoorCommand(program2, deps) {
74501
75224
  identitiesDir: paths.identitiesDir,
74502
75225
  frontdoorDir: paths.frontdoorDir,
74503
75226
  identity,
74504
- newUuid: import_node_crypto12.randomUUID,
74505
- randomToken: () => (0, import_node_crypto12.randomBytes)(MINTED_TOKEN_RANDOM_BYTES).toString("base64url"),
75227
+ newUuid: import_node_crypto14.randomUUID,
75228
+ randomToken: () => (0, import_node_crypto14.randomBytes)(MINTED_TOKEN_RANDOM_BYTES).toString("base64url"),
74506
75229
  force: options.force === true,
74507
75230
  now: () => Date.now()
74508
75231
  });
@@ -74816,7 +75539,7 @@ function registerCompletionCommand(program2) {
74816
75539
  }
74817
75540
 
74818
75541
  // src/credentialCommand.ts
74819
- var import_node_child_process4 = require("node:child_process");
75542
+ var import_node_child_process5 = require("node:child_process");
74820
75543
 
74821
75544
  // src/identityToken.ts
74822
75545
  var import_node_fs14 = __toESM(require("node:fs"), 1);
@@ -74868,7 +75591,7 @@ function realCommandPorts(deps) {
74868
75591
  cache: createRealCredentialCache(deps.paths.root),
74869
75592
  platform: process.platform,
74870
75593
  now: () => Date.now(),
74871
- ssh: (host, remoteCommand, input2) => (0, import_node_child_process4.spawnSync)("ssh", ["--", host, remoteCommand], { input: input2, stdio: ["pipe", "inherit", "inherit"] }).status
75594
+ ssh: (host, remoteCommand, input2) => (0, import_node_child_process5.spawnSync)("ssh", ["--", host, remoteCommand], { input: input2, stdio: ["pipe", "inherit", "inherit"] }).status
74872
75595
  };
74873
75596
  }
74874
75597
  var REMOTE_ROOT_SCRIPT = 'root="${AGENT_SHIM_HOME:-${CLAUDE_USE_HOME:-}}"; if [ -z "$root" ]; then if [ ! -d "$HOME/.agent-shim" ] && [ -d "$HOME/.claude-use" ]; then root="$HOME/.claude-use"; else root="$HOME/.agent-shim"; fi; fi';
@@ -74999,15 +75722,15 @@ function registerDoctorCommand(program2, deps) {
74999
75722
 
75000
75723
  // src/headroom/commands.ts
75001
75724
  var import_node_fs16 = __toESM(require("node:fs"), 1);
75002
- var import_node_http2 = __toESM(require("node:http"), 1);
75725
+ var import_node_http3 = __toESM(require("node:http"), 1);
75003
75726
  var import_node_path39 = __toESM(require("node:path"), 1);
75004
- var import_node_child_process5 = require("node:child_process");
75727
+ var import_node_child_process6 = require("node:child_process");
75005
75728
 
75006
75729
  // src/frontdoor/realFrontDoorPort.ts
75007
75730
  var import_node_fs15 = __toESM(require("node:fs"), 1);
75008
75731
 
75009
75732
  // src/frontdoor/ensure.ts
75010
- var import_node_crypto13 = require("node:crypto");
75733
+ var import_node_crypto15 = require("node:crypto");
75011
75734
  var FRONTDOOR_START_TIMEOUT_MS = 3e4;
75012
75735
  var FRONTDOOR_LAUNCHER_POLL_MS = 100;
75013
75736
  var FrontDoorStartError = class extends CliError {
@@ -75041,7 +75764,7 @@ function ensureFrontDoor(params) {
75041
75764
  continue;
75042
75765
  }
75043
75766
  if (verdict.ok) {
75044
- const token = (0, import_node_crypto13.randomUUID)();
75767
+ const token = (0, import_node_crypto15.randomUUID)();
75045
75768
  writeFrontDoorSession(ports.fs, paths.frontdoorSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), token });
75046
75769
  return { port: state.port, connectPort: state.connectPort, token };
75047
75770
  }
@@ -75145,7 +75868,7 @@ function probeFrontDoorSync(port, caPem) {
75145
75868
  }
75146
75869
 
75147
75870
  // src/frontdoor/trust.ts
75148
- var import_node_crypto14 = require("node:crypto");
75871
+ var import_node_crypto16 = require("node:crypto");
75149
75872
  var import_node_path38 = __toESM(require("node:path"), 1);
75150
75873
  var BUNDLE_NAME_HEX_DIGITS = 16;
75151
75874
  function resolveTrustBundle(params) {
@@ -75168,7 +75891,7 @@ function resolveTrustBundle(params) {
75168
75891
  }
75169
75892
  const combined = `${inheritedPem.trimEnd()}
75170
75893
  ${caPem}`;
75171
- const bundle = import_node_path38.default.join(bundlesDir, `${(0, import_node_crypto14.createHash)("sha256").update(combined).digest("hex").slice(0, BUNDLE_NAME_HEX_DIGITS)}.pem`);
75894
+ const bundle = import_node_path38.default.join(bundlesDir, `${(0, import_node_crypto16.createHash)("sha256").update(combined).digest("hex").slice(0, BUNDLE_NAME_HEX_DIGITS)}.pem`);
75172
75895
  if (!fs19.exists(bundle)) {
75173
75896
  fs19.mkdirp(bundlesDir);
75174
75897
  fs19.writeFileAtomic(bundle, combined);
@@ -75308,7 +76031,7 @@ function headroomProxyArgs(socketPath, settings2) {
75308
76031
  }
75309
76032
  async function readyOverSocket(socketPath) {
75310
76033
  return await new Promise((resolve) => {
75311
- const probe = import_node_http2.default.get({ socketPath, path: "/readyz", timeout: READY_FETCH_TIMEOUT_MS }, (response) => {
76034
+ const probe = import_node_http3.default.get({ socketPath, path: "/readyz", timeout: READY_FETCH_TIMEOUT_MS }, (response) => {
75312
76035
  response.resume();
75313
76036
  resolve(response.statusCode === HTTP_STATUS.ok);
75314
76037
  });
@@ -75322,7 +76045,7 @@ async function readyOverSocket(socketPath) {
75322
76045
  }
75323
76046
  var HEADROOM_TOOL_NAME = "headroom-ai";
75324
76047
  function readInstalledHeadroomCommit() {
75325
- const toolDir = (0, import_node_child_process5.spawnSync)("uv", ["tool", "dir"], { encoding: "utf8" });
76048
+ const toolDir = (0, import_node_child_process6.spawnSync)("uv", ["tool", "dir"], { encoding: "utf8" });
75326
76049
  if (toolDir.status !== 0) {
75327
76050
  return void 0;
75328
76051
  }
@@ -75362,7 +76085,7 @@ function realSupervisorPorts(paths) {
75362
76085
  }
75363
76086
  const logFd = import_node_fs16.default.openSync(paths.headroomLogPath, "a");
75364
76087
  try {
75365
- const child = (0, import_node_child_process5.spawn)("headroom", headroomProxyArgs(socketPath, settings2), {
76088
+ const child = (0, import_node_child_process6.spawn)("headroom", headroomProxyArgs(socketPath, settings2), {
75366
76089
  detached: true,
75367
76090
  stdio: ["ignore", logFd, logFd],
75368
76091
  env: headroomSpawnEnv(process.env, allowlist, settings2, trust?.path)
@@ -75402,7 +76125,7 @@ function realSupervisorPorts(paths) {
75402
76125
  },
75403
76126
  ready: readyOverSocket,
75404
76127
  install: (spec) => {
75405
- const result = (0, import_node_child_process5.spawnSync)("uv", ["tool", "install", spec], { encoding: "utf8" });
76128
+ const result = (0, import_node_child_process6.spawnSync)("uv", ["tool", "install", spec], { encoding: "utf8" });
75406
76129
  if (result.error !== void 0) {
75407
76130
  return { ok: false, error: `uv is not installed or not on PATH (${result.error.message})` };
75408
76131
  }
@@ -75415,7 +76138,7 @@ function realSupervisorPorts(paths) {
75415
76138
  return result.status === 0 ? { ok: true } : { ok: false, error: `uv tool install exited ${String(result.status)}` };
75416
76139
  },
75417
76140
  headroomVersion: () => {
75418
- const result = (0, import_node_child_process5.spawnSync)("headroom", ["--version"], { encoding: "utf8" });
76141
+ const result = (0, import_node_child_process6.spawnSync)("headroom", ["--version"], { encoding: "utf8" });
75419
76142
  return result.status === 0 ? result.stdout.trim() : void 0;
75420
76143
  },
75421
76144
  installedCommit: readInstalledHeadroomCommit,
@@ -75598,13 +76321,14 @@ function toProviderView(name, provider) {
75598
76321
  };
75599
76322
  }
75600
76323
  function codexConfigFromOptions(options) {
75601
- if (options.codexDefaultModel === void 0 && options.codexModel === void 0 && options.codexEffort === void 0) {
76324
+ if (options.codexDefaultModel === void 0 && options.codexModel === void 0 && options.codexEffort === void 0 && options.codexLogin === void 0) {
75602
76325
  return void 0;
75603
76326
  }
75604
76327
  const candidate = {
75605
76328
  ...options.codexDefaultModel === void 0 ? {} : { defaultModel: options.codexDefaultModel },
75606
76329
  ...options.codexModel === void 0 ? {} : { models: options.codexModel },
75607
- ...options.codexEffort === void 0 ? {} : { effort: options.codexEffort }
76330
+ ...options.codexEffort === void 0 ? {} : { effort: options.codexEffort },
76331
+ ...options.codexLogin === void 0 ? {} : { login: options.codexLogin }
75608
76332
  };
75609
76333
  const parsed = CodexProviderConfigSchema.safeParse(candidate);
75610
76334
  if (!parsed.success) {
@@ -75627,7 +76351,7 @@ function registerProviderCommand(program2, deps) {
75627
76351
  new Option("--credential-target <target>", "Where the token goes: bearer sets ANTHROPIC_AUTH_TOKEN (default), apiKey sets ANTHROPIC_API_KEY.").choices(
75628
76352
  PROVIDER_CREDENTIAL_TARGETS
75629
76353
  )
75630
- ).option("--env <KEY=VALUE>", "Extra environment entry for the child (repeatable).", collectStringPair).option("--codex-default-model <model>", "Codex providers: the codex model a request maps to when no tier matches.").option("--codex-model <tier=model>", "Codex providers: the codex model one tier (fable, opus, sonnet or haiku) maps to (repeatable).", collectStringPair).addOption(new Option("--codex-effort <effort>", "Codex providers: the reasoning effort when a request asks for none the backend accepts.").choices(CODEX_EFFORTS)).option("--json", "Print the result as JSON.").action((name, options) => {
76354
+ ).option("--env <KEY=VALUE>", "Extra environment entry for the child (repeatable).", collectStringPair).option("--codex-default-model <model>", "Codex providers: the codex model a request maps to when no tier matches.").option("--codex-model <tier=model>", "Codex providers: the codex model one tier (fable, opus, sonnet or haiku) maps to (repeatable).", collectStringPair).addOption(new Option("--codex-effort <effort>", "Codex providers: the reasoning effort when a request asks for none the backend accepts.").choices(CODEX_EFFORTS)).addOption(new Option("--codex-login <login>", "Codex providers: which login the upstream call uses, the Codex CLI's own login or a Sign in with ChatGPT grant from `agent-shim codex login`.").choices(CODEX_LOGINS)).option("--json", "Print the result as JSON.").action((name, options) => {
75631
76355
  const codex = codexConfigFromOptions(options);
75632
76356
  const created = addProvider(paths, name, {
75633
76357
  ...options.kind === void 0 ? {} : { kind: options.kind },
@@ -75659,14 +76383,14 @@ function registerProviderCommand(program2, deps) {
75659
76383
  new Option("--credential-target <target>", "Where the token goes: bearer sets ANTHROPIC_AUTH_TOKEN, apiKey sets ANTHROPIC_API_KEY.").choices(
75660
76384
  PROVIDER_CREDENTIAL_TARGETS
75661
76385
  )
75662
- ).option("--env <KEY=VALUE>", "Add or replace an environment entry for the child (repeatable).", collectStringPair).option("--unset-env <KEY>", "Remove an environment entry (repeatable).", collectRepeated).option("--codex-default-model <model>", "Codex providers: replace the model a request maps to when no tier matches.").option("--codex-model <tier=model>", "Codex providers: set the codex model one tier maps to (repeatable; other tiers are kept).", collectStringPair).addOption(new Option("--codex-effort <effort>", "Codex providers: replace the default reasoning effort.").choices(CODEX_EFFORTS));
76386
+ ).option("--env <KEY=VALUE>", "Add or replace an environment entry for the child (repeatable).", collectStringPair).option("--unset-env <KEY>", "Remove an environment entry (repeatable).", collectRepeated).option("--codex-default-model <model>", "Codex providers: replace the model a request maps to when no tier matches.").option("--codex-model <tier=model>", "Codex providers: set the codex model one tier maps to (repeatable; other tiers are kept).", collectStringPair).addOption(new Option("--codex-effort <effort>", "Codex providers: replace the default reasoning effort.").choices(CODEX_EFFORTS)).addOption(new Option("--codex-login <login>", "Codex providers: replace which login the upstream call uses.").choices(CODEX_LOGINS));
75663
76387
  addCredentialCacheOptions(providerSet);
75664
76388
  withExamples(
75665
76389
  providerSet.option("--json", "Print the result as JSON.").action((name, allOptions) => {
75666
76390
  const { json: json2, ...options } = allOptions;
75667
76391
  if (Object.entries(allOptions).every(([key, value2]) => key === "json" || value2 === void 0)) {
75668
76392
  throw new UsageError(
75669
- "Nothing to change: pass --display-name, --base-url, --credential, --credential-target, a --credential-cache option, --env, --unset-env, --codex-default-model, --codex-model or --codex-effort."
76393
+ "Nothing to change: pass --display-name, --base-url, --credential, --credential-target, a --credential-cache option, --env, --unset-env, --codex-default-model, --codex-model, --codex-effort or --codex-login."
75670
76394
  );
75671
76395
  }
75672
76396
  const codex = codexConfigFromOptions(options);
@@ -75727,6 +76451,7 @@ function registerProviderCommand(program2, deps) {
75727
76451
  const config2 = resolveCodexConfig(found.codex);
75728
76452
  console.log(`Codex models: ${CODEX_TIERS.map((tier) => `${tier}=${config2.models[tier]}`).join(", ")}, otherwise ${config2.defaultModel}`);
75729
76453
  console.log(`Codex effort: ${config2.effort}`);
76454
+ console.log(`Codex login: ${config2.login}`);
75730
76455
  } else {
75731
76456
  console.log(`Base URL: ${found.baseUrl}`);
75732
76457
  }
@@ -76134,10 +76859,10 @@ function buildProgram(deps) {
76134
76859
 
76135
76860
  // src/launchWiring.ts
76136
76861
  var import_node_os8 = __toESM(require("node:os"), 1);
76137
- var import_node_crypto16 = require("node:crypto");
76862
+ var import_node_crypto18 = require("node:crypto");
76138
76863
 
76139
76864
  // src/launcher.ts
76140
- var import_node_crypto15 = require("node:crypto");
76865
+ var import_node_crypto17 = require("node:crypto");
76141
76866
  var import_node_path41 = __toESM(require("node:path"), 1);
76142
76867
 
76143
76868
  // src/launcher/cliOverride.ts
@@ -76638,7 +77363,7 @@ function prepareLaunch(params) {
76638
77363
  ...identityCredential === void 0 ? {} : { identityCredential },
76639
77364
  ...frontDoor === void 0 ? {} : { frontdoor: frontDoor },
76640
77365
  ...headroom === void 0 ? {} : { headroom },
76641
- sessionId: (0, import_node_crypto15.randomUUID)()
77366
+ sessionId: (0, import_node_crypto17.randomUUID)()
76642
77367
  });
76643
77368
  const releases = [...headroom === void 0 || headroomPort === void 0 ? [] : [headroomPort.release], ...frontDoorRelease === void 0 ? [] : [frontDoorRelease]];
76644
77369
  const release = () => {
@@ -76683,7 +77408,7 @@ function buildFarmRuntime(paths, cwd) {
76683
77408
  ...cliOverride === void 0 ? {} : { cliOverride }
76684
77409
  }).input,
76685
77410
  now: () => Date.now(),
76686
- uniqueSuffix: `${String(process.pid)}.${(0, import_node_crypto16.randomUUID)()}`,
77411
+ uniqueSuffix: `${String(process.pid)}.${(0, import_node_crypto18.randomUUID)()}`,
76687
77412
  // Zombie-aware on purpose: a previous launcher that crashed out of a resync without releasing the lock may sit unreaped, still answering signal 0 as alive, and must read as a dead holder so this launch takes the lock over instead of timing out.
76688
77413
  lock: { pid: process.pid, isRunning: realIsProcessRunning, sleep: realSleepSync }
76689
77414
  },