agent-shim 8.24.0 → 8.25.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
@@ -3207,9 +3207,6 @@ var HTTP_STATUS = {
3207
3207
  badGateway: 502
3208
3208
  };
3209
3209
 
3210
- // src/frontdoor/rcControl.ts
3211
- import * as https from "node:https";
3212
-
3213
3210
  // src/frontdoor/rcSessions.ts
3214
3211
  var RC_SESSIONS_PATH_PREFIX = "/v1/code/sessions";
3215
3212
  var RC_SESSION_ID_PREFIX = "cse_";
@@ -3807,7 +3804,7 @@ async function answerRcControlRequest(deps, sessionId, requestId, decision) {
3807
3804
  }
3808
3805
  return result;
3809
3806
  }
3810
- async function deliverRcClientPayload(deps, sessionId, payload, action) {
3807
+ async function deliverRcClientPayload(deps, sessionId, payload, action, to = "events") {
3811
3808
  const credential = deps.credentialOf(sessionId);
3812
3809
  if (credential === void 0) {
3813
3810
  return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
@@ -3818,7 +3815,7 @@ async function deliverRcClientPayload(deps, sessionId, payload, action) {
3818
3815
  const body = JSON.stringify(buildRcEventWriteBody(payload));
3819
3816
  let answer;
3820
3817
  try {
3821
- answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
3818
+ answer = await (to === "teleport-events" ? deps.dial.writeTeleportEvents(sessionId, rcWriteHeaders(credential), body) : deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body));
3822
3819
  } catch (error) {
3823
3820
  return { ok: false, message: `the door could not reach the API host to ${action}: ${error instanceof Error ? error.message : String(error)}` };
3824
3821
  }
@@ -3912,8 +3909,18 @@ async function submitRcSessionMcpOAuthCallbackUrl(deps, sessionId, serverName, c
3912
3909
  async function sendRcKeepAlive(deps, sessionId) {
3913
3910
  return await deliverRcClientPayload(deps, sessionId, buildRcKeepAlivePayload(), `keep session ${sessionId} alive`);
3914
3911
  }
3912
+ async function teleportRcSession(deps, sessionId, marker) {
3913
+ if (marker === "") {
3914
+ return { ok: false, message: "a teleport write needs a non-empty marker: the teleport relay anchors on a marker line's uuid and string content, and an empty string carries neither" };
3915
+ }
3916
+ return await deliverRcClientPayload(deps, sessionId, buildRcUserMessagePayload(deps.newUuid(), sessionId, marker), `teleport a marker into session ${sessionId}`, "teleport-events");
3917
+ }
3918
+ function buildRcMarkReadBody(eventId) {
3919
+ return eventId === void 0 ? {} : { event_id: eventId };
3920
+ }
3915
3921
 
3916
3922
  // src/frontdoor/rcControl.ts
3923
+ import * as https from "node:https";
3917
3924
  var RC_PERMISSION_MODE_LIST = RC_PERMISSION_MODES.join(", ");
3918
3925
  var CONTROL_PATH_PREFIX = "/__agent-shim/rc";
3919
3926
  var CONTROL_BODY_CAP_BYTES = 10485760;
@@ -4087,6 +4094,15 @@ function createRcControlHandler(deps) {
4087
4094
  const callbackUrl = bodyString(body, "callbackUrl");
4088
4095
  return session === void 0 || serverName === void 0 || callbackUrl === void 0 ? { error: "the mcp-oauth-callback-url body must be JSON naming a non-empty session id, server name, and callback URL" } : { session, run: async () => await deps.mcpOAuthCallbackUrl(session, serverName, callbackUrl) };
4089
4096
  }
4097
+ },
4098
+ {
4099
+ name: "teleport",
4100
+ path: "teleport",
4101
+ read: (body) => {
4102
+ const session = bodyString(body, "session");
4103
+ const marker = bodyString(body, "marker");
4104
+ return session === void 0 || marker === void 0 ? { error: "the teleport body must be JSON naming a non-empty session id and marker" } : { session, run: async () => await deps.teleport(session, marker) };
4105
+ }
4090
4106
  }
4091
4107
  ];
4092
4108
  return (request4, response) => {
@@ -4450,7 +4466,8 @@ function frontDoorRcControl(transport, token) {
4450
4466
  mcpStatus: async (sessionId) => await writeOf(`${CONTROL_PATH_PREFIX}/mcp-status`, JSON.stringify({ session: sessionId }), (status, message) => `requesting the MCP status of ${sessionId} failed (HTTP ${String(status)}): ${message}`),
4451
4467
  mcpReconnect: async (sessionId, serverName) => await writeOf(`${CONTROL_PATH_PREFIX}/mcp-reconnect`, JSON.stringify({ session: sessionId, serverName }), (status, message) => `reconnecting MCP server ${serverName} on ${sessionId} failed (HTTP ${String(status)}): ${message}`),
4452
4468
  mcpAuthenticate: async (sessionId, serverName, redirectUri) => await writeOf(`${CONTROL_PATH_PREFIX}/mcp-authenticate`, JSON.stringify({ session: sessionId, serverName, redirectUri }), (status, message) => `authenticating MCP server ${serverName} on ${sessionId} failed (HTTP ${String(status)}): ${message}`),
4453
- mcpOAuthCallbackUrl: async (sessionId, serverName, callbackUrl) => await writeOf(`${CONTROL_PATH_PREFIX}/mcp-oauth-callback-url`, JSON.stringify({ session: sessionId, serverName, callbackUrl }), (status, message) => `handing MCP server ${serverName}'s OAuth callback to ${sessionId} failed (HTTP ${String(status)}): ${message}`)
4469
+ mcpOAuthCallbackUrl: async (sessionId, serverName, callbackUrl) => await writeOf(`${CONTROL_PATH_PREFIX}/mcp-oauth-callback-url`, JSON.stringify({ session: sessionId, serverName, callbackUrl }), (status, message) => `handing MCP server ${serverName}'s OAuth callback to ${sessionId} failed (HTTP ${String(status)}): ${message}`),
4470
+ teleport: async (sessionId, marker) => await writeOf(`${CONTROL_PATH_PREFIX}/teleport`, JSON.stringify({ session: sessionId, marker }), (status, message) => `teleporting a marker into ${sessionId} failed (HTTP ${String(status)}): ${message}`)
4454
4471
  };
4455
4472
  }
4456
4473
 
@@ -4597,36 +4614,38 @@ function tlsFacts(options) {
4597
4614
  }
4598
4615
  function realRcEventDial(target = { host: CONNECT_INTERCEPT_HOST, port: HTTPS_PORT }) {
4599
4616
  const agent = new ExemptTlsAgent();
4600
- return {
4601
- writeEvents: async (sessionId, headers, body) => await new Promise((resolve, reject) => {
4602
- const options = {
4603
- host: target.host,
4604
- port: target.port,
4605
- method: "POST",
4606
- path: `/v1/code/sessions/${encodeURIComponent(sessionId)}/events`,
4607
- // The Host header names the host whose API this is, not the address dialled: a redirected test dials a loopback stand-in while still speaking to the API host by name, exactly as the forwarded paths present their SNI.
4608
- headers: { ...headers, host: target.servername ?? target.host, "content-length": String(Buffer.byteLength(body, "utf8")) },
4609
- agent
4610
- };
4611
- options.servername = target.servername ?? target.host;
4612
- if (target.ca !== void 0) {
4613
- options.ca = [...target.ca];
4614
- }
4615
- if (target.rejectUnauthorized !== void 0) {
4616
- options.rejectUnauthorized = target.rejectUnauthorized;
4617
- }
4618
- const request4 = https2.request(options, (response) => {
4619
- const chunks = [];
4620
- response.on("data", (chunk) => {
4621
- chunks.push(chunk);
4622
- });
4623
- response.on("end", () => {
4624
- resolve({ status: response.statusCode ?? HTTP_BAD_GATEWAY, body: Buffer.concat(chunks).toString("utf8") });
4625
- });
4617
+ const post = async (path32, headers, body) => await new Promise((resolve, reject) => {
4618
+ const options = {
4619
+ host: target.host,
4620
+ port: target.port,
4621
+ method: "POST",
4622
+ path: path32,
4623
+ // The Host header names the host whose API this is, not the address dialled: a redirected test dials a loopback stand-in while still speaking to the API host by name, exactly as the forwarded paths present their SNI.
4624
+ headers: { ...headers, host: target.servername ?? target.host, "content-length": String(Buffer.byteLength(body, "utf8")) },
4625
+ agent
4626
+ };
4627
+ options.servername = target.servername ?? target.host;
4628
+ if (target.ca !== void 0) {
4629
+ options.ca = [...target.ca];
4630
+ }
4631
+ if (target.rejectUnauthorized !== void 0) {
4632
+ options.rejectUnauthorized = target.rejectUnauthorized;
4633
+ }
4634
+ const request4 = https2.request(options, (response) => {
4635
+ const chunks = [];
4636
+ response.on("data", (chunk) => {
4637
+ chunks.push(chunk);
4626
4638
  });
4627
- request4.once("error", reject);
4628
- request4.end(body);
4629
- })
4639
+ response.on("end", () => {
4640
+ resolve({ status: response.statusCode ?? HTTP_BAD_GATEWAY, body: Buffer.concat(chunks).toString("utf8") });
4641
+ });
4642
+ });
4643
+ request4.once("error", reject);
4644
+ request4.end(body);
4645
+ });
4646
+ return {
4647
+ writeEvents: async (sessionId, headers, body) => await post(`/v1/code/sessions/${encodeURIComponent(sessionId)}/events`, headers, body),
4648
+ writeTeleportEvents: async (sessionId, headers, body) => await post(`/v1/code/sessions/${encodeURIComponent(sessionId)}/teleport-events`, headers, body)
4630
4649
  };
4631
4650
  }
4632
4651
  var NO_CHUNKS = {
@@ -4644,7 +4663,8 @@ function lateRcEventDial(resolve) {
4644
4663
  return built.dial;
4645
4664
  };
4646
4665
  return {
4647
- writeEvents: async (sessionId, headers, body) => await dialFor().writeEvents(sessionId, headers, body)
4666
+ writeEvents: async (sessionId, headers, body) => await dialFor().writeEvents(sessionId, headers, body),
4667
+ writeTeleportEvents: async (sessionId, headers, body) => await dialFor().writeTeleportEvents(sessionId, headers, body)
4648
4668
  };
4649
4669
  }
4650
4670
  function lateRcStreamDial(resolve) {
@@ -4657,7 +4677,8 @@ function lateRcStreamDial(resolve) {
4657
4677
  return built.dial;
4658
4678
  };
4659
4679
  return {
4660
- announcePresence: async (sessionId, headers, clientId) => await dialFor().announcePresence(sessionId, headers, clientId),
4680
+ announcePresence: async (sessionId, headers, clientId, clear) => await dialFor().announcePresence(sessionId, headers, clientId, clear),
4681
+ markRead: async (sessionId, headers, eventId) => await dialFor().markRead(sessionId, headers, eventId),
4661
4682
  openStream: async (sessionId, headers, resume, signal) => await dialFor().openStream(sessionId, headers, resume, signal)
4662
4683
  };
4663
4684
  }
@@ -4699,11 +4720,16 @@ function realRcStreamDial(target = { host: CONNECT_INTERCEPT_HOST, port: HTTPS_P
4699
4720
  return Buffer.concat(chunks).toString("utf8");
4700
4721
  };
4701
4722
  return {
4702
- announcePresence: async (sessionId, headers, clientId) => {
4703
- const body = JSON.stringify({ client_id: clientId, clear: false });
4723
+ announcePresence: async (sessionId, headers, clientId, clear) => {
4724
+ const body = JSON.stringify({ client_id: clientId, clear });
4704
4725
  const { status, response } = await request4("POST", `/v1/code/sessions/${encodeURIComponent(sessionId)}/client/presence`, { ...headers, "content-type": "application/json" }, body, new AbortController().signal);
4705
4726
  return { status, body: await readAll(response) };
4706
4727
  },
4728
+ markRead: async (sessionId, headers, eventId) => {
4729
+ const body = JSON.stringify(buildRcMarkReadBody(eventId));
4730
+ const { status, response } = await request4("POST", `/v1/code/sessions/${encodeURIComponent(sessionId)}/mark_read`, { ...headers, "content-type": "application/json" }, body, new AbortController().signal);
4731
+ return { status, body: await readAll(response) };
4732
+ },
4707
4733
  openStream: async (sessionId, headers, resume, signal) => {
4708
4734
  const path32 = resume === void 0 ? `/v1/code/sessions/${encodeURIComponent(sessionId)}/events/stream` : `/v1/code/sessions/${encodeURIComponent(sessionId)}/events/stream?from_sequence_num=${String(resume.fromSequenceNum)}`;
4709
4735
  const streamHeaders = resume === void 0 ? headers : { ...headers, "last-event-id": String(resume.fromSequenceNum) };
@@ -5114,6 +5140,10 @@ var RcMcpOAuthCallbackUrlInputSchema = z4.strictObject({
5114
5140
  serverName: z4.string().min(1),
5115
5141
  callbackUrl: z4.string().min(1)
5116
5142
  });
5143
+ var RcTeleportInputSchema = z4.strictObject({
5144
+ session: z4.string().min(1),
5145
+ marker: z4.string().min(1)
5146
+ });
5117
5147
  var RcListOutputSchema = z4.strictObject({ sessions: z4.readonly(z4.array(RcSessionSummarySchema)) });
5118
5148
  var RcStatusOutputSchema = z4.strictObject({ statuses: z4.readonly(z4.array(RcSessionStatusSchema)) });
5119
5149
  var RcPendingOutputSchema = z4.strictObject({ pending: z4.readonly(z4.array(RcPendingRequestSummarySchema)) });
@@ -5198,6 +5228,7 @@ function createRcApiRouter(deps) {
5198
5228
  mcpReconnect: authed.input(RcMcpReconnectInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpReconnect(input.session, input.serverName)) })),
5199
5229
  mcpAuthenticate: authed.input(RcMcpAuthenticateInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpAuthenticate(input.session, input.serverName, input.redirectUri)) })),
5200
5230
  mcpOAuthCallbackUrl: authed.input(RcMcpOAuthCallbackUrlInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpOAuthCallbackUrl(input.session, input.serverName, input.callbackUrl)) })),
5231
+ teleport: authed.input(RcTeleportInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.teleport(input.session, input.marker)) })),
5201
5232
  subscribe: authed.input(RcSessionQuerySchema).output(eventIterator(RcStreamEventSchema)).handler(async function* ({ input, signal }) {
5202
5233
  const queue = [];
5203
5234
  let wake;
@@ -10154,9 +10185,26 @@ function createRcStreamHub(deps) {
10154
10185
  }
10155
10186
  return headers;
10156
10187
  };
10188
+ let receiptFailureLogged = false;
10189
+ const sendReceipt = (attachment, headers, eventId) => {
10190
+ if (eventId === void 0) {
10191
+ return;
10192
+ }
10193
+ deps.dial.markRead(attachment.sessionId, headers, eventId).then(
10194
+ () => {
10195
+ receiptFailureLogged = false;
10196
+ },
10197
+ (error) => {
10198
+ if (!receiptFailureLogged) {
10199
+ receiptFailureLogged = true;
10200
+ deps.log?.(`rc stream ${attachment.sessionId}: the read receipt for event ${eventId} could not be sent (${error instanceof Error ? error.message : String(error)}); further receipt failures stay quiet until one lands`);
10201
+ }
10202
+ }
10203
+ );
10204
+ };
10157
10205
  const attempt = async (attachment, credential) => {
10158
10206
  const headers = streamHeaders(credential);
10159
- const presence = await deps.dial.announcePresence(attachment.sessionId, headers, attachment.clientId);
10207
+ const presence = await deps.dial.announcePresence(attachment.sessionId, headers, attachment.clientId, false);
10160
10208
  if (presence.status === HTTP_STATUS.unauthorized) {
10161
10209
  return "unauthorized";
10162
10210
  }
@@ -10176,6 +10224,7 @@ function createRcStreamHub(deps) {
10176
10224
  const parser = createSseParser();
10177
10225
  for await (const chunk of answer.chunks) {
10178
10226
  attachment.controller.signal.throwIfAborted();
10227
+ let lastEventId;
10179
10228
  for (const parsed of parser.write(Buffer.from(chunk).toString("utf8"))) {
10180
10229
  const envelope = parseRcStreamEnvelope(parsed);
10181
10230
  if (envelope === void 0) {
@@ -10183,10 +10232,21 @@ function createRcStreamHub(deps) {
10183
10232
  }
10184
10233
  deps.fileStreamEvent(attachment.sessionId, envelope);
10185
10234
  deps.fanout.publish({ session: attachment.sessionId, envelope });
10235
+ if (typeof envelope.event_id === "string" && envelope.event_type !== "mark_read") {
10236
+ lastEventId = envelope.event_id;
10237
+ }
10186
10238
  }
10239
+ sendReceipt(attachment, headers, lastEventId);
10187
10240
  }
10188
10241
  return "dropped";
10189
10242
  };
10243
+ const clearPresence = (attachment, headers) => {
10244
+ if (headers === void 0) {
10245
+ return;
10246
+ }
10247
+ void deps.dial.announcePresence(attachment.sessionId, headers, attachment.clientId, true).catch(() => {
10248
+ });
10249
+ };
10190
10250
  const runLoop = async (attachment) => {
10191
10251
  let retriedUnauthorized = false;
10192
10252
  for (; ; ) {
@@ -10198,6 +10258,7 @@ function createRcStreamHub(deps) {
10198
10258
  if (!live || credential?.authorization === void 0) {
10199
10259
  deps.log?.(`rc stream ${attachment.sessionId}: the attachment ended (${live ? "the session's client credential is no longer known" : "the tracker gave up on the session"})`);
10200
10260
  persistCursor(attachment.sessionId);
10261
+ clearPresence(attachment, credential === void 0 ? void 0 : streamHeaders(credential));
10201
10262
  if (attachments.get(attachment.sessionId) === attachment) {
10202
10263
  attachments.delete(attachment.sessionId);
10203
10264
  }
@@ -10260,6 +10321,8 @@ function createRcStreamHub(deps) {
10260
10321
  close: () => {
10261
10322
  for (const attachment of attachments.values()) {
10262
10323
  persistCursor(attachment.sessionId);
10324
+ const credential = deps.credentialOf(attachment.sessionId);
10325
+ clearPresence(attachment, credential?.authorization === void 0 ? void 0 : streamHeaders(credential));
10263
10326
  attachment.stop();
10264
10327
  }
10265
10328
  attachments.clear();
@@ -10267,6 +10330,193 @@ function createRcStreamHub(deps) {
10267
10330
  };
10268
10331
  }
10269
10332
 
10333
+ // src/frontdoor/rcSelfHostEndpoints.ts
10334
+ var RC_SOURCE_WORKER = "worker";
10335
+ var RC_SOURCE_CLIENT = "client";
10336
+ function isRecord6(value) {
10337
+ return typeof value === "object" && value !== null && !Array.isArray(value);
10338
+ }
10339
+ function parseJsonObject2(body) {
10340
+ let parsed;
10341
+ try {
10342
+ parsed = JSON.parse(body);
10343
+ } catch {
10344
+ return void 0;
10345
+ }
10346
+ return isRecord6(parsed) ? parsed : void 0;
10347
+ }
10348
+ function nonEmptyString2(record, field) {
10349
+ const value = record === void 0 ? void 0 : record[field];
10350
+ return typeof value === "string" && value !== "" ? value : void 0;
10351
+ }
10352
+ function numberOf(record, field) {
10353
+ const value = record === void 0 ? void 0 : record[field];
10354
+ if (typeof value === "number" && Number.isFinite(value)) {
10355
+ return value;
10356
+ }
10357
+ if (typeof value === "string" && /^[0-9]+$/.test(value)) {
10358
+ return Number(value);
10359
+ }
10360
+ return void 0;
10361
+ }
10362
+ function pageOfRcChannel(url, rows, idOf, rowOf) {
10363
+ const query = new URL(url, "http://127.0.0.1").searchParams;
10364
+ const anchor = query.get("after_event_id") ?? query.get("cursor");
10365
+ const rawLimit = query.get("limit");
10366
+ const limit = rawLimit === null ? void 0 : numberOf({ value: rawLimit }, "value");
10367
+ if (limit !== void 0 && limit < 1) {
10368
+ return { error: "the limit names a whole number of rows of at least one" };
10369
+ }
10370
+ const from = anchor === null ? 0 : rows.findIndex((row) => idOf(row) === anchor) + 1;
10371
+ if (anchor !== null && from === 0) {
10372
+ return { error: `the anchor names an event id this surface holds no row for: ${anchor}` };
10373
+ }
10374
+ const page = limit === void 0 ? rows.slice(from) : rows.slice(from, from + limit);
10375
+ const last = page[page.length - 1];
10376
+ return { data: page.map(rowOf), ...last === void 0 ? {} : { next_cursor: idOf(last) } };
10377
+ }
10378
+ async function serveRcClientChannels(ctx, tail, method, maxBatchEvents) {
10379
+ const { request: request4, response, session } = ctx;
10380
+ if (tail === "client/presence" && method === "POST") {
10381
+ const body = parseJsonObject2(await ctx.readBody(request4.body, CONTROL_BODY_CAP_BYTES));
10382
+ const clientId = nonEmptyString2(body, "client_id");
10383
+ const clear = body?.clear;
10384
+ if (clientId === void 0 || clear !== void 0 && typeof clear !== "boolean") {
10385
+ await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a presence announcement is JSON naming a non-empty client_id, with clear only as a boolean or absent" } });
10386
+ return true;
10387
+ }
10388
+ if (clear === true) {
10389
+ session.clients.delete(clientId);
10390
+ ctx.log?.(`rc selfhost ${session.id}: client ${clientId} cleared its presence`);
10391
+ } 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)`}`);
10394
+ }
10395
+ await ctx.answerJson(response, HTTP_STATUS.ok, { refresh_after_seconds: ctx.presenceRefreshSeconds });
10396
+ return true;
10397
+ }
10398
+ if (tail === "mark_read" && method === "POST") {
10399
+ const body = parseJsonObject2(await ctx.readBody(request4.body, CONTROL_BODY_CAP_BYTES));
10400
+ const eventId = nonEmptyString2(body, "event_id");
10401
+ const clientId = nonEmptyString2(body, "client_id");
10402
+ if (body !== void 0 && "event_id" in body && eventId === void 0 || "client_id" in (body ?? {}) && clientId === void 0) {
10403
+ 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
+ return true;
10405
+ }
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)}`);
10408
+ await ctx.answerJson(response, HTTP_STATUS.ok, {});
10409
+ return true;
10410
+ }
10411
+ if (tail === "teleport-events" && method === "POST") {
10412
+ const body = parseJsonObject2(await ctx.readBody(request4.body, CONTROL_BODY_CAP_BYTES));
10413
+ const events = body?.events;
10414
+ if (!Array.isArray(events) || events.length === 0 || events.length > maxBatchEvents) {
10415
+ await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: `a teleport write is a non-empty array of at most ${String(maxBatchEvents)} events` } });
10416
+ return true;
10417
+ }
10418
+ const results = [];
10419
+ for (const event of events) {
10420
+ if (!isRecord6(event) || !isRecord6(event.payload)) {
10421
+ 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
+ return true;
10423
+ }
10424
+ const published = ctx.publish(session, RC_SOURCE_CLIENT, event.payload);
10425
+ results.push({ sequence_num: String(published.sequenceNum), duplicate: published.duplicate });
10426
+ }
10427
+ ctx.sweep();
10428
+ ctx.log?.(`rc selfhost ${session.id}: teleport write published ${String(results.length)} marker event(s)`);
10429
+ await ctx.answerJson(response, HTTP_STATUS.ok, { results });
10430
+ return true;
10431
+ }
10432
+ 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 }));
10434
+ if ("error" in page) {
10435
+ await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: page.error } });
10436
+ return true;
10437
+ }
10438
+ await ctx.answerJson(response, HTTP_STATUS.ok, page);
10439
+ return true;
10440
+ }
10441
+ return false;
10442
+ }
10443
+ var compatSessionRow = (session, now) => ({ id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), updated_at: new Date(now()).toISOString(), session_status: "active" });
10444
+ async function serveRcCompatSessions(ctx, pathname, compatPath) {
10445
+ const { request: request4, response } = ctx;
10446
+ const method = request4.method.toUpperCase();
10447
+ if (pathname === compatPath) {
10448
+ if (method !== "GET") {
10449
+ await ctx.answerJson(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the compatibility session list is a GET" } });
10450
+ return;
10451
+ }
10452
+ const listRefusal = ctx.credentialRefusal(request4.headers);
10453
+ if (listRefusal !== void 0) {
10454
+ await ctx.unauthorized(response, listRefusal);
10455
+ return;
10456
+ }
10457
+ await ctx.answerJson(response, HTTP_STATUS.ok, { data: [...ctx.sessions.values()].map((session2) => compatSessionRow(session2, ctx.now)) });
10458
+ return;
10459
+ }
10460
+ const rest = pathname.slice(`${compatPath}/`.length);
10461
+ const slash = rest.indexOf("/");
10462
+ const address = slash === -1 ? rest : rest.slice(0, slash);
10463
+ const compatTail = slash === -1 ? "" : rest.slice(slash + 1);
10464
+ const refusal = ctx.credentialRefusal(request4.headers);
10465
+ if (refusal !== void 0) {
10466
+ await ctx.unauthorized(response, refusal);
10467
+ return;
10468
+ }
10469
+ const session = ctx.sessions.get(address) ?? ctx.sessions.get(`cse_${address}`);
10470
+ if (compatTail === "") {
10471
+ if (method === "GET") {
10472
+ if (session === void 0) {
10473
+ await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `Session not found: ${address}` } });
10474
+ return;
10475
+ }
10476
+ await ctx.answerJson(response, HTTP_STATUS.ok, compatSessionRow(session, ctx.now));
10477
+ return;
10478
+ }
10479
+ if (method === "PATCH" || method === "PUT") {
10480
+ if (session === void 0) {
10481
+ await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `Session not found: ${address}` } });
10482
+ return;
10483
+ }
10484
+ const body = parseJsonObject2(await ctx.readBody(request4.body, CONTROL_BODY_CAP_BYTES));
10485
+ const title = nonEmptyString2(body, "title");
10486
+ if (title === void 0) {
10487
+ await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a compatibility session update is JSON naming a non-empty title" } });
10488
+ return;
10489
+ }
10490
+ session.title = title;
10491
+ session.lastTrafficAt = ctx.now();
10492
+ ctx.log?.(`rc selfhost ${session.id}: retitled through the compatibility family`);
10493
+ await ctx.answerJson(response, HTTP_STATUS.ok, compatSessionRow(session, ctx.now));
10494
+ return;
10495
+ }
10496
+ await ctx.answerJson(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the compatibility session read is a GET and its update a PATCH or PUT" } });
10497
+ return;
10498
+ }
10499
+ if ((compatTail === "archive" || compatTail === "unarchive") && method === "POST") {
10500
+ await ctx.readBody(request4.body, CONTROL_BODY_CAP_BYTES);
10501
+ if (session === void 0) {
10502
+ await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `Session not found: ${address}` } });
10503
+ return;
10504
+ }
10505
+ if (compatTail === "unarchive") {
10506
+ await ctx.answerJson(response, HTTP_STATUS.ok, compatSessionRow(session, ctx.now));
10507
+ return;
10508
+ }
10509
+ for (const sink of [...session.workerStreams, ...session.clientStreams]) {
10510
+ sink.retire();
10511
+ }
10512
+ ctx.sessions.delete(session.id);
10513
+ ctx.log?.(`rc selfhost ${session.id}: archived through the compatibility family`);
10514
+ await ctx.answerJson(response, HTTP_STATUS.ok, {});
10515
+ return;
10516
+ }
10517
+ await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the compatibility session family serves no path "${compatTail}"` } });
10518
+ }
10519
+
10270
10520
  // src/frontdoor/rcWebFetch.ts
10271
10521
  import * as dns2 from "node:dns";
10272
10522
  import * as http2 from "node:http";
@@ -10535,21 +10785,19 @@ var RC_PRESENCE_REFRESH_SECONDS = 60;
10535
10785
  var RC_SELF_HOST_MAX_BATCH_EVENTS = 100;
10536
10786
  var RC_SELF_HOST_WORKER_EPOCH = 1;
10537
10787
  var RC_STREAM_EVENT_NAME2 = "client_event";
10538
- var RC_SOURCE_WORKER = "worker";
10539
- var RC_SOURCE_CLIENT = "client";
10540
10788
  var CONFLICT_REASON_HEADER2 = "x-ccr-conflict-reason";
10541
10789
  var CONFLICT_REASON_EPOCH_STALE = "epoch_stale";
10542
- function isRecord6(value) {
10790
+ function isRecord7(value) {
10543
10791
  return typeof value === "object" && value !== null && !Array.isArray(value);
10544
10792
  }
10545
10793
  function isWebSearchRefusal(value) {
10546
10794
  return !Array.isArray(value);
10547
10795
  }
10548
- function nonEmptyString2(record, field) {
10796
+ function nonEmptyString3(record, field) {
10549
10797
  const value = record === void 0 ? void 0 : record[field];
10550
10798
  return typeof value === "string" && value !== "" ? value : void 0;
10551
10799
  }
10552
- function numberOf(record, field) {
10800
+ function numberOf2(record, field) {
10553
10801
  const value = record === void 0 ? void 0 : record[field];
10554
10802
  if (typeof value === "number" && Number.isFinite(value)) {
10555
10803
  return value;
@@ -10616,14 +10864,14 @@ async function readBody2(request4, capBytes) {
10616
10864
  });
10617
10865
  });
10618
10866
  }
10619
- function parseJsonObject2(body) {
10867
+ function parseJsonObject3(body) {
10620
10868
  let parsed;
10621
10869
  try {
10622
10870
  parsed = JSON.parse(body);
10623
10871
  } catch {
10624
10872
  return void 0;
10625
10873
  }
10626
- return isRecord6(parsed) ? parsed : void 0;
10874
+ return isRecord7(parsed) ? parsed : void 0;
10627
10875
  }
10628
10876
  function mintWorkerJwt(randomToken, issuedAt) {
10629
10877
  const encode = (value) => Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
@@ -10646,11 +10894,16 @@ function createRcSelfHostSurface(deps) {
10646
10894
  for (const session of sessions.values()) {
10647
10895
  while (session.events[0] !== void 0 && now - session.events[0].storedAt > RC_SELF_HOST_RETENTION_MS) {
10648
10896
  const dropped = session.events.shift();
10649
- const uuid = dropped === void 0 ? void 0 : nonEmptyString2(dropped.payload, "uuid");
10897
+ const uuid = dropped === void 0 ? void 0 : nonEmptyString3(dropped.payload, "uuid");
10650
10898
  if (uuid !== void 0) {
10651
10899
  session.seenPayloadUuids.delete(uuid);
10652
10900
  }
10653
10901
  }
10902
+ for (const [clientId, lastSeenAt] of session.clients) {
10903
+ if (now - lastSeenAt > RC_SELF_HOST_RETENTION_MS) {
10904
+ session.clients.delete(clientId);
10905
+ }
10906
+ }
10654
10907
  if (session.workerStreams.size === 0 && session.clientStreams.size === 0 && now - session.lastTrafficAt > RC_SELF_HOST_RETENTION_MS) {
10655
10908
  sessions.delete(session.id);
10656
10909
  }
@@ -10689,7 +10942,7 @@ function createRcSelfHostSurface(deps) {
10689
10942
  const frameOf = (event) => {
10690
10943
  const envelope = {
10691
10944
  event_id: event.eventId,
10692
- event_type: nonEmptyString2(event.payload, "type") ?? "event",
10945
+ event_type: nonEmptyString3(event.payload, "type") ?? "event",
10693
10946
  sequence_num: event.sequenceNum,
10694
10947
  source: event.source,
10695
10948
  payload: event.payload,
@@ -10703,7 +10956,7 @@ data: ${JSON.stringify(envelope)}
10703
10956
  };
10704
10957
  const holdStream = async (session, which, wantsReplay, request4, response) => {
10705
10958
  const url = new URL(request4.url, "http://127.0.0.1");
10706
- const cursor = numberOf({ value: url.searchParams.get("from_sequence_num") }, "value") ?? numberOf({ value: singleHeader2(request4.headers, "last-event-id") }, "value");
10959
+ const cursor = numberOf2({ value: url.searchParams.get("from_sequence_num") }, "value") ?? numberOf2({ value: singleHeader2(request4.headers, "last-event-id") }, "value");
10707
10960
  const replay = cursor === void 0 ? [] : session.events.filter((event) => wantsReplay(event) && event.sequenceNum > cursor);
10708
10961
  response.start(HTTP_STATUS.ok, { "content-type": "text/event-stream", "cache-control": "no-cache" });
10709
10962
  response.flush();
@@ -10732,8 +10985,8 @@ data: ${JSON.stringify(envelope)}
10732
10985
  sink.retire();
10733
10986
  response.end();
10734
10987
  };
10735
- const publish = (session, source, payload) => {
10736
- const uuid = nonEmptyString2(payload, "uuid");
10988
+ const publish = (session, source, payload, toWorker = source === RC_SOURCE_CLIENT) => {
10989
+ const uuid = nonEmptyString3(payload, "uuid");
10737
10990
  const duplicate = uuid !== void 0 && session.seenPayloadUuids.has(uuid);
10738
10991
  session.sequenceNum += 1;
10739
10992
  const event = {
@@ -10741,9 +10994,9 @@ data: ${JSON.stringify(envelope)}
10741
10994
  eventId: uuid ?? deps.newUuid(),
10742
10995
  source,
10743
10996
  // 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.
10744
- payload: source === RC_SOURCE_CLIENT ? { ...payload, server_received_wall_ms: deps.now() } : payload,
10997
+ payload: source === RC_SOURCE_WORKER ? payload : { ...payload, server_received_wall_ms: deps.now() },
10745
10998
  createdAt: new Date(deps.now()).toISOString(),
10746
- toWorker: source === RC_SOURCE_CLIENT,
10999
+ toWorker,
10747
11000
  storedAt: deps.now()
10748
11001
  };
10749
11002
  session.events.push(event);
@@ -10791,8 +11044,26 @@ data: ${JSON.stringify(envelope)}
10791
11044
  }
10792
11045
  session.lastTrafficAt = deps.now();
10793
11046
  if (tail === "") {
11047
+ if (method === "PATCH" || method === "PUT") {
11048
+ const refusal = credentialRefusal(request4.headers);
11049
+ if (refusal !== void 0) {
11050
+ await unauthorized(response, refusal);
11051
+ return;
11052
+ }
11053
+ const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11054
+ const title = nonEmptyString3(body, "title");
11055
+ if (title === void 0) {
11056
+ 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
+ return;
11058
+ }
11059
+ session.title = title;
11060
+ session.lastTrafficAt = deps.now();
11061
+ deps.log?.(`rc selfhost ${session.id}: retitled at its client's request`);
11062
+ await answerJson2(response, HTTP_STATUS.ok, {});
11063
+ return;
11064
+ }
10794
11065
  if (method !== "GET") {
10795
- await answerJson2(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the session read is a GET" } });
11066
+ 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" } });
10796
11067
  return;
10797
11068
  }
10798
11069
  await answerJson2(response, HTTP_STATUS.ok, { session: { id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), status: "active" } });
@@ -10837,17 +11108,17 @@ data: ${JSON.stringify(envelope)}
10837
11108
  return;
10838
11109
  }
10839
11110
  if (workerTail === "" && method === "PUT") {
10840
- const body = parseJsonObject2(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
10841
- const claimedEpoch = numberOf(body, "worker_epoch");
11111
+ const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11112
+ const claimedEpoch = numberOf2(body, "worker_epoch");
10842
11113
  if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
10843
11114
  await epochStale(response);
10844
11115
  return;
10845
11116
  }
10846
- const status = nonEmptyString2(body, "worker_status");
11117
+ const status = nonEmptyString3(body, "worker_status");
10847
11118
  if (status !== void 0) {
10848
11119
  session.workerStatus = status;
10849
11120
  }
10850
- if (isRecord6(body?.external_metadata)) {
11121
+ if (isRecord7(body?.external_metadata)) {
10851
11122
  session.externalMetadata = body.external_metadata;
10852
11123
  }
10853
11124
  await answerJson2(response, HTTP_STATUS.ok, {});
@@ -10864,8 +11135,8 @@ data: ${JSON.stringify(envelope)}
10864
11135
  return;
10865
11136
  }
10866
11137
  if (workerTail === "events" && method === "POST") {
10867
- const body = parseJsonObject2(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
10868
- const claimedEpoch = numberOf(body, "worker_epoch");
11138
+ const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11139
+ const claimedEpoch = numberOf2(body, "worker_epoch");
10869
11140
  if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
10870
11141
  await epochStale(response);
10871
11142
  return;
@@ -10877,7 +11148,7 @@ data: ${JSON.stringify(envelope)}
10877
11148
  }
10878
11149
  const results = [];
10879
11150
  for (const event of events) {
10880
- if (!isRecord6(event) || !isRecord6(event.payload)) {
11151
+ if (!isRecord7(event) || !isRecord7(event.payload)) {
10881
11152
  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" } });
10882
11153
  return;
10883
11154
  }
@@ -10898,8 +11169,8 @@ data: ${JSON.stringify(envelope)}
10898
11169
  return;
10899
11170
  }
10900
11171
  if (workerTail === "internal-events" && method === "POST") {
10901
- const body = parseJsonObject2(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
10902
- const claimedEpoch = numberOf(body, "worker_epoch");
11172
+ const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11173
+ const claimedEpoch = numberOf2(body, "worker_epoch");
10903
11174
  if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
10904
11175
  await epochStale(response);
10905
11176
  return;
@@ -10910,19 +11181,26 @@ data: ${JSON.stringify(envelope)}
10910
11181
  return;
10911
11182
  }
10912
11183
  for (const event of events) {
10913
- if (isRecord6(event)) {
10914
- session.internalEvents.push({ cursor: deps.newUuid(), body: event });
11184
+ if (isRecord7(event)) {
11185
+ session.internalEvents.push({ eventId: deps.newUuid(), body: event });
10915
11186
  }
10916
11187
  }
10917
11188
  await answerJson2(response, HTTP_STATUS.ok, {});
10918
11189
  return;
10919
11190
  }
10920
11191
  if (workerTail === "internal-events" && method === "GET") {
10921
- const url = new URL(request4.url, "http://127.0.0.1");
10922
- const cursor = url.searchParams.get("cursor");
10923
- const from = cursor === null ? 0 : session.internalEvents.findIndex((event) => event.cursor === cursor) + 1;
10924
- const page = session.internalEvents.slice(from);
10925
- await answerJson2(response, HTTP_STATUS.ok, { data: page.map((event) => event.body), next_cursor: page.length === 0 ? void 0 : page[page.length - 1]?.cursor });
11192
+ const query = new URL(request4.url, "http://127.0.0.1").searchParams;
11193
+ if (query.get("session_agent_id") !== null || query.get("subagents") !== null) {
11194
+ await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "this surface keeps one internal-events channel per session, so an agent-scoped read has no subset to serve" } });
11195
+ return;
11196
+ }
11197
+ const page = pageOfRcChannel(request4.url, session.internalEvents, (event) => event.eventId, (event) => ({ event_id: event.eventId, payload: event.body }));
11198
+ if ("error" in page) {
11199
+ const anchorUnknown = query.get("after_event_id") !== null && page.error.startsWith("the anchor names an event id");
11200
+ await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: anchorUnknown ? "after_event_id_not_found" : "invalid_request_error", message: page.error } });
11201
+ return;
11202
+ }
11203
+ await answerJson2(response, HTTP_STATUS.ok, page);
10926
11204
  return;
10927
11205
  }
10928
11206
  if (workerTail === "diagnostics" && method === "POST") {
@@ -10931,8 +11209,8 @@ data: ${JSON.stringify(envelope)}
10931
11209
  return;
10932
11210
  }
10933
11211
  if (workerTail === "web-fetch" && method === "POST") {
10934
- const body = parseJsonObject2(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
10935
- const target = nonEmptyString2(body, "url");
11212
+ const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11213
+ const target = nonEmptyString3(body, "url");
10936
11214
  if (target === void 0) {
10937
11215
  await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-fetch carries one url" } });
10938
11216
  return;
@@ -10954,8 +11232,8 @@ data: ${JSON.stringify(envelope)}
10954
11232
  return;
10955
11233
  }
10956
11234
  if (workerTail === "web-search" && method === "POST") {
10957
- const body = parseJsonObject2(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
10958
- const query = nonEmptyString2(body, "query");
11235
+ const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11236
+ const query = nonEmptyString3(body, "query");
10959
11237
  if (query === void 0) {
10960
11238
  await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-search carries one query" } });
10961
11239
  return;
@@ -10966,7 +11244,7 @@ data: ${JSON.stringify(envelope)}
10966
11244
  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" } });
10967
11245
  return;
10968
11246
  }
10969
- const searchProfile = nonEmptyString2(body, "search_profile");
11247
+ const searchProfile = nonEmptyString3(body, "search_profile");
10970
11248
  const backend = deps.webSearch;
10971
11249
  if (backend === void 0) {
10972
11250
  deps.log?.(`rc selfhost ${session.id}: worker web-search of "${query}" refused: no search backend serves this door`);
@@ -10991,7 +11269,7 @@ data: ${JSON.stringify(envelope)}
10991
11269
  return;
10992
11270
  }
10993
11271
  if (tail === "events" && method === "POST") {
10994
- const body = parseJsonObject2(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11272
+ const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
10995
11273
  const events = body?.events;
10996
11274
  if (!Array.isArray(events) || events.length === 0 || events.length > RC_SELF_HOST_MAX_BATCH_EVENTS) {
10997
11275
  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` } });
@@ -10999,7 +11277,7 @@ data: ${JSON.stringify(envelope)}
10999
11277
  }
11000
11278
  const results = [];
11001
11279
  for (const event of events) {
11002
- if (!isRecord6(event) || !isRecord6(event.payload)) {
11280
+ if (!isRecord7(event) || !isRecord7(event.payload)) {
11003
11281
  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" } });
11004
11282
  return;
11005
11283
  }
@@ -11011,26 +11289,14 @@ data: ${JSON.stringify(envelope)}
11011
11289
  return;
11012
11290
  }
11013
11291
  if (tail === "events" && method === "GET") {
11014
- await answerJson2(response, HTTP_STATUS.ok, { data: session.events.map((event) => ({ event_id: event.eventId, event_type: nonEmptyString2(event.payload, "type") ?? "event", sequence_num: event.sequenceNum, source: event.source, payload: event.payload, created_at: event.createdAt })) });
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 })) });
11015
11293
  return;
11016
11294
  }
11017
11295
  if (tail === "events/stream" && method === "GET") {
11018
11296
  await holdStream(session, "clientStreams", () => true, request4, response);
11019
11297
  return;
11020
11298
  }
11021
- if (tail === "client/presence" && method === "POST") {
11022
- await readBody2(request4.body, CONTROL_BODY_CAP_BYTES);
11023
- await answerJson2(response, HTTP_STATUS.ok, { refresh_after_seconds: RC_PRESENCE_REFRESH_SECONDS });
11024
- return;
11025
- }
11026
- if (tail === "mark_read" && method === "POST") {
11027
- await readBody2(request4.body, CONTROL_BODY_CAP_BYTES);
11028
- await answerJson2(response, HTTP_STATUS.ok, {});
11029
- return;
11030
- }
11031
- if (tail === "teleport-events" && method === "POST") {
11032
- await readBody2(request4.body, CONTROL_BODY_CAP_BYTES);
11033
- await answerJson2(response, HTTP_STATUS.ok, {});
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)) {
11034
11300
  return;
11035
11301
  }
11036
11302
  await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the self-hosted Remote Control surface serves no path "${tail}"` } });
@@ -11047,17 +11313,8 @@ data: ${JSON.stringify(envelope)}
11047
11313
  await noCredential(response);
11048
11314
  return;
11049
11315
  }
11050
- if (pathname === RC_COMPAT_SESSIONS_PATH) {
11051
- if (method !== "GET") {
11052
- await answerJson2(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the compatibility session list is a GET" } });
11053
- return;
11054
- }
11055
- const listRefusal = credentialRefusal(request4.headers);
11056
- if (listRefusal !== void 0) {
11057
- await unauthorized(response, listRefusal);
11058
- return;
11059
- }
11060
- await answerJson2(response, HTTP_STATUS.ok, { data: [] });
11316
+ 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);
11061
11318
  return;
11062
11319
  }
11063
11320
  if (pathname !== RC_SESSIONS_PATH_PREFIX2 && !pathname.startsWith(`${RC_SESSIONS_PATH_PREFIX2}/`)) {
@@ -11071,11 +11328,11 @@ data: ${JSON.stringify(envelope)}
11071
11328
  await unauthorized(response, createRefusal);
11072
11329
  return;
11073
11330
  }
11074
- const body = parseJsonObject2(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11331
+ const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11075
11332
  const session = {
11076
11333
  id: `${RC_SESSION_ID_PREFIX2}${deps.newUuid()}`,
11077
11334
  createdAt: deps.now(),
11078
- title: nonEmptyString2(body, "title"),
11335
+ title: nonEmptyString3(body, "title"),
11079
11336
  workerJwts: /* @__PURE__ */ new Set(),
11080
11337
  sequenceNum: 0,
11081
11338
  events: [],
@@ -11083,6 +11340,7 @@ data: ${JSON.stringify(envelope)}
11083
11340
  workerStreams: /* @__PURE__ */ new Set(),
11084
11341
  clientStreams: /* @__PURE__ */ new Set(),
11085
11342
  internalEvents: [],
11343
+ clients: /* @__PURE__ */ new Map(),
11086
11344
  workerStatus: void 0,
11087
11345
  externalMetadata: void 0,
11088
11346
  lastTrafficAt: deps.now()
@@ -11127,7 +11385,7 @@ data: ${JSON.stringify(envelope)}
11127
11385
  const pathname = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
11128
11386
  if (host === RC_SELF_HOST_OAUTH_HOST) {
11129
11387
  if (method === "POST" && pathname === "/v1/oauth/token") {
11130
- const body = parseJsonObject2(await readBody2(request4, CONTROL_BODY_CAP_BYTES));
11388
+ const body = parseJsonObject3(await readBody2(request4, CONTROL_BODY_CAP_BYTES));
11131
11389
  const record = deps.credentialRecord();
11132
11390
  if (record === void 0) {
11133
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" });
@@ -11213,7 +11471,7 @@ var RC_SELF_HOST_RECORD_DIR = "rc-selfhost";
11213
11471
  var RC_SELF_HOST_RECORD_FILE = "credential.json";
11214
11472
  var SELF_HOST_EMAIL = "selfhost@agent-shim.invalid";
11215
11473
  var SELF_HOST_SUBSCRIPTION_TYPE = "max";
11216
- function isRecord7(value) {
11474
+ function isRecord8(value) {
11217
11475
  return typeof value === "object" && value !== null && !Array.isArray(value);
11218
11476
  }
11219
11477
  function readJsonObject(fs12, file) {
@@ -11227,7 +11485,7 @@ function readJsonObject(fs12, file) {
11227
11485
  } catch {
11228
11486
  return void 0;
11229
11487
  }
11230
- return isRecord7(parsed) ? parsed : void 0;
11488
+ return isRecord8(parsed) ? parsed : void 0;
11231
11489
  }
11232
11490
  function readRcSelfHostRecord(fs12, frontdoorDir) {
11233
11491
  const parsed = readJsonObject(fs12, path28.join(frontdoorDir, RC_SELF_HOST_RECORD_DIR, RC_SELF_HOST_RECORD_FILE));
@@ -11251,7 +11509,7 @@ function mintRcSelfHostCredential(deps) {
11251
11509
  const recordFile = path28.join(recordDir, RC_SELF_HOST_RECORD_FILE);
11252
11510
  const previousRecord = readRcSelfHostRecord(deps.fs, deps.frontdoorDir);
11253
11511
  const existingCredentials = readJsonObject(deps.fs, credentialsFile);
11254
- const existingOauth = isRecord7(existingCredentials?.claudeAiOauth) ? existingCredentials.claudeAiOauth : void 0;
11512
+ const existingOauth = isRecord8(existingCredentials?.claudeAiOauth) ? existingCredentials.claudeAiOauth : void 0;
11255
11513
  const existingToken = typeof existingOauth?.accessToken === "string" && existingOauth.accessToken !== "" ? existingOauth.accessToken : void 0;
11256
11514
  const existingIsOwn = existingToken !== void 0 && previousRecord?.accessToken === existingToken;
11257
11515
  if (existingToken !== void 0 && !existingIsOwn && !deps.force) {
@@ -11268,7 +11526,7 @@ function mintRcSelfHostCredential(deps) {
11268
11526
  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)}
11269
11527
  `);
11270
11528
  const claudeJson = readJsonObject(deps.fs, claudeJsonFile) ?? {};
11271
- const existingBlock = (value) => isRecord7(value) ? value : {};
11529
+ const existingBlock = (value) => isRecord8(value) ? value : {};
11272
11530
  deps.fs.writeFilePrivate(claudeJsonFile, `${JSON.stringify({
11273
11531
  ...claudeJson,
11274
11532
  oauthAccount: {
@@ -11467,7 +11725,7 @@ import path30 from "node:path";
11467
11725
  // package.json
11468
11726
  var package_default = {
11469
11727
  name: "agent-shim",
11470
- version: "8.24.0",
11728
+ version: "8.25.0",
11471
11729
  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.",
11472
11730
  license: "MIT",
11473
11731
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -12757,6 +13015,7 @@ export {
12757
13015
  buildRcGetUsagePayload,
12758
13016
  buildRcInterruptPayload,
12759
13017
  buildRcKeepAlivePayload,
13018
+ buildRcMarkReadBody,
12760
13019
  buildRcMcpAuthenticatePayload,
12761
13020
  buildRcMcpOAuthCallbackUrlPayload,
12762
13021
  buildRcMcpReconnectPayload,
@@ -12903,6 +13162,7 @@ export {
12903
13162
  startConnectServer,
12904
13163
  submitRcSessionMcpOAuthCallbackUrl,
12905
13164
  suggestRcSessionFiles,
13165
+ teleportRcSession,
12906
13166
  topLevelNames,
12907
13167
  updateDirectoryRule,
12908
13168
  updateProvider,