agent-shim 8.26.0 → 8.27.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -61796,7 +61796,7 @@ function registerIdentityCommand(program2, deps) {
61796
61796
  // package.json
61797
61797
  var package_default = {
61798
61798
  name: "agent-shim",
61799
- version: "8.26.0",
61799
+ version: "8.27.1",
61800
61800
  description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
61801
61801
  license: "MIT",
61802
61802
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -73850,9 +73850,12 @@ function createRcLiveUsage(deps) {
73850
73850
  // src/frontdoor/rcSelfHostMint.ts
73851
73851
  var import_node_path34 = __toESM(require("node:path"), 1);
73852
73852
 
73853
- // src/frontdoor/rcSelfHostEndpoints.ts
73854
- var RC_SOURCE_WORKER = "worker";
73855
- var RC_SOURCE_CLIENT = "client";
73853
+ // src/frontdoor/rcSelfHostLocal.ts
73854
+ var RC_SELF_HOST_OAUTH_HOST = "platform.claude.com";
73855
+ var RC_SELF_HOST_SCOPE_LIST = ["user:profile", "user:inference", "user:sessions:claude_code", "user:mcp_servers", "user:file_upload"];
73856
+ var RC_SELF_HOST_OAUTH_TOKEN_TTL_SECONDS = 3600;
73857
+ var MS_PER_SECOND7 = 1e3;
73858
+ var HTTP_SERVICE_UNAVAILABLE = 503;
73856
73859
  function isRecord5(value2) {
73857
73860
  return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
73858
73861
  }
@@ -73865,2186 +73868,2328 @@ function parseJsonObject2(body) {
73865
73868
  }
73866
73869
  return isRecord5(parsed) ? parsed : void 0;
73867
73870
  }
73868
- function nonEmptyString2(record2, field) {
73869
- const value2 = record2 === void 0 ? void 0 : record2[field];
73870
- return typeof value2 === "string" && value2 !== "" ? value2 : void 0;
73871
+ function growthBookFeatures() {
73872
+ return {
73873
+ tengu_ccr_bridge: { defaultValue: true },
73874
+ tengu_bridge_repl_v2: { defaultValue: true },
73875
+ tengu_bridge_repl_v2_cse_shim_enabled: { defaultValue: true },
73876
+ tengu_bridge_min_version: { defaultValue: { minVersion: "0.0.0" } }
73877
+ };
73871
73878
  }
73872
- function numberOf(record2, field) {
73873
- const value2 = record2 === void 0 ? void 0 : record2[field];
73874
- if (typeof value2 === "number" && Number.isFinite(value2)) {
73875
- return value2;
73879
+ var answerServerJson = (response, status, body) => {
73880
+ const text2 = JSON.stringify(body);
73881
+ response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text2, "utf8")) });
73882
+ response.end(text2);
73883
+ };
73884
+ function createRcSelfHostLocal(deps) {
73885
+ return {
73886
+ parsesHost: (host) => host === RC_SELF_HOST_OAUTH_HOST,
73887
+ serve: async (host, request7, response) => {
73888
+ const method = (request7.method ?? "").toUpperCase();
73889
+ const pathname = new URL(request7.url ?? "/", "http://127.0.0.1").pathname;
73890
+ if (host === RC_SELF_HOST_OAUTH_HOST) {
73891
+ if (method === "POST" && pathname === "/v1/oauth/token") {
73892
+ const body = parseJsonObject2(await deps.readBody(request7, CONTROL_BODY_CAP_BYTES));
73893
+ const record2 = deps.credentialRecord();
73894
+ if (record2 === void 0) {
73895
+ 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" });
73896
+ return true;
73897
+ }
73898
+ if (body?.grant_type !== void 0 && body.grant_type !== "refresh_token") {
73899
+ answerServerJson(response, HTTP_STATUS.badRequest, { error: "unsupported_grant_type" });
73900
+ return true;
73901
+ }
73902
+ answerServerJson(response, HTTP_STATUS.ok, { access_token: record2.accessToken, refresh_token: record2.refreshToken, expires_in: RC_SELF_HOST_OAUTH_TOKEN_TTL_SECONDS, scope: RC_SELF_HOST_SCOPE_LIST.join(" ") });
73903
+ return true;
73904
+ }
73905
+ if (method === "GET" && pathname === "/v1/oauth/hello") {
73906
+ answerServerJson(response, HTTP_STATUS.ok, {});
73907
+ return true;
73908
+ }
73909
+ return false;
73910
+ }
73911
+ if (method === "POST" && pathname.startsWith("/api/eval/")) {
73912
+ await deps.readBody(request7, CONTROL_BODY_CAP_BYTES);
73913
+ answerServerJson(response, HTTP_STATUS.ok, { features: growthBookFeatures(), dateUpdated: Math.floor(deps.now() / MS_PER_SECOND7) });
73914
+ return true;
73915
+ }
73916
+ if (method === "GET" && pathname === "/api/oauth/profile") {
73917
+ const record2 = deps.credentialRecord();
73918
+ if (record2 === void 0) {
73919
+ 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" });
73920
+ return true;
73921
+ }
73922
+ answerServerJson(response, HTTP_STATUS.ok, {
73923
+ account: { uuid: record2.accountUuid, display_name: "agent-shim self-hosted", created_at: (/* @__PURE__ */ new Date(0)).toISOString() },
73924
+ organization: { uuid: record2.organizationUuid, organization_type: "claude_max" }
73925
+ });
73926
+ return true;
73927
+ }
73928
+ if (method === "GET" && pathname === "/api/claude_code/policy_limits") {
73929
+ answerServerJson(response, HTTP_STATUS.ok, {});
73930
+ return true;
73931
+ }
73932
+ if (method === "POST" && pathname === "/api/oauth/validate") {
73933
+ await deps.readBody(request7, CONTROL_BODY_CAP_BYTES);
73934
+ const record2 = deps.credentialRecord();
73935
+ if (record2 === void 0) {
73936
+ 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" });
73937
+ return true;
73938
+ }
73939
+ answerServerJson(response, HTTP_STATUS.ok, { scopes: [...RC_SELF_HOST_SCOPE_LIST], expiresAt: null, subscriptionType: "max", account_uuid: record2.accountUuid, organization_uuid: record2.organizationUuid });
73940
+ return true;
73941
+ }
73942
+ if (method === "POST" && (pathname === "/api/event_logging/v2/batch" || pathname === "/api/claude_code/metrics" || pathname === "/api/claude_cli_feedback")) {
73943
+ await deps.readBody(request7, CONTROL_BODY_CAP_BYTES);
73944
+ answerServerJson(response, HTTP_STATUS.ok, {});
73945
+ return true;
73946
+ }
73947
+ if (method === "GET" && pathname === "/api/hello") {
73948
+ answerServerJson(response, HTTP_STATUS.ok, {});
73949
+ return true;
73950
+ }
73951
+ return false;
73952
+ }
73953
+ };
73954
+ }
73955
+
73956
+ // src/frontdoor/rcSelfHostMint.ts
73957
+ var CREDENTIALS_FILE = ".credentials.json";
73958
+ var CLAUDE_JSON2 = ".claude.json";
73959
+ var RC_SELF_HOST_RECORD_DIR = "rc-selfhost";
73960
+ var RC_SELF_HOST_RECORD_FILE = "credential.json";
73961
+ var SELF_HOST_EMAIL = "selfhost@agent-shim.invalid";
73962
+ var SELF_HOST_SUBSCRIPTION_TYPE = "max";
73963
+ function isRecord6(value2) {
73964
+ return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
73965
+ }
73966
+ function readJsonObject(fs20, file2) {
73967
+ const raw = fs20.readFileUtf8(file2);
73968
+ if (raw === void 0) {
73969
+ return void 0;
73876
73970
  }
73877
- if (typeof value2 === "string" && /^[0-9]+$/.test(value2)) {
73878
- return Number(value2);
73971
+ let parsed;
73972
+ try {
73973
+ parsed = JSON.parse(raw);
73974
+ } catch {
73975
+ return void 0;
73879
73976
  }
73880
- return void 0;
73977
+ return isRecord6(parsed) ? parsed : void 0;
73881
73978
  }
73882
- function pageOfRcChannel(url2, rows, idOf, rowOf) {
73883
- const query = new URL(url2, "http://127.0.0.1").searchParams;
73884
- const anchor2 = query.get("after_event_id") ?? query.get("cursor");
73885
- const rawLimit = query.get("limit");
73886
- const limit = rawLimit === null ? void 0 : numberOf({ value: rawLimit }, "value");
73887
- if (limit !== void 0 && limit < 1) {
73888
- return { error: "the limit names a whole number of rows of at least one" };
73979
+ function readRcSelfHostRecord(fs20, frontdoorDir) {
73980
+ const parsed = readJsonObject(fs20, import_node_path34.default.join(frontdoorDir, RC_SELF_HOST_RECORD_DIR, RC_SELF_HOST_RECORD_FILE));
73981
+ if (parsed === void 0) {
73982
+ return void 0;
73889
73983
  }
73890
- const from = anchor2 === null ? 0 : rows.findIndex((row) => idOf(row) === anchor2) + 1;
73891
- if (anchor2 !== null && from === 0) {
73892
- return { error: `the anchor names an event id this surface holds no row for: ${anchor2}` };
73984
+ const { accessToken, refreshToken, organizationUuid, accountUuid } = parsed;
73985
+ if (typeof accessToken !== "string" || accessToken === "" || typeof refreshToken !== "string" || refreshToken === "" || typeof organizationUuid !== "string" || organizationUuid === "" || typeof accountUuid !== "string" || accountUuid === "") {
73986
+ return void 0;
73893
73987
  }
73894
- const page = limit === void 0 ? rows.slice(from) : rows.slice(from, from + limit);
73895
- const last = page[page.length - 1];
73896
- return { data: page.map(rowOf), ...last === void 0 ? {} : { next_cursor: idOf(last) } };
73988
+ return { accessToken, refreshToken, organizationUuid, accountUuid };
73897
73989
  }
73898
- async function serveRcClientChannels(ctx, tail, method, maxBatchEvents) {
73899
- const { request: request7, response, session } = ctx;
73900
- if (tail === "client/presence" && method === "POST") {
73901
- const body = parseJsonObject2(await ctx.readBody(request7.body, CONTROL_BODY_CAP_BYTES));
73902
- const clientId = nonEmptyString2(body, "client_id");
73903
- const clear = body?.clear;
73904
- if (clientId === void 0 || clear !== void 0 && typeof clear !== "boolean") {
73905
- 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" } });
73906
- return true;
73907
- }
73908
- if (clear === true) {
73909
- session.clients.delete(clientId);
73910
- ctx.log?.(`rc selfhost ${session.id}: client ${clientId} cleared its presence`);
73911
- } else {
73912
- session.clients.set(clientId, ctx.now());
73913
- ctx.log?.(`rc selfhost ${session.id}: client ${clientId} announced its presence${session.clients.size === 1 ? "" : ` (${String(session.clients.size)} clients now announced)`}`);
73914
- }
73915
- await ctx.answerJson(response, HTTP_STATUS.ok, { refresh_after_seconds: ctx.presenceRefreshSeconds });
73916
- return true;
73990
+ function mintRcSelfHostCredential(deps) {
73991
+ if (!isIdentityName(deps.identity)) {
73992
+ throw new Error(`"${deps.identity}" is not a valid identity name.`);
73917
73993
  }
73918
- if (tail === "mark_read" && method === "POST") {
73919
- const body = parseJsonObject2(await ctx.readBody(request7.body, CONTROL_BODY_CAP_BYTES));
73920
- const eventId = nonEmptyString2(body, "event_id");
73921
- const clientId = nonEmptyString2(body, "client_id");
73922
- if (body !== void 0 && "event_id" in body && eventId === void 0 || "client_id" in (body ?? {}) && clientId === void 0) {
73923
- 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" } });
73924
- return true;
73925
- }
73926
- 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);
73927
- 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)}`);
73928
- await ctx.answerJson(response, HTTP_STATUS.ok, {});
73929
- return true;
73994
+ const identityDir = import_node_path34.default.join(deps.identitiesDir, deps.identity);
73995
+ const credentialsFile = import_node_path34.default.join(identityDir, CREDENTIALS_FILE);
73996
+ const claudeJsonFile = import_node_path34.default.join(identityDir, CLAUDE_JSON2);
73997
+ const recordDir = import_node_path34.default.join(deps.frontdoorDir, RC_SELF_HOST_RECORD_DIR);
73998
+ const recordFile = import_node_path34.default.join(recordDir, RC_SELF_HOST_RECORD_FILE);
73999
+ const previousRecord = readRcSelfHostRecord(deps.fs, deps.frontdoorDir);
74000
+ const existingCredentials = readJsonObject(deps.fs, credentialsFile);
74001
+ const existingOauth = isRecord6(existingCredentials?.claudeAiOauth) ? existingCredentials.claudeAiOauth : void 0;
74002
+ const existingToken = typeof existingOauth?.accessToken === "string" && existingOauth.accessToken !== "" ? existingOauth.accessToken : void 0;
74003
+ const existingIsOwn = existingToken !== void 0 && previousRecord?.accessToken === existingToken;
74004
+ if (existingToken !== void 0 && !existingIsOwn && !deps.force) {
74005
+ throw new Error(
74006
+ `identity ${deps.identity} already holds an OAuth credential this door did not mint (a real claude.ai login): pass --force to replace it, or mint into an identity with no login. The self-hosted mode expects no Anthropic credential to be in play.`
74007
+ );
73930
74008
  }
73931
- if (tail === "teleport-events" && method === "POST") {
73932
- const body = parseJsonObject2(await ctx.readBody(request7.body, CONTROL_BODY_CAP_BYTES));
73933
- const events = body?.events;
73934
- if (!Array.isArray(events) || events.length === 0 || events.length > maxBatchEvents) {
73935
- 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` } });
73936
- return true;
73937
- }
73938
- const results = [];
73939
- for (const event of events) {
73940
- if (!isRecord5(event) || !isRecord5(event.payload)) {
73941
- 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" } });
73942
- return true;
73943
- }
73944
- const published = ctx.publish(session, RC_SOURCE_CLIENT, event.payload);
73945
- results.push({ sequence_num: String(published.sequenceNum), duplicate: published.duplicate });
74009
+ const accessToken = `sk-ant-oat${deps.randomToken()}`;
74010
+ const refreshToken = deps.randomToken();
74011
+ const organizationUuid = deps.newUuid();
74012
+ const accountUuid = deps.newUuid();
74013
+ const mintedAt = new Date(deps.now()).toISOString();
74014
+ deps.fs.mkdirPrivate(import_node_path34.default.dirname(credentialsFile));
74015
+ 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)}
74016
+ `);
74017
+ const claudeJson = readJsonObject(deps.fs, claudeJsonFile) ?? {};
74018
+ const existingBlock = (value2) => isRecord6(value2) ? value2 : {};
74019
+ deps.fs.writeFilePrivate(claudeJsonFile, `${JSON.stringify({
74020
+ ...claudeJson,
74021
+ oauthAccount: {
74022
+ ...existingBlock(claudeJson.oauthAccount),
74023
+ accountUuid,
74024
+ emailAddress: SELF_HOST_EMAIL,
74025
+ organizationUuid,
74026
+ organizationName: "agent-shim self-hosted",
74027
+ // The billing and creation fields exist so the CLI's "populate the account block if it is missing" step finds a complete block and skips its profile fetch; the door answers that fetch locally anyway, so these are the quiet path, not a load-bearing claim.
74028
+ billingType: "stripe_subscription",
74029
+ accountCreatedAt: mintedAt,
74030
+ subscriptionCreatedAt: mintedAt
74031
+ },
74032
+ cachedGrowthBookFeatures: {
74033
+ ...existingBlock(claudeJson.cachedGrowthBookFeatures),
74034
+ tengu_ccr_bridge: true,
74035
+ tengu_bridge_repl_v2: true,
74036
+ tengu_bridge_repl_v2_cse_shim_enabled: true
73946
74037
  }
73947
- ctx.sweep();
73948
- ctx.log?.(`rc selfhost ${session.id}: teleport write published ${String(results.length)} marker event(s)`);
73949
- await ctx.answerJson(response, HTTP_STATUS.ok, { results });
73950
- return true;
74038
+ }, null, 2)}
74039
+ `);
74040
+ deps.fs.mkdirPrivate(recordDir);
74041
+ deps.fs.writeFilePrivate(recordFile, `${JSON.stringify({ accessToken, refreshToken, organizationUuid, accountUuid }, null, 2)}
74042
+ `);
74043
+ return { identity: deps.identity, organizationUuid, credentialsFile, claudeJsonFile, recordFile, replaced: existingIsOwn };
74044
+ }
74045
+
74046
+ // src/frontdoor/rcCommands.ts
74047
+ var MINTED_TOKEN_RANDOM_BYTES = 32;
74048
+ function reportControlWrite(what, session, result, json2, extra = {}) {
74049
+ if (!result.ok) {
74050
+ throw new Error(result.message);
73951
74051
  }
73952
- if (tail === "teleport-events" && method === "GET") {
73953
- const page = pageOfRcChannel(request7.url, session.events, (event) => event.eventId, (event) => ({ event_id: event.eventId, payload: event.payload }));
73954
- if ("error" in page) {
73955
- await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: page.error } });
73956
- return true;
73957
- }
73958
- await ctx.answerJson(response, HTTP_STATUS.ok, page);
73959
- return true;
74052
+ if (json2 === true) {
74053
+ printJson({ session, ...result.requestId === void 0 ? {} : { request: result.requestId }, sequenceNums: result.sequenceNums, ...extra });
74054
+ return;
73960
74055
  }
73961
- return false;
74056
+ console.log(`${what} on ${session}${result.requestId === void 0 ? "" : `: request ${result.requestId}`} sequence_num ${result.sequenceNums.join(", ")}`);
73962
74057
  }
73963
- 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" });
73964
- async function serveRcCompatSessions(ctx, pathname, compatPath) {
73965
- const { request: request7, response } = ctx;
73966
- const method = request7.method.toUpperCase();
73967
- if (pathname === compatPath) {
73968
- if (method !== "GET") {
73969
- await ctx.answerJson(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the compatibility session list is a GET" } });
73970
- return;
73971
- }
73972
- const listRefusal = ctx.credentialRefusal(request7.headers);
73973
- if (listRefusal !== void 0) {
73974
- await ctx.unauthorized(response, listRefusal);
73975
- return;
73976
- }
73977
- await ctx.answerJson(response, HTTP_STATUS.ok, { data: [...ctx.sessions.values()].map((session2) => compatSessionRow(session2, ctx.now)) });
73978
- return;
74058
+ function formatRcSessionList(sessions) {
74059
+ if (sessions.length === 0) {
74060
+ return ["no Remote Control sessions observed"];
73979
74061
  }
73980
- const rest = pathname.slice(`${compatPath}/`.length);
73981
- const slash = rest.indexOf("/");
73982
- const address = slash === -1 ? rest : rest.slice(0, slash);
73983
- const compatTail = slash === -1 ? "" : rest.slice(slash + 1);
73984
- const refusal = ctx.credentialRefusal(request7.headers);
73985
- if (refusal !== void 0) {
73986
- await ctx.unauthorized(response, refusal);
73987
- return;
74062
+ return sessions.map((session) => `${session.id} created ${new Date(session.createdAt).toISOString()} last seen ${new Date(session.lastSeenAt).toISOString()}`);
74063
+ }
74064
+ function formatRcSessionStatus(statuses) {
74065
+ if (statuses.length === 0) {
74066
+ return ["no Remote Control sessions observed"];
73988
74067
  }
73989
- const session = ctx.sessions.get(address) ?? ctx.sessions.get(`cse_${address}`);
73990
- if (compatTail === "") {
73991
- if (method === "GET") {
73992
- if (session === void 0) {
73993
- await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `Session not found: ${address}` } });
73994
- return;
73995
- }
73996
- await ctx.answerJson(response, HTTP_STATUS.ok, compatSessionRow(session, ctx.now));
73997
- return;
73998
- }
73999
- if (method === "PATCH" || method === "PUT") {
74000
- if (session === void 0) {
74001
- await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `Session not found: ${address}` } });
74002
- return;
74003
- }
74004
- const body = parseJsonObject2(await ctx.readBody(request7.body, CONTROL_BODY_CAP_BYTES));
74005
- const title = nonEmptyString2(body, "title");
74006
- if (title === void 0) {
74007
- 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" } });
74008
- return;
74068
+ const lines = [];
74069
+ for (const status of statuses) {
74070
+ lines.push(`${status.id} created ${new Date(status.createdAt).toISOString()} last seen ${new Date(status.lastSeenAt).toISOString()}`);
74071
+ const worker = status.workerState === void 0 && status.workerIdleSeconds === void 0 ? "not observed" : [
74072
+ status.workerState === void 0 ? "state unknown" : `state ${status.workerState.value} since ${new Date(status.workerState.observedAt).toISOString()}`,
74073
+ status.workerIdleSeconds === void 0 ? "idle unknown" : `idle ${String(status.workerIdleSeconds.value)} s as of ${new Date(status.workerIdleSeconds.observedAt).toISOString()}`
74074
+ ].join(", ");
74075
+ lines.push(` worker: ${worker}`);
74076
+ if (status.pending.length === 0) {
74077
+ lines.push(" pending: none");
74078
+ } else {
74079
+ lines.push(" pending:");
74080
+ for (const pending of status.pending) {
74081
+ lines.push(...formatRcPendingList([pending]).map((line) => ` ${line}`));
74009
74082
  }
74010
- session.title = title;
74011
- session.lastTrafficAt = ctx.now();
74012
- ctx.log?.(`rc selfhost ${session.id}: retitled through the compatibility family`);
74013
- await ctx.answerJson(response, HTTP_STATUS.ok, compatSessionRow(session, ctx.now));
74014
- return;
74015
74083
  }
74016
- 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" } });
74017
- return;
74018
74084
  }
74019
- if ((compatTail === "archive" || compatTail === "unarchive") && method === "POST") {
74020
- await ctx.readBody(request7.body, CONTROL_BODY_CAP_BYTES);
74021
- if (session === void 0) {
74022
- await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `Session not found: ${address}` } });
74023
- return;
74024
- }
74025
- if (compatTail === "unarchive") {
74026
- await ctx.answerJson(response, HTTP_STATUS.ok, compatSessionRow(session, ctx.now));
74027
- return;
74028
- }
74029
- for (const sink of [...session.workerStreams, ...session.clientStreams]) {
74030
- sink.retire();
74031
- }
74032
- ctx.sessions.delete(session.id);
74033
- ctx.log?.(`rc selfhost ${session.id}: archived through the compatibility family`);
74034
- await ctx.answerJson(response, HTTP_STATUS.ok, {});
74035
- return;
74085
+ return lines;
74086
+ }
74087
+ function formatRcPendingList(pending) {
74088
+ if (pending.length === 0) {
74089
+ return ["no pending control requests observed"];
74036
74090
  }
74037
- await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the compatibility session family serves no path "${compatTail}"` } });
74091
+ return pending.map((request7) => `${request7.sessionId} ${request7.requestId} ${request7.type}${request7.summary === "" ? "" : ` ${request7.summary}`} observed ${new Date(request7.observedAt).toISOString()}`);
74038
74092
  }
74039
-
74040
- // src/frontdoor/rcWebFetch.ts
74041
- var dns2 = __toESM(require("node:dns"), 1);
74042
- var http6 = __toESM(require("node:http"), 1);
74043
- var https5 = __toESM(require("node:https"), 1);
74044
- var net3 = __toESM(require("node:net"), 1);
74045
- var RC_WEB_FETCH_MAX_BYTES = 12582912;
74046
- var RC_WEB_FETCH_DEADLINE_MS = 4e4;
74047
- var RC_WEB_FETCH_MAX_REDIRECTS = 20;
74048
- var RC_WEB_FETCH_ALLOW_PRIVATE_ENV = "AGENT_SHIM_FRONTDOOR_RC_SELF_HOST_WEB_FETCH_ALLOW_PRIVATE";
74049
- function rcWebFetchAllowsPrivateFromEnv(env) {
74050
- return env[RC_WEB_FETCH_ALLOW_PRIVATE_ENV] === "1";
74093
+ function eventExcerpt(text2) {
74094
+ return text2.length <= RC_PENDING_SUMMARY_EXCERPT_CHARS ? text2 : `${text2.slice(0, RC_PENDING_SUMMARY_EXCERPT_CHARS)}...`;
74051
74095
  }
74052
- var WEB_FETCH_USER_AGENT = "agent-shim-frontdoor";
74053
- var ERROR_PREFIX = "web_fetch_";
74054
- var V4_THIS_NETWORK_OCTET = 0;
74055
- var V4_PRIVATE_A_OCTET = 10;
74056
- var V4_LOOPBACK_OCTET = 127;
74057
- var V4_CGNAT_OCTET = 100;
74058
- var V4_CGNAT_SECOND_FROM = 64;
74059
- var V4_CGNAT_SECOND_TO = 127;
74060
- var V4_LINK_LOCAL_OCTET = 169;
74061
- var V4_LINK_LOCAL_SECOND = 254;
74062
- var V4_PRIVATE_B_OCTET = 172;
74063
- var V4_PRIVATE_B_SECOND_FROM = 16;
74064
- var V4_PRIVATE_B_SECOND_TO = 31;
74065
- var V4_PRIVATE_C_OCTET = 192;
74066
- var V4_PRIVATE_C_SECOND = 168;
74067
- var V4_MULTICAST_FROM_OCTET = 224;
74068
- var V6_UNIQUE_LOCAL_FROM = 64512;
74069
- var V6_UNIQUE_LOCAL_TO = 65023;
74070
- var V6_LINK_LOCAL_FROM = 65152;
74071
- var V6_LINK_LOCAL_TO = 65215;
74072
- var V6_MULTICAST_FROM = 65280;
74073
- var IP_VERSION_4 = 4;
74074
- var IP_VERSION_6 = 6;
74075
- var HTTP_MOVED_PERMANENTLY = 301;
74076
- var HTTP_FOUND = 302;
74077
- var HTTP_SEE_OTHER = 303;
74078
- var HTTP_TEMPORARY_REDIRECT = 307;
74079
- var HTTP_PERMANENT_REDIRECT = 308;
74080
- var SUCCESS_STATUS_MAX_EXCLUSIVE3 = 300;
74081
- function refused(errorType, errorMessage2) {
74082
- return { kind: "refused", errorType: `${ERROR_PREFIX}${errorType}`, errorMessage: errorMessage2 };
74096
+ var PERCENT_SCALE = 100;
74097
+ var MS_PER_SECOND8 = 1e3;
74098
+ function percentOf(fraction) {
74099
+ return `${String(Math.round(fraction * PERCENT_SCALE))}%`;
74083
74100
  }
74084
- function isPrivateAddress(address) {
74085
- const version2 = net3.isIP(address);
74086
- if (version2 === IP_VERSION_4) {
74087
- const octets = address.split(".").map((part) => Number(part));
74088
- const first = octets[0] ?? -1;
74089
- const second = octets[1] ?? -1;
74090
- if (first === V4_THIS_NETWORK_OCTET || first === V4_PRIVATE_A_OCTET || first === V4_LOOPBACK_OCTET) {
74091
- return true;
74092
- }
74093
- if (first === V4_CGNAT_OCTET && second >= V4_CGNAT_SECOND_FROM && second <= V4_CGNAT_SECOND_TO) {
74094
- return true;
74095
- }
74096
- if (first === V4_LINK_LOCAL_OCTET && second === V4_LINK_LOCAL_SECOND) {
74097
- return true;
74098
- }
74099
- if (first === V4_PRIVATE_B_OCTET && second >= V4_PRIVATE_B_SECOND_FROM && second <= V4_PRIVATE_B_SECOND_TO) {
74100
- return true;
74101
- }
74102
- if (first === V4_PRIVATE_C_OCTET && second === V4_PRIVATE_C_SECOND) {
74103
- return true;
74104
- }
74105
- return first >= V4_MULTICAST_FROM_OCTET;
74101
+ function resetInstantOf(epochSeconds) {
74102
+ return new Date(epochSeconds * MS_PER_SECOND8).toISOString();
74103
+ }
74104
+ function formatRcRateLimitInfo(info) {
74105
+ const parts = [info.status];
74106
+ if (info.rateLimitType !== void 0) {
74107
+ parts.push(info.rateLimitType);
74106
74108
  }
74107
- if (version2 === IP_VERSION_6) {
74108
- const lower = address.toLowerCase();
74109
- const mapped = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/.exec(lower);
74110
- if (mapped !== null) {
74111
- return isPrivateAddress(mapped[1] ?? "");
74112
- }
74113
- if (lower === "::" || lower === "::1") {
74114
- return true;
74115
- }
74116
- const firstGroup = Number.parseInt(/^([0-9a-f]+):/.exec(lower)?.[1] ?? "0", 16);
74117
- if (Number.isNaN(firstGroup)) {
74118
- return true;
74109
+ if (info.utilization !== void 0) {
74110
+ parts.push(percentOf(info.utilization));
74111
+ }
74112
+ if (info.resetsAt !== void 0) {
74113
+ parts.push(`resets ${resetInstantOf(info.resetsAt)}`);
74114
+ }
74115
+ const windows = [];
74116
+ for (const name of ["five_hour", "seven_day", "seven_day_overage_included"]) {
74117
+ const window = info.unifiedWindows?.[name];
74118
+ if (window === void 0) {
74119
+ continue;
74119
74120
  }
74120
- if (firstGroup >= V6_UNIQUE_LOCAL_FROM && firstGroup <= V6_UNIQUE_LOCAL_TO) {
74121
- return true;
74121
+ const bits = [];
74122
+ if (window.utilization !== void 0) {
74123
+ bits.push(percentOf(window.utilization));
74122
74124
  }
74123
- if (firstGroup >= V6_LINK_LOCAL_FROM && firstGroup <= V6_LINK_LOCAL_TO) {
74124
- return true;
74125
+ if (window.resetsAt !== void 0) {
74126
+ bits.push(`resets ${resetInstantOf(window.resetsAt)}`);
74125
74127
  }
74126
- return firstGroup >= V6_MULTICAST_FROM;
74128
+ windows.push(bits.length === 0 ? name : `${name} ${bits.join(" ")}`);
74127
74129
  }
74128
- return true;
74130
+ if (windows.length > 0) {
74131
+ parts.push(`windows ${windows.join(", ")}`);
74132
+ }
74133
+ if (info.isUsingOverage === true) {
74134
+ parts.push("overage in use");
74135
+ } else if (info.overageStatus !== void 0) {
74136
+ parts.push(`overage ${info.overageStatus}`);
74137
+ }
74138
+ return parts.join(" ");
74129
74139
  }
74130
- function isSuccessful3(status) {
74131
- return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE3;
74140
+ function formatRcStreamEvent(event) {
74141
+ const { envelope } = event;
74142
+ const rateLimit = rcRateLimitInfoOf(event);
74143
+ const sketch = rateLimit !== void 0 ? ` ${formatRcRateLimitInfo(rateLimit)}` : envelope.payload === void 0 ? "" : ` ${eventExcerpt(JSON.stringify(envelope.payload))}`;
74144
+ return `${event.session} ${envelope.event_type} sequence_num ${String(envelope.sequence_num)} source ${envelope.source}${sketch}`;
74132
74145
  }
74133
- function isRedirect(status) {
74134
- return status === HTTP_MOVED_PERMANENTLY || status === HTTP_FOUND || status === HTTP_SEE_OTHER || status === HTTP_TEMPORARY_REDIRECT || status === HTTP_PERMANENT_REDIRECT;
74146
+ function formatRcSelfHostMint(result) {
74147
+ return [
74148
+ `identity: ${result.identity} (organisation ${result.organizationUuid})`,
74149
+ `credential: ${result.credentialsFile}${result.replaced ? " (replacing this door's previous mint)" : ""}`,
74150
+ `account block: ${result.claudeJsonFile} (oauthAccount and the feature-cache seed merged in)`,
74151
+ `door record: ${result.recordFile} (the copy the door's served surface authenticates against)`,
74152
+ "next: start the door with AGENT_SHIM_FRONTDOOR_RC_SELF_HOST=1 and its transparent surface pointed at the interception port, then launch this identity and switch Remote Control on; no claude.ai login is involved"
74153
+ ];
74135
74154
  }
74136
- function readableContentType(contentType) {
74137
- if (contentType === void 0 || contentType.trim() === "") {
74138
- return true;
74155
+ function frontDoorRcControlFromState(fsPort, paths) {
74156
+ const state = readFrontDoorState(fsPort, paths.frontdoorStateFile);
74157
+ if (state?.port === void 0) {
74158
+ throw new Error("the front door is not serving: Remote Control sessions are observed only while it runs, so start a session through the door first");
74139
74159
  }
74140
- const normalised = contentType.split(";")[0]?.trim().toLowerCase() ?? "";
74141
- const slash = normalised.indexOf("/");
74142
- const main2 = slash === -1 ? normalised : normalised.slice(0, slash);
74143
- const sub = slash === -1 ? "" : normalised.slice(slash + 1);
74144
- if (main2 === "text") {
74145
- return true;
74160
+ const ca = fsPort.readFileUtf8(paths.frontdoorCaCertFile);
74161
+ if (ca === void 0) {
74162
+ throw new Error(`the front door's CA certificate is missing at ${paths.frontdoorCaCertFile}, so its control listener cannot be authenticated`);
74146
74163
  }
74147
- return /json|xml|javascript|ecmascript|yaml|csv/.test(sub);
74148
- }
74149
- async function readCapped(chunks) {
74150
- const collected = [];
74151
- let total = 0;
74152
- for await (const chunk of chunks) {
74153
- total += chunk.byteLength;
74154
- if (total > RC_WEB_FETCH_MAX_BYTES) {
74155
- return { tooLarge: true, text: "" };
74156
- }
74157
- collected.push(Buffer.from(chunk));
74164
+ const token = fsPort.readFileUtf8(paths.frontdoorControlTokenFile)?.trim();
74165
+ if (token === void 0 || token === "") {
74166
+ throw new Error(`the serving front door's control token is missing at ${paths.frontdoorControlTokenFile}`);
74158
74167
  }
74159
- return { tooLarge: false, text: Buffer.concat(collected).toString("utf8") };
74168
+ return frontDoorRcControl(realRcControlTransport(state.port, ca), token);
74160
74169
  }
74161
- async function checkTarget(url2, deps) {
74162
- if (url2.protocol !== "http:" && url2.protocol !== "https:") {
74163
- return refused("scheme", `the web-fetch proxy dials http and https only, not ${url2.protocol}`);
74164
- }
74165
- if (deps.allowPrivate) {
74166
- return void 0;
74170
+ function frontDoorRcApiFromState(fsPort, paths) {
74171
+ const state = readFrontDoorState(fsPort, paths.frontdoorStateFile);
74172
+ if (state?.port === void 0) {
74173
+ throw new Error("the front door is not serving: Remote Control sessions are observed only while it runs, so start a session through the door first");
74167
74174
  }
74168
- const hostname3 = url2.hostname.replace(/^\[|\]$/g, "");
74169
- let addresses;
74170
- try {
74171
- addresses = net3.isIP(hostname3) === 0 ? await deps.lookup(hostname3) : [hostname3];
74172
- } catch {
74173
- return refused("resolve", `${hostname3} did not resolve`);
74175
+ const ca = fsPort.readFileUtf8(paths.frontdoorCaCertFile);
74176
+ if (ca === void 0) {
74177
+ throw new Error(`the front door's CA certificate is missing at ${paths.frontdoorCaCertFile}, so its typed API cannot be authenticated`);
74174
74178
  }
74175
- for (const address of addresses) {
74176
- if (isPrivateAddress(address)) {
74177
- return refused("private_address", `${hostname3} resolves to ${address}, a private address this web-fetch proxy refuses; set ${RC_WEB_FETCH_ALLOW_PRIVATE_ENV}=1 on the door if serving internal targets is intended`);
74178
- }
74179
+ const token = fsPort.readFileUtf8(paths.frontdoorControlTokenFile)?.trim();
74180
+ if (token === void 0 || token === "") {
74181
+ throw new Error(`the serving front door's control token is missing at ${paths.frontdoorControlTokenFile}`);
74179
74182
  }
74180
- return void 0;
74183
+ return frontDoorRcApiClient(state.port, ca, token);
74181
74184
  }
74182
- async function fetchThroughWebProxy(url2, deps) {
74183
- let current;
74184
- try {
74185
- current = new URL(url2);
74186
- } catch {
74187
- return refused("malformed_url", `${url2} is not a URL this proxy can dial`);
74188
- }
74189
- const controller = new AbortController();
74190
- const timer = setTimeout(() => {
74191
- controller.abort();
74192
- }, deps.deadlineMs);
74193
- timer.unref();
74194
- try {
74195
- let redirects = 0;
74196
- for (; ; ) {
74197
- const refusal = await checkTarget(current, deps);
74198
- if (refusal !== void 0) {
74199
- return refusal;
74185
+ function registerRcCommand(frontdoor, paths) {
74186
+ const rc = withExamples(
74187
+ frontdoor.command("rc").description("Observe the Remote Control sessions passing through the front door, watch the stream their attached client receives, send a prompt into one, answer its pending control requests, and steer one with the client half's own control requests (interrupt, model, permission mode, end-session, the usage and context queries, read-file, file-suggestions, keep-alive, and the mcp_* family)."),
74188
+ ["agent-shim frontdoor rc list", "agent-shim frontdoor rc pending"]
74189
+ );
74190
+ withExamples(
74191
+ rc.command("list").description("List the Remote Control sessions the front door has observed, with when each was created and last seen. Read-only.").option("--json", "Print the sessions as JSON.").action(async (options) => {
74192
+ const sessions = await frontDoorRcControlFromState(realFarmFs, paths).listSessions();
74193
+ if (options.json === true) {
74194
+ printJson({ sessions });
74195
+ return;
74200
74196
  }
74201
- let hop;
74202
- try {
74203
- hop = await deps.dial(current, controller.signal);
74204
- } catch (error62) {
74205
- if (controller.signal.aborted) {
74206
- return refused("deadline", `the fetch of ${url2} did not settle within its deadline`);
74207
- }
74208
- return refused("transport", `the dial of ${current.toString()} failed: ${error62 instanceof Error ? error62.message : "unknown error"}`);
74197
+ for (const line of formatRcSessionList(sessions)) {
74198
+ console.log(line);
74209
74199
  }
74210
- if (isRedirect(hop.status) && hop.location !== void 0) {
74211
- if (redirects >= RC_WEB_FETCH_MAX_REDIRECTS) {
74212
- controller.abort();
74213
- return refused("too_many_redirects", `${url2} followed more than ${String(RC_WEB_FETCH_MAX_REDIRECTS)} redirects without settling`);
74214
- }
74215
- let next;
74216
- try {
74217
- next = new URL(hop.location, current);
74218
- } catch {
74219
- controller.abort();
74220
- return refused("malformed_url", `a redirect of ${current.toString()} named ${hop.location}, which is not a URL this proxy can dial`);
74221
- }
74222
- const drained = await readCapped(hop.chunks);
74223
- if (drained.tooLarge) {
74224
- controller.abort();
74225
- return refused("too_large", `the body of a redirect from ${url2} exceeds the ${String(RC_WEB_FETCH_MAX_BYTES)} byte cap the CLI's proxy client reads`);
74226
- }
74227
- redirects += 1;
74228
- current = next;
74229
- continue;
74200
+ }),
74201
+ ["agent-shim frontdoor rc list", "agent-shim frontdoor rc list --json"]
74202
+ );
74203
+ withExamples(
74204
+ rc.command("status").description("Report each observed Remote Control session's worker state and idle from its latest observed heartbeat and registration, with its pending control requests. Read-only.").argument("[session]", "One cse_ session id, as `frontdoor rc list` shows it; every observed session when omitted.").option("--json", "Print the statuses as JSON.").action(async (session, options) => {
74205
+ const statuses = await frontDoorRcControlFromState(realFarmFs, paths).statusOf(session);
74206
+ if (options.json === true) {
74207
+ printJson({ statuses });
74208
+ return;
74230
74209
  }
74231
- if (!isSuccessful3(hop.status)) {
74232
- controller.abort();
74233
- return refused("status", `the target answered HTTP ${String(hop.status)} for ${current.toString()}`);
74210
+ for (const line of formatRcSessionStatus(statuses)) {
74211
+ console.log(line);
74234
74212
  }
74235
- const contentType = hop.contentType ?? "";
74236
- if (!readableContentType(contentType)) {
74237
- controller.abort();
74238
- return refused("content_type", `the target at ${current.toString()} served a ${contentType} answer, which the proxy's text field cannot carry`);
74213
+ }),
74214
+ ["agent-shim frontdoor rc status", "agent-shim frontdoor rc status cse_00000000-0000-4000-8000-000000000000 --json"]
74215
+ );
74216
+ withExamples(
74217
+ rc.command("pending").description("List the control requests (tool and plan approvals above all) the front door has observed and that are still awaiting an answer. Read-only.").argument("[session]", "One cse_ session id, as `frontdoor rc list` shows it; every observed session when omitted.").option("--json", "Print the pending requests as JSON.").action(async (session, options) => {
74218
+ const pending = await frontDoorRcControlFromState(realFarmFs, paths).pendingOf(session);
74219
+ if (options.json === true) {
74220
+ printJson({ pending });
74221
+ return;
74239
74222
  }
74240
- const read = await readCapped(hop.chunks);
74241
- if (read.tooLarge) {
74242
- controller.abort();
74243
- return refused("too_large", `the body of ${current.toString()} exceeds the ${String(RC_WEB_FETCH_MAX_BYTES)} byte cap the CLI's proxy client reads`);
74223
+ for (const line of formatRcPendingList(pending)) {
74224
+ console.log(line);
74244
74225
  }
74245
- return { kind: "fetched", url: url2, destinationUrl: current.toString(), contentType: hop.contentType, text: read.text };
74246
- }
74247
- } finally {
74248
- clearTimeout(timer);
74249
- }
74250
- }
74251
- function realRcWebFetch(options) {
74252
- const plainAgent = new ExemptHttpAgent();
74253
- const tlsAgent = new ExemptTlsAgent();
74254
- const deps = {
74255
- dial: async (url2, signal) => await new Promise((resolve, reject) => {
74256
- const request7 = (url2.protocol === "https:" ? https5 : http6).request(
74257
- {
74258
- hostname: url2.hostname,
74259
- port: url2.port === "" ? void 0 : Number(url2.port),
74260
- method: "GET",
74261
- path: `${url2.pathname}${url2.search}`,
74262
- headers: { accept: "*/*", "accept-encoding": "identity", "user-agent": WEB_FETCH_USER_AGENT },
74263
- agent: url2.protocol === "https:" ? tlsAgent : plainAgent,
74264
- signal
74265
- },
74266
- (response) => {
74267
- const location = response.headers.location;
74268
- resolve({
74269
- status: response.statusCode ?? 0,
74270
- location: typeof location === "string" ? location : void 0,
74271
- contentType: typeof response.headers["content-type"] === "string" ? response.headers["content-type"] : void 0,
74272
- chunks: response
74273
- });
74274
- }
74275
- );
74276
- request7.once("error", reject);
74277
- request7.end();
74278
74226
  }),
74279
- lookup: async (hostname3) => (await dns2.promises.lookup(hostname3, { all: true })).map((entry) => entry.address),
74280
- allowPrivate: options.allowPrivate,
74281
- deadlineMs: RC_WEB_FETCH_DEADLINE_MS
74282
- };
74283
- return async (url2) => await fetchThroughWebProxy(url2, deps);
74284
- }
74285
-
74286
- // src/frontdoor/rcSelfHost.ts
74287
- var RC_SELF_HOST_ENV = "AGENT_SHIM_FRONTDOOR_RC_SELF_HOST";
74288
- function rcSelfHostFromEnv(env) {
74289
- return env[RC_SELF_HOST_ENV] === "1";
74290
- }
74291
- var RC_SELF_HOST_SCOPE_LIST = ["user:profile", "user:inference", "user:sessions:claude_code", "user:mcp_servers", "user:file_upload"];
74292
- var RC_SESSIONS_PATH_PREFIX2 = "/v1/code/sessions";
74293
- var RC_COMPAT_SESSIONS_PATH = "/v1/sessions";
74294
- var RC_SESSION_ID_PREFIX2 = "cse_";
74295
- var RC_SELF_HOST_API_BASE_URL = "https://api.anthropic.com";
74296
- var RC_SELF_HOST_OAUTH_HOST = "platform.claude.com";
74297
- var HTTP_CONFLICT = 409;
74298
- var MS_PER_SECOND7 = 1e3;
74299
- var HTTP_SERVICE_UNAVAILABLE = 503;
74300
- var RC_SELF_HOST_WORKER_JWT_TTL_SECONDS = 46800;
74301
- var RC_SELF_HOST_OAUTH_TOKEN_TTL_SECONDS = 3600;
74302
- var RC_SELF_HOST_KEEPALIVE_MS = 15e3;
74303
- var RC_SELF_HOST_RETENTION_MS = 6e5;
74304
- var RC_PRESENCE_REFRESH_SECONDS = 60;
74305
- var RC_SELF_HOST_MAX_BATCH_EVENTS = 100;
74306
- var RC_SELF_HOST_WORKER_EPOCH = 1;
74307
- var RC_STREAM_EVENT_NAME = "client_event";
74308
- var CONFLICT_REASON_HEADER2 = "x-ccr-conflict-reason";
74309
- var CONFLICT_REASON_EPOCH_STALE = "epoch_stale";
74310
- function isRecord6(value2) {
74311
- return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
74312
- }
74313
- function isWebSearchRefusal(value2) {
74314
- return !Array.isArray(value2);
74315
- }
74316
- function nonEmptyString3(record2, field) {
74317
- const value2 = record2 === void 0 ? void 0 : record2[field];
74318
- return typeof value2 === "string" && value2 !== "" ? value2 : void 0;
74319
- }
74320
- function numberOf2(record2, field) {
74321
- const value2 = record2 === void 0 ? void 0 : record2[field];
74322
- if (typeof value2 === "number" && Number.isFinite(value2)) {
74323
- return value2;
74324
- }
74325
- if (typeof value2 === "string" && /^[0-9]+$/.test(value2)) {
74326
- return Number(value2);
74327
- }
74328
- return void 0;
74329
- }
74330
- function stringArray(record2, field) {
74331
- const value2 = record2 === void 0 ? void 0 : record2[field];
74332
- if (!Array.isArray(value2)) {
74333
- return void 0;
74334
- }
74335
- const members2 = [];
74336
- for (const member of value2) {
74337
- if (typeof member !== "string") {
74338
- return void 0;
74339
- }
74340
- members2.push(member);
74341
- }
74342
- return members2;
74343
- }
74344
- function singleHeader2(headers, name) {
74345
- const value2 = headers[name];
74346
- return typeof value2 === "string" ? value2 : void 0;
74347
- }
74348
- function bearerOf(headers) {
74349
- const presented = singleHeader2(headers, "authorization");
74350
- if (presented === void 0) {
74351
- return void 0;
74352
- }
74353
- return presented.slice(0, "bearer ".length).toLowerCase() === "bearer " ? presented.slice("bearer ".length) : void 0;
74354
- }
74355
- function presentationRefusal(headers) {
74356
- const presented = singleHeader2(headers, "authorization");
74357
- if (presented === void 0) {
74358
- return "absent";
74359
- }
74360
- if (presented.slice(0, "bearer ".length).toLowerCase() !== "bearer ") {
74361
- return "not-a-bearer";
74362
- }
74363
- const bearer = presented.slice("bearer ".length);
74364
- return bearer.slice(0, "bearer ".length).toLowerCase() === "bearer " ? "doubled-scheme-word" : "unrecognised";
74365
- }
74366
- async function readBody3(request7, capBytes) {
74367
- return await new Promise((resolve, reject) => {
74368
- const chunks = [];
74369
- let total = 0;
74370
- request7.on("data", (chunk) => {
74371
- total += chunk.length;
74372
- if (total > capBytes) {
74373
- request7.destroy();
74374
- reject(new Error("body too large"));
74227
+ ["agent-shim frontdoor rc pending", "agent-shim frontdoor rc pending --json"]
74228
+ );
74229
+ withExamples(
74230
+ rc.command("send").description("Send a text prompt into one observed Remote Control session, arriving as a message from an attached client would.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").requiredOption("--text <text>", "The prompt text to deliver.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
74231
+ const result = await frontDoorRcControlFromState(realFarmFs, paths).sendPrompt(options.session, options.text);
74232
+ if (!result.ok) {
74233
+ throw new Error(result.message);
74234
+ }
74235
+ if (options.json === true) {
74236
+ printJson({ session: options.session, sequenceNums: result.sequenceNums });
74375
74237
  return;
74376
74238
  }
74377
- chunks.push(chunk);
74378
- });
74379
- request7.on("end", () => {
74380
- resolve(Buffer.concat(chunks).toString("utf8"));
74381
- });
74382
- request7.on("error", () => {
74383
- reject(new Error("the request body ended early"));
74384
- });
74385
- });
74386
- }
74387
- function parseJsonObject3(body) {
74388
- let parsed;
74389
- try {
74390
- parsed = JSON.parse(body);
74391
- } catch {
74392
- return void 0;
74393
- }
74394
- return isRecord6(parsed) ? parsed : void 0;
74395
- }
74396
- function mintWorkerJwt(randomToken, issuedAt) {
74397
- const encode3 = (value2) => Buffer.from(JSON.stringify(value2), "utf8").toString("base64url");
74398
- const header = encode3({ alg: "HS256", typ: "JWT" });
74399
- const payload = encode3({ exp: Math.floor((issuedAt + RC_SELF_HOST_WORKER_JWT_TTL_SECONDS * MS_PER_SECOND7) / MS_PER_SECOND7), iat: Math.floor(issuedAt / MS_PER_SECOND7) });
74400
- return `${header}.${payload}.${Buffer.from(randomToken(), "utf8").toString("base64url")}`;
74401
- }
74402
- function growthBookFeatures() {
74403
- return {
74404
- tengu_ccr_bridge: { defaultValue: true },
74405
- tengu_bridge_repl_v2: { defaultValue: true },
74406
- tengu_bridge_repl_v2_cse_shim_enabled: { defaultValue: true },
74407
- tengu_bridge_min_version: { defaultValue: { minVersion: "0.0.0" } }
74408
- };
74409
- }
74410
- function createRcSelfHostSurface(deps) {
74411
- const sessions = /* @__PURE__ */ new Map();
74412
- const sweep = () => {
74413
- const now = deps.now();
74414
- for (const session of sessions.values()) {
74415
- while (session.events[0] !== void 0 && now - session.events[0].storedAt > RC_SELF_HOST_RETENTION_MS) {
74416
- const dropped = session.events.shift();
74417
- const uuid3 = dropped === void 0 ? void 0 : nonEmptyString3(dropped.payload, "uuid");
74418
- if (uuid3 !== void 0) {
74419
- session.seenPayloadUuids.delete(uuid3);
74420
- }
74421
- }
74422
- for (const [clientId, lastSeenAt] of session.clients) {
74423
- if (now - lastSeenAt > RC_SELF_HOST_RETENTION_MS) {
74424
- session.clients.delete(clientId);
74425
- }
74426
- }
74427
- if (session.workerStreams.size === 0 && session.clientStreams.size === 0 && now - session.lastTrafficAt > RC_SELF_HOST_RETENTION_MS) {
74428
- sessions.delete(session.id);
74239
+ console.log(`sent to ${options.session}: sequence_num ${result.sequenceNums.join(", ")}`);
74240
+ }),
74241
+ ['agent-shim frontdoor rc send --session cse_00000000-0000-4000-8000-000000000000 --text "run the tests"']
74242
+ );
74243
+ withExamples(
74244
+ rc.command("answer").description("Answer one pending control request on an observed session, approving or denying it as an attached client's approval would.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").requiredOption("--request <id>", "The control request's id, as `frontdoor rc pending` shows it.").option("--approve", "Approve the request.").option("--deny", "Deny the request.").option("--text <text>", "The denial message the worker sees; applies to --deny only.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
74245
+ if (options.approve === options.deny) {
74246
+ throw new UsageError("answer exactly one way: pass --approve or --deny, not both and not neither");
74429
74247
  }
74430
- }
74431
- };
74432
- const timer = setInterval(sweep, RC_SELF_HOST_RETENTION_MS);
74433
- timer.unref();
74434
- const keepalive = setInterval(() => {
74435
- for (const session of sessions.values()) {
74436
- for (const sink of [...session.workerStreams, ...session.clientStreams]) {
74437
- sink.write(": keep-alive\n\n");
74248
+ if (options.approve === true && options.text !== void 0) {
74249
+ throw new UsageError("--text is the denial message; an approval carries no text (the protocol's allow result has no message field)");
74438
74250
  }
74439
- }
74440
- }, RC_SELF_HOST_KEEPALIVE_MS);
74441
- keepalive.unref();
74442
- const presentsCredential = (headers) => {
74443
- const expected = deps.credentialRecord()?.accessToken;
74444
- const presented = bearerOf(headers);
74445
- return expected !== void 0 && presented !== void 0 && isLiveCapability(presented, [expected]);
74446
- };
74447
- const presentsWorkerJwt = (headers, session) => {
74448
- const presented = bearerOf(headers);
74449
- if (presented === void 0) {
74450
- return false;
74451
- }
74452
- for (const minted of session.workerJwts) {
74453
- if (isLiveCapability(presented, [minted])) {
74454
- return true;
74251
+ const result = await frontDoorRcControlFromState(realFarmFs, paths).answerRequest(options.session, options.request, { approve: options.approve === true, message: options.deny === true ? options.text : void 0 });
74252
+ if (!result.ok) {
74253
+ throw new Error(result.message);
74455
74254
  }
74456
- }
74457
- return false;
74458
- };
74459
- const credentialRefusal = (headers) => presentsCredential(headers) ? void 0 : presentationRefusal(headers);
74460
- const workerRefusal = (headers, session) => presentsWorkerJwt(headers, session) ? void 0 : presentationRefusal(headers);
74461
- const workerOrCredentialRefusal = (headers, session) => presentsWorkerJwt(headers, session) || presentsCredential(headers) ? void 0 : presentationRefusal(headers);
74462
- const frameOf = (event) => {
74463
- const envelope = {
74464
- event_id: event.eventId,
74465
- event_type: nonEmptyString3(event.payload, "type") ?? "event",
74466
- sequence_num: event.sequenceNum,
74467
- source: event.source,
74468
- payload: event.payload,
74469
- created_at: event.createdAt
74470
- };
74471
- return `event: ${RC_STREAM_EVENT_NAME}
74472
- id: ${String(event.sequenceNum)}
74473
- data: ${JSON.stringify(envelope)}
74474
-
74475
- `;
74476
- };
74477
- const holdStream = async (session, which, wantsReplay, request7, response) => {
74478
- const url2 = new URL(request7.url, "http://127.0.0.1");
74479
- const cursor3 = numberOf2({ value: url2.searchParams.get("from_sequence_num") }, "value") ?? numberOf2({ value: singleHeader2(request7.headers, "last-event-id") }, "value");
74480
- const replay = cursor3 === void 0 ? [] : session.events.filter((event) => wantsReplay(event) && event.sequenceNum > cursor3);
74481
- response.start(HTTP_STATUS.ok, { "content-type": "text/event-stream", "cache-control": "no-cache" });
74482
- response.flush();
74483
- let pending = Promise.resolve();
74484
- const sink = {
74485
- write: (frame) => {
74486
- pending = pending.then(async () => {
74487
- await response.write(frame);
74488
- }).catch(() => {
74489
- });
74490
- },
74491
- retire: () => {
74492
- session[which].delete(sink);
74255
+ if (options.json === true) {
74256
+ printJson({ session: options.session, request: options.request, sequenceNums: result.sequenceNums });
74257
+ return;
74493
74258
  }
74494
- };
74495
- session[which].add(sink);
74496
- session.lastTrafficAt = deps.now();
74497
- for (const event of replay) {
74498
- sink.write(frameOf(event));
74499
- }
74500
- await new Promise((resolve) => {
74501
- request7.signal.addEventListener("abort", () => {
74502
- resolve(void 0);
74503
- }, { once: true });
74504
- });
74505
- sink.retire();
74506
- response.end();
74507
- };
74508
- const publish = (session, source, payload, toWorker = source === RC_SOURCE_CLIENT) => {
74509
- const uuid3 = nonEmptyString3(payload, "uuid");
74510
- const duplicate = uuid3 !== void 0 && session.seenPayloadUuids.has(uuid3);
74511
- session.sequenceNum += 1;
74512
- const event = {
74513
- sequenceNum: session.sequenceNum,
74514
- eventId: uuid3 ?? deps.newUuid(),
74515
- source,
74516
- // 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.
74517
- payload: source === RC_SOURCE_WORKER ? payload : { ...payload, server_received_wall_ms: deps.now() },
74518
- createdAt: new Date(deps.now()).toISOString(),
74519
- toWorker,
74520
- storedAt: deps.now()
74521
- };
74522
- session.events.push(event);
74523
- if (uuid3 !== void 0) {
74524
- session.seenPayloadUuids.add(uuid3);
74525
- }
74526
- session.lastTrafficAt = event.storedAt;
74527
- const frame = frameOf(event);
74528
- if (event.toWorker) {
74529
- for (const sink of [...session.workerStreams]) {
74530
- sink.write(frame);
74259
+ console.log(`${options.approve === true ? "approved" : "denied"} ${options.request} on ${options.session}: sequence_num ${result.sequenceNums.join(", ")}`);
74260
+ }),
74261
+ [
74262
+ "agent-shim frontdoor rc answer --session cse_00000000-0000-4000-8000-000000000000 --request req_00000000-0000-4000-8000-000000000000 --approve",
74263
+ 'agent-shim frontdoor rc answer --session cse_00000000-0000-4000-8000-000000000000 --request req_00000000-0000-4000-8000-000000000000 --deny --text "not today"'
74264
+ ]
74265
+ );
74266
+ withExamples(
74267
+ rc.command("interrupt").description("Interrupt one observed Remote Control session's running turn, as an attached client's stop button would. Queued commands survive the interrupt.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
74268
+ reportControlWrite("interrupted", options.session, await frontDoorRcControlFromState(realFarmFs, paths).interruptSession(options.session), options.json);
74269
+ }),
74270
+ ["agent-shim frontdoor rc interrupt --session cse_00000000-0000-4000-8000-000000000000"]
74271
+ );
74272
+ withExamples(
74273
+ rc.command("set-model").description("Set the model one observed Remote Control session's subsequent turns use, as an attached client's model picker would.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").requiredOption("--model <model>", "The model id to switch the session to.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
74274
+ reportControlWrite(`set model ${options.model}`, options.session, await frontDoorRcControlFromState(realFarmFs, paths).setModel(options.session, options.model), options.json, { model: options.model });
74275
+ }),
74276
+ ["agent-shim frontdoor rc set-model --session cse_00000000-0000-4000-8000-000000000000 --model claude-opus-5-5"]
74277
+ );
74278
+ withExamples(
74279
+ rc.command("set-permission-mode").description("Set one observed Remote Control session's permission mode, as an attached client's mode switcher would.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").requiredOption("--mode <mode>", `The permission mode to set; one of the SDK's own modes (${RC_PERMISSION_MODES.join(", ")}).`).option("--json", "Print the delivery result as JSON.").action(async (options) => {
74280
+ if (!isRcPermissionMode(options.mode)) {
74281
+ throw new UsageError(`--mode must be one of the SDK's own permission modes (${RC_PERMISSION_MODES.join(", ")}), not "${options.mode}"`);
74531
74282
  }
74532
- }
74533
- for (const sink of [...session.clientStreams]) {
74534
- sink.write(frame);
74535
- }
74536
- return { sequenceNum: event.sequenceNum, eventId: event.eventId, duplicate };
74537
- };
74538
- const answerJson2 = async (response, status, body, extraHeaders = {}) => {
74539
- const text2 = JSON.stringify(body);
74540
- response.start(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text2, "utf8")), ...extraHeaders });
74541
- await response.write(text2);
74542
- response.end();
74543
- };
74544
- const noCredential = async (response) => {
74545
- await answerJson2(response, HTTP_SERVICE_UNAVAILABLE, { type: "error", error: { type: "api_error", message: "the front door's self-hosted Remote Control surface is on but no credential has been minted for it; run agent-shim frontdoor rc selfhost mint first" } });
74546
- };
74547
- const epochStale = async (response) => {
74548
- await answerJson2(response, HTTP_CONFLICT, { type: "error", error: { type: "api_error", message: "worker_epoch does not match the session's live epoch" } }, { [CONFLICT_REASON_HEADER2]: CONFLICT_REASON_EPOCH_STALE });
74549
- };
74550
- const refusalMessages = {
74551
- absent: "the self-hosted Remote Control surface authenticates by the Authorization header, and this call carried none",
74552
- "not-a-bearer": "the self-hosted Remote Control surface reads a Bearer authorization, and this call's Authorization header did not carry the Bearer scheme word",
74553
- "doubled-scheme-word": "the self-hosted Remote Control surface reads one Bearer scheme word, and this call's Authorization header carried it twice: a persisted session record holds the whole header value with the scheme word included, so prefixing Bearer onto it presents a different credential",
74554
- unrecognised: "the self-hosted Remote Control surface did not recognise the presented credential"
74555
- };
74556
- const unauthorized = async (response, refusal) => {
74557
- await answerJson2(response, HTTP_STATUS.unauthorized, { type: "error", error: { type: "authentication_error", message: refusalMessages[refusal] } });
74558
- };
74559
- const serveSessionPath = async (session, tail, request7, response) => {
74560
- const method = request7.method.toUpperCase();
74561
- if (session === void 0) {
74562
- await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: "no such Remote Control session on this door" } });
74563
- return;
74564
- }
74565
- session.lastTrafficAt = deps.now();
74566
- if (tail === "") {
74567
- if (method === "PATCH" || method === "PUT") {
74568
- const refusal = credentialRefusal(request7.headers);
74569
- if (refusal !== void 0) {
74570
- await unauthorized(response, refusal);
74571
- return;
74572
- }
74573
- const body = parseJsonObject3(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74574
- const title = nonEmptyString3(body, "title");
74575
- if (title === void 0) {
74576
- await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a session update is JSON naming a non-empty title" } });
74577
- return;
74578
- }
74579
- session.title = title;
74580
- session.lastTrafficAt = deps.now();
74581
- deps.log?.(`rc selfhost ${session.id}: retitled at its client's request`);
74582
- await answerJson2(response, HTTP_STATUS.ok, {});
74583
- return;
74283
+ reportControlWrite(`set permission mode ${options.mode}`, options.session, await frontDoorRcControlFromState(realFarmFs, paths).setPermissionMode(options.session, options.mode), options.json, { mode: options.mode });
74284
+ }),
74285
+ [
74286
+ "agent-shim frontdoor rc set-permission-mode --session cse_00000000-0000-4000-8000-000000000000 --mode plan",
74287
+ "agent-shim frontdoor rc set-permission-mode --session cse_00000000-0000-4000-8000-000000000000 --mode acceptEdits"
74288
+ ]
74289
+ );
74290
+ withExamples(
74291
+ rc.command("end-session").description("End one observed Remote Control session, as an attached client closing it would: the worker aborts its turn and shuts down on receipt of the request.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").option("--reason <text>", "The reason the worker's own log names; omitted means the protocol's unspecified form.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
74292
+ reportControlWrite("ended", options.session, await frontDoorRcControlFromState(realFarmFs, paths).endSession(options.session, options.reason), options.json);
74293
+ }),
74294
+ ["agent-shim frontdoor rc end-session --session cse_00000000-0000-4000-8000-000000000000 --reason done for today"]
74295
+ );
74296
+ withExamples(
74297
+ rc.command("get-usage").description("Ask one observed session's worker for its structured usage (session totals plus the plan's rate limits). The worker's answer arrives on `frontdoor rc watch` as the control_response echoing the printed request id.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").option("--skip-behaviors", "Skip the worker's local-transcript scan that fills the answer's behaviours section.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
74298
+ reportControlWrite("requested usage", options.session, await frontDoorRcControlFromState(realFarmFs, paths).getUsage(options.session, options.skipBehaviors === true ? true : void 0), options.json);
74299
+ }),
74300
+ ["agent-shim frontdoor rc get-usage --session cse_00000000-0000-4000-8000-000000000000 --skip-behaviors"]
74301
+ );
74302
+ withExamples(
74303
+ rc.command("get-context-usage").description("Ask one observed session's worker for its context-window breakdown by category. The worker's answer arrives on `frontdoor rc watch` as the control_response echoing the printed request id.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").option("--detail <level>", `The detail level; one of the SDK's own levels (${RC_CONTEXT_USAGE_DETAILS.join(", ")}); the worker defaults to full.`).option("--json", "Print the delivery result as JSON.").action(async (options) => {
74304
+ if (options.detail !== void 0 && !isRcContextUsageDetail(options.detail)) {
74305
+ throw new UsageError(`--detail must be one of the SDK's own context-usage levels (${RC_CONTEXT_USAGE_DETAILS.join(", ")}), not "${options.detail}"`);
74584
74306
  }
74585
- if (method !== "GET") {
74586
- 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" } });
74587
- return;
74307
+ reportControlWrite("requested context usage", options.session, await frontDoorRcControlFromState(realFarmFs, paths).getContextUsage(options.session, options.detail), options.json);
74308
+ }),
74309
+ ["agent-shim frontdoor rc get-context-usage --session cse_00000000-0000-4000-8000-000000000000 --detail summary"]
74310
+ );
74311
+ withExamples(
74312
+ rc.command("read-file").description("Ask one observed session's worker to read one file from the session filesystem, gated by its own read-permission rules. The worker's answer arrives on `frontdoor rc watch` as the control_response echoing the printed request id.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").requiredOption("--path <path>", "The file's path, as the worker resolves it against the session's cwd.").option("--max-bytes <n>", "The byte cap on the read, a positive whole number.").option("--encoding <encoding>", `How the answer encodes the contents; one of the SDK's own encodings (${RC_READ_FILE_ENCODINGS.join(", ")}).`).option("--json", "Print the delivery result as JSON.").action(async (options) => {
74313
+ if (options.maxBytes !== void 0 && !/^[1-9][0-9]*$/.test(options.maxBytes)) {
74314
+ throw new UsageError(`--max-bytes must be a positive whole number of bytes, not "${options.maxBytes}"`);
74588
74315
  }
74589
- await answerJson2(response, HTTP_STATUS.ok, { session: { id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), status: "active" } });
74590
- return;
74591
- }
74592
- if (tail === "bridge" && method === "POST") {
74593
- const refusal = credentialRefusal(request7.headers);
74594
- if (refusal !== void 0) {
74595
- await unauthorized(response, refusal);
74596
- return;
74316
+ if (options.encoding !== void 0 && !isRcReadFileEncoding(options.encoding)) {
74317
+ throw new UsageError(`--encoding must be one of the SDK's own read-file encodings (${RC_READ_FILE_ENCODINGS.join(", ")}), not "${options.encoding}"`);
74597
74318
  }
74598
- const workerJwt = mintWorkerJwt(deps.randomToken, deps.now());
74599
- session.workerJwts.add(workerJwt);
74600
- deps.log?.(`rc selfhost ${session.id}: bridge registration minted a worker credential`);
74601
- await answerJson2(response, HTTP_STATUS.ok, { worker_jwt: workerJwt, expires_in: RC_SELF_HOST_WORKER_JWT_TTL_SECONDS, api_base_url: RC_SELF_HOST_API_BASE_URL, worker_epoch: RC_SELF_HOST_WORKER_EPOCH });
74602
- return;
74603
- }
74604
- if (tail === "archive" && method === "POST") {
74605
- const refusal = credentialRefusal(request7.headers);
74606
- if (refusal !== void 0) {
74607
- await unauthorized(response, refusal);
74608
- return;
74609
- }
74610
- for (const sink of [...session.workerStreams, ...session.clientStreams]) {
74611
- sink.retire();
74612
- }
74613
- sessions.delete(session.id);
74614
- deps.log?.(`rc selfhost ${session.id}: archived at its client's request`);
74615
- await answerJson2(response, HTTP_STATUS.ok, {});
74616
- return;
74617
- }
74618
- if (tail === "worker" || tail.startsWith("worker/")) {
74619
- const workerTail = tail === "worker" ? "" : tail.slice("worker/".length);
74620
- const webProxy = workerTail === "web-fetch" || workerTail === "web-search";
74621
- const refusal = webProxy ? workerOrCredentialRefusal(request7.headers, session) : workerRefusal(request7.headers, session);
74622
- if (refusal !== void 0) {
74623
- await unauthorized(response, refusal);
74624
- return;
74625
- }
74626
- if (workerTail === "" && method === "GET") {
74627
- await answerJson2(response, HTTP_STATUS.ok, { worker: { external_metadata: session.externalMetadata ?? null } });
74628
- return;
74629
- }
74630
- if (workerTail === "" && method === "PUT") {
74631
- const body = parseJsonObject3(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74632
- const claimedEpoch = numberOf2(body, "worker_epoch");
74633
- if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
74634
- await epochStale(response);
74635
- return;
74636
- }
74637
- const status = nonEmptyString3(body, "worker_status");
74638
- if (status !== void 0) {
74639
- session.workerStatus = status;
74319
+ reportControlWrite(`requested a read of ${options.path}`, options.session, await frontDoorRcControlFromState(realFarmFs, paths).readFile(options.session, options.path, { ...options.maxBytes === void 0 ? {} : { maxBytes: Number(options.maxBytes) }, ...options.encoding === void 0 ? {} : { encoding: options.encoding } }), options.json, { path: options.path });
74320
+ }),
74321
+ [
74322
+ "agent-shim frontdoor rc read-file --session cse_00000000-0000-4000-8000-000000000000 --path src/index.ts",
74323
+ "agent-shim frontdoor rc read-file --session cse_00000000-0000-4000-8000-000000000000 --path screenshot.png --encoding base64 --max-bytes 65536"
74324
+ ]
74325
+ );
74326
+ withExamples(
74327
+ rc.command("file-suggestions").description("Ask one observed session's worker for its at-mention file suggestions for a partial path prefix, the same fuzzy-matched results the TUI shows. The worker's answer arrives on `frontdoor rc watch` as the control_response echoing the printed request id.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").requiredOption("--query <prefix>", "The partial path prefix to fuzzy-match; the empty string is the root listing.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
74328
+ reportControlWrite(`requested file suggestions for "${options.query}"`, options.session, await frontDoorRcControlFromState(realFarmFs, paths).fileSuggestions(options.session, options.query), options.json);
74329
+ }),
74330
+ ['agent-shim frontdoor rc file-suggestions --session cse_00000000-0000-4000-8000-000000000000 --query "src/frontdoor/rc"']
74331
+ );
74332
+ withExamples(
74333
+ rc.command("keep-alive").description("Send one liveness heartbeat into an observed session: the payload every receiver ignores by its own contract, useful to keep the client half's stream warm without saying anything.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
74334
+ reportControlWrite("sent a keep-alive", options.session, await frontDoorRcControlFromState(realFarmFs, paths).keepAlive(options.session), options.json);
74335
+ }),
74336
+ ["agent-shim frontdoor rc keep-alive --session cse_00000000-0000-4000-8000-000000000000"]
74337
+ );
74338
+ withExamples(
74339
+ rc.command("mcp-status").description("Ask one observed session's worker for the status of its MCP server connections. The worker's answer arrives on `frontdoor rc watch` as the control_response echoing the printed request id.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
74340
+ reportControlWrite("requested MCP status", options.session, await frontDoorRcControlFromState(realFarmFs, paths).mcpStatus(options.session), options.json);
74341
+ }),
74342
+ ["agent-shim frontdoor rc mcp-status --session cse_00000000-0000-4000-8000-000000000000"]
74343
+ );
74344
+ withExamples(
74345
+ rc.command("mcp-reconnect").description("Ask one observed session's worker to reconnect one MCP server, named exactly as `mcp-status` reports it.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").requiredOption("--server <name>", "The MCP server's name, as `mcp-status` reports it.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
74346
+ reportControlWrite(`requested a reconnect of MCP server ${options.server}`, options.session, await frontDoorRcControlFromState(realFarmFs, paths).mcpReconnect(options.session, options.server), options.json);
74347
+ }),
74348
+ ["agent-shim frontdoor rc mcp-reconnect --session cse_00000000-0000-4000-8000-000000000000 --server github"]
74349
+ );
74350
+ withExamples(
74351
+ rc.command("mcp-authenticate").description("Start one MCP server's OAuth handshake on an observed session, naming the redirect URI the flow redirects back to. The worker's answer arrives on `frontdoor rc watch` as the control_response echoing the printed request id.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").requiredOption("--server <name>", "The MCP server's name, as `mcp-status` reports it.").requiredOption("--redirect-uri <uri>", "The URI the server's OAuth flow redirects back to.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
74352
+ reportControlWrite(`requested authentication of MCP server ${options.server}`, options.session, await frontDoorRcControlFromState(realFarmFs, paths).mcpAuthenticate(options.session, options.server, options.redirectUri), options.json);
74353
+ }),
74354
+ ["agent-shim frontdoor rc mcp-authenticate --session cse_00000000-0000-4000-8000-000000000000 --server github --redirect-uri https://example.com/oauth/callback"]
74355
+ );
74356
+ withExamples(
74357
+ rc.command("mcp-oauth-callback-url").description("Hand one MCP server's OAuth callback URL to an observed session's worker, completing the handshake `mcp-authenticate` started. The worker's answer arrives on `frontdoor rc watch` as the control_response echoing the printed request id.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").requiredOption("--server <name>", "The MCP server's name, as `mcp-status` reports it.").requiredOption("--callback-url <url>", "The callback URL the browser landed on.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
74358
+ reportControlWrite(`handed MCP server ${options.server} its OAuth callback`, options.session, await frontDoorRcControlFromState(realFarmFs, paths).mcpOAuthCallbackUrl(options.session, options.server, options.callbackUrl), options.json);
74359
+ }),
74360
+ ["agent-shim frontdoor rc mcp-oauth-callback-url --session cse_00000000-0000-4000-8000-000000000000 --server github --callback-url https://example.com/oauth/callback?code=x"]
74361
+ );
74362
+ withExamples(
74363
+ rc.command("teleport").description("Send one teleport marker into an observed session over the teleport-events channel, the write half the cloud UI's ultrapan path owns on the real service. The marker rides the SDK user-message shape, the line the teleport relay anchors on.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").requiredOption("--marker <text>", "The marker text, the string content the teleport relay anchors on.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
74364
+ reportControlWrite(`teleported a marker into the session`, options.session, await frontDoorRcControlFromState(realFarmFs, paths).teleport(options.session, options.marker), options.json, { marker: options.marker });
74365
+ }),
74366
+ ['agent-shim frontdoor rc teleport --session cse_00000000-0000-4000-8000-000000000000 --marker "__ULTRAPAN_TELEPORT_LOCAL__"']
74367
+ );
74368
+ withExamples(
74369
+ rc.command("watch").description("Print the observed sessions' client read stream events as they arrive, the approvals the door's held stream receives above all, with each rate_limit_event rendered as the account's quota state. Read-only, until Ctrl-C leaves.").argument("[session]", "One cse_ session id, as `frontdoor rc list` shows it; every observed session when omitted.").option("--json", "Print each event as one JSON line as it arrives.").action(async (session, options) => {
74370
+ const client = frontDoorRcApiFromState(realFarmFs, paths);
74371
+ const leave = new AbortController();
74372
+ const onInterrupt = () => {
74373
+ leave.abort();
74374
+ };
74375
+ process.once("SIGINT", onInterrupt);
74376
+ try {
74377
+ for await (const event of await client.rc.subscribe(session === void 0 ? {} : { session }, { signal: leave.signal })) {
74378
+ if (options.json === true) {
74379
+ console.log(JSON.stringify(event));
74380
+ continue;
74381
+ }
74382
+ console.log(formatRcStreamEvent(event));
74640
74383
  }
74641
- if (isRecord6(body?.external_metadata)) {
74642
- session.externalMetadata = body.external_metadata;
74384
+ } catch (error62) {
74385
+ if (!leave.signal.aborted) {
74386
+ throw error62;
74643
74387
  }
74644
- await answerJson2(response, HTTP_STATUS.ok, {});
74645
- return;
74388
+ } finally {
74389
+ process.off("SIGINT", onInterrupt);
74646
74390
  }
74647
- if (workerTail === "register" && method === "POST") {
74648
- await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
74649
- await answerJson2(response, HTTP_STATUS.ok, { worker_epoch: RC_SELF_HOST_WORKER_EPOCH });
74391
+ }),
74392
+ ["agent-shim frontdoor rc watch", "agent-shim frontdoor rc watch cse_00000000-0000-4000-8000-000000000000 --json"]
74393
+ );
74394
+ const selfhost = withExamples(
74395
+ rc.command("selfhost").description("Mint the local credential behind the door's self-hosted Remote Control surface: the opt-in mode (AGENT_SHIM_FRONTDOOR_RC_SELF_HOST=1 in the door's environment) where the door serves the CCR surface itself and a session activates Remote Control with no Anthropic credential anywhere."),
74396
+ ["agent-shim frontdoor rc selfhost mint rig"]
74397
+ );
74398
+ withExamples(
74399
+ selfhost.command("mint").description("Mint the local OAuth credential, account block and feature-cache seed one identity needs to activate Remote Control against the door's own served surface, and record the door-side copy the surface authenticates against. Refuses to replace a real claude.ai login unless --force. Never prints a token.").argument("<identity>", "The identity to mint into, as `agent-shim identity list` names it.").option("--force", "Replace an existing OAuth credential this door did not mint.").option("--json", "Print the mint result as JSON.").action((identity, options) => {
74400
+ const result = mintRcSelfHostCredential({
74401
+ fs: realFarmFs,
74402
+ identitiesDir: paths.identitiesDir,
74403
+ frontdoorDir: paths.frontdoorDir,
74404
+ identity,
74405
+ newUuid: import_node_crypto14.randomUUID,
74406
+ randomToken: () => (0, import_node_crypto14.randomBytes)(MINTED_TOKEN_RANDOM_BYTES).toString("base64url"),
74407
+ force: options.force === true,
74408
+ now: () => Date.now()
74409
+ });
74410
+ if (options.json === true) {
74411
+ printJson(result);
74650
74412
  return;
74651
74413
  }
74652
- if (workerTail === "heartbeat" && method === "POST") {
74653
- await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
74654
- await answerJson2(response, HTTP_STATUS.ok, {});
74655
- return;
74414
+ for (const line of formatRcSelfHostMint(result)) {
74415
+ console.log(line);
74656
74416
  }
74657
- if (workerTail === "events" && method === "POST") {
74658
- const body = parseJsonObject3(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74659
- const claimedEpoch = numberOf2(body, "worker_epoch");
74660
- if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
74661
- await epochStale(response);
74662
- return;
74663
- }
74664
- const events = body?.events;
74665
- if (!Array.isArray(events) || events.length === 0 || events.length > RC_SELF_HOST_MAX_BATCH_EVENTS) {
74666
- await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: `a worker event batch is a non-empty array of at most ${String(RC_SELF_HOST_MAX_BATCH_EVENTS)} events` } });
74667
- return;
74668
- }
74669
- const results = [];
74670
- for (const event of events) {
74671
- if (!isRecord6(event) || !isRecord6(event.payload)) {
74672
- 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" } });
74673
- return;
74674
- }
74675
- const published = publish(session, RC_SOURCE_WORKER, event.payload);
74676
- results.push({ event_id: published.eventId, sequence_num: String(published.sequenceNum) });
74677
- }
74678
- sweep();
74679
- await answerJson2(response, HTTP_STATUS.ok, { results });
74680
- return;
74417
+ }),
74418
+ ["agent-shim frontdoor rc selfhost mint rig", "agent-shim frontdoor rc selfhost mint rig --force"]
74419
+ );
74420
+ }
74421
+
74422
+ // src/frontdoor/rcCredentialStore.ts
74423
+ var import_node_path35 = __toESM(require("node:path"), 1);
74424
+ function isSafeSessionId(sessionId) {
74425
+ return /^[A-Za-z0-9_-]+$/.test(sessionId);
74426
+ }
74427
+ function isRecord7(value2) {
74428
+ return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
74429
+ }
74430
+ function credentialOfJson(parsed) {
74431
+ if (!isRecord7(parsed)) {
74432
+ return void 0;
74433
+ }
74434
+ const { authorization, anthropicVersion, anthropicClientPlatform } = parsed;
74435
+ if (typeof authorization !== "string" || authorization === "") {
74436
+ return void 0;
74437
+ }
74438
+ return {
74439
+ authorization,
74440
+ anthropicVersion: typeof anthropicVersion === "string" ? anthropicVersion : void 0,
74441
+ anthropicClientPlatform: typeof anthropicClientPlatform === "string" ? anthropicClientPlatform : void 0
74442
+ };
74443
+ }
74444
+ function sequenceNumOfJson(value2) {
74445
+ return typeof value2 === "number" && Number.isInteger(value2) && value2 >= 0 ? value2 : void 0;
74446
+ }
74447
+ function createRcCredentialStore(fs20, dir) {
74448
+ fs20.mkdirPrivate(dir);
74449
+ const fileFor = (sessionId) => import_node_path35.default.join(dir, `${sessionId}.json`);
74450
+ const readParsed = (sessionId) => {
74451
+ const raw = fs20.readFileUtf8(fileFor(sessionId));
74452
+ if (raw === void 0) {
74453
+ return void 0;
74454
+ }
74455
+ let parsed;
74456
+ try {
74457
+ parsed = JSON.parse(raw);
74458
+ } catch {
74459
+ return void 0;
74460
+ }
74461
+ return isRecord7(parsed) ? parsed : void 0;
74462
+ };
74463
+ return {
74464
+ read: (sessionId) => {
74465
+ if (!isSafeSessionId(sessionId)) {
74466
+ return void 0;
74681
74467
  }
74682
- if (workerTail === "events/stream" && method === "GET") {
74683
- await holdStream(session, "workerStreams", (event) => event.toWorker, request7, response);
74468
+ return credentialOfJson(readParsed(sessionId));
74469
+ },
74470
+ write: (sessionId, credential) => {
74471
+ if (!isSafeSessionId(sessionId)) {
74684
74472
  return;
74685
74473
  }
74686
- if (workerTail === "events/delivery" && method === "POST") {
74687
- await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
74688
- await answerJson2(response, HTTP_STATUS.ok, {});
74474
+ const cursor3 = sequenceNumOfJson(readParsed(sessionId)?.highestSequenceNum);
74475
+ const record2 = { ...credential, ...cursor3 === void 0 ? {} : { highestSequenceNum: cursor3 } };
74476
+ fs20.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(record2, null, 2)}
74477
+ `);
74478
+ },
74479
+ readCursor: (sessionId) => {
74480
+ if (!isSafeSessionId(sessionId)) {
74481
+ return void 0;
74482
+ }
74483
+ return sequenceNumOfJson(readParsed(sessionId)?.highestSequenceNum);
74484
+ },
74485
+ writeCursor: (sessionId, highestSequenceNum) => {
74486
+ if (!isSafeSessionId(sessionId)) {
74689
74487
  return;
74690
74488
  }
74691
- if (workerTail === "internal-events" && method === "POST") {
74692
- const body = parseJsonObject3(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74693
- const claimedEpoch = numberOf2(body, "worker_epoch");
74694
- if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
74695
- await epochStale(response);
74696
- return;
74697
- }
74698
- const events = body?.events;
74699
- if (!Array.isArray(events)) {
74700
- await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "an internal event batch is an array" } });
74701
- return;
74702
- }
74703
- for (const event of events) {
74704
- if (isRecord6(event)) {
74705
- session.internalEvents.push({ eventId: deps.newUuid(), body: event });
74706
- }
74707
- }
74708
- await answerJson2(response, HTTP_STATUS.ok, {});
74489
+ const credential = credentialOfJson(readParsed(sessionId));
74490
+ const record2 = credential === void 0 ? { highestSequenceNum } : { ...credential, highestSequenceNum };
74491
+ fs20.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(record2, null, 2)}
74492
+ `);
74493
+ },
74494
+ remove: (sessionId) => {
74495
+ if (!isSafeSessionId(sessionId)) {
74709
74496
  return;
74710
74497
  }
74711
- if (workerTail === "internal-events" && method === "GET") {
74712
- const query = new URL(request7.url, "http://127.0.0.1").searchParams;
74713
- if (query.get("session_agent_id") !== null || query.get("subagents") !== null) {
74714
- 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" } });
74715
- return;
74716
- }
74717
- const page = pageOfRcChannel(request7.url, session.internalEvents, (event) => event.eventId, (event) => ({ event_id: event.eventId, payload: event.body }));
74718
- if ("error" in page) {
74719
- const anchorUnknown = query.get("after_event_id") !== null && page.error.startsWith("the anchor names an event id");
74720
- await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: anchorUnknown ? "after_event_id_not_found" : "invalid_request_error", message: page.error } });
74721
- return;
74722
- }
74723
- await answerJson2(response, HTTP_STATUS.ok, page);
74724
- return;
74725
- }
74726
- if (workerTail === "diagnostics" && method === "POST") {
74727
- await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
74728
- await answerJson2(response, HTTP_STATUS.ok, {});
74729
- return;
74730
- }
74731
- if (workerTail === "web-fetch" && method === "POST") {
74732
- const body = parseJsonObject3(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74733
- const target = nonEmptyString3(body, "url");
74734
- if (target === void 0) {
74735
- await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-fetch carries one url" } });
74736
- return;
74737
- }
74738
- const outcome = await deps.webFetch(target);
74739
- if (outcome.kind === "fetched") {
74740
- const answer = { url: target, destination_url: outcome.destinationUrl, text: outcome.text, ...outcome.contentType === void 0 ? {} : { content_type: outcome.contentType } };
74741
- if (Buffer.byteLength(JSON.stringify(answer), "utf8") > RC_WEB_FETCH_MAX_BYTES) {
74742
- deps.log?.(`rc selfhost ${session.id}: worker web-fetch of ${target} escaped past the reader cap when framed`);
74743
- await answerJson2(response, HTTP_STATUS.ok, { error: { error_type: "web_fetch_too_large", error_message: `the answer for ${target} exceeds the ${String(RC_WEB_FETCH_MAX_BYTES)} byte cap the CLI's proxy client reads` } });
74744
- return;
74745
- }
74746
- deps.log?.(`rc selfhost ${session.id}: worker web-fetch served ${target} as ${outcome.destinationUrl}`);
74747
- await answerJson2(response, HTTP_STATUS.ok, answer);
74748
- return;
74749
- }
74750
- deps.log?.(`rc selfhost ${session.id}: worker web-fetch of ${target} refused: ${outcome.errorType}`);
74751
- await answerJson2(response, HTTP_STATUS.ok, { error: { error_type: outcome.errorType, error_message: outcome.errorMessage } });
74752
- return;
74753
- }
74754
- if (workerTail === "web-search" && method === "POST") {
74755
- const body = parseJsonObject3(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74756
- const query = nonEmptyString3(body, "query");
74757
- if (query === void 0) {
74758
- await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-search carries one query" } });
74759
- return;
74760
- }
74761
- const allowedDomains = stringArray(body, "allowed_domains");
74762
- const blockedDomains = stringArray(body, "blocked_domains");
74763
- if (body?.allowed_domains !== void 0 && allowedDomains === void 0 || body?.blocked_domains !== void 0 && blockedDomains === void 0) {
74764
- 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" } });
74765
- return;
74766
- }
74767
- const searchProfile = nonEmptyString3(body, "search_profile");
74768
- const backend = deps.webSearch;
74769
- if (backend === void 0) {
74770
- deps.log?.(`rc selfhost ${session.id}: worker web-search of "${query}" refused: no search backend serves this door`);
74771
- await answerJson2(response, HTTP_STATUS.ok, { results: [], error: { error_type: "web_search_unavailable", error_message: "this front door serves the Remote Control web-search proxy without a search backend, so no query can be served here" } });
74772
- return;
74773
- }
74774
- const answered = await backend({ query, ...allowedDomains === void 0 ? {} : { allowedDomains }, ...blockedDomains === void 0 ? {} : { blockedDomains }, ...searchProfile === void 0 ? {} : { searchProfile } });
74775
- deps.log?.(`rc selfhost ${session.id}: worker web-search of "${query}" answered by its backend`);
74776
- if (isWebSearchRefusal(answered)) {
74777
- await answerJson2(response, HTTP_STATUS.ok, { results: [], error: { error_type: answered.errorType, error_message: answered.errorMessage } });
74778
- return;
74498
+ fs20.removeRecursive(fileFor(sessionId));
74499
+ }
74500
+ };
74501
+ }
74502
+
74503
+ // src/frontdoor/rcClientPage.ts
74504
+ var RC_CLIENT_PATH = "/__agent-shim/client";
74505
+ var RC_CLIENT_PAGE_SCRIPT = `'use strict';
74506
+ // The door's typed API as this page speaks it: the oRPC RPC protocol over plain fetch, which is one POST per procedure with a {"json": input} body and a {"json": output} answer, so no client library is needed. These helpers are plain top-level functions (not boot-scoped) so the page's tests can evaluate this script outside a browser and drive them against a mounted listener.
74507
+ var API_PREFIX = '/__agent-shim/orpc';
74508
+ var TOKEN_KEY = 'agent-shim-rc-token';
74509
+ var token = null;
74510
+
74511
+ function apiBase() { return window.location.origin + API_PREFIX; }
74512
+
74513
+ // Calls one procedure: resolves the output's own value, rejects with the door's error message when it answered one.
74514
+ async function rpc(path, input) {
74515
+ var response = await fetch(apiBase() + '/' + path, {
74516
+ method: 'POST',
74517
+ headers: { 'content-type': 'application/json', authorization: 'Bearer ' + token },
74518
+ body: JSON.stringify({ json: input === undefined ? {} : input }),
74519
+ });
74520
+ var envelope = await response.json().catch(function () { return {}; });
74521
+ if (!response.ok) {
74522
+ var message = envelope && envelope.json && envelope.json.message ? envelope.json.message : ('HTTP ' + response.status);
74523
+ var error = new Error(message);
74524
+ error.status = response.status;
74525
+ throw error;
74526
+ }
74527
+ return envelope.json;
74528
+ }
74529
+
74530
+ // Parses every complete SSE frame out of the buffered text, returning the frames and the unparsed remainder. Comment frames (the door's keepalive opens with one) carry no event name and are dropped.
74531
+ function parseSseFrames(buffer) {
74532
+ var frames = [];
74533
+ var rest = buffer;
74534
+ for (;;) {
74535
+ var boundary = rest.indexOf('\\n\\n');
74536
+ if (boundary === -1) { break; }
74537
+ var raw = rest.slice(0, boundary);
74538
+ rest = rest.slice(boundary + 2);
74539
+ var frame = { event: 'message', id: '', data: '' };
74540
+ var lines = raw.split('\\n');
74541
+ for (var i = 0; i < lines.length; i++) {
74542
+ var line = lines[i];
74543
+ if (line === '' || line.charAt(0) === ':') { continue; }
74544
+ var colon = line.indexOf(':');
74545
+ var field = colon === -1 ? line : line.slice(0, colon);
74546
+ var value = colon === -1 ? '' : line.slice(colon + 1);
74547
+ if (value.charAt(0) === ' ') { value = value.slice(1); }
74548
+ if (field === 'event') { frame.event = value; }
74549
+ if (field === 'id') { frame.id = value; }
74550
+ if (field === 'data') { frame.data += (frame.data === '' ? '' : '\\n') + value; }
74551
+ }
74552
+ if (frame.data !== '') { frames.push(frame); }
74553
+ }
74554
+ return { frames: frames, rest: rest };
74555
+ }
74556
+
74557
+ // Whether one source-tagged event continues the per-source sequence the tracker holds: the count of events the stream dropped when it does not, zero when it does. The tracker keeps the high-water mark, so a number the door re-sends after a reconnect is a duplicate, not a step back, and never reports a negative gap. The backbone holds no replay, so a gap is reported, never hidden.
74558
+ function sequenceGap(tracker, event) {
74559
+ var previous = tracker[event.source];
74560
+ if (event.sequence > (previous === undefined ? 0 : previous)) { tracker[event.source] = event.sequence; }
74561
+ if (previous === undefined || event.sequence <= previous) { return 0; }
74562
+ return event.sequence - previous - 1;
74563
+ }
74564
+
74565
+ function shortId(id) { return id.length > 14 ? id.slice(0, 8) + '..' + id.slice(-4) : id; }
74566
+
74567
+ // A bounded sketch of any payload, the feed's fallback rendering: one JSON line, cut with a marker rather than silently.
74568
+ function sketch(value, cap) {
74569
+ var text;
74570
+ try { text = JSON.stringify(value); } catch (e) { text = String(value); }
74571
+ if (text === undefined) { text = 'undefined'; }
74572
+ return text.length > cap ? text.slice(0, cap) + '...' : text;
74573
+ }
74574
+
74575
+ // One utilization fraction as a whole percentage, the same rounding the CLI's own watch line applies.
74576
+ function percentOf(fraction) { return String(Math.round(fraction * 100)) + '%'; }
74577
+
74578
+ // The reset instant a rate-limit payload states (unix epoch seconds) as an ISO instant; no countdown is derived from it, because a ticking derivation would claim a freshness the event stream never promised.
74579
+ function resetInstantOf(epochSeconds) { return new Date(epochSeconds * 1000).toISOString(); }
74580
+
74581
+ // Whether one rate-limit payload is readable, narrowed the same way the door's live usage state narrows it: the event type and a rate_limit_info object.
74582
+ function rateLimitInfoOf(payload) {
74583
+ if (payload === undefined || payload === null || typeof payload !== 'object' || payload.type !== 'rate_limit_event') { return undefined; }
74584
+ var info = payload.rate_limit_info;
74585
+ if (info === undefined || info === null || typeof info !== 'object' || typeof info.status !== 'string') { return undefined; }
74586
+ return info;
74587
+ }
74588
+
74589
+ // One rate-limit payload's facts as the quota summary the feed and the header render: the limiting window's status and utilisation, each subscription window, and the overage state, every value the payload's own words.
74590
+ function quotaSummary(info) {
74591
+ var parts = [info.status];
74592
+ if (info.rateLimitType !== undefined) { parts.push(info.rateLimitType); }
74593
+ if (typeof info.utilization === 'number') { parts.push(percentOf(info.utilization)); }
74594
+ if (typeof info.resetsAt === 'number') { parts.push('resets ' + resetInstantOf(info.resetsAt)); }
74595
+ if (info.unifiedWindows !== undefined && info.unifiedWindows !== null && typeof info.unifiedWindows === 'object') {
74596
+ var names = ['five_hour', 'seven_day', 'seven_day_overage_included'];
74597
+ var windows = [];
74598
+ for (var i = 0; i < names.length; i++) {
74599
+ var window = info.unifiedWindows[names[i]];
74600
+ if (window === undefined || window === null) { continue; }
74601
+ var bits = [];
74602
+ if (typeof window.utilization === 'number') { bits.push(percentOf(window.utilization)); }
74603
+ if (typeof window.resetsAt === 'number') { bits.push('resets ' + resetInstantOf(window.resetsAt)); }
74604
+ windows.push(bits.length === 0 ? names[i] : names[i] + ' ' + bits.join(' '));
74605
+ }
74606
+ if (windows.length > 0) { parts.push('windows ' + windows.join(', ')); }
74607
+ }
74608
+ if (info.isUsingOverage === true) { parts.push('overage in use'); }
74609
+ else if (typeof info.overageStatus === 'string') { parts.push('overage ' + info.overageStatus); }
74610
+ return parts.join(' ');
74611
+ }
74612
+
74613
+ // The header's quota text from the door's freshest live observation: the quota summary plus when the door observed it, or the plain statement that none has arrived yet.
74614
+ function quotaText(latest) {
74615
+ if (latest === undefined || latest === null) { return 'quota: not observed yet'; }
74616
+ var info = rateLimitInfoOf({ type: 'rate_limit_event', rate_limit_info: latest.rateLimit });
74617
+ if (info === undefined) { return 'quota: not readable'; }
74618
+ return 'quota: ' + quotaSummary(info) + ' observed ' + new Date(latest.observedAt).toLocaleTimeString();
74619
+ }
74620
+
74621
+ // The headline text of one Remote Control stream event: a rate-limit payload renders as its quota summary, the assistant and user payloads that carry a content array of text blocks render as their prose, everything else as its type plus a sketch.
74622
+ function rcLine(event) {
74623
+ var envelope = event.envelope;
74624
+ var payload = envelope.payload;
74625
+ var headline = '';
74626
+ var rateLimit = rateLimitInfoOf(payload);
74627
+ if (rateLimit !== undefined) {
74628
+ headline = quotaSummary(rateLimit);
74629
+ } else if (payload !== undefined && payload !== null && typeof payload === 'object' && Array.isArray(payload.content)) {
74630
+ var parts = [];
74631
+ for (var i = 0; i < payload.content.length; i++) {
74632
+ var block = payload.content[i];
74633
+ if (block !== null && typeof block === 'object' && block.type === 'text' && typeof block.text === 'string') { parts.push(block.text); }
74634
+ }
74635
+ if (parts.length > 0) { headline = parts.join(' '); }
74636
+ }
74637
+ if (headline === '') {
74638
+ headline = envelope.event_type;
74639
+ if (payload !== undefined) { headline += ' ' + sketch(payload, 240); }
74640
+ }
74641
+ return new Date().toLocaleTimeString() + ' ' + shortId(event.session) + ' ' + envelope.event_type + ' ' + headline;
74642
+ }
74643
+
74644
+ function el(tag, className, text) {
74645
+ var node = document.createElement(tag);
74646
+ if (className) { node.className = className; }
74647
+ if (text !== undefined) { node.textContent = text; }
74648
+ return node;
74649
+ }
74650
+
74651
+ var ui = null;
74652
+
74653
+ function renderEvent(event, gap) {
74654
+ if (ui === null) { return; }
74655
+ var list = ui.eventList;
74656
+ if (gap > 0) {
74657
+ list.appendChild(el('div', 'event gap', 'dropped events: ' + String(gap) + ' on source ' + event.source + ' (the stream holds no replay)'));
74658
+ }
74659
+ var line;
74660
+ if (event.source === 'rc') {
74661
+ line = rcLine(event.payload);
74662
+ } else {
74663
+ line = new Date().toLocaleTimeString() + ' [' + event.source + '] ' + sketch(event.payload, 240);
74664
+ }
74665
+ list.appendChild(el('div', 'event', line));
74666
+ while (list.childElementCount > 500) { list.removeChild(list.firstChild); }
74667
+ list.scrollTop = list.scrollHeight;
74668
+ }
74669
+
74670
+ function renderSessions(statuses) {
74671
+ if (ui === null) { return; }
74672
+ var list = ui.sessionList;
74673
+ list.textContent = '';
74674
+ if (statuses.length === 0) {
74675
+ list.appendChild(el('div', 'event dim', 'no Remote Control sessions observed'));
74676
+ return;
74677
+ }
74678
+ for (var i = 0; i < statuses.length; i++) {
74679
+ var status = statuses[i];
74680
+ var worker = status.workerState === undefined ? 'worker unknown' : 'worker ' + status.workerState.value;
74681
+ var row = el('div', 'session' + (status.id === ui.selected ? ' selected' : ''));
74682
+ row.appendChild(el('div', null, shortId(status.id)));
74683
+ row.appendChild(el('div', 'dim', worker + (status.pending.length === 0 ? '' : ', ' + String(status.pending.length) + ' pending') + ', seen ' + new Date(status.lastSeenAt).toLocaleTimeString()));
74684
+ row.addEventListener('click', selectSession(status.id));
74685
+ list.appendChild(row);
74686
+ }
74687
+ }
74688
+
74689
+ function selectSession(id) {
74690
+ return function () {
74691
+ if (ui === null) { return; }
74692
+ ui.selected = ui.selected === id ? null : id;
74693
+ ui.promptTarget.textContent = ui.selected === null ? 'no session selected' : 'to ' + ui.selected;
74694
+ refreshSessions();
74695
+ refreshPending();
74696
+ };
74697
+ }
74698
+
74699
+ function renderPending(pending) {
74700
+ if (ui === null) { return; }
74701
+ var list = ui.pendingList;
74702
+ list.textContent = '';
74703
+ if (pending.length === 0) {
74704
+ list.appendChild(el('div', 'event dim', 'no control requests awaiting an answer'));
74705
+ return;
74706
+ }
74707
+ for (var i = 0; i < pending.length; i++) {
74708
+ var request = pending[i];
74709
+ var card = el('div', 'card');
74710
+ card.appendChild(el('div', null, request.type + ' ' + (request.summary === '' ? shortId(request.requestId) : request.summary)));
74711
+ card.appendChild(el('div', 'dim', shortId(request.sessionId) + ' ' + shortId(request.requestId) + ' observed ' + new Date(request.observedAt).toLocaleTimeString()));
74712
+ var row = el('div', 'row');
74713
+ var denial = el('input');
74714
+ denial.type = 'text';
74715
+ denial.placeholder = 'denial message (optional)';
74716
+ var approve = el('button', null, 'Approve');
74717
+ var deny = el('button', 'secondary', 'Deny');
74718
+ approve.addEventListener('click', answerRequest(request, true, denial));
74719
+ deny.addEventListener('click', answerRequest(request, false, denial));
74720
+ row.appendChild(denial);
74721
+ row.appendChild(approve);
74722
+ row.appendChild(deny);
74723
+ card.appendChild(row);
74724
+ list.appendChild(card);
74725
+ }
74726
+ }
74727
+
74728
+ function answerRequest(request, approve, denialInput) {
74729
+ return function () {
74730
+ var input = { session: request.sessionId, request: request.requestId, approve: approve };
74731
+ if (!approve && denialInput.value !== '') { input.text = denialInput.value; }
74732
+ rpc('rc/answer', input).then(function () {
74733
+ renderEvent({ source: 'page', sequence: 0, payload: { note: (approve ? 'approved' : 'denied') + ' ' + request.requestId } }, 0);
74734
+ refreshPending();
74735
+ }, function (error) { window.alert('answer failed: ' + error.message); });
74736
+ };
74737
+ }
74738
+
74739
+ function sendPrompt() {
74740
+ if (ui === null || ui.selected === null) { window.alert('select a session first'); return; }
74741
+ var text = ui.prompt.value;
74742
+ if (text === '') { return; }
74743
+ ui.send.disabled = true;
74744
+ rpc('rc/send', { session: ui.selected, text: text }).then(function (result) {
74745
+ ui.prompt.value = '';
74746
+ renderEvent({ source: 'page', sequence: 0, payload: { note: 'sent, sequence_num ' + result.sequenceNums.join(', ') } }, 0);
74747
+ }, function (error) { window.alert('send failed: ' + error.message); }).then(function () { ui.send.disabled = false; });
74748
+ }
74749
+
74750
+ function refreshSessions() {
74751
+ rpc('rc/status', {}).then(function (result) { renderSessions(result.statuses); }, authOrReport('rc/status'));
74752
+ }
74753
+
74754
+ function refreshPending() {
74755
+ rpc('rc/pending', ui !== null && ui.selected !== null ? { session: ui.selected } : {}).then(function (result) { renderPending(result.pending); }, authOrReport('rc/pending'));
74756
+ }
74757
+
74758
+ // The header's quota span, fed by the door's live read on the same refresh cycle as the session and pending lists: the freshest rate-limit observation any session's stream has filed, with its observation time so its freshness reads at a glance.
74759
+ function refreshQuota() {
74760
+ rpc('usage/live', {}).then(function (result) {
74761
+ if (ui !== null) { ui.quotaState.textContent = quotaText(result.latest); }
74762
+ }, authOrReport('usage/live'));
74763
+ }
74764
+
74765
+ // The one error path every call shares: a 401 means this token is not this generation's, so the page says so and asks for it again; anything else is reported in the feed.
74766
+ function authOrReport(what) {
74767
+ return function (error) {
74768
+ if (error.status === 401) {
74769
+ handleUnauthorized();
74770
+ return;
74771
+ }
74772
+ renderEvent({ source: 'page', sequence: 0, payload: { note: what + ' failed: ' + error.message } }, 0);
74773
+ };
74774
+ }
74775
+
74776
+ function handleUnauthorized() {
74777
+ window.sessionStorage.removeItem(TOKEN_KEY);
74778
+ if (feedAbort !== null) { feedAbort.abort(); }
74779
+ streamState('');
74780
+ showTokenForm('this door refused the token: it names a different door generation, paste the current one');
74781
+ }
74782
+
74783
+ function streamState(text) { if (ui !== null) { ui.streamState.textContent = text; } }
74784
+
74785
+ var feedAbort = null;
74786
+
74787
+ // Subscribes to the door's backbone and renders what it yields, reconnecting on any drop. The stream holds no replay, so a reconnect starts at the live head and the per-source sequence numbers surface whatever the gap swallowed.
74788
+ async function runFeed() {
74789
+ var tracker = {};
74790
+ for (;;) {
74791
+ if (feedAbort !== null) { feedAbort.abort(); }
74792
+ feedAbort = new AbortController();
74793
+ var filter = document.getElementById('source-filter').value;
74794
+ var input = filter === '' ? {} : { sources: [filter] };
74795
+ try {
74796
+ streamState('stream: connecting');
74797
+ var response = await fetch(apiBase() + '/events/subscribe', {
74798
+ method: 'POST',
74799
+ headers: { 'content-type': 'application/json', authorization: 'Bearer ' + token },
74800
+ body: JSON.stringify({ json: input }),
74801
+ signal: feedAbort.signal,
74802
+ });
74803
+ if (response.status === 401) { handleUnauthorized(); return; }
74804
+ if (!response.ok || response.body === null) { throw new Error('HTTP ' + String(response.status)); }
74805
+ streamState('stream: live');
74806
+ var reader = response.body.getReader();
74807
+ var decoder = new TextDecoder();
74808
+ var buffer = '';
74809
+ for (;;) {
74810
+ var chunk = await reader.read();
74811
+ if (chunk.done) { break; }
74812
+ buffer += decoder.decode(chunk.value, { stream: true });
74813
+ var parsed = parseSseFrames(buffer);
74814
+ buffer = parsed.rest;
74815
+ for (var i = 0; i < parsed.frames.length; i++) {
74816
+ var frame = parsed.frames[i];
74817
+ if (frame.event !== 'message') { continue; }
74818
+ var event;
74819
+ try { event = JSON.parse(frame.data).json; } catch (e) { continue; }
74820
+ if (event === undefined || event.source === undefined) { continue; }
74821
+ renderEvent(event, sequenceGap(tracker, event));
74779
74822
  }
74780
- await answerJson2(response, HTTP_STATUS.ok, { results: answered.map((result) => ({ title: result.title ?? "", url: result.url, snippet: result.snippet ?? "" })) });
74781
- return;
74782
74823
  }
74783
- await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the self-hosted Remote Control surface serves no worker path "${workerTail}"` } });
74784
- return;
74824
+ } catch (error) {
74825
+ if (feedAbort !== null && feedAbort.signal.aborted) { return; }
74826
+ }
74827
+ streamState('stream: dropped, reconnecting');
74828
+ await new Promise(function (resolve) { window.setTimeout(resolve, 1000); });
74829
+ }
74830
+ }
74831
+
74832
+ function showTokenForm(message) {
74833
+ if (feedAbort !== null) { feedAbort.abort(); }
74834
+ if (ui !== null) { ui.main.classList.add('hidden'); }
74835
+ var form = document.getElementById('token-form');
74836
+ form.classList.remove('hidden');
74837
+ document.getElementById('token-error').textContent = message === undefined ? '' : message;
74838
+ document.getElementById('token-input').focus();
74839
+ }
74840
+
74841
+ function startPage(heldToken) {
74842
+ token = heldToken;
74843
+ ui = {
74844
+ main: document.getElementById('main'),
74845
+ eventList: document.getElementById('event-list'),
74846
+ sessionList: document.getElementById('session-list'),
74847
+ pendingList: document.getElementById('pending-list'),
74848
+ prompt: document.getElementById('prompt'),
74849
+ promptTarget: document.getElementById('prompt-target'),
74850
+ send: document.getElementById('send'),
74851
+ streamState: document.getElementById('stream-state'),
74852
+ quotaState: document.getElementById('quota-state'),
74853
+ selected: null,
74854
+ };
74855
+ document.getElementById('token-form').classList.add('hidden');
74856
+ ui.main.classList.remove('hidden');
74857
+ document.getElementById('refresh').addEventListener('click', function () { refreshSessions(); refreshPending(); refreshQuota(); });
74858
+ document.getElementById('send').addEventListener('click', sendPrompt);
74859
+ document.getElementById('source-filter').addEventListener('change', function () { runFeed(); });
74860
+ ui.prompt.addEventListener('keydown', function (event) {
74861
+ if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) { sendPrompt(); }
74862
+ });
74863
+ refreshSessions();
74864
+ refreshPending();
74865
+ refreshQuota();
74866
+ window.setInterval(function () { refreshSessions(); refreshPending(); refreshQuota(); }, 4000);
74867
+ runFeed();
74868
+ document.getElementById('door-state').textContent = 'connected';
74869
+ }
74870
+
74871
+ function boot() {
74872
+ var held = null;
74873
+ var query = new URLSearchParams(window.location.search);
74874
+ var fromQuery = query.get('token');
74875
+ if (fromQuery !== null && fromQuery !== '') {
74876
+ window.sessionStorage.setItem(TOKEN_KEY, fromQuery);
74877
+ // The token leaves the address bar the moment the page holds it, so a refresh or a shared screen shows the clean URL; sessionStorage keeps the tab working.
74878
+ window.history.replaceState(null, '', window.location.pathname);
74879
+ held = fromQuery;
74880
+ } else {
74881
+ held = window.sessionStorage.getItem(TOKEN_KEY);
74882
+ }
74883
+ document.getElementById('token-save').addEventListener('click', function () {
74884
+ var value = document.getElementById('token-input').value.trim();
74885
+ if (value === '') { return; }
74886
+ window.sessionStorage.setItem(TOKEN_KEY, value);
74887
+ startPage(value);
74888
+ });
74889
+ document.getElementById('token-input').addEventListener('keydown', function (event) {
74890
+ if (event.key === 'Enter') { document.getElementById('token-save').click(); }
74891
+ });
74892
+ if (held !== null && held !== '') { startPage(held); }
74893
+ }
74894
+
74895
+ if (typeof document !== 'undefined' && document.getElementById('event-list') !== null) {
74896
+ window.addEventListener('DOMContentLoaded', boot);
74897
+ }
74898
+ `;
74899
+ var RC_CLIENT_PAGE_HTML = `<!doctype html>
74900
+ <html lang="en">
74901
+ <head>
74902
+ <meta charset="utf-8">
74903
+ <meta name="viewport" content="width=device-width, initial-scale=1">
74904
+ <title>agent-shim Remote Control</title>
74905
+ <style>
74906
+ :root { color-scheme: dark; }
74907
+ * { box-sizing: border-box; }
74908
+ body { margin: 0; background: #101418; color: #d7dde3; font: 14px/1.45 ui-monospace, Menlo, Consolas, monospace; }
74909
+ header { display: flex; gap: 12px; align-items: baseline; padding: 10px 16px; background: #171d24; border-bottom: 1px solid #2a333d; }
74910
+ header h1 { font-size: 15px; margin: 0; font-weight: 600; }
74911
+ header .state { color: #8b98a5; }
74912
+ main { display: flex; gap: 1px; background: #2a333d; min-height: calc(100vh - 39px); }
74913
+ section { background: #101418; padding: 12px 16px; overflow-y: auto; }
74914
+ #sessions { flex: 0 0 320px; border-right: 1px solid #2a333d; }
74915
+ #right { flex: 1; display: flex; flex-direction: column; min-width: 0; }
74916
+ #feed { flex: 1; }
74917
+ #controls { border-top: 1px solid #2a333d; }
74918
+ h2 { font-size: 12px; text-transform: uppercase; letter-spacing: 0.08em; color: #8b98a5; margin: 8px 0 8px; font-weight: 600; }
74919
+ .session { padding: 6px 8px; border: 1px solid transparent; border-radius: 4px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
74920
+ .session:hover { background: #171d24; }
74921
+ .session.selected { border-color: #3d6ea5; background: #17222e; }
74922
+ .session .dim { color: #8b98a5; }
74923
+ .event { padding: 2px 0; white-space: pre-wrap; word-break: break-word; }
74924
+ .event .dim { color: #8b98a5; }
74925
+ .event.gap { color: #d29922; }
74926
+ .event.error { color: #e5534b; }
74927
+ textarea, input[type=text], select { background: #171d24; color: #d7dde3; border: 1px solid #2a333d; border-radius: 4px; padding: 6px 8px; font: inherit; width: 100%; }
74928
+ textarea { resize: vertical; min-height: 44px; }
74929
+ button { background: #1f4a7a; color: #d7dde3; border: 1px solid #3d6ea5; border-radius: 4px; padding: 5px 12px; font: inherit; cursor: pointer; }
74930
+ button.secondary { background: #2a333d; border-color: #3a444f; }
74931
+ button:disabled { opacity: 0.5; cursor: default; }
74932
+ .row { display: flex; gap: 8px; margin: 6px 0; align-items: center; }
74933
+ .card { border: 1px solid #3d6ea5; border-radius: 4px; padding: 8px; margin: 6px 0; background: #17222e; }
74934
+ .card .dim { color: #8b98a5; }
74935
+ #token-form { max-width: 480px; margin: 48px auto; }
74936
+ #token-form p { color: #8b98a5; }
74937
+ .hidden { display: none !important; }
74938
+ </style>
74939
+ </head>
74940
+ <body data-agent-shim-rc-client>
74941
+ <header>
74942
+ <h1>agent-shim Remote Control</h1>
74943
+ <span class="state" id="door-state">connecting</span>
74944
+ <span class="state" id="stream-state"></span>
74945
+ <span class="state" id="quota-state">quota: not observed yet</span>
74946
+ </header>
74947
+ <div id="token-form">
74948
+ <h2>This door's control token</h2>
74949
+ <p>The page is served behind the door's per-generation control token. Paste this door generation's token (the owner-only value the door writes beside its state) to use the page; it stays in this tab only.</p>
74950
+ <div class="row"><input type="text" id="token-input" placeholder="control token" autocomplete="off"><button id="token-save">Use</button></div>
74951
+ <p id="token-error" class="event error"></p>
74952
+ </div>
74953
+ <main class="hidden" id="main">
74954
+ <section id="sessions">
74955
+ <h2>Sessions <button class="secondary" id="refresh">Refresh</button></h2>
74956
+ <div id="session-list"></div>
74957
+ </section>
74958
+ <section id="right">
74959
+ <div id="feed">
74960
+ <h2>Feed <select id="source-filter"><option value="">all sources</option><option value="rc">Remote Control only</option></select></h2>
74961
+ <div id="event-list"></div>
74962
+ </div>
74963
+ <div id="controls">
74964
+ <h2>Pending approvals</h2>
74965
+ <div id="pending-list"></div>
74966
+ <h2>Send a prompt</h2>
74967
+ <div class="row"><textarea id="prompt" placeholder="Prompt for the selected session"></textarea></div>
74968
+ <div class="row"><span class="dim" id="prompt-target">no session selected</span><span style="flex:1"></span><button id="send">Send</button></div>
74969
+ </div>
74970
+ </section>
74971
+ </main>
74972
+ <script>
74973
+ ${RC_CLIENT_PAGE_SCRIPT}
74974
+ </script>
74975
+ </body>
74976
+ </html>
74977
+ `;
74978
+ function pageTokenOf(request7, url2) {
74979
+ const presented = request7.headers.authorization;
74980
+ const bearerPrefix = "bearer ".length;
74981
+ const header = typeof presented === "string" && presented.slice(0, bearerPrefix).toLowerCase() === "bearer " ? presented.slice(bearerPrefix) : void 0;
74982
+ const query = url2.searchParams.get("token") ?? void 0;
74983
+ return header ?? query;
74984
+ }
74985
+ function serveRcClientPage(expectedToken, request7, response) {
74986
+ const url2 = new URL(request7.url ?? "/", "https://door.invalid");
74987
+ if (url2.pathname !== RC_CLIENT_PATH && url2.pathname !== `${RC_CLIENT_PATH}/`) {
74988
+ response.writeHead(HTTP_STATUS.notFound, { "Content-Type": "text/plain" });
74989
+ response.end("no such page under the door's client prefix");
74990
+ return;
74991
+ }
74992
+ if (request7.method !== "GET" && request7.method !== "HEAD") {
74993
+ response.writeHead(HTTP_STATUS.methodNotAllowed, { Allow: "GET, HEAD", "Content-Type": "text/plain" });
74994
+ response.end("the door's web client page is read-only");
74995
+ return;
74996
+ }
74997
+ const token = pageTokenOf(request7, url2);
74998
+ if (token === void 0 || !isLiveCapability(token, [expectedToken])) {
74999
+ response.writeHead(HTTP_STATUS.unauthorized, { "Content-Type": "application/json" });
75000
+ response.end(JSON.stringify({ error: "the door's web client demands this generation's control token, as a Bearer credential or the token query parameter" }));
75001
+ return;
75002
+ }
75003
+ response.writeHead(HTTP_STATUS.ok, {
75004
+ "Content-Type": "text/html; charset=utf-8",
75005
+ "Cache-Control": "no-store",
75006
+ "Referrer-Policy": "no-referrer",
75007
+ "Content-Security-Policy": "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'self'"
75008
+ });
75009
+ response.end(request7.method === "HEAD" ? void 0 : RC_CLIENT_PAGE_HTML);
75010
+ }
75011
+ function createRcClientPage(expectedToken) {
75012
+ return {
75013
+ pathPrefix: RC_CLIENT_PATH,
75014
+ handle: async (request7, response) => {
75015
+ serveRcClientPage(expectedToken, request7, response);
75016
+ await Promise.resolve();
75017
+ return { matched: true };
75018
+ }
75019
+ };
75020
+ }
75021
+
75022
+ // src/frontdoor/rcSelfHostEndpoints.ts
75023
+ var RC_SOURCE_WORKER = "worker";
75024
+ var RC_SOURCE_CLIENT = "client";
75025
+ function isRecord8(value2) {
75026
+ return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
75027
+ }
75028
+ function parseJsonObject3(body) {
75029
+ let parsed;
75030
+ try {
75031
+ parsed = JSON.parse(body);
75032
+ } catch {
75033
+ return void 0;
75034
+ }
75035
+ return isRecord8(parsed) ? parsed : void 0;
75036
+ }
75037
+ function nonEmptyString2(record2, field) {
75038
+ const value2 = record2 === void 0 ? void 0 : record2[field];
75039
+ return typeof value2 === "string" && value2 !== "" ? value2 : void 0;
75040
+ }
75041
+ function numberOf(record2, field) {
75042
+ const value2 = record2 === void 0 ? void 0 : record2[field];
75043
+ if (typeof value2 === "number" && Number.isFinite(value2)) {
75044
+ return value2;
75045
+ }
75046
+ if (typeof value2 === "string" && /^[0-9]+$/.test(value2)) {
75047
+ return Number(value2);
75048
+ }
75049
+ return void 0;
75050
+ }
75051
+ function pageOfRcChannel(url2, rows, idOf, rowOf) {
75052
+ const query = new URL(url2, "http://127.0.0.1").searchParams;
75053
+ const anchor2 = query.get("after_event_id") ?? query.get("cursor");
75054
+ const rawLimit = query.get("limit");
75055
+ const limit = rawLimit === null ? void 0 : numberOf({ value: rawLimit }, "value");
75056
+ if (limit !== void 0 && limit < 1) {
75057
+ return { error: "the limit names a whole number of rows of at least one" };
75058
+ }
75059
+ const from = anchor2 === null ? 0 : rows.findIndex((row) => idOf(row) === anchor2) + 1;
75060
+ if (anchor2 !== null && from === 0) {
75061
+ return { error: `the anchor names an event id this surface holds no row for: ${anchor2}` };
75062
+ }
75063
+ const page = limit === void 0 ? rows.slice(from) : rows.slice(from, from + limit);
75064
+ const last = page[page.length - 1];
75065
+ return { data: page.map(rowOf), ...last === void 0 ? {} : { next_cursor: idOf(last) } };
75066
+ }
75067
+ async function serveRcClientChannels(ctx, tail, method, maxBatchEvents) {
75068
+ const { request: request7, response, host } = ctx;
75069
+ if (tail === "client/presence" && method === "POST") {
75070
+ const body = parseJsonObject3(await ctx.readBody(request7.body, CONTROL_BODY_CAP_BYTES));
75071
+ const clientId = nonEmptyString2(body, "client_id");
75072
+ const clear = body?.clear;
75073
+ if (clientId === void 0 || clear !== void 0 && typeof clear !== "boolean") {
75074
+ 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" } });
75075
+ return true;
74785
75076
  }
74786
- const clientRefusal = credentialRefusal(request7.headers);
74787
- if (clientRefusal !== void 0) {
74788
- await unauthorized(response, clientRefusal);
74789
- return;
75077
+ if (clear === true) {
75078
+ host.clients.delete(clientId);
75079
+ ctx.log?.(`rc selfhost ${host.id}: client ${clientId} cleared its presence`);
75080
+ } else {
75081
+ host.clients.set(clientId, ctx.now());
75082
+ ctx.log?.(`rc selfhost ${host.id}: client ${clientId} announced its presence${host.clients.size === 1 ? "" : ` (${String(host.clients.size)} clients now announced)`}`);
74790
75083
  }
74791
- if (tail === "events" && method === "POST") {
74792
- const body = parseJsonObject3(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74793
- const events = body?.events;
74794
- if (!Array.isArray(events) || events.length === 0 || events.length > RC_SELF_HOST_MAX_BATCH_EVENTS) {
74795
- 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` } });
74796
- return;
74797
- }
74798
- const results = [];
74799
- for (const event of events) {
74800
- if (!isRecord6(event) || !isRecord6(event.payload)) {
74801
- 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" } });
74802
- return;
74803
- }
74804
- const published = publish(session, RC_SOURCE_CLIENT, event.payload);
74805
- results.push({ sequence_num: String(published.sequenceNum), duplicate: published.duplicate });
75084
+ await ctx.answerJson(response, HTTP_STATUS.ok, { refresh_after_seconds: ctx.presenceRefreshSeconds });
75085
+ return true;
75086
+ }
75087
+ if (tail === "mark_read" && method === "POST") {
75088
+ const body = parseJsonObject3(await ctx.readBody(request7.body, CONTROL_BODY_CAP_BYTES));
75089
+ const eventId = nonEmptyString2(body, "event_id");
75090
+ const clientId = nonEmptyString2(body, "client_id");
75091
+ if (body !== void 0 && "event_id" in body && eventId === void 0 || "client_id" in (body ?? {}) && clientId === void 0) {
75092
+ 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" } });
75093
+ return true;
75094
+ }
75095
+ const receipt = ctx.publish(host.conversation, host.writer, clientId ?? RC_SOURCE_CLIENT, { type: "mark_read", ...eventId === void 0 ? {} : { event_id: eventId }, ...clientId === void 0 ? {} : { client_id: clientId } });
75096
+ ctx.log?.(`rc selfhost ${host.id}: ${clientId === void 0 ? "an unnamed client" : `client ${clientId}`} marked ${eventId === void 0 ? "the session read" : `event ${eventId} read`} as sequence ${String(receipt.sequenceNum)}`);
75097
+ await ctx.answerJson(response, HTTP_STATUS.ok, {});
75098
+ return true;
75099
+ }
75100
+ if (tail === "teleport-events" && method === "POST") {
75101
+ const body = parseJsonObject3(await ctx.readBody(request7.body, CONTROL_BODY_CAP_BYTES));
75102
+ const events = body?.events;
75103
+ if (!Array.isArray(events) || events.length === 0 || events.length > maxBatchEvents) {
75104
+ 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` } });
75105
+ return true;
75106
+ }
75107
+ const results = [];
75108
+ for (const event of events) {
75109
+ if (!isRecord8(event) || !isRecord8(event.payload)) {
75110
+ 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" } });
75111
+ return true;
74806
75112
  }
74807
- sweep();
74808
- await answerJson2(response, HTTP_STATUS.ok, { results });
74809
- return;
75113
+ const published = ctx.publish(host.conversation, host.writer, RC_SOURCE_CLIENT, event.payload);
75114
+ results.push({ sequence_num: String(published.sequenceNum), duplicate: published.duplicate });
74810
75115
  }
74811
- if (tail === "events" && method === "GET") {
74812
- 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 })) });
74813
- return;
75116
+ ctx.sweep();
75117
+ ctx.log?.(`rc selfhost ${host.id}: teleport write published ${String(results.length)} marker event(s)`);
75118
+ await ctx.answerJson(response, HTTP_STATUS.ok, { results });
75119
+ return true;
75120
+ }
75121
+ if (tail === "teleport-events" && method === "GET") {
75122
+ const page = pageOfRcChannel(request7.url, host.conversation.events, (event) => event.eventId, (event) => ({ event_id: event.eventId, payload: event.payload }));
75123
+ if ("error" in page) {
75124
+ await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: page.error } });
75125
+ return true;
74814
75126
  }
74815
- if (tail === "events/stream" && method === "GET") {
74816
- await holdStream(session, "clientStreams", () => true, request7, response);
75127
+ await ctx.answerJson(response, HTTP_STATUS.ok, page);
75128
+ return true;
75129
+ }
75130
+ return false;
75131
+ }
75132
+ 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" });
75133
+ async function serveRcCompatSessions(ctx, pathname, compatPath) {
75134
+ const { request: request7, response } = ctx;
75135
+ const method = request7.method.toUpperCase();
75136
+ if (pathname === compatPath) {
75137
+ if (method !== "GET") {
75138
+ await ctx.answerJson(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the compatibility session list is a GET" } });
74817
75139
  return;
74818
75140
  }
74819
- if (await serveRcClientChannels({ request: request7, response, session, answerJson: answerJson2, readBody: readBody3, publish, sweep, now: deps.now, presenceRefreshSeconds: RC_PRESENCE_REFRESH_SECONDS, ...deps.log === void 0 ? {} : { log: deps.log } }, tail, method, RC_SELF_HOST_MAX_BATCH_EVENTS)) {
75141
+ const listRefusal = ctx.credentialRefusal(request7.headers);
75142
+ if (listRefusal !== void 0) {
75143
+ await ctx.unauthorized(response, listRefusal);
74820
75144
  return;
74821
75145
  }
74822
- await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the self-hosted Remote Control surface serves no path "${tail}"` } });
74823
- };
74824
- const recordMissing = () => deps.credentialRecord() === void 0;
74825
- const route = {
74826
- name: "rc-selfhost",
74827
- headroomEligible: false,
74828
- headroomUpstream: void 0,
74829
- serve: async (request7, response) => {
74830
- const method = request7.method.toUpperCase();
74831
- const pathname = new URL(request7.url, "http://127.0.0.1").pathname;
74832
- if (recordMissing()) {
74833
- await noCredential(response);
74834
- return;
74835
- }
74836
- if (pathname === RC_COMPAT_SESSIONS_PATH || pathname.startsWith(`${RC_COMPAT_SESSIONS_PATH}/`)) {
74837
- await serveRcCompatSessions({ request: request7, response, sessions, answerJson: answerJson2, readBody: readBody3, credentialRefusal, unauthorized, now: deps.now, ...deps.log === void 0 ? {} : { log: deps.log } }, pathname, RC_COMPAT_SESSIONS_PATH);
74838
- return;
74839
- }
74840
- if (pathname !== RC_SESSIONS_PATH_PREFIX2 && !pathname.startsWith(`${RC_SESSIONS_PATH_PREFIX2}/`)) {
74841
- await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the self-hosted Remote Control surface serves no path "${pathname}"` } });
74842
- return;
74843
- }
74844
- if (pathname === RC_SESSIONS_PATH_PREFIX2) {
74845
- if (method === "POST") {
74846
- const createRefusal = credentialRefusal(request7.headers);
74847
- if (createRefusal !== void 0) {
74848
- await unauthorized(response, createRefusal);
74849
- return;
74850
- }
74851
- const body = parseJsonObject3(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74852
- const session = {
74853
- id: `${RC_SESSION_ID_PREFIX2}${deps.newUuid()}`,
74854
- createdAt: deps.now(),
74855
- title: nonEmptyString3(body, "title"),
74856
- workerJwts: /* @__PURE__ */ new Set(),
74857
- sequenceNum: 0,
74858
- events: [],
74859
- seenPayloadUuids: /* @__PURE__ */ new Set(),
74860
- workerStreams: /* @__PURE__ */ new Set(),
74861
- clientStreams: /* @__PURE__ */ new Set(),
74862
- internalEvents: [],
74863
- clients: /* @__PURE__ */ new Map(),
74864
- workerStatus: void 0,
74865
- externalMetadata: void 0,
74866
- lastTrafficAt: deps.now()
74867
- };
74868
- sessions.set(session.id, session);
74869
- deps.log?.(`rc selfhost ${session.id}: session created on the door's own surface`);
74870
- await answerJson2(response, HTTP_STATUS.ok, { session: { id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), status: "active" } });
74871
- return;
74872
- }
74873
- if (method === "GET") {
74874
- const readRefusal = credentialRefusal(request7.headers);
74875
- if (readRefusal !== void 0) {
74876
- await unauthorized(response, readRefusal);
74877
- return;
74878
- }
74879
- await answerJson2(response, HTTP_STATUS.ok, { data: [...sessions.values()].map((session) => ({ id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), updated_at: new Date(session.lastTrafficAt).toISOString(), session_status: "active" })) });
74880
- return;
74881
- }
74882
- await answerJson2(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the session collection is a POST (create) and a GET (list)" } });
74883
- return;
74884
- }
74885
- const rest = pathname.slice(`${RC_SESSIONS_PATH_PREFIX2}/`.length);
74886
- const slash = rest.indexOf("/");
74887
- const id = slash === -1 ? rest : rest.slice(0, slash);
74888
- const tail = slash === -1 ? "" : rest.slice(slash + 1);
74889
- if (!id.startsWith(RC_SESSION_ID_PREFIX2)) {
74890
- await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: "the session family names one cse_-prefixed session id" } });
74891
- return;
74892
- }
74893
- await serveSessionPath(sessions.get(id), tail, request7, response);
74894
- }
74895
- };
74896
- const answerServerJson = (response, status, body) => {
74897
- const text2 = JSON.stringify(body);
74898
- response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text2, "utf8")) });
74899
- response.end(text2);
74900
- };
74901
- const local = {
74902
- parsesHost: (host) => host === RC_SELF_HOST_OAUTH_HOST,
74903
- serve: async (host, request7, response) => {
74904
- const method = (request7.method ?? "").toUpperCase();
74905
- const pathname = new URL(request7.url ?? "/", "http://127.0.0.1").pathname;
74906
- if (host === RC_SELF_HOST_OAUTH_HOST) {
74907
- if (method === "POST" && pathname === "/v1/oauth/token") {
74908
- const body = parseJsonObject3(await readBody3(request7, CONTROL_BODY_CAP_BYTES));
74909
- const record2 = deps.credentialRecord();
74910
- if (record2 === void 0) {
74911
- 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" });
74912
- return true;
74913
- }
74914
- if (body?.grant_type !== void 0 && body.grant_type !== "refresh_token") {
74915
- answerServerJson(response, HTTP_STATUS.badRequest, { error: "unsupported_grant_type" });
74916
- return true;
74917
- }
74918
- answerServerJson(response, HTTP_STATUS.ok, { access_token: record2.accessToken, refresh_token: record2.refreshToken, expires_in: RC_SELF_HOST_OAUTH_TOKEN_TTL_SECONDS, scope: RC_SELF_HOST_SCOPE_LIST.join(" ") });
74919
- return true;
74920
- }
74921
- if (method === "GET" && pathname === "/v1/oauth/hello") {
74922
- answerServerJson(response, HTTP_STATUS.ok, {});
74923
- return true;
74924
- }
74925
- return false;
74926
- }
74927
- if (method === "POST" && pathname.startsWith("/api/eval/")) {
74928
- await readBody3(request7, CONTROL_BODY_CAP_BYTES);
74929
- answerServerJson(response, HTTP_STATUS.ok, { features: growthBookFeatures(), dateUpdated: Math.floor(deps.now() / MS_PER_SECOND7) });
74930
- return true;
74931
- }
74932
- if (method === "GET" && pathname === "/api/oauth/profile") {
74933
- const record2 = deps.credentialRecord();
74934
- if (record2 === void 0) {
74935
- 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" });
74936
- return true;
74937
- }
74938
- answerServerJson(response, HTTP_STATUS.ok, {
74939
- account: { uuid: record2.accountUuid, display_name: "agent-shim self-hosted", created_at: (/* @__PURE__ */ new Date(0)).toISOString() },
74940
- organization: { uuid: record2.organizationUuid, organization_type: "claude_max" }
74941
- });
74942
- return true;
74943
- }
74944
- if (method === "GET" && pathname === "/api/claude_code/policy_limits") {
74945
- answerServerJson(response, HTTP_STATUS.ok, {});
74946
- return true;
74947
- }
74948
- if (method === "POST" && pathname === "/api/oauth/validate") {
74949
- await readBody3(request7, CONTROL_BODY_CAP_BYTES);
74950
- const record2 = deps.credentialRecord();
74951
- if (record2 === void 0) {
74952
- 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" });
74953
- return true;
74954
- }
74955
- answerServerJson(response, HTTP_STATUS.ok, { scopes: [...RC_SELF_HOST_SCOPE_LIST], expiresAt: null, subscriptionType: "max", account_uuid: record2.accountUuid, organization_uuid: record2.organizationUuid });
74956
- return true;
74957
- }
74958
- if (method === "POST" && (pathname === "/api/event_logging/v2/batch" || pathname === "/api/claude_code/metrics" || pathname === "/api/claude_cli_feedback")) {
74959
- await readBody3(request7, CONTROL_BODY_CAP_BYTES);
74960
- answerServerJson(response, HTTP_STATUS.ok, {});
74961
- return true;
74962
- }
74963
- if (method === "GET" && pathname === "/api/hello") {
74964
- answerServerJson(response, HTTP_STATUS.ok, {});
74965
- return true;
75146
+ await ctx.answerJson(response, HTTP_STATUS.ok, { data: [...ctx.sessions.values()].map((session2) => compatSessionRow(session2, ctx.now)) });
75147
+ return;
75148
+ }
75149
+ const rest = pathname.slice(`${compatPath}/`.length);
75150
+ const slash = rest.indexOf("/");
75151
+ const address = slash === -1 ? rest : rest.slice(0, slash);
75152
+ const compatTail = slash === -1 ? "" : rest.slice(slash + 1);
75153
+ const refusal = ctx.credentialRefusal(request7.headers);
75154
+ if (refusal !== void 0) {
75155
+ await ctx.unauthorized(response, refusal);
75156
+ return;
75157
+ }
75158
+ const session = ctx.sessions.get(address) ?? ctx.sessions.get(`cse_${address}`);
75159
+ if (compatTail === "") {
75160
+ if (method === "GET") {
75161
+ if (session === void 0) {
75162
+ await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `Session not found: ${address}` } });
75163
+ return;
74966
75164
  }
74967
- return false;
75165
+ await ctx.answerJson(response, HTTP_STATUS.ok, compatSessionRow(session, ctx.now));
75166
+ return;
74968
75167
  }
74969
- };
74970
- return {
74971
- route,
74972
- local,
74973
- close: () => {
74974
- clearInterval(timer);
74975
- clearInterval(keepalive);
74976
- for (const session of sessions.values()) {
74977
- for (const sink of [...session.workerStreams, ...session.clientStreams]) {
74978
- sink.retire();
74979
- }
75168
+ if (method === "PATCH" || method === "PUT") {
75169
+ if (session === void 0) {
75170
+ await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `Session not found: ${address}` } });
75171
+ return;
74980
75172
  }
74981
- sessions.clear();
75173
+ const body = parseJsonObject3(await ctx.readBody(request7.body, CONTROL_BODY_CAP_BYTES));
75174
+ const title = nonEmptyString2(body, "title");
75175
+ if (title === void 0) {
75176
+ 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" } });
75177
+ return;
75178
+ }
75179
+ session.title = title;
75180
+ session.lastTrafficAt = ctx.now();
75181
+ ctx.log?.(`rc selfhost ${session.id}: retitled through the compatibility family`);
75182
+ await ctx.answerJson(response, HTTP_STATUS.ok, compatSessionRow(session, ctx.now));
75183
+ return;
74982
75184
  }
74983
- };
75185
+ 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" } });
75186
+ return;
75187
+ }
75188
+ if ((compatTail === "archive" || compatTail === "unarchive") && method === "POST") {
75189
+ await ctx.readBody(request7.body, CONTROL_BODY_CAP_BYTES);
75190
+ if (session === void 0) {
75191
+ await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `Session not found: ${address}` } });
75192
+ return;
75193
+ }
75194
+ if (compatTail === "unarchive") {
75195
+ await ctx.answerJson(response, HTTP_STATUS.ok, compatSessionRow(session, ctx.now));
75196
+ return;
75197
+ }
75198
+ ctx.retireSession(session);
75199
+ ctx.log?.(`rc selfhost ${session.id}: archived through the compatibility family`);
75200
+ await ctx.answerJson(response, HTTP_STATUS.ok, {});
75201
+ return;
75202
+ }
75203
+ await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the compatibility session family serves no path "${compatTail}"` } });
74984
75204
  }
74985
75205
 
74986
- // src/frontdoor/rcSelfHostMint.ts
74987
- var CREDENTIALS_FILE = ".credentials.json";
74988
- var CLAUDE_JSON2 = ".claude.json";
74989
- var RC_SELF_HOST_RECORD_DIR = "rc-selfhost";
74990
- var RC_SELF_HOST_RECORD_FILE = "credential.json";
74991
- var SELF_HOST_EMAIL = "selfhost@agent-shim.invalid";
74992
- var SELF_HOST_SUBSCRIPTION_TYPE = "max";
74993
- function isRecord7(value2) {
75206
+ // src/frontdoor/rcSelfHostConversations.ts
75207
+ var RC_CONVERSATIONS_PATH_PREFIX = "/v1/code/conversations";
75208
+ var RC_CONVERSATION_ID_PREFIX = "conv_";
75209
+ function isRecord9(value2) {
74994
75210
  return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
74995
75211
  }
74996
- function readJsonObject(fs20, file2) {
74997
- const raw = fs20.readFileUtf8(file2);
74998
- if (raw === void 0) {
74999
- return void 0;
75000
- }
75212
+ function parseJsonObject4(body) {
75001
75213
  let parsed;
75002
75214
  try {
75003
- parsed = JSON.parse(raw);
75215
+ parsed = JSON.parse(body);
75004
75216
  } catch {
75005
75217
  return void 0;
75006
75218
  }
75007
- return isRecord7(parsed) ? parsed : void 0;
75219
+ return isRecord9(parsed) ? parsed : void 0;
75008
75220
  }
75009
- function readRcSelfHostRecord(fs20, frontdoorDir) {
75010
- const parsed = readJsonObject(fs20, import_node_path34.default.join(frontdoorDir, RC_SELF_HOST_RECORD_DIR, RC_SELF_HOST_RECORD_FILE));
75011
- if (parsed === void 0) {
75012
- return void 0;
75221
+ function nonEmptyString3(record2, field) {
75222
+ const value2 = record2 === void 0 ? void 0 : record2[field];
75223
+ return typeof value2 === "string" && value2 !== "" ? value2 : void 0;
75224
+ }
75225
+ function newRcSelfHostConversation(newUuid, now) {
75226
+ return { id: `${RC_CONVERSATION_ID_PREFIX}${newUuid()}`, createdAt: now(), sequenceNum: 0, events: [], seenPayloadUuids: /* @__PURE__ */ new Set(), workerStreams: /* @__PURE__ */ new Set(), clientStreams: /* @__PURE__ */ new Set(), clients: /* @__PURE__ */ new Map(), sessionIds: /* @__PURE__ */ new Set(), lastTrafficAt: now() };
75227
+ }
75228
+ function conversationRow(conversation) {
75229
+ return { id: conversation.id, created_at: new Date(conversation.createdAt).toISOString(), updated_at: new Date(conversation.lastTrafficAt).toISOString(), sessions: [...conversation.sessionIds] };
75230
+ }
75231
+ async function serveRcConversations(ctx, pathname, method) {
75232
+ const { request: request7, response, conversations } = ctx;
75233
+ const refusal = ctx.credentialRefusal(request7.headers);
75234
+ if (refusal !== void 0) {
75235
+ await ctx.unauthorized(response, refusal);
75236
+ return;
75013
75237
  }
75014
- const { accessToken, refreshToken, organizationUuid, accountUuid } = parsed;
75015
- if (typeof accessToken !== "string" || accessToken === "" || typeof refreshToken !== "string" || refreshToken === "" || typeof organizationUuid !== "string" || organizationUuid === "" || typeof accountUuid !== "string" || accountUuid === "") {
75016
- return void 0;
75238
+ if (pathname === RC_CONVERSATIONS_PATH_PREFIX) {
75239
+ if (method !== "GET") {
75240
+ await ctx.answerJson(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the conversation collection is a GET; a conversation is born from a session create, never here" } });
75241
+ return;
75242
+ }
75243
+ await ctx.answerJson(response, HTTP_STATUS.ok, { data: [...conversations.values()].sort((left, right) => left.createdAt - right.createdAt || (left.id < right.id ? -1 : 1)).map(conversationRow) });
75244
+ return;
75017
75245
  }
75018
- return { accessToken, refreshToken, organizationUuid, accountUuid };
75019
- }
75020
- function mintRcSelfHostCredential(deps) {
75021
- if (!isIdentityName(deps.identity)) {
75022
- throw new Error(`"${deps.identity}" is not a valid identity name.`);
75246
+ const rest = pathname.slice(`${RC_CONVERSATIONS_PATH_PREFIX}/`.length);
75247
+ const slash = rest.indexOf("/");
75248
+ const conversationId = slash === -1 ? rest : rest.slice(0, slash);
75249
+ const tail = slash === -1 ? "" : rest.slice(slash + 1);
75250
+ if (!conversationId.startsWith(RC_CONVERSATION_ID_PREFIX)) {
75251
+ await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: "the conversation family names one conv_-prefixed conversation id" } });
75252
+ return;
75023
75253
  }
75024
- const identityDir = import_node_path34.default.join(deps.identitiesDir, deps.identity);
75025
- const credentialsFile = import_node_path34.default.join(identityDir, CREDENTIALS_FILE);
75026
- const claudeJsonFile = import_node_path34.default.join(identityDir, CLAUDE_JSON2);
75027
- const recordDir = import_node_path34.default.join(deps.frontdoorDir, RC_SELF_HOST_RECORD_DIR);
75028
- const recordFile = import_node_path34.default.join(recordDir, RC_SELF_HOST_RECORD_FILE);
75029
- const previousRecord = readRcSelfHostRecord(deps.fs, deps.frontdoorDir);
75030
- const existingCredentials = readJsonObject(deps.fs, credentialsFile);
75031
- const existingOauth = isRecord7(existingCredentials?.claudeAiOauth) ? existingCredentials.claudeAiOauth : void 0;
75032
- const existingToken = typeof existingOauth?.accessToken === "string" && existingOauth.accessToken !== "" ? existingOauth.accessToken : void 0;
75033
- const existingIsOwn = existingToken !== void 0 && previousRecord?.accessToken === existingToken;
75034
- if (existingToken !== void 0 && !existingIsOwn && !deps.force) {
75035
- throw new Error(
75036
- `identity ${deps.identity} already holds an OAuth credential this door did not mint (a real claude.ai login): pass --force to replace it, or mint into an identity with no login. The self-hosted mode expects no Anthropic credential to be in play.`
75037
- );
75254
+ const conversation = conversations.get(conversationId);
75255
+ if (conversation === void 0) {
75256
+ await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: "no such Remote Control conversation on this door" } });
75257
+ return;
75038
75258
  }
75039
- const accessToken = `sk-ant-oat${deps.randomToken()}`;
75040
- const refreshToken = deps.randomToken();
75041
- const organizationUuid = deps.newUuid();
75042
- const accountUuid = deps.newUuid();
75043
- const mintedAt = new Date(deps.now()).toISOString();
75044
- deps.fs.mkdirPrivate(import_node_path34.default.dirname(credentialsFile));
75045
- 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)}
75046
- `);
75047
- const claudeJson = readJsonObject(deps.fs, claudeJsonFile) ?? {};
75048
- const existingBlock = (value2) => isRecord7(value2) ? value2 : {};
75049
- deps.fs.writeFilePrivate(claudeJsonFile, `${JSON.stringify({
75050
- ...claudeJson,
75051
- oauthAccount: {
75052
- ...existingBlock(claudeJson.oauthAccount),
75053
- accountUuid,
75054
- emailAddress: SELF_HOST_EMAIL,
75055
- organizationUuid,
75056
- organizationName: "agent-shim self-hosted",
75057
- // The billing and creation fields exist so the CLI's "populate the account block if it is missing" step finds a complete block and skips its profile fetch; the door answers that fetch locally anyway, so these are the quiet path, not a load-bearing claim.
75058
- billingType: "stripe_subscription",
75059
- accountCreatedAt: mintedAt,
75060
- subscriptionCreatedAt: mintedAt
75061
- },
75062
- cachedGrowthBookFeatures: {
75063
- ...existingBlock(claudeJson.cachedGrowthBookFeatures),
75064
- tengu_ccr_bridge: true,
75065
- tengu_bridge_repl_v2: true,
75066
- tengu_bridge_repl_v2_cse_shim_enabled: true
75259
+ conversation.lastTrafficAt = ctx.now();
75260
+ if (tail === "" && method === "GET") {
75261
+ await ctx.answerJson(response, HTTP_STATUS.ok, { conversation: conversationRow(conversation) });
75262
+ return;
75263
+ }
75264
+ if (tail === "events" && method === "POST") {
75265
+ const body = parseJsonObject4(await ctx.readBody(request7.body, CONTROL_BODY_CAP_BYTES));
75266
+ const events = body?.events;
75267
+ if (!Array.isArray(events) || events.length === 0 || events.length > ctx.maxBatchEvents) {
75268
+ await ctx.answerJson(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(ctx.maxBatchEvents)} events` } });
75269
+ return;
75067
75270
  }
75068
- }, null, 2)}
75069
- `);
75070
- deps.fs.mkdirPrivate(recordDir);
75071
- deps.fs.writeFilePrivate(recordFile, `${JSON.stringify({ accessToken, refreshToken, organizationUuid, accountUuid }, null, 2)}
75072
- `);
75073
- return { identity: deps.identity, organizationUuid, credentialsFile, claudeJsonFile, recordFile, replaced: existingIsOwn };
75074
- }
75075
-
75076
- // src/frontdoor/rcCommands.ts
75077
- var MINTED_TOKEN_RANDOM_BYTES = 32;
75078
- function reportControlWrite(what, session, result, json2, extra = {}) {
75079
- if (!result.ok) {
75080
- throw new Error(result.message);
75271
+ const results = [];
75272
+ for (const event of events) {
75273
+ if (!isRecord9(event) || !isRecord9(event.payload)) {
75274
+ await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "every event in a write is an object carrying one payload object" } });
75275
+ return;
75276
+ }
75277
+ const published = ctx.publish(conversation, { session: void 0, half: "client" }, RC_SOURCE_CLIENT, event.payload);
75278
+ results.push({ sequence_num: String(published.sequenceNum), duplicate: published.duplicate });
75279
+ }
75280
+ ctx.sweep();
75281
+ ctx.log?.(`rc selfhost ${conversation.id}: a conversation client wrote ${String(results.length)} event(s) into the shared space`);
75282
+ await ctx.answerJson(response, HTTP_STATUS.ok, { results });
75283
+ return;
75081
75284
  }
75082
- if (json2 === true) {
75083
- printJson({ session, ...result.requestId === void 0 ? {} : { request: result.requestId }, sequenceNums: result.sequenceNums, ...extra });
75285
+ if (tail === "events" && method === "GET") {
75286
+ await ctx.answerJson(response, HTTP_STATUS.ok, { data: conversation.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 })) });
75084
75287
  return;
75085
75288
  }
75086
- console.log(`${what} on ${session}${result.requestId === void 0 ? "" : `: request ${result.requestId}`} sequence_num ${result.sequenceNums.join(", ")}`);
75087
- }
75088
- function formatRcSessionList(sessions) {
75089
- if (sessions.length === 0) {
75090
- return ["no Remote Control sessions observed"];
75289
+ if (tail === "events/stream" && method === "GET") {
75290
+ await ctx.holdStream(conversation, conversation.id, "clientStreams", () => true, request7, response);
75291
+ return;
75091
75292
  }
75092
- return sessions.map((session) => `${session.id} created ${new Date(session.createdAt).toISOString()} last seen ${new Date(session.lastSeenAt).toISOString()}`);
75293
+ if (await serveRcClientChannels({ request: request7, response, host: { id: conversation.id, clients: conversation.clients, conversation, writer: { session: void 0, half: "client" } }, answerJson: ctx.answerJson, readBody: ctx.readBody, publish: ctx.publish, sweep: ctx.sweep, now: ctx.now, presenceRefreshSeconds: ctx.presenceRefreshSeconds, ...ctx.log === void 0 ? {} : { log: ctx.log } }, tail, method, ctx.maxBatchEvents)) {
75294
+ return;
75295
+ }
75296
+ await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the conversation family serves no path "${tail}"` } });
75093
75297
  }
75094
- function formatRcSessionStatus(statuses) {
75095
- if (statuses.length === 0) {
75096
- return ["no Remote Control sessions observed"];
75298
+
75299
+ // src/frontdoor/rcWebFetch.ts
75300
+ var dns2 = __toESM(require("node:dns"), 1);
75301
+ var http6 = __toESM(require("node:http"), 1);
75302
+ var https5 = __toESM(require("node:https"), 1);
75303
+ var net3 = __toESM(require("node:net"), 1);
75304
+ var RC_WEB_FETCH_MAX_BYTES = 12582912;
75305
+ var RC_WEB_FETCH_DEADLINE_MS = 4e4;
75306
+ var RC_WEB_FETCH_MAX_REDIRECTS = 20;
75307
+ var RC_WEB_FETCH_ALLOW_PRIVATE_ENV = "AGENT_SHIM_FRONTDOOR_RC_SELF_HOST_WEB_FETCH_ALLOW_PRIVATE";
75308
+ function rcWebFetchAllowsPrivateFromEnv(env) {
75309
+ return env[RC_WEB_FETCH_ALLOW_PRIVATE_ENV] === "1";
75310
+ }
75311
+ var WEB_FETCH_USER_AGENT = "agent-shim-frontdoor";
75312
+ var ERROR_PREFIX = "web_fetch_";
75313
+ var V4_THIS_NETWORK_OCTET = 0;
75314
+ var V4_PRIVATE_A_OCTET = 10;
75315
+ var V4_LOOPBACK_OCTET = 127;
75316
+ var V4_CGNAT_OCTET = 100;
75317
+ var V4_CGNAT_SECOND_FROM = 64;
75318
+ var V4_CGNAT_SECOND_TO = 127;
75319
+ var V4_LINK_LOCAL_OCTET = 169;
75320
+ var V4_LINK_LOCAL_SECOND = 254;
75321
+ var V4_PRIVATE_B_OCTET = 172;
75322
+ var V4_PRIVATE_B_SECOND_FROM = 16;
75323
+ var V4_PRIVATE_B_SECOND_TO = 31;
75324
+ var V4_PRIVATE_C_OCTET = 192;
75325
+ var V4_PRIVATE_C_SECOND = 168;
75326
+ var V4_MULTICAST_FROM_OCTET = 224;
75327
+ var V6_UNIQUE_LOCAL_FROM = 64512;
75328
+ var V6_UNIQUE_LOCAL_TO = 65023;
75329
+ var V6_LINK_LOCAL_FROM = 65152;
75330
+ var V6_LINK_LOCAL_TO = 65215;
75331
+ var V6_MULTICAST_FROM = 65280;
75332
+ var IP_VERSION_4 = 4;
75333
+ var IP_VERSION_6 = 6;
75334
+ var HTTP_MOVED_PERMANENTLY = 301;
75335
+ var HTTP_FOUND = 302;
75336
+ var HTTP_SEE_OTHER = 303;
75337
+ var HTTP_TEMPORARY_REDIRECT = 307;
75338
+ var HTTP_PERMANENT_REDIRECT = 308;
75339
+ var SUCCESS_STATUS_MAX_EXCLUSIVE3 = 300;
75340
+ function refused(errorType, errorMessage2) {
75341
+ return { kind: "refused", errorType: `${ERROR_PREFIX}${errorType}`, errorMessage: errorMessage2 };
75342
+ }
75343
+ function isPrivateAddress(address) {
75344
+ const version2 = net3.isIP(address);
75345
+ if (version2 === IP_VERSION_4) {
75346
+ const octets = address.split(".").map((part) => Number(part));
75347
+ const first = octets[0] ?? -1;
75348
+ const second = octets[1] ?? -1;
75349
+ if (first === V4_THIS_NETWORK_OCTET || first === V4_PRIVATE_A_OCTET || first === V4_LOOPBACK_OCTET) {
75350
+ return true;
75351
+ }
75352
+ if (first === V4_CGNAT_OCTET && second >= V4_CGNAT_SECOND_FROM && second <= V4_CGNAT_SECOND_TO) {
75353
+ return true;
75354
+ }
75355
+ if (first === V4_LINK_LOCAL_OCTET && second === V4_LINK_LOCAL_SECOND) {
75356
+ return true;
75357
+ }
75358
+ if (first === V4_PRIVATE_B_OCTET && second >= V4_PRIVATE_B_SECOND_FROM && second <= V4_PRIVATE_B_SECOND_TO) {
75359
+ return true;
75360
+ }
75361
+ if (first === V4_PRIVATE_C_OCTET && second === V4_PRIVATE_C_SECOND) {
75362
+ return true;
75363
+ }
75364
+ return first >= V4_MULTICAST_FROM_OCTET;
75097
75365
  }
75098
- const lines = [];
75099
- for (const status of statuses) {
75100
- lines.push(`${status.id} created ${new Date(status.createdAt).toISOString()} last seen ${new Date(status.lastSeenAt).toISOString()}`);
75101
- const worker = status.workerState === void 0 && status.workerIdleSeconds === void 0 ? "not observed" : [
75102
- status.workerState === void 0 ? "state unknown" : `state ${status.workerState.value} since ${new Date(status.workerState.observedAt).toISOString()}`,
75103
- status.workerIdleSeconds === void 0 ? "idle unknown" : `idle ${String(status.workerIdleSeconds.value)} s as of ${new Date(status.workerIdleSeconds.observedAt).toISOString()}`
75104
- ].join(", ");
75105
- lines.push(` worker: ${worker}`);
75106
- if (status.pending.length === 0) {
75107
- lines.push(" pending: none");
75108
- } else {
75109
- lines.push(" pending:");
75110
- for (const pending of status.pending) {
75111
- lines.push(...formatRcPendingList([pending]).map((line) => ` ${line}`));
75112
- }
75366
+ if (version2 === IP_VERSION_6) {
75367
+ const lower = address.toLowerCase();
75368
+ const mapped = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/.exec(lower);
75369
+ if (mapped !== null) {
75370
+ return isPrivateAddress(mapped[1] ?? "");
75371
+ }
75372
+ if (lower === "::" || lower === "::1") {
75373
+ return true;
75374
+ }
75375
+ const firstGroup = Number.parseInt(/^([0-9a-f]+):/.exec(lower)?.[1] ?? "0", 16);
75376
+ if (Number.isNaN(firstGroup)) {
75377
+ return true;
75378
+ }
75379
+ if (firstGroup >= V6_UNIQUE_LOCAL_FROM && firstGroup <= V6_UNIQUE_LOCAL_TO) {
75380
+ return true;
75381
+ }
75382
+ if (firstGroup >= V6_LINK_LOCAL_FROM && firstGroup <= V6_LINK_LOCAL_TO) {
75383
+ return true;
75113
75384
  }
75385
+ return firstGroup >= V6_MULTICAST_FROM;
75114
75386
  }
75115
- return lines;
75387
+ return true;
75116
75388
  }
75117
- function formatRcPendingList(pending) {
75118
- if (pending.length === 0) {
75119
- return ["no pending control requests observed"];
75120
- }
75121
- return pending.map((request7) => `${request7.sessionId} ${request7.requestId} ${request7.type}${request7.summary === "" ? "" : ` ${request7.summary}`} observed ${new Date(request7.observedAt).toISOString()}`);
75389
+ function isSuccessful3(status) {
75390
+ return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE3;
75122
75391
  }
75123
- function eventExcerpt(text2) {
75124
- return text2.length <= RC_PENDING_SUMMARY_EXCERPT_CHARS ? text2 : `${text2.slice(0, RC_PENDING_SUMMARY_EXCERPT_CHARS)}...`;
75392
+ function isRedirect(status) {
75393
+ return status === HTTP_MOVED_PERMANENTLY || status === HTTP_FOUND || status === HTTP_SEE_OTHER || status === HTTP_TEMPORARY_REDIRECT || status === HTTP_PERMANENT_REDIRECT;
75125
75394
  }
75126
- var PERCENT_SCALE = 100;
75127
- var MS_PER_SECOND8 = 1e3;
75128
- function percentOf(fraction) {
75129
- return `${String(Math.round(fraction * PERCENT_SCALE))}%`;
75395
+ function readableContentType(contentType) {
75396
+ if (contentType === void 0 || contentType.trim() === "") {
75397
+ return true;
75398
+ }
75399
+ const normalised = contentType.split(";")[0]?.trim().toLowerCase() ?? "";
75400
+ const slash = normalised.indexOf("/");
75401
+ const main2 = slash === -1 ? normalised : normalised.slice(0, slash);
75402
+ const sub = slash === -1 ? "" : normalised.slice(slash + 1);
75403
+ if (main2 === "text") {
75404
+ return true;
75405
+ }
75406
+ return /json|xml|javascript|ecmascript|yaml|csv/.test(sub);
75130
75407
  }
75131
- function resetInstantOf(epochSeconds) {
75132
- return new Date(epochSeconds * MS_PER_SECOND8).toISOString();
75408
+ async function readCapped(chunks) {
75409
+ const collected = [];
75410
+ let total = 0;
75411
+ for await (const chunk of chunks) {
75412
+ total += chunk.byteLength;
75413
+ if (total > RC_WEB_FETCH_MAX_BYTES) {
75414
+ return { tooLarge: true, text: "" };
75415
+ }
75416
+ collected.push(Buffer.from(chunk));
75417
+ }
75418
+ return { tooLarge: false, text: Buffer.concat(collected).toString("utf8") };
75133
75419
  }
75134
- function formatRcRateLimitInfo(info) {
75135
- const parts = [info.status];
75136
- if (info.rateLimitType !== void 0) {
75137
- parts.push(info.rateLimitType);
75420
+ async function checkTarget(url2, deps) {
75421
+ if (url2.protocol !== "http:" && url2.protocol !== "https:") {
75422
+ return refused("scheme", `the web-fetch proxy dials http and https only, not ${url2.protocol}`);
75138
75423
  }
75139
- if (info.utilization !== void 0) {
75140
- parts.push(percentOf(info.utilization));
75424
+ if (deps.allowPrivate) {
75425
+ return void 0;
75141
75426
  }
75142
- if (info.resetsAt !== void 0) {
75143
- parts.push(`resets ${resetInstantOf(info.resetsAt)}`);
75427
+ const hostname3 = url2.hostname.replace(/^\[|\]$/g, "");
75428
+ let addresses;
75429
+ try {
75430
+ addresses = net3.isIP(hostname3) === 0 ? await deps.lookup(hostname3) : [hostname3];
75431
+ } catch {
75432
+ return refused("resolve", `${hostname3} did not resolve`);
75144
75433
  }
75145
- const windows = [];
75146
- for (const name of ["five_hour", "seven_day", "seven_day_overage_included"]) {
75147
- const window = info.unifiedWindows?.[name];
75148
- if (window === void 0) {
75149
- continue;
75150
- }
75151
- const bits = [];
75152
- if (window.utilization !== void 0) {
75153
- bits.push(percentOf(window.utilization));
75154
- }
75155
- if (window.resetsAt !== void 0) {
75156
- bits.push(`resets ${resetInstantOf(window.resetsAt)}`);
75434
+ for (const address of addresses) {
75435
+ if (isPrivateAddress(address)) {
75436
+ return refused("private_address", `${hostname3} resolves to ${address}, a private address this web-fetch proxy refuses; set ${RC_WEB_FETCH_ALLOW_PRIVATE_ENV}=1 on the door if serving internal targets is intended`);
75157
75437
  }
75158
- windows.push(bits.length === 0 ? name : `${name} ${bits.join(" ")}`);
75159
75438
  }
75160
- if (windows.length > 0) {
75161
- parts.push(`windows ${windows.join(", ")}`);
75439
+ return void 0;
75440
+ }
75441
+ async function fetchThroughWebProxy(url2, deps) {
75442
+ let current;
75443
+ try {
75444
+ current = new URL(url2);
75445
+ } catch {
75446
+ return refused("malformed_url", `${url2} is not a URL this proxy can dial`);
75162
75447
  }
75163
- if (info.isUsingOverage === true) {
75164
- parts.push("overage in use");
75165
- } else if (info.overageStatus !== void 0) {
75166
- parts.push(`overage ${info.overageStatus}`);
75448
+ const controller = new AbortController();
75449
+ const timer = setTimeout(() => {
75450
+ controller.abort();
75451
+ }, deps.deadlineMs);
75452
+ timer.unref();
75453
+ try {
75454
+ let redirects = 0;
75455
+ for (; ; ) {
75456
+ const refusal = await checkTarget(current, deps);
75457
+ if (refusal !== void 0) {
75458
+ return refusal;
75459
+ }
75460
+ let hop;
75461
+ try {
75462
+ hop = await deps.dial(current, controller.signal);
75463
+ } catch (error62) {
75464
+ if (controller.signal.aborted) {
75465
+ return refused("deadline", `the fetch of ${url2} did not settle within its deadline`);
75466
+ }
75467
+ return refused("transport", `the dial of ${current.toString()} failed: ${error62 instanceof Error ? error62.message : "unknown error"}`);
75468
+ }
75469
+ if (isRedirect(hop.status) && hop.location !== void 0) {
75470
+ if (redirects >= RC_WEB_FETCH_MAX_REDIRECTS) {
75471
+ controller.abort();
75472
+ return refused("too_many_redirects", `${url2} followed more than ${String(RC_WEB_FETCH_MAX_REDIRECTS)} redirects without settling`);
75473
+ }
75474
+ let next;
75475
+ try {
75476
+ next = new URL(hop.location, current);
75477
+ } catch {
75478
+ controller.abort();
75479
+ return refused("malformed_url", `a redirect of ${current.toString()} named ${hop.location}, which is not a URL this proxy can dial`);
75480
+ }
75481
+ const drained = await readCapped(hop.chunks);
75482
+ if (drained.tooLarge) {
75483
+ controller.abort();
75484
+ return refused("too_large", `the body of a redirect from ${url2} exceeds the ${String(RC_WEB_FETCH_MAX_BYTES)} byte cap the CLI's proxy client reads`);
75485
+ }
75486
+ redirects += 1;
75487
+ current = next;
75488
+ continue;
75489
+ }
75490
+ if (!isSuccessful3(hop.status)) {
75491
+ controller.abort();
75492
+ return refused("status", `the target answered HTTP ${String(hop.status)} for ${current.toString()}`);
75493
+ }
75494
+ const contentType = hop.contentType ?? "";
75495
+ if (!readableContentType(contentType)) {
75496
+ controller.abort();
75497
+ return refused("content_type", `the target at ${current.toString()} served a ${contentType} answer, which the proxy's text field cannot carry`);
75498
+ }
75499
+ const read = await readCapped(hop.chunks);
75500
+ if (read.tooLarge) {
75501
+ controller.abort();
75502
+ return refused("too_large", `the body of ${current.toString()} exceeds the ${String(RC_WEB_FETCH_MAX_BYTES)} byte cap the CLI's proxy client reads`);
75503
+ }
75504
+ return { kind: "fetched", url: url2, destinationUrl: current.toString(), contentType: hop.contentType, text: read.text };
75505
+ }
75506
+ } finally {
75507
+ clearTimeout(timer);
75167
75508
  }
75168
- return parts.join(" ");
75169
75509
  }
75170
- function formatRcStreamEvent(event) {
75171
- const { envelope } = event;
75172
- const rateLimit = rcRateLimitInfoOf(event);
75173
- const sketch = rateLimit !== void 0 ? ` ${formatRcRateLimitInfo(rateLimit)}` : envelope.payload === void 0 ? "" : ` ${eventExcerpt(JSON.stringify(envelope.payload))}`;
75174
- return `${event.session} ${envelope.event_type} sequence_num ${String(envelope.sequence_num)} source ${envelope.source}${sketch}`;
75510
+ function realRcWebFetch(options) {
75511
+ const plainAgent = new ExemptHttpAgent();
75512
+ const tlsAgent = new ExemptTlsAgent();
75513
+ const deps = {
75514
+ dial: async (url2, signal) => await new Promise((resolve, reject) => {
75515
+ const request7 = (url2.protocol === "https:" ? https5 : http6).request(
75516
+ {
75517
+ hostname: url2.hostname,
75518
+ port: url2.port === "" ? void 0 : Number(url2.port),
75519
+ method: "GET",
75520
+ path: `${url2.pathname}${url2.search}`,
75521
+ headers: { accept: "*/*", "accept-encoding": "identity", "user-agent": WEB_FETCH_USER_AGENT },
75522
+ agent: url2.protocol === "https:" ? tlsAgent : plainAgent,
75523
+ signal
75524
+ },
75525
+ (response) => {
75526
+ const location = response.headers.location;
75527
+ resolve({
75528
+ status: response.statusCode ?? 0,
75529
+ location: typeof location === "string" ? location : void 0,
75530
+ contentType: typeof response.headers["content-type"] === "string" ? response.headers["content-type"] : void 0,
75531
+ chunks: response
75532
+ });
75533
+ }
75534
+ );
75535
+ request7.once("error", reject);
75536
+ request7.end();
75537
+ }),
75538
+ lookup: async (hostname3) => (await dns2.promises.lookup(hostname3, { all: true })).map((entry) => entry.address),
75539
+ allowPrivate: options.allowPrivate,
75540
+ deadlineMs: RC_WEB_FETCH_DEADLINE_MS
75541
+ };
75542
+ return async (url2) => await fetchThroughWebProxy(url2, deps);
75175
75543
  }
75176
- function formatRcSelfHostMint(result) {
75177
- return [
75178
- `identity: ${result.identity} (organisation ${result.organizationUuid})`,
75179
- `credential: ${result.credentialsFile}${result.replaced ? " (replacing this door's previous mint)" : ""}`,
75180
- `account block: ${result.claudeJsonFile} (oauthAccount and the feature-cache seed merged in)`,
75181
- `door record: ${result.recordFile} (the copy the door's served surface authenticates against)`,
75182
- "next: start the door with AGENT_SHIM_FRONTDOOR_RC_SELF_HOST=1 and its transparent surface pointed at the interception port, then launch this identity and switch Remote Control on; no claude.ai login is involved"
75183
- ];
75544
+
75545
+ // src/frontdoor/rcSelfHost.ts
75546
+ var RC_SELF_HOST_ENV = "AGENT_SHIM_FRONTDOOR_RC_SELF_HOST";
75547
+ function rcSelfHostFromEnv(env) {
75548
+ return env[RC_SELF_HOST_ENV] === "1";
75184
75549
  }
75185
- function frontDoorRcControlFromState(fsPort, paths) {
75186
- const state = readFrontDoorState(fsPort, paths.frontdoorStateFile);
75187
- if (state?.port === void 0) {
75188
- throw new Error("the front door is not serving: Remote Control sessions are observed only while it runs, so start a session through the door first");
75550
+ var RC_SESSIONS_PATH_PREFIX2 = "/v1/code/sessions";
75551
+ var RC_COMPAT_SESSIONS_PATH = "/v1/sessions";
75552
+ var RC_SESSION_ID_PREFIX2 = "cse_";
75553
+ var RC_SELF_HOST_API_BASE_URL = "https://api.anthropic.com";
75554
+ var HTTP_CONFLICT = 409;
75555
+ var MS_PER_SECOND9 = 1e3;
75556
+ var HTTP_SERVICE_UNAVAILABLE2 = 503;
75557
+ var RC_SELF_HOST_WORKER_JWT_TTL_SECONDS = 46800;
75558
+ var RC_SELF_HOST_KEEPALIVE_MS = 15e3;
75559
+ var RC_SELF_HOST_RETENTION_MS = 6e5;
75560
+ var RC_PRESENCE_REFRESH_SECONDS = 60;
75561
+ var RC_SELF_HOST_MAX_BATCH_EVENTS = 100;
75562
+ var RC_SELF_HOST_WORKER_EPOCH = 1;
75563
+ var RC_STREAM_EVENT_NAME = "client_event";
75564
+ var CONFLICT_REASON_HEADER2 = "x-ccr-conflict-reason";
75565
+ var CONFLICT_REASON_EPOCH_STALE = "epoch_stale";
75566
+ function isRecord10(value2) {
75567
+ return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
75568
+ }
75569
+ function isWebSearchRefusal(value2) {
75570
+ return !Array.isArray(value2);
75571
+ }
75572
+ function nonEmptyString4(record2, field) {
75573
+ const value2 = record2 === void 0 ? void 0 : record2[field];
75574
+ return typeof value2 === "string" && value2 !== "" ? value2 : void 0;
75575
+ }
75576
+ function numberOf2(record2, field) {
75577
+ const value2 = record2 === void 0 ? void 0 : record2[field];
75578
+ if (typeof value2 === "number" && Number.isFinite(value2)) {
75579
+ return value2;
75189
75580
  }
75190
- const ca = fsPort.readFileUtf8(paths.frontdoorCaCertFile);
75191
- if (ca === void 0) {
75192
- throw new Error(`the front door's CA certificate is missing at ${paths.frontdoorCaCertFile}, so its control listener cannot be authenticated`);
75581
+ if (typeof value2 === "string" && /^[0-9]+$/.test(value2)) {
75582
+ return Number(value2);
75193
75583
  }
75194
- const token = fsPort.readFileUtf8(paths.frontdoorControlTokenFile)?.trim();
75195
- if (token === void 0 || token === "") {
75196
- throw new Error(`the serving front door's control token is missing at ${paths.frontdoorControlTokenFile}`);
75584
+ return void 0;
75585
+ }
75586
+ function stringArray(record2, field) {
75587
+ const value2 = record2 === void 0 ? void 0 : record2[field];
75588
+ if (!Array.isArray(value2)) {
75589
+ return void 0;
75197
75590
  }
75198
- return frontDoorRcControl(realRcControlTransport(state.port, ca), token);
75591
+ const members2 = [];
75592
+ for (const member of value2) {
75593
+ if (typeof member !== "string") {
75594
+ return void 0;
75595
+ }
75596
+ members2.push(member);
75597
+ }
75598
+ return members2;
75199
75599
  }
75200
- function frontDoorRcApiFromState(fsPort, paths) {
75201
- const state = readFrontDoorState(fsPort, paths.frontdoorStateFile);
75202
- if (state?.port === void 0) {
75203
- throw new Error("the front door is not serving: Remote Control sessions are observed only while it runs, so start a session through the door first");
75600
+ function singleHeader2(headers, name) {
75601
+ const value2 = headers[name];
75602
+ return typeof value2 === "string" ? value2 : void 0;
75603
+ }
75604
+ function bearerOf(headers) {
75605
+ const presented = singleHeader2(headers, "authorization");
75606
+ if (presented === void 0) {
75607
+ return void 0;
75204
75608
  }
75205
- const ca = fsPort.readFileUtf8(paths.frontdoorCaCertFile);
75206
- if (ca === void 0) {
75207
- throw new Error(`the front door's CA certificate is missing at ${paths.frontdoorCaCertFile}, so its typed API cannot be authenticated`);
75609
+ return presented.slice(0, "bearer ".length).toLowerCase() === "bearer " ? presented.slice("bearer ".length) : void 0;
75610
+ }
75611
+ function presentationRefusal(headers) {
75612
+ const presented = singleHeader2(headers, "authorization");
75613
+ if (presented === void 0) {
75614
+ return "absent";
75208
75615
  }
75209
- const token = fsPort.readFileUtf8(paths.frontdoorControlTokenFile)?.trim();
75210
- if (token === void 0 || token === "") {
75211
- throw new Error(`the serving front door's control token is missing at ${paths.frontdoorControlTokenFile}`);
75616
+ if (presented.slice(0, "bearer ".length).toLowerCase() !== "bearer ") {
75617
+ return "not-a-bearer";
75212
75618
  }
75213
- return frontDoorRcApiClient(state.port, ca, token);
75619
+ const bearer = presented.slice("bearer ".length);
75620
+ return bearer.slice(0, "bearer ".length).toLowerCase() === "bearer " ? "doubled-scheme-word" : "unrecognised";
75214
75621
  }
75215
- function registerRcCommand(frontdoor, paths) {
75216
- const rc = withExamples(
75217
- frontdoor.command("rc").description("Observe the Remote Control sessions passing through the front door, watch the stream their attached client receives, send a prompt into one, answer its pending control requests, and steer one with the client half's own control requests (interrupt, model, permission mode, end-session, the usage and context queries, read-file, file-suggestions, keep-alive, and the mcp_* family)."),
75218
- ["agent-shim frontdoor rc list", "agent-shim frontdoor rc pending"]
75219
- );
75220
- withExamples(
75221
- rc.command("list").description("List the Remote Control sessions the front door has observed, with when each was created and last seen. Read-only.").option("--json", "Print the sessions as JSON.").action(async (options) => {
75222
- const sessions = await frontDoorRcControlFromState(realFarmFs, paths).listSessions();
75223
- if (options.json === true) {
75224
- printJson({ sessions });
75622
+ async function readBody3(request7, capBytes) {
75623
+ return await new Promise((resolve, reject) => {
75624
+ const chunks = [];
75625
+ let total = 0;
75626
+ request7.on("data", (chunk) => {
75627
+ total += chunk.length;
75628
+ if (total > capBytes) {
75629
+ request7.destroy();
75630
+ reject(new Error("body too large"));
75225
75631
  return;
75226
75632
  }
75227
- for (const line of formatRcSessionList(sessions)) {
75228
- console.log(line);
75633
+ chunks.push(chunk);
75634
+ });
75635
+ request7.on("end", () => {
75636
+ resolve(Buffer.concat(chunks).toString("utf8"));
75637
+ });
75638
+ request7.on("error", () => {
75639
+ reject(new Error("the request body ended early"));
75640
+ });
75641
+ });
75642
+ }
75643
+ function parseJsonObject5(body) {
75644
+ let parsed;
75645
+ try {
75646
+ parsed = JSON.parse(body);
75647
+ } catch {
75648
+ return void 0;
75649
+ }
75650
+ return isRecord10(parsed) ? parsed : void 0;
75651
+ }
75652
+ function mintWorkerJwt(randomToken, issuedAt) {
75653
+ const encode3 = (value2) => Buffer.from(JSON.stringify(value2), "utf8").toString("base64url");
75654
+ const header = encode3({ alg: "HS256", typ: "JWT" });
75655
+ const payload = encode3({ exp: Math.floor((issuedAt + RC_SELF_HOST_WORKER_JWT_TTL_SECONDS * MS_PER_SECOND9) / MS_PER_SECOND9), iat: Math.floor(issuedAt / MS_PER_SECOND9) });
75656
+ return `${header}.${payload}.${Buffer.from(randomToken(), "utf8").toString("base64url")}`;
75657
+ }
75658
+ function createRcSelfHostSurface(deps) {
75659
+ const sessions = /* @__PURE__ */ new Map();
75660
+ const conversations = /* @__PURE__ */ new Map();
75661
+ const retireSession = (session) => {
75662
+ for (const sink of [...session.conversation.workerStreams, ...session.conversation.clientStreams]) {
75663
+ if (sink.owner === session.id) {
75664
+ sink.retire();
75229
75665
  }
75230
- }),
75231
- ["agent-shim frontdoor rc list", "agent-shim frontdoor rc list --json"]
75232
- );
75233
- withExamples(
75234
- rc.command("status").description("Report each observed Remote Control session's worker state and idle from its latest observed heartbeat and registration, with its pending control requests. Read-only.").argument("[session]", "One cse_ session id, as `frontdoor rc list` shows it; every observed session when omitted.").option("--json", "Print the statuses as JSON.").action(async (session, options) => {
75235
- const statuses = await frontDoorRcControlFromState(realFarmFs, paths).statusOf(session);
75236
- if (options.json === true) {
75237
- printJson({ statuses });
75238
- return;
75666
+ }
75667
+ session.conversation.sessionIds.delete(session.id);
75668
+ sessions.delete(session.id);
75669
+ };
75670
+ const sweep = () => {
75671
+ const now = deps.now();
75672
+ for (const session of sessions.values()) {
75673
+ for (const [clientId, lastSeenAt] of session.clients) {
75674
+ if (now - lastSeenAt > RC_SELF_HOST_RETENTION_MS) {
75675
+ session.clients.delete(clientId);
75676
+ }
75239
75677
  }
75240
- for (const line of formatRcSessionStatus(statuses)) {
75241
- console.log(line);
75678
+ const holdsStream = [...session.conversation.workerStreams, ...session.conversation.clientStreams].some((sink) => sink.owner === session.id);
75679
+ if (!holdsStream && now - session.lastTrafficAt > RC_SELF_HOST_RETENTION_MS) {
75680
+ retireSession(session);
75242
75681
  }
75243
- }),
75244
- ["agent-shim frontdoor rc status", "agent-shim frontdoor rc status cse_00000000-0000-4000-8000-000000000000 --json"]
75245
- );
75246
- withExamples(
75247
- rc.command("pending").description("List the control requests (tool and plan approvals above all) the front door has observed and that are still awaiting an answer. Read-only.").argument("[session]", "One cse_ session id, as `frontdoor rc list` shows it; every observed session when omitted.").option("--json", "Print the pending requests as JSON.").action(async (session, options) => {
75248
- const pending = await frontDoorRcControlFromState(realFarmFs, paths).pendingOf(session);
75249
- if (options.json === true) {
75250
- printJson({ pending });
75682
+ }
75683
+ for (const conversation of conversations.values()) {
75684
+ while (conversation.events[0] !== void 0 && now - conversation.events[0].storedAt > RC_SELF_HOST_RETENTION_MS) {
75685
+ const dropped = conversation.events.shift();
75686
+ const uuid3 = dropped === void 0 ? void 0 : nonEmptyString4(dropped.payload, "uuid");
75687
+ if (uuid3 !== void 0) {
75688
+ conversation.seenPayloadUuids.delete(uuid3);
75689
+ }
75690
+ }
75691
+ for (const [clientId, lastSeenAt] of conversation.clients) {
75692
+ if (now - lastSeenAt > RC_SELF_HOST_RETENTION_MS) {
75693
+ conversation.clients.delete(clientId);
75694
+ }
75695
+ }
75696
+ if (conversation.sessionIds.size === 0 && conversation.workerStreams.size === 0 && conversation.clientStreams.size === 0 && now - conversation.lastTrafficAt > RC_SELF_HOST_RETENTION_MS) {
75697
+ conversations.delete(conversation.id);
75698
+ }
75699
+ }
75700
+ };
75701
+ const timer = setInterval(sweep, RC_SELF_HOST_RETENTION_MS);
75702
+ timer.unref();
75703
+ const keepalive = setInterval(() => {
75704
+ for (const conversation of conversations.values()) {
75705
+ for (const sink of [...conversation.workerStreams, ...conversation.clientStreams]) {
75706
+ sink.write(": keep-alive\n\n");
75707
+ }
75708
+ }
75709
+ }, RC_SELF_HOST_KEEPALIVE_MS);
75710
+ keepalive.unref();
75711
+ const presentsCredential = (headers) => {
75712
+ const expected = deps.credentialRecord()?.accessToken;
75713
+ const presented = bearerOf(headers);
75714
+ return expected !== void 0 && presented !== void 0 && isLiveCapability(presented, [expected]);
75715
+ };
75716
+ const presentsWorkerJwt = (headers, session) => {
75717
+ const presented = bearerOf(headers);
75718
+ if (presented === void 0) {
75719
+ return false;
75720
+ }
75721
+ for (const minted of session.workerJwts) {
75722
+ if (isLiveCapability(presented, [minted])) {
75723
+ return true;
75724
+ }
75725
+ }
75726
+ return false;
75727
+ };
75728
+ const credentialRefusal = (headers) => presentsCredential(headers) ? void 0 : presentationRefusal(headers);
75729
+ const workerRefusal = (headers, session) => presentsWorkerJwt(headers, session) ? void 0 : presentationRefusal(headers);
75730
+ const workerOrCredentialRefusal = (headers, session) => presentsWorkerJwt(headers, session) || presentsCredential(headers) ? void 0 : presentationRefusal(headers);
75731
+ const frameOf = (event) => {
75732
+ const envelope = {
75733
+ event_id: event.eventId,
75734
+ event_type: nonEmptyString4(event.payload, "type") ?? "event",
75735
+ sequence_num: event.sequenceNum,
75736
+ source: event.source,
75737
+ payload: event.payload,
75738
+ created_at: event.createdAt
75739
+ };
75740
+ return `event: ${RC_STREAM_EVENT_NAME}
75741
+ id: ${String(event.sequenceNum)}
75742
+ data: ${JSON.stringify(envelope)}
75743
+
75744
+ `;
75745
+ };
75746
+ const reachesWorkerStream = (event, owner) => !(event.writer.half === "worker" && event.writer.session === owner);
75747
+ const holdStream = async (conversation, owner, which, wantsReplay, request7, response) => {
75748
+ const url2 = new URL(request7.url, "http://127.0.0.1");
75749
+ const cursor3 = numberOf2({ value: url2.searchParams.get("from_sequence_num") }, "value") ?? numberOf2({ value: singleHeader2(request7.headers, "last-event-id") }, "value");
75750
+ const replay = cursor3 === void 0 ? [] : conversation.events.filter((event) => wantsReplay(event) && event.sequenceNum > cursor3);
75751
+ response.start(HTTP_STATUS.ok, { "content-type": "text/event-stream", "cache-control": "no-cache" });
75752
+ response.flush();
75753
+ let pending = Promise.resolve();
75754
+ const sink = {
75755
+ owner,
75756
+ write: (frame) => {
75757
+ pending = pending.then(async () => {
75758
+ await response.write(frame);
75759
+ }).catch(() => {
75760
+ });
75761
+ },
75762
+ retire: () => {
75763
+ conversation[which].delete(sink);
75764
+ }
75765
+ };
75766
+ conversation[which].add(sink);
75767
+ conversation.lastTrafficAt = deps.now();
75768
+ for (const event of replay) {
75769
+ sink.write(frameOf(event));
75770
+ }
75771
+ await new Promise((resolve) => {
75772
+ request7.signal.addEventListener("abort", () => {
75773
+ resolve(void 0);
75774
+ }, { once: true });
75775
+ });
75776
+ sink.retire();
75777
+ response.end();
75778
+ };
75779
+ const publish = (conversation, writer, source, payload) => {
75780
+ const uuid3 = nonEmptyString4(payload, "uuid");
75781
+ const duplicate = uuid3 !== void 0 && conversation.seenPayloadUuids.has(uuid3);
75782
+ conversation.sequenceNum += 1;
75783
+ const event = {
75784
+ sequenceNum: conversation.sequenceNum,
75785
+ eventId: uuid3 ?? deps.newUuid(),
75786
+ source,
75787
+ // 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.
75788
+ payload: source === RC_SOURCE_WORKER ? payload : { ...payload, server_received_wall_ms: deps.now() },
75789
+ createdAt: new Date(deps.now()).toISOString(),
75790
+ writer,
75791
+ storedAt: deps.now()
75792
+ };
75793
+ conversation.events.push(event);
75794
+ if (uuid3 !== void 0) {
75795
+ conversation.seenPayloadUuids.add(uuid3);
75796
+ }
75797
+ conversation.lastTrafficAt = event.storedAt;
75798
+ const frame = frameOf(event);
75799
+ for (const sink of [...conversation.workerStreams]) {
75800
+ if (reachesWorkerStream(event, sink.owner)) {
75801
+ sink.write(frame);
75802
+ }
75803
+ }
75804
+ for (const sink of [...conversation.clientStreams]) {
75805
+ sink.write(frame);
75806
+ }
75807
+ return { sequenceNum: event.sequenceNum, eventId: event.eventId, duplicate };
75808
+ };
75809
+ const sessionRow = (session) => ({ id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), status: "active", conversation_id: session.conversation.id });
75810
+ const answerJson2 = async (response, status, body, extraHeaders = {}) => {
75811
+ const text2 = JSON.stringify(body);
75812
+ response.start(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text2, "utf8")), ...extraHeaders });
75813
+ await response.write(text2);
75814
+ response.end();
75815
+ };
75816
+ const noCredential = async (response) => {
75817
+ await answerJson2(response, HTTP_SERVICE_UNAVAILABLE2, { type: "error", error: { type: "api_error", message: "the front door's self-hosted Remote Control surface is on but no credential has been minted for it; run agent-shim frontdoor rc selfhost mint first" } });
75818
+ };
75819
+ const epochStale = async (response) => {
75820
+ await answerJson2(response, HTTP_CONFLICT, { type: "error", error: { type: "api_error", message: "worker_epoch does not match the session's live epoch" } }, { [CONFLICT_REASON_HEADER2]: CONFLICT_REASON_EPOCH_STALE });
75821
+ };
75822
+ const refusalMessages = {
75823
+ absent: "the self-hosted Remote Control surface authenticates by the Authorization header, and this call carried none",
75824
+ "not-a-bearer": "the self-hosted Remote Control surface reads a Bearer authorization, and this call's Authorization header did not carry the Bearer scheme word",
75825
+ "doubled-scheme-word": "the self-hosted Remote Control surface reads one Bearer scheme word, and this call's Authorization header carried it twice: a persisted session record holds the whole header value with the scheme word included, so prefixing Bearer onto it presents a different credential",
75826
+ unrecognised: "the self-hosted Remote Control surface did not recognise the presented credential"
75827
+ };
75828
+ const unauthorized = async (response, refusal) => {
75829
+ await answerJson2(response, HTTP_STATUS.unauthorized, { type: "error", error: { type: "authentication_error", message: refusalMessages[refusal] } });
75830
+ };
75831
+ const serveSessionPath = async (session, tail, request7, response) => {
75832
+ const method = request7.method.toUpperCase();
75833
+ if (session === void 0) {
75834
+ await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: "no such Remote Control session on this door" } });
75835
+ return;
75836
+ }
75837
+ session.lastTrafficAt = deps.now();
75838
+ if (tail === "") {
75839
+ if (method === "PATCH" || method === "PUT") {
75840
+ const refusal = credentialRefusal(request7.headers);
75841
+ if (refusal !== void 0) {
75842
+ await unauthorized(response, refusal);
75843
+ return;
75844
+ }
75845
+ const body = parseJsonObject5(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
75846
+ const title = nonEmptyString4(body, "title");
75847
+ if (title === void 0) {
75848
+ await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a session update is JSON naming a non-empty title" } });
75849
+ return;
75850
+ }
75851
+ session.title = title;
75852
+ session.lastTrafficAt = deps.now();
75853
+ deps.log?.(`rc selfhost ${session.id}: retitled at its client's request`);
75854
+ await answerJson2(response, HTTP_STATUS.ok, {});
75251
75855
  return;
75252
75856
  }
75253
- for (const line of formatRcPendingList(pending)) {
75254
- console.log(line);
75255
- }
75256
- }),
75257
- ["agent-shim frontdoor rc pending", "agent-shim frontdoor rc pending --json"]
75258
- );
75259
- withExamples(
75260
- rc.command("send").description("Send a text prompt into one observed Remote Control session, arriving as a message from an attached client would.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").requiredOption("--text <text>", "The prompt text to deliver.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
75261
- const result = await frontDoorRcControlFromState(realFarmFs, paths).sendPrompt(options.session, options.text);
75262
- if (!result.ok) {
75263
- throw new Error(result.message);
75264
- }
75265
- if (options.json === true) {
75266
- printJson({ session: options.session, sequenceNums: result.sequenceNums });
75857
+ if (method !== "GET") {
75858
+ 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" } });
75267
75859
  return;
75268
75860
  }
75269
- console.log(`sent to ${options.session}: sequence_num ${result.sequenceNums.join(", ")}`);
75270
- }),
75271
- ['agent-shim frontdoor rc send --session cse_00000000-0000-4000-8000-000000000000 --text "run the tests"']
75272
- );
75273
- withExamples(
75274
- rc.command("answer").description("Answer one pending control request on an observed session, approving or denying it as an attached client's approval would.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").requiredOption("--request <id>", "The control request's id, as `frontdoor rc pending` shows it.").option("--approve", "Approve the request.").option("--deny", "Deny the request.").option("--text <text>", "The denial message the worker sees; applies to --deny only.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
75275
- if (options.approve === options.deny) {
75276
- throw new UsageError("answer exactly one way: pass --approve or --deny, not both and not neither");
75277
- }
75278
- if (options.approve === true && options.text !== void 0) {
75279
- throw new UsageError("--text is the denial message; an approval carries no text (the protocol's allow result has no message field)");
75861
+ await answerJson2(response, HTTP_STATUS.ok, { session: sessionRow(session) });
75862
+ return;
75863
+ }
75864
+ if (tail === "bridge" && method === "POST") {
75865
+ const refusal = credentialRefusal(request7.headers);
75866
+ if (refusal !== void 0) {
75867
+ await unauthorized(response, refusal);
75868
+ return;
75280
75869
  }
75281
- const result = await frontDoorRcControlFromState(realFarmFs, paths).answerRequest(options.session, options.request, { approve: options.approve === true, message: options.deny === true ? options.text : void 0 });
75282
- if (!result.ok) {
75283
- throw new Error(result.message);
75870
+ const workerJwt = mintWorkerJwt(deps.randomToken, deps.now());
75871
+ session.workerJwts.add(workerJwt);
75872
+ deps.log?.(`rc selfhost ${session.id}: bridge registration minted a worker credential`);
75873
+ await answerJson2(response, HTTP_STATUS.ok, { worker_jwt: workerJwt, expires_in: RC_SELF_HOST_WORKER_JWT_TTL_SECONDS, api_base_url: RC_SELF_HOST_API_BASE_URL, worker_epoch: RC_SELF_HOST_WORKER_EPOCH });
75874
+ return;
75875
+ }
75876
+ if (tail === "archive" && method === "POST") {
75877
+ const refusal = credentialRefusal(request7.headers);
75878
+ if (refusal !== void 0) {
75879
+ await unauthorized(response, refusal);
75880
+ return;
75284
75881
  }
75285
- if (options.json === true) {
75286
- printJson({ session: options.session, request: options.request, sequenceNums: result.sequenceNums });
75882
+ retireSession(session);
75883
+ deps.log?.(`rc selfhost ${session.id}: archived at its client's request`);
75884
+ await answerJson2(response, HTTP_STATUS.ok, {});
75885
+ return;
75886
+ }
75887
+ if (tail === "worker" || tail.startsWith("worker/")) {
75888
+ const workerTail = tail === "worker" ? "" : tail.slice("worker/".length);
75889
+ const webProxy = workerTail === "web-fetch" || workerTail === "web-search";
75890
+ const refusal = webProxy ? workerOrCredentialRefusal(request7.headers, session) : workerRefusal(request7.headers, session);
75891
+ if (refusal !== void 0) {
75892
+ await unauthorized(response, refusal);
75287
75893
  return;
75288
75894
  }
75289
- console.log(`${options.approve === true ? "approved" : "denied"} ${options.request} on ${options.session}: sequence_num ${result.sequenceNums.join(", ")}`);
75290
- }),
75291
- [
75292
- "agent-shim frontdoor rc answer --session cse_00000000-0000-4000-8000-000000000000 --request req_00000000-0000-4000-8000-000000000000 --approve",
75293
- 'agent-shim frontdoor rc answer --session cse_00000000-0000-4000-8000-000000000000 --request req_00000000-0000-4000-8000-000000000000 --deny --text "not today"'
75294
- ]
75295
- );
75296
- withExamples(
75297
- rc.command("interrupt").description("Interrupt one observed Remote Control session's running turn, as an attached client's stop button would. Queued commands survive the interrupt.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
75298
- reportControlWrite("interrupted", options.session, await frontDoorRcControlFromState(realFarmFs, paths).interruptSession(options.session), options.json);
75299
- }),
75300
- ["agent-shim frontdoor rc interrupt --session cse_00000000-0000-4000-8000-000000000000"]
75301
- );
75302
- withExamples(
75303
- rc.command("set-model").description("Set the model one observed Remote Control session's subsequent turns use, as an attached client's model picker would.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").requiredOption("--model <model>", "The model id to switch the session to.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
75304
- reportControlWrite(`set model ${options.model}`, options.session, await frontDoorRcControlFromState(realFarmFs, paths).setModel(options.session, options.model), options.json, { model: options.model });
75305
- }),
75306
- ["agent-shim frontdoor rc set-model --session cse_00000000-0000-4000-8000-000000000000 --model claude-opus-5-5"]
75307
- );
75308
- withExamples(
75309
- rc.command("set-permission-mode").description("Set one observed Remote Control session's permission mode, as an attached client's mode switcher would.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").requiredOption("--mode <mode>", `The permission mode to set; one of the SDK's own modes (${RC_PERMISSION_MODES.join(", ")}).`).option("--json", "Print the delivery result as JSON.").action(async (options) => {
75310
- if (!isRcPermissionMode(options.mode)) {
75311
- throw new UsageError(`--mode must be one of the SDK's own permission modes (${RC_PERMISSION_MODES.join(", ")}), not "${options.mode}"`);
75895
+ if (workerTail === "" && method === "GET") {
75896
+ await answerJson2(response, HTTP_STATUS.ok, { worker: { external_metadata: session.externalMetadata ?? null } });
75897
+ return;
75312
75898
  }
75313
- reportControlWrite(`set permission mode ${options.mode}`, options.session, await frontDoorRcControlFromState(realFarmFs, paths).setPermissionMode(options.session, options.mode), options.json, { mode: options.mode });
75314
- }),
75315
- [
75316
- "agent-shim frontdoor rc set-permission-mode --session cse_00000000-0000-4000-8000-000000000000 --mode plan",
75317
- "agent-shim frontdoor rc set-permission-mode --session cse_00000000-0000-4000-8000-000000000000 --mode acceptEdits"
75318
- ]
75319
- );
75320
- withExamples(
75321
- rc.command("end-session").description("End one observed Remote Control session, as an attached client closing it would: the worker aborts its turn and shuts down on receipt of the request.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").option("--reason <text>", "The reason the worker's own log names; omitted means the protocol's unspecified form.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
75322
- reportControlWrite("ended", options.session, await frontDoorRcControlFromState(realFarmFs, paths).endSession(options.session, options.reason), options.json);
75323
- }),
75324
- ["agent-shim frontdoor rc end-session --session cse_00000000-0000-4000-8000-000000000000 --reason done for today"]
75325
- );
75326
- withExamples(
75327
- rc.command("get-usage").description("Ask one observed session's worker for its structured usage (session totals plus the plan's rate limits). The worker's answer arrives on `frontdoor rc watch` as the control_response echoing the printed request id.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").option("--skip-behaviors", "Skip the worker's local-transcript scan that fills the answer's behaviours section.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
75328
- reportControlWrite("requested usage", options.session, await frontDoorRcControlFromState(realFarmFs, paths).getUsage(options.session, options.skipBehaviors === true ? true : void 0), options.json);
75329
- }),
75330
- ["agent-shim frontdoor rc get-usage --session cse_00000000-0000-4000-8000-000000000000 --skip-behaviors"]
75331
- );
75332
- withExamples(
75333
- rc.command("get-context-usage").description("Ask one observed session's worker for its context-window breakdown by category. The worker's answer arrives on `frontdoor rc watch` as the control_response echoing the printed request id.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").option("--detail <level>", `The detail level; one of the SDK's own levels (${RC_CONTEXT_USAGE_DETAILS.join(", ")}); the worker defaults to full.`).option("--json", "Print the delivery result as JSON.").action(async (options) => {
75334
- if (options.detail !== void 0 && !isRcContextUsageDetail(options.detail)) {
75335
- throw new UsageError(`--detail must be one of the SDK's own context-usage levels (${RC_CONTEXT_USAGE_DETAILS.join(", ")}), not "${options.detail}"`);
75899
+ if (workerTail === "" && method === "PUT") {
75900
+ const body = parseJsonObject5(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
75901
+ const claimedEpoch = numberOf2(body, "worker_epoch");
75902
+ if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
75903
+ await epochStale(response);
75904
+ return;
75905
+ }
75906
+ const status = nonEmptyString4(body, "worker_status");
75907
+ if (status !== void 0) {
75908
+ session.workerStatus = status;
75909
+ }
75910
+ if (isRecord10(body?.external_metadata)) {
75911
+ session.externalMetadata = body.external_metadata;
75912
+ }
75913
+ await answerJson2(response, HTTP_STATUS.ok, {});
75914
+ return;
75336
75915
  }
75337
- reportControlWrite("requested context usage", options.session, await frontDoorRcControlFromState(realFarmFs, paths).getContextUsage(options.session, options.detail), options.json);
75338
- }),
75339
- ["agent-shim frontdoor rc get-context-usage --session cse_00000000-0000-4000-8000-000000000000 --detail summary"]
75340
- );
75341
- withExamples(
75342
- rc.command("read-file").description("Ask one observed session's worker to read one file from the session filesystem, gated by its own read-permission rules. The worker's answer arrives on `frontdoor rc watch` as the control_response echoing the printed request id.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").requiredOption("--path <path>", "The file's path, as the worker resolves it against the session's cwd.").option("--max-bytes <n>", "The byte cap on the read, a positive whole number.").option("--encoding <encoding>", `How the answer encodes the contents; one of the SDK's own encodings (${RC_READ_FILE_ENCODINGS.join(", ")}).`).option("--json", "Print the delivery result as JSON.").action(async (options) => {
75343
- if (options.maxBytes !== void 0 && !/^[1-9][0-9]*$/.test(options.maxBytes)) {
75344
- throw new UsageError(`--max-bytes must be a positive whole number of bytes, not "${options.maxBytes}"`);
75916
+ if (workerTail === "register" && method === "POST") {
75917
+ await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
75918
+ await answerJson2(response, HTTP_STATUS.ok, { worker_epoch: RC_SELF_HOST_WORKER_EPOCH });
75919
+ return;
75345
75920
  }
75346
- if (options.encoding !== void 0 && !isRcReadFileEncoding(options.encoding)) {
75347
- throw new UsageError(`--encoding must be one of the SDK's own read-file encodings (${RC_READ_FILE_ENCODINGS.join(", ")}), not "${options.encoding}"`);
75921
+ if (workerTail === "heartbeat" && method === "POST") {
75922
+ await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
75923
+ await answerJson2(response, HTTP_STATUS.ok, {});
75924
+ return;
75348
75925
  }
75349
- reportControlWrite(`requested a read of ${options.path}`, options.session, await frontDoorRcControlFromState(realFarmFs, paths).readFile(options.session, options.path, { ...options.maxBytes === void 0 ? {} : { maxBytes: Number(options.maxBytes) }, ...options.encoding === void 0 ? {} : { encoding: options.encoding } }), options.json, { path: options.path });
75350
- }),
75351
- [
75352
- "agent-shim frontdoor rc read-file --session cse_00000000-0000-4000-8000-000000000000 --path src/index.ts",
75353
- "agent-shim frontdoor rc read-file --session cse_00000000-0000-4000-8000-000000000000 --path screenshot.png --encoding base64 --max-bytes 65536"
75354
- ]
75355
- );
75356
- withExamples(
75357
- rc.command("file-suggestions").description("Ask one observed session's worker for its at-mention file suggestions for a partial path prefix, the same fuzzy-matched results the TUI shows. The worker's answer arrives on `frontdoor rc watch` as the control_response echoing the printed request id.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").requiredOption("--query <prefix>", "The partial path prefix to fuzzy-match; the empty string is the root listing.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
75358
- reportControlWrite(`requested file suggestions for "${options.query}"`, options.session, await frontDoorRcControlFromState(realFarmFs, paths).fileSuggestions(options.session, options.query), options.json);
75359
- }),
75360
- ['agent-shim frontdoor rc file-suggestions --session cse_00000000-0000-4000-8000-000000000000 --query "src/frontdoor/rc"']
75361
- );
75362
- withExamples(
75363
- rc.command("keep-alive").description("Send one liveness heartbeat into an observed session: the payload every receiver ignores by its own contract, useful to keep the client half's stream warm without saying anything.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
75364
- reportControlWrite("sent a keep-alive", options.session, await frontDoorRcControlFromState(realFarmFs, paths).keepAlive(options.session), options.json);
75365
- }),
75366
- ["agent-shim frontdoor rc keep-alive --session cse_00000000-0000-4000-8000-000000000000"]
75367
- );
75368
- withExamples(
75369
- rc.command("mcp-status").description("Ask one observed session's worker for the status of its MCP server connections. The worker's answer arrives on `frontdoor rc watch` as the control_response echoing the printed request id.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
75370
- reportControlWrite("requested MCP status", options.session, await frontDoorRcControlFromState(realFarmFs, paths).mcpStatus(options.session), options.json);
75371
- }),
75372
- ["agent-shim frontdoor rc mcp-status --session cse_00000000-0000-4000-8000-000000000000"]
75373
- );
75374
- withExamples(
75375
- rc.command("mcp-reconnect").description("Ask one observed session's worker to reconnect one MCP server, named exactly as `mcp-status` reports it.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").requiredOption("--server <name>", "The MCP server's name, as `mcp-status` reports it.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
75376
- reportControlWrite(`requested a reconnect of MCP server ${options.server}`, options.session, await frontDoorRcControlFromState(realFarmFs, paths).mcpReconnect(options.session, options.server), options.json);
75377
- }),
75378
- ["agent-shim frontdoor rc mcp-reconnect --session cse_00000000-0000-4000-8000-000000000000 --server github"]
75379
- );
75380
- withExamples(
75381
- rc.command("mcp-authenticate").description("Start one MCP server's OAuth handshake on an observed session, naming the redirect URI the flow redirects back to. The worker's answer arrives on `frontdoor rc watch` as the control_response echoing the printed request id.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").requiredOption("--server <name>", "The MCP server's name, as `mcp-status` reports it.").requiredOption("--redirect-uri <uri>", "The URI the server's OAuth flow redirects back to.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
75382
- reportControlWrite(`requested authentication of MCP server ${options.server}`, options.session, await frontDoorRcControlFromState(realFarmFs, paths).mcpAuthenticate(options.session, options.server, options.redirectUri), options.json);
75383
- }),
75384
- ["agent-shim frontdoor rc mcp-authenticate --session cse_00000000-0000-4000-8000-000000000000 --server github --redirect-uri https://example.com/oauth/callback"]
75385
- );
75386
- withExamples(
75387
- rc.command("mcp-oauth-callback-url").description("Hand one MCP server's OAuth callback URL to an observed session's worker, completing the handshake `mcp-authenticate` started. The worker's answer arrives on `frontdoor rc watch` as the control_response echoing the printed request id.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").requiredOption("--server <name>", "The MCP server's name, as `mcp-status` reports it.").requiredOption("--callback-url <url>", "The callback URL the browser landed on.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
75388
- reportControlWrite(`handed MCP server ${options.server} its OAuth callback`, options.session, await frontDoorRcControlFromState(realFarmFs, paths).mcpOAuthCallbackUrl(options.session, options.server, options.callbackUrl), options.json);
75389
- }),
75390
- ["agent-shim frontdoor rc mcp-oauth-callback-url --session cse_00000000-0000-4000-8000-000000000000 --server github --callback-url https://example.com/oauth/callback?code=x"]
75391
- );
75392
- withExamples(
75393
- rc.command("teleport").description("Send one teleport marker into an observed session over the teleport-events channel, the write half the cloud UI's ultrapan path owns on the real service. The marker rides the SDK user-message shape, the line the teleport relay anchors on.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").requiredOption("--marker <text>", "The marker text, the string content the teleport relay anchors on.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
75394
- reportControlWrite(`teleported a marker into the session`, options.session, await frontDoorRcControlFromState(realFarmFs, paths).teleport(options.session, options.marker), options.json, { marker: options.marker });
75395
- }),
75396
- ['agent-shim frontdoor rc teleport --session cse_00000000-0000-4000-8000-000000000000 --marker "__ULTRAPAN_TELEPORT_LOCAL__"']
75397
- );
75398
- withExamples(
75399
- rc.command("watch").description("Print the observed sessions' client read stream events as they arrive, the approvals the door's held stream receives above all, with each rate_limit_event rendered as the account's quota state. Read-only, until Ctrl-C leaves.").argument("[session]", "One cse_ session id, as `frontdoor rc list` shows it; every observed session when omitted.").option("--json", "Print each event as one JSON line as it arrives.").action(async (session, options) => {
75400
- const client = frontDoorRcApiFromState(realFarmFs, paths);
75401
- const leave = new AbortController();
75402
- const onInterrupt = () => {
75403
- leave.abort();
75404
- };
75405
- process.once("SIGINT", onInterrupt);
75406
- try {
75407
- for await (const event of await client.rc.subscribe(session === void 0 ? {} : { session }, { signal: leave.signal })) {
75408
- if (options.json === true) {
75409
- console.log(JSON.stringify(event));
75410
- continue;
75411
- }
75412
- console.log(formatRcStreamEvent(event));
75926
+ if (workerTail === "events" && method === "POST") {
75927
+ const body = parseJsonObject5(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
75928
+ const claimedEpoch = numberOf2(body, "worker_epoch");
75929
+ if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
75930
+ await epochStale(response);
75931
+ return;
75413
75932
  }
75414
- } catch (error62) {
75415
- if (!leave.signal.aborted) {
75416
- throw error62;
75933
+ const events = body?.events;
75934
+ if (!Array.isArray(events) || events.length === 0 || events.length > RC_SELF_HOST_MAX_BATCH_EVENTS) {
75935
+ await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: `a worker event batch is a non-empty array of at most ${String(RC_SELF_HOST_MAX_BATCH_EVENTS)} events` } });
75936
+ return;
75417
75937
  }
75418
- } finally {
75419
- process.off("SIGINT", onInterrupt);
75420
- }
75421
- }),
75422
- ["agent-shim frontdoor rc watch", "agent-shim frontdoor rc watch cse_00000000-0000-4000-8000-000000000000 --json"]
75423
- );
75424
- const selfhost = withExamples(
75425
- rc.command("selfhost").description("Mint the local credential behind the door's self-hosted Remote Control surface: the opt-in mode (AGENT_SHIM_FRONTDOOR_RC_SELF_HOST=1 in the door's environment) where the door serves the CCR surface itself and a session activates Remote Control with no Anthropic credential anywhere."),
75426
- ["agent-shim frontdoor rc selfhost mint rig"]
75427
- );
75428
- withExamples(
75429
- selfhost.command("mint").description("Mint the local OAuth credential, account block and feature-cache seed one identity needs to activate Remote Control against the door's own served surface, and record the door-side copy the surface authenticates against. Refuses to replace a real claude.ai login unless --force. Never prints a token.").argument("<identity>", "The identity to mint into, as `agent-shim identity list` names it.").option("--force", "Replace an existing OAuth credential this door did not mint.").option("--json", "Print the mint result as JSON.").action((identity, options) => {
75430
- const result = mintRcSelfHostCredential({
75431
- fs: realFarmFs,
75432
- identitiesDir: paths.identitiesDir,
75433
- frontdoorDir: paths.frontdoorDir,
75434
- identity,
75435
- newUuid: import_node_crypto14.randomUUID,
75436
- randomToken: () => (0, import_node_crypto14.randomBytes)(MINTED_TOKEN_RANDOM_BYTES).toString("base64url"),
75437
- force: options.force === true,
75438
- now: () => Date.now()
75439
- });
75440
- if (options.json === true) {
75441
- printJson(result);
75938
+ const results = [];
75939
+ for (const event of events) {
75940
+ if (!isRecord10(event) || !isRecord10(event.payload)) {
75941
+ 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" } });
75942
+ return;
75943
+ }
75944
+ const published = publish(session.conversation, { session: session.id, half: "worker" }, RC_SOURCE_WORKER, event.payload);
75945
+ results.push({ event_id: published.eventId, sequence_num: String(published.sequenceNum) });
75946
+ }
75947
+ sweep();
75948
+ await answerJson2(response, HTTP_STATUS.ok, { results });
75442
75949
  return;
75443
75950
  }
75444
- for (const line of formatRcSelfHostMint(result)) {
75445
- console.log(line);
75951
+ if (workerTail === "events/stream" && method === "GET") {
75952
+ await holdStream(session.conversation, session.id, "workerStreams", (event) => reachesWorkerStream(event, session.id), request7, response);
75953
+ return;
75446
75954
  }
75447
- }),
75448
- ["agent-shim frontdoor rc selfhost mint rig", "agent-shim frontdoor rc selfhost mint rig --force"]
75449
- );
75450
- }
75451
-
75452
- // src/frontdoor/rcCredentialStore.ts
75453
- var import_node_path35 = __toESM(require("node:path"), 1);
75454
- function isSafeSessionId(sessionId) {
75455
- return /^[A-Za-z0-9_-]+$/.test(sessionId);
75456
- }
75457
- function isRecord8(value2) {
75458
- return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
75459
- }
75460
- function credentialOfJson(parsed) {
75461
- if (!isRecord8(parsed)) {
75462
- return void 0;
75463
- }
75464
- const { authorization, anthropicVersion, anthropicClientPlatform } = parsed;
75465
- if (typeof authorization !== "string" || authorization === "") {
75466
- return void 0;
75467
- }
75468
- return {
75469
- authorization,
75470
- anthropicVersion: typeof anthropicVersion === "string" ? anthropicVersion : void 0,
75471
- anthropicClientPlatform: typeof anthropicClientPlatform === "string" ? anthropicClientPlatform : void 0
75472
- };
75473
- }
75474
- function sequenceNumOfJson(value2) {
75475
- return typeof value2 === "number" && Number.isInteger(value2) && value2 >= 0 ? value2 : void 0;
75476
- }
75477
- function createRcCredentialStore(fs20, dir) {
75478
- fs20.mkdirPrivate(dir);
75479
- const fileFor = (sessionId) => import_node_path35.default.join(dir, `${sessionId}.json`);
75480
- const readParsed = (sessionId) => {
75481
- const raw = fs20.readFileUtf8(fileFor(sessionId));
75482
- if (raw === void 0) {
75483
- return void 0;
75484
- }
75485
- let parsed;
75486
- try {
75487
- parsed = JSON.parse(raw);
75488
- } catch {
75489
- return void 0;
75490
- }
75491
- return isRecord8(parsed) ? parsed : void 0;
75492
- };
75493
- return {
75494
- read: (sessionId) => {
75495
- if (!isSafeSessionId(sessionId)) {
75496
- return void 0;
75955
+ if (workerTail === "events/delivery" && method === "POST") {
75956
+ await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
75957
+ await answerJson2(response, HTTP_STATUS.ok, {});
75958
+ return;
75497
75959
  }
75498
- return credentialOfJson(readParsed(sessionId));
75499
- },
75500
- write: (sessionId, credential) => {
75501
- if (!isSafeSessionId(sessionId)) {
75960
+ if (workerTail === "internal-events" && method === "POST") {
75961
+ const body = parseJsonObject5(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
75962
+ const claimedEpoch = numberOf2(body, "worker_epoch");
75963
+ if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
75964
+ await epochStale(response);
75965
+ return;
75966
+ }
75967
+ const events = body?.events;
75968
+ if (!Array.isArray(events)) {
75969
+ await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "an internal event batch is an array" } });
75970
+ return;
75971
+ }
75972
+ for (const event of events) {
75973
+ if (isRecord10(event)) {
75974
+ session.internalEvents.push({ eventId: deps.newUuid(), body: event });
75975
+ }
75976
+ }
75977
+ await answerJson2(response, HTTP_STATUS.ok, {});
75502
75978
  return;
75503
75979
  }
75504
- const cursor3 = sequenceNumOfJson(readParsed(sessionId)?.highestSequenceNum);
75505
- const record2 = { ...credential, ...cursor3 === void 0 ? {} : { highestSequenceNum: cursor3 } };
75506
- fs20.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(record2, null, 2)}
75507
- `);
75508
- },
75509
- readCursor: (sessionId) => {
75510
- if (!isSafeSessionId(sessionId)) {
75511
- return void 0;
75980
+ if (workerTail === "internal-events" && method === "GET") {
75981
+ const query = new URL(request7.url, "http://127.0.0.1").searchParams;
75982
+ if (query.get("session_agent_id") !== null || query.get("subagents") !== null) {
75983
+ 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" } });
75984
+ return;
75985
+ }
75986
+ const page = pageOfRcChannel(request7.url, session.internalEvents, (event) => event.eventId, (event) => ({ event_id: event.eventId, payload: event.body }));
75987
+ if ("error" in page) {
75988
+ const anchorUnknown = query.get("after_event_id") !== null && page.error.startsWith("the anchor names an event id");
75989
+ await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: anchorUnknown ? "after_event_id_not_found" : "invalid_request_error", message: page.error } });
75990
+ return;
75991
+ }
75992
+ await answerJson2(response, HTTP_STATUS.ok, page);
75993
+ return;
75512
75994
  }
75513
- return sequenceNumOfJson(readParsed(sessionId)?.highestSequenceNum);
75514
- },
75515
- writeCursor: (sessionId, highestSequenceNum) => {
75516
- if (!isSafeSessionId(sessionId)) {
75995
+ if (workerTail === "diagnostics" && method === "POST") {
75996
+ await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
75997
+ await answerJson2(response, HTTP_STATUS.ok, {});
75517
75998
  return;
75518
75999
  }
75519
- const credential = credentialOfJson(readParsed(sessionId));
75520
- const record2 = credential === void 0 ? { highestSequenceNum } : { ...credential, highestSequenceNum };
75521
- fs20.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(record2, null, 2)}
75522
- `);
75523
- },
75524
- remove: (sessionId) => {
75525
- if (!isSafeSessionId(sessionId)) {
76000
+ if (workerTail === "web-fetch" && method === "POST") {
76001
+ const body = parseJsonObject5(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
76002
+ const target = nonEmptyString4(body, "url");
76003
+ if (target === void 0) {
76004
+ await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-fetch carries one url" } });
76005
+ return;
76006
+ }
76007
+ const outcome = await deps.webFetch(target);
76008
+ if (outcome.kind === "fetched") {
76009
+ const answer = { url: target, destination_url: outcome.destinationUrl, text: outcome.text, ...outcome.contentType === void 0 ? {} : { content_type: outcome.contentType } };
76010
+ if (Buffer.byteLength(JSON.stringify(answer), "utf8") > RC_WEB_FETCH_MAX_BYTES) {
76011
+ deps.log?.(`rc selfhost ${session.id}: worker web-fetch of ${target} escaped past the reader cap when framed`);
76012
+ await answerJson2(response, HTTP_STATUS.ok, { error: { error_type: "web_fetch_too_large", error_message: `the answer for ${target} exceeds the ${String(RC_WEB_FETCH_MAX_BYTES)} byte cap the CLI's proxy client reads` } });
76013
+ return;
76014
+ }
76015
+ deps.log?.(`rc selfhost ${session.id}: worker web-fetch served ${target} as ${outcome.destinationUrl}`);
76016
+ await answerJson2(response, HTTP_STATUS.ok, answer);
76017
+ return;
76018
+ }
76019
+ deps.log?.(`rc selfhost ${session.id}: worker web-fetch of ${target} refused: ${outcome.errorType}`);
76020
+ await answerJson2(response, HTTP_STATUS.ok, { error: { error_type: outcome.errorType, error_message: outcome.errorMessage } });
75526
76021
  return;
75527
76022
  }
75528
- fs20.removeRecursive(fileFor(sessionId));
75529
- }
75530
- };
75531
- }
75532
-
75533
- // src/frontdoor/rcClientPage.ts
75534
- var RC_CLIENT_PATH = "/__agent-shim/client";
75535
- var RC_CLIENT_PAGE_SCRIPT = `'use strict';
75536
- // The door's typed API as this page speaks it: the oRPC RPC protocol over plain fetch, which is one POST per procedure with a {"json": input} body and a {"json": output} answer, so no client library is needed. These helpers are plain top-level functions (not boot-scoped) so the page's tests can evaluate this script outside a browser and drive them against a mounted listener.
75537
- var API_PREFIX = '/__agent-shim/orpc';
75538
- var TOKEN_KEY = 'agent-shim-rc-token';
75539
- var token = null;
75540
-
75541
- function apiBase() { return window.location.origin + API_PREFIX; }
75542
-
75543
- // Calls one procedure: resolves the output's own value, rejects with the door's error message when it answered one.
75544
- async function rpc(path, input) {
75545
- var response = await fetch(apiBase() + '/' + path, {
75546
- method: 'POST',
75547
- headers: { 'content-type': 'application/json', authorization: 'Bearer ' + token },
75548
- body: JSON.stringify({ json: input === undefined ? {} : input }),
75549
- });
75550
- var envelope = await response.json().catch(function () { return {}; });
75551
- if (!response.ok) {
75552
- var message = envelope && envelope.json && envelope.json.message ? envelope.json.message : ('HTTP ' + response.status);
75553
- var error = new Error(message);
75554
- error.status = response.status;
75555
- throw error;
75556
- }
75557
- return envelope.json;
75558
- }
75559
-
75560
- // Parses every complete SSE frame out of the buffered text, returning the frames and the unparsed remainder. Comment frames (the door's keepalive opens with one) carry no event name and are dropped.
75561
- function parseSseFrames(buffer) {
75562
- var frames = [];
75563
- var rest = buffer;
75564
- for (;;) {
75565
- var boundary = rest.indexOf('\\n\\n');
75566
- if (boundary === -1) { break; }
75567
- var raw = rest.slice(0, boundary);
75568
- rest = rest.slice(boundary + 2);
75569
- var frame = { event: 'message', id: '', data: '' };
75570
- var lines = raw.split('\\n');
75571
- for (var i = 0; i < lines.length; i++) {
75572
- var line = lines[i];
75573
- if (line === '' || line.charAt(0) === ':') { continue; }
75574
- var colon = line.indexOf(':');
75575
- var field = colon === -1 ? line : line.slice(0, colon);
75576
- var value = colon === -1 ? '' : line.slice(colon + 1);
75577
- if (value.charAt(0) === ' ') { value = value.slice(1); }
75578
- if (field === 'event') { frame.event = value; }
75579
- if (field === 'id') { frame.id = value; }
75580
- if (field === 'data') { frame.data += (frame.data === '' ? '' : '\\n') + value; }
75581
- }
75582
- if (frame.data !== '') { frames.push(frame); }
75583
- }
75584
- return { frames: frames, rest: rest };
75585
- }
75586
-
75587
- // Whether one source-tagged event continues the per-source sequence the tracker holds: the count of events the stream dropped when it does not, zero when it does. The tracker keeps the high-water mark, so a number the door re-sends after a reconnect is a duplicate, not a step back, and never reports a negative gap. The backbone holds no replay, so a gap is reported, never hidden.
75588
- function sequenceGap(tracker, event) {
75589
- var previous = tracker[event.source];
75590
- if (event.sequence > (previous === undefined ? 0 : previous)) { tracker[event.source] = event.sequence; }
75591
- if (previous === undefined || event.sequence <= previous) { return 0; }
75592
- return event.sequence - previous - 1;
75593
- }
75594
-
75595
- function shortId(id) { return id.length > 14 ? id.slice(0, 8) + '..' + id.slice(-4) : id; }
75596
-
75597
- // A bounded sketch of any payload, the feed's fallback rendering: one JSON line, cut with a marker rather than silently.
75598
- function sketch(value, cap) {
75599
- var text;
75600
- try { text = JSON.stringify(value); } catch (e) { text = String(value); }
75601
- if (text === undefined) { text = 'undefined'; }
75602
- return text.length > cap ? text.slice(0, cap) + '...' : text;
75603
- }
75604
-
75605
- // One utilization fraction as a whole percentage, the same rounding the CLI's own watch line applies.
75606
- function percentOf(fraction) { return String(Math.round(fraction * 100)) + '%'; }
75607
-
75608
- // The reset instant a rate-limit payload states (unix epoch seconds) as an ISO instant; no countdown is derived from it, because a ticking derivation would claim a freshness the event stream never promised.
75609
- function resetInstantOf(epochSeconds) { return new Date(epochSeconds * 1000).toISOString(); }
75610
-
75611
- // Whether one rate-limit payload is readable, narrowed the same way the door's live usage state narrows it: the event type and a rate_limit_info object.
75612
- function rateLimitInfoOf(payload) {
75613
- if (payload === undefined || payload === null || typeof payload !== 'object' || payload.type !== 'rate_limit_event') { return undefined; }
75614
- var info = payload.rate_limit_info;
75615
- if (info === undefined || info === null || typeof info !== 'object' || typeof info.status !== 'string') { return undefined; }
75616
- return info;
75617
- }
75618
-
75619
- // One rate-limit payload's facts as the quota summary the feed and the header render: the limiting window's status and utilisation, each subscription window, and the overage state, every value the payload's own words.
75620
- function quotaSummary(info) {
75621
- var parts = [info.status];
75622
- if (info.rateLimitType !== undefined) { parts.push(info.rateLimitType); }
75623
- if (typeof info.utilization === 'number') { parts.push(percentOf(info.utilization)); }
75624
- if (typeof info.resetsAt === 'number') { parts.push('resets ' + resetInstantOf(info.resetsAt)); }
75625
- if (info.unifiedWindows !== undefined && info.unifiedWindows !== null && typeof info.unifiedWindows === 'object') {
75626
- var names = ['five_hour', 'seven_day', 'seven_day_overage_included'];
75627
- var windows = [];
75628
- for (var i = 0; i < names.length; i++) {
75629
- var window = info.unifiedWindows[names[i]];
75630
- if (window === undefined || window === null) { continue; }
75631
- var bits = [];
75632
- if (typeof window.utilization === 'number') { bits.push(percentOf(window.utilization)); }
75633
- if (typeof window.resetsAt === 'number') { bits.push('resets ' + resetInstantOf(window.resetsAt)); }
75634
- windows.push(bits.length === 0 ? names[i] : names[i] + ' ' + bits.join(' '));
76023
+ if (workerTail === "web-search" && method === "POST") {
76024
+ const body = parseJsonObject5(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
76025
+ const query = nonEmptyString4(body, "query");
76026
+ if (query === void 0) {
76027
+ await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-search carries one query" } });
76028
+ return;
76029
+ }
76030
+ const allowedDomains = stringArray(body, "allowed_domains");
76031
+ const blockedDomains = stringArray(body, "blocked_domains");
76032
+ if (body?.allowed_domains !== void 0 && allowedDomains === void 0 || body?.blocked_domains !== void 0 && blockedDomains === void 0) {
76033
+ 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" } });
76034
+ return;
76035
+ }
76036
+ const searchProfile = nonEmptyString4(body, "search_profile");
76037
+ const backend = deps.webSearch;
76038
+ if (backend === void 0) {
76039
+ deps.log?.(`rc selfhost ${session.id}: worker web-search of "${query}" refused: no search backend serves this door`);
76040
+ await answerJson2(response, HTTP_STATUS.ok, { results: [], error: { error_type: "web_search_unavailable", error_message: "this front door serves the Remote Control web-search proxy without a search backend, so no query can be served here" } });
76041
+ return;
76042
+ }
76043
+ const answered = await backend({ query, ...allowedDomains === void 0 ? {} : { allowedDomains }, ...blockedDomains === void 0 ? {} : { blockedDomains }, ...searchProfile === void 0 ? {} : { searchProfile } });
76044
+ deps.log?.(`rc selfhost ${session.id}: worker web-search of "${query}" answered by its backend`);
76045
+ if (isWebSearchRefusal(answered)) {
76046
+ await answerJson2(response, HTTP_STATUS.ok, { results: [], error: { error_type: answered.errorType, error_message: answered.errorMessage } });
76047
+ return;
76048
+ }
76049
+ await answerJson2(response, HTTP_STATUS.ok, { results: answered.map((result) => ({ title: result.title ?? "", url: result.url, snippet: result.snippet ?? "" })) });
76050
+ return;
76051
+ }
76052
+ await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the self-hosted Remote Control surface serves no worker path "${workerTail}"` } });
76053
+ return;
75635
76054
  }
75636
- if (windows.length > 0) { parts.push('windows ' + windows.join(', ')); }
75637
- }
75638
- if (info.isUsingOverage === true) { parts.push('overage in use'); }
75639
- else if (typeof info.overageStatus === 'string') { parts.push('overage ' + info.overageStatus); }
75640
- return parts.join(' ');
75641
- }
75642
-
75643
- // The header's quota text from the door's freshest live observation: the quota summary plus when the door observed it, or the plain statement that none has arrived yet.
75644
- function quotaText(latest) {
75645
- if (latest === undefined || latest === null) { return 'quota: not observed yet'; }
75646
- var info = rateLimitInfoOf({ type: 'rate_limit_event', rate_limit_info: latest.rateLimit });
75647
- if (info === undefined) { return 'quota: not readable'; }
75648
- return 'quota: ' + quotaSummary(info) + ' observed ' + new Date(latest.observedAt).toLocaleTimeString();
75649
- }
75650
-
75651
- // The headline text of one Remote Control stream event: a rate-limit payload renders as its quota summary, the assistant and user payloads that carry a content array of text blocks render as their prose, everything else as its type plus a sketch.
75652
- function rcLine(event) {
75653
- var envelope = event.envelope;
75654
- var payload = envelope.payload;
75655
- var headline = '';
75656
- var rateLimit = rateLimitInfoOf(payload);
75657
- if (rateLimit !== undefined) {
75658
- headline = quotaSummary(rateLimit);
75659
- } else if (payload !== undefined && payload !== null && typeof payload === 'object' && Array.isArray(payload.content)) {
75660
- var parts = [];
75661
- for (var i = 0; i < payload.content.length; i++) {
75662
- var block = payload.content[i];
75663
- if (block !== null && typeof block === 'object' && block.type === 'text' && typeof block.text === 'string') { parts.push(block.text); }
76055
+ const clientRefusal = credentialRefusal(request7.headers);
76056
+ if (clientRefusal !== void 0) {
76057
+ await unauthorized(response, clientRefusal);
76058
+ return;
75664
76059
  }
75665
- if (parts.length > 0) { headline = parts.join(' '); }
75666
- }
75667
- if (headline === '') {
75668
- headline = envelope.event_type;
75669
- if (payload !== undefined) { headline += ' ' + sketch(payload, 240); }
75670
- }
75671
- return new Date().toLocaleTimeString() + ' ' + shortId(event.session) + ' ' + envelope.event_type + ' ' + headline;
75672
- }
75673
-
75674
- function el(tag, className, text) {
75675
- var node = document.createElement(tag);
75676
- if (className) { node.className = className; }
75677
- if (text !== undefined) { node.textContent = text; }
75678
- return node;
75679
- }
75680
-
75681
- var ui = null;
75682
-
75683
- function renderEvent(event, gap) {
75684
- if (ui === null) { return; }
75685
- var list = ui.eventList;
75686
- if (gap > 0) {
75687
- list.appendChild(el('div', 'event gap', 'dropped events: ' + String(gap) + ' on source ' + event.source + ' (the stream holds no replay)'));
75688
- }
75689
- var line;
75690
- if (event.source === 'rc') {
75691
- line = rcLine(event.payload);
75692
- } else {
75693
- line = new Date().toLocaleTimeString() + ' [' + event.source + '] ' + sketch(event.payload, 240);
75694
- }
75695
- list.appendChild(el('div', 'event', line));
75696
- while (list.childElementCount > 500) { list.removeChild(list.firstChild); }
75697
- list.scrollTop = list.scrollHeight;
75698
- }
75699
-
75700
- function renderSessions(statuses) {
75701
- if (ui === null) { return; }
75702
- var list = ui.sessionList;
75703
- list.textContent = '';
75704
- if (statuses.length === 0) {
75705
- list.appendChild(el('div', 'event dim', 'no Remote Control sessions observed'));
75706
- return;
75707
- }
75708
- for (var i = 0; i < statuses.length; i++) {
75709
- var status = statuses[i];
75710
- var worker = status.workerState === undefined ? 'worker unknown' : 'worker ' + status.workerState.value;
75711
- var row = el('div', 'session' + (status.id === ui.selected ? ' selected' : ''));
75712
- row.appendChild(el('div', null, shortId(status.id)));
75713
- row.appendChild(el('div', 'dim', worker + (status.pending.length === 0 ? '' : ', ' + String(status.pending.length) + ' pending') + ', seen ' + new Date(status.lastSeenAt).toLocaleTimeString()));
75714
- row.addEventListener('click', selectSession(status.id));
75715
- list.appendChild(row);
75716
- }
75717
- }
75718
-
75719
- function selectSession(id) {
75720
- return function () {
75721
- if (ui === null) { return; }
75722
- ui.selected = ui.selected === id ? null : id;
75723
- ui.promptTarget.textContent = ui.selected === null ? 'no session selected' : 'to ' + ui.selected;
75724
- refreshSessions();
75725
- refreshPending();
75726
- };
75727
- }
75728
-
75729
- function renderPending(pending) {
75730
- if (ui === null) { return; }
75731
- var list = ui.pendingList;
75732
- list.textContent = '';
75733
- if (pending.length === 0) {
75734
- list.appendChild(el('div', 'event dim', 'no control requests awaiting an answer'));
75735
- return;
75736
- }
75737
- for (var i = 0; i < pending.length; i++) {
75738
- var request = pending[i];
75739
- var card = el('div', 'card');
75740
- card.appendChild(el('div', null, request.type + ' ' + (request.summary === '' ? shortId(request.requestId) : request.summary)));
75741
- card.appendChild(el('div', 'dim', shortId(request.sessionId) + ' ' + shortId(request.requestId) + ' observed ' + new Date(request.observedAt).toLocaleTimeString()));
75742
- var row = el('div', 'row');
75743
- var denial = el('input');
75744
- denial.type = 'text';
75745
- denial.placeholder = 'denial message (optional)';
75746
- var approve = el('button', null, 'Approve');
75747
- var deny = el('button', 'secondary', 'Deny');
75748
- approve.addEventListener('click', answerRequest(request, true, denial));
75749
- deny.addEventListener('click', answerRequest(request, false, denial));
75750
- row.appendChild(denial);
75751
- row.appendChild(approve);
75752
- row.appendChild(deny);
75753
- card.appendChild(row);
75754
- list.appendChild(card);
75755
- }
75756
- }
75757
-
75758
- function answerRequest(request, approve, denialInput) {
75759
- return function () {
75760
- var input = { session: request.sessionId, request: request.requestId, approve: approve };
75761
- if (!approve && denialInput.value !== '') { input.text = denialInput.value; }
75762
- rpc('rc/answer', input).then(function () {
75763
- renderEvent({ source: 'page', sequence: 0, payload: { note: (approve ? 'approved' : 'denied') + ' ' + request.requestId } }, 0);
75764
- refreshPending();
75765
- }, function (error) { window.alert('answer failed: ' + error.message); });
75766
- };
75767
- }
75768
-
75769
- function sendPrompt() {
75770
- if (ui === null || ui.selected === null) { window.alert('select a session first'); return; }
75771
- var text = ui.prompt.value;
75772
- if (text === '') { return; }
75773
- ui.send.disabled = true;
75774
- rpc('rc/send', { session: ui.selected, text: text }).then(function (result) {
75775
- ui.prompt.value = '';
75776
- renderEvent({ source: 'page', sequence: 0, payload: { note: 'sent, sequence_num ' + result.sequenceNums.join(', ') } }, 0);
75777
- }, function (error) { window.alert('send failed: ' + error.message); }).then(function () { ui.send.disabled = false; });
75778
- }
75779
-
75780
- function refreshSessions() {
75781
- rpc('rc/status', {}).then(function (result) { renderSessions(result.statuses); }, authOrReport('rc/status'));
75782
- }
75783
-
75784
- function refreshPending() {
75785
- rpc('rc/pending', ui !== null && ui.selected !== null ? { session: ui.selected } : {}).then(function (result) { renderPending(result.pending); }, authOrReport('rc/pending'));
75786
- }
75787
-
75788
- // The header's quota span, fed by the door's live read on the same refresh cycle as the session and pending lists: the freshest rate-limit observation any session's stream has filed, with its observation time so its freshness reads at a glance.
75789
- function refreshQuota() {
75790
- rpc('usage/live', {}).then(function (result) {
75791
- if (ui !== null) { ui.quotaState.textContent = quotaText(result.latest); }
75792
- }, authOrReport('usage/live'));
75793
- }
75794
-
75795
- // The one error path every call shares: a 401 means this token is not this generation's, so the page says so and asks for it again; anything else is reported in the feed.
75796
- function authOrReport(what) {
75797
- return function (error) {
75798
- if (error.status === 401) {
75799
- handleUnauthorized();
76060
+ if (tail === "events" && method === "POST") {
76061
+ const body = parseJsonObject5(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
76062
+ const events = body?.events;
76063
+ if (!Array.isArray(events) || events.length === 0 || events.length > RC_SELF_HOST_MAX_BATCH_EVENTS) {
76064
+ 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` } });
76065
+ return;
76066
+ }
76067
+ const results = [];
76068
+ for (const event of events) {
76069
+ if (!isRecord10(event) || !isRecord10(event.payload)) {
76070
+ 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" } });
76071
+ return;
76072
+ }
76073
+ const published = publish(session.conversation, { session: session.id, half: "client" }, RC_SOURCE_CLIENT, event.payload);
76074
+ results.push({ sequence_num: String(published.sequenceNum), duplicate: published.duplicate });
76075
+ }
76076
+ sweep();
76077
+ await answerJson2(response, HTTP_STATUS.ok, { results });
75800
76078
  return;
75801
76079
  }
75802
- renderEvent({ source: 'page', sequence: 0, payload: { note: what + ' failed: ' + error.message } }, 0);
76080
+ if (tail === "events" && method === "GET") {
76081
+ await answerJson2(response, HTTP_STATUS.ok, { data: session.conversation.events.map((event) => ({ event_id: event.eventId, event_type: nonEmptyString4(event.payload, "type") ?? "event", sequence_num: event.sequenceNum, source: event.source, payload: event.payload, created_at: event.createdAt })) });
76082
+ return;
76083
+ }
76084
+ if (tail === "events/stream" && method === "GET") {
76085
+ await holdStream(session.conversation, session.id, "clientStreams", () => true, request7, response);
76086
+ return;
76087
+ }
76088
+ if (await serveRcClientChannels({ request: request7, response, host: { id: session.id, clients: session.clients, conversation: session.conversation, writer: { session: session.id, half: "client" } }, answerJson: answerJson2, readBody: readBody3, publish, sweep, now: deps.now, presenceRefreshSeconds: RC_PRESENCE_REFRESH_SECONDS, ...deps.log === void 0 ? {} : { log: deps.log } }, tail, method, RC_SELF_HOST_MAX_BATCH_EVENTS)) {
76089
+ return;
76090
+ }
76091
+ await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the self-hosted Remote Control surface serves no path "${tail}"` } });
75803
76092
  };
75804
- }
75805
-
75806
- function handleUnauthorized() {
75807
- window.sessionStorage.removeItem(TOKEN_KEY);
75808
- if (feedAbort !== null) { feedAbort.abort(); }
75809
- streamState('');
75810
- showTokenForm('this door refused the token: it names a different door generation, paste the current one');
75811
- }
75812
-
75813
- function streamState(text) { if (ui !== null) { ui.streamState.textContent = text; } }
75814
-
75815
- var feedAbort = null;
75816
-
75817
- // Subscribes to the door's backbone and renders what it yields, reconnecting on any drop. The stream holds no replay, so a reconnect starts at the live head and the per-source sequence numbers surface whatever the gap swallowed.
75818
- async function runFeed() {
75819
- var tracker = {};
75820
- for (;;) {
75821
- if (feedAbort !== null) { feedAbort.abort(); }
75822
- feedAbort = new AbortController();
75823
- var filter = document.getElementById('source-filter').value;
75824
- var input = filter === '' ? {} : { sources: [filter] };
75825
- try {
75826
- streamState('stream: connecting');
75827
- var response = await fetch(apiBase() + '/events/subscribe', {
75828
- method: 'POST',
75829
- headers: { 'content-type': 'application/json', authorization: 'Bearer ' + token },
75830
- body: JSON.stringify({ json: input }),
75831
- signal: feedAbort.signal,
75832
- });
75833
- if (response.status === 401) { handleUnauthorized(); return; }
75834
- if (!response.ok || response.body === null) { throw new Error('HTTP ' + String(response.status)); }
75835
- streamState('stream: live');
75836
- var reader = response.body.getReader();
75837
- var decoder = new TextDecoder();
75838
- var buffer = '';
75839
- for (;;) {
75840
- var chunk = await reader.read();
75841
- if (chunk.done) { break; }
75842
- buffer += decoder.decode(chunk.value, { stream: true });
75843
- var parsed = parseSseFrames(buffer);
75844
- buffer = parsed.rest;
75845
- for (var i = 0; i < parsed.frames.length; i++) {
75846
- var frame = parsed.frames[i];
75847
- if (frame.event !== 'message') { continue; }
75848
- var event;
75849
- try { event = JSON.parse(frame.data).json; } catch (e) { continue; }
75850
- if (event === undefined || event.source === undefined) { continue; }
75851
- renderEvent(event, sequenceGap(tracker, event));
76093
+ const recordMissing = () => deps.credentialRecord() === void 0;
76094
+ const route = {
76095
+ name: "rc-selfhost",
76096
+ headroomEligible: false,
76097
+ headroomUpstream: void 0,
76098
+ serve: async (request7, response) => {
76099
+ const method = request7.method.toUpperCase();
76100
+ const pathname = new URL(request7.url, "http://127.0.0.1").pathname;
76101
+ if (recordMissing()) {
76102
+ await noCredential(response);
76103
+ return;
76104
+ }
76105
+ if (pathname === RC_COMPAT_SESSIONS_PATH || pathname.startsWith(`${RC_COMPAT_SESSIONS_PATH}/`)) {
76106
+ await serveRcCompatSessions({ request: request7, response, sessions, retireSession, answerJson: answerJson2, readBody: readBody3, credentialRefusal, unauthorized, now: deps.now, ...deps.log === void 0 ? {} : { log: deps.log } }, pathname, RC_COMPAT_SESSIONS_PATH);
76107
+ return;
76108
+ }
76109
+ if (pathname === RC_CONVERSATIONS_PATH_PREFIX || pathname.startsWith(`${RC_CONVERSATIONS_PATH_PREFIX}/`)) {
76110
+ await serveRcConversations({ request: request7, response, conversations, answerJson: answerJson2, readBody: readBody3, credentialRefusal, unauthorized, publish, holdStream, sweep, now: deps.now, presenceRefreshSeconds: RC_PRESENCE_REFRESH_SECONDS, maxBatchEvents: RC_SELF_HOST_MAX_BATCH_EVENTS, ...deps.log === void 0 ? {} : { log: deps.log } }, pathname, method);
76111
+ return;
76112
+ }
76113
+ if (pathname !== RC_SESSIONS_PATH_PREFIX2 && !pathname.startsWith(`${RC_SESSIONS_PATH_PREFIX2}/`)) {
76114
+ await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the self-hosted Remote Control surface serves no path "${pathname}"` } });
76115
+ return;
76116
+ }
76117
+ if (pathname === RC_SESSIONS_PATH_PREFIX2) {
76118
+ if (method === "POST") {
76119
+ const createRefusal = credentialRefusal(request7.headers);
76120
+ if (createRefusal !== void 0) {
76121
+ await unauthorized(response, createRefusal);
76122
+ return;
76123
+ }
76124
+ const body = parseJsonObject5(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
76125
+ const namedConversation = nonEmptyString4(body, "conversation");
76126
+ let conversation;
76127
+ if (namedConversation === void 0) {
76128
+ conversation = newRcSelfHostConversation(deps.newUuid, deps.now);
76129
+ conversations.set(conversation.id, conversation);
76130
+ } else {
76131
+ conversation = conversations.get(namedConversation);
76132
+ if (conversation === void 0) {
76133
+ await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `no such Remote Control conversation on this door: ${namedConversation}` } });
76134
+ return;
76135
+ }
76136
+ conversation.lastTrafficAt = deps.now();
76137
+ }
76138
+ const session = {
76139
+ id: `${RC_SESSION_ID_PREFIX2}${deps.newUuid()}`,
76140
+ createdAt: deps.now(),
76141
+ title: nonEmptyString4(body, "title"),
76142
+ conversation,
76143
+ workerJwts: /* @__PURE__ */ new Set(),
76144
+ internalEvents: [],
76145
+ clients: /* @__PURE__ */ new Map(),
76146
+ workerStatus: void 0,
76147
+ externalMetadata: void 0,
76148
+ lastTrafficAt: deps.now()
76149
+ };
76150
+ conversation.sessionIds.add(session.id);
76151
+ sessions.set(session.id, session);
76152
+ deps.log?.(`rc selfhost ${session.id}: session created on the door's own surface, attached to conversation ${conversation.id}${namedConversation === void 0 ? "" : " at its client's request"}`);
76153
+ await answerJson2(response, HTTP_STATUS.ok, { session: sessionRow(session) });
76154
+ return;
76155
+ }
76156
+ if (method === "GET") {
76157
+ const readRefusal = credentialRefusal(request7.headers);
76158
+ if (readRefusal !== void 0) {
76159
+ await unauthorized(response, readRefusal);
76160
+ return;
76161
+ }
76162
+ await answerJson2(response, HTTP_STATUS.ok, { data: [...sessions.values()].map((session) => ({ id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), updated_at: new Date(session.lastTrafficAt).toISOString(), session_status: "active", conversation_id: session.conversation.id })) });
76163
+ return;
75852
76164
  }
76165
+ await answerJson2(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the session collection is a POST (create) and a GET (list)" } });
76166
+ return;
75853
76167
  }
75854
- } catch (error) {
75855
- if (feedAbort !== null && feedAbort.signal.aborted) { return; }
76168
+ const rest = pathname.slice(`${RC_SESSIONS_PATH_PREFIX2}/`.length);
76169
+ const slash = rest.indexOf("/");
76170
+ const id = slash === -1 ? rest : rest.slice(0, slash);
76171
+ const tail = slash === -1 ? "" : rest.slice(slash + 1);
76172
+ if (!id.startsWith(RC_SESSION_ID_PREFIX2)) {
76173
+ await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: "the session family names one cse_-prefixed session id" } });
76174
+ return;
76175
+ }
76176
+ await serveSessionPath(sessions.get(id), tail, request7, response);
75856
76177
  }
75857
- streamState('stream: dropped, reconnecting');
75858
- await new Promise(function (resolve) { window.setTimeout(resolve, 1000); });
75859
- }
75860
- }
75861
-
75862
- function showTokenForm(message) {
75863
- if (feedAbort !== null) { feedAbort.abort(); }
75864
- if (ui !== null) { ui.main.classList.add('hidden'); }
75865
- var form = document.getElementById('token-form');
75866
- form.classList.remove('hidden');
75867
- document.getElementById('token-error').textContent = message === undefined ? '' : message;
75868
- document.getElementById('token-input').focus();
75869
- }
75870
-
75871
- function startPage(heldToken) {
75872
- token = heldToken;
75873
- ui = {
75874
- main: document.getElementById('main'),
75875
- eventList: document.getElementById('event-list'),
75876
- sessionList: document.getElementById('session-list'),
75877
- pendingList: document.getElementById('pending-list'),
75878
- prompt: document.getElementById('prompt'),
75879
- promptTarget: document.getElementById('prompt-target'),
75880
- send: document.getElementById('send'),
75881
- streamState: document.getElementById('stream-state'),
75882
- quotaState: document.getElementById('quota-state'),
75883
- selected: null,
75884
76178
  };
75885
- document.getElementById('token-form').classList.add('hidden');
75886
- ui.main.classList.remove('hidden');
75887
- document.getElementById('refresh').addEventListener('click', function () { refreshSessions(); refreshPending(); refreshQuota(); });
75888
- document.getElementById('send').addEventListener('click', sendPrompt);
75889
- document.getElementById('source-filter').addEventListener('change', function () { runFeed(); });
75890
- ui.prompt.addEventListener('keydown', function (event) {
75891
- if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) { sendPrompt(); }
75892
- });
75893
- refreshSessions();
75894
- refreshPending();
75895
- refreshQuota();
75896
- window.setInterval(function () { refreshSessions(); refreshPending(); refreshQuota(); }, 4000);
75897
- runFeed();
75898
- document.getElementById('door-state').textContent = 'connected';
75899
- }
75900
-
75901
- function boot() {
75902
- var held = null;
75903
- var query = new URLSearchParams(window.location.search);
75904
- var fromQuery = query.get('token');
75905
- if (fromQuery !== null && fromQuery !== '') {
75906
- window.sessionStorage.setItem(TOKEN_KEY, fromQuery);
75907
- // The token leaves the address bar the moment the page holds it, so a refresh or a shared screen shows the clean URL; sessionStorage keeps the tab working.
75908
- window.history.replaceState(null, '', window.location.pathname);
75909
- held = fromQuery;
75910
- } else {
75911
- held = window.sessionStorage.getItem(TOKEN_KEY);
75912
- }
75913
- document.getElementById('token-save').addEventListener('click', function () {
75914
- var value = document.getElementById('token-input').value.trim();
75915
- if (value === '') { return; }
75916
- window.sessionStorage.setItem(TOKEN_KEY, value);
75917
- startPage(value);
75918
- });
75919
- document.getElementById('token-input').addEventListener('keydown', function (event) {
75920
- if (event.key === 'Enter') { document.getElementById('token-save').click(); }
75921
- });
75922
- if (held !== null && held !== '') { startPage(held); }
75923
- }
75924
-
75925
- if (typeof document !== 'undefined' && document.getElementById('event-list') !== null) {
75926
- window.addEventListener('DOMContentLoaded', boot);
75927
- }
75928
- `;
75929
- var RC_CLIENT_PAGE_HTML = `<!doctype html>
75930
- <html lang="en">
75931
- <head>
75932
- <meta charset="utf-8">
75933
- <meta name="viewport" content="width=device-width, initial-scale=1">
75934
- <title>agent-shim Remote Control</title>
75935
- <style>
75936
- :root { color-scheme: dark; }
75937
- * { box-sizing: border-box; }
75938
- body { margin: 0; background: #101418; color: #d7dde3; font: 14px/1.45 ui-monospace, Menlo, Consolas, monospace; }
75939
- header { display: flex; gap: 12px; align-items: baseline; padding: 10px 16px; background: #171d24; border-bottom: 1px solid #2a333d; }
75940
- header h1 { font-size: 15px; margin: 0; font-weight: 600; }
75941
- header .state { color: #8b98a5; }
75942
- main { display: flex; gap: 1px; background: #2a333d; min-height: calc(100vh - 39px); }
75943
- section { background: #101418; padding: 12px 16px; overflow-y: auto; }
75944
- #sessions { flex: 0 0 320px; border-right: 1px solid #2a333d; }
75945
- #right { flex: 1; display: flex; flex-direction: column; min-width: 0; }
75946
- #feed { flex: 1; }
75947
- #controls { border-top: 1px solid #2a333d; }
75948
- h2 { font-size: 12px; text-transform: uppercase; letter-spacing: 0.08em; color: #8b98a5; margin: 8px 0 8px; font-weight: 600; }
75949
- .session { padding: 6px 8px; border: 1px solid transparent; border-radius: 4px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
75950
- .session:hover { background: #171d24; }
75951
- .session.selected { border-color: #3d6ea5; background: #17222e; }
75952
- .session .dim { color: #8b98a5; }
75953
- .event { padding: 2px 0; white-space: pre-wrap; word-break: break-word; }
75954
- .event .dim { color: #8b98a5; }
75955
- .event.gap { color: #d29922; }
75956
- .event.error { color: #e5534b; }
75957
- textarea, input[type=text], select { background: #171d24; color: #d7dde3; border: 1px solid #2a333d; border-radius: 4px; padding: 6px 8px; font: inherit; width: 100%; }
75958
- textarea { resize: vertical; min-height: 44px; }
75959
- button { background: #1f4a7a; color: #d7dde3; border: 1px solid #3d6ea5; border-radius: 4px; padding: 5px 12px; font: inherit; cursor: pointer; }
75960
- button.secondary { background: #2a333d; border-color: #3a444f; }
75961
- button:disabled { opacity: 0.5; cursor: default; }
75962
- .row { display: flex; gap: 8px; margin: 6px 0; align-items: center; }
75963
- .card { border: 1px solid #3d6ea5; border-radius: 4px; padding: 8px; margin: 6px 0; background: #17222e; }
75964
- .card .dim { color: #8b98a5; }
75965
- #token-form { max-width: 480px; margin: 48px auto; }
75966
- #token-form p { color: #8b98a5; }
75967
- .hidden { display: none !important; }
75968
- </style>
75969
- </head>
75970
- <body data-agent-shim-rc-client>
75971
- <header>
75972
- <h1>agent-shim Remote Control</h1>
75973
- <span class="state" id="door-state">connecting</span>
75974
- <span class="state" id="stream-state"></span>
75975
- <span class="state" id="quota-state">quota: not observed yet</span>
75976
- </header>
75977
- <div id="token-form">
75978
- <h2>This door's control token</h2>
75979
- <p>The page is served behind the door's per-generation control token. Paste this door generation's token (the owner-only value the door writes beside its state) to use the page; it stays in this tab only.</p>
75980
- <div class="row"><input type="text" id="token-input" placeholder="control token" autocomplete="off"><button id="token-save">Use</button></div>
75981
- <p id="token-error" class="event error"></p>
75982
- </div>
75983
- <main class="hidden" id="main">
75984
- <section id="sessions">
75985
- <h2>Sessions <button class="secondary" id="refresh">Refresh</button></h2>
75986
- <div id="session-list"></div>
75987
- </section>
75988
- <section id="right">
75989
- <div id="feed">
75990
- <h2>Feed <select id="source-filter"><option value="">all sources</option><option value="rc">Remote Control only</option></select></h2>
75991
- <div id="event-list"></div>
75992
- </div>
75993
- <div id="controls">
75994
- <h2>Pending approvals</h2>
75995
- <div id="pending-list"></div>
75996
- <h2>Send a prompt</h2>
75997
- <div class="row"><textarea id="prompt" placeholder="Prompt for the selected session"></textarea></div>
75998
- <div class="row"><span class="dim" id="prompt-target">no session selected</span><span style="flex:1"></span><button id="send">Send</button></div>
75999
- </div>
76000
- </section>
76001
- </main>
76002
- <script>
76003
- ${RC_CLIENT_PAGE_SCRIPT}
76004
- </script>
76005
- </body>
76006
- </html>
76007
- `;
76008
- function pageTokenOf(request7, url2) {
76009
- const presented = request7.headers.authorization;
76010
- const bearerPrefix = "bearer ".length;
76011
- const header = typeof presented === "string" && presented.slice(0, bearerPrefix).toLowerCase() === "bearer " ? presented.slice(bearerPrefix) : void 0;
76012
- const query = url2.searchParams.get("token") ?? void 0;
76013
- return header ?? query;
76014
- }
76015
- function serveRcClientPage(expectedToken, request7, response) {
76016
- const url2 = new URL(request7.url ?? "/", "https://door.invalid");
76017
- if (url2.pathname !== RC_CLIENT_PATH && url2.pathname !== `${RC_CLIENT_PATH}/`) {
76018
- response.writeHead(HTTP_STATUS.notFound, { "Content-Type": "text/plain" });
76019
- response.end("no such page under the door's client prefix");
76020
- return;
76021
- }
76022
- if (request7.method !== "GET" && request7.method !== "HEAD") {
76023
- response.writeHead(HTTP_STATUS.methodNotAllowed, { Allow: "GET, HEAD", "Content-Type": "text/plain" });
76024
- response.end("the door's web client page is read-only");
76025
- return;
76026
- }
76027
- const token = pageTokenOf(request7, url2);
76028
- if (token === void 0 || !isLiveCapability(token, [expectedToken])) {
76029
- response.writeHead(HTTP_STATUS.unauthorized, { "Content-Type": "application/json" });
76030
- response.end(JSON.stringify({ error: "the door's web client demands this generation's control token, as a Bearer credential or the token query parameter" }));
76031
- return;
76032
- }
76033
- response.writeHead(HTTP_STATUS.ok, {
76034
- "Content-Type": "text/html; charset=utf-8",
76035
- "Cache-Control": "no-store",
76036
- "Referrer-Policy": "no-referrer",
76037
- "Content-Security-Policy": "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'self'"
76038
- });
76039
- response.end(request7.method === "HEAD" ? void 0 : RC_CLIENT_PAGE_HTML);
76040
- }
76041
- function createRcClientPage(expectedToken) {
76179
+ const local = createRcSelfHostLocal({ now: deps.now, credentialRecord: deps.credentialRecord, readBody: readBody3 });
76042
76180
  return {
76043
- pathPrefix: RC_CLIENT_PATH,
76044
- handle: async (request7, response) => {
76045
- serveRcClientPage(expectedToken, request7, response);
76046
- await Promise.resolve();
76047
- return { matched: true };
76181
+ route,
76182
+ local,
76183
+ close: () => {
76184
+ clearInterval(timer);
76185
+ clearInterval(keepalive);
76186
+ for (const conversation of conversations.values()) {
76187
+ for (const sink of [...conversation.workerStreams, ...conversation.clientStreams]) {
76188
+ sink.retire();
76189
+ }
76190
+ }
76191
+ sessions.clear();
76192
+ conversations.clear();
76048
76193
  }
76049
76194
  };
76050
76195
  }
@@ -76111,7 +76256,7 @@ function createSseParser() {
76111
76256
  }
76112
76257
  };
76113
76258
  }
76114
- function isRecord9(value2) {
76259
+ function isRecord11(value2) {
76115
76260
  return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
76116
76261
  }
76117
76262
  function sequenceNumOf(value2) {
@@ -76133,7 +76278,7 @@ function parseRcStreamEnvelope(parsed) {
76133
76278
  } catch {
76134
76279
  return void 0;
76135
76280
  }
76136
- if (!isRecord9(data)) {
76281
+ if (!isRecord11(data)) {
76137
76282
  return void 0;
76138
76283
  }
76139
76284
  const eventType = data.event_type;
@@ -76421,7 +76566,7 @@ var ProviderQuotaError = class extends Error {
76421
76566
  this.name = "ProviderQuotaError";
76422
76567
  }
76423
76568
  };
76424
- var MS_PER_SECOND9 = 1e3;
76569
+ var MS_PER_SECOND10 = 1e3;
76425
76570
  var SECONDS_PER_HOUR = 3600;
76426
76571
  var HOURS_PER_DAY3 = 24;
76427
76572
  var DAYS_PER_MONTH = 30;
@@ -76433,8 +76578,8 @@ var ZAI_QUOTA_URL = `https://${ZAI_HOST}/api/monitor/usage/quota/limit`;
76433
76578
  var ZAI_UNIT_HOURS = 3;
76434
76579
  var ZAI_UNIT_MONTHS = 5;
76435
76580
  var ZAI_UNIT_MS = /* @__PURE__ */ new Map([
76436
- [ZAI_UNIT_HOURS, SECONDS_PER_HOUR * MS_PER_SECOND9],
76437
- [ZAI_UNIT_MONTHS, DAYS_PER_MONTH * HOURS_PER_DAY3 * SECONDS_PER_HOUR * MS_PER_SECOND9]
76581
+ [ZAI_UNIT_HOURS, SECONDS_PER_HOUR * MS_PER_SECOND10],
76582
+ [ZAI_UNIT_MONTHS, DAYS_PER_MONTH * HOURS_PER_DAY3 * SECONDS_PER_HOUR * MS_PER_SECOND10]
76438
76583
  ]);
76439
76584
  var ZAI_MEASURES = { TOKENS_LIMIT: "tokens", TIME_LIMIT: "tool-calls" };
76440
76585
  var ZaiLimitSchema = external_exports.object({
@@ -76498,7 +76643,7 @@ var OPENROUTER_HOST = "openrouter.ai";
76498
76643
  var OPENROUTER_KEY_URL = `https://${OPENROUTER_HOST}/api/v1/key`;
76499
76644
  var OPENROUTER_CREDITS_URL = `https://${OPENROUTER_HOST}/api/v1/credits`;
76500
76645
  var HTTP_FORBIDDEN = 403;
76501
- var MS_PER_DAY2 = HOURS_PER_DAY3 * SECONDS_PER_HOUR * MS_PER_SECOND9;
76646
+ var MS_PER_DAY2 = HOURS_PER_DAY3 * SECONDS_PER_HOUR * MS_PER_SECOND10;
76502
76647
  var OpenRouterKeySchema = external_exports.object({
76503
76648
  data: external_exports.object({
76504
76649
  limit: external_exports.number().nonnegative().nullable(),
@@ -79178,6 +79323,11 @@ function buildFlagArgs(flags) {
79178
79323
  function buildArgv(params) {
79179
79324
  return [...params.toolFlags, ...params.extraFlags, ...params.passthrough];
79180
79325
  }
79326
+ var LOOPBACK_HOSTS = ["127.0.0.1", "localhost", "::1"];
79327
+ function withLoopbackHosts(existing) {
79328
+ const listed = (existing ?? "").split(",").map((entry) => entry.trim()).filter((entry) => entry !== "");
79329
+ return [...listed, ...LOOPBACK_HOSTS.filter((host) => !listed.includes(host))].join(",");
79330
+ }
79181
79331
  function buildEnv(params) {
79182
79332
  const env = { ...params.baseEnv };
79183
79333
  const parentCustomHeaders = params.baseEnv.ANTHROPIC_CUSTOM_HEADERS;
@@ -79222,6 +79372,8 @@ function buildEnv(params) {
79222
79372
  );
79223
79373
  env.NODE_EXTRA_CA_CERTS = params.frontdoor.trustBundlePath;
79224
79374
  env.HTTPS_PROXY = connectProxyUrl(params.frontdoor.connectPort, params.frontdoor.sessionToken);
79375
+ env.NO_PROXY = withLoopbackHosts(env.NO_PROXY);
79376
+ env.no_proxy = withLoopbackHosts(env.no_proxy);
79225
79377
  }
79226
79378
  return env;
79227
79379
  }