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