agent-shim 8.23.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
@@ -93,10 +93,12 @@ __export(index_exports, {
93
93
  ProviderKindMismatchError: () => ProviderKindMismatchError,
94
94
  ProviderNotFoundError: () => ProviderNotFoundError,
95
95
  ProviderSchema: () => ProviderSchema,
96
+ RC_CONTEXT_USAGE_DETAILS: () => RC_CONTEXT_USAGE_DETAILS,
96
97
  RC_IDLE_EXPIRY_MS: () => RC_IDLE_EXPIRY_MS,
97
98
  RC_ORPC_PATH_PREFIX: () => RC_ORPC_PATH_PREFIX,
98
99
  RC_PENDING_DEADLINE_MS: () => RC_PENDING_DEADLINE_MS,
99
100
  RC_PERMISSION_MODES: () => RC_PERMISSION_MODES,
101
+ RC_READ_FILE_ENCODINGS: () => RC_READ_FILE_ENCODINGS,
100
102
  RC_REQUEST_PARSE_CAP_BYTES: () => RC_REQUEST_PARSE_CAP_BYTES,
101
103
  RC_SELF_HOST_ENV: () => RC_SELF_HOST_ENV,
102
104
  RC_SELF_HOST_KEEPALIVE_MS: () => RC_SELF_HOST_KEEPALIVE_MS,
@@ -127,11 +129,24 @@ __export(index_exports, {
127
129
  addPool: () => addPool,
128
130
  addProvider: () => addProvider,
129
131
  answerRcControlRequest: () => answerRcControlRequest,
132
+ authenticateRcSessionMcpServer: () => authenticateRcSessionMcpServer,
130
133
  buildEntryFacts: () => buildEntryFacts,
131
134
  buildLayoutPaths: () => buildLayoutPaths,
135
+ buildRcControlRequestPayload: () => buildRcControlRequestPayload,
132
136
  buildRcControlResponsePayload: () => buildRcControlResponsePayload,
137
+ buildRcEndSessionPayload: () => buildRcEndSessionPayload,
133
138
  buildRcEventWriteBody: () => buildRcEventWriteBody,
139
+ buildRcFileSuggestionsPayload: () => buildRcFileSuggestionsPayload,
140
+ buildRcGetContextUsagePayload: () => buildRcGetContextUsagePayload,
141
+ buildRcGetUsagePayload: () => buildRcGetUsagePayload,
134
142
  buildRcInterruptPayload: () => buildRcInterruptPayload,
143
+ buildRcKeepAlivePayload: () => buildRcKeepAlivePayload,
144
+ buildRcMarkReadBody: () => buildRcMarkReadBody,
145
+ buildRcMcpAuthenticatePayload: () => buildRcMcpAuthenticatePayload,
146
+ buildRcMcpOAuthCallbackUrlPayload: () => buildRcMcpOAuthCallbackUrlPayload,
147
+ buildRcMcpReconnectPayload: () => buildRcMcpReconnectPayload,
148
+ buildRcMcpStatusPayload: () => buildRcMcpStatusPayload,
149
+ buildRcReadFilePayload: () => buildRcReadFilePayload,
135
150
  buildRcSetModelPayload: () => buildRcSetModelPayload,
136
151
  buildRcSetPermissionModePayload: () => buildRcSetPermissionModePayload,
137
152
  buildRcUserMessagePayload: () => buildRcUserMessagePayload,
@@ -164,6 +179,7 @@ __export(index_exports, {
164
179
  doorApiAuth: () => doorApiAuth,
165
180
  doorApiNodeHandlerOf: () => doorApiNodeHandlerOf,
166
181
  effectiveWindow: () => effectiveWindow,
182
+ endRcSession: () => endRcSession,
167
183
  ensureCa: () => ensureCa,
168
184
  ensureFrontDoor: () => ensureFrontDoor,
169
185
  ensureHeadroom: () => ensureHeadroom,
@@ -180,6 +196,9 @@ __export(index_exports, {
180
196
  frontDoorRcApiClient: () => frontDoorRcApiClient,
181
197
  frontDoorRcControl: () => frontDoorRcControl,
182
198
  generateCa: () => generateCa,
199
+ getRcSessionContextUsage: () => getRcSessionContextUsage,
200
+ getRcSessionMcpStatus: () => getRcSessionMcpStatus,
201
+ getRcSessionUsage: () => getRcSessionUsage,
183
202
  headroomSocketTarget: () => headroomSocketTarget,
184
203
  identityExists: () => identityExists,
185
204
  injectRcUserMessage: () => injectRcUserMessage,
@@ -187,7 +206,9 @@ __export(index_exports, {
187
206
  isIdentityDirectoryName: () => isIdentityDirectoryName,
188
207
  isInterceptedHost: () => isInterceptedHost,
189
208
  isPrivateAddress: () => isPrivateAddress,
209
+ isRcContextUsageDetail: () => isRcContextUsageDetail,
190
210
  isRcPermissionMode: () => isRcPermissionMode,
211
+ isRcReadFileEncoding: () => isRcReadFileEncoding,
191
212
  lateRcEventDial: () => lateRcEventDial,
192
213
  lateRcStreamDial: () => lateRcStreamDial,
193
214
  listDirectoryRules: () => listDirectoryRules,
@@ -221,6 +242,7 @@ __export(index_exports, {
221
242
  readProfile: () => readProfile,
222
243
  readProvider: () => readProvider,
223
244
  readRcSelfHostRecord: () => readRcSelfHostRecord,
245
+ readRcSessionFile: () => readRcSessionFile,
224
246
  readUsageSnapshot: () => readUsageSnapshot,
225
247
  realConnectCertStore: () => realConnectCertStore,
226
248
  realConnectEffects: () => realConnectEffects,
@@ -228,6 +250,7 @@ __export(index_exports, {
228
250
  realRcEventDial: () => realRcEventDial,
229
251
  realRcStreamDial: () => realRcStreamDial,
230
252
  realRcWebFetch: () => realRcWebFetch,
253
+ reconnectRcSessionMcpServer: () => reconnectRcSessionMcpServer,
231
254
  recoverFarm: () => recoverFarm,
232
255
  recoveryDiagnostics: () => recoveryDiagnostics,
233
256
  removeDirectoryRule: () => removeDirectoryRule,
@@ -247,6 +270,7 @@ __export(index_exports, {
247
270
  runFrontDoorSupervisor: () => runFrontDoorSupervisor,
248
271
  runLauncher: () => runLauncher,
249
272
  runSupervisor: () => runSupervisor,
273
+ sendRcKeepAlive: () => sendRcKeepAlive,
250
274
  servedByPipeline: () => servedByPipeline,
251
275
  setAllowAmbientCredential: () => setAllowAmbientCredential,
252
276
  setDefaultConfigProfile: () => setDefaultConfigProfile,
@@ -262,6 +286,9 @@ __export(index_exports, {
262
286
  snapshotPath: () => snapshotPath,
263
287
  spawnClaude: () => spawnClaude,
264
288
  startConnectServer: () => startConnectServer,
289
+ submitRcSessionMcpOAuthCallbackUrl: () => submitRcSessionMcpOAuthCallbackUrl,
290
+ suggestRcSessionFiles: () => suggestRcSessionFiles,
291
+ teleportRcSession: () => teleportRcSession,
265
292
  topLevelNames: () => topLevelNames,
266
293
  updateDirectoryRule: () => updateDirectoryRule,
267
294
  updateProvider: () => updateProvider,
@@ -3479,9 +3506,6 @@ var HTTP_STATUS = {
3479
3506
  badGateway: 502
3480
3507
  };
3481
3508
 
3482
- // src/frontdoor/rcControl.ts
3483
- var https = __toESM(require("node:https"), 1);
3484
-
3485
3509
  // src/frontdoor/rcSessions.ts
3486
3510
  var RC_SESSIONS_PATH_PREFIX = "/v1/code/sessions";
3487
3511
  var RC_SESSION_ID_PREFIX = "cse_";
@@ -3902,6 +3926,8 @@ function rcObserverAsPassthrough(observer) {
3902
3926
  }
3903
3927
  };
3904
3928
  }
3929
+
3930
+ // src/frontdoor/rcWrites.ts
3905
3931
  function buildRcUserMessagePayload(uuid, sessionId, text) {
3906
3932
  return {
3907
3933
  type: "user",
@@ -4002,14 +4028,55 @@ var RC_PERMISSION_MODES = ["default", "acceptEdits", "bypassPermissions", "plan"
4002
4028
  function isRcPermissionMode(value) {
4003
4029
  return RC_PERMISSION_MODES.some((mode) => mode === value);
4004
4030
  }
4031
+ function buildRcControlRequestPayload(requestId, request4) {
4032
+ return { type: "control_request", request_id: requestId, request: request4 };
4033
+ }
4005
4034
  function buildRcInterruptPayload(requestId) {
4006
- return { type: "control_request", request_id: requestId, request: { subtype: "interrupt" } };
4035
+ return buildRcControlRequestPayload(requestId, { subtype: "interrupt" });
4007
4036
  }
4008
4037
  function buildRcSetModelPayload(requestId, model) {
4009
- return { type: "control_request", request_id: requestId, request: { subtype: "set_model", model } };
4038
+ return buildRcControlRequestPayload(requestId, { subtype: "set_model", model });
4010
4039
  }
4011
4040
  function buildRcSetPermissionModePayload(requestId, mode) {
4012
- return { type: "control_request", request_id: requestId, request: { subtype: "set_permission_mode", mode } };
4041
+ return buildRcControlRequestPayload(requestId, { subtype: "set_permission_mode", mode });
4042
+ }
4043
+ function buildRcEndSessionPayload(requestId, reason) {
4044
+ return buildRcControlRequestPayload(requestId, { subtype: "end_session", ...reason === void 0 ? {} : { reason } });
4045
+ }
4046
+ function buildRcGetUsagePayload(requestId, skipBehaviors) {
4047
+ return buildRcControlRequestPayload(requestId, { subtype: "get_usage", ...skipBehaviors === true ? { skip_behaviors: true } : {} });
4048
+ }
4049
+ var RC_CONTEXT_USAGE_DETAILS = ["summary", "full"];
4050
+ function isRcContextUsageDetail(value) {
4051
+ return RC_CONTEXT_USAGE_DETAILS.some((detail) => detail === value);
4052
+ }
4053
+ function buildRcGetContextUsagePayload(requestId, detail) {
4054
+ return buildRcControlRequestPayload(requestId, { subtype: "get_context_usage", ...detail === void 0 ? {} : { detail } });
4055
+ }
4056
+ var RC_READ_FILE_ENCODINGS = ["utf-8", "base64"];
4057
+ function isRcReadFileEncoding(value) {
4058
+ return RC_READ_FILE_ENCODINGS.some((encoding) => encoding === value);
4059
+ }
4060
+ function buildRcReadFilePayload(requestId, path32, options) {
4061
+ return buildRcControlRequestPayload(requestId, { subtype: "read_file", path: path32, ...options?.maxBytes === void 0 ? {} : { max_bytes: options.maxBytes }, ...options?.encoding === void 0 ? {} : { encoding: options.encoding } });
4062
+ }
4063
+ function buildRcFileSuggestionsPayload(requestId, query) {
4064
+ return buildRcControlRequestPayload(requestId, { subtype: "file_suggestions", query });
4065
+ }
4066
+ function buildRcMcpStatusPayload(requestId) {
4067
+ return buildRcControlRequestPayload(requestId, { subtype: "mcp_status" });
4068
+ }
4069
+ function buildRcMcpReconnectPayload(requestId, serverName) {
4070
+ return buildRcControlRequestPayload(requestId, { subtype: "mcp_reconnect", serverName });
4071
+ }
4072
+ function buildRcMcpAuthenticatePayload(requestId, serverName, redirectUri) {
4073
+ return buildRcControlRequestPayload(requestId, { subtype: "mcp_authenticate", serverName, redirectUri });
4074
+ }
4075
+ function buildRcMcpOAuthCallbackUrlPayload(requestId, serverName, callbackUrl) {
4076
+ return buildRcControlRequestPayload(requestId, { subtype: "mcp_oauth_callback_url", serverName, callbackUrl });
4077
+ }
4078
+ function buildRcKeepAlivePayload() {
4079
+ return { type: "keep_alive" };
4013
4080
  }
4014
4081
  async function answerRcControlRequest(deps, sessionId, requestId, decision) {
4015
4082
  const credential = deps.credentialOf(sessionId);
@@ -4036,7 +4103,7 @@ async function answerRcControlRequest(deps, sessionId, requestId, decision) {
4036
4103
  }
4037
4104
  return result;
4038
4105
  }
4039
- async function interruptRcSession(deps, sessionId) {
4106
+ async function deliverRcClientPayload(deps, sessionId, payload, action, to = "events") {
4040
4107
  const credential = deps.credentialOf(sessionId);
4041
4108
  if (credential === void 0) {
4042
4109
  return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
@@ -4044,12 +4111,12 @@ async function interruptRcSession(deps, sessionId) {
4044
4111
  if (credential.authorization === void 0) {
4045
4112
  return noOAuthCredentialResult(sessionId);
4046
4113
  }
4047
- const body = JSON.stringify(buildRcEventWriteBody(buildRcInterruptPayload(deps.newUuid())));
4114
+ const body = JSON.stringify(buildRcEventWriteBody(payload));
4048
4115
  let answer;
4049
4116
  try {
4050
- 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));
4051
4118
  } catch (error) {
4052
- return { ok: false, message: `the door could not reach the API host to interrupt session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
4119
+ return { ok: false, message: `the door could not reach the API host to ${action}: ${error instanceof Error ? error.message : String(error)}` };
4053
4120
  }
4054
4121
  const result = rcEventWriteResultFromAnswer(answer);
4055
4122
  if (result.ok) {
@@ -4057,53 +4124,102 @@ async function interruptRcSession(deps, sessionId) {
4057
4124
  }
4058
4125
  return result;
4059
4126
  }
4127
+ async function interruptRcSession(deps, sessionId) {
4128
+ const requestId = deps.newUuid();
4129
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcInterruptPayload(requestId), `interrupt session ${sessionId}`);
4130
+ return result.ok ? { ...result, requestId } : result;
4131
+ }
4060
4132
  async function setRcSessionModel(deps, sessionId, model) {
4061
4133
  if (model === "") {
4062
4134
  return { ok: false, message: "a set-model write needs a non-empty model id: the SDK's own field treats an omitted or null value as a reset to the session default, which this operation never sends, and an empty string is neither" };
4063
4135
  }
4064
- const credential = deps.credentialOf(sessionId);
4065
- if (credential === void 0) {
4066
- return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
4067
- }
4068
- if (credential.authorization === void 0) {
4069
- return noOAuthCredentialResult(sessionId);
4136
+ const requestId = deps.newUuid();
4137
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcSetModelPayload(requestId, model), `set the model on session ${sessionId}`);
4138
+ return result.ok ? { ...result, requestId } : result;
4139
+ }
4140
+ async function setRcSessionPermissionMode(deps, sessionId, mode) {
4141
+ const requestId = deps.newUuid();
4142
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcSetPermissionModePayload(requestId, mode), `set the permission mode on session ${sessionId}`);
4143
+ return result.ok ? { ...result, requestId } : result;
4144
+ }
4145
+ async function endRcSession(deps, sessionId, reason) {
4146
+ if (reason === "") {
4147
+ return { ok: false, message: "an end-session write needs either no reason or a non-empty one: the worker reads an absent reason as its own unspecified form, and an empty string is neither" };
4070
4148
  }
4071
- const body = JSON.stringify(buildRcEventWriteBody(buildRcSetModelPayload(deps.newUuid(), model)));
4072
- let answer;
4073
- try {
4074
- answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
4075
- } catch (error) {
4076
- return { ok: false, message: `the door could not reach the API host to set the model on session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
4149
+ const requestId = deps.newUuid();
4150
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcEndSessionPayload(requestId, reason), `end session ${sessionId}`);
4151
+ return result.ok ? { ...result, requestId } : result;
4152
+ }
4153
+ async function getRcSessionUsage(deps, sessionId, skipBehaviors) {
4154
+ const requestId = deps.newUuid();
4155
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcGetUsagePayload(requestId, skipBehaviors), `request the usage of session ${sessionId}`);
4156
+ return result.ok ? { ...result, requestId } : result;
4157
+ }
4158
+ async function getRcSessionContextUsage(deps, sessionId, detail) {
4159
+ const requestId = deps.newUuid();
4160
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcGetContextUsagePayload(requestId, detail), `request the context usage of session ${sessionId}`);
4161
+ return result.ok ? { ...result, requestId } : result;
4162
+ }
4163
+ async function readRcSessionFile(deps, sessionId, path32, options) {
4164
+ if (path32 === "") {
4165
+ return { ok: false, message: "a read-file write needs a non-empty path: the SDK's own field names the file the worker resolves against the session's cwd, and an empty string names none" };
4077
4166
  }
4078
- const result = rcEventWriteResultFromAnswer(answer);
4079
- if (result.ok) {
4080
- deps.noteSequenceNums(sessionId, result.sequenceNums);
4167
+ if (options?.maxBytes !== void 0 && (!Number.isInteger(options.maxBytes) || options.maxBytes <= 0)) {
4168
+ return { ok: false, message: "a read-file write's max-bytes is a positive whole number of bytes: the SDK's own field caps the read, and a zero or negative cap reads nothing" };
4081
4169
  }
4082
- return result;
4170
+ const requestId = deps.newUuid();
4171
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcReadFilePayload(requestId, path32, options), `read ${path32} from session ${sessionId}`);
4172
+ return result.ok ? { ...result, requestId } : result;
4083
4173
  }
4084
- async function setRcSessionPermissionMode(deps, sessionId, mode) {
4085
- const credential = deps.credentialOf(sessionId);
4086
- if (credential === void 0) {
4087
- return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
4174
+ async function suggestRcSessionFiles(deps, sessionId, query) {
4175
+ const requestId = deps.newUuid();
4176
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcFileSuggestionsPayload(requestId, query), `request file suggestions from session ${sessionId}`);
4177
+ return result.ok ? { ...result, requestId } : result;
4178
+ }
4179
+ async function getRcSessionMcpStatus(deps, sessionId) {
4180
+ const requestId = deps.newUuid();
4181
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcMcpStatusPayload(requestId), `request the MCP status of session ${sessionId}`);
4182
+ return result.ok ? { ...result, requestId } : result;
4183
+ }
4184
+ async function reconnectRcSessionMcpServer(deps, sessionId, serverName) {
4185
+ if (serverName === "") {
4186
+ return { ok: false, message: "an mcp-reconnect write needs a non-empty server name: the SDK's own field names the server as mcp_status reports it, and an empty string names none" };
4088
4187
  }
4089
- if (credential.authorization === void 0) {
4090
- return noOAuthCredentialResult(sessionId);
4188
+ const requestId = deps.newUuid();
4189
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcMcpReconnectPayload(requestId, serverName), `reconnect MCP server ${serverName} on session ${sessionId}`);
4190
+ return result.ok ? { ...result, requestId } : result;
4191
+ }
4192
+ async function authenticateRcSessionMcpServer(deps, sessionId, serverName, redirectUri) {
4193
+ if (serverName === "" || redirectUri === "") {
4194
+ return { ok: false, message: "an mcp-authenticate write needs a non-empty server name and redirect URI: the CLI's own request names the server and where its OAuth flow redirects back to, and an empty string names neither" };
4091
4195
  }
4092
- const body = JSON.stringify(buildRcEventWriteBody(buildRcSetPermissionModePayload(deps.newUuid(), mode)));
4093
- let answer;
4094
- try {
4095
- answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
4096
- } catch (error) {
4097
- return { ok: false, message: `the door could not reach the API host to set the permission mode on session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
4196
+ const requestId = deps.newUuid();
4197
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcMcpAuthenticatePayload(requestId, serverName, redirectUri), `authenticate MCP server ${serverName} on session ${sessionId}`);
4198
+ return result.ok ? { ...result, requestId } : result;
4199
+ }
4200
+ async function submitRcSessionMcpOAuthCallbackUrl(deps, sessionId, serverName, callbackUrl) {
4201
+ if (serverName === "" || callbackUrl === "") {
4202
+ return { ok: false, message: "an mcp-oauth-callback-url write needs a non-empty server name and callback URL: the CLI's own request names the server and the URL its OAuth flow landed on, and an empty string names neither" };
4098
4203
  }
4099
- const result = rcEventWriteResultFromAnswer(answer);
4100
- if (result.ok) {
4101
- deps.noteSequenceNums(sessionId, result.sequenceNums);
4204
+ const requestId = deps.newUuid();
4205
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcMcpOAuthCallbackUrlPayload(requestId, serverName, callbackUrl), `hand MCP server ${serverName}'s OAuth callback to session ${sessionId}`);
4206
+ return result.ok ? { ...result, requestId } : result;
4207
+ }
4208
+ async function sendRcKeepAlive(deps, sessionId) {
4209
+ return await deliverRcClientPayload(deps, sessionId, buildRcKeepAlivePayload(), `keep session ${sessionId} alive`);
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" };
4102
4214
  }
4103
- return result;
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 };
4104
4219
  }
4105
4220
 
4106
4221
  // src/frontdoor/rcControl.ts
4222
+ var https = __toESM(require("node:https"), 1);
4107
4223
  var RC_PERMISSION_MODE_LIST = RC_PERMISSION_MODES.join(", ");
4108
4224
  var CONTROL_PATH_PREFIX = "/__agent-shim/rc";
4109
4225
  var CONTROL_BODY_CAP_BYTES = 10485760;
@@ -4112,6 +4228,25 @@ function answerJson(response, status, body) {
4112
4228
  response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text, "utf8")) });
4113
4229
  response.end(text);
4114
4230
  }
4231
+ function isJsonObject(value) {
4232
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4233
+ }
4234
+ function bodyString(body, field) {
4235
+ const value = body[field];
4236
+ return typeof value === "string" && value !== "" ? value : void 0;
4237
+ }
4238
+ function bodyStringAllowingEmpty(body, field) {
4239
+ const value = body[field];
4240
+ return typeof value === "string" ? value : void 0;
4241
+ }
4242
+ function bodyBoolean(body, field) {
4243
+ const value = body[field];
4244
+ return typeof value === "boolean" ? value : void 0;
4245
+ }
4246
+ function bodyPositiveInteger(body, field) {
4247
+ const value = body[field];
4248
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
4249
+ }
4115
4250
  async function readBody(request4) {
4116
4251
  return await new Promise((resolve, reject) => {
4117
4252
  const chunks = [];
@@ -4139,9 +4274,136 @@ function createRcControlHandler(deps) {
4139
4274
  const pendingPath = `${CONTROL_PATH_PREFIX}/pending`;
4140
4275
  const injectPath = `${CONTROL_PATH_PREFIX}/inject`;
4141
4276
  const answerPath = `${CONTROL_PATH_PREFIX}/answer`;
4142
- const interruptPath = `${CONTROL_PATH_PREFIX}/interrupt`;
4143
- const setModelPath = `${CONTROL_PATH_PREFIX}/set-model`;
4144
- const setPermissionModePath = `${CONTROL_PATH_PREFIX}/set-permission-mode`;
4277
+ const controlWrites = [
4278
+ {
4279
+ name: "interrupt",
4280
+ path: "interrupt",
4281
+ read: (body) => {
4282
+ const session = bodyString(body, "session");
4283
+ return session === void 0 ? { error: "the interrupt body must be JSON naming a non-empty session id" } : { session, run: async () => await deps.interrupt(session) };
4284
+ }
4285
+ },
4286
+ {
4287
+ name: "set-model",
4288
+ path: "set-model",
4289
+ read: (body) => {
4290
+ const session = bodyString(body, "session");
4291
+ const model = bodyString(body, "model");
4292
+ return session === void 0 || model === void 0 ? { error: "the set-model body must be JSON naming a non-empty session id and model id" } : { session, run: async () => await deps.setModel(session, model) };
4293
+ }
4294
+ },
4295
+ {
4296
+ name: "set-permission-mode",
4297
+ path: "set-permission-mode",
4298
+ read: (body) => {
4299
+ const session = bodyString(body, "session");
4300
+ const mode = body.mode;
4301
+ return session === void 0 || !isRcPermissionMode(mode) ? { error: `the set-permission-mode body must be JSON naming a non-empty session id and a mode the SDK's own type permits (${RC_PERMISSION_MODE_LIST})` } : { session, run: async () => await deps.setPermissionMode(session, mode) };
4302
+ }
4303
+ },
4304
+ {
4305
+ name: "end-session",
4306
+ path: "end-session",
4307
+ read: (body) => {
4308
+ const session = bodyString(body, "session");
4309
+ const reason = "reason" in body ? bodyString(body, "reason") : void 0;
4310
+ return session === void 0 || "reason" in body && reason === void 0 ? { error: "the end-session body must be JSON naming a non-empty session id, and a reason only as a non-empty string or absent" } : { session, run: async () => await deps.endSession(session, reason) };
4311
+ }
4312
+ },
4313
+ {
4314
+ name: "get-usage",
4315
+ path: "get-usage",
4316
+ read: (body) => {
4317
+ const session = bodyString(body, "session");
4318
+ const skipBehaviors = "skipBehaviors" in body ? bodyBoolean(body, "skipBehaviors") : void 0;
4319
+ return session === void 0 || "skipBehaviors" in body && skipBehaviors === void 0 ? { error: "the get-usage body must be JSON naming a non-empty session id, and skipBehaviors only as a boolean or absent" } : { session, run: async () => await deps.getUsage(session, skipBehaviors) };
4320
+ }
4321
+ },
4322
+ {
4323
+ name: "get-context-usage",
4324
+ path: "get-context-usage",
4325
+ read: (body) => {
4326
+ const session = bodyString(body, "session");
4327
+ const detail = "detail" in body && isRcContextUsageDetail(body.detail) ? body.detail : void 0;
4328
+ return session === void 0 || "detail" in body && detail === void 0 ? { error: `the get-context-usage body must be JSON naming a non-empty session id, and a detail level the SDK's own type permits (${RC_CONTEXT_USAGE_DETAILS.join(", ")}) or none` } : { session, run: async () => await deps.getContextUsage(session, detail) };
4329
+ }
4330
+ },
4331
+ {
4332
+ name: "read-file",
4333
+ path: "read-file",
4334
+ read: (body) => {
4335
+ const session = bodyString(body, "session");
4336
+ const path32 = bodyString(body, "path");
4337
+ const maxBytes = "maxBytes" in body ? bodyPositiveInteger(body, "maxBytes") : void 0;
4338
+ const encoding = "encoding" in body && isRcReadFileEncoding(body.encoding) ? body.encoding : void 0;
4339
+ const refused2 = session === void 0 || path32 === void 0 || "maxBytes" in body && maxBytes === void 0 || "encoding" in body && encoding === void 0;
4340
+ return refused2 ? { error: `the read-file body must be JSON naming a non-empty session id and path, maxBytes only as a positive whole number or absent, and an encoding the SDK's own type permits (${RC_READ_FILE_ENCODINGS.join(", ")}) or none` } : { session, run: async () => await deps.readFile(session, path32, { ...maxBytes === void 0 ? {} : { maxBytes }, ...encoding === void 0 ? {} : { encoding } }) };
4341
+ }
4342
+ },
4343
+ {
4344
+ name: "file-suggestions",
4345
+ path: "file-suggestions",
4346
+ read: (body) => {
4347
+ const session = bodyString(body, "session");
4348
+ const query = bodyStringAllowingEmpty(body, "query");
4349
+ return session === void 0 || query === void 0 ? { error: "the file-suggestions body must be JSON naming a non-empty session id and a query string, which may be empty" } : { session, run: async () => await deps.fileSuggestions(session, query) };
4350
+ }
4351
+ },
4352
+ {
4353
+ name: "keep-alive",
4354
+ path: "keep-alive",
4355
+ read: (body) => {
4356
+ const session = bodyString(body, "session");
4357
+ return session === void 0 ? { error: "the keep-alive body must be JSON naming a non-empty session id" } : { session, run: async () => await deps.keepAlive(session) };
4358
+ }
4359
+ },
4360
+ {
4361
+ name: "mcp-status",
4362
+ path: "mcp-status",
4363
+ read: (body) => {
4364
+ const session = bodyString(body, "session");
4365
+ return session === void 0 ? { error: "the mcp-status body must be JSON naming a non-empty session id" } : { session, run: async () => await deps.mcpStatus(session) };
4366
+ }
4367
+ },
4368
+ {
4369
+ name: "mcp-reconnect",
4370
+ path: "mcp-reconnect",
4371
+ read: (body) => {
4372
+ const session = bodyString(body, "session");
4373
+ const serverName = bodyString(body, "serverName");
4374
+ return session === void 0 || serverName === void 0 ? { error: "the mcp-reconnect body must be JSON naming a non-empty session id and server name" } : { session, run: async () => await deps.mcpReconnect(session, serverName) };
4375
+ }
4376
+ },
4377
+ {
4378
+ name: "mcp-authenticate",
4379
+ path: "mcp-authenticate",
4380
+ read: (body) => {
4381
+ const session = bodyString(body, "session");
4382
+ const serverName = bodyString(body, "serverName");
4383
+ const redirectUri = bodyString(body, "redirectUri");
4384
+ return session === void 0 || serverName === void 0 || redirectUri === void 0 ? { error: "the mcp-authenticate body must be JSON naming a non-empty session id, server name, and redirect URI" } : { session, run: async () => await deps.mcpAuthenticate(session, serverName, redirectUri) };
4385
+ }
4386
+ },
4387
+ {
4388
+ name: "mcp-oauth-callback-url",
4389
+ path: "mcp-oauth-callback-url",
4390
+ read: (body) => {
4391
+ const session = bodyString(body, "session");
4392
+ const serverName = bodyString(body, "serverName");
4393
+ const callbackUrl = bodyString(body, "callbackUrl");
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) };
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
+ }
4405
+ }
4406
+ ];
4145
4407
  return (request4, response) => {
4146
4408
  const presented = request4.headers.authorization;
4147
4409
  const bearerPrefix = "bearer ".length;
@@ -4226,73 +4488,33 @@ function createRcControlHandler(deps) {
4226
4488
  answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
4227
4489
  return;
4228
4490
  }
4229
- if (url.pathname === interruptPath) {
4230
- if (request4.method !== "POST") {
4231
- answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the interrupt operation is a POST" });
4232
- return;
4233
- }
4234
- let parsed;
4235
- try {
4236
- parsed = JSON.parse(await readBody(request4));
4237
- } catch {
4238
- answerJson(response, HTTP_STATUS.badRequest, { error: "the interrupt body is not readable JSON within the control body cap" });
4239
- return;
4240
- }
4241
- if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || typeof parsed.session !== "string" || parsed.session === "") {
4242
- answerJson(response, HTTP_STATUS.badRequest, { error: "the interrupt body must be JSON naming a non-empty session id" });
4243
- return;
4244
- }
4245
- const result = await deps.interrupt(parsed.session);
4246
- if (result.ok) {
4247
- answerJson(response, HTTP_STATUS.ok, { session: parsed.session, sequenceNums: result.sequenceNums });
4248
- return;
4491
+ for (const route of controlWrites) {
4492
+ if (url.pathname !== `${CONTROL_PATH_PREFIX}/${route.path}`) {
4493
+ continue;
4249
4494
  }
4250
- answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
4251
- return;
4252
- }
4253
- if (url.pathname === setModelPath) {
4254
4495
  if (request4.method !== "POST") {
4255
- answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the set-model operation is a POST" });
4496
+ answerJson(response, HTTP_STATUS.methodNotAllowed, { error: `the ${route.name} operation is a POST` });
4256
4497
  return;
4257
4498
  }
4258
4499
  let parsed;
4259
4500
  try {
4260
4501
  parsed = JSON.parse(await readBody(request4));
4261
4502
  } catch {
4262
- answerJson(response, HTTP_STATUS.badRequest, { error: "the set-model body is not readable JSON within the control body cap" });
4263
- return;
4264
- }
4265
- if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("model" in parsed) || typeof parsed.session !== "string" || typeof parsed.model !== "string" || parsed.session === "" || parsed.model === "") {
4266
- answerJson(response, HTTP_STATUS.badRequest, { error: "the set-model body must be JSON naming a non-empty session id and model id" });
4267
- return;
4268
- }
4269
- const result = await deps.setModel(parsed.session, parsed.model);
4270
- if (result.ok) {
4271
- answerJson(response, HTTP_STATUS.ok, { session: parsed.session, model: parsed.model, sequenceNums: result.sequenceNums });
4272
- return;
4273
- }
4274
- answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
4275
- return;
4276
- }
4277
- if (url.pathname === setPermissionModePath) {
4278
- if (request4.method !== "POST") {
4279
- answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the set-permission-mode operation is a POST" });
4503
+ answerJson(response, HTTP_STATUS.badRequest, { error: `the ${route.name} body is not readable JSON within the control body cap` });
4280
4504
  return;
4281
4505
  }
4282
- let parsed;
4283
- try {
4284
- parsed = JSON.parse(await readBody(request4));
4285
- } catch {
4286
- answerJson(response, HTTP_STATUS.badRequest, { error: "the set-permission-mode body is not readable JSON within the control body cap" });
4506
+ if (!isJsonObject(parsed)) {
4507
+ answerJson(response, HTTP_STATUS.badRequest, { error: `the ${route.name} body must be a JSON object` });
4287
4508
  return;
4288
4509
  }
4289
- if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("mode" in parsed) || typeof parsed.session !== "string" || parsed.session === "" || !isRcPermissionMode(parsed.mode)) {
4290
- answerJson(response, HTTP_STATUS.badRequest, { error: `the set-permission-mode body must be JSON naming a non-empty session id and a mode the SDK's own type permits (${RC_PERMISSION_MODE_LIST})` });
4510
+ const read = route.read(parsed);
4511
+ if ("error" in read) {
4512
+ answerJson(response, HTTP_STATUS.badRequest, { error: read.error });
4291
4513
  return;
4292
4514
  }
4293
- const result = await deps.setPermissionMode(parsed.session, parsed.mode);
4515
+ const result = await read.run();
4294
4516
  if (result.ok) {
4295
- answerJson(response, HTTP_STATUS.ok, { session: parsed.session, mode: parsed.mode, sequenceNums: result.sequenceNums });
4517
+ answerJson(response, HTTP_STATUS.ok, { session: read.session, ...result.requestId === void 0 ? {} : { request: result.requestId }, sequenceNums: result.sequenceNums });
4296
4518
  return;
4297
4519
  }
4298
4520
  answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
@@ -4410,6 +4632,19 @@ function sequenceNumsOfBody(body) {
4410
4632
  }
4411
4633
  return sequenceNums;
4412
4634
  }
4635
+ function requestIdOfBody(body) {
4636
+ let parsed;
4637
+ try {
4638
+ parsed = JSON.parse(body);
4639
+ } catch {
4640
+ return void 0;
4641
+ }
4642
+ if (typeof parsed !== "object" || parsed === null || !("request" in parsed)) {
4643
+ return void 0;
4644
+ }
4645
+ const value = parsed.request;
4646
+ return typeof value === "string" && value !== "" ? value : void 0;
4647
+ }
4413
4648
  function frontDoorRcControl(transport, token) {
4414
4649
  const headers = { authorization: `Bearer ${token}` };
4415
4650
  const listAnswerOf = async (path32, what) => {
@@ -4498,7 +4733,8 @@ function frontDoorRcControl(transport, token) {
4498
4733
  if (sequenceNums === void 0) {
4499
4734
  return { ok: false, message: `the door accepted the event but its answer named no sequence numbers: ${answer.body}` };
4500
4735
  }
4501
- return { ok: true, sequenceNums };
4736
+ const requestId = requestIdOfBody(answer.body);
4737
+ return requestId === void 0 ? { ok: true, sequenceNums } : { ok: true, sequenceNums, requestId };
4502
4738
  }
4503
4739
  return { ok: false, message: describeFailure(answer.status, controlErrorOf(answer.body) ?? answer.body) };
4504
4740
  };
@@ -4515,7 +4751,22 @@ function frontDoorRcControl(transport, token) {
4515
4751
  ),
4516
4752
  interruptSession: async (sessionId) => await writeOf(`${CONTROL_PATH_PREFIX}/interrupt`, JSON.stringify({ session: sessionId }), (status, message) => `interrupting ${sessionId} failed (HTTP ${String(status)}): ${message}`),
4517
4753
  setModel: async (sessionId, model) => await writeOf(`${CONTROL_PATH_PREFIX}/set-model`, JSON.stringify({ session: sessionId, model }), (status, message) => `setting the model on ${sessionId} failed (HTTP ${String(status)}): ${message}`),
4518
- setPermissionMode: async (sessionId, mode) => await writeOf(`${CONTROL_PATH_PREFIX}/set-permission-mode`, JSON.stringify({ session: sessionId, mode }), (status, message) => `setting the permission mode on ${sessionId} failed (HTTP ${String(status)}): ${message}`)
4754
+ setPermissionMode: async (sessionId, mode) => await writeOf(`${CONTROL_PATH_PREFIX}/set-permission-mode`, JSON.stringify({ session: sessionId, mode }), (status, message) => `setting the permission mode on ${sessionId} failed (HTTP ${String(status)}): ${message}`),
4755
+ endSession: async (sessionId, reason) => await writeOf(`${CONTROL_PATH_PREFIX}/end-session`, JSON.stringify({ session: sessionId, ...reason === void 0 ? {} : { reason } }), (status, message) => `ending ${sessionId} failed (HTTP ${String(status)}): ${message}`),
4756
+ getUsage: async (sessionId, skipBehaviors) => await writeOf(`${CONTROL_PATH_PREFIX}/get-usage`, JSON.stringify({ session: sessionId, ...skipBehaviors === void 0 ? {} : { skipBehaviors } }), (status, message) => `requesting the usage of ${sessionId} failed (HTTP ${String(status)}): ${message}`),
4757
+ getContextUsage: async (sessionId, detail) => await writeOf(`${CONTROL_PATH_PREFIX}/get-context-usage`, JSON.stringify({ session: sessionId, ...detail === void 0 ? {} : { detail } }), (status, message) => `requesting the context usage of ${sessionId} failed (HTTP ${String(status)}): ${message}`),
4758
+ readFile: async (sessionId, path32, options) => await writeOf(
4759
+ `${CONTROL_PATH_PREFIX}/read-file`,
4760
+ JSON.stringify({ session: sessionId, path: path32, ...options?.maxBytes === void 0 ? {} : { maxBytes: options.maxBytes }, ...options?.encoding === void 0 ? {} : { encoding: options.encoding } }),
4761
+ (status, message) => `reading ${path32} from ${sessionId} failed (HTTP ${String(status)}): ${message}`
4762
+ ),
4763
+ fileSuggestions: async (sessionId, query) => await writeOf(`${CONTROL_PATH_PREFIX}/file-suggestions`, JSON.stringify({ session: sessionId, query }), (status, message) => `requesting file suggestions from ${sessionId} failed (HTTP ${String(status)}): ${message}`),
4764
+ keepAlive: async (sessionId) => await writeOf(`${CONTROL_PATH_PREFIX}/keep-alive`, JSON.stringify({ session: sessionId }), (status, message) => `keeping ${sessionId} alive failed (HTTP ${String(status)}): ${message}`),
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}`),
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}`),
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}`),
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}`)
4519
4770
  };
4520
4771
  }
4521
4772
 
@@ -4662,36 +4913,38 @@ function tlsFacts(options) {
4662
4913
  }
4663
4914
  function realRcEventDial(target = { host: CONNECT_INTERCEPT_HOST, port: HTTPS_PORT }) {
4664
4915
  const agent = new ExemptTlsAgent();
4665
- return {
4666
- writeEvents: async (sessionId, headers, body) => await new Promise((resolve, reject) => {
4667
- const options = {
4668
- host: target.host,
4669
- port: target.port,
4670
- method: "POST",
4671
- path: `/v1/code/sessions/${encodeURIComponent(sessionId)}/events`,
4672
- // 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.
4673
- headers: { ...headers, host: target.servername ?? target.host, "content-length": String(Buffer.byteLength(body, "utf8")) },
4674
- agent
4675
- };
4676
- options.servername = target.servername ?? target.host;
4677
- if (target.ca !== void 0) {
4678
- options.ca = [...target.ca];
4679
- }
4680
- if (target.rejectUnauthorized !== void 0) {
4681
- options.rejectUnauthorized = target.rejectUnauthorized;
4682
- }
4683
- const request4 = https2.request(options, (response) => {
4684
- const chunks = [];
4685
- response.on("data", (chunk) => {
4686
- chunks.push(chunk);
4687
- });
4688
- response.on("end", () => {
4689
- resolve({ status: response.statusCode ?? HTTP_BAD_GATEWAY, body: Buffer.concat(chunks).toString("utf8") });
4690
- });
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);
4691
4937
  });
4692
- request4.once("error", reject);
4693
- request4.end(body);
4694
- })
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)
4695
4948
  };
4696
4949
  }
4697
4950
  var NO_CHUNKS = {
@@ -4709,7 +4962,8 @@ function lateRcEventDial(resolve) {
4709
4962
  return built.dial;
4710
4963
  };
4711
4964
  return {
4712
- 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)
4713
4967
  };
4714
4968
  }
4715
4969
  function lateRcStreamDial(resolve) {
@@ -4722,7 +4976,8 @@ function lateRcStreamDial(resolve) {
4722
4976
  return built.dial;
4723
4977
  };
4724
4978
  return {
4725
- 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),
4726
4981
  openStream: async (sessionId, headers, resume, signal) => await dialFor().openStream(sessionId, headers, resume, signal)
4727
4982
  };
4728
4983
  }
@@ -4764,11 +5019,16 @@ function realRcStreamDial(target = { host: CONNECT_INTERCEPT_HOST, port: HTTPS_P
4764
5019
  return Buffer.concat(chunks).toString("utf8");
4765
5020
  };
4766
5021
  return {
4767
- announcePresence: async (sessionId, headers, clientId) => {
4768
- 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 });
4769
5024
  const { status, response } = await request4("POST", `/v1/code/sessions/${encodeURIComponent(sessionId)}/client/presence`, { ...headers, "content-type": "application/json" }, body, new AbortController().signal);
4770
5025
  return { status, body: await readAll(response) };
4771
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
+ },
4772
5032
  openStream: async (sessionId, headers, resume, signal) => {
4773
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)}`;
4774
5034
  const streamHeaders = resume === void 0 ? headers : { ...headers, "last-event-id": String(resume.fromSequenceNum) };
@@ -5137,6 +5397,52 @@ var RcSetPermissionModeInputSchema = import_zod4.z.strictObject({
5137
5397
  session: import_zod4.z.string().min(1),
5138
5398
  mode: RcPermissionModeSchema
5139
5399
  });
5400
+ var RcEndSessionInputSchema = import_zod4.z.strictObject({
5401
+ session: import_zod4.z.string().min(1),
5402
+ reason: import_zod4.z.string().min(1).optional()
5403
+ });
5404
+ var RcGetUsageInputSchema = import_zod4.z.strictObject({
5405
+ session: import_zod4.z.string().min(1),
5406
+ skipBehaviors: import_zod4.z.boolean().optional()
5407
+ });
5408
+ var RcGetContextUsageInputSchema = import_zod4.z.strictObject({
5409
+ session: import_zod4.z.string().min(1),
5410
+ detail: import_zod4.z.enum(RC_CONTEXT_USAGE_DETAILS).optional()
5411
+ });
5412
+ var RcReadFileInputSchema = import_zod4.z.strictObject({
5413
+ session: import_zod4.z.string().min(1),
5414
+ path: import_zod4.z.string().min(1),
5415
+ maxBytes: import_zod4.z.number().int().positive().optional(),
5416
+ encoding: import_zod4.z.enum(RC_READ_FILE_ENCODINGS).optional()
5417
+ });
5418
+ var RcFileSuggestionsInputSchema = import_zod4.z.strictObject({
5419
+ session: import_zod4.z.string().min(1),
5420
+ query: import_zod4.z.string()
5421
+ });
5422
+ var RcKeepAliveInputSchema = import_zod4.z.strictObject({
5423
+ session: import_zod4.z.string().min(1)
5424
+ });
5425
+ var RcMcpStatusInputSchema = import_zod4.z.strictObject({
5426
+ session: import_zod4.z.string().min(1)
5427
+ });
5428
+ var RcMcpReconnectInputSchema = import_zod4.z.strictObject({
5429
+ session: import_zod4.z.string().min(1),
5430
+ serverName: import_zod4.z.string().min(1)
5431
+ });
5432
+ var RcMcpAuthenticateInputSchema = import_zod4.z.strictObject({
5433
+ session: import_zod4.z.string().min(1),
5434
+ serverName: import_zod4.z.string().min(1),
5435
+ redirectUri: import_zod4.z.string().min(1)
5436
+ });
5437
+ var RcMcpOAuthCallbackUrlInputSchema = import_zod4.z.strictObject({
5438
+ session: import_zod4.z.string().min(1),
5439
+ serverName: import_zod4.z.string().min(1),
5440
+ callbackUrl: import_zod4.z.string().min(1)
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
+ });
5140
5446
  var RcListOutputSchema = import_zod4.z.strictObject({ sessions: import_zod4.z.readonly(import_zod4.z.array(RcSessionSummarySchema)) });
5141
5447
  var RcStatusOutputSchema = import_zod4.z.strictObject({ statuses: import_zod4.z.readonly(import_zod4.z.array(RcSessionStatusSchema)) });
5142
5448
  var RcPendingOutputSchema = import_zod4.z.strictObject({ pending: import_zod4.z.readonly(import_zod4.z.array(RcPendingRequestSummarySchema)) });
@@ -5144,6 +5450,11 @@ var RcWriteOutputSchema = import_zod4.z.strictObject({
5144
5450
  session: import_zod4.z.string(),
5145
5451
  sequenceNums: import_zod4.z.readonly(import_zod4.z.array(import_zod4.z.number()))
5146
5452
  });
5453
+ var RcControlWriteOutputSchema = import_zod4.z.strictObject({
5454
+ session: import_zod4.z.string(),
5455
+ request: import_zod4.z.string(),
5456
+ sequenceNums: import_zod4.z.readonly(import_zod4.z.array(import_zod4.z.number()))
5457
+ });
5147
5458
  var RcAnswerOutputSchema = import_zod4.z.strictObject({
5148
5459
  session: import_zod4.z.string(),
5149
5460
  request: import_zod4.z.string(),
@@ -5172,6 +5483,15 @@ function createRcApiRouter(deps) {
5172
5483
  }
5173
5484
  return result.sequenceNums;
5174
5485
  };
5486
+ const deliveredControl = (sessionId, result) => {
5487
+ if (!result.ok) {
5488
+ throw new import_client.ORPCError("BAD_GATEWAY", { message: result.message });
5489
+ }
5490
+ if (result.requestId === void 0) {
5491
+ throw new import_client.ORPCError("INTERNAL_SERVER_ERROR", { message: "the control request was delivered but the door cannot name the request id it minted, so the worker's answer cannot be matched" });
5492
+ }
5493
+ return { request: result.requestId, sequenceNums: result.sequenceNums };
5494
+ };
5175
5495
  return {
5176
5496
  rc: {
5177
5497
  list: authed.output(RcListOutputSchema).handler(() => ({ sessions: deps.list() })),
@@ -5194,9 +5514,20 @@ function createRcApiRouter(deps) {
5194
5514
  const decision = { approve: input.approve, message: input.approve || input.text === void 0 ? void 0 : input.text };
5195
5515
  return { session: input.session, request: input.request, sequenceNums: delivered(input.session, await deps.answer(input.session, input.request, decision)) };
5196
5516
  }),
5197
- interrupt: authed.input(RcInterruptInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.interrupt(input.session)) })),
5198
- setModel: authed.input(RcSetModelInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.setModel(input.session, input.model)) })),
5199
- setPermissionMode: authed.input(RcSetPermissionModeInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.setPermissionMode(input.session, input.mode)) })),
5517
+ interrupt: authed.input(RcInterruptInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.interrupt(input.session)) })),
5518
+ setModel: authed.input(RcSetModelInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.setModel(input.session, input.model)) })),
5519
+ setPermissionMode: authed.input(RcSetPermissionModeInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.setPermissionMode(input.session, input.mode)) })),
5520
+ endSession: authed.input(RcEndSessionInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.endSession(input.session, input.reason)) })),
5521
+ getUsage: authed.input(RcGetUsageInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.getUsage(input.session, input.skipBehaviors)) })),
5522
+ getContextUsage: authed.input(RcGetContextUsageInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.getContextUsage(input.session, input.detail)) })),
5523
+ readFile: authed.input(RcReadFileInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.readFile(input.session, input.path, { ...input.maxBytes === void 0 ? {} : { maxBytes: input.maxBytes }, ...input.encoding === void 0 ? {} : { encoding: input.encoding } })) })),
5524
+ fileSuggestions: authed.input(RcFileSuggestionsInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.fileSuggestions(input.session, input.query)) })),
5525
+ keepAlive: authed.input(RcKeepAliveInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.keepAlive(input.session)) })),
5526
+ mcpStatus: authed.input(RcMcpStatusInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpStatus(input.session)) })),
5527
+ mcpReconnect: authed.input(RcMcpReconnectInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpReconnect(input.session, input.serverName)) })),
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)) })),
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)) })),
5200
5531
  subscribe: authed.input(RcSessionQuerySchema).output((0, import_server2.eventIterator)(RcStreamEventSchema)).handler(async function* ({ input, signal }) {
5201
5532
  const queue = [];
5202
5533
  let wake;
@@ -10153,9 +10484,26 @@ function createRcStreamHub(deps) {
10153
10484
  }
10154
10485
  return headers;
10155
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
+ };
10156
10504
  const attempt = async (attachment, credential) => {
10157
10505
  const headers = streamHeaders(credential);
10158
- const presence = await deps.dial.announcePresence(attachment.sessionId, headers, attachment.clientId);
10506
+ const presence = await deps.dial.announcePresence(attachment.sessionId, headers, attachment.clientId, false);
10159
10507
  if (presence.status === HTTP_STATUS.unauthorized) {
10160
10508
  return "unauthorized";
10161
10509
  }
@@ -10175,6 +10523,7 @@ function createRcStreamHub(deps) {
10175
10523
  const parser = createSseParser();
10176
10524
  for await (const chunk of answer.chunks) {
10177
10525
  attachment.controller.signal.throwIfAborted();
10526
+ let lastEventId;
10178
10527
  for (const parsed of parser.write(Buffer.from(chunk).toString("utf8"))) {
10179
10528
  const envelope = parseRcStreamEnvelope(parsed);
10180
10529
  if (envelope === void 0) {
@@ -10182,10 +10531,21 @@ function createRcStreamHub(deps) {
10182
10531
  }
10183
10532
  deps.fileStreamEvent(attachment.sessionId, envelope);
10184
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
+ }
10185
10537
  }
10538
+ sendReceipt(attachment, headers, lastEventId);
10186
10539
  }
10187
10540
  return "dropped";
10188
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
+ };
10189
10549
  const runLoop = async (attachment) => {
10190
10550
  let retriedUnauthorized = false;
10191
10551
  for (; ; ) {
@@ -10197,6 +10557,7 @@ function createRcStreamHub(deps) {
10197
10557
  if (!live || credential?.authorization === void 0) {
10198
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"})`);
10199
10559
  persistCursor(attachment.sessionId);
10560
+ clearPresence(attachment, credential === void 0 ? void 0 : streamHeaders(credential));
10200
10561
  if (attachments.get(attachment.sessionId) === attachment) {
10201
10562
  attachments.delete(attachment.sessionId);
10202
10563
  }
@@ -10259,6 +10620,8 @@ function createRcStreamHub(deps) {
10259
10620
  close: () => {
10260
10621
  for (const attachment of attachments.values()) {
10261
10622
  persistCursor(attachment.sessionId);
10623
+ const credential = deps.credentialOf(attachment.sessionId);
10624
+ clearPresence(attachment, credential?.authorization === void 0 ? void 0 : streamHeaders(credential));
10262
10625
  attachment.stop();
10263
10626
  }
10264
10627
  attachments.clear();
@@ -10266,6 +10629,193 @@ function createRcStreamHub(deps) {
10266
10629
  };
10267
10630
  }
10268
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
+
10269
10819
  // src/frontdoor/rcWebFetch.ts
10270
10820
  var dns2 = __toESM(require("node:dns"), 1);
10271
10821
  var http2 = __toESM(require("node:http"), 1);
@@ -10534,21 +11084,19 @@ var RC_PRESENCE_REFRESH_SECONDS = 60;
10534
11084
  var RC_SELF_HOST_MAX_BATCH_EVENTS = 100;
10535
11085
  var RC_SELF_HOST_WORKER_EPOCH = 1;
10536
11086
  var RC_STREAM_EVENT_NAME2 = "client_event";
10537
- var RC_SOURCE_WORKER = "worker";
10538
- var RC_SOURCE_CLIENT = "client";
10539
11087
  var CONFLICT_REASON_HEADER2 = "x-ccr-conflict-reason";
10540
11088
  var CONFLICT_REASON_EPOCH_STALE = "epoch_stale";
10541
- function isRecord6(value) {
11089
+ function isRecord7(value) {
10542
11090
  return typeof value === "object" && value !== null && !Array.isArray(value);
10543
11091
  }
10544
11092
  function isWebSearchRefusal(value) {
10545
11093
  return !Array.isArray(value);
10546
11094
  }
10547
- function nonEmptyString2(record, field) {
11095
+ function nonEmptyString3(record, field) {
10548
11096
  const value = record === void 0 ? void 0 : record[field];
10549
11097
  return typeof value === "string" && value !== "" ? value : void 0;
10550
11098
  }
10551
- function numberOf(record, field) {
11099
+ function numberOf2(record, field) {
10552
11100
  const value = record === void 0 ? void 0 : record[field];
10553
11101
  if (typeof value === "number" && Number.isFinite(value)) {
10554
11102
  return value;
@@ -10615,14 +11163,14 @@ async function readBody2(request4, capBytes) {
10615
11163
  });
10616
11164
  });
10617
11165
  }
10618
- function parseJsonObject2(body) {
11166
+ function parseJsonObject3(body) {
10619
11167
  let parsed;
10620
11168
  try {
10621
11169
  parsed = JSON.parse(body);
10622
11170
  } catch {
10623
11171
  return void 0;
10624
11172
  }
10625
- return isRecord6(parsed) ? parsed : void 0;
11173
+ return isRecord7(parsed) ? parsed : void 0;
10626
11174
  }
10627
11175
  function mintWorkerJwt(randomToken, issuedAt) {
10628
11176
  const encode = (value) => Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
@@ -10645,11 +11193,16 @@ function createRcSelfHostSurface(deps) {
10645
11193
  for (const session of sessions.values()) {
10646
11194
  while (session.events[0] !== void 0 && now - session.events[0].storedAt > RC_SELF_HOST_RETENTION_MS) {
10647
11195
  const dropped = session.events.shift();
10648
- const uuid = dropped === void 0 ? void 0 : nonEmptyString2(dropped.payload, "uuid");
11196
+ const uuid = dropped === void 0 ? void 0 : nonEmptyString3(dropped.payload, "uuid");
10649
11197
  if (uuid !== void 0) {
10650
11198
  session.seenPayloadUuids.delete(uuid);
10651
11199
  }
10652
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
+ }
10653
11206
  if (session.workerStreams.size === 0 && session.clientStreams.size === 0 && now - session.lastTrafficAt > RC_SELF_HOST_RETENTION_MS) {
10654
11207
  sessions.delete(session.id);
10655
11208
  }
@@ -10688,7 +11241,7 @@ function createRcSelfHostSurface(deps) {
10688
11241
  const frameOf = (event) => {
10689
11242
  const envelope = {
10690
11243
  event_id: event.eventId,
10691
- event_type: nonEmptyString2(event.payload, "type") ?? "event",
11244
+ event_type: nonEmptyString3(event.payload, "type") ?? "event",
10692
11245
  sequence_num: event.sequenceNum,
10693
11246
  source: event.source,
10694
11247
  payload: event.payload,
@@ -10702,7 +11255,7 @@ data: ${JSON.stringify(envelope)}
10702
11255
  };
10703
11256
  const holdStream = async (session, which, wantsReplay, request4, response) => {
10704
11257
  const url = new URL(request4.url, "http://127.0.0.1");
10705
- 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");
10706
11259
  const replay = cursor === void 0 ? [] : session.events.filter((event) => wantsReplay(event) && event.sequenceNum > cursor);
10707
11260
  response.start(HTTP_STATUS.ok, { "content-type": "text/event-stream", "cache-control": "no-cache" });
10708
11261
  response.flush();
@@ -10731,8 +11284,8 @@ data: ${JSON.stringify(envelope)}
10731
11284
  sink.retire();
10732
11285
  response.end();
10733
11286
  };
10734
- const publish = (session, source, payload) => {
10735
- const uuid = nonEmptyString2(payload, "uuid");
11287
+ const publish = (session, source, payload, toWorker = source === RC_SOURCE_CLIENT) => {
11288
+ const uuid = nonEmptyString3(payload, "uuid");
10736
11289
  const duplicate = uuid !== void 0 && session.seenPayloadUuids.has(uuid);
10737
11290
  session.sequenceNum += 1;
10738
11291
  const event = {
@@ -10740,9 +11293,9 @@ data: ${JSON.stringify(envelope)}
10740
11293
  eventId: uuid ?? deps.newUuid(),
10741
11294
  source,
10742
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.
10743
- 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() },
10744
11297
  createdAt: new Date(deps.now()).toISOString(),
10745
- toWorker: source === RC_SOURCE_CLIENT,
11298
+ toWorker,
10746
11299
  storedAt: deps.now()
10747
11300
  };
10748
11301
  session.events.push(event);
@@ -10790,8 +11343,26 @@ data: ${JSON.stringify(envelope)}
10790
11343
  }
10791
11344
  session.lastTrafficAt = deps.now();
10792
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
+ }
10793
11364
  if (method !== "GET") {
10794
- 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" } });
10795
11366
  return;
10796
11367
  }
10797
11368
  await answerJson2(response, HTTP_STATUS.ok, { session: { id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), status: "active" } });
@@ -10836,17 +11407,17 @@ data: ${JSON.stringify(envelope)}
10836
11407
  return;
10837
11408
  }
10838
11409
  if (workerTail === "" && method === "PUT") {
10839
- const body = parseJsonObject2(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
10840
- 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");
10841
11412
  if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
10842
11413
  await epochStale(response);
10843
11414
  return;
10844
11415
  }
10845
- const status = nonEmptyString2(body, "worker_status");
11416
+ const status = nonEmptyString3(body, "worker_status");
10846
11417
  if (status !== void 0) {
10847
11418
  session.workerStatus = status;
10848
11419
  }
10849
- if (isRecord6(body?.external_metadata)) {
11420
+ if (isRecord7(body?.external_metadata)) {
10850
11421
  session.externalMetadata = body.external_metadata;
10851
11422
  }
10852
11423
  await answerJson2(response, HTTP_STATUS.ok, {});
@@ -10863,8 +11434,8 @@ data: ${JSON.stringify(envelope)}
10863
11434
  return;
10864
11435
  }
10865
11436
  if (workerTail === "events" && method === "POST") {
10866
- const body = parseJsonObject2(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
10867
- 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");
10868
11439
  if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
10869
11440
  await epochStale(response);
10870
11441
  return;
@@ -10876,7 +11447,7 @@ data: ${JSON.stringify(envelope)}
10876
11447
  }
10877
11448
  const results = [];
10878
11449
  for (const event of events) {
10879
- if (!isRecord6(event) || !isRecord6(event.payload)) {
11450
+ if (!isRecord7(event) || !isRecord7(event.payload)) {
10880
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" } });
10881
11452
  return;
10882
11453
  }
@@ -10897,8 +11468,8 @@ data: ${JSON.stringify(envelope)}
10897
11468
  return;
10898
11469
  }
10899
11470
  if (workerTail === "internal-events" && method === "POST") {
10900
- const body = parseJsonObject2(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
10901
- 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");
10902
11473
  if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
10903
11474
  await epochStale(response);
10904
11475
  return;
@@ -10909,19 +11480,26 @@ data: ${JSON.stringify(envelope)}
10909
11480
  return;
10910
11481
  }
10911
11482
  for (const event of events) {
10912
- if (isRecord6(event)) {
10913
- session.internalEvents.push({ cursor: deps.newUuid(), body: event });
11483
+ if (isRecord7(event)) {
11484
+ session.internalEvents.push({ eventId: deps.newUuid(), body: event });
10914
11485
  }
10915
11486
  }
10916
11487
  await answerJson2(response, HTTP_STATUS.ok, {});
10917
11488
  return;
10918
11489
  }
10919
11490
  if (workerTail === "internal-events" && method === "GET") {
10920
- const url = new URL(request4.url, "http://127.0.0.1");
10921
- const cursor = url.searchParams.get("cursor");
10922
- const from = cursor === null ? 0 : session.internalEvents.findIndex((event) => event.cursor === cursor) + 1;
10923
- const page = session.internalEvents.slice(from);
10924
- 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);
10925
11503
  return;
10926
11504
  }
10927
11505
  if (workerTail === "diagnostics" && method === "POST") {
@@ -10930,8 +11508,8 @@ data: ${JSON.stringify(envelope)}
10930
11508
  return;
10931
11509
  }
10932
11510
  if (workerTail === "web-fetch" && method === "POST") {
10933
- const body = parseJsonObject2(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
10934
- const target = nonEmptyString2(body, "url");
11511
+ const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11512
+ const target = nonEmptyString3(body, "url");
10935
11513
  if (target === void 0) {
10936
11514
  await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-fetch carries one url" } });
10937
11515
  return;
@@ -10953,8 +11531,8 @@ data: ${JSON.stringify(envelope)}
10953
11531
  return;
10954
11532
  }
10955
11533
  if (workerTail === "web-search" && method === "POST") {
10956
- const body = parseJsonObject2(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
10957
- const query = nonEmptyString2(body, "query");
11534
+ const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11535
+ const query = nonEmptyString3(body, "query");
10958
11536
  if (query === void 0) {
10959
11537
  await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-search carries one query" } });
10960
11538
  return;
@@ -10965,7 +11543,7 @@ data: ${JSON.stringify(envelope)}
10965
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" } });
10966
11544
  return;
10967
11545
  }
10968
- const searchProfile = nonEmptyString2(body, "search_profile");
11546
+ const searchProfile = nonEmptyString3(body, "search_profile");
10969
11547
  const backend = deps.webSearch;
10970
11548
  if (backend === void 0) {
10971
11549
  deps.log?.(`rc selfhost ${session.id}: worker web-search of "${query}" refused: no search backend serves this door`);
@@ -10990,7 +11568,7 @@ data: ${JSON.stringify(envelope)}
10990
11568
  return;
10991
11569
  }
10992
11570
  if (tail === "events" && method === "POST") {
10993
- const body = parseJsonObject2(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11571
+ const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
10994
11572
  const events = body?.events;
10995
11573
  if (!Array.isArray(events) || events.length === 0 || events.length > RC_SELF_HOST_MAX_BATCH_EVENTS) {
10996
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` } });
@@ -10998,7 +11576,7 @@ data: ${JSON.stringify(envelope)}
10998
11576
  }
10999
11577
  const results = [];
11000
11578
  for (const event of events) {
11001
- if (!isRecord6(event) || !isRecord6(event.payload)) {
11579
+ if (!isRecord7(event) || !isRecord7(event.payload)) {
11002
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" } });
11003
11581
  return;
11004
11582
  }
@@ -11010,26 +11588,14 @@ data: ${JSON.stringify(envelope)}
11010
11588
  return;
11011
11589
  }
11012
11590
  if (tail === "events" && method === "GET") {
11013
- 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 })) });
11014
11592
  return;
11015
11593
  }
11016
11594
  if (tail === "events/stream" && method === "GET") {
11017
11595
  await holdStream(session, "clientStreams", () => true, request4, response);
11018
11596
  return;
11019
11597
  }
11020
- if (tail === "client/presence" && method === "POST") {
11021
- await readBody2(request4.body, CONTROL_BODY_CAP_BYTES);
11022
- await answerJson2(response, HTTP_STATUS.ok, { refresh_after_seconds: RC_PRESENCE_REFRESH_SECONDS });
11023
- return;
11024
- }
11025
- if (tail === "mark_read" && method === "POST") {
11026
- await readBody2(request4.body, CONTROL_BODY_CAP_BYTES);
11027
- await answerJson2(response, HTTP_STATUS.ok, {});
11028
- return;
11029
- }
11030
- if (tail === "teleport-events" && method === "POST") {
11031
- await readBody2(request4.body, CONTROL_BODY_CAP_BYTES);
11032
- 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)) {
11033
11599
  return;
11034
11600
  }
11035
11601
  await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the self-hosted Remote Control surface serves no path "${tail}"` } });
@@ -11046,17 +11612,8 @@ data: ${JSON.stringify(envelope)}
11046
11612
  await noCredential(response);
11047
11613
  return;
11048
11614
  }
11049
- if (pathname === RC_COMPAT_SESSIONS_PATH) {
11050
- if (method !== "GET") {
11051
- await answerJson2(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the compatibility session list is a GET" } });
11052
- return;
11053
- }
11054
- const listRefusal = credentialRefusal(request4.headers);
11055
- if (listRefusal !== void 0) {
11056
- await unauthorized(response, listRefusal);
11057
- return;
11058
- }
11059
- 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);
11060
11617
  return;
11061
11618
  }
11062
11619
  if (pathname !== RC_SESSIONS_PATH_PREFIX2 && !pathname.startsWith(`${RC_SESSIONS_PATH_PREFIX2}/`)) {
@@ -11070,11 +11627,11 @@ data: ${JSON.stringify(envelope)}
11070
11627
  await unauthorized(response, createRefusal);
11071
11628
  return;
11072
11629
  }
11073
- const body = parseJsonObject2(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11630
+ const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
11074
11631
  const session = {
11075
11632
  id: `${RC_SESSION_ID_PREFIX2}${deps.newUuid()}`,
11076
11633
  createdAt: deps.now(),
11077
- title: nonEmptyString2(body, "title"),
11634
+ title: nonEmptyString3(body, "title"),
11078
11635
  workerJwts: /* @__PURE__ */ new Set(),
11079
11636
  sequenceNum: 0,
11080
11637
  events: [],
@@ -11082,6 +11639,7 @@ data: ${JSON.stringify(envelope)}
11082
11639
  workerStreams: /* @__PURE__ */ new Set(),
11083
11640
  clientStreams: /* @__PURE__ */ new Set(),
11084
11641
  internalEvents: [],
11642
+ clients: /* @__PURE__ */ new Map(),
11085
11643
  workerStatus: void 0,
11086
11644
  externalMetadata: void 0,
11087
11645
  lastTrafficAt: deps.now()
@@ -11126,7 +11684,7 @@ data: ${JSON.stringify(envelope)}
11126
11684
  const pathname = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
11127
11685
  if (host === RC_SELF_HOST_OAUTH_HOST) {
11128
11686
  if (method === "POST" && pathname === "/v1/oauth/token") {
11129
- const body = parseJsonObject2(await readBody2(request4, CONTROL_BODY_CAP_BYTES));
11687
+ const body = parseJsonObject3(await readBody2(request4, CONTROL_BODY_CAP_BYTES));
11130
11688
  const record = deps.credentialRecord();
11131
11689
  if (record === void 0) {
11132
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" });
@@ -11212,7 +11770,7 @@ var RC_SELF_HOST_RECORD_DIR = "rc-selfhost";
11212
11770
  var RC_SELF_HOST_RECORD_FILE = "credential.json";
11213
11771
  var SELF_HOST_EMAIL = "selfhost@agent-shim.invalid";
11214
11772
  var SELF_HOST_SUBSCRIPTION_TYPE = "max";
11215
- function isRecord7(value) {
11773
+ function isRecord8(value) {
11216
11774
  return typeof value === "object" && value !== null && !Array.isArray(value);
11217
11775
  }
11218
11776
  function readJsonObject(fs12, file) {
@@ -11226,7 +11784,7 @@ function readJsonObject(fs12, file) {
11226
11784
  } catch {
11227
11785
  return void 0;
11228
11786
  }
11229
- return isRecord7(parsed) ? parsed : void 0;
11787
+ return isRecord8(parsed) ? parsed : void 0;
11230
11788
  }
11231
11789
  function readRcSelfHostRecord(fs12, frontdoorDir) {
11232
11790
  const parsed = readJsonObject(fs12, import_node_path28.default.join(frontdoorDir, RC_SELF_HOST_RECORD_DIR, RC_SELF_HOST_RECORD_FILE));
@@ -11250,7 +11808,7 @@ function mintRcSelfHostCredential(deps) {
11250
11808
  const recordFile = import_node_path28.default.join(recordDir, RC_SELF_HOST_RECORD_FILE);
11251
11809
  const previousRecord = readRcSelfHostRecord(deps.fs, deps.frontdoorDir);
11252
11810
  const existingCredentials = readJsonObject(deps.fs, credentialsFile);
11253
- const existingOauth = isRecord7(existingCredentials?.claudeAiOauth) ? existingCredentials.claudeAiOauth : void 0;
11811
+ const existingOauth = isRecord8(existingCredentials?.claudeAiOauth) ? existingCredentials.claudeAiOauth : void 0;
11254
11812
  const existingToken = typeof existingOauth?.accessToken === "string" && existingOauth.accessToken !== "" ? existingOauth.accessToken : void 0;
11255
11813
  const existingIsOwn = existingToken !== void 0 && previousRecord?.accessToken === existingToken;
11256
11814
  if (existingToken !== void 0 && !existingIsOwn && !deps.force) {
@@ -11267,7 +11825,7 @@ function mintRcSelfHostCredential(deps) {
11267
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)}
11268
11826
  `);
11269
11827
  const claudeJson = readJsonObject(deps.fs, claudeJsonFile) ?? {};
11270
- const existingBlock = (value) => isRecord7(value) ? value : {};
11828
+ const existingBlock = (value) => isRecord8(value) ? value : {};
11271
11829
  deps.fs.writeFilePrivate(claudeJsonFile, `${JSON.stringify({
11272
11830
  ...claudeJson,
11273
11831
  oauthAccount: {
@@ -11466,7 +12024,7 @@ var import_node_path30 = __toESM(require("node:path"), 1);
11466
12024
  // package.json
11467
12025
  var package_default = {
11468
12026
  name: "agent-shim",
11469
- version: "8.23.0",
12027
+ version: "8.25.0",
11470
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.",
11471
12029
  license: "MIT",
11472
12030
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -12709,10 +13267,12 @@ function prepareClaudeLaunch(options) {
12709
13267
  ProviderKindMismatchError,
12710
13268
  ProviderNotFoundError,
12711
13269
  ProviderSchema,
13270
+ RC_CONTEXT_USAGE_DETAILS,
12712
13271
  RC_IDLE_EXPIRY_MS,
12713
13272
  RC_ORPC_PATH_PREFIX,
12714
13273
  RC_PENDING_DEADLINE_MS,
12715
13274
  RC_PERMISSION_MODES,
13275
+ RC_READ_FILE_ENCODINGS,
12716
13276
  RC_REQUEST_PARSE_CAP_BYTES,
12717
13277
  RC_SELF_HOST_ENV,
12718
13278
  RC_SELF_HOST_KEEPALIVE_MS,
@@ -12743,11 +13303,24 @@ function prepareClaudeLaunch(options) {
12743
13303
  addPool,
12744
13304
  addProvider,
12745
13305
  answerRcControlRequest,
13306
+ authenticateRcSessionMcpServer,
12746
13307
  buildEntryFacts,
12747
13308
  buildLayoutPaths,
13309
+ buildRcControlRequestPayload,
12748
13310
  buildRcControlResponsePayload,
13311
+ buildRcEndSessionPayload,
12749
13312
  buildRcEventWriteBody,
13313
+ buildRcFileSuggestionsPayload,
13314
+ buildRcGetContextUsagePayload,
13315
+ buildRcGetUsagePayload,
12750
13316
  buildRcInterruptPayload,
13317
+ buildRcKeepAlivePayload,
13318
+ buildRcMarkReadBody,
13319
+ buildRcMcpAuthenticatePayload,
13320
+ buildRcMcpOAuthCallbackUrlPayload,
13321
+ buildRcMcpReconnectPayload,
13322
+ buildRcMcpStatusPayload,
13323
+ buildRcReadFilePayload,
12751
13324
  buildRcSetModelPayload,
12752
13325
  buildRcSetPermissionModePayload,
12753
13326
  buildRcUserMessagePayload,
@@ -12780,6 +13353,7 @@ function prepareClaudeLaunch(options) {
12780
13353
  doorApiAuth,
12781
13354
  doorApiNodeHandlerOf,
12782
13355
  effectiveWindow,
13356
+ endRcSession,
12783
13357
  ensureCa,
12784
13358
  ensureFrontDoor,
12785
13359
  ensureHeadroom,
@@ -12796,6 +13370,9 @@ function prepareClaudeLaunch(options) {
12796
13370
  frontDoorRcApiClient,
12797
13371
  frontDoorRcControl,
12798
13372
  generateCa,
13373
+ getRcSessionContextUsage,
13374
+ getRcSessionMcpStatus,
13375
+ getRcSessionUsage,
12799
13376
  headroomSocketTarget,
12800
13377
  identityExists,
12801
13378
  injectRcUserMessage,
@@ -12803,7 +13380,9 @@ function prepareClaudeLaunch(options) {
12803
13380
  isIdentityDirectoryName,
12804
13381
  isInterceptedHost,
12805
13382
  isPrivateAddress,
13383
+ isRcContextUsageDetail,
12806
13384
  isRcPermissionMode,
13385
+ isRcReadFileEncoding,
12807
13386
  lateRcEventDial,
12808
13387
  lateRcStreamDial,
12809
13388
  listDirectoryRules,
@@ -12837,6 +13416,7 @@ function prepareClaudeLaunch(options) {
12837
13416
  readProfile,
12838
13417
  readProvider,
12839
13418
  readRcSelfHostRecord,
13419
+ readRcSessionFile,
12840
13420
  readUsageSnapshot,
12841
13421
  realConnectCertStore,
12842
13422
  realConnectEffects,
@@ -12844,6 +13424,7 @@ function prepareClaudeLaunch(options) {
12844
13424
  realRcEventDial,
12845
13425
  realRcStreamDial,
12846
13426
  realRcWebFetch,
13427
+ reconnectRcSessionMcpServer,
12847
13428
  recoverFarm,
12848
13429
  recoveryDiagnostics,
12849
13430
  removeDirectoryRule,
@@ -12863,6 +13444,7 @@ function prepareClaudeLaunch(options) {
12863
13444
  runFrontDoorSupervisor,
12864
13445
  runLauncher,
12865
13446
  runSupervisor,
13447
+ sendRcKeepAlive,
12866
13448
  servedByPipeline,
12867
13449
  setAllowAmbientCredential,
12868
13450
  setDefaultConfigProfile,
@@ -12878,6 +13460,9 @@ function prepareClaudeLaunch(options) {
12878
13460
  snapshotPath,
12879
13461
  spawnClaude,
12880
13462
  startConnectServer,
13463
+ submitRcSessionMcpOAuthCallbackUrl,
13464
+ suggestRcSessionFiles,
13465
+ teleportRcSession,
12881
13466
  topLevelNames,
12882
13467
  updateDirectoryRule,
12883
13468
  updateProvider,