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.cjs CHANGED
@@ -141,6 +141,7 @@ __export(index_exports, {
141
141
  buildRcGetUsagePayload: () => buildRcGetUsagePayload,
142
142
  buildRcInterruptPayload: () => buildRcInterruptPayload,
143
143
  buildRcKeepAlivePayload: () => buildRcKeepAlivePayload,
144
+ buildRcMarkReadBody: () => buildRcMarkReadBody,
144
145
  buildRcMcpAuthenticatePayload: () => buildRcMcpAuthenticatePayload,
145
146
  buildRcMcpOAuthCallbackUrlPayload: () => buildRcMcpOAuthCallbackUrlPayload,
146
147
  buildRcMcpReconnectPayload: () => buildRcMcpReconnectPayload,
@@ -287,6 +288,7 @@ __export(index_exports, {
287
288
  startConnectServer: () => startConnectServer,
288
289
  submitRcSessionMcpOAuthCallbackUrl: () => submitRcSessionMcpOAuthCallbackUrl,
289
290
  suggestRcSessionFiles: () => suggestRcSessionFiles,
291
+ teleportRcSession: () => teleportRcSession,
290
292
  topLevelNames: () => topLevelNames,
291
293
  updateDirectoryRule: () => updateDirectoryRule,
292
294
  updateProvider: () => updateProvider,
@@ -3504,9 +3506,6 @@ var HTTP_STATUS = {
3504
3506
  badGateway: 502
3505
3507
  };
3506
3508
 
3507
- // src/frontdoor/rcControl.ts
3508
- var https = __toESM(require("node:https"), 1);
3509
-
3510
3509
  // src/frontdoor/rcSessions.ts
3511
3510
  var RC_SESSIONS_PATH_PREFIX = "/v1/code/sessions";
3512
3511
  var RC_SESSION_ID_PREFIX = "cse_";
@@ -4104,7 +4103,7 @@ async function answerRcControlRequest(deps, sessionId, requestId, decision) {
4104
4103
  }
4105
4104
  return result;
4106
4105
  }
4107
- async function deliverRcClientPayload(deps, sessionId, payload, action) {
4106
+ async function deliverRcClientPayload(deps, sessionId, payload, action, to = "events") {
4108
4107
  const credential = deps.credentialOf(sessionId);
4109
4108
  if (credential === void 0) {
4110
4109
  return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
@@ -4115,7 +4114,7 @@ async function deliverRcClientPayload(deps, sessionId, payload, action) {
4115
4114
  const body = JSON.stringify(buildRcEventWriteBody(payload));
4116
4115
  let answer;
4117
4116
  try {
4118
- answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
4117
+ answer = await (to === "teleport-events" ? deps.dial.writeTeleportEvents(sessionId, rcWriteHeaders(credential), body) : deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body));
4119
4118
  } catch (error) {
4120
4119
  return { ok: false, message: `the door could not reach the API host to ${action}: ${error instanceof Error ? error.message : String(error)}` };
4121
4120
  }
@@ -4209,8 +4208,18 @@ async function submitRcSessionMcpOAuthCallbackUrl(deps, sessionId, serverName, c
4209
4208
  async function sendRcKeepAlive(deps, sessionId) {
4210
4209
  return await deliverRcClientPayload(deps, sessionId, buildRcKeepAlivePayload(), `keep session ${sessionId} alive`);
4211
4210
  }
4211
+ async function teleportRcSession(deps, sessionId, marker) {
4212
+ if (marker === "") {
4213
+ 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" };
4214
+ }
4215
+ return await deliverRcClientPayload(deps, sessionId, buildRcUserMessagePayload(deps.newUuid(), sessionId, marker), `teleport a marker into session ${sessionId}`, "teleport-events");
4216
+ }
4217
+ function buildRcMarkReadBody(eventId) {
4218
+ return eventId === void 0 ? {} : { event_id: eventId };
4219
+ }
4212
4220
 
4213
4221
  // src/frontdoor/rcControl.ts
4222
+ var https = __toESM(require("node:https"), 1);
4214
4223
  var RC_PERMISSION_MODE_LIST = RC_PERMISSION_MODES.join(", ");
4215
4224
  var CONTROL_PATH_PREFIX = "/__agent-shim/rc";
4216
4225
  var CONTROL_BODY_CAP_BYTES = 10485760;
@@ -4384,6 +4393,15 @@ function createRcControlHandler(deps) {
4384
4393
  const callbackUrl = bodyString(body, "callbackUrl");
4385
4394
  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) };
4386
4395
  }
4396
+ },
4397
+ {
4398
+ name: "teleport",
4399
+ path: "teleport",
4400
+ read: (body) => {
4401
+ const session = bodyString(body, "session");
4402
+ const marker = bodyString(body, "marker");
4403
+ 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) };
4404
+ }
4387
4405
  }
4388
4406
  ];
4389
4407
  return (request4, response) => {
@@ -4747,7 +4765,8 @@ function frontDoorRcControl(transport, token) {
4747
4765
  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}`),
4748
4766
  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}`),
4749
4767
  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}`),
4750
- 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}`)
4768
+ 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}`),
4769
+ 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}`)
4751
4770
  };
4752
4771
  }
4753
4772
 
@@ -4894,36 +4913,38 @@ function tlsFacts(options) {
4894
4913
  }
4895
4914
  function realRcEventDial(target = { host: CONNECT_INTERCEPT_HOST, port: HTTPS_PORT }) {
4896
4915
  const agent = new ExemptTlsAgent();
4897
- return {
4898
- writeEvents: async (sessionId, headers, body) => await new Promise((resolve, reject) => {
4899
- const options = {
4900
- host: target.host,
4901
- port: target.port,
4902
- method: "POST",
4903
- path: `/v1/code/sessions/${encodeURIComponent(sessionId)}/events`,
4904
- // 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.
4905
- headers: { ...headers, host: target.servername ?? target.host, "content-length": String(Buffer.byteLength(body, "utf8")) },
4906
- agent
4907
- };
4908
- options.servername = target.servername ?? target.host;
4909
- if (target.ca !== void 0) {
4910
- options.ca = [...target.ca];
4911
- }
4912
- if (target.rejectUnauthorized !== void 0) {
4913
- options.rejectUnauthorized = target.rejectUnauthorized;
4914
- }
4915
- const request4 = https2.request(options, (response) => {
4916
- const chunks = [];
4917
- response.on("data", (chunk) => {
4918
- chunks.push(chunk);
4919
- });
4920
- response.on("end", () => {
4921
- resolve({ status: response.statusCode ?? HTTP_BAD_GATEWAY, body: Buffer.concat(chunks).toString("utf8") });
4922
- });
4916
+ const post = async (path32, headers, body) => await new Promise((resolve, reject) => {
4917
+ const options = {
4918
+ host: target.host,
4919
+ port: target.port,
4920
+ method: "POST",
4921
+ path: path32,
4922
+ // 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.
4923
+ headers: { ...headers, host: target.servername ?? target.host, "content-length": String(Buffer.byteLength(body, "utf8")) },
4924
+ agent
4925
+ };
4926
+ options.servername = target.servername ?? target.host;
4927
+ if (target.ca !== void 0) {
4928
+ options.ca = [...target.ca];
4929
+ }
4930
+ if (target.rejectUnauthorized !== void 0) {
4931
+ options.rejectUnauthorized = target.rejectUnauthorized;
4932
+ }
4933
+ const request4 = https2.request(options, (response) => {
4934
+ const chunks = [];
4935
+ response.on("data", (chunk) => {
4936
+ chunks.push(chunk);
4923
4937
  });
4924
- request4.once("error", reject);
4925
- request4.end(body);
4926
- })
4938
+ response.on("end", () => {
4939
+ resolve({ status: response.statusCode ?? HTTP_BAD_GATEWAY, body: Buffer.concat(chunks).toString("utf8") });
4940
+ });
4941
+ });
4942
+ request4.once("error", reject);
4943
+ request4.end(body);
4944
+ });
4945
+ return {
4946
+ writeEvents: async (sessionId, headers, body) => await post(`/v1/code/sessions/${encodeURIComponent(sessionId)}/events`, headers, body),
4947
+ writeTeleportEvents: async (sessionId, headers, body) => await post(`/v1/code/sessions/${encodeURIComponent(sessionId)}/teleport-events`, headers, body)
4927
4948
  };
4928
4949
  }
4929
4950
  var NO_CHUNKS = {
@@ -4941,7 +4962,8 @@ function lateRcEventDial(resolve) {
4941
4962
  return built.dial;
4942
4963
  };
4943
4964
  return {
4944
- writeEvents: async (sessionId, headers, body) => await dialFor().writeEvents(sessionId, headers, body)
4965
+ writeEvents: async (sessionId, headers, body) => await dialFor().writeEvents(sessionId, headers, body),
4966
+ writeTeleportEvents: async (sessionId, headers, body) => await dialFor().writeTeleportEvents(sessionId, headers, body)
4945
4967
  };
4946
4968
  }
4947
4969
  function lateRcStreamDial(resolve) {
@@ -4954,7 +4976,8 @@ function lateRcStreamDial(resolve) {
4954
4976
  return built.dial;
4955
4977
  };
4956
4978
  return {
4957
- announcePresence: async (sessionId, headers, clientId) => await dialFor().announcePresence(sessionId, headers, clientId),
4979
+ announcePresence: async (sessionId, headers, clientId, clear) => await dialFor().announcePresence(sessionId, headers, clientId, clear),
4980
+ markRead: async (sessionId, headers, eventId) => await dialFor().markRead(sessionId, headers, eventId),
4958
4981
  openStream: async (sessionId, headers, resume, signal) => await dialFor().openStream(sessionId, headers, resume, signal)
4959
4982
  };
4960
4983
  }
@@ -4996,11 +5019,16 @@ function realRcStreamDial(target = { host: CONNECT_INTERCEPT_HOST, port: HTTPS_P
4996
5019
  return Buffer.concat(chunks).toString("utf8");
4997
5020
  };
4998
5021
  return {
4999
- announcePresence: async (sessionId, headers, clientId) => {
5000
- const body = JSON.stringify({ client_id: clientId, clear: false });
5022
+ announcePresence: async (sessionId, headers, clientId, clear) => {
5023
+ const body = JSON.stringify({ client_id: clientId, clear });
5001
5024
  const { status, response } = await request4("POST", `/v1/code/sessions/${encodeURIComponent(sessionId)}/client/presence`, { ...headers, "content-type": "application/json" }, body, new AbortController().signal);
5002
5025
  return { status, body: await readAll(response) };
5003
5026
  },
5027
+ markRead: async (sessionId, headers, eventId) => {
5028
+ const body = JSON.stringify(buildRcMarkReadBody(eventId));
5029
+ const { status, response } = await request4("POST", `/v1/code/sessions/${encodeURIComponent(sessionId)}/mark_read`, { ...headers, "content-type": "application/json" }, body, new AbortController().signal);
5030
+ return { status, body: await readAll(response) };
5031
+ },
5004
5032
  openStream: async (sessionId, headers, resume, signal) => {
5005
5033
  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)}`;
5006
5034
  const streamHeaders = resume === void 0 ? headers : { ...headers, "last-event-id": String(resume.fromSequenceNum) };
@@ -5411,6 +5439,10 @@ var RcMcpOAuthCallbackUrlInputSchema = import_zod4.z.strictObject({
5411
5439
  serverName: import_zod4.z.string().min(1),
5412
5440
  callbackUrl: import_zod4.z.string().min(1)
5413
5441
  });
5442
+ var RcTeleportInputSchema = import_zod4.z.strictObject({
5443
+ session: import_zod4.z.string().min(1),
5444
+ marker: import_zod4.z.string().min(1)
5445
+ });
5414
5446
  var RcListOutputSchema = import_zod4.z.strictObject({ sessions: import_zod4.z.readonly(import_zod4.z.array(RcSessionSummarySchema)) });
5415
5447
  var RcStatusOutputSchema = import_zod4.z.strictObject({ statuses: import_zod4.z.readonly(import_zod4.z.array(RcSessionStatusSchema)) });
5416
5448
  var RcPendingOutputSchema = import_zod4.z.strictObject({ pending: import_zod4.z.readonly(import_zod4.z.array(RcPendingRequestSummarySchema)) });
@@ -5495,6 +5527,7 @@ function createRcApiRouter(deps) {
5495
5527
  mcpReconnect: authed.input(RcMcpReconnectInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpReconnect(input.session, input.serverName)) })),
5496
5528
  mcpAuthenticate: authed.input(RcMcpAuthenticateInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpAuthenticate(input.session, input.serverName, input.redirectUri)) })),
5497
5529
  mcpOAuthCallbackUrl: authed.input(RcMcpOAuthCallbackUrlInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpOAuthCallbackUrl(input.session, input.serverName, input.callbackUrl)) })),
5530
+ teleport: authed.input(RcTeleportInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.teleport(input.session, input.marker)) })),
5498
5531
  subscribe: authed.input(RcSessionQuerySchema).output((0, import_server2.eventIterator)(RcStreamEventSchema)).handler(async function* ({ input, signal }) {
5499
5532
  const queue = [];
5500
5533
  let wake;
@@ -10451,9 +10484,26 @@ function createRcStreamHub(deps) {
10451
10484
  }
10452
10485
  return headers;
10453
10486
  };
10487
+ let receiptFailureLogged = false;
10488
+ const sendReceipt = (attachment, headers, eventId) => {
10489
+ if (eventId === void 0) {
10490
+ return;
10491
+ }
10492
+ deps.dial.markRead(attachment.sessionId, headers, eventId).then(
10493
+ () => {
10494
+ receiptFailureLogged = false;
10495
+ },
10496
+ (error) => {
10497
+ if (!receiptFailureLogged) {
10498
+ receiptFailureLogged = true;
10499
+ 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`);
10500
+ }
10501
+ }
10502
+ );
10503
+ };
10454
10504
  const attempt = async (attachment, credential) => {
10455
10505
  const headers = streamHeaders(credential);
10456
- const presence = await deps.dial.announcePresence(attachment.sessionId, headers, attachment.clientId);
10506
+ const presence = await deps.dial.announcePresence(attachment.sessionId, headers, attachment.clientId, false);
10457
10507
  if (presence.status === HTTP_STATUS.unauthorized) {
10458
10508
  return "unauthorized";
10459
10509
  }
@@ -10473,6 +10523,7 @@ function createRcStreamHub(deps) {
10473
10523
  const parser = createSseParser();
10474
10524
  for await (const chunk of answer.chunks) {
10475
10525
  attachment.controller.signal.throwIfAborted();
10526
+ let lastEventId;
10476
10527
  for (const parsed of parser.write(Buffer.from(chunk).toString("utf8"))) {
10477
10528
  const envelope = parseRcStreamEnvelope(parsed);
10478
10529
  if (envelope === void 0) {
@@ -10480,10 +10531,21 @@ function createRcStreamHub(deps) {
10480
10531
  }
10481
10532
  deps.fileStreamEvent(attachment.sessionId, envelope);
10482
10533
  deps.fanout.publish({ session: attachment.sessionId, envelope });
10534
+ if (typeof envelope.event_id === "string" && envelope.event_type !== "mark_read") {
10535
+ lastEventId = envelope.event_id;
10536
+ }
10483
10537
  }
10538
+ sendReceipt(attachment, headers, lastEventId);
10484
10539
  }
10485
10540
  return "dropped";
10486
10541
  };
10542
+ const clearPresence = (attachment, headers) => {
10543
+ if (headers === void 0) {
10544
+ return;
10545
+ }
10546
+ void deps.dial.announcePresence(attachment.sessionId, headers, attachment.clientId, true).catch(() => {
10547
+ });
10548
+ };
10487
10549
  const runLoop = async (attachment) => {
10488
10550
  let retriedUnauthorized = false;
10489
10551
  for (; ; ) {
@@ -10495,6 +10557,7 @@ function createRcStreamHub(deps) {
10495
10557
  if (!live || credential?.authorization === void 0) {
10496
10558
  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"})`);
10497
10559
  persistCursor(attachment.sessionId);
10560
+ clearPresence(attachment, credential === void 0 ? void 0 : streamHeaders(credential));
10498
10561
  if (attachments.get(attachment.sessionId) === attachment) {
10499
10562
  attachments.delete(attachment.sessionId);
10500
10563
  }
@@ -10557,6 +10620,8 @@ function createRcStreamHub(deps) {
10557
10620
  close: () => {
10558
10621
  for (const attachment of attachments.values()) {
10559
10622
  persistCursor(attachment.sessionId);
10623
+ const credential = deps.credentialOf(attachment.sessionId);
10624
+ clearPresence(attachment, credential?.authorization === void 0 ? void 0 : streamHeaders(credential));
10560
10625
  attachment.stop();
10561
10626
  }
10562
10627
  attachments.clear();
@@ -10564,6 +10629,193 @@ function createRcStreamHub(deps) {
10564
10629
  };
10565
10630
  }
10566
10631
 
10632
+ // src/frontdoor/rcSelfHostEndpoints.ts
10633
+ var RC_SOURCE_WORKER = "worker";
10634
+ var RC_SOURCE_CLIENT = "client";
10635
+ function isRecord6(value) {
10636
+ return typeof value === "object" && value !== null && !Array.isArray(value);
10637
+ }
10638
+ function parseJsonObject2(body) {
10639
+ let parsed;
10640
+ try {
10641
+ parsed = JSON.parse(body);
10642
+ } catch {
10643
+ return void 0;
10644
+ }
10645
+ return isRecord6(parsed) ? parsed : void 0;
10646
+ }
10647
+ function nonEmptyString2(record, field) {
10648
+ const value = record === void 0 ? void 0 : record[field];
10649
+ return typeof value === "string" && value !== "" ? value : void 0;
10650
+ }
10651
+ function numberOf(record, field) {
10652
+ const value = record === void 0 ? void 0 : record[field];
10653
+ if (typeof value === "number" && Number.isFinite(value)) {
10654
+ return value;
10655
+ }
10656
+ if (typeof value === "string" && /^[0-9]+$/.test(value)) {
10657
+ return Number(value);
10658
+ }
10659
+ return void 0;
10660
+ }
10661
+ function pageOfRcChannel(url, rows, idOf, rowOf) {
10662
+ const query = new URL(url, "http://127.0.0.1").searchParams;
10663
+ const anchor = query.get("after_event_id") ?? query.get("cursor");
10664
+ const rawLimit = query.get("limit");
10665
+ const limit = rawLimit === null ? void 0 : numberOf({ value: rawLimit }, "value");
10666
+ if (limit !== void 0 && limit < 1) {
10667
+ return { error: "the limit names a whole number of rows of at least one" };
10668
+ }
10669
+ const from = anchor === null ? 0 : rows.findIndex((row) => idOf(row) === anchor) + 1;
10670
+ if (anchor !== null && from === 0) {
10671
+ return { error: `the anchor names an event id this surface holds no row for: ${anchor}` };
10672
+ }
10673
+ const page = limit === void 0 ? rows.slice(from) : rows.slice(from, from + limit);
10674
+ const last = page[page.length - 1];
10675
+ return { data: page.map(rowOf), ...last === void 0 ? {} : { next_cursor: idOf(last) } };
10676
+ }
10677
+ async function serveRcClientChannels(ctx, tail, method, maxBatchEvents) {
10678
+ const { request: request4, response, session } = ctx;
10679
+ if (tail === "client/presence" && method === "POST") {
10680
+ const body = parseJsonObject2(await ctx.readBody(request4.body, CONTROL_BODY_CAP_BYTES));
10681
+ const clientId = nonEmptyString2(body, "client_id");
10682
+ const clear = body?.clear;
10683
+ if (clientId === void 0 || clear !== void 0 && typeof clear !== "boolean") {
10684
+ 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" } });
10685
+ return true;
10686
+ }
10687
+ if (clear === true) {
10688
+ session.clients.delete(clientId);
10689
+ ctx.log?.(`rc selfhost ${session.id}: client ${clientId} cleared its presence`);
10690
+ } else {
10691
+ session.clients.set(clientId, ctx.now());
10692
+ ctx.log?.(`rc selfhost ${session.id}: client ${clientId} announced its presence${session.clients.size === 1 ? "" : ` (${String(session.clients.size)} clients now announced)`}`);
10693
+ }
10694
+ await ctx.answerJson(response, HTTP_STATUS.ok, { refresh_after_seconds: ctx.presenceRefreshSeconds });
10695
+ return true;
10696
+ }
10697
+ if (tail === "mark_read" && method === "POST") {
10698
+ const body = parseJsonObject2(await ctx.readBody(request4.body, CONTROL_BODY_CAP_BYTES));
10699
+ const eventId = nonEmptyString2(body, "event_id");
10700
+ const clientId = nonEmptyString2(body, "client_id");
10701
+ if (body !== void 0 && "event_id" in body && eventId === void 0 || "client_id" in (body ?? {}) && clientId === void 0) {
10702
+ 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" } });
10703
+ return true;
10704
+ }
10705
+ 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);
10706
+ 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)}`);
10707
+ await ctx.answerJson(response, HTTP_STATUS.ok, {});
10708
+ return true;
10709
+ }
10710
+ if (tail === "teleport-events" && method === "POST") {
10711
+ const body = parseJsonObject2(await ctx.readBody(request4.body, CONTROL_BODY_CAP_BYTES));
10712
+ const events = body?.events;
10713
+ if (!Array.isArray(events) || events.length === 0 || events.length > maxBatchEvents) {
10714
+ 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` } });
10715
+ return true;
10716
+ }
10717
+ const results = [];
10718
+ for (const event of events) {
10719
+ if (!isRecord6(event) || !isRecord6(event.payload)) {
10720
+ 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" } });
10721
+ return true;
10722
+ }
10723
+ const published = ctx.publish(session, RC_SOURCE_CLIENT, event.payload);
10724
+ results.push({ sequence_num: String(published.sequenceNum), duplicate: published.duplicate });
10725
+ }
10726
+ ctx.sweep();
10727
+ ctx.log?.(`rc selfhost ${session.id}: teleport write published ${String(results.length)} marker event(s)`);
10728
+ await ctx.answerJson(response, HTTP_STATUS.ok, { results });
10729
+ return true;
10730
+ }
10731
+ if (tail === "teleport-events" && method === "GET") {
10732
+ const page = pageOfRcChannel(request4.url, session.events, (event) => event.eventId, (event) => ({ event_id: event.eventId, payload: event.payload }));
10733
+ if ("error" in page) {
10734
+ await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: page.error } });
10735
+ return true;
10736
+ }
10737
+ await ctx.answerJson(response, HTTP_STATUS.ok, page);
10738
+ return true;
10739
+ }
10740
+ return false;
10741
+ }
10742
+ 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" });
10743
+ async function serveRcCompatSessions(ctx, pathname, compatPath) {
10744
+ const { request: request4, response } = ctx;
10745
+ const method = request4.method.toUpperCase();
10746
+ if (pathname === compatPath) {
10747
+ if (method !== "GET") {
10748
+ await ctx.answerJson(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the compatibility session list is a GET" } });
10749
+ return;
10750
+ }
10751
+ const listRefusal = ctx.credentialRefusal(request4.headers);
10752
+ if (listRefusal !== void 0) {
10753
+ await ctx.unauthorized(response, listRefusal);
10754
+ return;
10755
+ }
10756
+ await ctx.answerJson(response, HTTP_STATUS.ok, { data: [...ctx.sessions.values()].map((session2) => compatSessionRow(session2, ctx.now)) });
10757
+ return;
10758
+ }
10759
+ const rest = pathname.slice(`${compatPath}/`.length);
10760
+ const slash = rest.indexOf("/");
10761
+ const address = slash === -1 ? rest : rest.slice(0, slash);
10762
+ const compatTail = slash === -1 ? "" : rest.slice(slash + 1);
10763
+ const refusal = ctx.credentialRefusal(request4.headers);
10764
+ if (refusal !== void 0) {
10765
+ await ctx.unauthorized(response, refusal);
10766
+ return;
10767
+ }
10768
+ const session = ctx.sessions.get(address) ?? ctx.sessions.get(`cse_${address}`);
10769
+ if (compatTail === "") {
10770
+ if (method === "GET") {
10771
+ if (session === void 0) {
10772
+ await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `Session not found: ${address}` } });
10773
+ return;
10774
+ }
10775
+ await ctx.answerJson(response, HTTP_STATUS.ok, compatSessionRow(session, ctx.now));
10776
+ return;
10777
+ }
10778
+ if (method === "PATCH" || method === "PUT") {
10779
+ if (session === void 0) {
10780
+ await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `Session not found: ${address}` } });
10781
+ return;
10782
+ }
10783
+ const body = parseJsonObject2(await ctx.readBody(request4.body, CONTROL_BODY_CAP_BYTES));
10784
+ const title = nonEmptyString2(body, "title");
10785
+ if (title === void 0) {
10786
+ 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" } });
10787
+ return;
10788
+ }
10789
+ session.title = title;
10790
+ session.lastTrafficAt = ctx.now();
10791
+ ctx.log?.(`rc selfhost ${session.id}: retitled through the compatibility family`);
10792
+ await ctx.answerJson(response, HTTP_STATUS.ok, compatSessionRow(session, ctx.now));
10793
+ return;
10794
+ }
10795
+ 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" } });
10796
+ return;
10797
+ }
10798
+ if ((compatTail === "archive" || compatTail === "unarchive") && method === "POST") {
10799
+ await ctx.readBody(request4.body, CONTROL_BODY_CAP_BYTES);
10800
+ if (session === void 0) {
10801
+ await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `Session not found: ${address}` } });
10802
+ return;
10803
+ }
10804
+ if (compatTail === "unarchive") {
10805
+ await ctx.answerJson(response, HTTP_STATUS.ok, compatSessionRow(session, ctx.now));
10806
+ return;
10807
+ }
10808
+ for (const sink of [...session.workerStreams, ...session.clientStreams]) {
10809
+ sink.retire();
10810
+ }
10811
+ ctx.sessions.delete(session.id);
10812
+ ctx.log?.(`rc selfhost ${session.id}: archived through the compatibility family`);
10813
+ await ctx.answerJson(response, HTTP_STATUS.ok, {});
10814
+ return;
10815
+ }
10816
+ await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the compatibility session family serves no path "${compatTail}"` } });
10817
+ }
10818
+
10567
10819
  // src/frontdoor/rcWebFetch.ts
10568
10820
  var dns2 = __toESM(require("node:dns"), 1);
10569
10821
  var http2 = __toESM(require("node:http"), 1);
@@ -10832,21 +11084,19 @@ var RC_PRESENCE_REFRESH_SECONDS = 60;
10832
11084
  var RC_SELF_HOST_MAX_BATCH_EVENTS = 100;
10833
11085
  var RC_SELF_HOST_WORKER_EPOCH = 1;
10834
11086
  var RC_STREAM_EVENT_NAME2 = "client_event";
10835
- var RC_SOURCE_WORKER = "worker";
10836
- var RC_SOURCE_CLIENT = "client";
10837
11087
  var CONFLICT_REASON_HEADER2 = "x-ccr-conflict-reason";
10838
11088
  var CONFLICT_REASON_EPOCH_STALE = "epoch_stale";
10839
- function isRecord6(value) {
11089
+ function isRecord7(value) {
10840
11090
  return typeof value === "object" && value !== null && !Array.isArray(value);
10841
11091
  }
10842
11092
  function isWebSearchRefusal(value) {
10843
11093
  return !Array.isArray(value);
10844
11094
  }
10845
- function nonEmptyString2(record, field) {
11095
+ function nonEmptyString3(record, field) {
10846
11096
  const value = record === void 0 ? void 0 : record[field];
10847
11097
  return typeof value === "string" && value !== "" ? value : void 0;
10848
11098
  }
10849
- function numberOf(record, field) {
11099
+ function numberOf2(record, field) {
10850
11100
  const value = record === void 0 ? void 0 : record[field];
10851
11101
  if (typeof value === "number" && Number.isFinite(value)) {
10852
11102
  return value;
@@ -10913,14 +11163,14 @@ async function readBody2(request4, capBytes) {
10913
11163
  });
10914
11164
  });
10915
11165
  }
10916
- function parseJsonObject2(body) {
11166
+ function parseJsonObject3(body) {
10917
11167
  let parsed;
10918
11168
  try {
10919
11169
  parsed = JSON.parse(body);
10920
11170
  } catch {
10921
11171
  return void 0;
10922
11172
  }
10923
- return isRecord6(parsed) ? parsed : void 0;
11173
+ return isRecord7(parsed) ? parsed : void 0;
10924
11174
  }
10925
11175
  function mintWorkerJwt(randomToken, issuedAt) {
10926
11176
  const encode = (value) => Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
@@ -10943,11 +11193,16 @@ function createRcSelfHostSurface(deps) {
10943
11193
  for (const session of sessions.values()) {
10944
11194
  while (session.events[0] !== void 0 && now - session.events[0].storedAt > RC_SELF_HOST_RETENTION_MS) {
10945
11195
  const dropped = session.events.shift();
10946
- const uuid = dropped === void 0 ? void 0 : nonEmptyString2(dropped.payload, "uuid");
11196
+ const uuid = dropped === void 0 ? void 0 : nonEmptyString3(dropped.payload, "uuid");
10947
11197
  if (uuid !== void 0) {
10948
11198
  session.seenPayloadUuids.delete(uuid);
10949
11199
  }
10950
11200
  }
11201
+ for (const [clientId, lastSeenAt] of session.clients) {
11202
+ if (now - lastSeenAt > RC_SELF_HOST_RETENTION_MS) {
11203
+ session.clients.delete(clientId);
11204
+ }
11205
+ }
10951
11206
  if (session.workerStreams.size === 0 && session.clientStreams.size === 0 && now - session.lastTrafficAt > RC_SELF_HOST_RETENTION_MS) {
10952
11207
  sessions.delete(session.id);
10953
11208
  }
@@ -10986,7 +11241,7 @@ function createRcSelfHostSurface(deps) {
10986
11241
  const frameOf = (event) => {
10987
11242
  const envelope = {
10988
11243
  event_id: event.eventId,
10989
- event_type: nonEmptyString2(event.payload, "type") ?? "event",
11244
+ event_type: nonEmptyString3(event.payload, "type") ?? "event",
10990
11245
  sequence_num: event.sequenceNum,
10991
11246
  source: event.source,
10992
11247
  payload: event.payload,
@@ -11000,7 +11255,7 @@ data: ${JSON.stringify(envelope)}
11000
11255
  };
11001
11256
  const holdStream = async (session, which, wantsReplay, request4, response) => {
11002
11257
  const url = new URL(request4.url, "http://127.0.0.1");
11003
- const cursor = numberOf({ value: url.searchParams.get("from_sequence_num") }, "value") ?? numberOf({ value: singleHeader2(request4.headers, "last-event-id") }, "value");
11258
+ const cursor = numberOf2({ value: url.searchParams.get("from_sequence_num") }, "value") ?? numberOf2({ value: singleHeader2(request4.headers, "last-event-id") }, "value");
11004
11259
  const replay = cursor === void 0 ? [] : session.events.filter((event) => wantsReplay(event) && event.sequenceNum > cursor);
11005
11260
  response.start(HTTP_STATUS.ok, { "content-type": "text/event-stream", "cache-control": "no-cache" });
11006
11261
  response.flush();
@@ -11029,8 +11284,8 @@ data: ${JSON.stringify(envelope)}
11029
11284
  sink.retire();
11030
11285
  response.end();
11031
11286
  };
11032
- const publish = (session, source, payload) => {
11033
- const uuid = nonEmptyString2(payload, "uuid");
11287
+ const publish = (session, source, payload, toWorker = source === RC_SOURCE_CLIENT) => {
11288
+ const uuid = nonEmptyString3(payload, "uuid");
11034
11289
  const duplicate = uuid !== void 0 && session.seenPayloadUuids.has(uuid);
11035
11290
  session.sequenceNum += 1;
11036
11291
  const event = {
@@ -11038,9 +11293,9 @@ data: ${JSON.stringify(envelope)}
11038
11293
  eventId: uuid ?? deps.newUuid(),
11039
11294
  source,
11040
11295
  // 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.
11041
- payload: source === RC_SOURCE_CLIENT ? { ...payload, server_received_wall_ms: deps.now() } : payload,
11296
+ payload: source === RC_SOURCE_WORKER ? payload : { ...payload, server_received_wall_ms: deps.now() },
11042
11297
  createdAt: new Date(deps.now()).toISOString(),
11043
- toWorker: source === RC_SOURCE_CLIENT,
11298
+ toWorker,
11044
11299
  storedAt: deps.now()
11045
11300
  };
11046
11301
  session.events.push(event);
@@ -11088,8 +11343,26 @@ data: ${JSON.stringify(envelope)}
11088
11343
  }
11089
11344
  session.lastTrafficAt = deps.now();
11090
11345
  if (tail === "") {
11346
+ if (method === "PATCH" || method === "PUT") {
11347
+ const refusal = credentialRefusal(request4.headers);
11348
+ if (refusal !== void 0) {
11349
+ await unauthorized(response, refusal);
11350
+ return;
11351
+ }
11352
+ const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11353
+ const title = nonEmptyString3(body, "title");
11354
+ if (title === void 0) {
11355
+ await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a session update is JSON naming a non-empty title" } });
11356
+ return;
11357
+ }
11358
+ session.title = title;
11359
+ session.lastTrafficAt = deps.now();
11360
+ deps.log?.(`rc selfhost ${session.id}: retitled at its client's request`);
11361
+ await answerJson2(response, HTTP_STATUS.ok, {});
11362
+ return;
11363
+ }
11091
11364
  if (method !== "GET") {
11092
- await answerJson2(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the session read is a GET" } });
11365
+ 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" } });
11093
11366
  return;
11094
11367
  }
11095
11368
  await answerJson2(response, HTTP_STATUS.ok, { session: { id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), status: "active" } });
@@ -11134,17 +11407,17 @@ data: ${JSON.stringify(envelope)}
11134
11407
  return;
11135
11408
  }
11136
11409
  if (workerTail === "" && method === "PUT") {
11137
- const body = parseJsonObject2(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11138
- const claimedEpoch = numberOf(body, "worker_epoch");
11410
+ const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11411
+ const claimedEpoch = numberOf2(body, "worker_epoch");
11139
11412
  if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
11140
11413
  await epochStale(response);
11141
11414
  return;
11142
11415
  }
11143
- const status = nonEmptyString2(body, "worker_status");
11416
+ const status = nonEmptyString3(body, "worker_status");
11144
11417
  if (status !== void 0) {
11145
11418
  session.workerStatus = status;
11146
11419
  }
11147
- if (isRecord6(body?.external_metadata)) {
11420
+ if (isRecord7(body?.external_metadata)) {
11148
11421
  session.externalMetadata = body.external_metadata;
11149
11422
  }
11150
11423
  await answerJson2(response, HTTP_STATUS.ok, {});
@@ -11161,8 +11434,8 @@ data: ${JSON.stringify(envelope)}
11161
11434
  return;
11162
11435
  }
11163
11436
  if (workerTail === "events" && method === "POST") {
11164
- const body = parseJsonObject2(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11165
- const claimedEpoch = numberOf(body, "worker_epoch");
11437
+ const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11438
+ const claimedEpoch = numberOf2(body, "worker_epoch");
11166
11439
  if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
11167
11440
  await epochStale(response);
11168
11441
  return;
@@ -11174,7 +11447,7 @@ data: ${JSON.stringify(envelope)}
11174
11447
  }
11175
11448
  const results = [];
11176
11449
  for (const event of events) {
11177
- if (!isRecord6(event) || !isRecord6(event.payload)) {
11450
+ if (!isRecord7(event) || !isRecord7(event.payload)) {
11178
11451
  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" } });
11179
11452
  return;
11180
11453
  }
@@ -11195,8 +11468,8 @@ data: ${JSON.stringify(envelope)}
11195
11468
  return;
11196
11469
  }
11197
11470
  if (workerTail === "internal-events" && method === "POST") {
11198
- const body = parseJsonObject2(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11199
- const claimedEpoch = numberOf(body, "worker_epoch");
11471
+ const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11472
+ const claimedEpoch = numberOf2(body, "worker_epoch");
11200
11473
  if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
11201
11474
  await epochStale(response);
11202
11475
  return;
@@ -11207,19 +11480,26 @@ data: ${JSON.stringify(envelope)}
11207
11480
  return;
11208
11481
  }
11209
11482
  for (const event of events) {
11210
- if (isRecord6(event)) {
11211
- session.internalEvents.push({ cursor: deps.newUuid(), body: event });
11483
+ if (isRecord7(event)) {
11484
+ session.internalEvents.push({ eventId: deps.newUuid(), body: event });
11212
11485
  }
11213
11486
  }
11214
11487
  await answerJson2(response, HTTP_STATUS.ok, {});
11215
11488
  return;
11216
11489
  }
11217
11490
  if (workerTail === "internal-events" && method === "GET") {
11218
- const url = new URL(request4.url, "http://127.0.0.1");
11219
- const cursor = url.searchParams.get("cursor");
11220
- const from = cursor === null ? 0 : session.internalEvents.findIndex((event) => event.cursor === cursor) + 1;
11221
- const page = session.internalEvents.slice(from);
11222
- await answerJson2(response, HTTP_STATUS.ok, { data: page.map((event) => event.body), next_cursor: page.length === 0 ? void 0 : page[page.length - 1]?.cursor });
11491
+ const query = new URL(request4.url, "http://127.0.0.1").searchParams;
11492
+ if (query.get("session_agent_id") !== null || query.get("subagents") !== null) {
11493
+ 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" } });
11494
+ return;
11495
+ }
11496
+ const page = pageOfRcChannel(request4.url, session.internalEvents, (event) => event.eventId, (event) => ({ event_id: event.eventId, payload: event.body }));
11497
+ if ("error" in page) {
11498
+ const anchorUnknown = query.get("after_event_id") !== null && page.error.startsWith("the anchor names an event id");
11499
+ await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: anchorUnknown ? "after_event_id_not_found" : "invalid_request_error", message: page.error } });
11500
+ return;
11501
+ }
11502
+ await answerJson2(response, HTTP_STATUS.ok, page);
11223
11503
  return;
11224
11504
  }
11225
11505
  if (workerTail === "diagnostics" && method === "POST") {
@@ -11228,8 +11508,8 @@ data: ${JSON.stringify(envelope)}
11228
11508
  return;
11229
11509
  }
11230
11510
  if (workerTail === "web-fetch" && method === "POST") {
11231
- const body = parseJsonObject2(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11232
- const target = nonEmptyString2(body, "url");
11511
+ const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11512
+ const target = nonEmptyString3(body, "url");
11233
11513
  if (target === void 0) {
11234
11514
  await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-fetch carries one url" } });
11235
11515
  return;
@@ -11251,8 +11531,8 @@ data: ${JSON.stringify(envelope)}
11251
11531
  return;
11252
11532
  }
11253
11533
  if (workerTail === "web-search" && method === "POST") {
11254
- const body = parseJsonObject2(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11255
- const query = nonEmptyString2(body, "query");
11534
+ const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11535
+ const query = nonEmptyString3(body, "query");
11256
11536
  if (query === void 0) {
11257
11537
  await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-search carries one query" } });
11258
11538
  return;
@@ -11263,7 +11543,7 @@ data: ${JSON.stringify(envelope)}
11263
11543
  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" } });
11264
11544
  return;
11265
11545
  }
11266
- const searchProfile = nonEmptyString2(body, "search_profile");
11546
+ const searchProfile = nonEmptyString3(body, "search_profile");
11267
11547
  const backend = deps.webSearch;
11268
11548
  if (backend === void 0) {
11269
11549
  deps.log?.(`rc selfhost ${session.id}: worker web-search of "${query}" refused: no search backend serves this door`);
@@ -11288,7 +11568,7 @@ data: ${JSON.stringify(envelope)}
11288
11568
  return;
11289
11569
  }
11290
11570
  if (tail === "events" && method === "POST") {
11291
- const body = parseJsonObject2(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11571
+ const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11292
11572
  const events = body?.events;
11293
11573
  if (!Array.isArray(events) || events.length === 0 || events.length > RC_SELF_HOST_MAX_BATCH_EVENTS) {
11294
11574
  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` } });
@@ -11296,7 +11576,7 @@ data: ${JSON.stringify(envelope)}
11296
11576
  }
11297
11577
  const results = [];
11298
11578
  for (const event of events) {
11299
- if (!isRecord6(event) || !isRecord6(event.payload)) {
11579
+ if (!isRecord7(event) || !isRecord7(event.payload)) {
11300
11580
  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" } });
11301
11581
  return;
11302
11582
  }
@@ -11308,26 +11588,14 @@ data: ${JSON.stringify(envelope)}
11308
11588
  return;
11309
11589
  }
11310
11590
  if (tail === "events" && method === "GET") {
11311
- 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 })) });
11591
+ 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 })) });
11312
11592
  return;
11313
11593
  }
11314
11594
  if (tail === "events/stream" && method === "GET") {
11315
11595
  await holdStream(session, "clientStreams", () => true, request4, response);
11316
11596
  return;
11317
11597
  }
11318
- if (tail === "client/presence" && method === "POST") {
11319
- await readBody2(request4.body, CONTROL_BODY_CAP_BYTES);
11320
- await answerJson2(response, HTTP_STATUS.ok, { refresh_after_seconds: RC_PRESENCE_REFRESH_SECONDS });
11321
- return;
11322
- }
11323
- if (tail === "mark_read" && method === "POST") {
11324
- await readBody2(request4.body, CONTROL_BODY_CAP_BYTES);
11325
- await answerJson2(response, HTTP_STATUS.ok, {});
11326
- return;
11327
- }
11328
- if (tail === "teleport-events" && method === "POST") {
11329
- await readBody2(request4.body, CONTROL_BODY_CAP_BYTES);
11330
- await answerJson2(response, HTTP_STATUS.ok, {});
11598
+ 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)) {
11331
11599
  return;
11332
11600
  }
11333
11601
  await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the self-hosted Remote Control surface serves no path "${tail}"` } });
@@ -11344,17 +11612,8 @@ data: ${JSON.stringify(envelope)}
11344
11612
  await noCredential(response);
11345
11613
  return;
11346
11614
  }
11347
- if (pathname === RC_COMPAT_SESSIONS_PATH) {
11348
- if (method !== "GET") {
11349
- await answerJson2(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the compatibility session list is a GET" } });
11350
- return;
11351
- }
11352
- const listRefusal = credentialRefusal(request4.headers);
11353
- if (listRefusal !== void 0) {
11354
- await unauthorized(response, listRefusal);
11355
- return;
11356
- }
11357
- await answerJson2(response, HTTP_STATUS.ok, { data: [] });
11615
+ if (pathname === RC_COMPAT_SESSIONS_PATH || pathname.startsWith(`${RC_COMPAT_SESSIONS_PATH}/`)) {
11616
+ 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);
11358
11617
  return;
11359
11618
  }
11360
11619
  if (pathname !== RC_SESSIONS_PATH_PREFIX2 && !pathname.startsWith(`${RC_SESSIONS_PATH_PREFIX2}/`)) {
@@ -11368,11 +11627,11 @@ data: ${JSON.stringify(envelope)}
11368
11627
  await unauthorized(response, createRefusal);
11369
11628
  return;
11370
11629
  }
11371
- const body = parseJsonObject2(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11630
+ const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11372
11631
  const session = {
11373
11632
  id: `${RC_SESSION_ID_PREFIX2}${deps.newUuid()}`,
11374
11633
  createdAt: deps.now(),
11375
- title: nonEmptyString2(body, "title"),
11634
+ title: nonEmptyString3(body, "title"),
11376
11635
  workerJwts: /* @__PURE__ */ new Set(),
11377
11636
  sequenceNum: 0,
11378
11637
  events: [],
@@ -11380,6 +11639,7 @@ data: ${JSON.stringify(envelope)}
11380
11639
  workerStreams: /* @__PURE__ */ new Set(),
11381
11640
  clientStreams: /* @__PURE__ */ new Set(),
11382
11641
  internalEvents: [],
11642
+ clients: /* @__PURE__ */ new Map(),
11383
11643
  workerStatus: void 0,
11384
11644
  externalMetadata: void 0,
11385
11645
  lastTrafficAt: deps.now()
@@ -11424,7 +11684,7 @@ data: ${JSON.stringify(envelope)}
11424
11684
  const pathname = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
11425
11685
  if (host === RC_SELF_HOST_OAUTH_HOST) {
11426
11686
  if (method === "POST" && pathname === "/v1/oauth/token") {
11427
- const body = parseJsonObject2(await readBody2(request4, CONTROL_BODY_CAP_BYTES));
11687
+ const body = parseJsonObject3(await readBody2(request4, CONTROL_BODY_CAP_BYTES));
11428
11688
  const record = deps.credentialRecord();
11429
11689
  if (record === void 0) {
11430
11690
  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" });
@@ -11510,7 +11770,7 @@ var RC_SELF_HOST_RECORD_DIR = "rc-selfhost";
11510
11770
  var RC_SELF_HOST_RECORD_FILE = "credential.json";
11511
11771
  var SELF_HOST_EMAIL = "selfhost@agent-shim.invalid";
11512
11772
  var SELF_HOST_SUBSCRIPTION_TYPE = "max";
11513
- function isRecord7(value) {
11773
+ function isRecord8(value) {
11514
11774
  return typeof value === "object" && value !== null && !Array.isArray(value);
11515
11775
  }
11516
11776
  function readJsonObject(fs12, file) {
@@ -11524,7 +11784,7 @@ function readJsonObject(fs12, file) {
11524
11784
  } catch {
11525
11785
  return void 0;
11526
11786
  }
11527
- return isRecord7(parsed) ? parsed : void 0;
11787
+ return isRecord8(parsed) ? parsed : void 0;
11528
11788
  }
11529
11789
  function readRcSelfHostRecord(fs12, frontdoorDir) {
11530
11790
  const parsed = readJsonObject(fs12, import_node_path28.default.join(frontdoorDir, RC_SELF_HOST_RECORD_DIR, RC_SELF_HOST_RECORD_FILE));
@@ -11548,7 +11808,7 @@ function mintRcSelfHostCredential(deps) {
11548
11808
  const recordFile = import_node_path28.default.join(recordDir, RC_SELF_HOST_RECORD_FILE);
11549
11809
  const previousRecord = readRcSelfHostRecord(deps.fs, deps.frontdoorDir);
11550
11810
  const existingCredentials = readJsonObject(deps.fs, credentialsFile);
11551
- const existingOauth = isRecord7(existingCredentials?.claudeAiOauth) ? existingCredentials.claudeAiOauth : void 0;
11811
+ const existingOauth = isRecord8(existingCredentials?.claudeAiOauth) ? existingCredentials.claudeAiOauth : void 0;
11552
11812
  const existingToken = typeof existingOauth?.accessToken === "string" && existingOauth.accessToken !== "" ? existingOauth.accessToken : void 0;
11553
11813
  const existingIsOwn = existingToken !== void 0 && previousRecord?.accessToken === existingToken;
11554
11814
  if (existingToken !== void 0 && !existingIsOwn && !deps.force) {
@@ -11565,7 +11825,7 @@ function mintRcSelfHostCredential(deps) {
11565
11825
  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)}
11566
11826
  `);
11567
11827
  const claudeJson = readJsonObject(deps.fs, claudeJsonFile) ?? {};
11568
- const existingBlock = (value) => isRecord7(value) ? value : {};
11828
+ const existingBlock = (value) => isRecord8(value) ? value : {};
11569
11829
  deps.fs.writeFilePrivate(claudeJsonFile, `${JSON.stringify({
11570
11830
  ...claudeJson,
11571
11831
  oauthAccount: {
@@ -11764,7 +12024,7 @@ var import_node_path30 = __toESM(require("node:path"), 1);
11764
12024
  // package.json
11765
12025
  var package_default = {
11766
12026
  name: "agent-shim",
11767
- version: "8.24.0",
12027
+ version: "8.25.0",
11768
12028
  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.",
11769
12029
  license: "MIT",
11770
12030
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -13055,6 +13315,7 @@ function prepareClaudeLaunch(options) {
13055
13315
  buildRcGetUsagePayload,
13056
13316
  buildRcInterruptPayload,
13057
13317
  buildRcKeepAlivePayload,
13318
+ buildRcMarkReadBody,
13058
13319
  buildRcMcpAuthenticatePayload,
13059
13320
  buildRcMcpOAuthCallbackUrlPayload,
13060
13321
  buildRcMcpReconnectPayload,
@@ -13201,6 +13462,7 @@ function prepareClaudeLaunch(options) {
13201
13462
  startConnectServer,
13202
13463
  submitRcSessionMcpOAuthCallbackUrl,
13203
13464
  suggestRcSessionFiles,
13465
+ teleportRcSession,
13204
13466
  topLevelNames,
13205
13467
  updateDirectoryRule,
13206
13468
  updateProvider,