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.mjs CHANGED
@@ -253,6 +253,7 @@ var ProviderEnvSchema = z.record(z.string().min(1), z.string()).refine((env) =>
253
253
  message: `env may not set ${RESERVED_PROVIDER_ENV_KEYS.join(", ")}: the credential block's target sets one and agent-shim removes the others`
254
254
  });
255
255
  var CODEX_EFFORTS = ["none", "low", "medium", "high"];
256
+ var CODEX_LOGINS = ["codex-cli", "chatgpt-sign-in"];
256
257
  var CODEX_DEFAULT_TIER_MODELS = Object.freeze({
257
258
  fable: "gpt-5.6-sol",
258
259
  opus: "gpt-5.6-sol",
@@ -270,7 +271,9 @@ var CodexProviderConfigSchema = z.strictObject({
270
271
  haiku: z.string().min(1).optional()
271
272
  }).optional(),
272
273
  /** The reasoning effort used when the request's own `output_config.effort` is not one the backend accepts. */
273
- effort: z.enum(CODEX_EFFORTS).optional()
274
+ effort: z.enum(CODEX_EFFORTS).optional(),
275
+ /** Which login the upstream call is made with; see `CODEX_LOGINS`. */
276
+ login: z.enum(CODEX_LOGINS).optional()
274
277
  });
275
278
  var HttpProviderSchema = z.strictObject({
276
279
  $schema: z.string().optional(),
@@ -2759,8 +2762,11 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
2759
2762
  return FRONTDOOR_SUPERVISOR_STILL_RUNNING;
2760
2763
  }
2761
2764
  ticks += 1;
2762
- pruneDeadFrontDoorSessions(fs12, paths.frontdoorSessionsDir, ports.isRunning);
2763
- if (listFrontDoorSessions(fs12, paths.frontdoorSessionsDir).length === 0) {
2765
+ const listed = listFrontDoorSessions(fs12, paths.frontdoorSessionsDir);
2766
+ const pruned = pruneDeadFrontDoorSessions(fs12, paths.frontdoorSessionsDir, ports.isRunning);
2767
+ ports.observeLaunchRegistry(listed, pruned);
2768
+ const prunedPids = new Set(pruned);
2769
+ if (listed.filter((session) => !prunedPids.has(session.pid)).length === 0) {
2764
2770
  idleSince ??= ports.now();
2765
2771
  if (ports.now() - idleSince >= idleShutdownMinutes * MS_PER_MINUTE) {
2766
2772
  ports.log(`agent-shim frontdoor supervisor: no sessions for ${String(idleShutdownMinutes)} minute(s); closing the front door and exiting`);
@@ -4982,7 +4988,7 @@ function frontDoorRcApiClient(port, ca, token) {
4982
4988
  }
4983
4989
 
4984
4990
  // src/frontdoor/controlApi.ts
4985
- import { createORPCClient as createORPCClient2, ORPCError as ORPCError2 } from "@orpc/client";
4991
+ import { createORPCClient as createORPCClient2, ORPCError as ORPCError3 } from "@orpc/client";
4986
4992
 
4987
4993
  // src/checkReport.ts
4988
4994
  import os5 from "node:os";
@@ -5332,6 +5338,7 @@ function buildLayoutPaths(root) {
5332
5338
  frontdoorCaCertFile: path14.join(root, "frontdoor", "ca", "ca.pem"),
5333
5339
  frontdoorCaKeyFile: path14.join(root, "frontdoor", "ca", "ca.key"),
5334
5340
  frontdoorCaBundlesDir: path14.join(root, "frontdoor", "ca", "bundles"),
5341
+ chatgptSignInFile: path14.join(root, "codex", "chatgpt-sign-in.json"),
5335
5342
  frontdoorStateFile: path14.join(root, "frontdoor", "state.json"),
5336
5343
  frontdoorLockFile: path14.join(root, "frontdoor", "start.lock"),
5337
5344
  frontdoorControlTokenFile: path14.join(root, "frontdoor", "control-token"),
@@ -6767,7 +6774,7 @@ var ProviderKindMismatchError = class extends UsageError {
6767
6774
  }
6768
6775
  };
6769
6776
  function isEmptyCodexConfig(config) {
6770
- return config === void 0 || config.defaultModel === void 0 && config.effort === void 0 && (config.models === void 0 || Object.keys(config.models).length === 0);
6777
+ 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);
6771
6778
  }
6772
6779
  function addProvider(paths, name, input) {
6773
6780
  if (providerExists(paths, name)) {
@@ -8435,7 +8442,7 @@ var CheckReportJsonSchema = z14.strictObject({
8435
8442
 
8436
8443
  // src/frontdoor/controlSchemas.ts
8437
8444
  import path27 from "node:path";
8438
- import { z as z16 } from "zod";
8445
+ import { z as z19 } from "zod";
8439
8446
 
8440
8447
  // src/claudeShim.ts
8441
8448
  import fs8 from "node:fs";
@@ -8639,6 +8646,85 @@ function removeIdentity(paths, name) {
8639
8646
  // src/doctorReport.ts
8640
8647
  import fs10 from "node:fs";
8641
8648
  import path26 from "node:path";
8649
+
8650
+ // src/codex/siwcStore.ts
8651
+ import { z as z18 } from "zod";
8652
+
8653
+ // src/codex/auth.ts
8654
+ import { z as z16 } from "zod";
8655
+ var AuthFileSchema = z16.looseObject({
8656
+ tokens: z16.looseObject({
8657
+ access_token: z16.string().optional(),
8658
+ refresh_token: z16.string().optional(),
8659
+ id_token: z16.string().optional(),
8660
+ account_id: z16.string().optional()
8661
+ }).nullish(),
8662
+ last_refresh: z16.string().optional()
8663
+ });
8664
+ var TokenGrantSchema = z16.looseObject({
8665
+ access_token: z16.string().min(1).optional(),
8666
+ refresh_token: z16.string().min(1).optional(),
8667
+ id_token: z16.string().min(1).optional(),
8668
+ account_id: z16.string().min(1).optional()
8669
+ });
8670
+ var CodexAuthError = class extends Error {
8671
+ constructor(message) {
8672
+ super(message);
8673
+ this.name = "CodexAuthError";
8674
+ }
8675
+ };
8676
+
8677
+ // src/codex/siwc.ts
8678
+ import { z as z17 } from "zod";
8679
+ var SIWC_RESOURCE = "https://api.openai.com/v1";
8680
+ var SIWC_RESPONSES_URL = `${SIWC_RESOURCE}/responses`;
8681
+ var SIWC_PLAN_SCOPE = "chatgpt.tokens.use.direct";
8682
+ var TokenResponseSchema = z17.looseObject({
8683
+ access_token: z17.string().min(1),
8684
+ refresh_token: z17.string().min(1).optional(),
8685
+ id_token: z17.string().min(1).optional(),
8686
+ expires_in: z17.number().positive(),
8687
+ scope: z17.string().optional()
8688
+ });
8689
+ var JwtHeaderSchema = z17.looseObject({ alg: z17.string(), kid: z17.string().optional() });
8690
+ var IdTokenClaimsSchema = z17.looseObject({
8691
+ iss: z17.string(),
8692
+ sub: z17.string().min(1),
8693
+ aud: z17.union([z17.string(), z17.array(z17.string())]),
8694
+ exp: z17.number(),
8695
+ nonce: z17.string().optional(),
8696
+ email: z17.string().optional()
8697
+ });
8698
+ var JwksSchema = z17.object({ keys: z17.array(z17.looseObject({ kid: z17.string().optional(), kty: z17.string() })) });
8699
+
8700
+ // src/codex/siwcStore.ts
8701
+ var SiwcGrantSchema = z18.strictObject({
8702
+ clientId: z18.string().min(1),
8703
+ sub: z18.string().min(1),
8704
+ email: z18.string().optional(),
8705
+ idToken: z18.string().min(1),
8706
+ accessToken: z18.string().min(1),
8707
+ refreshToken: z18.string().min(1),
8708
+ scopes: z18.array(z18.string()).readonly(),
8709
+ /** When the access token stops working, in milliseconds since the epoch. */
8710
+ expiresAt: z18.number()
8711
+ });
8712
+ var SiwcFileSchema = z18.strictObject({ registeredClientId: z18.string().min(1).optional(), grant: SiwcGrantSchema.optional() });
8713
+ function parseSiwcFile(raw, filePath) {
8714
+ let json;
8715
+ try {
8716
+ json = JSON.parse(raw);
8717
+ } catch {
8718
+ throw new CodexAuthError(`${filePath} is not valid JSON: run \`agent-shim codex login\` again`);
8719
+ }
8720
+ const parsed = SiwcFileSchema.safeParse(json);
8721
+ if (!parsed.success) {
8722
+ throw new CodexAuthError(`${filePath} is not a Sign in with ChatGPT file: run \`agent-shim codex login\` again`);
8723
+ }
8724
+ return parsed.data;
8725
+ }
8726
+
8727
+ // src/doctorReport.ts
8642
8728
  var DOCTOR_SEVERITIES = ["pass", "warn", "fail"];
8643
8729
  var DOCTOR_SECTIONS = [
8644
8730
  "ambient-credential",
@@ -8729,7 +8815,21 @@ function cacheClause(cacheEnv, owner, block) {
8729
8815
  const state = describeCachedCredential({ port: cacheEnv.port, platform: cacheEnv.platform, owner, cache: block.cache, nowMs: cacheEnv.now() });
8730
8816
  return { text: `; ${formatCachedCredentialState(state)}`, unreadable: state.status === "unreadable" };
8731
8817
  }
8732
- function pushProvider(push, entry, cacheEnv) {
8818
+ function signInProblem(signIn) {
8819
+ if (signIn.raw === void 0) {
8820
+ return "nobody is signed in: run `agent-shim codex login`.";
8821
+ }
8822
+ try {
8823
+ const grant = parseSiwcFile(signIn.raw, signIn.path).grant;
8824
+ if (grant === void 0) {
8825
+ return "nobody is signed in: run `agent-shim codex login`.";
8826
+ }
8827
+ 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.";
8828
+ } catch (error) {
8829
+ return error instanceof Error ? error.message : String(error);
8830
+ }
8831
+ }
8832
+ function pushProvider(push, entry, cacheEnv, signIn) {
8733
8833
  if (entry.raw === void 0) {
8734
8834
  push("provider", "fail", `${entry.path} is missing.`, entry.name);
8735
8835
  return;
@@ -8756,6 +8856,12 @@ function pushProvider(push, entry, cacheEnv) {
8756
8856
  if (validated2.data.env?.ENABLE_TOOL_SEARCH === void 0) {
8757
8857
  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);
8758
8858
  }
8859
+ if (signIn !== void 0 && isCodexProvider(validated2.data) && validated2.data.codex?.login === "chatgpt-sign-in") {
8860
+ const problem = signInProblem(signIn);
8861
+ if (problem !== void 0) {
8862
+ push("provider", "warn", `${entry.name} uses the Sign in with ChatGPT login, but ${problem}`, entry.name);
8863
+ }
8864
+ }
8759
8865
  }
8760
8866
  function pushLegacyName(push, params) {
8761
8867
  const legacyVariables = Object.keys(params.env).filter((name) => name.startsWith(LEGACY_ENV_PREFIX) && params.env[name] !== void 0).sort();
@@ -8880,7 +8986,7 @@ function runDoctor(params) {
8880
8986
  return poolName === void 0 ? validIdentityNames.has(selector) : poolName in pools;
8881
8987
  };
8882
8988
  for (const entry of params.providers) {
8883
- pushProvider(push, entry, params.credentialCache);
8989
+ pushProvider(push, entry, params.credentialCache, params.chatgptSignIn);
8884
8990
  }
8885
8991
  if (params.platform !== "darwin" || params.run === void 0) {
8886
8992
  push("keychain", "pass", "Skipped (not macOS).");
@@ -9122,6 +9228,7 @@ function collectDoctorReport(params) {
9122
9228
  const report = runDoctor({
9123
9229
  env: params.env,
9124
9230
  credentialCache: realCredentialCacheEnv(paths),
9231
+ chatgptSignIn: { path: paths.chatgptSignInFile, raw: realFsPort.readFileUtf8(paths.chatgptSignInFile) },
9125
9232
  installedClaudeVersions: realInstalledClaudeVersions(),
9126
9233
  identities,
9127
9234
  configProfiles,
@@ -9158,60 +9265,121 @@ function collectDoctorReport(params) {
9158
9265
  }
9159
9266
 
9160
9267
  // src/frontdoor/controlSchemas.ts
9161
- var UsageListOutputSchema = z16.strictObject({ snapshots: z16.readonly(z16.array(UsageSnapshotSchema)) });
9162
- var UsageWindowsInputSchema = z16.strictObject({
9163
- identity: z16.string().min(1),
9164
- provider: z16.string().min(1).optional()
9268
+ var UsageListOutputSchema = z19.strictObject({ snapshots: z19.readonly(z19.array(UsageSnapshotSchema)) });
9269
+ var UsageWindowsInputSchema = z19.strictObject({
9270
+ identity: z19.string().min(1),
9271
+ provider: z19.string().min(1).optional()
9165
9272
  });
9166
- var EffectiveWindowSchema = z16.strictObject({
9273
+ var EffectiveWindowSchema = z19.strictObject({
9167
9274
  /** True when the window's reset time has passed, so the recorded observation describes a window that no longer exists. */
9168
- reset: z16.boolean(),
9275
+ reset: z19.boolean(),
9169
9276
  /** The fraction used: 0 for a reset window, otherwise what was last observed, absent when none was reported. */
9170
- utilization: z16.number().optional(),
9277
+ utilization: z19.number().optional(),
9171
9278
  /** The reset instant, for a window that has not reset yet and reported one. */
9172
- resetsAtMs: z16.number().optional(),
9279
+ resetsAtMs: z19.number().optional(),
9173
9280
  /** The window's own status, for a window that has not reset yet. */
9174
- status: z16.string().optional()
9281
+ status: z19.string().optional()
9175
9282
  });
9176
- var UsageWindowsOutputSchema = z16.strictObject({
9177
- identity: z16.string(),
9178
- provider: z16.string(),
9283
+ var UsageWindowsOutputSchema = z19.strictObject({
9284
+ identity: z19.string(),
9285
+ provider: z19.string(),
9179
9286
  /** When the rate-limit state the windows come from was observed. */
9180
- observedAt: z16.iso.datetime().optional(),
9287
+ observedAt: z19.iso.datetime().optional(),
9181
9288
  fiveHour: EffectiveWindowSchema.optional(),
9182
9289
  sevenDay: EffectiveWindowSchema.optional()
9183
9290
  });
9184
- var FrontDoorSessionStatusSchema = z16.strictObject({
9185
- pid: z16.int().positive(),
9186
- startedAt: z16.number(),
9187
- alive: z16.boolean()
9291
+ var FrontDoorSessionStatusSchema = z19.strictObject({
9292
+ pid: z19.int().positive(),
9293
+ startedAt: z19.number(),
9294
+ alive: z19.boolean()
9188
9295
  });
9189
- var HeadroomSocketTargetSchema = z16.union([z16.strictObject({ socketPath: z16.string() }), z16.strictObject({ refused: z16.string() })]);
9190
- var FrontDoorStatusOutputSchema = z16.strictObject({
9296
+ var HeadroomSocketTargetSchema = z19.union([z19.strictObject({ socketPath: z19.string() }), z19.strictObject({ refused: z19.string() })]);
9297
+ var FrontDoorStatusOutputSchema = z19.strictObject({
9191
9298
  state: FrontDoorStateSchema,
9192
- supervisorAlive: z16.boolean(),
9193
- sessions: z16.readonly(z16.array(FrontDoorSessionStatusSchema)),
9299
+ supervisorAlive: z19.boolean(),
9300
+ sessions: z19.readonly(z19.array(FrontDoorSessionStatusSchema)),
9194
9301
  headroomSocket: HeadroomSocketTargetSchema.optional(),
9195
- logPath: z16.string(),
9196
- logExists: z16.boolean()
9302
+ logPath: z19.string(),
9303
+ logExists: z19.boolean()
9197
9304
  });
9198
- var FrontDoorSessionsOutputSchema = z16.strictObject({ sessions: z16.readonly(z16.array(FrontDoorSessionStatusSchema)) });
9199
- var CheckRunInputSchema = z16.strictObject({
9200
- path: z16.string().min(1),
9201
- identity: z16.string().min(1).optional()
9305
+ var FrontDoorSessionsOutputSchema = z19.strictObject({ sessions: z19.readonly(z19.array(FrontDoorSessionStatusSchema)) });
9306
+ var CheckRunInputSchema = z19.strictObject({
9307
+ path: z19.string().min(1),
9308
+ identity: z19.string().min(1).optional()
9202
9309
  }).refine((input) => path27.isAbsolute(input.path), { message: "path must be absolute: the door cannot resolve a relative path against the caller's working directory" });
9203
- var DoctorFindingSchema = z16.strictObject({
9204
- section: z16.enum(DOCTOR_SECTIONS),
9205
- subject: z16.string().optional(),
9206
- severity: z16.enum(DOCTOR_SEVERITIES),
9207
- message: z16.string()
9310
+ var DoctorFindingSchema = z19.strictObject({
9311
+ section: z19.enum(DOCTOR_SECTIONS),
9312
+ subject: z19.string().optional(),
9313
+ severity: z19.enum(DOCTOR_SEVERITIES),
9314
+ message: z19.string()
9208
9315
  });
9209
- var DoctorRunOutputSchema = z16.strictObject({
9316
+ var DoctorRunOutputSchema = z19.strictObject({
9210
9317
  /** False exactly when any finding is a `fail`; a `warn` never fails the report on its own. */
9211
- ok: z16.boolean(),
9212
- findings: z16.readonly(z16.array(DoctorFindingSchema))
9318
+ ok: z19.boolean(),
9319
+ findings: z19.readonly(z19.array(DoctorFindingSchema))
9320
+ });
9321
+
9322
+ // src/frontdoor/eventsApi.ts
9323
+ import { ORPCError as ORPCError2 } from "@orpc/client";
9324
+ import { eventIterator as eventIterator2, withEventMeta as withEventMeta2 } from "@orpc/server";
9325
+
9326
+ // src/frontdoor/eventSchemas.ts
9327
+ import { z as z20 } from "zod";
9328
+ var DOOR_EVENT_SOURCE_RC = "rc";
9329
+ var LAUNCH_EVENT_SOURCE = "launch";
9330
+ var DoorEventSchema = z20.strictObject({
9331
+ source: z20.string().min(1),
9332
+ /** A positive integer because the backbone's counters start at one and only ever increase. */
9333
+ sequence: z20.number().int().positive(),
9334
+ payload: z20.unknown()
9335
+ });
9336
+ var DoorEventSourceQuerySchema = z20.strictObject({
9337
+ sources: z20.array(z20.string().min(1)).min(1).optional()
9338
+ });
9339
+ var LaunchLifecycleEventSchema = z20.strictObject({
9340
+ kind: z20.enum(["registered", "pruned", "ended"]),
9341
+ pid: z20.number().int().positive(),
9342
+ startedAt: z20.number(),
9343
+ observedAt: z20.number()
9213
9344
  });
9214
9345
 
9346
+ // src/frontdoor/eventsApi.ts
9347
+ function createEventsApiRouter(deps) {
9348
+ const authed = doorApiAuth(deps.expectedToken);
9349
+ return {
9350
+ events: {
9351
+ subscribe: authed.input(DoorEventSourceQuerySchema).output(eventIterator2(DoorEventSchema)).handler(async function* ({ input, signal }) {
9352
+ if (input.sources !== void 0) {
9353
+ const live = deps.events.sources();
9354
+ const unknown = input.sources.filter((source) => !live.includes(source));
9355
+ if (unknown.length > 0) {
9356
+ throw new ORPCError2("NOT_FOUND", { message: `the door publishes no event source named ${unknown.map((source) => `"${source}"`).join(", ")}; the live sources are ${live.map((source) => `"${source}"`).join(", ")}` });
9357
+ }
9358
+ }
9359
+ const stream = deps.events.stream(input.sources);
9360
+ const ended = new Promise((resolve) => {
9361
+ signal?.addEventListener("abort", () => {
9362
+ resolve();
9363
+ }, { once: true });
9364
+ });
9365
+ try {
9366
+ for (; ; ) {
9367
+ signal?.throwIfAborted();
9368
+ const next = await Promise.race([stream.next(), ended.then(() => ({ value: void 0, done: true }))]);
9369
+ if (next.done === true) {
9370
+ signal?.throwIfAborted();
9371
+ return;
9372
+ }
9373
+ yield withEventMeta2(next.value, { id: String(next.value.sequence) });
9374
+ }
9375
+ } finally {
9376
+ stream.close();
9377
+ }
9378
+ })
9379
+ }
9380
+ };
9381
+ }
9382
+
9215
9383
  // src/frontdoor/controlApi.ts
9216
9384
  function createControlApiRouter(deps) {
9217
9385
  const authed = doorApiAuth(deps.expectedToken);
@@ -9221,7 +9389,7 @@ function createControlApiRouter(deps) {
9221
9389
  effectiveWindow: authed.input(UsageWindowsInputSchema).output(UsageWindowsOutputSchema).handler(({ input }) => {
9222
9390
  const snapshot = deps.usageSnapshotOf(input.identity);
9223
9391
  if (snapshot === void 0) {
9224
- throw new ORPCError2("NOT_FOUND", { message: `identity ${input.identity} has no usage snapshot yet, so no quota window has been observed for it` });
9392
+ throw new ORPCError3("NOT_FOUND", { message: `identity ${input.identity} has no usage snapshot yet, so no quota window has been observed for it` });
9225
9393
  }
9226
9394
  const provider = input.provider ?? ANTHROPIC_PROVIDER;
9227
9395
  const rateLimit = snapshot.providers[provider]?.rateLimit;
@@ -9250,7 +9418,7 @@ function createControlApiRouter(deps) {
9250
9418
  };
9251
9419
  }
9252
9420
  function createDoorApiRouter(deps) {
9253
- return { ...createRcApiRouter(deps), ...createControlApiRouter(deps) };
9421
+ return { ...createRcApiRouter(deps), ...createControlApiRouter(deps), ...createEventsApiRouter(deps) };
9254
9422
  }
9255
9423
  function createDoorApiNodeHandler(deps) {
9256
9424
  return doorApiNodeHandlerOf(createDoorApiRouter(deps));
@@ -9312,6 +9480,148 @@ function formatFrontDoorStatus(status, caCertPath) {
9312
9480
  return lines;
9313
9481
  }
9314
9482
 
9483
+ // src/frontdoor/eventHub.ts
9484
+ var DOOR_EVENT_BUFFER_EVENTS = 100;
9485
+ function createDoorEventHub() {
9486
+ const sequences = /* @__PURE__ */ new Map();
9487
+ const registered = /* @__PURE__ */ new Set();
9488
+ const every = /* @__PURE__ */ new Set();
9489
+ const bySource = /* @__PURE__ */ new Map();
9490
+ const deliver = (event) => {
9491
+ for (const listener of every) {
9492
+ listener(event);
9493
+ }
9494
+ for (const listener of bySource.get(event.source) ?? []) {
9495
+ listener(event);
9496
+ }
9497
+ };
9498
+ const publish = (source, payload) => {
9499
+ const sequence2 = (sequences.get(source) ?? 0) + 1;
9500
+ sequences.set(source, sequence2);
9501
+ deliver({ source, sequence: sequence2, payload });
9502
+ };
9503
+ const addListener = (sources, listener) => {
9504
+ if (sources === void 0) {
9505
+ every.add(listener);
9506
+ return () => {
9507
+ every.delete(listener);
9508
+ };
9509
+ }
9510
+ const listenersFor = (source) => {
9511
+ const listeners = bySource.get(source) ?? /* @__PURE__ */ new Set();
9512
+ bySource.set(source, listeners);
9513
+ return listeners;
9514
+ };
9515
+ const detachers = sources.map((source) => {
9516
+ const listeners = listenersFor(source);
9517
+ listeners.add(listener);
9518
+ return () => {
9519
+ listeners.delete(listener);
9520
+ if (listeners.size === 0) {
9521
+ bySource.delete(source);
9522
+ }
9523
+ };
9524
+ });
9525
+ return () => {
9526
+ for (const detach of detachers) {
9527
+ detach();
9528
+ }
9529
+ };
9530
+ };
9531
+ return {
9532
+ publisher: (source) => {
9533
+ registered.add(source);
9534
+ return {
9535
+ source,
9536
+ publish: (payload) => {
9537
+ publish(source, payload);
9538
+ }
9539
+ };
9540
+ },
9541
+ subscribe: addListener,
9542
+ stream: (sources) => {
9543
+ const queue = [];
9544
+ let wake;
9545
+ let closed = false;
9546
+ const detach = addListener(sources, (event) => {
9547
+ if (queue.length >= DOOR_EVENT_BUFFER_EVENTS) {
9548
+ queue.shift();
9549
+ }
9550
+ queue.push(event);
9551
+ wake?.();
9552
+ });
9553
+ return {
9554
+ next: async () => {
9555
+ for (; ; ) {
9556
+ const event = queue.shift();
9557
+ if (event !== void 0) {
9558
+ return { value: event, done: false };
9559
+ }
9560
+ if (closed) {
9561
+ return { value: void 0, done: true };
9562
+ }
9563
+ await new Promise((resolve) => {
9564
+ wake = resolve;
9565
+ });
9566
+ }
9567
+ },
9568
+ close: () => {
9569
+ closed = true;
9570
+ detach();
9571
+ wake?.();
9572
+ }
9573
+ };
9574
+ },
9575
+ sources: () => [...registered].sort()
9576
+ };
9577
+ }
9578
+ function rcFanoutOnDoorHub(fanout, hub) {
9579
+ const publisher = hub.publisher(DOOR_EVENT_SOURCE_RC);
9580
+ return {
9581
+ publish: (event) => {
9582
+ fanout.publish(event);
9583
+ publisher.publish(event);
9584
+ },
9585
+ subscribe: (session, listener) => fanout.subscribe(session, listener)
9586
+ };
9587
+ }
9588
+
9589
+ // src/frontdoor/launchEvents.ts
9590
+ function createLaunchEventPublisher(hub, now) {
9591
+ const publisher = hub.publisher(LAUNCH_EVENT_SOURCE);
9592
+ let known;
9593
+ return {
9594
+ observe: (sessions, pruned) => {
9595
+ const previous = known;
9596
+ const listed = new Map(sessions.map((session) => [session.pid, session]));
9597
+ const prunedSet = new Set(pruned);
9598
+ known = new Map([...listed].filter(([pid]) => !prunedSet.has(pid)));
9599
+ if (previous === void 0) {
9600
+ return;
9601
+ }
9602
+ const emit = (kind, pid, startedAt) => {
9603
+ publisher.publish({ kind, pid, startedAt, observedAt: now() });
9604
+ };
9605
+ for (const pid of pruned) {
9606
+ const session = listed.get(pid);
9607
+ if (session !== void 0) {
9608
+ emit("pruned", pid, session.startedAt);
9609
+ }
9610
+ }
9611
+ for (const [pid, session] of previous) {
9612
+ if (!listed.has(pid) && !prunedSet.has(pid)) {
9613
+ emit("ended", pid, session.startedAt);
9614
+ }
9615
+ }
9616
+ for (const [pid, session] of listed) {
9617
+ if (!previous.has(pid) && !prunedSet.has(pid)) {
9618
+ emit("registered", pid, session.startedAt);
9619
+ }
9620
+ }
9621
+ }
9622
+ };
9623
+ }
9624
+
9315
9625
  // src/frontdoor/rcStream.ts
9316
9626
  var RC_STREAM_EVENT_NAME = "client_event";
9317
9627
  var SSE_BYTE_ORDER_MARK = 65279;
@@ -11397,6 +11707,8 @@ export {
11397
11707
  CredentialCacheSchema,
11398
11708
  CredentialSchema,
11399
11709
  CredentialSourceSchema,
11710
+ DOOR_EVENT_BUFFER_EVENTS,
11711
+ DOOR_EVENT_SOURCE_RC,
11400
11712
  DirectoryRuleAlreadyExistsError,
11401
11713
  DirectoryRuleMissingTargetError,
11402
11714
  DirectoryRuleNotFoundError,
@@ -11404,6 +11716,8 @@ export {
11404
11716
  DirectoryRulesSchema,
11405
11717
  DoctorFindingSchema,
11406
11718
  DoctorRunOutputSchema,
11719
+ DoorEventSchema,
11720
+ DoorEventSourceQuerySchema,
11407
11721
  EXIT_FAILURE,
11408
11722
  EXIT_USAGE,
11409
11723
  EffectiveWindowSchema,
@@ -11423,6 +11737,8 @@ export {
11423
11737
  InvalidCategoryNameError,
11424
11738
  InvalidIdentityNameError,
11425
11739
  InvalidProviderNameError,
11740
+ LAUNCH_EVENT_SOURCE,
11741
+ LaunchLifecycleEventSchema,
11426
11742
  LaunchRefusedError,
11427
11743
  LegacyProviderFileError,
11428
11744
  PROMPT_CACHE_TTL_MS,
@@ -11486,6 +11802,9 @@ export {
11486
11802
  createControlApiRouter,
11487
11803
  createDoorApiNodeHandler,
11488
11804
  createDoorApiRouter,
11805
+ createDoorEventHub,
11806
+ createEventsApiRouter,
11807
+ createLaunchEventPublisher,
11489
11808
  createLeafCache,
11490
11809
  createProfile,
11491
11810
  createRcApiNodeHandler,
@@ -11544,6 +11863,7 @@ export {
11544
11863
  providerExists,
11545
11864
  rankPool,
11546
11865
  rcEventWriteResultFromAnswer,
11866
+ rcFanoutOnDoorHub,
11547
11867
  rcObserverAsPassthrough,
11548
11868
  rcSelfHostFromEnv,
11549
11869
  readActiveIdentity,