agent-shim 8.25.0 → 8.27.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
@@ -5074,6 +5074,35 @@ var RcStreamEventSchema = z4.strictObject({
5074
5074
  session: z4.string(),
5075
5075
  envelope: RcStreamEnvelopeSchema
5076
5076
  });
5077
+ var RcRateLimitWindowSchema = z4.looseObject({
5078
+ /** The fraction of the window used, usually between 0 and 1, above 1 when usage legitimately runs past the window's cap (the CLI's own field description states both). */
5079
+ utilization: z4.number().optional(),
5080
+ /** When the window resets, in unix epoch seconds: the unit is the CLI's own field description, stated in the 2.1.289 bundle's schema. */
5081
+ resetsAt: z4.number().optional()
5082
+ });
5083
+ var RcRateLimitInfoSchema = z4.looseObject({
5084
+ /** The limiting window's own status (`allowed`, `allowed_warning`, `rejected`), asserted only as a string because the vocabulary grows with the CLI. */
5085
+ status: z4.string(),
5086
+ /** When the limiting window resets, in unix epoch seconds. */
5087
+ resetsAt: z4.number().optional(),
5088
+ /** Which window the top-level fields describe (`five_hour`, `seven_day`, and kin), the payload's own name for it. */
5089
+ rateLimitType: z4.string().optional(),
5090
+ /** The fraction of the limiting window used, on the same scale the per-window utilizations carry. */
5091
+ utilization: z4.number().optional(),
5092
+ /** Whether usage beyond the plan (extra usage) is currently covering the overflow, the payload's own flag for it. */
5093
+ isUsingOverage: z4.boolean().optional(),
5094
+ /** The overage's own status, the same vocabulary the limiting window's `status` carries. */
5095
+ overageStatus: z4.string().optional(),
5096
+ /** When the overage window resets, in unix epoch seconds. */
5097
+ overageResetsAt: z4.number().optional(),
5098
+ /** Each subscription window the account's response headers carried: the session (five-hour), weekly (seven-day), and overage-included weekly windows, each present only when the account's responses carry it. */
5099
+ unifiedWindows: z4.looseObject({ five_hour: RcRateLimitWindowSchema.optional(), seven_day: RcRateLimitWindowSchema.optional(), seven_day_overage_included: RcRateLimitWindowSchema.optional() }).optional()
5100
+ });
5101
+ var RcLiveRateLimitSchema = z4.strictObject({
5102
+ session: z4.string(),
5103
+ observedAt: z4.number(),
5104
+ rateLimit: RcRateLimitInfoSchema
5105
+ });
5077
5106
  var RcSessionQuerySchema = z4.strictObject({
5078
5107
  session: z4.string().min(1).optional()
5079
5108
  });
@@ -9684,6 +9713,12 @@ var UsageWindowsOutputSchema = z19.strictObject({
9684
9713
  fiveHour: EffectiveWindowSchema.optional(),
9685
9714
  sevenDay: EffectiveWindowSchema.optional()
9686
9715
  });
9716
+ var UsageLiveOutputSchema = z19.strictObject({
9717
+ /** The freshest observation the door holds, never narrowed by the read's session filter because it is the door's single latest statement of the account's quota; absent while no rate_limit_event has been filed at all. */
9718
+ latest: RcLiveRateLimitSchema.optional(),
9719
+ /** One observation per session whose stream has filed a rate_limit_event this door generation. */
9720
+ sessions: z19.readonly(z19.array(RcLiveRateLimitSchema))
9721
+ });
9687
9722
  var FrontDoorSessionStatusSchema = z19.strictObject({
9688
9723
  pid: z19.int().positive(),
9689
9724
  startedAt: z19.number(),
@@ -9798,6 +9833,16 @@ function createControlApiRouter(deps) {
9798
9833
  ...unified?.fiveHour === void 0 ? {} : { fiveHour: effectiveWindow(unified.fiveHour, now) },
9799
9834
  ...unified?.sevenDay === void 0 ? {} : { sevenDay: effectiveWindow(unified.sevenDay, now) }
9800
9835
  };
9836
+ }),
9837
+ live: authed.input(RcSessionQuerySchema).output(UsageLiveOutputSchema).handler(({ input }) => {
9838
+ const sessions = deps.liveRateLimits(input.session);
9839
+ if (input.session !== void 0 && sessions.length === 0) {
9840
+ if (deps.list().some((session) => session.id === input.session)) {
9841
+ throw new ORPCError3("NOT_FOUND", { message: `session ${input.session} has filed no rate_limit_event on its stream yet: the worker emits one only once a turn has completed, so retry after this session's next turn` });
9842
+ }
9843
+ throw new ORPCError3("NOT_FOUND", { message: rcSessionNotObservedMessage(input.session) });
9844
+ }
9845
+ return { latest: deps.latestRateLimit(), sessions };
9801
9846
  })
9802
9847
  },
9803
9848
  frontdoor: {
@@ -10018,6 +10063,42 @@ function createLaunchEventPublisher(hub, now) {
10018
10063
  };
10019
10064
  }
10020
10065
 
10066
+ // src/frontdoor/rcLiveUsage.ts
10067
+ var RC_RATE_LIMIT_EVENT_TYPE = "rate_limit_event";
10068
+ function isRecord5(value) {
10069
+ return typeof value === "object" && value !== null && !Array.isArray(value);
10070
+ }
10071
+ function rcRateLimitInfoOf(event) {
10072
+ const payload = event.envelope.payload;
10073
+ if (!isRecord5(payload) || payload.type !== RC_RATE_LIMIT_EVENT_TYPE) {
10074
+ return void 0;
10075
+ }
10076
+ if (!isRecord5(payload.rate_limit_info)) {
10077
+ return void 0;
10078
+ }
10079
+ const parsed = RcRateLimitInfoSchema.safeParse(payload.rate_limit_info);
10080
+ return parsed.success ? parsed.data : void 0;
10081
+ }
10082
+ function createRcLiveUsage(deps) {
10083
+ const observed = /* @__PURE__ */ new Map();
10084
+ deps.hub.subscribe([DOOR_EVENT_SOURCE_RC], (event) => {
10085
+ const stream = RcStreamEventSchema.safeParse(event.payload);
10086
+ if (!stream.success) {
10087
+ return;
10088
+ }
10089
+ const rateLimit = rcRateLimitInfoOf(stream.data);
10090
+ if (rateLimit === void 0) {
10091
+ return;
10092
+ }
10093
+ observed.set(stream.data.session, { session: stream.data.session, observedAt: deps.now(), rateLimit });
10094
+ });
10095
+ const sorted = () => [...observed.values()].sort((left, right) => left.observedAt - right.observedAt || (left.session < right.session ? -1 : 1));
10096
+ return {
10097
+ liveOf: (sessionId) => sorted().filter((entry) => sessionId === void 0 || entry.session === sessionId),
10098
+ latestOf: () => sorted().at(-1)
10099
+ };
10100
+ }
10101
+
10021
10102
  // src/frontdoor/rcStream.ts
10022
10103
  var RC_STREAM_EVENT_NAME = "client_event";
10023
10104
  var SSE_BYTE_ORDER_MARK = 65279;
@@ -10080,7 +10161,7 @@ function createSseParser() {
10080
10161
  }
10081
10162
  };
10082
10163
  }
10083
- function isRecord5(value) {
10164
+ function isRecord6(value) {
10084
10165
  return typeof value === "object" && value !== null && !Array.isArray(value);
10085
10166
  }
10086
10167
  function sequenceNumOf(value) {
@@ -10102,7 +10183,7 @@ function parseRcStreamEnvelope(parsed) {
10102
10183
  } catch {
10103
10184
  return void 0;
10104
10185
  }
10105
- if (!isRecord5(data)) {
10186
+ if (!isRecord6(data)) {
10106
10187
  return void 0;
10107
10188
  }
10108
10189
  const eventType = data.event_type;
@@ -10333,7 +10414,7 @@ function createRcStreamHub(deps) {
10333
10414
  // src/frontdoor/rcSelfHostEndpoints.ts
10334
10415
  var RC_SOURCE_WORKER = "worker";
10335
10416
  var RC_SOURCE_CLIENT = "client";
10336
- function isRecord6(value) {
10417
+ function isRecord7(value) {
10337
10418
  return typeof value === "object" && value !== null && !Array.isArray(value);
10338
10419
  }
10339
10420
  function parseJsonObject2(body) {
@@ -10343,7 +10424,7 @@ function parseJsonObject2(body) {
10343
10424
  } catch {
10344
10425
  return void 0;
10345
10426
  }
10346
- return isRecord6(parsed) ? parsed : void 0;
10427
+ return isRecord7(parsed) ? parsed : void 0;
10347
10428
  }
10348
10429
  function nonEmptyString2(record, field) {
10349
10430
  const value = record === void 0 ? void 0 : record[field];
@@ -10376,7 +10457,7 @@ function pageOfRcChannel(url, rows, idOf, rowOf) {
10376
10457
  return { data: page.map(rowOf), ...last === void 0 ? {} : { next_cursor: idOf(last) } };
10377
10458
  }
10378
10459
  async function serveRcClientChannels(ctx, tail, method, maxBatchEvents) {
10379
- const { request: request4, response, session } = ctx;
10460
+ const { request: request4, response, host } = ctx;
10380
10461
  if (tail === "client/presence" && method === "POST") {
10381
10462
  const body = parseJsonObject2(await ctx.readBody(request4.body, CONTROL_BODY_CAP_BYTES));
10382
10463
  const clientId = nonEmptyString2(body, "client_id");
@@ -10386,11 +10467,11 @@ async function serveRcClientChannels(ctx, tail, method, maxBatchEvents) {
10386
10467
  return true;
10387
10468
  }
10388
10469
  if (clear === true) {
10389
- session.clients.delete(clientId);
10390
- ctx.log?.(`rc selfhost ${session.id}: client ${clientId} cleared its presence`);
10470
+ host.clients.delete(clientId);
10471
+ ctx.log?.(`rc selfhost ${host.id}: client ${clientId} cleared its presence`);
10391
10472
  } else {
10392
- session.clients.set(clientId, ctx.now());
10393
- ctx.log?.(`rc selfhost ${session.id}: client ${clientId} announced its presence${session.clients.size === 1 ? "" : ` (${String(session.clients.size)} clients now announced)`}`);
10473
+ host.clients.set(clientId, ctx.now());
10474
+ ctx.log?.(`rc selfhost ${host.id}: client ${clientId} announced its presence${host.clients.size === 1 ? "" : ` (${String(host.clients.size)} clients now announced)`}`);
10394
10475
  }
10395
10476
  await ctx.answerJson(response, HTTP_STATUS.ok, { refresh_after_seconds: ctx.presenceRefreshSeconds });
10396
10477
  return true;
@@ -10403,8 +10484,8 @@ async function serveRcClientChannels(ctx, tail, method, maxBatchEvents) {
10403
10484
  await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a read receipt is JSON naming the event_id it read or none for the whole session, and client_id only as a non-empty string or absent" } });
10404
10485
  return true;
10405
10486
  }
10406
- const receipt = ctx.publish(session, clientId ?? RC_SOURCE_CLIENT, { type: "mark_read", ...eventId === void 0 ? {} : { event_id: eventId }, ...clientId === void 0 ? {} : { client_id: clientId } }, true);
10407
- ctx.log?.(`rc selfhost ${session.id}: ${clientId === void 0 ? "an unnamed client" : `client ${clientId}`} marked ${eventId === void 0 ? "the session read" : `event ${eventId} read`} as sequence ${String(receipt.sequenceNum)}`);
10487
+ const receipt = ctx.publish(host.conversation, host.writer, clientId ?? RC_SOURCE_CLIENT, { type: "mark_read", ...eventId === void 0 ? {} : { event_id: eventId }, ...clientId === void 0 ? {} : { client_id: clientId } });
10488
+ ctx.log?.(`rc selfhost ${host.id}: ${clientId === void 0 ? "an unnamed client" : `client ${clientId}`} marked ${eventId === void 0 ? "the session read" : `event ${eventId} read`} as sequence ${String(receipt.sequenceNum)}`);
10408
10489
  await ctx.answerJson(response, HTTP_STATUS.ok, {});
10409
10490
  return true;
10410
10491
  }
@@ -10417,20 +10498,20 @@ async function serveRcClientChannels(ctx, tail, method, maxBatchEvents) {
10417
10498
  }
10418
10499
  const results = [];
10419
10500
  for (const event of events) {
10420
- if (!isRecord6(event) || !isRecord6(event.payload)) {
10501
+ if (!isRecord7(event) || !isRecord7(event.payload)) {
10421
10502
  await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "every event in a teleport write is an object carrying one payload object" } });
10422
10503
  return true;
10423
10504
  }
10424
- const published = ctx.publish(session, RC_SOURCE_CLIENT, event.payload);
10505
+ const published = ctx.publish(host.conversation, host.writer, RC_SOURCE_CLIENT, event.payload);
10425
10506
  results.push({ sequence_num: String(published.sequenceNum), duplicate: published.duplicate });
10426
10507
  }
10427
10508
  ctx.sweep();
10428
- ctx.log?.(`rc selfhost ${session.id}: teleport write published ${String(results.length)} marker event(s)`);
10509
+ ctx.log?.(`rc selfhost ${host.id}: teleport write published ${String(results.length)} marker event(s)`);
10429
10510
  await ctx.answerJson(response, HTTP_STATUS.ok, { results });
10430
10511
  return true;
10431
10512
  }
10432
10513
  if (tail === "teleport-events" && method === "GET") {
10433
- const page = pageOfRcChannel(request4.url, session.events, (event) => event.eventId, (event) => ({ event_id: event.eventId, payload: event.payload }));
10514
+ const page = pageOfRcChannel(request4.url, host.conversation.events, (event) => event.eventId, (event) => ({ event_id: event.eventId, payload: event.payload }));
10434
10515
  if ("error" in page) {
10435
10516
  await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: page.error } });
10436
10517
  return true;
@@ -10506,10 +10587,7 @@ async function serveRcCompatSessions(ctx, pathname, compatPath) {
10506
10587
  await ctx.answerJson(response, HTTP_STATUS.ok, compatSessionRow(session, ctx.now));
10507
10588
  return;
10508
10589
  }
10509
- for (const sink of [...session.workerStreams, ...session.clientStreams]) {
10510
- sink.retire();
10511
- }
10512
- ctx.sessions.delete(session.id);
10590
+ ctx.retireSession(session);
10513
10591
  ctx.log?.(`rc selfhost ${session.id}: archived through the compatibility family`);
10514
10592
  await ctx.answerJson(response, HTTP_STATUS.ok, {});
10515
10593
  return;
@@ -10517,6 +10595,202 @@ async function serveRcCompatSessions(ctx, pathname, compatPath) {
10517
10595
  await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the compatibility session family serves no path "${compatTail}"` } });
10518
10596
  }
10519
10597
 
10598
+ // src/frontdoor/rcSelfHostConversations.ts
10599
+ var RC_CONVERSATIONS_PATH_PREFIX = "/v1/code/conversations";
10600
+ var RC_CONVERSATION_ID_PREFIX = "conv_";
10601
+ function isRecord8(value) {
10602
+ return typeof value === "object" && value !== null && !Array.isArray(value);
10603
+ }
10604
+ function parseJsonObject3(body) {
10605
+ let parsed;
10606
+ try {
10607
+ parsed = JSON.parse(body);
10608
+ } catch {
10609
+ return void 0;
10610
+ }
10611
+ return isRecord8(parsed) ? parsed : void 0;
10612
+ }
10613
+ function nonEmptyString3(record, field) {
10614
+ const value = record === void 0 ? void 0 : record[field];
10615
+ return typeof value === "string" && value !== "" ? value : void 0;
10616
+ }
10617
+ function newRcSelfHostConversation(newUuid, now) {
10618
+ return { id: `${RC_CONVERSATION_ID_PREFIX}${newUuid()}`, createdAt: now(), sequenceNum: 0, events: [], seenPayloadUuids: /* @__PURE__ */ new Set(), workerStreams: /* @__PURE__ */ new Set(), clientStreams: /* @__PURE__ */ new Set(), clients: /* @__PURE__ */ new Map(), sessionIds: /* @__PURE__ */ new Set(), lastTrafficAt: now() };
10619
+ }
10620
+ function conversationRow(conversation) {
10621
+ return { id: conversation.id, created_at: new Date(conversation.createdAt).toISOString(), updated_at: new Date(conversation.lastTrafficAt).toISOString(), sessions: [...conversation.sessionIds] };
10622
+ }
10623
+ async function serveRcConversations(ctx, pathname, method) {
10624
+ const { request: request4, response, conversations } = ctx;
10625
+ const refusal = ctx.credentialRefusal(request4.headers);
10626
+ if (refusal !== void 0) {
10627
+ await ctx.unauthorized(response, refusal);
10628
+ return;
10629
+ }
10630
+ if (pathname === RC_CONVERSATIONS_PATH_PREFIX) {
10631
+ if (method !== "GET") {
10632
+ await ctx.answerJson(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the conversation collection is a GET; a conversation is born from a session create, never here" } });
10633
+ return;
10634
+ }
10635
+ await ctx.answerJson(response, HTTP_STATUS.ok, { data: [...conversations.values()].sort((left, right) => left.createdAt - right.createdAt || (left.id < right.id ? -1 : 1)).map(conversationRow) });
10636
+ return;
10637
+ }
10638
+ const rest = pathname.slice(`${RC_CONVERSATIONS_PATH_PREFIX}/`.length);
10639
+ const slash = rest.indexOf("/");
10640
+ const conversationId = slash === -1 ? rest : rest.slice(0, slash);
10641
+ const tail = slash === -1 ? "" : rest.slice(slash + 1);
10642
+ if (!conversationId.startsWith(RC_CONVERSATION_ID_PREFIX)) {
10643
+ await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: "the conversation family names one conv_-prefixed conversation id" } });
10644
+ return;
10645
+ }
10646
+ const conversation = conversations.get(conversationId);
10647
+ if (conversation === void 0) {
10648
+ await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: "no such Remote Control conversation on this door" } });
10649
+ return;
10650
+ }
10651
+ conversation.lastTrafficAt = ctx.now();
10652
+ if (tail === "" && method === "GET") {
10653
+ await ctx.answerJson(response, HTTP_STATUS.ok, { conversation: conversationRow(conversation) });
10654
+ return;
10655
+ }
10656
+ if (tail === "events" && method === "POST") {
10657
+ const body = parseJsonObject3(await ctx.readBody(request4.body, CONTROL_BODY_CAP_BYTES));
10658
+ const events = body?.events;
10659
+ if (!Array.isArray(events) || events.length === 0 || events.length > ctx.maxBatchEvents) {
10660
+ await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: `a client event write is a non-empty array of at most ${String(ctx.maxBatchEvents)} events` } });
10661
+ return;
10662
+ }
10663
+ const results = [];
10664
+ for (const event of events) {
10665
+ if (!isRecord8(event) || !isRecord8(event.payload)) {
10666
+ await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "every event in a write is an object carrying one payload object" } });
10667
+ return;
10668
+ }
10669
+ const published = ctx.publish(conversation, { session: void 0, half: "client" }, RC_SOURCE_CLIENT, event.payload);
10670
+ results.push({ sequence_num: String(published.sequenceNum), duplicate: published.duplicate });
10671
+ }
10672
+ ctx.sweep();
10673
+ ctx.log?.(`rc selfhost ${conversation.id}: a conversation client wrote ${String(results.length)} event(s) into the shared space`);
10674
+ await ctx.answerJson(response, HTTP_STATUS.ok, { results });
10675
+ return;
10676
+ }
10677
+ if (tail === "events" && method === "GET") {
10678
+ await ctx.answerJson(response, HTTP_STATUS.ok, { data: conversation.events.map((event) => ({ event_id: event.eventId, event_type: nonEmptyString3(event.payload, "type") ?? "event", sequence_num: event.sequenceNum, source: event.source, payload: event.payload, created_at: event.createdAt })) });
10679
+ return;
10680
+ }
10681
+ if (tail === "events/stream" && method === "GET") {
10682
+ await ctx.holdStream(conversation, conversation.id, "clientStreams", () => true, request4, response);
10683
+ return;
10684
+ }
10685
+ if (await serveRcClientChannels({ request: request4, response, host: { id: conversation.id, clients: conversation.clients, conversation, writer: { session: void 0, half: "client" } }, answerJson: ctx.answerJson, readBody: ctx.readBody, publish: ctx.publish, sweep: ctx.sweep, now: ctx.now, presenceRefreshSeconds: ctx.presenceRefreshSeconds, ...ctx.log === void 0 ? {} : { log: ctx.log } }, tail, method, ctx.maxBatchEvents)) {
10686
+ return;
10687
+ }
10688
+ await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the conversation family serves no path "${tail}"` } });
10689
+ }
10690
+
10691
+ // src/frontdoor/rcSelfHostLocal.ts
10692
+ var RC_SELF_HOST_OAUTH_HOST = "platform.claude.com";
10693
+ var RC_SELF_HOST_SCOPE_LIST = ["user:profile", "user:inference", "user:sessions:claude_code", "user:mcp_servers", "user:file_upload"];
10694
+ var RC_SELF_HOST_OAUTH_TOKEN_TTL_SECONDS = 3600;
10695
+ var MS_PER_SECOND5 = 1e3;
10696
+ var HTTP_SERVICE_UNAVAILABLE = 503;
10697
+ function isRecord9(value) {
10698
+ return typeof value === "object" && value !== null && !Array.isArray(value);
10699
+ }
10700
+ function parseJsonObject4(body) {
10701
+ let parsed;
10702
+ try {
10703
+ parsed = JSON.parse(body);
10704
+ } catch {
10705
+ return void 0;
10706
+ }
10707
+ return isRecord9(parsed) ? parsed : void 0;
10708
+ }
10709
+ function growthBookFeatures() {
10710
+ return {
10711
+ tengu_ccr_bridge: { defaultValue: true },
10712
+ tengu_bridge_repl_v2: { defaultValue: true },
10713
+ tengu_bridge_repl_v2_cse_shim_enabled: { defaultValue: true },
10714
+ tengu_bridge_min_version: { defaultValue: { minVersion: "0.0.0" } }
10715
+ };
10716
+ }
10717
+ var answerServerJson = (response, status, body) => {
10718
+ const text = JSON.stringify(body);
10719
+ response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text, "utf8")) });
10720
+ response.end(text);
10721
+ };
10722
+ function createRcSelfHostLocal(deps) {
10723
+ return {
10724
+ parsesHost: (host) => host === RC_SELF_HOST_OAUTH_HOST,
10725
+ serve: async (host, request4, response) => {
10726
+ const method = (request4.method ?? "").toUpperCase();
10727
+ const pathname = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
10728
+ if (host === RC_SELF_HOST_OAUTH_HOST) {
10729
+ if (method === "POST" && pathname === "/v1/oauth/token") {
10730
+ const body = parseJsonObject4(await deps.readBody(request4, CONTROL_BODY_CAP_BYTES));
10731
+ const record = deps.credentialRecord();
10732
+ if (record === void 0) {
10733
+ answerServerJson(response, HTTP_SERVICE_UNAVAILABLE, { error: "the front door's self-hosted Remote Control surface is on but no credential has been minted for it" });
10734
+ return true;
10735
+ }
10736
+ if (body?.grant_type !== void 0 && body.grant_type !== "refresh_token") {
10737
+ answerServerJson(response, HTTP_STATUS.badRequest, { error: "unsupported_grant_type" });
10738
+ return true;
10739
+ }
10740
+ answerServerJson(response, HTTP_STATUS.ok, { access_token: record.accessToken, refresh_token: record.refreshToken, expires_in: RC_SELF_HOST_OAUTH_TOKEN_TTL_SECONDS, scope: RC_SELF_HOST_SCOPE_LIST.join(" ") });
10741
+ return true;
10742
+ }
10743
+ if (method === "GET" && pathname === "/v1/oauth/hello") {
10744
+ answerServerJson(response, HTTP_STATUS.ok, {});
10745
+ return true;
10746
+ }
10747
+ return false;
10748
+ }
10749
+ if (method === "POST" && pathname.startsWith("/api/eval/")) {
10750
+ await deps.readBody(request4, CONTROL_BODY_CAP_BYTES);
10751
+ answerServerJson(response, HTTP_STATUS.ok, { features: growthBookFeatures(), dateUpdated: Math.floor(deps.now() / MS_PER_SECOND5) });
10752
+ return true;
10753
+ }
10754
+ if (method === "GET" && pathname === "/api/oauth/profile") {
10755
+ const record = deps.credentialRecord();
10756
+ if (record === void 0) {
10757
+ answerServerJson(response, HTTP_SERVICE_UNAVAILABLE, { error: "the front door's self-hosted Remote Control surface is on but no credential has been minted for it" });
10758
+ return true;
10759
+ }
10760
+ answerServerJson(response, HTTP_STATUS.ok, {
10761
+ account: { uuid: record.accountUuid, display_name: "agent-shim self-hosted", created_at: (/* @__PURE__ */ new Date(0)).toISOString() },
10762
+ organization: { uuid: record.organizationUuid, organization_type: "claude_max" }
10763
+ });
10764
+ return true;
10765
+ }
10766
+ if (method === "GET" && pathname === "/api/claude_code/policy_limits") {
10767
+ answerServerJson(response, HTTP_STATUS.ok, {});
10768
+ return true;
10769
+ }
10770
+ if (method === "POST" && pathname === "/api/oauth/validate") {
10771
+ await deps.readBody(request4, CONTROL_BODY_CAP_BYTES);
10772
+ const record = deps.credentialRecord();
10773
+ if (record === void 0) {
10774
+ answerServerJson(response, HTTP_SERVICE_UNAVAILABLE, { error: "the front door's self-hosted Remote Control surface is on but no credential has been minted for it" });
10775
+ return true;
10776
+ }
10777
+ answerServerJson(response, HTTP_STATUS.ok, { scopes: [...RC_SELF_HOST_SCOPE_LIST], expiresAt: null, subscriptionType: "max", account_uuid: record.accountUuid, organization_uuid: record.organizationUuid });
10778
+ return true;
10779
+ }
10780
+ if (method === "POST" && (pathname === "/api/event_logging/v2/batch" || pathname === "/api/claude_code/metrics" || pathname === "/api/claude_cli_feedback")) {
10781
+ await deps.readBody(request4, CONTROL_BODY_CAP_BYTES);
10782
+ answerServerJson(response, HTTP_STATUS.ok, {});
10783
+ return true;
10784
+ }
10785
+ if (method === "GET" && pathname === "/api/hello") {
10786
+ answerServerJson(response, HTTP_STATUS.ok, {});
10787
+ return true;
10788
+ }
10789
+ return false;
10790
+ }
10791
+ };
10792
+ }
10793
+
10520
10794
  // src/frontdoor/rcWebFetch.ts
10521
10795
  import * as dns2 from "node:dns";
10522
10796
  import * as http2 from "node:http";
@@ -10768,17 +11042,14 @@ var RC_SELF_HOST_ENV = "AGENT_SHIM_FRONTDOOR_RC_SELF_HOST";
10768
11042
  function rcSelfHostFromEnv(env) {
10769
11043
  return env[RC_SELF_HOST_ENV] === "1";
10770
11044
  }
10771
- var RC_SELF_HOST_SCOPE_LIST = ["user:profile", "user:inference", "user:sessions:claude_code", "user:mcp_servers", "user:file_upload"];
10772
11045
  var RC_SESSIONS_PATH_PREFIX2 = "/v1/code/sessions";
10773
11046
  var RC_COMPAT_SESSIONS_PATH = "/v1/sessions";
10774
11047
  var RC_SESSION_ID_PREFIX2 = "cse_";
10775
11048
  var RC_SELF_HOST_API_BASE_URL = "https://api.anthropic.com";
10776
- var RC_SELF_HOST_OAUTH_HOST = "platform.claude.com";
10777
11049
  var HTTP_CONFLICT = 409;
10778
- var MS_PER_SECOND5 = 1e3;
10779
- var HTTP_SERVICE_UNAVAILABLE = 503;
11050
+ var MS_PER_SECOND6 = 1e3;
11051
+ var HTTP_SERVICE_UNAVAILABLE2 = 503;
10780
11052
  var RC_SELF_HOST_WORKER_JWT_TTL_SECONDS = 46800;
10781
- var RC_SELF_HOST_OAUTH_TOKEN_TTL_SECONDS = 3600;
10782
11053
  var RC_SELF_HOST_KEEPALIVE_MS = 15e3;
10783
11054
  var RC_SELF_HOST_RETENTION_MS = 6e5;
10784
11055
  var RC_PRESENCE_REFRESH_SECONDS = 60;
@@ -10787,13 +11058,13 @@ var RC_SELF_HOST_WORKER_EPOCH = 1;
10787
11058
  var RC_STREAM_EVENT_NAME2 = "client_event";
10788
11059
  var CONFLICT_REASON_HEADER2 = "x-ccr-conflict-reason";
10789
11060
  var CONFLICT_REASON_EPOCH_STALE = "epoch_stale";
10790
- function isRecord7(value) {
11061
+ function isRecord10(value) {
10791
11062
  return typeof value === "object" && value !== null && !Array.isArray(value);
10792
11063
  }
10793
11064
  function isWebSearchRefusal(value) {
10794
11065
  return !Array.isArray(value);
10795
11066
  }
10796
- function nonEmptyString3(record, field) {
11067
+ function nonEmptyString4(record, field) {
10797
11068
  const value = record === void 0 ? void 0 : record[field];
10798
11069
  return typeof value === "string" && value !== "" ? value : void 0;
10799
11070
  }
@@ -10864,56 +11135,69 @@ async function readBody2(request4, capBytes) {
10864
11135
  });
10865
11136
  });
10866
11137
  }
10867
- function parseJsonObject3(body) {
11138
+ function parseJsonObject5(body) {
10868
11139
  let parsed;
10869
11140
  try {
10870
11141
  parsed = JSON.parse(body);
10871
11142
  } catch {
10872
11143
  return void 0;
10873
11144
  }
10874
- return isRecord7(parsed) ? parsed : void 0;
11145
+ return isRecord10(parsed) ? parsed : void 0;
10875
11146
  }
10876
11147
  function mintWorkerJwt(randomToken, issuedAt) {
10877
11148
  const encode = (value) => Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
10878
11149
  const header = encode({ alg: "HS256", typ: "JWT" });
10879
- const payload = encode({ exp: Math.floor((issuedAt + RC_SELF_HOST_WORKER_JWT_TTL_SECONDS * MS_PER_SECOND5) / MS_PER_SECOND5), iat: Math.floor(issuedAt / MS_PER_SECOND5) });
11150
+ const payload = encode({ exp: Math.floor((issuedAt + RC_SELF_HOST_WORKER_JWT_TTL_SECONDS * MS_PER_SECOND6) / MS_PER_SECOND6), iat: Math.floor(issuedAt / MS_PER_SECOND6) });
10880
11151
  return `${header}.${payload}.${Buffer.from(randomToken(), "utf8").toString("base64url")}`;
10881
11152
  }
10882
- function growthBookFeatures() {
10883
- return {
10884
- tengu_ccr_bridge: { defaultValue: true },
10885
- tengu_bridge_repl_v2: { defaultValue: true },
10886
- tengu_bridge_repl_v2_cse_shim_enabled: { defaultValue: true },
10887
- tengu_bridge_min_version: { defaultValue: { minVersion: "0.0.0" } }
10888
- };
10889
- }
10890
11153
  function createRcSelfHostSurface(deps) {
10891
11154
  const sessions = /* @__PURE__ */ new Map();
11155
+ const conversations = /* @__PURE__ */ new Map();
11156
+ const retireSession = (session) => {
11157
+ for (const sink of [...session.conversation.workerStreams, ...session.conversation.clientStreams]) {
11158
+ if (sink.owner === session.id) {
11159
+ sink.retire();
11160
+ }
11161
+ }
11162
+ session.conversation.sessionIds.delete(session.id);
11163
+ sessions.delete(session.id);
11164
+ };
10892
11165
  const sweep = () => {
10893
11166
  const now = deps.now();
10894
11167
  for (const session of sessions.values()) {
10895
- while (session.events[0] !== void 0 && now - session.events[0].storedAt > RC_SELF_HOST_RETENTION_MS) {
10896
- const dropped = session.events.shift();
10897
- const uuid = dropped === void 0 ? void 0 : nonEmptyString3(dropped.payload, "uuid");
11168
+ for (const [clientId, lastSeenAt] of session.clients) {
11169
+ if (now - lastSeenAt > RC_SELF_HOST_RETENTION_MS) {
11170
+ session.clients.delete(clientId);
11171
+ }
11172
+ }
11173
+ const holdsStream = [...session.conversation.workerStreams, ...session.conversation.clientStreams].some((sink) => sink.owner === session.id);
11174
+ if (!holdsStream && now - session.lastTrafficAt > RC_SELF_HOST_RETENTION_MS) {
11175
+ retireSession(session);
11176
+ }
11177
+ }
11178
+ for (const conversation of conversations.values()) {
11179
+ while (conversation.events[0] !== void 0 && now - conversation.events[0].storedAt > RC_SELF_HOST_RETENTION_MS) {
11180
+ const dropped = conversation.events.shift();
11181
+ const uuid = dropped === void 0 ? void 0 : nonEmptyString4(dropped.payload, "uuid");
10898
11182
  if (uuid !== void 0) {
10899
- session.seenPayloadUuids.delete(uuid);
11183
+ conversation.seenPayloadUuids.delete(uuid);
10900
11184
  }
10901
11185
  }
10902
- for (const [clientId, lastSeenAt] of session.clients) {
11186
+ for (const [clientId, lastSeenAt] of conversation.clients) {
10903
11187
  if (now - lastSeenAt > RC_SELF_HOST_RETENTION_MS) {
10904
- session.clients.delete(clientId);
11188
+ conversation.clients.delete(clientId);
10905
11189
  }
10906
11190
  }
10907
- if (session.workerStreams.size === 0 && session.clientStreams.size === 0 && now - session.lastTrafficAt > RC_SELF_HOST_RETENTION_MS) {
10908
- sessions.delete(session.id);
11191
+ if (conversation.sessionIds.size === 0 && conversation.workerStreams.size === 0 && conversation.clientStreams.size === 0 && now - conversation.lastTrafficAt > RC_SELF_HOST_RETENTION_MS) {
11192
+ conversations.delete(conversation.id);
10909
11193
  }
10910
11194
  }
10911
11195
  };
10912
11196
  const timer = setInterval(sweep, RC_SELF_HOST_RETENTION_MS);
10913
11197
  timer.unref();
10914
11198
  const keepalive = setInterval(() => {
10915
- for (const session of sessions.values()) {
10916
- for (const sink of [...session.workerStreams, ...session.clientStreams]) {
11199
+ for (const conversation of conversations.values()) {
11200
+ for (const sink of [...conversation.workerStreams, ...conversation.clientStreams]) {
10917
11201
  sink.write(": keep-alive\n\n");
10918
11202
  }
10919
11203
  }
@@ -10942,7 +11226,7 @@ function createRcSelfHostSurface(deps) {
10942
11226
  const frameOf = (event) => {
10943
11227
  const envelope = {
10944
11228
  event_id: event.eventId,
10945
- event_type: nonEmptyString3(event.payload, "type") ?? "event",
11229
+ event_type: nonEmptyString4(event.payload, "type") ?? "event",
10946
11230
  sequence_num: event.sequenceNum,
10947
11231
  source: event.source,
10948
11232
  payload: event.payload,
@@ -10954,14 +11238,16 @@ data: ${JSON.stringify(envelope)}
10954
11238
 
10955
11239
  `;
10956
11240
  };
10957
- const holdStream = async (session, which, wantsReplay, request4, response) => {
11241
+ const reachesWorkerStream = (event, owner) => !(event.writer.half === "worker" && event.writer.session === owner);
11242
+ const holdStream = async (conversation, owner, which, wantsReplay, request4, response) => {
10958
11243
  const url = new URL(request4.url, "http://127.0.0.1");
10959
11244
  const cursor = numberOf2({ value: url.searchParams.get("from_sequence_num") }, "value") ?? numberOf2({ value: singleHeader2(request4.headers, "last-event-id") }, "value");
10960
- const replay = cursor === void 0 ? [] : session.events.filter((event) => wantsReplay(event) && event.sequenceNum > cursor);
11245
+ const replay = cursor === void 0 ? [] : conversation.events.filter((event) => wantsReplay(event) && event.sequenceNum > cursor);
10961
11246
  response.start(HTTP_STATUS.ok, { "content-type": "text/event-stream", "cache-control": "no-cache" });
10962
11247
  response.flush();
10963
11248
  let pending = Promise.resolve();
10964
11249
  const sink = {
11250
+ owner,
10965
11251
  write: (frame) => {
10966
11252
  pending = pending.then(async () => {
10967
11253
  await response.write(frame);
@@ -10969,11 +11255,11 @@ data: ${JSON.stringify(envelope)}
10969
11255
  });
10970
11256
  },
10971
11257
  retire: () => {
10972
- session[which].delete(sink);
11258
+ conversation[which].delete(sink);
10973
11259
  }
10974
11260
  };
10975
- session[which].add(sink);
10976
- session.lastTrafficAt = deps.now();
11261
+ conversation[which].add(sink);
11262
+ conversation.lastTrafficAt = deps.now();
10977
11263
  for (const event of replay) {
10978
11264
  sink.write(frameOf(event));
10979
11265
  }
@@ -10985,36 +11271,37 @@ data: ${JSON.stringify(envelope)}
10985
11271
  sink.retire();
10986
11272
  response.end();
10987
11273
  };
10988
- const publish = (session, source, payload, toWorker = source === RC_SOURCE_CLIENT) => {
10989
- const uuid = nonEmptyString3(payload, "uuid");
10990
- const duplicate = uuid !== void 0 && session.seenPayloadUuids.has(uuid);
10991
- session.sequenceNum += 1;
11274
+ const publish = (conversation, writer, source, payload) => {
11275
+ const uuid = nonEmptyString4(payload, "uuid");
11276
+ const duplicate = uuid !== void 0 && conversation.seenPayloadUuids.has(uuid);
11277
+ conversation.sequenceNum += 1;
10992
11278
  const event = {
10993
- sequenceNum: session.sequenceNum,
11279
+ sequenceNum: conversation.sequenceNum,
10994
11280
  eventId: uuid ?? deps.newUuid(),
10995
11281
  source,
10996
11282
  // The one enrichment the real service was observed making on the live rig's stream: the wall-clock instant it took the write. The worker's handler ignores it; the door's own parser forwards it verbatim.
10997
11283
  payload: source === RC_SOURCE_WORKER ? payload : { ...payload, server_received_wall_ms: deps.now() },
10998
11284
  createdAt: new Date(deps.now()).toISOString(),
10999
- toWorker,
11285
+ writer,
11000
11286
  storedAt: deps.now()
11001
11287
  };
11002
- session.events.push(event);
11288
+ conversation.events.push(event);
11003
11289
  if (uuid !== void 0) {
11004
- session.seenPayloadUuids.add(uuid);
11290
+ conversation.seenPayloadUuids.add(uuid);
11005
11291
  }
11006
- session.lastTrafficAt = event.storedAt;
11292
+ conversation.lastTrafficAt = event.storedAt;
11007
11293
  const frame = frameOf(event);
11008
- if (event.toWorker) {
11009
- for (const sink of [...session.workerStreams]) {
11294
+ for (const sink of [...conversation.workerStreams]) {
11295
+ if (reachesWorkerStream(event, sink.owner)) {
11010
11296
  sink.write(frame);
11011
11297
  }
11012
11298
  }
11013
- for (const sink of [...session.clientStreams]) {
11299
+ for (const sink of [...conversation.clientStreams]) {
11014
11300
  sink.write(frame);
11015
11301
  }
11016
11302
  return { sequenceNum: event.sequenceNum, eventId: event.eventId, duplicate };
11017
11303
  };
11304
+ const sessionRow = (session) => ({ id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), status: "active", conversation_id: session.conversation.id });
11018
11305
  const answerJson2 = async (response, status, body, extraHeaders = {}) => {
11019
11306
  const text = JSON.stringify(body);
11020
11307
  response.start(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text, "utf8")), ...extraHeaders });
@@ -11022,7 +11309,7 @@ data: ${JSON.stringify(envelope)}
11022
11309
  response.end();
11023
11310
  };
11024
11311
  const noCredential = async (response) => {
11025
- await answerJson2(response, HTTP_SERVICE_UNAVAILABLE, { type: "error", error: { type: "api_error", message: "the front door's self-hosted Remote Control surface is on but no credential has been minted for it; run agent-shim frontdoor rc selfhost mint first" } });
11312
+ await answerJson2(response, HTTP_SERVICE_UNAVAILABLE2, { type: "error", error: { type: "api_error", message: "the front door's self-hosted Remote Control surface is on but no credential has been minted for it; run agent-shim frontdoor rc selfhost mint first" } });
11026
11313
  };
11027
11314
  const epochStale = async (response) => {
11028
11315
  await answerJson2(response, HTTP_CONFLICT, { type: "error", error: { type: "api_error", message: "worker_epoch does not match the session's live epoch" } }, { [CONFLICT_REASON_HEADER2]: CONFLICT_REASON_EPOCH_STALE });
@@ -11050,8 +11337,8 @@ data: ${JSON.stringify(envelope)}
11050
11337
  await unauthorized(response, refusal);
11051
11338
  return;
11052
11339
  }
11053
- const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11054
- const title = nonEmptyString3(body, "title");
11340
+ const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11341
+ const title = nonEmptyString4(body, "title");
11055
11342
  if (title === void 0) {
11056
11343
  await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a session update is JSON naming a non-empty title" } });
11057
11344
  return;
@@ -11066,7 +11353,7 @@ data: ${JSON.stringify(envelope)}
11066
11353
  await answerJson2(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the session read is a GET, and its update a PATCH or PUT" } });
11067
11354
  return;
11068
11355
  }
11069
- await answerJson2(response, HTTP_STATUS.ok, { session: { id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), status: "active" } });
11356
+ await answerJson2(response, HTTP_STATUS.ok, { session: sessionRow(session) });
11070
11357
  return;
11071
11358
  }
11072
11359
  if (tail === "bridge" && method === "POST") {
@@ -11087,10 +11374,7 @@ data: ${JSON.stringify(envelope)}
11087
11374
  await unauthorized(response, refusal);
11088
11375
  return;
11089
11376
  }
11090
- for (const sink of [...session.workerStreams, ...session.clientStreams]) {
11091
- sink.retire();
11092
- }
11093
- sessions.delete(session.id);
11377
+ retireSession(session);
11094
11378
  deps.log?.(`rc selfhost ${session.id}: archived at its client's request`);
11095
11379
  await answerJson2(response, HTTP_STATUS.ok, {});
11096
11380
  return;
@@ -11108,17 +11392,17 @@ data: ${JSON.stringify(envelope)}
11108
11392
  return;
11109
11393
  }
11110
11394
  if (workerTail === "" && method === "PUT") {
11111
- const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11395
+ const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11112
11396
  const claimedEpoch = numberOf2(body, "worker_epoch");
11113
11397
  if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
11114
11398
  await epochStale(response);
11115
11399
  return;
11116
11400
  }
11117
- const status = nonEmptyString3(body, "worker_status");
11401
+ const status = nonEmptyString4(body, "worker_status");
11118
11402
  if (status !== void 0) {
11119
11403
  session.workerStatus = status;
11120
11404
  }
11121
- if (isRecord7(body?.external_metadata)) {
11405
+ if (isRecord10(body?.external_metadata)) {
11122
11406
  session.externalMetadata = body.external_metadata;
11123
11407
  }
11124
11408
  await answerJson2(response, HTTP_STATUS.ok, {});
@@ -11135,7 +11419,7 @@ data: ${JSON.stringify(envelope)}
11135
11419
  return;
11136
11420
  }
11137
11421
  if (workerTail === "events" && method === "POST") {
11138
- const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11422
+ const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11139
11423
  const claimedEpoch = numberOf2(body, "worker_epoch");
11140
11424
  if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
11141
11425
  await epochStale(response);
@@ -11148,11 +11432,11 @@ data: ${JSON.stringify(envelope)}
11148
11432
  }
11149
11433
  const results = [];
11150
11434
  for (const event of events) {
11151
- if (!isRecord7(event) || !isRecord7(event.payload)) {
11435
+ if (!isRecord10(event) || !isRecord10(event.payload)) {
11152
11436
  await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "every event in a batch is an object carrying one payload object" } });
11153
11437
  return;
11154
11438
  }
11155
- const published = publish(session, RC_SOURCE_WORKER, event.payload);
11439
+ const published = publish(session.conversation, { session: session.id, half: "worker" }, RC_SOURCE_WORKER, event.payload);
11156
11440
  results.push({ event_id: published.eventId, sequence_num: String(published.sequenceNum) });
11157
11441
  }
11158
11442
  sweep();
@@ -11160,7 +11444,7 @@ data: ${JSON.stringify(envelope)}
11160
11444
  return;
11161
11445
  }
11162
11446
  if (workerTail === "events/stream" && method === "GET") {
11163
- await holdStream(session, "workerStreams", (event) => event.toWorker, request4, response);
11447
+ await holdStream(session.conversation, session.id, "workerStreams", (event) => reachesWorkerStream(event, session.id), request4, response);
11164
11448
  return;
11165
11449
  }
11166
11450
  if (workerTail === "events/delivery" && method === "POST") {
@@ -11169,7 +11453,7 @@ data: ${JSON.stringify(envelope)}
11169
11453
  return;
11170
11454
  }
11171
11455
  if (workerTail === "internal-events" && method === "POST") {
11172
- const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11456
+ const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11173
11457
  const claimedEpoch = numberOf2(body, "worker_epoch");
11174
11458
  if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
11175
11459
  await epochStale(response);
@@ -11181,7 +11465,7 @@ data: ${JSON.stringify(envelope)}
11181
11465
  return;
11182
11466
  }
11183
11467
  for (const event of events) {
11184
- if (isRecord7(event)) {
11468
+ if (isRecord10(event)) {
11185
11469
  session.internalEvents.push({ eventId: deps.newUuid(), body: event });
11186
11470
  }
11187
11471
  }
@@ -11209,8 +11493,8 @@ data: ${JSON.stringify(envelope)}
11209
11493
  return;
11210
11494
  }
11211
11495
  if (workerTail === "web-fetch" && method === "POST") {
11212
- const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11213
- const target = nonEmptyString3(body, "url");
11496
+ const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11497
+ const target = nonEmptyString4(body, "url");
11214
11498
  if (target === void 0) {
11215
11499
  await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-fetch carries one url" } });
11216
11500
  return;
@@ -11232,8 +11516,8 @@ data: ${JSON.stringify(envelope)}
11232
11516
  return;
11233
11517
  }
11234
11518
  if (workerTail === "web-search" && method === "POST") {
11235
- const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11236
- const query = nonEmptyString3(body, "query");
11519
+ const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11520
+ const query = nonEmptyString4(body, "query");
11237
11521
  if (query === void 0) {
11238
11522
  await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-search carries one query" } });
11239
11523
  return;
@@ -11244,7 +11528,7 @@ data: ${JSON.stringify(envelope)}
11244
11528
  await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "the domain filters of a worker web-search are string arrays" } });
11245
11529
  return;
11246
11530
  }
11247
- const searchProfile = nonEmptyString3(body, "search_profile");
11531
+ const searchProfile = nonEmptyString4(body, "search_profile");
11248
11532
  const backend = deps.webSearch;
11249
11533
  if (backend === void 0) {
11250
11534
  deps.log?.(`rc selfhost ${session.id}: worker web-search of "${query}" refused: no search backend serves this door`);
@@ -11269,7 +11553,7 @@ data: ${JSON.stringify(envelope)}
11269
11553
  return;
11270
11554
  }
11271
11555
  if (tail === "events" && method === "POST") {
11272
- const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11556
+ const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11273
11557
  const events = body?.events;
11274
11558
  if (!Array.isArray(events) || events.length === 0 || events.length > RC_SELF_HOST_MAX_BATCH_EVENTS) {
11275
11559
  await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: `a client event write is a non-empty array of at most ${String(RC_SELF_HOST_MAX_BATCH_EVENTS)} events` } });
@@ -11277,11 +11561,11 @@ data: ${JSON.stringify(envelope)}
11277
11561
  }
11278
11562
  const results = [];
11279
11563
  for (const event of events) {
11280
- if (!isRecord7(event) || !isRecord7(event.payload)) {
11564
+ if (!isRecord10(event) || !isRecord10(event.payload)) {
11281
11565
  await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "every event in a write is an object carrying one payload object" } });
11282
11566
  return;
11283
11567
  }
11284
- const published = publish(session, RC_SOURCE_CLIENT, event.payload);
11568
+ const published = publish(session.conversation, { session: session.id, half: "client" }, RC_SOURCE_CLIENT, event.payload);
11285
11569
  results.push({ sequence_num: String(published.sequenceNum), duplicate: published.duplicate });
11286
11570
  }
11287
11571
  sweep();
@@ -11289,14 +11573,14 @@ data: ${JSON.stringify(envelope)}
11289
11573
  return;
11290
11574
  }
11291
11575
  if (tail === "events" && method === "GET") {
11292
- await answerJson2(response, HTTP_STATUS.ok, { data: session.events.map((event) => ({ event_id: event.eventId, event_type: nonEmptyString3(event.payload, "type") ?? "event", sequence_num: event.sequenceNum, source: event.source, payload: event.payload, created_at: event.createdAt })) });
11576
+ await answerJson2(response, HTTP_STATUS.ok, { data: session.conversation.events.map((event) => ({ event_id: event.eventId, event_type: nonEmptyString4(event.payload, "type") ?? "event", sequence_num: event.sequenceNum, source: event.source, payload: event.payload, created_at: event.createdAt })) });
11293
11577
  return;
11294
11578
  }
11295
11579
  if (tail === "events/stream" && method === "GET") {
11296
- await holdStream(session, "clientStreams", () => true, request4, response);
11580
+ await holdStream(session.conversation, session.id, "clientStreams", () => true, request4, response);
11297
11581
  return;
11298
11582
  }
11299
- if (await serveRcClientChannels({ request: request4, response, session, answerJson: answerJson2, readBody: readBody2, publish, sweep, now: deps.now, presenceRefreshSeconds: RC_PRESENCE_REFRESH_SECONDS, ...deps.log === void 0 ? {} : { log: deps.log } }, tail, method, RC_SELF_HOST_MAX_BATCH_EVENTS)) {
11583
+ if (await serveRcClientChannels({ request: request4, response, host: { id: session.id, clients: session.clients, conversation: session.conversation, writer: { session: session.id, half: "client" } }, answerJson: answerJson2, readBody: readBody2, publish, sweep, now: deps.now, presenceRefreshSeconds: RC_PRESENCE_REFRESH_SECONDS, ...deps.log === void 0 ? {} : { log: deps.log } }, tail, method, RC_SELF_HOST_MAX_BATCH_EVENTS)) {
11300
11584
  return;
11301
11585
  }
11302
11586
  await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the self-hosted Remote Control surface serves no path "${tail}"` } });
@@ -11314,7 +11598,11 @@ data: ${JSON.stringify(envelope)}
11314
11598
  return;
11315
11599
  }
11316
11600
  if (pathname === RC_COMPAT_SESSIONS_PATH || pathname.startsWith(`${RC_COMPAT_SESSIONS_PATH}/`)) {
11317
- await serveRcCompatSessions({ request: request4, response, sessions, answerJson: answerJson2, readBody: readBody2, credentialRefusal, unauthorized, now: deps.now, ...deps.log === void 0 ? {} : { log: deps.log } }, pathname, RC_COMPAT_SESSIONS_PATH);
11601
+ await serveRcCompatSessions({ request: request4, response, sessions, retireSession, answerJson: answerJson2, readBody: readBody2, credentialRefusal, unauthorized, now: deps.now, ...deps.log === void 0 ? {} : { log: deps.log } }, pathname, RC_COMPAT_SESSIONS_PATH);
11602
+ return;
11603
+ }
11604
+ if (pathname === RC_CONVERSATIONS_PATH_PREFIX || pathname.startsWith(`${RC_CONVERSATIONS_PATH_PREFIX}/`)) {
11605
+ await serveRcConversations({ request: request4, response, conversations, answerJson: answerJson2, readBody: readBody2, credentialRefusal, unauthorized, publish, holdStream, sweep, now: deps.now, presenceRefreshSeconds: RC_PRESENCE_REFRESH_SECONDS, maxBatchEvents: RC_SELF_HOST_MAX_BATCH_EVENTS, ...deps.log === void 0 ? {} : { log: deps.log } }, pathname, method);
11318
11606
  return;
11319
11607
  }
11320
11608
  if (pathname !== RC_SESSIONS_PATH_PREFIX2 && !pathname.startsWith(`${RC_SESSIONS_PATH_PREFIX2}/`)) {
@@ -11328,26 +11616,36 @@ data: ${JSON.stringify(envelope)}
11328
11616
  await unauthorized(response, createRefusal);
11329
11617
  return;
11330
11618
  }
11331
- const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11619
+ const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11620
+ const namedConversation = nonEmptyString4(body, "conversation");
11621
+ let conversation;
11622
+ if (namedConversation === void 0) {
11623
+ conversation = newRcSelfHostConversation(deps.newUuid, deps.now);
11624
+ conversations.set(conversation.id, conversation);
11625
+ } else {
11626
+ conversation = conversations.get(namedConversation);
11627
+ if (conversation === void 0) {
11628
+ await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `no such Remote Control conversation on this door: ${namedConversation}` } });
11629
+ return;
11630
+ }
11631
+ conversation.lastTrafficAt = deps.now();
11632
+ }
11332
11633
  const session = {
11333
11634
  id: `${RC_SESSION_ID_PREFIX2}${deps.newUuid()}`,
11334
11635
  createdAt: deps.now(),
11335
- title: nonEmptyString3(body, "title"),
11636
+ title: nonEmptyString4(body, "title"),
11637
+ conversation,
11336
11638
  workerJwts: /* @__PURE__ */ new Set(),
11337
- sequenceNum: 0,
11338
- events: [],
11339
- seenPayloadUuids: /* @__PURE__ */ new Set(),
11340
- workerStreams: /* @__PURE__ */ new Set(),
11341
- clientStreams: /* @__PURE__ */ new Set(),
11342
11639
  internalEvents: [],
11343
11640
  clients: /* @__PURE__ */ new Map(),
11344
11641
  workerStatus: void 0,
11345
11642
  externalMetadata: void 0,
11346
11643
  lastTrafficAt: deps.now()
11347
11644
  };
11645
+ conversation.sessionIds.add(session.id);
11348
11646
  sessions.set(session.id, session);
11349
- deps.log?.(`rc selfhost ${session.id}: session created on the door's own surface`);
11350
- await answerJson2(response, HTTP_STATUS.ok, { session: { id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), status: "active" } });
11647
+ deps.log?.(`rc selfhost ${session.id}: session created on the door's own surface, attached to conversation ${conversation.id}${namedConversation === void 0 ? "" : " at its client's request"}`);
11648
+ await answerJson2(response, HTTP_STATUS.ok, { session: sessionRow(session) });
11351
11649
  return;
11352
11650
  }
11353
11651
  if (method === "GET") {
@@ -11356,7 +11654,7 @@ data: ${JSON.stringify(envelope)}
11356
11654
  await unauthorized(response, readRefusal);
11357
11655
  return;
11358
11656
  }
11359
- await answerJson2(response, HTTP_STATUS.ok, { data: [...sessions.values()].map((session) => ({ id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), updated_at: new Date(session.lastTrafficAt).toISOString(), session_status: "active" })) });
11657
+ await answerJson2(response, HTTP_STATUS.ok, { data: [...sessions.values()].map((session) => ({ id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), updated_at: new Date(session.lastTrafficAt).toISOString(), session_status: "active", conversation_id: session.conversation.id })) });
11360
11658
  return;
11361
11659
  }
11362
11660
  await answerJson2(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the session collection is a POST (create) and a GET (list)" } });
@@ -11373,92 +11671,20 @@ data: ${JSON.stringify(envelope)}
11373
11671
  await serveSessionPath(sessions.get(id), tail, request4, response);
11374
11672
  }
11375
11673
  };
11376
- const answerServerJson = (response, status, body) => {
11377
- const text = JSON.stringify(body);
11378
- response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text, "utf8")) });
11379
- response.end(text);
11380
- };
11381
- const local = {
11382
- parsesHost: (host) => host === RC_SELF_HOST_OAUTH_HOST,
11383
- serve: async (host, request4, response) => {
11384
- const method = (request4.method ?? "").toUpperCase();
11385
- const pathname = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
11386
- if (host === RC_SELF_HOST_OAUTH_HOST) {
11387
- if (method === "POST" && pathname === "/v1/oauth/token") {
11388
- const body = parseJsonObject3(await readBody2(request4, CONTROL_BODY_CAP_BYTES));
11389
- const record = deps.credentialRecord();
11390
- if (record === void 0) {
11391
- answerServerJson(response, HTTP_SERVICE_UNAVAILABLE, { error: "the front door's self-hosted Remote Control surface is on but no credential has been minted for it" });
11392
- return true;
11393
- }
11394
- if (body?.grant_type !== void 0 && body.grant_type !== "refresh_token") {
11395
- answerServerJson(response, HTTP_STATUS.badRequest, { error: "unsupported_grant_type" });
11396
- return true;
11397
- }
11398
- answerServerJson(response, HTTP_STATUS.ok, { access_token: record.accessToken, refresh_token: record.refreshToken, expires_in: RC_SELF_HOST_OAUTH_TOKEN_TTL_SECONDS, scope: RC_SELF_HOST_SCOPE_LIST.join(" ") });
11399
- return true;
11400
- }
11401
- if (method === "GET" && pathname === "/v1/oauth/hello") {
11402
- answerServerJson(response, HTTP_STATUS.ok, {});
11403
- return true;
11404
- }
11405
- return false;
11406
- }
11407
- if (method === "POST" && pathname.startsWith("/api/eval/")) {
11408
- await readBody2(request4, CONTROL_BODY_CAP_BYTES);
11409
- answerServerJson(response, HTTP_STATUS.ok, { features: growthBookFeatures(), dateUpdated: Math.floor(deps.now() / MS_PER_SECOND5) });
11410
- return true;
11411
- }
11412
- if (method === "GET" && pathname === "/api/oauth/profile") {
11413
- const record = deps.credentialRecord();
11414
- if (record === void 0) {
11415
- answerServerJson(response, HTTP_SERVICE_UNAVAILABLE, { error: "the front door's self-hosted Remote Control surface is on but no credential has been minted for it" });
11416
- return true;
11417
- }
11418
- answerServerJson(response, HTTP_STATUS.ok, {
11419
- account: { uuid: record.accountUuid, display_name: "agent-shim self-hosted", created_at: (/* @__PURE__ */ new Date(0)).toISOString() },
11420
- organization: { uuid: record.organizationUuid, organization_type: "claude_max" }
11421
- });
11422
- return true;
11423
- }
11424
- if (method === "GET" && pathname === "/api/claude_code/policy_limits") {
11425
- answerServerJson(response, HTTP_STATUS.ok, {});
11426
- return true;
11427
- }
11428
- if (method === "POST" && pathname === "/api/oauth/validate") {
11429
- await readBody2(request4, CONTROL_BODY_CAP_BYTES);
11430
- const record = deps.credentialRecord();
11431
- if (record === void 0) {
11432
- answerServerJson(response, HTTP_SERVICE_UNAVAILABLE, { error: "the front door's self-hosted Remote Control surface is on but no credential has been minted for it" });
11433
- return true;
11434
- }
11435
- answerServerJson(response, HTTP_STATUS.ok, { scopes: [...RC_SELF_HOST_SCOPE_LIST], expiresAt: null, subscriptionType: "max", account_uuid: record.accountUuid, organization_uuid: record.organizationUuid });
11436
- return true;
11437
- }
11438
- if (method === "POST" && (pathname === "/api/event_logging/v2/batch" || pathname === "/api/claude_code/metrics" || pathname === "/api/claude_cli_feedback")) {
11439
- await readBody2(request4, CONTROL_BODY_CAP_BYTES);
11440
- answerServerJson(response, HTTP_STATUS.ok, {});
11441
- return true;
11442
- }
11443
- if (method === "GET" && pathname === "/api/hello") {
11444
- answerServerJson(response, HTTP_STATUS.ok, {});
11445
- return true;
11446
- }
11447
- return false;
11448
- }
11449
- };
11674
+ const local = createRcSelfHostLocal({ now: deps.now, credentialRecord: deps.credentialRecord, readBody: readBody2 });
11450
11675
  return {
11451
11676
  route,
11452
11677
  local,
11453
11678
  close: () => {
11454
11679
  clearInterval(timer);
11455
11680
  clearInterval(keepalive);
11456
- for (const session of sessions.values()) {
11457
- for (const sink of [...session.workerStreams, ...session.clientStreams]) {
11681
+ for (const conversation of conversations.values()) {
11682
+ for (const sink of [...conversation.workerStreams, ...conversation.clientStreams]) {
11458
11683
  sink.retire();
11459
11684
  }
11460
11685
  }
11461
11686
  sessions.clear();
11687
+ conversations.clear();
11462
11688
  }
11463
11689
  };
11464
11690
  }
@@ -11471,7 +11697,7 @@ var RC_SELF_HOST_RECORD_DIR = "rc-selfhost";
11471
11697
  var RC_SELF_HOST_RECORD_FILE = "credential.json";
11472
11698
  var SELF_HOST_EMAIL = "selfhost@agent-shim.invalid";
11473
11699
  var SELF_HOST_SUBSCRIPTION_TYPE = "max";
11474
- function isRecord8(value) {
11700
+ function isRecord11(value) {
11475
11701
  return typeof value === "object" && value !== null && !Array.isArray(value);
11476
11702
  }
11477
11703
  function readJsonObject(fs12, file) {
@@ -11485,7 +11711,7 @@ function readJsonObject(fs12, file) {
11485
11711
  } catch {
11486
11712
  return void 0;
11487
11713
  }
11488
- return isRecord8(parsed) ? parsed : void 0;
11714
+ return isRecord11(parsed) ? parsed : void 0;
11489
11715
  }
11490
11716
  function readRcSelfHostRecord(fs12, frontdoorDir) {
11491
11717
  const parsed = readJsonObject(fs12, path28.join(frontdoorDir, RC_SELF_HOST_RECORD_DIR, RC_SELF_HOST_RECORD_FILE));
@@ -11509,7 +11735,7 @@ function mintRcSelfHostCredential(deps) {
11509
11735
  const recordFile = path28.join(recordDir, RC_SELF_HOST_RECORD_FILE);
11510
11736
  const previousRecord = readRcSelfHostRecord(deps.fs, deps.frontdoorDir);
11511
11737
  const existingCredentials = readJsonObject(deps.fs, credentialsFile);
11512
- const existingOauth = isRecord8(existingCredentials?.claudeAiOauth) ? existingCredentials.claudeAiOauth : void 0;
11738
+ const existingOauth = isRecord11(existingCredentials?.claudeAiOauth) ? existingCredentials.claudeAiOauth : void 0;
11513
11739
  const existingToken = typeof existingOauth?.accessToken === "string" && existingOauth.accessToken !== "" ? existingOauth.accessToken : void 0;
11514
11740
  const existingIsOwn = existingToken !== void 0 && previousRecord?.accessToken === existingToken;
11515
11741
  if (existingToken !== void 0 && !existingIsOwn && !deps.force) {
@@ -11526,7 +11752,7 @@ function mintRcSelfHostCredential(deps) {
11526
11752
  deps.fs.writeFilePrivate(credentialsFile, `${JSON.stringify({ ...existingCredentials, claudeAiOauth: { accessToken, refreshToken, expiresAt: null, scopes: [...RC_SELF_HOST_SCOPE_LIST], subscriptionType: SELF_HOST_SUBSCRIPTION_TYPE, rateLimitTier: null } }, null, 2)}
11527
11753
  `);
11528
11754
  const claudeJson = readJsonObject(deps.fs, claudeJsonFile) ?? {};
11529
- const existingBlock = (value) => isRecord8(value) ? value : {};
11755
+ const existingBlock = (value) => isRecord11(value) ? value : {};
11530
11756
  deps.fs.writeFilePrivate(claudeJsonFile, `${JSON.stringify({
11531
11757
  ...claudeJson,
11532
11758
  oauthAccount: {
@@ -11725,7 +11951,7 @@ import path30 from "node:path";
11725
11951
  // package.json
11726
11952
  var package_default = {
11727
11953
  name: "agent-shim",
11728
- version: "8.25.0",
11954
+ version: "8.27.0",
11729
11955
  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.",
11730
11956
  license: "MIT",
11731
11957
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -12988,11 +13214,15 @@ export {
12988
13214
  RC_WEB_FETCH_MAX_BYTES,
12989
13215
  RC_WEB_FETCH_MAX_REDIRECTS,
12990
13216
  ROUTED_PATH_PREFIX,
13217
+ RcLiveRateLimitSchema,
12991
13218
  RcPermissionModeSchema,
13219
+ RcRateLimitInfoSchema,
13220
+ RcRateLimitWindowSchema,
12992
13221
  RcStreamEnvelopeSchema,
12993
13222
  RcStreamEventSchema,
12994
13223
  UsageError,
12995
13224
  UsageListOutputSchema,
13225
+ UsageLiveOutputSchema,
12996
13226
  UsageSnapshotError,
12997
13227
  UsageSnapshotSchema,
12998
13228
  UsageWindowsInputSchema,
@@ -13044,6 +13274,7 @@ export {
13044
13274
  createRcControlHandler,
13045
13275
  createRcCredentialStore,
13046
13276
  createRcEventFanout,
13277
+ createRcLiveUsage,
13047
13278
  createRcSelfHostSurface,
13048
13279
  createRcSessionTracker,
13049
13280
  createRcStreamHub,
@@ -13105,6 +13336,7 @@ export {
13105
13336
  rcEventWriteResultFromAnswer,
13106
13337
  rcFanoutOnDoorHub,
13107
13338
  rcObserverAsPassthrough,
13339
+ rcRateLimitInfoOf,
13108
13340
  rcSelfHostFromEnv,
13109
13341
  rcWebFetchAllowsPrivateFromEnv,
13110
13342
  readActiveIdentity,