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/cli.cjs CHANGED
@@ -61796,7 +61796,7 @@ function registerIdentityCommand(program2, deps) {
61796
61796
  // package.json
61797
61797
  var package_default = {
61798
61798
  name: "agent-shim",
61799
- version: "8.24.0",
61799
+ version: "8.25.0",
61800
61800
  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.",
61801
61801
  license: "MIT",
61802
61802
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -66142,13 +66142,6 @@ var https3 = __toESM(require("node:https"), 1);
66142
66142
  var net2 = __toESM(require("node:net"), 1);
66143
66143
  var tls = __toESM(require("node:tls"), 1);
66144
66144
 
66145
- // src/frontdoor/server.ts
66146
- var import_node_http2 = __toESM(require("node:http"), 1);
66147
- var import_node_https = __toESM(require("node:https"), 1);
66148
-
66149
- // src/frontdoor/rcControl.ts
66150
- var https = __toESM(require("node:https"), 1);
66151
-
66152
66145
  // src/frontdoor/rcSessions.ts
66153
66146
  var RC_SESSIONS_PATH_PREFIX = "/v1/code/sessions";
66154
66147
  var RC_SESSION_ID_PREFIX = "cse_";
@@ -66725,7 +66718,7 @@ async function answerRcControlRequest(deps, sessionId, requestId, decision) {
66725
66718
  }
66726
66719
  return result;
66727
66720
  }
66728
- async function deliverRcClientPayload(deps, sessionId, payload, action) {
66721
+ async function deliverRcClientPayload(deps, sessionId, payload, action, to = "events") {
66729
66722
  const credential = deps.credentialOf(sessionId);
66730
66723
  if (credential === void 0) {
66731
66724
  return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
@@ -66736,7 +66729,7 @@ async function deliverRcClientPayload(deps, sessionId, payload, action) {
66736
66729
  const body = JSON.stringify(buildRcEventWriteBody(payload));
66737
66730
  let answer;
66738
66731
  try {
66739
- answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
66732
+ answer = await (to === "teleport-events" ? deps.dial.writeTeleportEvents(sessionId, rcWriteHeaders(credential), body) : deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body));
66740
66733
  } catch (error62) {
66741
66734
  return { ok: false, message: `the door could not reach the API host to ${action}: ${error62 instanceof Error ? error62.message : String(error62)}` };
66742
66735
  }
@@ -66830,8 +66823,22 @@ async function submitRcSessionMcpOAuthCallbackUrl(deps, sessionId, serverName, c
66830
66823
  async function sendRcKeepAlive(deps, sessionId) {
66831
66824
  return await deliverRcClientPayload(deps, sessionId, buildRcKeepAlivePayload(), `keep session ${sessionId} alive`);
66832
66825
  }
66826
+ async function teleportRcSession(deps, sessionId, marker) {
66827
+ if (marker === "") {
66828
+ 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" };
66829
+ }
66830
+ return await deliverRcClientPayload(deps, sessionId, buildRcUserMessagePayload(deps.newUuid(), sessionId, marker), `teleport a marker into session ${sessionId}`, "teleport-events");
66831
+ }
66832
+ function buildRcMarkReadBody(eventId) {
66833
+ return eventId === void 0 ? {} : { event_id: eventId };
66834
+ }
66835
+
66836
+ // src/frontdoor/server.ts
66837
+ var import_node_http2 = __toESM(require("node:http"), 1);
66838
+ var import_node_https = __toESM(require("node:https"), 1);
66833
66839
 
66834
66840
  // src/frontdoor/rcControl.ts
66841
+ var https = __toESM(require("node:https"), 1);
66835
66842
  var RC_PERMISSION_MODE_LIST = RC_PERMISSION_MODES.join(", ");
66836
66843
  var CONTROL_PATH_PREFIX = "/__agent-shim/rc";
66837
66844
  var CONTROL_BODY_CAP_BYTES = 10485760;
@@ -67005,6 +67012,15 @@ function createRcControlHandler(deps) {
67005
67012
  const callbackUrl = bodyString(body, "callbackUrl");
67006
67013
  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) };
67007
67014
  }
67015
+ },
67016
+ {
67017
+ name: "teleport",
67018
+ path: "teleport",
67019
+ read: (body) => {
67020
+ const session = bodyString(body, "session");
67021
+ const marker = bodyString(body, "marker");
67022
+ 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) };
67023
+ }
67008
67024
  }
67009
67025
  ];
67010
67026
  return (request7, response) => {
@@ -67368,7 +67384,8 @@ function frontDoorRcControl(transport, token) {
67368
67384
  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}`),
67369
67385
  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}`),
67370
67386
  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}`),
67371
- 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}`)
67387
+ 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}`),
67388
+ 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}`)
67372
67389
  };
67373
67390
  }
67374
67391
 
@@ -67650,36 +67667,38 @@ function tlsFacts(options) {
67650
67667
  }
67651
67668
  function realRcEventDial(target = { host: CONNECT_INTERCEPT_HOST, port: HTTPS_PORT }) {
67652
67669
  const agent = new ExemptTlsAgent();
67653
- return {
67654
- writeEvents: async (sessionId, headers, body) => await new Promise((resolve, reject) => {
67655
- const options = {
67656
- host: target.host,
67657
- port: target.port,
67658
- method: "POST",
67659
- path: `/v1/code/sessions/${encodeURIComponent(sessionId)}/events`,
67660
- // 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.
67661
- headers: { ...headers, host: target.servername ?? target.host, "content-length": String(Buffer.byteLength(body, "utf8")) },
67662
- agent
67663
- };
67664
- options.servername = target.servername ?? target.host;
67665
- if (target.ca !== void 0) {
67666
- options.ca = [...target.ca];
67667
- }
67668
- if (target.rejectUnauthorized !== void 0) {
67669
- options.rejectUnauthorized = target.rejectUnauthorized;
67670
- }
67671
- const request7 = https3.request(options, (response) => {
67672
- const chunks = [];
67673
- response.on("data", (chunk) => {
67674
- chunks.push(chunk);
67675
- });
67676
- response.on("end", () => {
67677
- resolve({ status: response.statusCode ?? HTTP_BAD_GATEWAY, body: Buffer.concat(chunks).toString("utf8") });
67678
- });
67670
+ const post = async (path46, headers, body) => await new Promise((resolve, reject) => {
67671
+ const options = {
67672
+ host: target.host,
67673
+ port: target.port,
67674
+ method: "POST",
67675
+ path: path46,
67676
+ // 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.
67677
+ headers: { ...headers, host: target.servername ?? target.host, "content-length": String(Buffer.byteLength(body, "utf8")) },
67678
+ agent
67679
+ };
67680
+ options.servername = target.servername ?? target.host;
67681
+ if (target.ca !== void 0) {
67682
+ options.ca = [...target.ca];
67683
+ }
67684
+ if (target.rejectUnauthorized !== void 0) {
67685
+ options.rejectUnauthorized = target.rejectUnauthorized;
67686
+ }
67687
+ const request7 = https3.request(options, (response) => {
67688
+ const chunks = [];
67689
+ response.on("data", (chunk) => {
67690
+ chunks.push(chunk);
67679
67691
  });
67680
- request7.once("error", reject);
67681
- request7.end(body);
67682
- })
67692
+ response.on("end", () => {
67693
+ resolve({ status: response.statusCode ?? HTTP_BAD_GATEWAY, body: Buffer.concat(chunks).toString("utf8") });
67694
+ });
67695
+ });
67696
+ request7.once("error", reject);
67697
+ request7.end(body);
67698
+ });
67699
+ return {
67700
+ writeEvents: async (sessionId, headers, body) => await post(`/v1/code/sessions/${encodeURIComponent(sessionId)}/events`, headers, body),
67701
+ writeTeleportEvents: async (sessionId, headers, body) => await post(`/v1/code/sessions/${encodeURIComponent(sessionId)}/teleport-events`, headers, body)
67683
67702
  };
67684
67703
  }
67685
67704
  var NO_CHUNKS = {
@@ -67697,7 +67716,8 @@ function lateRcEventDial(resolve) {
67697
67716
  return built.dial;
67698
67717
  };
67699
67718
  return {
67700
- writeEvents: async (sessionId, headers, body) => await dialFor().writeEvents(sessionId, headers, body)
67719
+ writeEvents: async (sessionId, headers, body) => await dialFor().writeEvents(sessionId, headers, body),
67720
+ writeTeleportEvents: async (sessionId, headers, body) => await dialFor().writeTeleportEvents(sessionId, headers, body)
67701
67721
  };
67702
67722
  }
67703
67723
  function lateRcStreamDial(resolve) {
@@ -67710,7 +67730,8 @@ function lateRcStreamDial(resolve) {
67710
67730
  return built.dial;
67711
67731
  };
67712
67732
  return {
67713
- announcePresence: async (sessionId, headers, clientId) => await dialFor().announcePresence(sessionId, headers, clientId),
67733
+ announcePresence: async (sessionId, headers, clientId, clear) => await dialFor().announcePresence(sessionId, headers, clientId, clear),
67734
+ markRead: async (sessionId, headers, eventId) => await dialFor().markRead(sessionId, headers, eventId),
67714
67735
  openStream: async (sessionId, headers, resume, signal) => await dialFor().openStream(sessionId, headers, resume, signal)
67715
67736
  };
67716
67737
  }
@@ -67752,11 +67773,16 @@ function realRcStreamDial(target = { host: CONNECT_INTERCEPT_HOST, port: HTTPS_P
67752
67773
  return Buffer.concat(chunks).toString("utf8");
67753
67774
  };
67754
67775
  return {
67755
- announcePresence: async (sessionId, headers, clientId) => {
67756
- const body = JSON.stringify({ client_id: clientId, clear: false });
67776
+ announcePresence: async (sessionId, headers, clientId, clear) => {
67777
+ const body = JSON.stringify({ client_id: clientId, clear });
67757
67778
  const { status, response } = await request7("POST", `/v1/code/sessions/${encodeURIComponent(sessionId)}/client/presence`, { ...headers, "content-type": "application/json" }, body, new AbortController().signal);
67758
67779
  return { status, body: await readAll(response) };
67759
67780
  },
67781
+ markRead: async (sessionId, headers, eventId) => {
67782
+ const body = JSON.stringify(buildRcMarkReadBody(eventId));
67783
+ const { status, response } = await request7("POST", `/v1/code/sessions/${encodeURIComponent(sessionId)}/mark_read`, { ...headers, "content-type": "application/json" }, body, new AbortController().signal);
67784
+ return { status, body: await readAll(response) };
67785
+ },
67760
67786
  openStream: async (sessionId, headers, resume, signal) => {
67761
67787
  const path46 = resume === void 0 ? `/v1/code/sessions/${encodeURIComponent(sessionId)}/events/stream` : `/v1/code/sessions/${encodeURIComponent(sessionId)}/events/stream?from_sequence_num=${String(resume.fromSequenceNum)}`;
67762
67788
  const streamHeaders = resume === void 0 ? headers : { ...headers, "last-event-id": String(resume.fromSequenceNum) };
@@ -72074,6 +72100,10 @@ var RcMcpOAuthCallbackUrlInputSchema = external_exports.strictObject({
72074
72100
  serverName: external_exports.string().min(1),
72075
72101
  callbackUrl: external_exports.string().min(1)
72076
72102
  });
72103
+ var RcTeleportInputSchema = external_exports.strictObject({
72104
+ session: external_exports.string().min(1),
72105
+ marker: external_exports.string().min(1)
72106
+ });
72077
72107
  var RcListOutputSchema = external_exports.strictObject({ sessions: external_exports.readonly(external_exports.array(RcSessionSummarySchema)) });
72078
72108
  var RcStatusOutputSchema = external_exports.strictObject({ statuses: external_exports.readonly(external_exports.array(RcSessionStatusSchema)) });
72079
72109
  var RcPendingOutputSchema = external_exports.strictObject({ pending: external_exports.readonly(external_exports.array(RcPendingRequestSummarySchema)) });
@@ -72158,6 +72188,7 @@ function createRcApiRouter(deps) {
72158
72188
  mcpReconnect: authed.input(RcMcpReconnectInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input: input2 }) => ({ session: input2.session, ...deliveredControl(input2.session, await deps.mcpReconnect(input2.session, input2.serverName)) })),
72159
72189
  mcpAuthenticate: authed.input(RcMcpAuthenticateInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input: input2 }) => ({ session: input2.session, ...deliveredControl(input2.session, await deps.mcpAuthenticate(input2.session, input2.serverName, input2.redirectUri)) })),
72160
72190
  mcpOAuthCallbackUrl: authed.input(RcMcpOAuthCallbackUrlInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input: input2 }) => ({ session: input2.session, ...deliveredControl(input2.session, await deps.mcpOAuthCallbackUrl(input2.session, input2.serverName, input2.callbackUrl)) })),
72191
+ teleport: authed.input(RcTeleportInputSchema).output(RcWriteOutputSchema).handler(async ({ input: input2 }) => ({ session: input2.session, sequenceNums: delivered(input2.session, await deps.teleport(input2.session, input2.marker)) })),
72161
72192
  subscribe: authed.input(RcSessionQuerySchema).output(eventIterator(RcStreamEventSchema)).handler(async function* ({ input: input2, signal }) {
72162
72193
  const queue = [];
72163
72194
  let wake;
@@ -73738,6 +73769,193 @@ var import_node_crypto14 = require("node:crypto");
73738
73769
  // src/frontdoor/rcSelfHostMint.ts
73739
73770
  var import_node_path34 = __toESM(require("node:path"), 1);
73740
73771
 
73772
+ // src/frontdoor/rcSelfHostEndpoints.ts
73773
+ var RC_SOURCE_WORKER = "worker";
73774
+ var RC_SOURCE_CLIENT = "client";
73775
+ function isRecord4(value2) {
73776
+ return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
73777
+ }
73778
+ function parseJsonObject2(body) {
73779
+ let parsed;
73780
+ try {
73781
+ parsed = JSON.parse(body);
73782
+ } catch {
73783
+ return void 0;
73784
+ }
73785
+ return isRecord4(parsed) ? parsed : void 0;
73786
+ }
73787
+ function nonEmptyString2(record2, field) {
73788
+ const value2 = record2 === void 0 ? void 0 : record2[field];
73789
+ return typeof value2 === "string" && value2 !== "" ? value2 : void 0;
73790
+ }
73791
+ function numberOf(record2, field) {
73792
+ const value2 = record2 === void 0 ? void 0 : record2[field];
73793
+ if (typeof value2 === "number" && Number.isFinite(value2)) {
73794
+ return value2;
73795
+ }
73796
+ if (typeof value2 === "string" && /^[0-9]+$/.test(value2)) {
73797
+ return Number(value2);
73798
+ }
73799
+ return void 0;
73800
+ }
73801
+ function pageOfRcChannel(url2, rows, idOf, rowOf) {
73802
+ const query = new URL(url2, "http://127.0.0.1").searchParams;
73803
+ const anchor2 = query.get("after_event_id") ?? query.get("cursor");
73804
+ const rawLimit = query.get("limit");
73805
+ const limit = rawLimit === null ? void 0 : numberOf({ value: rawLimit }, "value");
73806
+ if (limit !== void 0 && limit < 1) {
73807
+ return { error: "the limit names a whole number of rows of at least one" };
73808
+ }
73809
+ const from = anchor2 === null ? 0 : rows.findIndex((row) => idOf(row) === anchor2) + 1;
73810
+ if (anchor2 !== null && from === 0) {
73811
+ return { error: `the anchor names an event id this surface holds no row for: ${anchor2}` };
73812
+ }
73813
+ const page = limit === void 0 ? rows.slice(from) : rows.slice(from, from + limit);
73814
+ const last = page[page.length - 1];
73815
+ return { data: page.map(rowOf), ...last === void 0 ? {} : { next_cursor: idOf(last) } };
73816
+ }
73817
+ async function serveRcClientChannels(ctx, tail, method, maxBatchEvents) {
73818
+ const { request: request7, response, session } = ctx;
73819
+ if (tail === "client/presence" && method === "POST") {
73820
+ const body = parseJsonObject2(await ctx.readBody(request7.body, CONTROL_BODY_CAP_BYTES));
73821
+ const clientId = nonEmptyString2(body, "client_id");
73822
+ const clear = body?.clear;
73823
+ if (clientId === void 0 || clear !== void 0 && typeof clear !== "boolean") {
73824
+ 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" } });
73825
+ return true;
73826
+ }
73827
+ if (clear === true) {
73828
+ session.clients.delete(clientId);
73829
+ ctx.log?.(`rc selfhost ${session.id}: client ${clientId} cleared its presence`);
73830
+ } else {
73831
+ session.clients.set(clientId, ctx.now());
73832
+ ctx.log?.(`rc selfhost ${session.id}: client ${clientId} announced its presence${session.clients.size === 1 ? "" : ` (${String(session.clients.size)} clients now announced)`}`);
73833
+ }
73834
+ await ctx.answerJson(response, HTTP_STATUS.ok, { refresh_after_seconds: ctx.presenceRefreshSeconds });
73835
+ return true;
73836
+ }
73837
+ if (tail === "mark_read" && method === "POST") {
73838
+ const body = parseJsonObject2(await ctx.readBody(request7.body, CONTROL_BODY_CAP_BYTES));
73839
+ const eventId = nonEmptyString2(body, "event_id");
73840
+ const clientId = nonEmptyString2(body, "client_id");
73841
+ if (body !== void 0 && "event_id" in body && eventId === void 0 || "client_id" in (body ?? {}) && clientId === void 0) {
73842
+ 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" } });
73843
+ return true;
73844
+ }
73845
+ 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);
73846
+ 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)}`);
73847
+ await ctx.answerJson(response, HTTP_STATUS.ok, {});
73848
+ return true;
73849
+ }
73850
+ if (tail === "teleport-events" && method === "POST") {
73851
+ const body = parseJsonObject2(await ctx.readBody(request7.body, CONTROL_BODY_CAP_BYTES));
73852
+ const events = body?.events;
73853
+ if (!Array.isArray(events) || events.length === 0 || events.length > maxBatchEvents) {
73854
+ 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` } });
73855
+ return true;
73856
+ }
73857
+ const results = [];
73858
+ for (const event of events) {
73859
+ if (!isRecord4(event) || !isRecord4(event.payload)) {
73860
+ 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" } });
73861
+ return true;
73862
+ }
73863
+ const published = ctx.publish(session, RC_SOURCE_CLIENT, event.payload);
73864
+ results.push({ sequence_num: String(published.sequenceNum), duplicate: published.duplicate });
73865
+ }
73866
+ ctx.sweep();
73867
+ ctx.log?.(`rc selfhost ${session.id}: teleport write published ${String(results.length)} marker event(s)`);
73868
+ await ctx.answerJson(response, HTTP_STATUS.ok, { results });
73869
+ return true;
73870
+ }
73871
+ if (tail === "teleport-events" && method === "GET") {
73872
+ const page = pageOfRcChannel(request7.url, session.events, (event) => event.eventId, (event) => ({ event_id: event.eventId, payload: event.payload }));
73873
+ if ("error" in page) {
73874
+ await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: page.error } });
73875
+ return true;
73876
+ }
73877
+ await ctx.answerJson(response, HTTP_STATUS.ok, page);
73878
+ return true;
73879
+ }
73880
+ return false;
73881
+ }
73882
+ 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" });
73883
+ async function serveRcCompatSessions(ctx, pathname, compatPath) {
73884
+ const { request: request7, response } = ctx;
73885
+ const method = request7.method.toUpperCase();
73886
+ if (pathname === compatPath) {
73887
+ if (method !== "GET") {
73888
+ await ctx.answerJson(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the compatibility session list is a GET" } });
73889
+ return;
73890
+ }
73891
+ const listRefusal = ctx.credentialRefusal(request7.headers);
73892
+ if (listRefusal !== void 0) {
73893
+ await ctx.unauthorized(response, listRefusal);
73894
+ return;
73895
+ }
73896
+ await ctx.answerJson(response, HTTP_STATUS.ok, { data: [...ctx.sessions.values()].map((session2) => compatSessionRow(session2, ctx.now)) });
73897
+ return;
73898
+ }
73899
+ const rest = pathname.slice(`${compatPath}/`.length);
73900
+ const slash = rest.indexOf("/");
73901
+ const address = slash === -1 ? rest : rest.slice(0, slash);
73902
+ const compatTail = slash === -1 ? "" : rest.slice(slash + 1);
73903
+ const refusal = ctx.credentialRefusal(request7.headers);
73904
+ if (refusal !== void 0) {
73905
+ await ctx.unauthorized(response, refusal);
73906
+ return;
73907
+ }
73908
+ const session = ctx.sessions.get(address) ?? ctx.sessions.get(`cse_${address}`);
73909
+ if (compatTail === "") {
73910
+ if (method === "GET") {
73911
+ if (session === void 0) {
73912
+ await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `Session not found: ${address}` } });
73913
+ return;
73914
+ }
73915
+ await ctx.answerJson(response, HTTP_STATUS.ok, compatSessionRow(session, ctx.now));
73916
+ return;
73917
+ }
73918
+ if (method === "PATCH" || method === "PUT") {
73919
+ if (session === void 0) {
73920
+ await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `Session not found: ${address}` } });
73921
+ return;
73922
+ }
73923
+ const body = parseJsonObject2(await ctx.readBody(request7.body, CONTROL_BODY_CAP_BYTES));
73924
+ const title = nonEmptyString2(body, "title");
73925
+ if (title === void 0) {
73926
+ 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" } });
73927
+ return;
73928
+ }
73929
+ session.title = title;
73930
+ session.lastTrafficAt = ctx.now();
73931
+ ctx.log?.(`rc selfhost ${session.id}: retitled through the compatibility family`);
73932
+ await ctx.answerJson(response, HTTP_STATUS.ok, compatSessionRow(session, ctx.now));
73933
+ return;
73934
+ }
73935
+ 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" } });
73936
+ return;
73937
+ }
73938
+ if ((compatTail === "archive" || compatTail === "unarchive") && method === "POST") {
73939
+ await ctx.readBody(request7.body, CONTROL_BODY_CAP_BYTES);
73940
+ if (session === void 0) {
73941
+ await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `Session not found: ${address}` } });
73942
+ return;
73943
+ }
73944
+ if (compatTail === "unarchive") {
73945
+ await ctx.answerJson(response, HTTP_STATUS.ok, compatSessionRow(session, ctx.now));
73946
+ return;
73947
+ }
73948
+ for (const sink of [...session.workerStreams, ...session.clientStreams]) {
73949
+ sink.retire();
73950
+ }
73951
+ ctx.sessions.delete(session.id);
73952
+ ctx.log?.(`rc selfhost ${session.id}: archived through the compatibility family`);
73953
+ await ctx.answerJson(response, HTTP_STATUS.ok, {});
73954
+ return;
73955
+ }
73956
+ await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the compatibility session family serves no path "${compatTail}"` } });
73957
+ }
73958
+
73741
73959
  // src/frontdoor/rcWebFetch.ts
73742
73960
  var dns2 = __toESM(require("node:dns"), 1);
73743
73961
  var http6 = __toESM(require("node:http"), 1);
@@ -74006,21 +74224,19 @@ var RC_PRESENCE_REFRESH_SECONDS = 60;
74006
74224
  var RC_SELF_HOST_MAX_BATCH_EVENTS = 100;
74007
74225
  var RC_SELF_HOST_WORKER_EPOCH = 1;
74008
74226
  var RC_STREAM_EVENT_NAME = "client_event";
74009
- var RC_SOURCE_WORKER = "worker";
74010
- var RC_SOURCE_CLIENT = "client";
74011
74227
  var CONFLICT_REASON_HEADER2 = "x-ccr-conflict-reason";
74012
74228
  var CONFLICT_REASON_EPOCH_STALE = "epoch_stale";
74013
- function isRecord4(value2) {
74229
+ function isRecord5(value2) {
74014
74230
  return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
74015
74231
  }
74016
74232
  function isWebSearchRefusal(value2) {
74017
74233
  return !Array.isArray(value2);
74018
74234
  }
74019
- function nonEmptyString2(record2, field) {
74235
+ function nonEmptyString3(record2, field) {
74020
74236
  const value2 = record2 === void 0 ? void 0 : record2[field];
74021
74237
  return typeof value2 === "string" && value2 !== "" ? value2 : void 0;
74022
74238
  }
74023
- function numberOf(record2, field) {
74239
+ function numberOf2(record2, field) {
74024
74240
  const value2 = record2 === void 0 ? void 0 : record2[field];
74025
74241
  if (typeof value2 === "number" && Number.isFinite(value2)) {
74026
74242
  return value2;
@@ -74087,14 +74303,14 @@ async function readBody3(request7, capBytes) {
74087
74303
  });
74088
74304
  });
74089
74305
  }
74090
- function parseJsonObject2(body) {
74306
+ function parseJsonObject3(body) {
74091
74307
  let parsed;
74092
74308
  try {
74093
74309
  parsed = JSON.parse(body);
74094
74310
  } catch {
74095
74311
  return void 0;
74096
74312
  }
74097
- return isRecord4(parsed) ? parsed : void 0;
74313
+ return isRecord5(parsed) ? parsed : void 0;
74098
74314
  }
74099
74315
  function mintWorkerJwt(randomToken, issuedAt) {
74100
74316
  const encode3 = (value2) => Buffer.from(JSON.stringify(value2), "utf8").toString("base64url");
@@ -74117,11 +74333,16 @@ function createRcSelfHostSurface(deps) {
74117
74333
  for (const session of sessions.values()) {
74118
74334
  while (session.events[0] !== void 0 && now - session.events[0].storedAt > RC_SELF_HOST_RETENTION_MS) {
74119
74335
  const dropped = session.events.shift();
74120
- const uuid3 = dropped === void 0 ? void 0 : nonEmptyString2(dropped.payload, "uuid");
74336
+ const uuid3 = dropped === void 0 ? void 0 : nonEmptyString3(dropped.payload, "uuid");
74121
74337
  if (uuid3 !== void 0) {
74122
74338
  session.seenPayloadUuids.delete(uuid3);
74123
74339
  }
74124
74340
  }
74341
+ for (const [clientId, lastSeenAt] of session.clients) {
74342
+ if (now - lastSeenAt > RC_SELF_HOST_RETENTION_MS) {
74343
+ session.clients.delete(clientId);
74344
+ }
74345
+ }
74125
74346
  if (session.workerStreams.size === 0 && session.clientStreams.size === 0 && now - session.lastTrafficAt > RC_SELF_HOST_RETENTION_MS) {
74126
74347
  sessions.delete(session.id);
74127
74348
  }
@@ -74160,7 +74381,7 @@ function createRcSelfHostSurface(deps) {
74160
74381
  const frameOf = (event) => {
74161
74382
  const envelope = {
74162
74383
  event_id: event.eventId,
74163
- event_type: nonEmptyString2(event.payload, "type") ?? "event",
74384
+ event_type: nonEmptyString3(event.payload, "type") ?? "event",
74164
74385
  sequence_num: event.sequenceNum,
74165
74386
  source: event.source,
74166
74387
  payload: event.payload,
@@ -74174,7 +74395,7 @@ data: ${JSON.stringify(envelope)}
74174
74395
  };
74175
74396
  const holdStream = async (session, which, wantsReplay, request7, response) => {
74176
74397
  const url2 = new URL(request7.url, "http://127.0.0.1");
74177
- const cursor3 = numberOf({ value: url2.searchParams.get("from_sequence_num") }, "value") ?? numberOf({ value: singleHeader2(request7.headers, "last-event-id") }, "value");
74398
+ const cursor3 = numberOf2({ value: url2.searchParams.get("from_sequence_num") }, "value") ?? numberOf2({ value: singleHeader2(request7.headers, "last-event-id") }, "value");
74178
74399
  const replay = cursor3 === void 0 ? [] : session.events.filter((event) => wantsReplay(event) && event.sequenceNum > cursor3);
74179
74400
  response.start(HTTP_STATUS.ok, { "content-type": "text/event-stream", "cache-control": "no-cache" });
74180
74401
  response.flush();
@@ -74203,8 +74424,8 @@ data: ${JSON.stringify(envelope)}
74203
74424
  sink.retire();
74204
74425
  response.end();
74205
74426
  };
74206
- const publish = (session, source, payload) => {
74207
- const uuid3 = nonEmptyString2(payload, "uuid");
74427
+ const publish = (session, source, payload, toWorker = source === RC_SOURCE_CLIENT) => {
74428
+ const uuid3 = nonEmptyString3(payload, "uuid");
74208
74429
  const duplicate = uuid3 !== void 0 && session.seenPayloadUuids.has(uuid3);
74209
74430
  session.sequenceNum += 1;
74210
74431
  const event = {
@@ -74212,9 +74433,9 @@ data: ${JSON.stringify(envelope)}
74212
74433
  eventId: uuid3 ?? deps.newUuid(),
74213
74434
  source,
74214
74435
  // 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.
74215
- payload: source === RC_SOURCE_CLIENT ? { ...payload, server_received_wall_ms: deps.now() } : payload,
74436
+ payload: source === RC_SOURCE_WORKER ? payload : { ...payload, server_received_wall_ms: deps.now() },
74216
74437
  createdAt: new Date(deps.now()).toISOString(),
74217
- toWorker: source === RC_SOURCE_CLIENT,
74438
+ toWorker,
74218
74439
  storedAt: deps.now()
74219
74440
  };
74220
74441
  session.events.push(event);
@@ -74262,8 +74483,26 @@ data: ${JSON.stringify(envelope)}
74262
74483
  }
74263
74484
  session.lastTrafficAt = deps.now();
74264
74485
  if (tail === "") {
74486
+ if (method === "PATCH" || method === "PUT") {
74487
+ const refusal = credentialRefusal(request7.headers);
74488
+ if (refusal !== void 0) {
74489
+ await unauthorized(response, refusal);
74490
+ return;
74491
+ }
74492
+ const body = parseJsonObject3(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74493
+ const title = nonEmptyString3(body, "title");
74494
+ if (title === void 0) {
74495
+ await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a session update is JSON naming a non-empty title" } });
74496
+ return;
74497
+ }
74498
+ session.title = title;
74499
+ session.lastTrafficAt = deps.now();
74500
+ deps.log?.(`rc selfhost ${session.id}: retitled at its client's request`);
74501
+ await answerJson2(response, HTTP_STATUS.ok, {});
74502
+ return;
74503
+ }
74265
74504
  if (method !== "GET") {
74266
- await answerJson2(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the session read is a GET" } });
74505
+ 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" } });
74267
74506
  return;
74268
74507
  }
74269
74508
  await answerJson2(response, HTTP_STATUS.ok, { session: { id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), status: "active" } });
@@ -74308,17 +74547,17 @@ data: ${JSON.stringify(envelope)}
74308
74547
  return;
74309
74548
  }
74310
74549
  if (workerTail === "" && method === "PUT") {
74311
- const body = parseJsonObject2(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74312
- const claimedEpoch = numberOf(body, "worker_epoch");
74550
+ const body = parseJsonObject3(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74551
+ const claimedEpoch = numberOf2(body, "worker_epoch");
74313
74552
  if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
74314
74553
  await epochStale(response);
74315
74554
  return;
74316
74555
  }
74317
- const status = nonEmptyString2(body, "worker_status");
74556
+ const status = nonEmptyString3(body, "worker_status");
74318
74557
  if (status !== void 0) {
74319
74558
  session.workerStatus = status;
74320
74559
  }
74321
- if (isRecord4(body?.external_metadata)) {
74560
+ if (isRecord5(body?.external_metadata)) {
74322
74561
  session.externalMetadata = body.external_metadata;
74323
74562
  }
74324
74563
  await answerJson2(response, HTTP_STATUS.ok, {});
@@ -74335,8 +74574,8 @@ data: ${JSON.stringify(envelope)}
74335
74574
  return;
74336
74575
  }
74337
74576
  if (workerTail === "events" && method === "POST") {
74338
- const body = parseJsonObject2(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74339
- const claimedEpoch = numberOf(body, "worker_epoch");
74577
+ const body = parseJsonObject3(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74578
+ const claimedEpoch = numberOf2(body, "worker_epoch");
74340
74579
  if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
74341
74580
  await epochStale(response);
74342
74581
  return;
@@ -74348,7 +74587,7 @@ data: ${JSON.stringify(envelope)}
74348
74587
  }
74349
74588
  const results = [];
74350
74589
  for (const event of events) {
74351
- if (!isRecord4(event) || !isRecord4(event.payload)) {
74590
+ if (!isRecord5(event) || !isRecord5(event.payload)) {
74352
74591
  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" } });
74353
74592
  return;
74354
74593
  }
@@ -74369,8 +74608,8 @@ data: ${JSON.stringify(envelope)}
74369
74608
  return;
74370
74609
  }
74371
74610
  if (workerTail === "internal-events" && method === "POST") {
74372
- const body = parseJsonObject2(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74373
- const claimedEpoch = numberOf(body, "worker_epoch");
74611
+ const body = parseJsonObject3(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74612
+ const claimedEpoch = numberOf2(body, "worker_epoch");
74374
74613
  if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
74375
74614
  await epochStale(response);
74376
74615
  return;
@@ -74381,19 +74620,26 @@ data: ${JSON.stringify(envelope)}
74381
74620
  return;
74382
74621
  }
74383
74622
  for (const event of events) {
74384
- if (isRecord4(event)) {
74385
- session.internalEvents.push({ cursor: deps.newUuid(), body: event });
74623
+ if (isRecord5(event)) {
74624
+ session.internalEvents.push({ eventId: deps.newUuid(), body: event });
74386
74625
  }
74387
74626
  }
74388
74627
  await answerJson2(response, HTTP_STATUS.ok, {});
74389
74628
  return;
74390
74629
  }
74391
74630
  if (workerTail === "internal-events" && method === "GET") {
74392
- const url2 = new URL(request7.url, "http://127.0.0.1");
74393
- const cursor3 = url2.searchParams.get("cursor");
74394
- const from = cursor3 === null ? 0 : session.internalEvents.findIndex((event) => event.cursor === cursor3) + 1;
74395
- const page = session.internalEvents.slice(from);
74396
- await answerJson2(response, HTTP_STATUS.ok, { data: page.map((event) => event.body), next_cursor: page.length === 0 ? void 0 : page[page.length - 1]?.cursor });
74631
+ const query = new URL(request7.url, "http://127.0.0.1").searchParams;
74632
+ if (query.get("session_agent_id") !== null || query.get("subagents") !== null) {
74633
+ 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" } });
74634
+ return;
74635
+ }
74636
+ const page = pageOfRcChannel(request7.url, session.internalEvents, (event) => event.eventId, (event) => ({ event_id: event.eventId, payload: event.body }));
74637
+ if ("error" in page) {
74638
+ const anchorUnknown = query.get("after_event_id") !== null && page.error.startsWith("the anchor names an event id");
74639
+ await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: anchorUnknown ? "after_event_id_not_found" : "invalid_request_error", message: page.error } });
74640
+ return;
74641
+ }
74642
+ await answerJson2(response, HTTP_STATUS.ok, page);
74397
74643
  return;
74398
74644
  }
74399
74645
  if (workerTail === "diagnostics" && method === "POST") {
@@ -74402,8 +74648,8 @@ data: ${JSON.stringify(envelope)}
74402
74648
  return;
74403
74649
  }
74404
74650
  if (workerTail === "web-fetch" && method === "POST") {
74405
- const body = parseJsonObject2(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74406
- const target = nonEmptyString2(body, "url");
74651
+ const body = parseJsonObject3(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74652
+ const target = nonEmptyString3(body, "url");
74407
74653
  if (target === void 0) {
74408
74654
  await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-fetch carries one url" } });
74409
74655
  return;
@@ -74425,8 +74671,8 @@ data: ${JSON.stringify(envelope)}
74425
74671
  return;
74426
74672
  }
74427
74673
  if (workerTail === "web-search" && method === "POST") {
74428
- const body = parseJsonObject2(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74429
- const query = nonEmptyString2(body, "query");
74674
+ const body = parseJsonObject3(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74675
+ const query = nonEmptyString3(body, "query");
74430
74676
  if (query === void 0) {
74431
74677
  await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-search carries one query" } });
74432
74678
  return;
@@ -74437,7 +74683,7 @@ data: ${JSON.stringify(envelope)}
74437
74683
  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" } });
74438
74684
  return;
74439
74685
  }
74440
- const searchProfile = nonEmptyString2(body, "search_profile");
74686
+ const searchProfile = nonEmptyString3(body, "search_profile");
74441
74687
  const backend = deps.webSearch;
74442
74688
  if (backend === void 0) {
74443
74689
  deps.log?.(`rc selfhost ${session.id}: worker web-search of "${query}" refused: no search backend serves this door`);
@@ -74462,7 +74708,7 @@ data: ${JSON.stringify(envelope)}
74462
74708
  return;
74463
74709
  }
74464
74710
  if (tail === "events" && method === "POST") {
74465
- const body = parseJsonObject2(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74711
+ const body = parseJsonObject3(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74466
74712
  const events = body?.events;
74467
74713
  if (!Array.isArray(events) || events.length === 0 || events.length > RC_SELF_HOST_MAX_BATCH_EVENTS) {
74468
74714
  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` } });
@@ -74470,7 +74716,7 @@ data: ${JSON.stringify(envelope)}
74470
74716
  }
74471
74717
  const results = [];
74472
74718
  for (const event of events) {
74473
- if (!isRecord4(event) || !isRecord4(event.payload)) {
74719
+ if (!isRecord5(event) || !isRecord5(event.payload)) {
74474
74720
  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" } });
74475
74721
  return;
74476
74722
  }
@@ -74482,26 +74728,14 @@ data: ${JSON.stringify(envelope)}
74482
74728
  return;
74483
74729
  }
74484
74730
  if (tail === "events" && method === "GET") {
74485
- 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 })) });
74731
+ 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 })) });
74486
74732
  return;
74487
74733
  }
74488
74734
  if (tail === "events/stream" && method === "GET") {
74489
74735
  await holdStream(session, "clientStreams", () => true, request7, response);
74490
74736
  return;
74491
74737
  }
74492
- if (tail === "client/presence" && method === "POST") {
74493
- await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
74494
- await answerJson2(response, HTTP_STATUS.ok, { refresh_after_seconds: RC_PRESENCE_REFRESH_SECONDS });
74495
- return;
74496
- }
74497
- if (tail === "mark_read" && method === "POST") {
74498
- await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
74499
- await answerJson2(response, HTTP_STATUS.ok, {});
74500
- return;
74501
- }
74502
- if (tail === "teleport-events" && method === "POST") {
74503
- await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
74504
- await answerJson2(response, HTTP_STATUS.ok, {});
74738
+ if (await serveRcClientChannels({ request: request7, response, session, answerJson: answerJson2, readBody: readBody3, 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)) {
74505
74739
  return;
74506
74740
  }
74507
74741
  await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the self-hosted Remote Control surface serves no path "${tail}"` } });
@@ -74518,17 +74752,8 @@ data: ${JSON.stringify(envelope)}
74518
74752
  await noCredential(response);
74519
74753
  return;
74520
74754
  }
74521
- if (pathname === RC_COMPAT_SESSIONS_PATH) {
74522
- if (method !== "GET") {
74523
- await answerJson2(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the compatibility session list is a GET" } });
74524
- return;
74525
- }
74526
- const listRefusal = credentialRefusal(request7.headers);
74527
- if (listRefusal !== void 0) {
74528
- await unauthorized(response, listRefusal);
74529
- return;
74530
- }
74531
- await answerJson2(response, HTTP_STATUS.ok, { data: [] });
74755
+ if (pathname === RC_COMPAT_SESSIONS_PATH || pathname.startsWith(`${RC_COMPAT_SESSIONS_PATH}/`)) {
74756
+ await serveRcCompatSessions({ request: request7, response, sessions, answerJson: answerJson2, readBody: readBody3, credentialRefusal, unauthorized, now: deps.now, ...deps.log === void 0 ? {} : { log: deps.log } }, pathname, RC_COMPAT_SESSIONS_PATH);
74532
74757
  return;
74533
74758
  }
74534
74759
  if (pathname !== RC_SESSIONS_PATH_PREFIX2 && !pathname.startsWith(`${RC_SESSIONS_PATH_PREFIX2}/`)) {
@@ -74542,11 +74767,11 @@ data: ${JSON.stringify(envelope)}
74542
74767
  await unauthorized(response, createRefusal);
74543
74768
  return;
74544
74769
  }
74545
- const body = parseJsonObject2(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74770
+ const body = parseJsonObject3(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74546
74771
  const session = {
74547
74772
  id: `${RC_SESSION_ID_PREFIX2}${deps.newUuid()}`,
74548
74773
  createdAt: deps.now(),
74549
- title: nonEmptyString2(body, "title"),
74774
+ title: nonEmptyString3(body, "title"),
74550
74775
  workerJwts: /* @__PURE__ */ new Set(),
74551
74776
  sequenceNum: 0,
74552
74777
  events: [],
@@ -74554,6 +74779,7 @@ data: ${JSON.stringify(envelope)}
74554
74779
  workerStreams: /* @__PURE__ */ new Set(),
74555
74780
  clientStreams: /* @__PURE__ */ new Set(),
74556
74781
  internalEvents: [],
74782
+ clients: /* @__PURE__ */ new Map(),
74557
74783
  workerStatus: void 0,
74558
74784
  externalMetadata: void 0,
74559
74785
  lastTrafficAt: deps.now()
@@ -74598,7 +74824,7 @@ data: ${JSON.stringify(envelope)}
74598
74824
  const pathname = new URL(request7.url ?? "/", "http://127.0.0.1").pathname;
74599
74825
  if (host === RC_SELF_HOST_OAUTH_HOST) {
74600
74826
  if (method === "POST" && pathname === "/v1/oauth/token") {
74601
- const body = parseJsonObject2(await readBody3(request7, CONTROL_BODY_CAP_BYTES));
74827
+ const body = parseJsonObject3(await readBody3(request7, CONTROL_BODY_CAP_BYTES));
74602
74828
  const record2 = deps.credentialRecord();
74603
74829
  if (record2 === void 0) {
74604
74830
  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" });
@@ -74683,7 +74909,7 @@ var RC_SELF_HOST_RECORD_DIR = "rc-selfhost";
74683
74909
  var RC_SELF_HOST_RECORD_FILE = "credential.json";
74684
74910
  var SELF_HOST_EMAIL = "selfhost@agent-shim.invalid";
74685
74911
  var SELF_HOST_SUBSCRIPTION_TYPE = "max";
74686
- function isRecord5(value2) {
74912
+ function isRecord6(value2) {
74687
74913
  return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
74688
74914
  }
74689
74915
  function readJsonObject(fs20, file2) {
@@ -74697,7 +74923,7 @@ function readJsonObject(fs20, file2) {
74697
74923
  } catch {
74698
74924
  return void 0;
74699
74925
  }
74700
- return isRecord5(parsed) ? parsed : void 0;
74926
+ return isRecord6(parsed) ? parsed : void 0;
74701
74927
  }
74702
74928
  function readRcSelfHostRecord(fs20, frontdoorDir) {
74703
74929
  const parsed = readJsonObject(fs20, import_node_path34.default.join(frontdoorDir, RC_SELF_HOST_RECORD_DIR, RC_SELF_HOST_RECORD_FILE));
@@ -74721,7 +74947,7 @@ function mintRcSelfHostCredential(deps) {
74721
74947
  const recordFile = import_node_path34.default.join(recordDir, RC_SELF_HOST_RECORD_FILE);
74722
74948
  const previousRecord = readRcSelfHostRecord(deps.fs, deps.frontdoorDir);
74723
74949
  const existingCredentials = readJsonObject(deps.fs, credentialsFile);
74724
- const existingOauth = isRecord5(existingCredentials?.claudeAiOauth) ? existingCredentials.claudeAiOauth : void 0;
74950
+ const existingOauth = isRecord6(existingCredentials?.claudeAiOauth) ? existingCredentials.claudeAiOauth : void 0;
74725
74951
  const existingToken = typeof existingOauth?.accessToken === "string" && existingOauth.accessToken !== "" ? existingOauth.accessToken : void 0;
74726
74952
  const existingIsOwn = existingToken !== void 0 && previousRecord?.accessToken === existingToken;
74727
74953
  if (existingToken !== void 0 && !existingIsOwn && !deps.force) {
@@ -74738,7 +74964,7 @@ function mintRcSelfHostCredential(deps) {
74738
74964
  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)}
74739
74965
  `);
74740
74966
  const claudeJson = readJsonObject(deps.fs, claudeJsonFile) ?? {};
74741
- const existingBlock = (value2) => isRecord5(value2) ? value2 : {};
74967
+ const existingBlock = (value2) => isRecord6(value2) ? value2 : {};
74742
74968
  deps.fs.writeFilePrivate(claudeJsonFile, `${JSON.stringify({
74743
74969
  ...claudeJson,
74744
74970
  oauthAccount: {
@@ -75037,6 +75263,12 @@ function registerRcCommand(frontdoor, paths) {
75037
75263
  }),
75038
75264
  ["agent-shim frontdoor rc mcp-oauth-callback-url --session cse_00000000-0000-4000-8000-000000000000 --server github --callback-url https://example.com/oauth/callback?code=x"]
75039
75265
  );
75266
+ withExamples(
75267
+ rc.command("teleport").description("Send one teleport marker into an observed session over the teleport-events channel, the write half the cloud UI's ultrapan path owns on the real service. The marker rides the SDK user-message shape, the line the teleport relay anchors on.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").requiredOption("--marker <text>", "The marker text, the string content the teleport relay anchors on.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
75268
+ reportControlWrite(`teleported a marker into the session`, options.session, await frontDoorRcControlFromState(realFarmFs, paths).teleport(options.session, options.marker), options.json, { marker: options.marker });
75269
+ }),
75270
+ ['agent-shim frontdoor rc teleport --session cse_00000000-0000-4000-8000-000000000000 --marker "__ULTRAPAN_TELEPORT_LOCAL__"']
75271
+ );
75040
75272
  withExamples(
75041
75273
  rc.command("watch").description("Print the observed sessions' client read stream events as they arrive, the approvals the door's held stream receives above all, until Ctrl-C leaves. Read-only.").argument("[session]", "One cse_ session id, as `frontdoor rc list` shows it; every observed session when omitted.").option("--json", "Print each event as one JSON line as it arrives.").action(async (session, options) => {
75042
75274
  const client = frontDoorRcApiFromState(realFarmFs, paths);
@@ -75096,11 +75328,11 @@ var import_node_path35 = __toESM(require("node:path"), 1);
75096
75328
  function isSafeSessionId(sessionId) {
75097
75329
  return /^[A-Za-z0-9_-]+$/.test(sessionId);
75098
75330
  }
75099
- function isRecord6(value2) {
75331
+ function isRecord7(value2) {
75100
75332
  return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
75101
75333
  }
75102
75334
  function credentialOfJson(parsed) {
75103
- if (!isRecord6(parsed)) {
75335
+ if (!isRecord7(parsed)) {
75104
75336
  return void 0;
75105
75337
  }
75106
75338
  const { authorization, anthropicVersion, anthropicClientPlatform } = parsed;
@@ -75130,7 +75362,7 @@ function createRcCredentialStore(fs20, dir) {
75130
75362
  } catch {
75131
75363
  return void 0;
75132
75364
  }
75133
- return isRecord6(parsed) ? parsed : void 0;
75365
+ return isRecord7(parsed) ? parsed : void 0;
75134
75366
  };
75135
75367
  return {
75136
75368
  read: (sessionId) => {
@@ -75694,7 +75926,7 @@ function createSseParser() {
75694
75926
  }
75695
75927
  };
75696
75928
  }
75697
- function isRecord7(value2) {
75929
+ function isRecord8(value2) {
75698
75930
  return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
75699
75931
  }
75700
75932
  function sequenceNumOf(value2) {
@@ -75716,7 +75948,7 @@ function parseRcStreamEnvelope(parsed) {
75716
75948
  } catch {
75717
75949
  return void 0;
75718
75950
  }
75719
- if (!isRecord7(data)) {
75951
+ if (!isRecord8(data)) {
75720
75952
  return void 0;
75721
75953
  }
75722
75954
  const eventType = data.event_type;
@@ -75799,9 +76031,26 @@ function createRcStreamHub(deps) {
75799
76031
  }
75800
76032
  return headers;
75801
76033
  };
76034
+ let receiptFailureLogged = false;
76035
+ const sendReceipt = (attachment, headers, eventId) => {
76036
+ if (eventId === void 0) {
76037
+ return;
76038
+ }
76039
+ deps.dial.markRead(attachment.sessionId, headers, eventId).then(
76040
+ () => {
76041
+ receiptFailureLogged = false;
76042
+ },
76043
+ (error62) => {
76044
+ if (!receiptFailureLogged) {
76045
+ receiptFailureLogged = true;
76046
+ deps.log?.(`rc stream ${attachment.sessionId}: the read receipt for event ${eventId} could not be sent (${error62 instanceof Error ? error62.message : String(error62)}); further receipt failures stay quiet until one lands`);
76047
+ }
76048
+ }
76049
+ );
76050
+ };
75802
76051
  const attempt = async (attachment, credential) => {
75803
76052
  const headers = streamHeaders(credential);
75804
- const presence = await deps.dial.announcePresence(attachment.sessionId, headers, attachment.clientId);
76053
+ const presence = await deps.dial.announcePresence(attachment.sessionId, headers, attachment.clientId, false);
75805
76054
  if (presence.status === HTTP_STATUS.unauthorized) {
75806
76055
  return "unauthorized";
75807
76056
  }
@@ -75821,6 +76070,7 @@ function createRcStreamHub(deps) {
75821
76070
  const parser = createSseParser();
75822
76071
  for await (const chunk of answer.chunks) {
75823
76072
  attachment.controller.signal.throwIfAborted();
76073
+ let lastEventId;
75824
76074
  for (const parsed of parser.write(Buffer.from(chunk).toString("utf8"))) {
75825
76075
  const envelope = parseRcStreamEnvelope(parsed);
75826
76076
  if (envelope === void 0) {
@@ -75828,10 +76078,21 @@ function createRcStreamHub(deps) {
75828
76078
  }
75829
76079
  deps.fileStreamEvent(attachment.sessionId, envelope);
75830
76080
  deps.fanout.publish({ session: attachment.sessionId, envelope });
76081
+ if (typeof envelope.event_id === "string" && envelope.event_type !== "mark_read") {
76082
+ lastEventId = envelope.event_id;
76083
+ }
75831
76084
  }
76085
+ sendReceipt(attachment, headers, lastEventId);
75832
76086
  }
75833
76087
  return "dropped";
75834
76088
  };
76089
+ const clearPresence = (attachment, headers) => {
76090
+ if (headers === void 0) {
76091
+ return;
76092
+ }
76093
+ void deps.dial.announcePresence(attachment.sessionId, headers, attachment.clientId, true).catch(() => {
76094
+ });
76095
+ };
75835
76096
  const runLoop = async (attachment) => {
75836
76097
  let retriedUnauthorized = false;
75837
76098
  for (; ; ) {
@@ -75843,6 +76104,7 @@ function createRcStreamHub(deps) {
75843
76104
  if (!live || credential?.authorization === void 0) {
75844
76105
  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"})`);
75845
76106
  persistCursor(attachment.sessionId);
76107
+ clearPresence(attachment, credential === void 0 ? void 0 : streamHeaders(credential));
75846
76108
  if (attachments.get(attachment.sessionId) === attachment) {
75847
76109
  attachments.delete(attachment.sessionId);
75848
76110
  }
@@ -75905,6 +76167,8 @@ function createRcStreamHub(deps) {
75905
76167
  close: () => {
75906
76168
  for (const attachment of attachments.values()) {
75907
76169
  persistCursor(attachment.sessionId);
76170
+ const credential = deps.credentialOf(attachment.sessionId);
76171
+ clearPresence(attachment, credential?.authorization === void 0 ? void 0 : streamHeaders(credential));
75908
76172
  attachment.stop();
75909
76173
  }
75910
76174
  attachments.clear();
@@ -76392,6 +76656,7 @@ function realFrontDoorSupervisorPorts(paths) {
76392
76656
  const rcMcpReconnect = async (sessionId, serverName) => await reconnectRcSessionMcpServer(rcControlRequestDeps, sessionId, serverName);
76393
76657
  const rcMcpAuthenticate = async (sessionId, serverName, redirectUri) => await authenticateRcSessionMcpServer(rcControlRequestDeps, sessionId, serverName, redirectUri);
76394
76658
  const rcMcpOAuthCallbackUrl = async (sessionId, serverName, callbackUrl) => await submitRcSessionMcpOAuthCallbackUrl(rcControlRequestDeps, sessionId, serverName, callbackUrl);
76659
+ const rcTeleport = async (sessionId, marker) => await teleportRcSession(rcControlRequestDeps, sessionId, marker);
76395
76660
  const controlDeps = {
76396
76661
  usageSnapshots: () => listUsageSnapshots(realFarmFs, paths.usageSnapshotsDir),
76397
76662
  usageSnapshotOf: (identity) => readUsageSnapshot(realFarmFs, paths.usageSnapshotsDir, identity),
@@ -76409,7 +76674,7 @@ function realFrontDoorSupervisorPorts(paths) {
76409
76674
  },
76410
76675
  log,
76411
76676
  mintLeaf(authority, LOOPBACK_LEAF_NAMES, /* @__PURE__ */ new Date()),
76412
- createRcControlHandler({ expectedToken: rcControlToken, list: rcTracker.list, statusOf: rcTracker.statusOf, pendingOf: rcTracker.pendingOf, inject: rcInject, answer: rcAnswer, interrupt: rcInterrupt, setModel: rcSetModel, setPermissionMode: rcSetPermissionMode, endSession: rcEndSession, getUsage: rcGetUsage, getContextUsage: rcGetContextUsage, readFile: rcReadFile, fileSuggestions: rcFileSuggestions, keepAlive: rcKeepAlive, mcpStatus: rcMcpStatus, mcpReconnect: rcMcpReconnect, mcpAuthenticate: rcMcpAuthenticate, mcpOAuthCallbackUrl: rcMcpOAuthCallbackUrl }),
76677
+ createRcControlHandler({ expectedToken: rcControlToken, list: rcTracker.list, statusOf: rcTracker.statusOf, pendingOf: rcTracker.pendingOf, inject: rcInject, answer: rcAnswer, interrupt: rcInterrupt, setModel: rcSetModel, setPermissionMode: rcSetPermissionMode, endSession: rcEndSession, getUsage: rcGetUsage, getContextUsage: rcGetContextUsage, readFile: rcReadFile, fileSuggestions: rcFileSuggestions, keepAlive: rcKeepAlive, mcpStatus: rcMcpStatus, mcpReconnect: rcMcpReconnect, mcpAuthenticate: rcMcpAuthenticate, mcpOAuthCallbackUrl: rcMcpOAuthCallbackUrl, teleport: rcTeleport }),
76413
76678
  [
76414
76679
  createDoorApiNodeHandler({
76415
76680
  expectedToken: rcControlToken,
@@ -76431,6 +76696,7 @@ function realFrontDoorSupervisorPorts(paths) {
76431
76696
  mcpReconnect: rcMcpReconnect,
76432
76697
  mcpAuthenticate: rcMcpAuthenticate,
76433
76698
  mcpOAuthCallbackUrl: rcMcpOAuthCallbackUrl,
76699
+ teleport: rcTeleport,
76434
76700
  fanout: rcFanout,
76435
76701
  ...controlDeps,
76436
76702
  events: doorEvents