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