agent-shim 8.15.0 → 8.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -46,6 +46,8 @@ __export(index_exports, {
46
46
  CredentialCacheSchema: () => CredentialCacheSchema,
47
47
  CredentialSchema: () => CredentialSchema,
48
48
  CredentialSourceSchema: () => CredentialSourceSchema,
49
+ DOOR_EVENT_BUFFER_EVENTS: () => DOOR_EVENT_BUFFER_EVENTS,
50
+ DOOR_EVENT_SOURCE_RC: () => DOOR_EVENT_SOURCE_RC,
49
51
  DirectoryRuleAlreadyExistsError: () => DirectoryRuleAlreadyExistsError,
50
52
  DirectoryRuleMissingTargetError: () => DirectoryRuleMissingTargetError,
51
53
  DirectoryRuleNotFoundError: () => DirectoryRuleNotFoundError,
@@ -53,6 +55,8 @@ __export(index_exports, {
53
55
  DirectoryRulesSchema: () => DirectoryRulesSchema,
54
56
  DoctorFindingSchema: () => DoctorFindingSchema,
55
57
  DoctorRunOutputSchema: () => DoctorRunOutputSchema,
58
+ DoorEventSchema: () => DoorEventSchema,
59
+ DoorEventSourceQuerySchema: () => DoorEventSourceQuerySchema,
56
60
  EXIT_FAILURE: () => EXIT_FAILURE,
57
61
  EXIT_USAGE: () => EXIT_USAGE,
58
62
  EffectiveWindowSchema: () => EffectiveWindowSchema,
@@ -72,6 +76,8 @@ __export(index_exports, {
72
76
  InvalidCategoryNameError: () => InvalidCategoryNameError,
73
77
  InvalidIdentityNameError: () => InvalidIdentityNameError,
74
78
  InvalidProviderNameError: () => InvalidProviderNameError,
79
+ LAUNCH_EVENT_SOURCE: () => LAUNCH_EVENT_SOURCE,
80
+ LaunchLifecycleEventSchema: () => LaunchLifecycleEventSchema,
75
81
  LaunchRefusedError: () => LaunchRefusedError,
76
82
  LegacyProviderFileError: () => LegacyProviderFileError,
77
83
  PROMPT_CACHE_TTL_MS: () => PROMPT_CACHE_TTL_MS,
@@ -135,6 +141,9 @@ __export(index_exports, {
135
141
  createControlApiRouter: () => createControlApiRouter,
136
142
  createDoorApiNodeHandler: () => createDoorApiNodeHandler,
137
143
  createDoorApiRouter: () => createDoorApiRouter,
144
+ createDoorEventHub: () => createDoorEventHub,
145
+ createEventsApiRouter: () => createEventsApiRouter,
146
+ createLaunchEventPublisher: () => createLaunchEventPublisher,
138
147
  createLeafCache: () => createLeafCache,
139
148
  createProfile: () => createProfile,
140
149
  createRcApiNodeHandler: () => createRcApiNodeHandler,
@@ -193,6 +202,7 @@ __export(index_exports, {
193
202
  providerExists: () => providerExists,
194
203
  rankPool: () => rankPool,
195
204
  rcEventWriteResultFromAnswer: () => rcEventWriteResultFromAnswer,
205
+ rcFanoutOnDoorHub: () => rcFanoutOnDoorHub,
196
206
  rcObserverAsPassthrough: () => rcObserverAsPassthrough,
197
207
  rcSelfHostFromEnv: () => rcSelfHostFromEnv,
198
208
  readActiveIdentity: () => readActiveIdentity,
@@ -507,6 +517,7 @@ var ProviderEnvSchema = import_zod.z.record(import_zod.z.string().min(1), import
507
517
  message: `env may not set ${RESERVED_PROVIDER_ENV_KEYS.join(", ")}: the credential block's target sets one and agent-shim removes the others`
508
518
  });
509
519
  var CODEX_EFFORTS = ["none", "low", "medium", "high"];
520
+ var CODEX_LOGINS = ["codex-cli", "chatgpt-sign-in"];
510
521
  var CODEX_DEFAULT_TIER_MODELS = Object.freeze({
511
522
  fable: "gpt-5.6-sol",
512
523
  opus: "gpt-5.6-sol",
@@ -524,7 +535,9 @@ var CodexProviderConfigSchema = import_zod.z.strictObject({
524
535
  haiku: import_zod.z.string().min(1).optional()
525
536
  }).optional(),
526
537
  /** The reasoning effort used when the request's own `output_config.effort` is not one the backend accepts. */
527
- effort: import_zod.z.enum(CODEX_EFFORTS).optional()
538
+ effort: import_zod.z.enum(CODEX_EFFORTS).optional(),
539
+ /** Which login the upstream call is made with; see `CODEX_LOGINS`. */
540
+ login: import_zod.z.enum(CODEX_LOGINS).optional()
528
541
  });
529
542
  var HttpProviderSchema = import_zod.z.strictObject({
530
543
  $schema: import_zod.z.string().optional(),
@@ -3013,8 +3026,11 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
3013
3026
  return FRONTDOOR_SUPERVISOR_STILL_RUNNING;
3014
3027
  }
3015
3028
  ticks += 1;
3016
- pruneDeadFrontDoorSessions(fs12, paths.frontdoorSessionsDir, ports.isRunning);
3017
- if (listFrontDoorSessions(fs12, paths.frontdoorSessionsDir).length === 0) {
3029
+ const listed = listFrontDoorSessions(fs12, paths.frontdoorSessionsDir);
3030
+ const pruned = pruneDeadFrontDoorSessions(fs12, paths.frontdoorSessionsDir, ports.isRunning);
3031
+ ports.observeLaunchRegistry(listed, pruned);
3032
+ const prunedPids = new Set(pruned);
3033
+ if (listed.filter((session) => !prunedPids.has(session.pid)).length === 0) {
3018
3034
  idleSince ??= ports.now();
3019
3035
  if (ports.now() - idleSince >= idleShutdownMinutes * MS_PER_MINUTE) {
3020
3036
  ports.log(`agent-shim frontdoor supervisor: no sessions for ${String(idleShutdownMinutes)} minute(s); closing the front door and exiting`);
@@ -5236,7 +5252,7 @@ function frontDoorRcApiClient(port, ca, token) {
5236
5252
  }
5237
5253
 
5238
5254
  // src/frontdoor/controlApi.ts
5239
- var import_client2 = require("@orpc/client");
5255
+ var import_client3 = require("@orpc/client");
5240
5256
 
5241
5257
  // src/checkReport.ts
5242
5258
  var import_node_os4 = __toESM(require("node:os"), 1);
@@ -5586,6 +5602,7 @@ function buildLayoutPaths(root) {
5586
5602
  frontdoorCaCertFile: import_node_path14.default.join(root, "frontdoor", "ca", "ca.pem"),
5587
5603
  frontdoorCaKeyFile: import_node_path14.default.join(root, "frontdoor", "ca", "ca.key"),
5588
5604
  frontdoorCaBundlesDir: import_node_path14.default.join(root, "frontdoor", "ca", "bundles"),
5605
+ chatgptSignInFile: import_node_path14.default.join(root, "codex", "chatgpt-sign-in.json"),
5589
5606
  frontdoorStateFile: import_node_path14.default.join(root, "frontdoor", "state.json"),
5590
5607
  frontdoorLockFile: import_node_path14.default.join(root, "frontdoor", "start.lock"),
5591
5608
  frontdoorControlTokenFile: import_node_path14.default.join(root, "frontdoor", "control-token"),
@@ -7021,7 +7038,7 @@ var ProviderKindMismatchError = class extends UsageError {
7021
7038
  }
7022
7039
  };
7023
7040
  function isEmptyCodexConfig(config) {
7024
- return config === void 0 || config.defaultModel === void 0 && config.effort === void 0 && (config.models === void 0 || Object.keys(config.models).length === 0);
7041
+ return config === void 0 || config.defaultModel === void 0 && config.effort === void 0 && config.login === void 0 && (config.models === void 0 || Object.keys(config.models).length === 0);
7025
7042
  }
7026
7043
  function addProvider(paths, name, input) {
7027
7044
  if (providerExists(paths, name)) {
@@ -8689,7 +8706,7 @@ var CheckReportJsonSchema = import_zod14.z.strictObject({
8689
8706
 
8690
8707
  // src/frontdoor/controlSchemas.ts
8691
8708
  var import_node_path27 = __toESM(require("node:path"), 1);
8692
- var import_zod16 = require("zod");
8709
+ var import_zod19 = require("zod");
8693
8710
 
8694
8711
  // src/claudeShim.ts
8695
8712
  var import_node_fs7 = __toESM(require("node:fs"), 1);
@@ -8893,6 +8910,85 @@ function removeIdentity(paths, name) {
8893
8910
  // src/doctorReport.ts
8894
8911
  var import_node_fs9 = __toESM(require("node:fs"), 1);
8895
8912
  var import_node_path26 = __toESM(require("node:path"), 1);
8913
+
8914
+ // src/codex/siwcStore.ts
8915
+ var import_zod18 = require("zod");
8916
+
8917
+ // src/codex/auth.ts
8918
+ var import_zod16 = require("zod");
8919
+ var AuthFileSchema = import_zod16.z.looseObject({
8920
+ tokens: import_zod16.z.looseObject({
8921
+ access_token: import_zod16.z.string().optional(),
8922
+ refresh_token: import_zod16.z.string().optional(),
8923
+ id_token: import_zod16.z.string().optional(),
8924
+ account_id: import_zod16.z.string().optional()
8925
+ }).nullish(),
8926
+ last_refresh: import_zod16.z.string().optional()
8927
+ });
8928
+ var TokenGrantSchema = import_zod16.z.looseObject({
8929
+ access_token: import_zod16.z.string().min(1).optional(),
8930
+ refresh_token: import_zod16.z.string().min(1).optional(),
8931
+ id_token: import_zod16.z.string().min(1).optional(),
8932
+ account_id: import_zod16.z.string().min(1).optional()
8933
+ });
8934
+ var CodexAuthError = class extends Error {
8935
+ constructor(message) {
8936
+ super(message);
8937
+ this.name = "CodexAuthError";
8938
+ }
8939
+ };
8940
+
8941
+ // src/codex/siwc.ts
8942
+ var import_zod17 = require("zod");
8943
+ var SIWC_RESOURCE = "https://api.openai.com/v1";
8944
+ var SIWC_RESPONSES_URL = `${SIWC_RESOURCE}/responses`;
8945
+ var SIWC_PLAN_SCOPE = "chatgpt.tokens.use.direct";
8946
+ var TokenResponseSchema = import_zod17.z.looseObject({
8947
+ access_token: import_zod17.z.string().min(1),
8948
+ refresh_token: import_zod17.z.string().min(1).optional(),
8949
+ id_token: import_zod17.z.string().min(1).optional(),
8950
+ expires_in: import_zod17.z.number().positive(),
8951
+ scope: import_zod17.z.string().optional()
8952
+ });
8953
+ var JwtHeaderSchema = import_zod17.z.looseObject({ alg: import_zod17.z.string(), kid: import_zod17.z.string().optional() });
8954
+ var IdTokenClaimsSchema = import_zod17.z.looseObject({
8955
+ iss: import_zod17.z.string(),
8956
+ sub: import_zod17.z.string().min(1),
8957
+ aud: import_zod17.z.union([import_zod17.z.string(), import_zod17.z.array(import_zod17.z.string())]),
8958
+ exp: import_zod17.z.number(),
8959
+ nonce: import_zod17.z.string().optional(),
8960
+ email: import_zod17.z.string().optional()
8961
+ });
8962
+ var JwksSchema = import_zod17.z.object({ keys: import_zod17.z.array(import_zod17.z.looseObject({ kid: import_zod17.z.string().optional(), kty: import_zod17.z.string() })) });
8963
+
8964
+ // src/codex/siwcStore.ts
8965
+ var SiwcGrantSchema = import_zod18.z.strictObject({
8966
+ clientId: import_zod18.z.string().min(1),
8967
+ sub: import_zod18.z.string().min(1),
8968
+ email: import_zod18.z.string().optional(),
8969
+ idToken: import_zod18.z.string().min(1),
8970
+ accessToken: import_zod18.z.string().min(1),
8971
+ refreshToken: import_zod18.z.string().min(1),
8972
+ scopes: import_zod18.z.array(import_zod18.z.string()).readonly(),
8973
+ /** When the access token stops working, in milliseconds since the epoch. */
8974
+ expiresAt: import_zod18.z.number()
8975
+ });
8976
+ var SiwcFileSchema = import_zod18.z.strictObject({ registeredClientId: import_zod18.z.string().min(1).optional(), grant: SiwcGrantSchema.optional() });
8977
+ function parseSiwcFile(raw, filePath) {
8978
+ let json;
8979
+ try {
8980
+ json = JSON.parse(raw);
8981
+ } catch {
8982
+ throw new CodexAuthError(`${filePath} is not valid JSON: run \`agent-shim codex login\` again`);
8983
+ }
8984
+ const parsed = SiwcFileSchema.safeParse(json);
8985
+ if (!parsed.success) {
8986
+ throw new CodexAuthError(`${filePath} is not a Sign in with ChatGPT file: run \`agent-shim codex login\` again`);
8987
+ }
8988
+ return parsed.data;
8989
+ }
8990
+
8991
+ // src/doctorReport.ts
8896
8992
  var DOCTOR_SEVERITIES = ["pass", "warn", "fail"];
8897
8993
  var DOCTOR_SECTIONS = [
8898
8994
  "ambient-credential",
@@ -8983,7 +9079,21 @@ function cacheClause(cacheEnv, owner, block) {
8983
9079
  const state = describeCachedCredential({ port: cacheEnv.port, platform: cacheEnv.platform, owner, cache: block.cache, nowMs: cacheEnv.now() });
8984
9080
  return { text: `; ${formatCachedCredentialState(state)}`, unreadable: state.status === "unreadable" };
8985
9081
  }
8986
- function pushProvider(push, entry, cacheEnv) {
9082
+ function signInProblem(signIn) {
9083
+ if (signIn.raw === void 0) {
9084
+ return "nobody is signed in: run `agent-shim codex login`.";
9085
+ }
9086
+ try {
9087
+ const grant = parseSiwcFile(signIn.raw, signIn.path).grant;
9088
+ if (grant === void 0) {
9089
+ return "nobody is signed in: run `agent-shim codex login`.";
9090
+ }
9091
+ 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.";
9092
+ } catch (error) {
9093
+ return error instanceof Error ? error.message : String(error);
9094
+ }
9095
+ }
9096
+ function pushProvider(push, entry, cacheEnv, signIn) {
8987
9097
  if (entry.raw === void 0) {
8988
9098
  push("provider", "fail", `${entry.path} is missing.`, entry.name);
8989
9099
  return;
@@ -9010,6 +9120,12 @@ function pushProvider(push, entry, cacheEnv) {
9010
9120
  if (validated2.data.env?.ENABLE_TOOL_SEARCH === void 0) {
9011
9121
  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);
9012
9122
  }
9123
+ if (signIn !== void 0 && isCodexProvider(validated2.data) && validated2.data.codex?.login === "chatgpt-sign-in") {
9124
+ const problem = signInProblem(signIn);
9125
+ if (problem !== void 0) {
9126
+ push("provider", "warn", `${entry.name} uses the Sign in with ChatGPT login, but ${problem}`, entry.name);
9127
+ }
9128
+ }
9013
9129
  }
9014
9130
  function pushLegacyName(push, params) {
9015
9131
  const legacyVariables = Object.keys(params.env).filter((name) => name.startsWith(LEGACY_ENV_PREFIX) && params.env[name] !== void 0).sort();
@@ -9134,7 +9250,7 @@ function runDoctor(params) {
9134
9250
  return poolName === void 0 ? validIdentityNames.has(selector) : poolName in pools;
9135
9251
  };
9136
9252
  for (const entry of params.providers) {
9137
- pushProvider(push, entry, params.credentialCache);
9253
+ pushProvider(push, entry, params.credentialCache, params.chatgptSignIn);
9138
9254
  }
9139
9255
  if (params.platform !== "darwin" || params.run === void 0) {
9140
9256
  push("keychain", "pass", "Skipped (not macOS).");
@@ -9376,6 +9492,7 @@ function collectDoctorReport(params) {
9376
9492
  const report = runDoctor({
9377
9493
  env: params.env,
9378
9494
  credentialCache: realCredentialCacheEnv(paths),
9495
+ chatgptSignIn: { path: paths.chatgptSignInFile, raw: realFsPort.readFileUtf8(paths.chatgptSignInFile) },
9379
9496
  installedClaudeVersions: realInstalledClaudeVersions(),
9380
9497
  identities,
9381
9498
  configProfiles,
@@ -9412,60 +9529,121 @@ function collectDoctorReport(params) {
9412
9529
  }
9413
9530
 
9414
9531
  // src/frontdoor/controlSchemas.ts
9415
- var UsageListOutputSchema = import_zod16.z.strictObject({ snapshots: import_zod16.z.readonly(import_zod16.z.array(UsageSnapshotSchema)) });
9416
- var UsageWindowsInputSchema = import_zod16.z.strictObject({
9417
- identity: import_zod16.z.string().min(1),
9418
- provider: import_zod16.z.string().min(1).optional()
9532
+ var UsageListOutputSchema = import_zod19.z.strictObject({ snapshots: import_zod19.z.readonly(import_zod19.z.array(UsageSnapshotSchema)) });
9533
+ var UsageWindowsInputSchema = import_zod19.z.strictObject({
9534
+ identity: import_zod19.z.string().min(1),
9535
+ provider: import_zod19.z.string().min(1).optional()
9419
9536
  });
9420
- var EffectiveWindowSchema = import_zod16.z.strictObject({
9537
+ var EffectiveWindowSchema = import_zod19.z.strictObject({
9421
9538
  /** True when the window's reset time has passed, so the recorded observation describes a window that no longer exists. */
9422
- reset: import_zod16.z.boolean(),
9539
+ reset: import_zod19.z.boolean(),
9423
9540
  /** The fraction used: 0 for a reset window, otherwise what was last observed, absent when none was reported. */
9424
- utilization: import_zod16.z.number().optional(),
9541
+ utilization: import_zod19.z.number().optional(),
9425
9542
  /** The reset instant, for a window that has not reset yet and reported one. */
9426
- resetsAtMs: import_zod16.z.number().optional(),
9543
+ resetsAtMs: import_zod19.z.number().optional(),
9427
9544
  /** The window's own status, for a window that has not reset yet. */
9428
- status: import_zod16.z.string().optional()
9545
+ status: import_zod19.z.string().optional()
9429
9546
  });
9430
- var UsageWindowsOutputSchema = import_zod16.z.strictObject({
9431
- identity: import_zod16.z.string(),
9432
- provider: import_zod16.z.string(),
9547
+ var UsageWindowsOutputSchema = import_zod19.z.strictObject({
9548
+ identity: import_zod19.z.string(),
9549
+ provider: import_zod19.z.string(),
9433
9550
  /** When the rate-limit state the windows come from was observed. */
9434
- observedAt: import_zod16.z.iso.datetime().optional(),
9551
+ observedAt: import_zod19.z.iso.datetime().optional(),
9435
9552
  fiveHour: EffectiveWindowSchema.optional(),
9436
9553
  sevenDay: EffectiveWindowSchema.optional()
9437
9554
  });
9438
- var FrontDoorSessionStatusSchema = import_zod16.z.strictObject({
9439
- pid: import_zod16.z.int().positive(),
9440
- startedAt: import_zod16.z.number(),
9441
- alive: import_zod16.z.boolean()
9555
+ var FrontDoorSessionStatusSchema = import_zod19.z.strictObject({
9556
+ pid: import_zod19.z.int().positive(),
9557
+ startedAt: import_zod19.z.number(),
9558
+ alive: import_zod19.z.boolean()
9442
9559
  });
9443
- var HeadroomSocketTargetSchema = import_zod16.z.union([import_zod16.z.strictObject({ socketPath: import_zod16.z.string() }), import_zod16.z.strictObject({ refused: import_zod16.z.string() })]);
9444
- var FrontDoorStatusOutputSchema = import_zod16.z.strictObject({
9560
+ var HeadroomSocketTargetSchema = import_zod19.z.union([import_zod19.z.strictObject({ socketPath: import_zod19.z.string() }), import_zod19.z.strictObject({ refused: import_zod19.z.string() })]);
9561
+ var FrontDoorStatusOutputSchema = import_zod19.z.strictObject({
9445
9562
  state: FrontDoorStateSchema,
9446
- supervisorAlive: import_zod16.z.boolean(),
9447
- sessions: import_zod16.z.readonly(import_zod16.z.array(FrontDoorSessionStatusSchema)),
9563
+ supervisorAlive: import_zod19.z.boolean(),
9564
+ sessions: import_zod19.z.readonly(import_zod19.z.array(FrontDoorSessionStatusSchema)),
9448
9565
  headroomSocket: HeadroomSocketTargetSchema.optional(),
9449
- logPath: import_zod16.z.string(),
9450
- logExists: import_zod16.z.boolean()
9566
+ logPath: import_zod19.z.string(),
9567
+ logExists: import_zod19.z.boolean()
9451
9568
  });
9452
- var FrontDoorSessionsOutputSchema = import_zod16.z.strictObject({ sessions: import_zod16.z.readonly(import_zod16.z.array(FrontDoorSessionStatusSchema)) });
9453
- var CheckRunInputSchema = import_zod16.z.strictObject({
9454
- path: import_zod16.z.string().min(1),
9455
- identity: import_zod16.z.string().min(1).optional()
9569
+ var FrontDoorSessionsOutputSchema = import_zod19.z.strictObject({ sessions: import_zod19.z.readonly(import_zod19.z.array(FrontDoorSessionStatusSchema)) });
9570
+ var CheckRunInputSchema = import_zod19.z.strictObject({
9571
+ path: import_zod19.z.string().min(1),
9572
+ identity: import_zod19.z.string().min(1).optional()
9456
9573
  }).refine((input) => import_node_path27.default.isAbsolute(input.path), { message: "path must be absolute: the door cannot resolve a relative path against the caller's working directory" });
9457
- var DoctorFindingSchema = import_zod16.z.strictObject({
9458
- section: import_zod16.z.enum(DOCTOR_SECTIONS),
9459
- subject: import_zod16.z.string().optional(),
9460
- severity: import_zod16.z.enum(DOCTOR_SEVERITIES),
9461
- message: import_zod16.z.string()
9574
+ var DoctorFindingSchema = import_zod19.z.strictObject({
9575
+ section: import_zod19.z.enum(DOCTOR_SECTIONS),
9576
+ subject: import_zod19.z.string().optional(),
9577
+ severity: import_zod19.z.enum(DOCTOR_SEVERITIES),
9578
+ message: import_zod19.z.string()
9462
9579
  });
9463
- var DoctorRunOutputSchema = import_zod16.z.strictObject({
9580
+ var DoctorRunOutputSchema = import_zod19.z.strictObject({
9464
9581
  /** False exactly when any finding is a `fail`; a `warn` never fails the report on its own. */
9465
- ok: import_zod16.z.boolean(),
9466
- findings: import_zod16.z.readonly(import_zod16.z.array(DoctorFindingSchema))
9582
+ ok: import_zod19.z.boolean(),
9583
+ findings: import_zod19.z.readonly(import_zod19.z.array(DoctorFindingSchema))
9584
+ });
9585
+
9586
+ // src/frontdoor/eventsApi.ts
9587
+ var import_client2 = require("@orpc/client");
9588
+ var import_server3 = require("@orpc/server");
9589
+
9590
+ // src/frontdoor/eventSchemas.ts
9591
+ var import_zod20 = require("zod");
9592
+ var DOOR_EVENT_SOURCE_RC = "rc";
9593
+ var LAUNCH_EVENT_SOURCE = "launch";
9594
+ var DoorEventSchema = import_zod20.z.strictObject({
9595
+ source: import_zod20.z.string().min(1),
9596
+ /** A positive integer because the backbone's counters start at one and only ever increase. */
9597
+ sequence: import_zod20.z.number().int().positive(),
9598
+ payload: import_zod20.z.unknown()
9599
+ });
9600
+ var DoorEventSourceQuerySchema = import_zod20.z.strictObject({
9601
+ sources: import_zod20.z.array(import_zod20.z.string().min(1)).min(1).optional()
9602
+ });
9603
+ var LaunchLifecycleEventSchema = import_zod20.z.strictObject({
9604
+ kind: import_zod20.z.enum(["registered", "pruned", "ended"]),
9605
+ pid: import_zod20.z.number().int().positive(),
9606
+ startedAt: import_zod20.z.number(),
9607
+ observedAt: import_zod20.z.number()
9467
9608
  });
9468
9609
 
9610
+ // src/frontdoor/eventsApi.ts
9611
+ function createEventsApiRouter(deps) {
9612
+ const authed = doorApiAuth(deps.expectedToken);
9613
+ return {
9614
+ events: {
9615
+ subscribe: authed.input(DoorEventSourceQuerySchema).output((0, import_server3.eventIterator)(DoorEventSchema)).handler(async function* ({ input, signal }) {
9616
+ if (input.sources !== void 0) {
9617
+ const live = deps.events.sources();
9618
+ const unknown = input.sources.filter((source) => !live.includes(source));
9619
+ if (unknown.length > 0) {
9620
+ throw new import_client2.ORPCError("NOT_FOUND", { message: `the door publishes no event source named ${unknown.map((source) => `"${source}"`).join(", ")}; the live sources are ${live.map((source) => `"${source}"`).join(", ")}` });
9621
+ }
9622
+ }
9623
+ const stream = deps.events.stream(input.sources);
9624
+ const ended = new Promise((resolve) => {
9625
+ signal?.addEventListener("abort", () => {
9626
+ resolve();
9627
+ }, { once: true });
9628
+ });
9629
+ try {
9630
+ for (; ; ) {
9631
+ signal?.throwIfAborted();
9632
+ const next = await Promise.race([stream.next(), ended.then(() => ({ value: void 0, done: true }))]);
9633
+ if (next.done === true) {
9634
+ signal?.throwIfAborted();
9635
+ return;
9636
+ }
9637
+ yield (0, import_server3.withEventMeta)(next.value, { id: String(next.value.sequence) });
9638
+ }
9639
+ } finally {
9640
+ stream.close();
9641
+ }
9642
+ })
9643
+ }
9644
+ };
9645
+ }
9646
+
9469
9647
  // src/frontdoor/controlApi.ts
9470
9648
  function createControlApiRouter(deps) {
9471
9649
  const authed = doorApiAuth(deps.expectedToken);
@@ -9475,7 +9653,7 @@ function createControlApiRouter(deps) {
9475
9653
  effectiveWindow: authed.input(UsageWindowsInputSchema).output(UsageWindowsOutputSchema).handler(({ input }) => {
9476
9654
  const snapshot = deps.usageSnapshotOf(input.identity);
9477
9655
  if (snapshot === void 0) {
9478
- throw new import_client2.ORPCError("NOT_FOUND", { message: `identity ${input.identity} has no usage snapshot yet, so no quota window has been observed for it` });
9656
+ throw new import_client3.ORPCError("NOT_FOUND", { message: `identity ${input.identity} has no usage snapshot yet, so no quota window has been observed for it` });
9479
9657
  }
9480
9658
  const provider = input.provider ?? ANTHROPIC_PROVIDER;
9481
9659
  const rateLimit = snapshot.providers[provider]?.rateLimit;
@@ -9504,13 +9682,13 @@ function createControlApiRouter(deps) {
9504
9682
  };
9505
9683
  }
9506
9684
  function createDoorApiRouter(deps) {
9507
- return { ...createRcApiRouter(deps), ...createControlApiRouter(deps) };
9685
+ return { ...createRcApiRouter(deps), ...createControlApiRouter(deps), ...createEventsApiRouter(deps) };
9508
9686
  }
9509
9687
  function createDoorApiNodeHandler(deps) {
9510
9688
  return doorApiNodeHandlerOf(createDoorApiRouter(deps));
9511
9689
  }
9512
9690
  function frontDoorApiClient(port, ca, token) {
9513
- return (0, import_client2.createORPCClient)(frontDoorApiLink(port, ca, token));
9691
+ return (0, import_client3.createORPCClient)(frontDoorApiLink(port, ca, token));
9514
9692
  }
9515
9693
 
9516
9694
  // src/frontdoor/status.ts
@@ -9566,6 +9744,148 @@ function formatFrontDoorStatus(status, caCertPath) {
9566
9744
  return lines;
9567
9745
  }
9568
9746
 
9747
+ // src/frontdoor/eventHub.ts
9748
+ var DOOR_EVENT_BUFFER_EVENTS = 100;
9749
+ function createDoorEventHub() {
9750
+ const sequences = /* @__PURE__ */ new Map();
9751
+ const registered = /* @__PURE__ */ new Set();
9752
+ const every = /* @__PURE__ */ new Set();
9753
+ const bySource = /* @__PURE__ */ new Map();
9754
+ const deliver = (event) => {
9755
+ for (const listener of every) {
9756
+ listener(event);
9757
+ }
9758
+ for (const listener of bySource.get(event.source) ?? []) {
9759
+ listener(event);
9760
+ }
9761
+ };
9762
+ const publish = (source, payload) => {
9763
+ const sequence2 = (sequences.get(source) ?? 0) + 1;
9764
+ sequences.set(source, sequence2);
9765
+ deliver({ source, sequence: sequence2, payload });
9766
+ };
9767
+ const addListener = (sources, listener) => {
9768
+ if (sources === void 0) {
9769
+ every.add(listener);
9770
+ return () => {
9771
+ every.delete(listener);
9772
+ };
9773
+ }
9774
+ const listenersFor = (source) => {
9775
+ const listeners = bySource.get(source) ?? /* @__PURE__ */ new Set();
9776
+ bySource.set(source, listeners);
9777
+ return listeners;
9778
+ };
9779
+ const detachers = sources.map((source) => {
9780
+ const listeners = listenersFor(source);
9781
+ listeners.add(listener);
9782
+ return () => {
9783
+ listeners.delete(listener);
9784
+ if (listeners.size === 0) {
9785
+ bySource.delete(source);
9786
+ }
9787
+ };
9788
+ });
9789
+ return () => {
9790
+ for (const detach of detachers) {
9791
+ detach();
9792
+ }
9793
+ };
9794
+ };
9795
+ return {
9796
+ publisher: (source) => {
9797
+ registered.add(source);
9798
+ return {
9799
+ source,
9800
+ publish: (payload) => {
9801
+ publish(source, payload);
9802
+ }
9803
+ };
9804
+ },
9805
+ subscribe: addListener,
9806
+ stream: (sources) => {
9807
+ const queue = [];
9808
+ let wake;
9809
+ let closed = false;
9810
+ const detach = addListener(sources, (event) => {
9811
+ if (queue.length >= DOOR_EVENT_BUFFER_EVENTS) {
9812
+ queue.shift();
9813
+ }
9814
+ queue.push(event);
9815
+ wake?.();
9816
+ });
9817
+ return {
9818
+ next: async () => {
9819
+ for (; ; ) {
9820
+ const event = queue.shift();
9821
+ if (event !== void 0) {
9822
+ return { value: event, done: false };
9823
+ }
9824
+ if (closed) {
9825
+ return { value: void 0, done: true };
9826
+ }
9827
+ await new Promise((resolve) => {
9828
+ wake = resolve;
9829
+ });
9830
+ }
9831
+ },
9832
+ close: () => {
9833
+ closed = true;
9834
+ detach();
9835
+ wake?.();
9836
+ }
9837
+ };
9838
+ },
9839
+ sources: () => [...registered].sort()
9840
+ };
9841
+ }
9842
+ function rcFanoutOnDoorHub(fanout, hub) {
9843
+ const publisher = hub.publisher(DOOR_EVENT_SOURCE_RC);
9844
+ return {
9845
+ publish: (event) => {
9846
+ fanout.publish(event);
9847
+ publisher.publish(event);
9848
+ },
9849
+ subscribe: (session, listener) => fanout.subscribe(session, listener)
9850
+ };
9851
+ }
9852
+
9853
+ // src/frontdoor/launchEvents.ts
9854
+ function createLaunchEventPublisher(hub, now) {
9855
+ const publisher = hub.publisher(LAUNCH_EVENT_SOURCE);
9856
+ let known;
9857
+ return {
9858
+ observe: (sessions, pruned) => {
9859
+ const previous = known;
9860
+ const listed = new Map(sessions.map((session) => [session.pid, session]));
9861
+ const prunedSet = new Set(pruned);
9862
+ known = new Map([...listed].filter(([pid]) => !prunedSet.has(pid)));
9863
+ if (previous === void 0) {
9864
+ return;
9865
+ }
9866
+ const emit = (kind, pid, startedAt) => {
9867
+ publisher.publish({ kind, pid, startedAt, observedAt: now() });
9868
+ };
9869
+ for (const pid of pruned) {
9870
+ const session = listed.get(pid);
9871
+ if (session !== void 0) {
9872
+ emit("pruned", pid, session.startedAt);
9873
+ }
9874
+ }
9875
+ for (const [pid, session] of previous) {
9876
+ if (!listed.has(pid) && !prunedSet.has(pid)) {
9877
+ emit("ended", pid, session.startedAt);
9878
+ }
9879
+ }
9880
+ for (const [pid, session] of listed) {
9881
+ if (!previous.has(pid) && !prunedSet.has(pid)) {
9882
+ emit("registered", pid, session.startedAt);
9883
+ }
9884
+ }
9885
+ }
9886
+ };
9887
+ }
9888
+
9569
9889
  // src/frontdoor/rcStream.ts
9570
9890
  var RC_STREAM_EVENT_NAME = "client_event";
9571
9891
  var SSE_BYTE_ORDER_MARK = 65279;
@@ -11652,6 +11972,8 @@ function prepareClaudeLaunch(options) {
11652
11972
  CredentialCacheSchema,
11653
11973
  CredentialSchema,
11654
11974
  CredentialSourceSchema,
11975
+ DOOR_EVENT_BUFFER_EVENTS,
11976
+ DOOR_EVENT_SOURCE_RC,
11655
11977
  DirectoryRuleAlreadyExistsError,
11656
11978
  DirectoryRuleMissingTargetError,
11657
11979
  DirectoryRuleNotFoundError,
@@ -11659,6 +11981,8 @@ function prepareClaudeLaunch(options) {
11659
11981
  DirectoryRulesSchema,
11660
11982
  DoctorFindingSchema,
11661
11983
  DoctorRunOutputSchema,
11984
+ DoorEventSchema,
11985
+ DoorEventSourceQuerySchema,
11662
11986
  EXIT_FAILURE,
11663
11987
  EXIT_USAGE,
11664
11988
  EffectiveWindowSchema,
@@ -11678,6 +12002,8 @@ function prepareClaudeLaunch(options) {
11678
12002
  InvalidCategoryNameError,
11679
12003
  InvalidIdentityNameError,
11680
12004
  InvalidProviderNameError,
12005
+ LAUNCH_EVENT_SOURCE,
12006
+ LaunchLifecycleEventSchema,
11681
12007
  LaunchRefusedError,
11682
12008
  LegacyProviderFileError,
11683
12009
  PROMPT_CACHE_TTL_MS,
@@ -11741,6 +12067,9 @@ function prepareClaudeLaunch(options) {
11741
12067
  createControlApiRouter,
11742
12068
  createDoorApiNodeHandler,
11743
12069
  createDoorApiRouter,
12070
+ createDoorEventHub,
12071
+ createEventsApiRouter,
12072
+ createLaunchEventPublisher,
11744
12073
  createLeafCache,
11745
12074
  createProfile,
11746
12075
  createRcApiNodeHandler,
@@ -11799,6 +12128,7 @@ function prepareClaudeLaunch(options) {
11799
12128
  providerExists,
11800
12129
  rankPool,
11801
12130
  rcEventWriteResultFromAnswer,
12131
+ rcFanoutOnDoorHub,
11802
12132
  rcObserverAsPassthrough,
11803
12133
  rcSelfHostFromEnv,
11804
12134
  readActiveIdentity,