agent-shim 8.23.0 → 8.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -61796,7 +61796,7 @@ function registerIdentityCommand(program2, deps) {
61796
61796
  // package.json
61797
61797
  var package_default = {
61798
61798
  name: "agent-shim",
61799
- version: "8.23.0",
61799
+ version: "8.24.0",
61800
61800
  description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
61801
61801
  license: "MIT",
61802
61802
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -64385,12 +64385,12 @@ var SiwcInvalidGrantError = class extends SiwcError {
64385
64385
  function base64Url(bytes) {
64386
64386
  return Buffer.from(bytes).toString("base64url");
64387
64387
  }
64388
- function newPkce(randomBytes4) {
64389
- const verifier = base64Url(randomBytes4(SIWC_RANDOM_BYTES));
64388
+ function newPkce(randomBytes5) {
64389
+ const verifier = base64Url(randomBytes5(SIWC_RANDOM_BYTES));
64390
64390
  return { verifier, challenge: (0, import_node_crypto6.createHash)("sha256").update(verifier).digest("base64url") };
64391
64391
  }
64392
- function newOpaqueValue(randomBytes4) {
64393
- return base64Url(randomBytes4(SIWC_RANDOM_BYTES));
64392
+ function newOpaqueValue(randomBytes5) {
64393
+ return base64Url(randomBytes5(SIWC_RANDOM_BYTES));
64394
64394
  }
64395
64395
  function buildAuthorizeUrl(request7) {
64396
64396
  const url2 = new URL(SIWC_AUTHORIZE_URL);
@@ -65051,7 +65051,7 @@ function registerCodexCommand(program2, deps) {
65051
65051
  }
65052
65052
 
65053
65053
  // src/frontdoor/commands.ts
65054
- var import_node_crypto14 = require("node:crypto");
65054
+ var import_node_crypto15 = require("node:crypto");
65055
65055
  var import_node_path36 = __toESM(require("node:path"), 1);
65056
65056
  var import_node_fs13 = __toESM(require("node:fs"), 1);
65057
65057
 
@@ -66548,6 +66548,8 @@ function teeRoutedResponse(response, observer, onSettled) {
66548
66548
  }
66549
66549
  };
66550
66550
  }
66551
+
66552
+ // src/frontdoor/rcWrites.ts
66551
66553
  function buildRcUserMessagePayload(uuid3, sessionId, text2) {
66552
66554
  return {
66553
66555
  type: "user",
@@ -66648,14 +66650,55 @@ var RC_PERMISSION_MODES = ["default", "acceptEdits", "bypassPermissions", "plan"
66648
66650
  function isRcPermissionMode(value2) {
66649
66651
  return RC_PERMISSION_MODES.some((mode) => mode === value2);
66650
66652
  }
66653
+ function buildRcControlRequestPayload(requestId, request7) {
66654
+ return { type: "control_request", request_id: requestId, request: request7 };
66655
+ }
66651
66656
  function buildRcInterruptPayload(requestId) {
66652
- return { type: "control_request", request_id: requestId, request: { subtype: "interrupt" } };
66657
+ return buildRcControlRequestPayload(requestId, { subtype: "interrupt" });
66653
66658
  }
66654
66659
  function buildRcSetModelPayload(requestId, model) {
66655
- return { type: "control_request", request_id: requestId, request: { subtype: "set_model", model } };
66660
+ return buildRcControlRequestPayload(requestId, { subtype: "set_model", model });
66656
66661
  }
66657
66662
  function buildRcSetPermissionModePayload(requestId, mode) {
66658
- return { type: "control_request", request_id: requestId, request: { subtype: "set_permission_mode", mode } };
66663
+ return buildRcControlRequestPayload(requestId, { subtype: "set_permission_mode", mode });
66664
+ }
66665
+ function buildRcEndSessionPayload(requestId, reason) {
66666
+ return buildRcControlRequestPayload(requestId, { subtype: "end_session", ...reason === void 0 ? {} : { reason } });
66667
+ }
66668
+ function buildRcGetUsagePayload(requestId, skipBehaviors) {
66669
+ return buildRcControlRequestPayload(requestId, { subtype: "get_usage", ...skipBehaviors === true ? { skip_behaviors: true } : {} });
66670
+ }
66671
+ var RC_CONTEXT_USAGE_DETAILS = ["summary", "full"];
66672
+ function isRcContextUsageDetail(value2) {
66673
+ return RC_CONTEXT_USAGE_DETAILS.some((detail) => detail === value2);
66674
+ }
66675
+ function buildRcGetContextUsagePayload(requestId, detail) {
66676
+ return buildRcControlRequestPayload(requestId, { subtype: "get_context_usage", ...detail === void 0 ? {} : { detail } });
66677
+ }
66678
+ var RC_READ_FILE_ENCODINGS = ["utf-8", "base64"];
66679
+ function isRcReadFileEncoding(value2) {
66680
+ return RC_READ_FILE_ENCODINGS.some((encoding) => encoding === value2);
66681
+ }
66682
+ function buildRcReadFilePayload(requestId, path46, options) {
66683
+ return buildRcControlRequestPayload(requestId, { subtype: "read_file", path: path46, ...options?.maxBytes === void 0 ? {} : { max_bytes: options.maxBytes }, ...options?.encoding === void 0 ? {} : { encoding: options.encoding } });
66684
+ }
66685
+ function buildRcFileSuggestionsPayload(requestId, query) {
66686
+ return buildRcControlRequestPayload(requestId, { subtype: "file_suggestions", query });
66687
+ }
66688
+ function buildRcMcpStatusPayload(requestId) {
66689
+ return buildRcControlRequestPayload(requestId, { subtype: "mcp_status" });
66690
+ }
66691
+ function buildRcMcpReconnectPayload(requestId, serverName) {
66692
+ return buildRcControlRequestPayload(requestId, { subtype: "mcp_reconnect", serverName });
66693
+ }
66694
+ function buildRcMcpAuthenticatePayload(requestId, serverName, redirectUri) {
66695
+ return buildRcControlRequestPayload(requestId, { subtype: "mcp_authenticate", serverName, redirectUri });
66696
+ }
66697
+ function buildRcMcpOAuthCallbackUrlPayload(requestId, serverName, callbackUrl) {
66698
+ return buildRcControlRequestPayload(requestId, { subtype: "mcp_oauth_callback_url", serverName, callbackUrl });
66699
+ }
66700
+ function buildRcKeepAlivePayload() {
66701
+ return { type: "keep_alive" };
66659
66702
  }
66660
66703
  async function answerRcControlRequest(deps, sessionId, requestId, decision) {
66661
66704
  const credential = deps.credentialOf(sessionId);
@@ -66682,7 +66725,7 @@ async function answerRcControlRequest(deps, sessionId, requestId, decision) {
66682
66725
  }
66683
66726
  return result;
66684
66727
  }
66685
- async function interruptRcSession(deps, sessionId) {
66728
+ async function deliverRcClientPayload(deps, sessionId, payload, action) {
66686
66729
  const credential = deps.credentialOf(sessionId);
66687
66730
  if (credential === void 0) {
66688
66731
  return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
@@ -66690,12 +66733,12 @@ async function interruptRcSession(deps, sessionId) {
66690
66733
  if (credential.authorization === void 0) {
66691
66734
  return noOAuthCredentialResult(sessionId);
66692
66735
  }
66693
- const body = JSON.stringify(buildRcEventWriteBody(buildRcInterruptPayload(deps.newUuid())));
66736
+ const body = JSON.stringify(buildRcEventWriteBody(payload));
66694
66737
  let answer;
66695
66738
  try {
66696
66739
  answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
66697
66740
  } catch (error62) {
66698
- return { ok: false, message: `the door could not reach the API host to interrupt session ${sessionId}: ${error62 instanceof Error ? error62.message : String(error62)}` };
66741
+ return { ok: false, message: `the door could not reach the API host to ${action}: ${error62 instanceof Error ? error62.message : String(error62)}` };
66699
66742
  }
66700
66743
  const result = rcEventWriteResultFromAnswer(answer);
66701
66744
  if (result.ok) {
@@ -66703,50 +66746,89 @@ async function interruptRcSession(deps, sessionId) {
66703
66746
  }
66704
66747
  return result;
66705
66748
  }
66749
+ async function interruptRcSession(deps, sessionId) {
66750
+ const requestId = deps.newUuid();
66751
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcInterruptPayload(requestId), `interrupt session ${sessionId}`);
66752
+ return result.ok ? { ...result, requestId } : result;
66753
+ }
66706
66754
  async function setRcSessionModel(deps, sessionId, model) {
66707
66755
  if (model === "") {
66708
66756
  return { ok: false, message: "a set-model write needs a non-empty model id: the SDK's own field treats an omitted or null value as a reset to the session default, which this operation never sends, and an empty string is neither" };
66709
66757
  }
66710
- const credential = deps.credentialOf(sessionId);
66711
- if (credential === void 0) {
66712
- return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
66713
- }
66714
- if (credential.authorization === void 0) {
66715
- return noOAuthCredentialResult(sessionId);
66758
+ const requestId = deps.newUuid();
66759
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcSetModelPayload(requestId, model), `set the model on session ${sessionId}`);
66760
+ return result.ok ? { ...result, requestId } : result;
66761
+ }
66762
+ async function setRcSessionPermissionMode(deps, sessionId, mode) {
66763
+ const requestId = deps.newUuid();
66764
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcSetPermissionModePayload(requestId, mode), `set the permission mode on session ${sessionId}`);
66765
+ return result.ok ? { ...result, requestId } : result;
66766
+ }
66767
+ async function endRcSession(deps, sessionId, reason) {
66768
+ if (reason === "") {
66769
+ return { ok: false, message: "an end-session write needs either no reason or a non-empty one: the worker reads an absent reason as its own unspecified form, and an empty string is neither" };
66716
66770
  }
66717
- const body = JSON.stringify(buildRcEventWriteBody(buildRcSetModelPayload(deps.newUuid(), model)));
66718
- let answer;
66719
- try {
66720
- answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
66721
- } catch (error62) {
66722
- return { ok: false, message: `the door could not reach the API host to set the model on session ${sessionId}: ${error62 instanceof Error ? error62.message : String(error62)}` };
66771
+ const requestId = deps.newUuid();
66772
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcEndSessionPayload(requestId, reason), `end session ${sessionId}`);
66773
+ return result.ok ? { ...result, requestId } : result;
66774
+ }
66775
+ async function getRcSessionUsage(deps, sessionId, skipBehaviors) {
66776
+ const requestId = deps.newUuid();
66777
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcGetUsagePayload(requestId, skipBehaviors), `request the usage of session ${sessionId}`);
66778
+ return result.ok ? { ...result, requestId } : result;
66779
+ }
66780
+ async function getRcSessionContextUsage(deps, sessionId, detail) {
66781
+ const requestId = deps.newUuid();
66782
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcGetContextUsagePayload(requestId, detail), `request the context usage of session ${sessionId}`);
66783
+ return result.ok ? { ...result, requestId } : result;
66784
+ }
66785
+ async function readRcSessionFile(deps, sessionId, path46, options) {
66786
+ if (path46 === "") {
66787
+ return { ok: false, message: "a read-file write needs a non-empty path: the SDK's own field names the file the worker resolves against the session's cwd, and an empty string names none" };
66723
66788
  }
66724
- const result = rcEventWriteResultFromAnswer(answer);
66725
- if (result.ok) {
66726
- deps.noteSequenceNums(sessionId, result.sequenceNums);
66789
+ if (options?.maxBytes !== void 0 && (!Number.isInteger(options.maxBytes) || options.maxBytes <= 0)) {
66790
+ return { ok: false, message: "a read-file write's max-bytes is a positive whole number of bytes: the SDK's own field caps the read, and a zero or negative cap reads nothing" };
66727
66791
  }
66728
- return result;
66792
+ const requestId = deps.newUuid();
66793
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcReadFilePayload(requestId, path46, options), `read ${path46} from session ${sessionId}`);
66794
+ return result.ok ? { ...result, requestId } : result;
66729
66795
  }
66730
- async function setRcSessionPermissionMode(deps, sessionId, mode) {
66731
- const credential = deps.credentialOf(sessionId);
66732
- if (credential === void 0) {
66733
- return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
66734
- }
66735
- if (credential.authorization === void 0) {
66736
- return noOAuthCredentialResult(sessionId);
66796
+ async function suggestRcSessionFiles(deps, sessionId, query) {
66797
+ const requestId = deps.newUuid();
66798
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcFileSuggestionsPayload(requestId, query), `request file suggestions from session ${sessionId}`);
66799
+ return result.ok ? { ...result, requestId } : result;
66800
+ }
66801
+ async function getRcSessionMcpStatus(deps, sessionId) {
66802
+ const requestId = deps.newUuid();
66803
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcMcpStatusPayload(requestId), `request the MCP status of session ${sessionId}`);
66804
+ return result.ok ? { ...result, requestId } : result;
66805
+ }
66806
+ async function reconnectRcSessionMcpServer(deps, sessionId, serverName) {
66807
+ if (serverName === "") {
66808
+ return { ok: false, message: "an mcp-reconnect write needs a non-empty server name: the SDK's own field names the server as mcp_status reports it, and an empty string names none" };
66737
66809
  }
66738
- const body = JSON.stringify(buildRcEventWriteBody(buildRcSetPermissionModePayload(deps.newUuid(), mode)));
66739
- let answer;
66740
- try {
66741
- answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
66742
- } catch (error62) {
66743
- return { ok: false, message: `the door could not reach the API host to set the permission mode on session ${sessionId}: ${error62 instanceof Error ? error62.message : String(error62)}` };
66810
+ const requestId = deps.newUuid();
66811
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcMcpReconnectPayload(requestId, serverName), `reconnect MCP server ${serverName} on session ${sessionId}`);
66812
+ return result.ok ? { ...result, requestId } : result;
66813
+ }
66814
+ async function authenticateRcSessionMcpServer(deps, sessionId, serverName, redirectUri) {
66815
+ if (serverName === "" || redirectUri === "") {
66816
+ return { ok: false, message: "an mcp-authenticate write needs a non-empty server name and redirect URI: the CLI's own request names the server and where its OAuth flow redirects back to, and an empty string names neither" };
66744
66817
  }
66745
- const result = rcEventWriteResultFromAnswer(answer);
66746
- if (result.ok) {
66747
- deps.noteSequenceNums(sessionId, result.sequenceNums);
66818
+ const requestId = deps.newUuid();
66819
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcMcpAuthenticatePayload(requestId, serverName, redirectUri), `authenticate MCP server ${serverName} on session ${sessionId}`);
66820
+ return result.ok ? { ...result, requestId } : result;
66821
+ }
66822
+ async function submitRcSessionMcpOAuthCallbackUrl(deps, sessionId, serverName, callbackUrl) {
66823
+ if (serverName === "" || callbackUrl === "") {
66824
+ return { ok: false, message: "an mcp-oauth-callback-url write needs a non-empty server name and callback URL: the CLI's own request names the server and the URL its OAuth flow landed on, and an empty string names neither" };
66748
66825
  }
66749
- return result;
66826
+ const requestId = deps.newUuid();
66827
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcMcpOAuthCallbackUrlPayload(requestId, serverName, callbackUrl), `hand MCP server ${serverName}'s OAuth callback to session ${sessionId}`);
66828
+ return result.ok ? { ...result, requestId } : result;
66829
+ }
66830
+ async function sendRcKeepAlive(deps, sessionId) {
66831
+ return await deliverRcClientPayload(deps, sessionId, buildRcKeepAlivePayload(), `keep session ${sessionId} alive`);
66750
66832
  }
66751
66833
 
66752
66834
  // src/frontdoor/rcControl.ts
@@ -66758,6 +66840,25 @@ function answerJson(response, status, body) {
66758
66840
  response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text2, "utf8")) });
66759
66841
  response.end(text2);
66760
66842
  }
66843
+ function isJsonObject(value2) {
66844
+ return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
66845
+ }
66846
+ function bodyString(body, field) {
66847
+ const value2 = body[field];
66848
+ return typeof value2 === "string" && value2 !== "" ? value2 : void 0;
66849
+ }
66850
+ function bodyStringAllowingEmpty(body, field) {
66851
+ const value2 = body[field];
66852
+ return typeof value2 === "string" ? value2 : void 0;
66853
+ }
66854
+ function bodyBoolean(body, field) {
66855
+ const value2 = body[field];
66856
+ return typeof value2 === "boolean" ? value2 : void 0;
66857
+ }
66858
+ function bodyPositiveInteger(body, field) {
66859
+ const value2 = body[field];
66860
+ return typeof value2 === "number" && Number.isInteger(value2) && value2 > 0 ? value2 : void 0;
66861
+ }
66761
66862
  async function readBody(request7) {
66762
66863
  return await new Promise((resolve, reject) => {
66763
66864
  const chunks = [];
@@ -66785,9 +66886,127 @@ function createRcControlHandler(deps) {
66785
66886
  const pendingPath = `${CONTROL_PATH_PREFIX}/pending`;
66786
66887
  const injectPath = `${CONTROL_PATH_PREFIX}/inject`;
66787
66888
  const answerPath = `${CONTROL_PATH_PREFIX}/answer`;
66788
- const interruptPath = `${CONTROL_PATH_PREFIX}/interrupt`;
66789
- const setModelPath = `${CONTROL_PATH_PREFIX}/set-model`;
66790
- const setPermissionModePath = `${CONTROL_PATH_PREFIX}/set-permission-mode`;
66889
+ const controlWrites = [
66890
+ {
66891
+ name: "interrupt",
66892
+ path: "interrupt",
66893
+ read: (body) => {
66894
+ const session = bodyString(body, "session");
66895
+ return session === void 0 ? { error: "the interrupt body must be JSON naming a non-empty session id" } : { session, run: async () => await deps.interrupt(session) };
66896
+ }
66897
+ },
66898
+ {
66899
+ name: "set-model",
66900
+ path: "set-model",
66901
+ read: (body) => {
66902
+ const session = bodyString(body, "session");
66903
+ const model = bodyString(body, "model");
66904
+ return session === void 0 || model === void 0 ? { error: "the set-model body must be JSON naming a non-empty session id and model id" } : { session, run: async () => await deps.setModel(session, model) };
66905
+ }
66906
+ },
66907
+ {
66908
+ name: "set-permission-mode",
66909
+ path: "set-permission-mode",
66910
+ read: (body) => {
66911
+ const session = bodyString(body, "session");
66912
+ const mode = body.mode;
66913
+ return session === void 0 || !isRcPermissionMode(mode) ? { error: `the set-permission-mode body must be JSON naming a non-empty session id and a mode the SDK's own type permits (${RC_PERMISSION_MODE_LIST})` } : { session, run: async () => await deps.setPermissionMode(session, mode) };
66914
+ }
66915
+ },
66916
+ {
66917
+ name: "end-session",
66918
+ path: "end-session",
66919
+ read: (body) => {
66920
+ const session = bodyString(body, "session");
66921
+ const reason = "reason" in body ? bodyString(body, "reason") : void 0;
66922
+ return session === void 0 || "reason" in body && reason === void 0 ? { error: "the end-session body must be JSON naming a non-empty session id, and a reason only as a non-empty string or absent" } : { session, run: async () => await deps.endSession(session, reason) };
66923
+ }
66924
+ },
66925
+ {
66926
+ name: "get-usage",
66927
+ path: "get-usage",
66928
+ read: (body) => {
66929
+ const session = bodyString(body, "session");
66930
+ const skipBehaviors = "skipBehaviors" in body ? bodyBoolean(body, "skipBehaviors") : void 0;
66931
+ return session === void 0 || "skipBehaviors" in body && skipBehaviors === void 0 ? { error: "the get-usage body must be JSON naming a non-empty session id, and skipBehaviors only as a boolean or absent" } : { session, run: async () => await deps.getUsage(session, skipBehaviors) };
66932
+ }
66933
+ },
66934
+ {
66935
+ name: "get-context-usage",
66936
+ path: "get-context-usage",
66937
+ read: (body) => {
66938
+ const session = bodyString(body, "session");
66939
+ const detail = "detail" in body && isRcContextUsageDetail(body.detail) ? body.detail : void 0;
66940
+ return session === void 0 || "detail" in body && detail === void 0 ? { error: `the get-context-usage body must be JSON naming a non-empty session id, and a detail level the SDK's own type permits (${RC_CONTEXT_USAGE_DETAILS.join(", ")}) or none` } : { session, run: async () => await deps.getContextUsage(session, detail) };
66941
+ }
66942
+ },
66943
+ {
66944
+ name: "read-file",
66945
+ path: "read-file",
66946
+ read: (body) => {
66947
+ const session = bodyString(body, "session");
66948
+ const path46 = bodyString(body, "path");
66949
+ const maxBytes = "maxBytes" in body ? bodyPositiveInteger(body, "maxBytes") : void 0;
66950
+ const encoding = "encoding" in body && isRcReadFileEncoding(body.encoding) ? body.encoding : void 0;
66951
+ const refused2 = session === void 0 || path46 === void 0 || "maxBytes" in body && maxBytes === void 0 || "encoding" in body && encoding === void 0;
66952
+ return refused2 ? { error: `the read-file body must be JSON naming a non-empty session id and path, maxBytes only as a positive whole number or absent, and an encoding the SDK's own type permits (${RC_READ_FILE_ENCODINGS.join(", ")}) or none` } : { session, run: async () => await deps.readFile(session, path46, { ...maxBytes === void 0 ? {} : { maxBytes }, ...encoding === void 0 ? {} : { encoding } }) };
66953
+ }
66954
+ },
66955
+ {
66956
+ name: "file-suggestions",
66957
+ path: "file-suggestions",
66958
+ read: (body) => {
66959
+ const session = bodyString(body, "session");
66960
+ const query = bodyStringAllowingEmpty(body, "query");
66961
+ return session === void 0 || query === void 0 ? { error: "the file-suggestions body must be JSON naming a non-empty session id and a query string, which may be empty" } : { session, run: async () => await deps.fileSuggestions(session, query) };
66962
+ }
66963
+ },
66964
+ {
66965
+ name: "keep-alive",
66966
+ path: "keep-alive",
66967
+ read: (body) => {
66968
+ const session = bodyString(body, "session");
66969
+ return session === void 0 ? { error: "the keep-alive body must be JSON naming a non-empty session id" } : { session, run: async () => await deps.keepAlive(session) };
66970
+ }
66971
+ },
66972
+ {
66973
+ name: "mcp-status",
66974
+ path: "mcp-status",
66975
+ read: (body) => {
66976
+ const session = bodyString(body, "session");
66977
+ return session === void 0 ? { error: "the mcp-status body must be JSON naming a non-empty session id" } : { session, run: async () => await deps.mcpStatus(session) };
66978
+ }
66979
+ },
66980
+ {
66981
+ name: "mcp-reconnect",
66982
+ path: "mcp-reconnect",
66983
+ read: (body) => {
66984
+ const session = bodyString(body, "session");
66985
+ const serverName = bodyString(body, "serverName");
66986
+ return session === void 0 || serverName === void 0 ? { error: "the mcp-reconnect body must be JSON naming a non-empty session id and server name" } : { session, run: async () => await deps.mcpReconnect(session, serverName) };
66987
+ }
66988
+ },
66989
+ {
66990
+ name: "mcp-authenticate",
66991
+ path: "mcp-authenticate",
66992
+ read: (body) => {
66993
+ const session = bodyString(body, "session");
66994
+ const serverName = bodyString(body, "serverName");
66995
+ const redirectUri = bodyString(body, "redirectUri");
66996
+ return session === void 0 || serverName === void 0 || redirectUri === void 0 ? { error: "the mcp-authenticate body must be JSON naming a non-empty session id, server name, and redirect URI" } : { session, run: async () => await deps.mcpAuthenticate(session, serverName, redirectUri) };
66997
+ }
66998
+ },
66999
+ {
67000
+ name: "mcp-oauth-callback-url",
67001
+ path: "mcp-oauth-callback-url",
67002
+ read: (body) => {
67003
+ const session = bodyString(body, "session");
67004
+ const serverName = bodyString(body, "serverName");
67005
+ const callbackUrl = bodyString(body, "callbackUrl");
67006
+ return session === void 0 || serverName === void 0 || callbackUrl === void 0 ? { error: "the mcp-oauth-callback-url body must be JSON naming a non-empty session id, server name, and callback URL" } : { session, run: async () => await deps.mcpOAuthCallbackUrl(session, serverName, callbackUrl) };
67007
+ }
67008
+ }
67009
+ ];
66791
67010
  return (request7, response) => {
66792
67011
  const presented = request7.headers.authorization;
66793
67012
  const bearerPrefix = "bearer ".length;
@@ -66872,73 +67091,33 @@ function createRcControlHandler(deps) {
66872
67091
  answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
66873
67092
  return;
66874
67093
  }
66875
- if (url2.pathname === interruptPath) {
66876
- if (request7.method !== "POST") {
66877
- answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the interrupt operation is a POST" });
66878
- return;
66879
- }
66880
- let parsed;
66881
- try {
66882
- parsed = JSON.parse(await readBody(request7));
66883
- } catch {
66884
- answerJson(response, HTTP_STATUS.badRequest, { error: "the interrupt body is not readable JSON within the control body cap" });
66885
- return;
66886
- }
66887
- if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || typeof parsed.session !== "string" || parsed.session === "") {
66888
- answerJson(response, HTTP_STATUS.badRequest, { error: "the interrupt body must be JSON naming a non-empty session id" });
66889
- return;
66890
- }
66891
- const result = await deps.interrupt(parsed.session);
66892
- if (result.ok) {
66893
- answerJson(response, HTTP_STATUS.ok, { session: parsed.session, sequenceNums: result.sequenceNums });
66894
- return;
67094
+ for (const route of controlWrites) {
67095
+ if (url2.pathname !== `${CONTROL_PATH_PREFIX}/${route.path}`) {
67096
+ continue;
66895
67097
  }
66896
- answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
66897
- return;
66898
- }
66899
- if (url2.pathname === setModelPath) {
66900
67098
  if (request7.method !== "POST") {
66901
- answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the set-model operation is a POST" });
67099
+ answerJson(response, HTTP_STATUS.methodNotAllowed, { error: `the ${route.name} operation is a POST` });
66902
67100
  return;
66903
67101
  }
66904
67102
  let parsed;
66905
67103
  try {
66906
67104
  parsed = JSON.parse(await readBody(request7));
66907
67105
  } catch {
66908
- answerJson(response, HTTP_STATUS.badRequest, { error: "the set-model body is not readable JSON within the control body cap" });
66909
- return;
66910
- }
66911
- if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("model" in parsed) || typeof parsed.session !== "string" || typeof parsed.model !== "string" || parsed.session === "" || parsed.model === "") {
66912
- answerJson(response, HTTP_STATUS.badRequest, { error: "the set-model body must be JSON naming a non-empty session id and model id" });
66913
- return;
66914
- }
66915
- const result = await deps.setModel(parsed.session, parsed.model);
66916
- if (result.ok) {
66917
- answerJson(response, HTTP_STATUS.ok, { session: parsed.session, model: parsed.model, sequenceNums: result.sequenceNums });
66918
- return;
66919
- }
66920
- answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
66921
- return;
66922
- }
66923
- if (url2.pathname === setPermissionModePath) {
66924
- if (request7.method !== "POST") {
66925
- answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the set-permission-mode operation is a POST" });
67106
+ answerJson(response, HTTP_STATUS.badRequest, { error: `the ${route.name} body is not readable JSON within the control body cap` });
66926
67107
  return;
66927
67108
  }
66928
- let parsed;
66929
- try {
66930
- parsed = JSON.parse(await readBody(request7));
66931
- } catch {
66932
- answerJson(response, HTTP_STATUS.badRequest, { error: "the set-permission-mode body is not readable JSON within the control body cap" });
67109
+ if (!isJsonObject(parsed)) {
67110
+ answerJson(response, HTTP_STATUS.badRequest, { error: `the ${route.name} body must be a JSON object` });
66933
67111
  return;
66934
67112
  }
66935
- if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("mode" in parsed) || typeof parsed.session !== "string" || parsed.session === "" || !isRcPermissionMode(parsed.mode)) {
66936
- answerJson(response, HTTP_STATUS.badRequest, { error: `the set-permission-mode body must be JSON naming a non-empty session id and a mode the SDK's own type permits (${RC_PERMISSION_MODE_LIST})` });
67113
+ const read = route.read(parsed);
67114
+ if ("error" in read) {
67115
+ answerJson(response, HTTP_STATUS.badRequest, { error: read.error });
66937
67116
  return;
66938
67117
  }
66939
- const result = await deps.setPermissionMode(parsed.session, parsed.mode);
67118
+ const result = await read.run();
66940
67119
  if (result.ok) {
66941
- answerJson(response, HTTP_STATUS.ok, { session: parsed.session, mode: parsed.mode, sequenceNums: result.sequenceNums });
67120
+ answerJson(response, HTTP_STATUS.ok, { session: read.session, ...result.requestId === void 0 ? {} : { request: result.requestId }, sequenceNums: result.sequenceNums });
66942
67121
  return;
66943
67122
  }
66944
67123
  answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
@@ -67056,6 +67235,19 @@ function sequenceNumsOfBody(body) {
67056
67235
  }
67057
67236
  return sequenceNums;
67058
67237
  }
67238
+ function requestIdOfBody(body) {
67239
+ let parsed;
67240
+ try {
67241
+ parsed = JSON.parse(body);
67242
+ } catch {
67243
+ return void 0;
67244
+ }
67245
+ if (typeof parsed !== "object" || parsed === null || !("request" in parsed)) {
67246
+ return void 0;
67247
+ }
67248
+ const value2 = parsed.request;
67249
+ return typeof value2 === "string" && value2 !== "" ? value2 : void 0;
67250
+ }
67059
67251
  function frontDoorRcControl(transport, token) {
67060
67252
  const headers = { authorization: `Bearer ${token}` };
67061
67253
  const listAnswerOf = async (path46, what) => {
@@ -67144,7 +67336,8 @@ function frontDoorRcControl(transport, token) {
67144
67336
  if (sequenceNums === void 0) {
67145
67337
  return { ok: false, message: `the door accepted the event but its answer named no sequence numbers: ${answer.body}` };
67146
67338
  }
67147
- return { ok: true, sequenceNums };
67339
+ const requestId = requestIdOfBody(answer.body);
67340
+ return requestId === void 0 ? { ok: true, sequenceNums } : { ok: true, sequenceNums, requestId };
67148
67341
  }
67149
67342
  return { ok: false, message: describeFailure(answer.status, controlErrorOf(answer.body) ?? answer.body) };
67150
67343
  };
@@ -67161,7 +67354,21 @@ function frontDoorRcControl(transport, token) {
67161
67354
  ),
67162
67355
  interruptSession: async (sessionId) => await writeOf(`${CONTROL_PATH_PREFIX}/interrupt`, JSON.stringify({ session: sessionId }), (status, message) => `interrupting ${sessionId} failed (HTTP ${String(status)}): ${message}`),
67163
67356
  setModel: async (sessionId, model) => await writeOf(`${CONTROL_PATH_PREFIX}/set-model`, JSON.stringify({ session: sessionId, model }), (status, message) => `setting the model on ${sessionId} failed (HTTP ${String(status)}): ${message}`),
67164
- setPermissionMode: async (sessionId, mode) => await writeOf(`${CONTROL_PATH_PREFIX}/set-permission-mode`, JSON.stringify({ session: sessionId, mode }), (status, message) => `setting the permission mode on ${sessionId} failed (HTTP ${String(status)}): ${message}`)
67357
+ setPermissionMode: async (sessionId, mode) => await writeOf(`${CONTROL_PATH_PREFIX}/set-permission-mode`, JSON.stringify({ session: sessionId, mode }), (status, message) => `setting the permission mode on ${sessionId} failed (HTTP ${String(status)}): ${message}`),
67358
+ endSession: async (sessionId, reason) => await writeOf(`${CONTROL_PATH_PREFIX}/end-session`, JSON.stringify({ session: sessionId, ...reason === void 0 ? {} : { reason } }), (status, message) => `ending ${sessionId} failed (HTTP ${String(status)}): ${message}`),
67359
+ getUsage: async (sessionId, skipBehaviors) => await writeOf(`${CONTROL_PATH_PREFIX}/get-usage`, JSON.stringify({ session: sessionId, ...skipBehaviors === void 0 ? {} : { skipBehaviors } }), (status, message) => `requesting the usage of ${sessionId} failed (HTTP ${String(status)}): ${message}`),
67360
+ getContextUsage: async (sessionId, detail) => await writeOf(`${CONTROL_PATH_PREFIX}/get-context-usage`, JSON.stringify({ session: sessionId, ...detail === void 0 ? {} : { detail } }), (status, message) => `requesting the context usage of ${sessionId} failed (HTTP ${String(status)}): ${message}`),
67361
+ readFile: async (sessionId, path46, options) => await writeOf(
67362
+ `${CONTROL_PATH_PREFIX}/read-file`,
67363
+ JSON.stringify({ session: sessionId, path: path46, ...options?.maxBytes === void 0 ? {} : { maxBytes: options.maxBytes }, ...options?.encoding === void 0 ? {} : { encoding: options.encoding } }),
67364
+ (status, message) => `reading ${path46} from ${sessionId} failed (HTTP ${String(status)}): ${message}`
67365
+ ),
67366
+ fileSuggestions: async (sessionId, query) => await writeOf(`${CONTROL_PATH_PREFIX}/file-suggestions`, JSON.stringify({ session: sessionId, query }), (status, message) => `requesting file suggestions from ${sessionId} failed (HTTP ${String(status)}): ${message}`),
67367
+ keepAlive: async (sessionId) => await writeOf(`${CONTROL_PATH_PREFIX}/keep-alive`, JSON.stringify({ session: sessionId }), (status, message) => `keeping ${sessionId} alive failed (HTTP ${String(status)}): ${message}`),
67368
+ mcpStatus: async (sessionId) => await writeOf(`${CONTROL_PATH_PREFIX}/mcp-status`, JSON.stringify({ session: sessionId }), (status, message) => `requesting the MCP status of ${sessionId} failed (HTTP ${String(status)}): ${message}`),
67369
+ mcpReconnect: async (sessionId, serverName) => await writeOf(`${CONTROL_PATH_PREFIX}/mcp-reconnect`, JSON.stringify({ session: sessionId, serverName }), (status, message) => `reconnecting MCP server ${serverName} on ${sessionId} failed (HTTP ${String(status)}): ${message}`),
67370
+ mcpAuthenticate: async (sessionId, serverName, redirectUri) => await writeOf(`${CONTROL_PATH_PREFIX}/mcp-authenticate`, JSON.stringify({ session: sessionId, serverName, redirectUri }), (status, message) => `authenticating MCP server ${serverName} on ${sessionId} failed (HTTP ${String(status)}): ${message}`),
67371
+ mcpOAuthCallbackUrl: async (sessionId, serverName, callbackUrl) => await writeOf(`${CONTROL_PATH_PREFIX}/mcp-oauth-callback-url`, JSON.stringify({ session: sessionId, serverName, callbackUrl }), (status, message) => `handing MCP server ${serverName}'s OAuth callback to ${sessionId} failed (HTTP ${String(status)}): ${message}`)
67165
67372
  };
67166
67373
  }
67167
67374
 
@@ -71825,6 +72032,48 @@ var RcSetPermissionModeInputSchema = external_exports.strictObject({
71825
72032
  session: external_exports.string().min(1),
71826
72033
  mode: RcPermissionModeSchema
71827
72034
  });
72035
+ var RcEndSessionInputSchema = external_exports.strictObject({
72036
+ session: external_exports.string().min(1),
72037
+ reason: external_exports.string().min(1).optional()
72038
+ });
72039
+ var RcGetUsageInputSchema = external_exports.strictObject({
72040
+ session: external_exports.string().min(1),
72041
+ skipBehaviors: external_exports.boolean().optional()
72042
+ });
72043
+ var RcGetContextUsageInputSchema = external_exports.strictObject({
72044
+ session: external_exports.string().min(1),
72045
+ detail: external_exports.enum(RC_CONTEXT_USAGE_DETAILS).optional()
72046
+ });
72047
+ var RcReadFileInputSchema = external_exports.strictObject({
72048
+ session: external_exports.string().min(1),
72049
+ path: external_exports.string().min(1),
72050
+ maxBytes: external_exports.number().int().positive().optional(),
72051
+ encoding: external_exports.enum(RC_READ_FILE_ENCODINGS).optional()
72052
+ });
72053
+ var RcFileSuggestionsInputSchema = external_exports.strictObject({
72054
+ session: external_exports.string().min(1),
72055
+ query: external_exports.string()
72056
+ });
72057
+ var RcKeepAliveInputSchema = external_exports.strictObject({
72058
+ session: external_exports.string().min(1)
72059
+ });
72060
+ var RcMcpStatusInputSchema = external_exports.strictObject({
72061
+ session: external_exports.string().min(1)
72062
+ });
72063
+ var RcMcpReconnectInputSchema = external_exports.strictObject({
72064
+ session: external_exports.string().min(1),
72065
+ serverName: external_exports.string().min(1)
72066
+ });
72067
+ var RcMcpAuthenticateInputSchema = external_exports.strictObject({
72068
+ session: external_exports.string().min(1),
72069
+ serverName: external_exports.string().min(1),
72070
+ redirectUri: external_exports.string().min(1)
72071
+ });
72072
+ var RcMcpOAuthCallbackUrlInputSchema = external_exports.strictObject({
72073
+ session: external_exports.string().min(1),
72074
+ serverName: external_exports.string().min(1),
72075
+ callbackUrl: external_exports.string().min(1)
72076
+ });
71828
72077
  var RcListOutputSchema = external_exports.strictObject({ sessions: external_exports.readonly(external_exports.array(RcSessionSummarySchema)) });
71829
72078
  var RcStatusOutputSchema = external_exports.strictObject({ statuses: external_exports.readonly(external_exports.array(RcSessionStatusSchema)) });
71830
72079
  var RcPendingOutputSchema = external_exports.strictObject({ pending: external_exports.readonly(external_exports.array(RcPendingRequestSummarySchema)) });
@@ -71832,6 +72081,11 @@ var RcWriteOutputSchema = external_exports.strictObject({
71832
72081
  session: external_exports.string(),
71833
72082
  sequenceNums: external_exports.readonly(external_exports.array(external_exports.number()))
71834
72083
  });
72084
+ var RcControlWriteOutputSchema = external_exports.strictObject({
72085
+ session: external_exports.string(),
72086
+ request: external_exports.string(),
72087
+ sequenceNums: external_exports.readonly(external_exports.array(external_exports.number()))
72088
+ });
71835
72089
  var RcAnswerOutputSchema = external_exports.strictObject({
71836
72090
  session: external_exports.string(),
71837
72091
  request: external_exports.string(),
@@ -71860,6 +72114,15 @@ function createRcApiRouter(deps) {
71860
72114
  }
71861
72115
  return result.sequenceNums;
71862
72116
  };
72117
+ const deliveredControl = (sessionId, result) => {
72118
+ if (!result.ok) {
72119
+ throw new ORPCError("BAD_GATEWAY", { message: result.message });
72120
+ }
72121
+ if (result.requestId === void 0) {
72122
+ throw new ORPCError("INTERNAL_SERVER_ERROR", { message: "the control request was delivered but the door cannot name the request id it minted, so the worker's answer cannot be matched" });
72123
+ }
72124
+ return { request: result.requestId, sequenceNums: result.sequenceNums };
72125
+ };
71863
72126
  return {
71864
72127
  rc: {
71865
72128
  list: authed.output(RcListOutputSchema).handler(() => ({ sessions: deps.list() })),
@@ -71882,9 +72145,19 @@ function createRcApiRouter(deps) {
71882
72145
  const decision = { approve: input2.approve, message: input2.approve || input2.text === void 0 ? void 0 : input2.text };
71883
72146
  return { session: input2.session, request: input2.request, sequenceNums: delivered(input2.session, await deps.answer(input2.session, input2.request, decision)) };
71884
72147
  }),
71885
- interrupt: authed.input(RcInterruptInputSchema).output(RcWriteOutputSchema).handler(async ({ input: input2 }) => ({ session: input2.session, sequenceNums: delivered(input2.session, await deps.interrupt(input2.session)) })),
71886
- setModel: authed.input(RcSetModelInputSchema).output(RcWriteOutputSchema).handler(async ({ input: input2 }) => ({ session: input2.session, sequenceNums: delivered(input2.session, await deps.setModel(input2.session, input2.model)) })),
71887
- setPermissionMode: authed.input(RcSetPermissionModeInputSchema).output(RcWriteOutputSchema).handler(async ({ input: input2 }) => ({ session: input2.session, sequenceNums: delivered(input2.session, await deps.setPermissionMode(input2.session, input2.mode)) })),
72148
+ interrupt: authed.input(RcInterruptInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input: input2 }) => ({ session: input2.session, ...deliveredControl(input2.session, await deps.interrupt(input2.session)) })),
72149
+ setModel: authed.input(RcSetModelInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input: input2 }) => ({ session: input2.session, ...deliveredControl(input2.session, await deps.setModel(input2.session, input2.model)) })),
72150
+ setPermissionMode: authed.input(RcSetPermissionModeInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input: input2 }) => ({ session: input2.session, ...deliveredControl(input2.session, await deps.setPermissionMode(input2.session, input2.mode)) })),
72151
+ endSession: authed.input(RcEndSessionInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input: input2 }) => ({ session: input2.session, ...deliveredControl(input2.session, await deps.endSession(input2.session, input2.reason)) })),
72152
+ getUsage: authed.input(RcGetUsageInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input: input2 }) => ({ session: input2.session, ...deliveredControl(input2.session, await deps.getUsage(input2.session, input2.skipBehaviors)) })),
72153
+ getContextUsage: authed.input(RcGetContextUsageInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input: input2 }) => ({ session: input2.session, ...deliveredControl(input2.session, await deps.getContextUsage(input2.session, input2.detail)) })),
72154
+ readFile: authed.input(RcReadFileInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input: input2 }) => ({ session: input2.session, ...deliveredControl(input2.session, await deps.readFile(input2.session, input2.path, { ...input2.maxBytes === void 0 ? {} : { maxBytes: input2.maxBytes }, ...input2.encoding === void 0 ? {} : { encoding: input2.encoding } })) })),
72155
+ fileSuggestions: authed.input(RcFileSuggestionsInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input: input2 }) => ({ session: input2.session, ...deliveredControl(input2.session, await deps.fileSuggestions(input2.session, input2.query)) })),
72156
+ keepAlive: authed.input(RcKeepAliveInputSchema).output(RcWriteOutputSchema).handler(async ({ input: input2 }) => ({ session: input2.session, sequenceNums: delivered(input2.session, await deps.keepAlive(input2.session)) })),
72157
+ mcpStatus: authed.input(RcMcpStatusInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input: input2 }) => ({ session: input2.session, ...deliveredControl(input2.session, await deps.mcpStatus(input2.session)) })),
72158
+ mcpReconnect: authed.input(RcMcpReconnectInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input: input2 }) => ({ session: input2.session, ...deliveredControl(input2.session, await deps.mcpReconnect(input2.session, input2.serverName)) })),
72159
+ mcpAuthenticate: authed.input(RcMcpAuthenticateInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input: input2 }) => ({ session: input2.session, ...deliveredControl(input2.session, await deps.mcpAuthenticate(input2.session, input2.serverName, input2.redirectUri)) })),
72160
+ mcpOAuthCallbackUrl: authed.input(RcMcpOAuthCallbackUrlInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input: input2 }) => ({ session: input2.session, ...deliveredControl(input2.session, await deps.mcpOAuthCallbackUrl(input2.session, input2.serverName, input2.callbackUrl)) })),
71888
72161
  subscribe: authed.input(RcSessionQuerySchema).output(eventIterator(RcStreamEventSchema)).handler(async function* ({ input: input2, signal }) {
71889
72162
  const queue = [];
71890
72163
  let wake;
@@ -73459,1419 +73732,884 @@ function attachingProviderCredential(route, deps) {
73459
73732
  };
73460
73733
  }
73461
73734
 
73462
- // src/frontdoor/rcCredentialStore.ts
73735
+ // src/frontdoor/rcCommands.ts
73736
+ var import_node_crypto14 = require("node:crypto");
73737
+
73738
+ // src/frontdoor/rcSelfHostMint.ts
73463
73739
  var import_node_path34 = __toESM(require("node:path"), 1);
73464
- function isSafeSessionId(sessionId) {
73465
- return /^[A-Za-z0-9_-]+$/.test(sessionId);
73740
+
73741
+ // src/frontdoor/rcWebFetch.ts
73742
+ var dns2 = __toESM(require("node:dns"), 1);
73743
+ var http6 = __toESM(require("node:http"), 1);
73744
+ var https5 = __toESM(require("node:https"), 1);
73745
+ var net3 = __toESM(require("node:net"), 1);
73746
+ var RC_WEB_FETCH_MAX_BYTES = 12582912;
73747
+ var RC_WEB_FETCH_DEADLINE_MS = 4e4;
73748
+ var RC_WEB_FETCH_MAX_REDIRECTS = 20;
73749
+ var RC_WEB_FETCH_ALLOW_PRIVATE_ENV = "AGENT_SHIM_FRONTDOOR_RC_SELF_HOST_WEB_FETCH_ALLOW_PRIVATE";
73750
+ function rcWebFetchAllowsPrivateFromEnv(env) {
73751
+ return env[RC_WEB_FETCH_ALLOW_PRIVATE_ENV] === "1";
73466
73752
  }
73467
- function isRecord4(value2) {
73468
- return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
73753
+ var WEB_FETCH_USER_AGENT = "agent-shim-frontdoor";
73754
+ var ERROR_PREFIX = "web_fetch_";
73755
+ var V4_THIS_NETWORK_OCTET = 0;
73756
+ var V4_PRIVATE_A_OCTET = 10;
73757
+ var V4_LOOPBACK_OCTET = 127;
73758
+ var V4_CGNAT_OCTET = 100;
73759
+ var V4_CGNAT_SECOND_FROM = 64;
73760
+ var V4_CGNAT_SECOND_TO = 127;
73761
+ var V4_LINK_LOCAL_OCTET = 169;
73762
+ var V4_LINK_LOCAL_SECOND = 254;
73763
+ var V4_PRIVATE_B_OCTET = 172;
73764
+ var V4_PRIVATE_B_SECOND_FROM = 16;
73765
+ var V4_PRIVATE_B_SECOND_TO = 31;
73766
+ var V4_PRIVATE_C_OCTET = 192;
73767
+ var V4_PRIVATE_C_SECOND = 168;
73768
+ var V4_MULTICAST_FROM_OCTET = 224;
73769
+ var V6_UNIQUE_LOCAL_FROM = 64512;
73770
+ var V6_UNIQUE_LOCAL_TO = 65023;
73771
+ var V6_LINK_LOCAL_FROM = 65152;
73772
+ var V6_LINK_LOCAL_TO = 65215;
73773
+ var V6_MULTICAST_FROM = 65280;
73774
+ var IP_VERSION_4 = 4;
73775
+ var IP_VERSION_6 = 6;
73776
+ var HTTP_MOVED_PERMANENTLY = 301;
73777
+ var HTTP_FOUND = 302;
73778
+ var HTTP_SEE_OTHER = 303;
73779
+ var HTTP_TEMPORARY_REDIRECT = 307;
73780
+ var HTTP_PERMANENT_REDIRECT = 308;
73781
+ var SUCCESS_STATUS_MAX_EXCLUSIVE3 = 300;
73782
+ function refused(errorType, errorMessage2) {
73783
+ return { kind: "refused", errorType: `${ERROR_PREFIX}${errorType}`, errorMessage: errorMessage2 };
73469
73784
  }
73470
- function credentialOfJson(parsed) {
73471
- if (!isRecord4(parsed)) {
73472
- return void 0;
73785
+ function isPrivateAddress(address) {
73786
+ const version2 = net3.isIP(address);
73787
+ if (version2 === IP_VERSION_4) {
73788
+ const octets = address.split(".").map((part) => Number(part));
73789
+ const first = octets[0] ?? -1;
73790
+ const second = octets[1] ?? -1;
73791
+ if (first === V4_THIS_NETWORK_OCTET || first === V4_PRIVATE_A_OCTET || first === V4_LOOPBACK_OCTET) {
73792
+ return true;
73793
+ }
73794
+ if (first === V4_CGNAT_OCTET && second >= V4_CGNAT_SECOND_FROM && second <= V4_CGNAT_SECOND_TO) {
73795
+ return true;
73796
+ }
73797
+ if (first === V4_LINK_LOCAL_OCTET && second === V4_LINK_LOCAL_SECOND) {
73798
+ return true;
73799
+ }
73800
+ if (first === V4_PRIVATE_B_OCTET && second >= V4_PRIVATE_B_SECOND_FROM && second <= V4_PRIVATE_B_SECOND_TO) {
73801
+ return true;
73802
+ }
73803
+ if (first === V4_PRIVATE_C_OCTET && second === V4_PRIVATE_C_SECOND) {
73804
+ return true;
73805
+ }
73806
+ return first >= V4_MULTICAST_FROM_OCTET;
73473
73807
  }
73474
- const { authorization, anthropicVersion, anthropicClientPlatform } = parsed;
73475
- if (typeof authorization !== "string" || authorization === "") {
73476
- return void 0;
73477
- }
73478
- return {
73479
- authorization,
73480
- anthropicVersion: typeof anthropicVersion === "string" ? anthropicVersion : void 0,
73481
- anthropicClientPlatform: typeof anthropicClientPlatform === "string" ? anthropicClientPlatform : void 0
73482
- };
73483
- }
73484
- function sequenceNumOfJson(value2) {
73485
- return typeof value2 === "number" && Number.isInteger(value2) && value2 >= 0 ? value2 : void 0;
73486
- }
73487
- function createRcCredentialStore(fs20, dir) {
73488
- fs20.mkdirPrivate(dir);
73489
- const fileFor = (sessionId) => import_node_path34.default.join(dir, `${sessionId}.json`);
73490
- const readParsed = (sessionId) => {
73491
- const raw = fs20.readFileUtf8(fileFor(sessionId));
73492
- if (raw === void 0) {
73493
- return void 0;
73808
+ if (version2 === IP_VERSION_6) {
73809
+ const lower = address.toLowerCase();
73810
+ const mapped = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/.exec(lower);
73811
+ if (mapped !== null) {
73812
+ return isPrivateAddress(mapped[1] ?? "");
73494
73813
  }
73495
- let parsed;
73496
- try {
73497
- parsed = JSON.parse(raw);
73498
- } catch {
73499
- return void 0;
73814
+ if (lower === "::" || lower === "::1") {
73815
+ return true;
73500
73816
  }
73501
- return isRecord4(parsed) ? parsed : void 0;
73502
- };
73503
- return {
73504
- read: (sessionId) => {
73505
- if (!isSafeSessionId(sessionId)) {
73506
- return void 0;
73507
- }
73508
- return credentialOfJson(readParsed(sessionId));
73509
- },
73510
- write: (sessionId, credential) => {
73511
- if (!isSafeSessionId(sessionId)) {
73512
- return;
73513
- }
73514
- const cursor3 = sequenceNumOfJson(readParsed(sessionId)?.highestSequenceNum);
73515
- const record2 = { ...credential, ...cursor3 === void 0 ? {} : { highestSequenceNum: cursor3 } };
73516
- fs20.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(record2, null, 2)}
73517
- `);
73518
- },
73519
- readCursor: (sessionId) => {
73520
- if (!isSafeSessionId(sessionId)) {
73521
- return void 0;
73522
- }
73523
- return sequenceNumOfJson(readParsed(sessionId)?.highestSequenceNum);
73524
- },
73525
- writeCursor: (sessionId, highestSequenceNum) => {
73526
- if (!isSafeSessionId(sessionId)) {
73527
- return;
73528
- }
73529
- const credential = credentialOfJson(readParsed(sessionId));
73530
- const record2 = credential === void 0 ? { highestSequenceNum } : { ...credential, highestSequenceNum };
73531
- fs20.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(record2, null, 2)}
73532
- `);
73533
- },
73534
- remove: (sessionId) => {
73535
- if (!isSafeSessionId(sessionId)) {
73536
- return;
73537
- }
73538
- fs20.removeRecursive(fileFor(sessionId));
73817
+ const firstGroup = Number.parseInt(/^([0-9a-f]+):/.exec(lower)?.[1] ?? "0", 16);
73818
+ if (Number.isNaN(firstGroup)) {
73819
+ return true;
73539
73820
  }
73540
- };
73541
- }
73542
-
73543
- // src/frontdoor/rcClientPage.ts
73544
- var RC_CLIENT_PATH = "/__agent-shim/client";
73545
- var RC_CLIENT_PAGE_SCRIPT = `'use strict';
73546
- // 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.
73547
- var API_PREFIX = '/__agent-shim/orpc';
73548
- var TOKEN_KEY = 'agent-shim-rc-token';
73549
- var token = null;
73550
-
73551
- function apiBase() { return window.location.origin + API_PREFIX; }
73552
-
73553
- // Calls one procedure: resolves the output's own value, rejects with the door's error message when it answered one.
73554
- async function rpc(path, input) {
73555
- var response = await fetch(apiBase() + '/' + path, {
73556
- method: 'POST',
73557
- headers: { 'content-type': 'application/json', authorization: 'Bearer ' + token },
73558
- body: JSON.stringify({ json: input === undefined ? {} : input }),
73559
- });
73560
- var envelope = await response.json().catch(function () { return {}; });
73561
- if (!response.ok) {
73562
- var message = envelope && envelope.json && envelope.json.message ? envelope.json.message : ('HTTP ' + response.status);
73563
- var error = new Error(message);
73564
- error.status = response.status;
73565
- throw error;
73566
- }
73567
- return envelope.json;
73568
- }
73569
-
73570
- // 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.
73571
- function parseSseFrames(buffer) {
73572
- var frames = [];
73573
- var rest = buffer;
73574
- for (;;) {
73575
- var boundary = rest.indexOf('\\n\\n');
73576
- if (boundary === -1) { break; }
73577
- var raw = rest.slice(0, boundary);
73578
- rest = rest.slice(boundary + 2);
73579
- var frame = { event: 'message', id: '', data: '' };
73580
- var lines = raw.split('\\n');
73581
- for (var i = 0; i < lines.length; i++) {
73582
- var line = lines[i];
73583
- if (line === '' || line.charAt(0) === ':') { continue; }
73584
- var colon = line.indexOf(':');
73585
- var field = colon === -1 ? line : line.slice(0, colon);
73586
- var value = colon === -1 ? '' : line.slice(colon + 1);
73587
- if (value.charAt(0) === ' ') { value = value.slice(1); }
73588
- if (field === 'event') { frame.event = value; }
73589
- if (field === 'id') { frame.id = value; }
73590
- if (field === 'data') { frame.data += (frame.data === '' ? '' : '\\n') + value; }
73821
+ if (firstGroup >= V6_UNIQUE_LOCAL_FROM && firstGroup <= V6_UNIQUE_LOCAL_TO) {
73822
+ return true;
73591
73823
  }
73592
- if (frame.data !== '') { frames.push(frame); }
73824
+ if (firstGroup >= V6_LINK_LOCAL_FROM && firstGroup <= V6_LINK_LOCAL_TO) {
73825
+ return true;
73826
+ }
73827
+ return firstGroup >= V6_MULTICAST_FROM;
73593
73828
  }
73594
- return { frames: frames, rest: rest };
73829
+ return true;
73595
73830
  }
73596
-
73597
- // 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.
73598
- function sequenceGap(tracker, event) {
73599
- var previous = tracker[event.source];
73600
- if (event.sequence > (previous === undefined ? 0 : previous)) { tracker[event.source] = event.sequence; }
73601
- if (previous === undefined || event.sequence <= previous) { return 0; }
73602
- return event.sequence - previous - 1;
73831
+ function isSuccessful3(status) {
73832
+ return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE3;
73603
73833
  }
73604
-
73605
- function shortId(id) { return id.length > 14 ? id.slice(0, 8) + '..' + id.slice(-4) : id; }
73606
-
73607
- // A bounded sketch of any payload, the feed's fallback rendering: one JSON line, cut with a marker rather than silently.
73608
- function sketch(value, cap) {
73609
- var text;
73610
- try { text = JSON.stringify(value); } catch (e) { text = String(value); }
73611
- if (text === undefined) { text = 'undefined'; }
73612
- return text.length > cap ? text.slice(0, cap) + '...' : text;
73834
+ function isRedirect(status) {
73835
+ return status === HTTP_MOVED_PERMANENTLY || status === HTTP_FOUND || status === HTTP_SEE_OTHER || status === HTTP_TEMPORARY_REDIRECT || status === HTTP_PERMANENT_REDIRECT;
73613
73836
  }
73614
-
73615
- // The headline text of one Remote Control stream event: 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.
73616
- function rcLine(event) {
73617
- var envelope = event.envelope;
73618
- var payload = envelope.payload;
73619
- var headline = '';
73620
- if (payload !== undefined && payload !== null && typeof payload === 'object' && Array.isArray(payload.content)) {
73621
- var parts = [];
73622
- for (var i = 0; i < payload.content.length; i++) {
73623
- var block = payload.content[i];
73624
- if (block !== null && typeof block === 'object' && block.type === 'text' && typeof block.text === 'string') { parts.push(block.text); }
73625
- }
73626
- if (parts.length > 0) { headline = parts.join(' '); }
73837
+ function readableContentType(contentType) {
73838
+ if (contentType === void 0 || contentType.trim() === "") {
73839
+ return true;
73627
73840
  }
73628
- if (headline === '') {
73629
- headline = envelope.event_type;
73630
- if (payload !== undefined) { headline += ' ' + sketch(payload, 240); }
73841
+ const normalised = contentType.split(";")[0]?.trim().toLowerCase() ?? "";
73842
+ const slash = normalised.indexOf("/");
73843
+ const main2 = slash === -1 ? normalised : normalised.slice(0, slash);
73844
+ const sub = slash === -1 ? "" : normalised.slice(slash + 1);
73845
+ if (main2 === "text") {
73846
+ return true;
73631
73847
  }
73632
- return new Date().toLocaleTimeString() + ' ' + shortId(event.session) + ' ' + envelope.event_type + ' ' + headline;
73848
+ return /json|xml|javascript|ecmascript|yaml|csv/.test(sub);
73633
73849
  }
73634
-
73635
- function el(tag, className, text) {
73636
- var node = document.createElement(tag);
73637
- if (className) { node.className = className; }
73638
- if (text !== undefined) { node.textContent = text; }
73639
- return node;
73850
+ async function readCapped(chunks) {
73851
+ const collected = [];
73852
+ let total = 0;
73853
+ for await (const chunk of chunks) {
73854
+ total += chunk.byteLength;
73855
+ if (total > RC_WEB_FETCH_MAX_BYTES) {
73856
+ return { tooLarge: true, text: "" };
73857
+ }
73858
+ collected.push(Buffer.from(chunk));
73859
+ }
73860
+ return { tooLarge: false, text: Buffer.concat(collected).toString("utf8") };
73640
73861
  }
73641
-
73642
- var ui = null;
73643
-
73644
- function renderEvent(event, gap) {
73645
- if (ui === null) { return; }
73646
- var list = ui.eventList;
73647
- if (gap > 0) {
73648
- list.appendChild(el('div', 'event gap', 'dropped events: ' + String(gap) + ' on source ' + event.source + ' (the stream holds no replay)'));
73862
+ async function checkTarget(url2, deps) {
73863
+ if (url2.protocol !== "http:" && url2.protocol !== "https:") {
73864
+ return refused("scheme", `the web-fetch proxy dials http and https only, not ${url2.protocol}`);
73649
73865
  }
73650
- var line;
73651
- if (event.source === 'rc') {
73652
- line = rcLine(event.payload);
73653
- } else {
73654
- line = new Date().toLocaleTimeString() + ' [' + event.source + '] ' + sketch(event.payload, 240);
73866
+ if (deps.allowPrivate) {
73867
+ return void 0;
73655
73868
  }
73656
- list.appendChild(el('div', 'event', line));
73657
- while (list.childElementCount > 500) { list.removeChild(list.firstChild); }
73658
- list.scrollTop = list.scrollHeight;
73659
- }
73660
-
73661
- function renderSessions(statuses) {
73662
- if (ui === null) { return; }
73663
- var list = ui.sessionList;
73664
- list.textContent = '';
73665
- if (statuses.length === 0) {
73666
- list.appendChild(el('div', 'event dim', 'no Remote Control sessions observed'));
73667
- return;
73869
+ const hostname3 = url2.hostname.replace(/^\[|\]$/g, "");
73870
+ let addresses;
73871
+ try {
73872
+ addresses = net3.isIP(hostname3) === 0 ? await deps.lookup(hostname3) : [hostname3];
73873
+ } catch {
73874
+ return refused("resolve", `${hostname3} did not resolve`);
73668
73875
  }
73669
- for (var i = 0; i < statuses.length; i++) {
73670
- var status = statuses[i];
73671
- var worker = status.workerState === undefined ? 'worker unknown' : 'worker ' + status.workerState.value;
73672
- var row = el('div', 'session' + (status.id === ui.selected ? ' selected' : ''));
73673
- row.appendChild(el('div', null, shortId(status.id)));
73674
- row.appendChild(el('div', 'dim', worker + (status.pending.length === 0 ? '' : ', ' + String(status.pending.length) + ' pending') + ', seen ' + new Date(status.lastSeenAt).toLocaleTimeString()));
73675
- row.addEventListener('click', selectSession(status.id));
73676
- list.appendChild(row);
73876
+ for (const address of addresses) {
73877
+ if (isPrivateAddress(address)) {
73878
+ 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`);
73879
+ }
73677
73880
  }
73881
+ return void 0;
73678
73882
  }
73679
-
73680
- function selectSession(id) {
73681
- return function () {
73682
- if (ui === null) { return; }
73683
- ui.selected = ui.selected === id ? null : id;
73684
- ui.promptTarget.textContent = ui.selected === null ? 'no session selected' : 'to ' + ui.selected;
73685
- refreshSessions();
73686
- refreshPending();
73687
- };
73688
- }
73689
-
73690
- function renderPending(pending) {
73691
- if (ui === null) { return; }
73692
- var list = ui.pendingList;
73693
- list.textContent = '';
73694
- if (pending.length === 0) {
73695
- list.appendChild(el('div', 'event dim', 'no control requests awaiting an answer'));
73696
- return;
73883
+ async function fetchThroughWebProxy(url2, deps) {
73884
+ let current;
73885
+ try {
73886
+ current = new URL(url2);
73887
+ } catch {
73888
+ return refused("malformed_url", `${url2} is not a URL this proxy can dial`);
73697
73889
  }
73698
- for (var i = 0; i < pending.length; i++) {
73699
- var request = pending[i];
73700
- var card = el('div', 'card');
73701
- card.appendChild(el('div', null, request.type + ' ' + (request.summary === '' ? shortId(request.requestId) : request.summary)));
73702
- card.appendChild(el('div', 'dim', shortId(request.sessionId) + ' ' + shortId(request.requestId) + ' observed ' + new Date(request.observedAt).toLocaleTimeString()));
73703
- var row = el('div', 'row');
73704
- var denial = el('input');
73705
- denial.type = 'text';
73706
- denial.placeholder = 'denial message (optional)';
73707
- var approve = el('button', null, 'Approve');
73708
- var deny = el('button', 'secondary', 'Deny');
73709
- approve.addEventListener('click', answerRequest(request, true, denial));
73710
- deny.addEventListener('click', answerRequest(request, false, denial));
73711
- row.appendChild(denial);
73712
- row.appendChild(approve);
73713
- row.appendChild(deny);
73714
- card.appendChild(row);
73715
- list.appendChild(card);
73890
+ const controller = new AbortController();
73891
+ const timer = setTimeout(() => {
73892
+ controller.abort();
73893
+ }, deps.deadlineMs);
73894
+ timer.unref();
73895
+ try {
73896
+ let redirects = 0;
73897
+ for (; ; ) {
73898
+ const refusal = await checkTarget(current, deps);
73899
+ if (refusal !== void 0) {
73900
+ return refusal;
73901
+ }
73902
+ let hop;
73903
+ try {
73904
+ hop = await deps.dial(current, controller.signal);
73905
+ } catch (error62) {
73906
+ if (controller.signal.aborted) {
73907
+ return refused("deadline", `the fetch of ${url2} did not settle within its deadline`);
73908
+ }
73909
+ return refused("transport", `the dial of ${current.toString()} failed: ${error62 instanceof Error ? error62.message : "unknown error"}`);
73910
+ }
73911
+ if (isRedirect(hop.status) && hop.location !== void 0) {
73912
+ if (redirects >= RC_WEB_FETCH_MAX_REDIRECTS) {
73913
+ controller.abort();
73914
+ return refused("too_many_redirects", `${url2} followed more than ${String(RC_WEB_FETCH_MAX_REDIRECTS)} redirects without settling`);
73915
+ }
73916
+ let next;
73917
+ try {
73918
+ next = new URL(hop.location, current);
73919
+ } catch {
73920
+ controller.abort();
73921
+ return refused("malformed_url", `a redirect of ${current.toString()} named ${hop.location}, which is not a URL this proxy can dial`);
73922
+ }
73923
+ const drained = await readCapped(hop.chunks);
73924
+ if (drained.tooLarge) {
73925
+ controller.abort();
73926
+ 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`);
73927
+ }
73928
+ redirects += 1;
73929
+ current = next;
73930
+ continue;
73931
+ }
73932
+ if (!isSuccessful3(hop.status)) {
73933
+ controller.abort();
73934
+ return refused("status", `the target answered HTTP ${String(hop.status)} for ${current.toString()}`);
73935
+ }
73936
+ const contentType = hop.contentType ?? "";
73937
+ if (!readableContentType(contentType)) {
73938
+ controller.abort();
73939
+ return refused("content_type", `the target at ${current.toString()} served a ${contentType} answer, which the proxy's text field cannot carry`);
73940
+ }
73941
+ const read = await readCapped(hop.chunks);
73942
+ if (read.tooLarge) {
73943
+ controller.abort();
73944
+ 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`);
73945
+ }
73946
+ return { kind: "fetched", url: url2, destinationUrl: current.toString(), contentType: hop.contentType, text: read.text };
73947
+ }
73948
+ } finally {
73949
+ clearTimeout(timer);
73716
73950
  }
73717
73951
  }
73718
-
73719
- function answerRequest(request, approve, denialInput) {
73720
- return function () {
73721
- var input = { session: request.sessionId, request: request.requestId, approve: approve };
73722
- if (!approve && denialInput.value !== '') { input.text = denialInput.value; }
73723
- rpc('rc/answer', input).then(function () {
73724
- renderEvent({ source: 'page', sequence: 0, payload: { note: (approve ? 'approved' : 'denied') + ' ' + request.requestId } }, 0);
73725
- refreshPending();
73726
- }, function (error) { window.alert('answer failed: ' + error.message); });
73952
+ function realRcWebFetch(options) {
73953
+ const plainAgent = new ExemptHttpAgent();
73954
+ const tlsAgent = new ExemptTlsAgent();
73955
+ const deps = {
73956
+ dial: async (url2, signal) => await new Promise((resolve, reject) => {
73957
+ const request7 = (url2.protocol === "https:" ? https5 : http6).request(
73958
+ {
73959
+ hostname: url2.hostname,
73960
+ port: url2.port === "" ? void 0 : Number(url2.port),
73961
+ method: "GET",
73962
+ path: `${url2.pathname}${url2.search}`,
73963
+ headers: { accept: "*/*", "accept-encoding": "identity", "user-agent": WEB_FETCH_USER_AGENT },
73964
+ agent: url2.protocol === "https:" ? tlsAgent : plainAgent,
73965
+ signal
73966
+ },
73967
+ (response) => {
73968
+ const location = response.headers.location;
73969
+ resolve({
73970
+ status: response.statusCode ?? 0,
73971
+ location: typeof location === "string" ? location : void 0,
73972
+ contentType: typeof response.headers["content-type"] === "string" ? response.headers["content-type"] : void 0,
73973
+ chunks: response
73974
+ });
73975
+ }
73976
+ );
73977
+ request7.once("error", reject);
73978
+ request7.end();
73979
+ }),
73980
+ lookup: async (hostname3) => (await dns2.promises.lookup(hostname3, { all: true })).map((entry) => entry.address),
73981
+ allowPrivate: options.allowPrivate,
73982
+ deadlineMs: RC_WEB_FETCH_DEADLINE_MS
73727
73983
  };
73984
+ return async (url2) => await fetchThroughWebProxy(url2, deps);
73728
73985
  }
73729
73986
 
73730
- function sendPrompt() {
73731
- if (ui === null || ui.selected === null) { window.alert('select a session first'); return; }
73732
- var text = ui.prompt.value;
73733
- if (text === '') { return; }
73734
- ui.send.disabled = true;
73735
- rpc('rc/send', { session: ui.selected, text: text }).then(function (result) {
73736
- ui.prompt.value = '';
73737
- renderEvent({ source: 'page', sequence: 0, payload: { note: 'sent, sequence_num ' + result.sequenceNums.join(', ') } }, 0);
73738
- }, function (error) { window.alert('send failed: ' + error.message); }).then(function () { ui.send.disabled = false; });
73739
- }
73740
-
73741
- function refreshSessions() {
73742
- rpc('rc/status', {}).then(function (result) { renderSessions(result.statuses); }, authOrReport('rc/status'));
73987
+ // src/frontdoor/rcSelfHost.ts
73988
+ var RC_SELF_HOST_ENV = "AGENT_SHIM_FRONTDOOR_RC_SELF_HOST";
73989
+ function rcSelfHostFromEnv(env) {
73990
+ return env[RC_SELF_HOST_ENV] === "1";
73743
73991
  }
73744
-
73745
- function refreshPending() {
73746
- rpc('rc/pending', ui !== null && ui.selected !== null ? { session: ui.selected } : {}).then(function (result) { renderPending(result.pending); }, authOrReport('rc/pending'));
73992
+ var RC_SELF_HOST_SCOPE_LIST = ["user:profile", "user:inference", "user:sessions:claude_code", "user:mcp_servers", "user:file_upload"];
73993
+ var RC_SESSIONS_PATH_PREFIX2 = "/v1/code/sessions";
73994
+ var RC_COMPAT_SESSIONS_PATH = "/v1/sessions";
73995
+ var RC_SESSION_ID_PREFIX2 = "cse_";
73996
+ var RC_SELF_HOST_API_BASE_URL = "https://api.anthropic.com";
73997
+ var RC_SELF_HOST_OAUTH_HOST = "platform.claude.com";
73998
+ var HTTP_CONFLICT = 409;
73999
+ var MS_PER_SECOND7 = 1e3;
74000
+ var HTTP_SERVICE_UNAVAILABLE = 503;
74001
+ var RC_SELF_HOST_WORKER_JWT_TTL_SECONDS = 46800;
74002
+ var RC_SELF_HOST_OAUTH_TOKEN_TTL_SECONDS = 3600;
74003
+ var RC_SELF_HOST_KEEPALIVE_MS = 15e3;
74004
+ var RC_SELF_HOST_RETENTION_MS = 6e5;
74005
+ var RC_PRESENCE_REFRESH_SECONDS = 60;
74006
+ var RC_SELF_HOST_MAX_BATCH_EVENTS = 100;
74007
+ var RC_SELF_HOST_WORKER_EPOCH = 1;
74008
+ var RC_STREAM_EVENT_NAME = "client_event";
74009
+ var RC_SOURCE_WORKER = "worker";
74010
+ var RC_SOURCE_CLIENT = "client";
74011
+ var CONFLICT_REASON_HEADER2 = "x-ccr-conflict-reason";
74012
+ var CONFLICT_REASON_EPOCH_STALE = "epoch_stale";
74013
+ function isRecord4(value2) {
74014
+ return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
73747
74015
  }
73748
-
73749
- // 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.
73750
- function authOrReport(what) {
73751
- return function (error) {
73752
- if (error.status === 401) {
73753
- handleUnauthorized();
73754
- return;
73755
- }
73756
- renderEvent({ source: 'page', sequence: 0, payload: { note: what + ' failed: ' + error.message } }, 0);
73757
- };
74016
+ function isWebSearchRefusal(value2) {
74017
+ return !Array.isArray(value2);
73758
74018
  }
73759
-
73760
- function handleUnauthorized() {
73761
- window.sessionStorage.removeItem(TOKEN_KEY);
73762
- if (feedAbort !== null) { feedAbort.abort(); }
73763
- streamState('');
73764
- showTokenForm('this door refused the token: it names a different door generation, paste the current one');
74019
+ function nonEmptyString2(record2, field) {
74020
+ const value2 = record2 === void 0 ? void 0 : record2[field];
74021
+ return typeof value2 === "string" && value2 !== "" ? value2 : void 0;
73765
74022
  }
73766
-
73767
- function streamState(text) { if (ui !== null) { ui.streamState.textContent = text; } }
73768
-
73769
- var feedAbort = null;
73770
-
73771
- // 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.
73772
- async function runFeed() {
73773
- var tracker = {};
73774
- for (;;) {
73775
- if (feedAbort !== null) { feedAbort.abort(); }
73776
- feedAbort = new AbortController();
73777
- var filter = document.getElementById('source-filter').value;
73778
- var input = filter === '' ? {} : { sources: [filter] };
73779
- try {
73780
- streamState('stream: connecting');
73781
- var response = await fetch(apiBase() + '/events/subscribe', {
73782
- method: 'POST',
73783
- headers: { 'content-type': 'application/json', authorization: 'Bearer ' + token },
73784
- body: JSON.stringify({ json: input }),
73785
- signal: feedAbort.signal,
73786
- });
73787
- if (response.status === 401) { handleUnauthorized(); return; }
73788
- if (!response.ok || response.body === null) { throw new Error('HTTP ' + String(response.status)); }
73789
- streamState('stream: live');
73790
- var reader = response.body.getReader();
73791
- var decoder = new TextDecoder();
73792
- var buffer = '';
73793
- for (;;) {
73794
- var chunk = await reader.read();
73795
- if (chunk.done) { break; }
73796
- buffer += decoder.decode(chunk.value, { stream: true });
73797
- var parsed = parseSseFrames(buffer);
73798
- buffer = parsed.rest;
73799
- for (var i = 0; i < parsed.frames.length; i++) {
73800
- var frame = parsed.frames[i];
73801
- if (frame.event !== 'message') { continue; }
73802
- var event;
73803
- try { event = JSON.parse(frame.data).json; } catch (e) { continue; }
73804
- if (event === undefined || event.source === undefined) { continue; }
73805
- renderEvent(event, sequenceGap(tracker, event));
73806
- }
73807
- }
73808
- } catch (error) {
73809
- if (feedAbort !== null && feedAbort.signal.aborted) { return; }
73810
- }
73811
- streamState('stream: dropped, reconnecting');
73812
- await new Promise(function (resolve) { window.setTimeout(resolve, 1000); });
74023
+ function numberOf(record2, field) {
74024
+ const value2 = record2 === void 0 ? void 0 : record2[field];
74025
+ if (typeof value2 === "number" && Number.isFinite(value2)) {
74026
+ return value2;
74027
+ }
74028
+ if (typeof value2 === "string" && /^[0-9]+$/.test(value2)) {
74029
+ return Number(value2);
73813
74030
  }
74031
+ return void 0;
73814
74032
  }
73815
-
73816
- function showTokenForm(message) {
73817
- if (feedAbort !== null) { feedAbort.abort(); }
73818
- if (ui !== null) { ui.main.classList.add('hidden'); }
73819
- var form = document.getElementById('token-form');
73820
- form.classList.remove('hidden');
73821
- document.getElementById('token-error').textContent = message === undefined ? '' : message;
73822
- document.getElementById('token-input').focus();
73823
- }
73824
-
73825
- function startPage(heldToken) {
73826
- token = heldToken;
73827
- ui = {
73828
- main: document.getElementById('main'),
73829
- eventList: document.getElementById('event-list'),
73830
- sessionList: document.getElementById('session-list'),
73831
- pendingList: document.getElementById('pending-list'),
73832
- prompt: document.getElementById('prompt'),
73833
- promptTarget: document.getElementById('prompt-target'),
73834
- send: document.getElementById('send'),
73835
- streamState: document.getElementById('stream-state'),
73836
- selected: null,
73837
- };
73838
- document.getElementById('token-form').classList.add('hidden');
73839
- ui.main.classList.remove('hidden');
73840
- document.getElementById('refresh').addEventListener('click', function () { refreshSessions(); refreshPending(); });
73841
- document.getElementById('send').addEventListener('click', sendPrompt);
73842
- document.getElementById('source-filter').addEventListener('change', function () { runFeed(); });
73843
- ui.prompt.addEventListener('keydown', function (event) {
73844
- if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) { sendPrompt(); }
73845
- });
73846
- refreshSessions();
73847
- refreshPending();
73848
- window.setInterval(function () { refreshSessions(); refreshPending(); }, 4000);
73849
- runFeed();
73850
- document.getElementById('door-state').textContent = 'connected';
73851
- }
73852
-
73853
- function boot() {
73854
- var held = null;
73855
- var query = new URLSearchParams(window.location.search);
73856
- var fromQuery = query.get('token');
73857
- if (fromQuery !== null && fromQuery !== '') {
73858
- window.sessionStorage.setItem(TOKEN_KEY, fromQuery);
73859
- // 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.
73860
- window.history.replaceState(null, '', window.location.pathname);
73861
- held = fromQuery;
73862
- } else {
73863
- held = window.sessionStorage.getItem(TOKEN_KEY);
74033
+ function stringArray(record2, field) {
74034
+ const value2 = record2 === void 0 ? void 0 : record2[field];
74035
+ if (!Array.isArray(value2)) {
74036
+ return void 0;
73864
74037
  }
73865
- document.getElementById('token-save').addEventListener('click', function () {
73866
- var value = document.getElementById('token-input').value.trim();
73867
- if (value === '') { return; }
73868
- window.sessionStorage.setItem(TOKEN_KEY, value);
73869
- startPage(value);
73870
- });
73871
- document.getElementById('token-input').addEventListener('keydown', function (event) {
73872
- if (event.key === 'Enter') { document.getElementById('token-save').click(); }
73873
- });
73874
- if (held !== null && held !== '') { startPage(held); }
73875
- }
73876
-
73877
- if (typeof document !== 'undefined' && document.getElementById('event-list') !== null) {
73878
- window.addEventListener('DOMContentLoaded', boot);
74038
+ const members2 = [];
74039
+ for (const member of value2) {
74040
+ if (typeof member !== "string") {
74041
+ return void 0;
74042
+ }
74043
+ members2.push(member);
74044
+ }
74045
+ return members2;
73879
74046
  }
73880
- `;
73881
- var RC_CLIENT_PAGE_HTML = `<!doctype html>
73882
- <html lang="en">
73883
- <head>
73884
- <meta charset="utf-8">
73885
- <meta name="viewport" content="width=device-width, initial-scale=1">
73886
- <title>agent-shim Remote Control</title>
73887
- <style>
73888
- :root { color-scheme: dark; }
73889
- * { box-sizing: border-box; }
73890
- body { margin: 0; background: #101418; color: #d7dde3; font: 14px/1.45 ui-monospace, Menlo, Consolas, monospace; }
73891
- header { display: flex; gap: 12px; align-items: baseline; padding: 10px 16px; background: #171d24; border-bottom: 1px solid #2a333d; }
73892
- header h1 { font-size: 15px; margin: 0; font-weight: 600; }
73893
- header .state { color: #8b98a5; }
73894
- main { display: flex; gap: 1px; background: #2a333d; min-height: calc(100vh - 39px); }
73895
- section { background: #101418; padding: 12px 16px; overflow-y: auto; }
73896
- #sessions { flex: 0 0 320px; border-right: 1px solid #2a333d; }
73897
- #right { flex: 1; display: flex; flex-direction: column; min-width: 0; }
73898
- #feed { flex: 1; }
73899
- #controls { border-top: 1px solid #2a333d; }
73900
- h2 { font-size: 12px; text-transform: uppercase; letter-spacing: 0.08em; color: #8b98a5; margin: 8px 0 8px; font-weight: 600; }
73901
- .session { padding: 6px 8px; border: 1px solid transparent; border-radius: 4px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
73902
- .session:hover { background: #171d24; }
73903
- .session.selected { border-color: #3d6ea5; background: #17222e; }
73904
- .session .dim { color: #8b98a5; }
73905
- .event { padding: 2px 0; white-space: pre-wrap; word-break: break-word; }
73906
- .event .dim { color: #8b98a5; }
73907
- .event.gap { color: #d29922; }
73908
- .event.error { color: #e5534b; }
73909
- textarea, input[type=text], select { background: #171d24; color: #d7dde3; border: 1px solid #2a333d; border-radius: 4px; padding: 6px 8px; font: inherit; width: 100%; }
73910
- textarea { resize: vertical; min-height: 44px; }
73911
- button { background: #1f4a7a; color: #d7dde3; border: 1px solid #3d6ea5; border-radius: 4px; padding: 5px 12px; font: inherit; cursor: pointer; }
73912
- button.secondary { background: #2a333d; border-color: #3a444f; }
73913
- button:disabled { opacity: 0.5; cursor: default; }
73914
- .row { display: flex; gap: 8px; margin: 6px 0; align-items: center; }
73915
- .card { border: 1px solid #3d6ea5; border-radius: 4px; padding: 8px; margin: 6px 0; background: #17222e; }
73916
- .card .dim { color: #8b98a5; }
73917
- #token-form { max-width: 480px; margin: 48px auto; }
73918
- #token-form p { color: #8b98a5; }
73919
- .hidden { display: none !important; }
73920
- </style>
73921
- </head>
73922
- <body data-agent-shim-rc-client>
73923
- <header>
73924
- <h1>agent-shim Remote Control</h1>
73925
- <span class="state" id="door-state">connecting</span>
73926
- <span class="state" id="stream-state"></span>
73927
- </header>
73928
- <div id="token-form">
73929
- <h2>This door's control token</h2>
73930
- <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>
73931
- <div class="row"><input type="text" id="token-input" placeholder="control token" autocomplete="off"><button id="token-save">Use</button></div>
73932
- <p id="token-error" class="event error"></p>
73933
- </div>
73934
- <main class="hidden" id="main">
73935
- <section id="sessions">
73936
- <h2>Sessions <button class="secondary" id="refresh">Refresh</button></h2>
73937
- <div id="session-list"></div>
73938
- </section>
73939
- <section id="right">
73940
- <div id="feed">
73941
- <h2>Feed <select id="source-filter"><option value="">all sources</option><option value="rc">Remote Control only</option></select></h2>
73942
- <div id="event-list"></div>
73943
- </div>
73944
- <div id="controls">
73945
- <h2>Pending approvals</h2>
73946
- <div id="pending-list"></div>
73947
- <h2>Send a prompt</h2>
73948
- <div class="row"><textarea id="prompt" placeholder="Prompt for the selected session"></textarea></div>
73949
- <div class="row"><span class="dim" id="prompt-target">no session selected</span><span style="flex:1"></span><button id="send">Send</button></div>
73950
- </div>
73951
- </section>
73952
- </main>
73953
- <script>
73954
- ${RC_CLIENT_PAGE_SCRIPT}
73955
- </script>
73956
- </body>
73957
- </html>
73958
- `;
73959
- function pageTokenOf(request7, url2) {
73960
- const presented = request7.headers.authorization;
73961
- const bearerPrefix = "bearer ".length;
73962
- const header = typeof presented === "string" && presented.slice(0, bearerPrefix).toLowerCase() === "bearer " ? presented.slice(bearerPrefix) : void 0;
73963
- const query = url2.searchParams.get("token") ?? void 0;
73964
- return header ?? query;
74047
+ function singleHeader2(headers, name) {
74048
+ const value2 = headers[name];
74049
+ return typeof value2 === "string" ? value2 : void 0;
73965
74050
  }
73966
- function serveRcClientPage(expectedToken, request7, response) {
73967
- const url2 = new URL(request7.url ?? "/", "https://door.invalid");
73968
- if (url2.pathname !== RC_CLIENT_PATH && url2.pathname !== `${RC_CLIENT_PATH}/`) {
73969
- response.writeHead(HTTP_STATUS.notFound, { "Content-Type": "text/plain" });
73970
- response.end("no such page under the door's client prefix");
73971
- return;
74051
+ function bearerOf(headers) {
74052
+ const presented = singleHeader2(headers, "authorization");
74053
+ if (presented === void 0) {
74054
+ return void 0;
73972
74055
  }
73973
- if (request7.method !== "GET" && request7.method !== "HEAD") {
73974
- response.writeHead(HTTP_STATUS.methodNotAllowed, { Allow: "GET, HEAD", "Content-Type": "text/plain" });
73975
- response.end("the door's web client page is read-only");
73976
- return;
74056
+ return presented.slice(0, "bearer ".length).toLowerCase() === "bearer " ? presented.slice("bearer ".length) : void 0;
74057
+ }
74058
+ function presentationRefusal(headers) {
74059
+ const presented = singleHeader2(headers, "authorization");
74060
+ if (presented === void 0) {
74061
+ return "absent";
73977
74062
  }
73978
- const token = pageTokenOf(request7, url2);
73979
- if (token === void 0 || !isLiveCapability(token, [expectedToken])) {
73980
- response.writeHead(HTTP_STATUS.unauthorized, { "Content-Type": "application/json" });
73981
- 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" }));
73982
- return;
74063
+ if (presented.slice(0, "bearer ".length).toLowerCase() !== "bearer ") {
74064
+ return "not-a-bearer";
73983
74065
  }
73984
- response.writeHead(HTTP_STATUS.ok, {
73985
- "Content-Type": "text/html; charset=utf-8",
73986
- "Cache-Control": "no-store",
73987
- "Referrer-Policy": "no-referrer",
73988
- "Content-Security-Policy": "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'self'"
74066
+ const bearer = presented.slice("bearer ".length);
74067
+ return bearer.slice(0, "bearer ".length).toLowerCase() === "bearer " ? "doubled-scheme-word" : "unrecognised";
74068
+ }
74069
+ async function readBody3(request7, capBytes) {
74070
+ return await new Promise((resolve, reject) => {
74071
+ const chunks = [];
74072
+ let total = 0;
74073
+ request7.on("data", (chunk) => {
74074
+ total += chunk.length;
74075
+ if (total > capBytes) {
74076
+ request7.destroy();
74077
+ reject(new Error("body too large"));
74078
+ return;
74079
+ }
74080
+ chunks.push(chunk);
74081
+ });
74082
+ request7.on("end", () => {
74083
+ resolve(Buffer.concat(chunks).toString("utf8"));
74084
+ });
74085
+ request7.on("error", () => {
74086
+ reject(new Error("the request body ended early"));
74087
+ });
73989
74088
  });
73990
- response.end(request7.method === "HEAD" ? void 0 : RC_CLIENT_PAGE_HTML);
73991
74089
  }
73992
- function createRcClientPage(expectedToken) {
74090
+ function parseJsonObject2(body) {
74091
+ let parsed;
74092
+ try {
74093
+ parsed = JSON.parse(body);
74094
+ } catch {
74095
+ return void 0;
74096
+ }
74097
+ return isRecord4(parsed) ? parsed : void 0;
74098
+ }
74099
+ function mintWorkerJwt(randomToken, issuedAt) {
74100
+ const encode3 = (value2) => Buffer.from(JSON.stringify(value2), "utf8").toString("base64url");
74101
+ const header = encode3({ alg: "HS256", typ: "JWT" });
74102
+ 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) });
74103
+ return `${header}.${payload}.${Buffer.from(randomToken(), "utf8").toString("base64url")}`;
74104
+ }
74105
+ function growthBookFeatures() {
73993
74106
  return {
73994
- pathPrefix: RC_CLIENT_PATH,
73995
- handle: async (request7, response) => {
73996
- serveRcClientPage(expectedToken, request7, response);
73997
- await Promise.resolve();
73998
- return { matched: true };
73999
- }
74107
+ tengu_ccr_bridge: { defaultValue: true },
74108
+ tengu_bridge_repl_v2: { defaultValue: true },
74109
+ tengu_bridge_repl_v2_cse_shim_enabled: { defaultValue: true },
74110
+ tengu_bridge_min_version: { defaultValue: { minVersion: "0.0.0" } }
74000
74111
  };
74001
74112
  }
74002
-
74003
- // src/frontdoor/rcWebFetch.ts
74004
- var dns2 = __toESM(require("node:dns"), 1);
74005
- var http6 = __toESM(require("node:http"), 1);
74006
- var https5 = __toESM(require("node:https"), 1);
74007
- var net3 = __toESM(require("node:net"), 1);
74008
- var RC_WEB_FETCH_MAX_BYTES = 12582912;
74009
- var RC_WEB_FETCH_DEADLINE_MS = 4e4;
74010
- var RC_WEB_FETCH_MAX_REDIRECTS = 20;
74011
- var RC_WEB_FETCH_ALLOW_PRIVATE_ENV = "AGENT_SHIM_FRONTDOOR_RC_SELF_HOST_WEB_FETCH_ALLOW_PRIVATE";
74012
- function rcWebFetchAllowsPrivateFromEnv(env) {
74013
- return env[RC_WEB_FETCH_ALLOW_PRIVATE_ENV] === "1";
74014
- }
74015
- var WEB_FETCH_USER_AGENT = "agent-shim-frontdoor";
74016
- var ERROR_PREFIX = "web_fetch_";
74017
- var V4_THIS_NETWORK_OCTET = 0;
74018
- var V4_PRIVATE_A_OCTET = 10;
74019
- var V4_LOOPBACK_OCTET = 127;
74020
- var V4_CGNAT_OCTET = 100;
74021
- var V4_CGNAT_SECOND_FROM = 64;
74022
- var V4_CGNAT_SECOND_TO = 127;
74023
- var V4_LINK_LOCAL_OCTET = 169;
74024
- var V4_LINK_LOCAL_SECOND = 254;
74025
- var V4_PRIVATE_B_OCTET = 172;
74026
- var V4_PRIVATE_B_SECOND_FROM = 16;
74027
- var V4_PRIVATE_B_SECOND_TO = 31;
74028
- var V4_PRIVATE_C_OCTET = 192;
74029
- var V4_PRIVATE_C_SECOND = 168;
74030
- var V4_MULTICAST_FROM_OCTET = 224;
74031
- var V6_UNIQUE_LOCAL_FROM = 64512;
74032
- var V6_UNIQUE_LOCAL_TO = 65023;
74033
- var V6_LINK_LOCAL_FROM = 65152;
74034
- var V6_LINK_LOCAL_TO = 65215;
74035
- var V6_MULTICAST_FROM = 65280;
74036
- var IP_VERSION_4 = 4;
74037
- var IP_VERSION_6 = 6;
74038
- var HTTP_MOVED_PERMANENTLY = 301;
74039
- var HTTP_FOUND = 302;
74040
- var HTTP_SEE_OTHER = 303;
74041
- var HTTP_TEMPORARY_REDIRECT = 307;
74042
- var HTTP_PERMANENT_REDIRECT = 308;
74043
- var SUCCESS_STATUS_MAX_EXCLUSIVE3 = 300;
74044
- function refused(errorType, errorMessage2) {
74045
- return { kind: "refused", errorType: `${ERROR_PREFIX}${errorType}`, errorMessage: errorMessage2 };
74046
- }
74047
- function isPrivateAddress(address) {
74048
- const version2 = net3.isIP(address);
74049
- if (version2 === IP_VERSION_4) {
74050
- const octets = address.split(".").map((part) => Number(part));
74051
- const first = octets[0] ?? -1;
74052
- const second = octets[1] ?? -1;
74053
- if (first === V4_THIS_NETWORK_OCTET || first === V4_PRIVATE_A_OCTET || first === V4_LOOPBACK_OCTET) {
74054
- return true;
74113
+ function createRcSelfHostSurface(deps) {
74114
+ const sessions = /* @__PURE__ */ new Map();
74115
+ const sweep = () => {
74116
+ const now = deps.now();
74117
+ for (const session of sessions.values()) {
74118
+ while (session.events[0] !== void 0 && now - session.events[0].storedAt > RC_SELF_HOST_RETENTION_MS) {
74119
+ const dropped = session.events.shift();
74120
+ const uuid3 = dropped === void 0 ? void 0 : nonEmptyString2(dropped.payload, "uuid");
74121
+ if (uuid3 !== void 0) {
74122
+ session.seenPayloadUuids.delete(uuid3);
74123
+ }
74124
+ }
74125
+ if (session.workerStreams.size === 0 && session.clientStreams.size === 0 && now - session.lastTrafficAt > RC_SELF_HOST_RETENTION_MS) {
74126
+ sessions.delete(session.id);
74127
+ }
74055
74128
  }
74056
- if (first === V4_CGNAT_OCTET && second >= V4_CGNAT_SECOND_FROM && second <= V4_CGNAT_SECOND_TO) {
74057
- return true;
74129
+ };
74130
+ const timer = setInterval(sweep, RC_SELF_HOST_RETENTION_MS);
74131
+ timer.unref();
74132
+ const keepalive = setInterval(() => {
74133
+ for (const session of sessions.values()) {
74134
+ for (const sink of [...session.workerStreams, ...session.clientStreams]) {
74135
+ sink.write(": keep-alive\n\n");
74136
+ }
74058
74137
  }
74059
- if (first === V4_LINK_LOCAL_OCTET && second === V4_LINK_LOCAL_SECOND) {
74060
- return true;
74138
+ }, RC_SELF_HOST_KEEPALIVE_MS);
74139
+ keepalive.unref();
74140
+ const presentsCredential = (headers) => {
74141
+ const expected = deps.credentialRecord()?.accessToken;
74142
+ const presented = bearerOf(headers);
74143
+ return expected !== void 0 && presented !== void 0 && isLiveCapability(presented, [expected]);
74144
+ };
74145
+ const presentsWorkerJwt = (headers, session) => {
74146
+ const presented = bearerOf(headers);
74147
+ if (presented === void 0) {
74148
+ return false;
74061
74149
  }
74062
- if (first === V4_PRIVATE_B_OCTET && second >= V4_PRIVATE_B_SECOND_FROM && second <= V4_PRIVATE_B_SECOND_TO) {
74063
- return true;
74150
+ for (const minted of session.workerJwts) {
74151
+ if (isLiveCapability(presented, [minted])) {
74152
+ return true;
74153
+ }
74064
74154
  }
74065
- if (first === V4_PRIVATE_C_OCTET && second === V4_PRIVATE_C_SECOND) {
74066
- return true;
74155
+ return false;
74156
+ };
74157
+ const credentialRefusal = (headers) => presentsCredential(headers) ? void 0 : presentationRefusal(headers);
74158
+ const workerRefusal = (headers, session) => presentsWorkerJwt(headers, session) ? void 0 : presentationRefusal(headers);
74159
+ const workerOrCredentialRefusal = (headers, session) => presentsWorkerJwt(headers, session) || presentsCredential(headers) ? void 0 : presentationRefusal(headers);
74160
+ const frameOf = (event) => {
74161
+ const envelope = {
74162
+ event_id: event.eventId,
74163
+ event_type: nonEmptyString2(event.payload, "type") ?? "event",
74164
+ sequence_num: event.sequenceNum,
74165
+ source: event.source,
74166
+ payload: event.payload,
74167
+ created_at: event.createdAt
74168
+ };
74169
+ return `event: ${RC_STREAM_EVENT_NAME}
74170
+ id: ${String(event.sequenceNum)}
74171
+ data: ${JSON.stringify(envelope)}
74172
+
74173
+ `;
74174
+ };
74175
+ const holdStream = async (session, which, wantsReplay, request7, response) => {
74176
+ const url2 = new URL(request7.url, "http://127.0.0.1");
74177
+ const cursor3 = numberOf({ value: url2.searchParams.get("from_sequence_num") }, "value") ?? numberOf({ value: singleHeader2(request7.headers, "last-event-id") }, "value");
74178
+ const replay = cursor3 === void 0 ? [] : session.events.filter((event) => wantsReplay(event) && event.sequenceNum > cursor3);
74179
+ response.start(HTTP_STATUS.ok, { "content-type": "text/event-stream", "cache-control": "no-cache" });
74180
+ response.flush();
74181
+ let pending = Promise.resolve();
74182
+ const sink = {
74183
+ write: (frame) => {
74184
+ pending = pending.then(async () => {
74185
+ await response.write(frame);
74186
+ }).catch(() => {
74187
+ });
74188
+ },
74189
+ retire: () => {
74190
+ session[which].delete(sink);
74191
+ }
74192
+ };
74193
+ session[which].add(sink);
74194
+ session.lastTrafficAt = deps.now();
74195
+ for (const event of replay) {
74196
+ sink.write(frameOf(event));
74067
74197
  }
74068
- return first >= V4_MULTICAST_FROM_OCTET;
74069
- }
74070
- if (version2 === IP_VERSION_6) {
74071
- const lower = address.toLowerCase();
74072
- const mapped = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/.exec(lower);
74073
- if (mapped !== null) {
74074
- return isPrivateAddress(mapped[1] ?? "");
74198
+ await new Promise((resolve) => {
74199
+ request7.signal.addEventListener("abort", () => {
74200
+ resolve(void 0);
74201
+ }, { once: true });
74202
+ });
74203
+ sink.retire();
74204
+ response.end();
74205
+ };
74206
+ const publish = (session, source, payload) => {
74207
+ const uuid3 = nonEmptyString2(payload, "uuid");
74208
+ const duplicate = uuid3 !== void 0 && session.seenPayloadUuids.has(uuid3);
74209
+ session.sequenceNum += 1;
74210
+ const event = {
74211
+ sequenceNum: session.sequenceNum,
74212
+ eventId: uuid3 ?? deps.newUuid(),
74213
+ source,
74214
+ // The one enrichment the real service was observed making on the live rig's stream: the wall-clock instant it took the write. The worker's handler ignores it; the door's own parser forwards it verbatim.
74215
+ payload: source === RC_SOURCE_CLIENT ? { ...payload, server_received_wall_ms: deps.now() } : payload,
74216
+ createdAt: new Date(deps.now()).toISOString(),
74217
+ toWorker: source === RC_SOURCE_CLIENT,
74218
+ storedAt: deps.now()
74219
+ };
74220
+ session.events.push(event);
74221
+ if (uuid3 !== void 0) {
74222
+ session.seenPayloadUuids.add(uuid3);
74075
74223
  }
74076
- if (lower === "::" || lower === "::1") {
74077
- return true;
74224
+ session.lastTrafficAt = event.storedAt;
74225
+ const frame = frameOf(event);
74226
+ if (event.toWorker) {
74227
+ for (const sink of [...session.workerStreams]) {
74228
+ sink.write(frame);
74229
+ }
74078
74230
  }
74079
- const firstGroup = Number.parseInt(/^([0-9a-f]+):/.exec(lower)?.[1] ?? "0", 16);
74080
- if (Number.isNaN(firstGroup)) {
74081
- return true;
74231
+ for (const sink of [...session.clientStreams]) {
74232
+ sink.write(frame);
74082
74233
  }
74083
- if (firstGroup >= V6_UNIQUE_LOCAL_FROM && firstGroup <= V6_UNIQUE_LOCAL_TO) {
74084
- return true;
74234
+ return { sequenceNum: event.sequenceNum, eventId: event.eventId, duplicate };
74235
+ };
74236
+ const answerJson2 = async (response, status, body, extraHeaders = {}) => {
74237
+ const text2 = JSON.stringify(body);
74238
+ response.start(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text2, "utf8")), ...extraHeaders });
74239
+ await response.write(text2);
74240
+ response.end();
74241
+ };
74242
+ const noCredential = async (response) => {
74243
+ 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" } });
74244
+ };
74245
+ const epochStale = async (response) => {
74246
+ 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 });
74247
+ };
74248
+ const refusalMessages = {
74249
+ absent: "the self-hosted Remote Control surface authenticates by the Authorization header, and this call carried none",
74250
+ "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",
74251
+ "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",
74252
+ unrecognised: "the self-hosted Remote Control surface did not recognise the presented credential"
74253
+ };
74254
+ const unauthorized = async (response, refusal) => {
74255
+ await answerJson2(response, HTTP_STATUS.unauthorized, { type: "error", error: { type: "authentication_error", message: refusalMessages[refusal] } });
74256
+ };
74257
+ const serveSessionPath = async (session, tail, request7, response) => {
74258
+ const method = request7.method.toUpperCase();
74259
+ if (session === void 0) {
74260
+ await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: "no such Remote Control session on this door" } });
74261
+ return;
74085
74262
  }
74086
- if (firstGroup >= V6_LINK_LOCAL_FROM && firstGroup <= V6_LINK_LOCAL_TO) {
74087
- return true;
74263
+ session.lastTrafficAt = deps.now();
74264
+ if (tail === "") {
74265
+ if (method !== "GET") {
74266
+ await answerJson2(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the session read is a GET" } });
74267
+ return;
74268
+ }
74269
+ await answerJson2(response, HTTP_STATUS.ok, { session: { id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), status: "active" } });
74270
+ return;
74088
74271
  }
74089
- return firstGroup >= V6_MULTICAST_FROM;
74090
- }
74091
- return true;
74092
- }
74093
- function isSuccessful3(status) {
74094
- return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE3;
74095
- }
74096
- function isRedirect(status) {
74097
- return status === HTTP_MOVED_PERMANENTLY || status === HTTP_FOUND || status === HTTP_SEE_OTHER || status === HTTP_TEMPORARY_REDIRECT || status === HTTP_PERMANENT_REDIRECT;
74098
- }
74099
- function readableContentType(contentType) {
74100
- if (contentType === void 0 || contentType.trim() === "") {
74101
- return true;
74102
- }
74103
- const normalised = contentType.split(";")[0]?.trim().toLowerCase() ?? "";
74104
- const slash = normalised.indexOf("/");
74105
- const main2 = slash === -1 ? normalised : normalised.slice(0, slash);
74106
- const sub = slash === -1 ? "" : normalised.slice(slash + 1);
74107
- if (main2 === "text") {
74108
- return true;
74109
- }
74110
- return /json|xml|javascript|ecmascript|yaml|csv/.test(sub);
74111
- }
74112
- async function readCapped(chunks) {
74113
- const collected = [];
74114
- let total = 0;
74115
- for await (const chunk of chunks) {
74116
- total += chunk.byteLength;
74117
- if (total > RC_WEB_FETCH_MAX_BYTES) {
74118
- return { tooLarge: true, text: "" };
74272
+ if (tail === "bridge" && method === "POST") {
74273
+ const refusal = credentialRefusal(request7.headers);
74274
+ if (refusal !== void 0) {
74275
+ await unauthorized(response, refusal);
74276
+ return;
74277
+ }
74278
+ const workerJwt = mintWorkerJwt(deps.randomToken, deps.now());
74279
+ session.workerJwts.add(workerJwt);
74280
+ deps.log?.(`rc selfhost ${session.id}: bridge registration minted a worker credential`);
74281
+ 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 });
74282
+ return;
74119
74283
  }
74120
- collected.push(Buffer.from(chunk));
74121
- }
74122
- return { tooLarge: false, text: Buffer.concat(collected).toString("utf8") };
74123
- }
74124
- async function checkTarget(url2, deps) {
74125
- if (url2.protocol !== "http:" && url2.protocol !== "https:") {
74126
- return refused("scheme", `the web-fetch proxy dials http and https only, not ${url2.protocol}`);
74127
- }
74128
- if (deps.allowPrivate) {
74129
- return void 0;
74130
- }
74131
- const hostname3 = url2.hostname.replace(/^\[|\]$/g, "");
74132
- let addresses;
74133
- try {
74134
- addresses = net3.isIP(hostname3) === 0 ? await deps.lookup(hostname3) : [hostname3];
74135
- } catch {
74136
- return refused("resolve", `${hostname3} did not resolve`);
74137
- }
74138
- for (const address of addresses) {
74139
- if (isPrivateAddress(address)) {
74140
- 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`);
74284
+ if (tail === "archive" && method === "POST") {
74285
+ const refusal = credentialRefusal(request7.headers);
74286
+ if (refusal !== void 0) {
74287
+ await unauthorized(response, refusal);
74288
+ return;
74289
+ }
74290
+ for (const sink of [...session.workerStreams, ...session.clientStreams]) {
74291
+ sink.retire();
74292
+ }
74293
+ sessions.delete(session.id);
74294
+ deps.log?.(`rc selfhost ${session.id}: archived at its client's request`);
74295
+ await answerJson2(response, HTTP_STATUS.ok, {});
74296
+ return;
74141
74297
  }
74142
- }
74143
- return void 0;
74144
- }
74145
- async function fetchThroughWebProxy(url2, deps) {
74146
- let current;
74147
- try {
74148
- current = new URL(url2);
74149
- } catch {
74150
- return refused("malformed_url", `${url2} is not a URL this proxy can dial`);
74151
- }
74152
- const controller = new AbortController();
74153
- const timer = setTimeout(() => {
74154
- controller.abort();
74155
- }, deps.deadlineMs);
74156
- timer.unref();
74157
- try {
74158
- let redirects = 0;
74159
- for (; ; ) {
74160
- const refusal = await checkTarget(current, deps);
74298
+ if (tail === "worker" || tail.startsWith("worker/")) {
74299
+ const workerTail = tail === "worker" ? "" : tail.slice("worker/".length);
74300
+ const webProxy = workerTail === "web-fetch" || workerTail === "web-search";
74301
+ const refusal = webProxy ? workerOrCredentialRefusal(request7.headers, session) : workerRefusal(request7.headers, session);
74161
74302
  if (refusal !== void 0) {
74162
- return refusal;
74303
+ await unauthorized(response, refusal);
74304
+ return;
74163
74305
  }
74164
- let hop;
74165
- try {
74166
- hop = await deps.dial(current, controller.signal);
74167
- } catch (error62) {
74168
- if (controller.signal.aborted) {
74169
- return refused("deadline", `the fetch of ${url2} did not settle within its deadline`);
74306
+ if (workerTail === "" && method === "GET") {
74307
+ await answerJson2(response, HTTP_STATUS.ok, { worker: { external_metadata: session.externalMetadata ?? null } });
74308
+ return;
74309
+ }
74310
+ if (workerTail === "" && method === "PUT") {
74311
+ const body = parseJsonObject2(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74312
+ const claimedEpoch = numberOf(body, "worker_epoch");
74313
+ if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
74314
+ await epochStale(response);
74315
+ return;
74170
74316
  }
74171
- return refused("transport", `the dial of ${current.toString()} failed: ${error62 instanceof Error ? error62.message : "unknown error"}`);
74317
+ const status = nonEmptyString2(body, "worker_status");
74318
+ if (status !== void 0) {
74319
+ session.workerStatus = status;
74320
+ }
74321
+ if (isRecord4(body?.external_metadata)) {
74322
+ session.externalMetadata = body.external_metadata;
74323
+ }
74324
+ await answerJson2(response, HTTP_STATUS.ok, {});
74325
+ return;
74172
74326
  }
74173
- if (isRedirect(hop.status) && hop.location !== void 0) {
74174
- if (redirects >= RC_WEB_FETCH_MAX_REDIRECTS) {
74175
- controller.abort();
74176
- return refused("too_many_redirects", `${url2} followed more than ${String(RC_WEB_FETCH_MAX_REDIRECTS)} redirects without settling`);
74327
+ if (workerTail === "register" && method === "POST") {
74328
+ await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
74329
+ await answerJson2(response, HTTP_STATUS.ok, { worker_epoch: RC_SELF_HOST_WORKER_EPOCH });
74330
+ return;
74331
+ }
74332
+ if (workerTail === "heartbeat" && method === "POST") {
74333
+ await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
74334
+ await answerJson2(response, HTTP_STATUS.ok, {});
74335
+ return;
74336
+ }
74337
+ if (workerTail === "events" && method === "POST") {
74338
+ const body = parseJsonObject2(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74339
+ const claimedEpoch = numberOf(body, "worker_epoch");
74340
+ if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
74341
+ await epochStale(response);
74342
+ return;
74177
74343
  }
74178
- let next;
74179
- try {
74180
- next = new URL(hop.location, current);
74181
- } catch {
74182
- controller.abort();
74183
- return refused("malformed_url", `a redirect of ${current.toString()} named ${hop.location}, which is not a URL this proxy can dial`);
74344
+ const events = body?.events;
74345
+ if (!Array.isArray(events) || events.length === 0 || events.length > RC_SELF_HOST_MAX_BATCH_EVENTS) {
74346
+ 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` } });
74347
+ return;
74184
74348
  }
74185
- const drained = await readCapped(hop.chunks);
74186
- if (drained.tooLarge) {
74187
- controller.abort();
74188
- 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`);
74349
+ const results = [];
74350
+ for (const event of events) {
74351
+ if (!isRecord4(event) || !isRecord4(event.payload)) {
74352
+ await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "every event in a batch is an object carrying one payload object" } });
74353
+ return;
74354
+ }
74355
+ const published = publish(session, RC_SOURCE_WORKER, event.payload);
74356
+ results.push({ event_id: published.eventId, sequence_num: String(published.sequenceNum) });
74189
74357
  }
74190
- redirects += 1;
74191
- current = next;
74192
- continue;
74358
+ sweep();
74359
+ await answerJson2(response, HTTP_STATUS.ok, { results });
74360
+ return;
74193
74361
  }
74194
- if (!isSuccessful3(hop.status)) {
74195
- controller.abort();
74196
- return refused("status", `the target answered HTTP ${String(hop.status)} for ${current.toString()}`);
74362
+ if (workerTail === "events/stream" && method === "GET") {
74363
+ await holdStream(session, "workerStreams", (event) => event.toWorker, request7, response);
74364
+ return;
74197
74365
  }
74198
- const contentType = hop.contentType ?? "";
74199
- if (!readableContentType(contentType)) {
74200
- controller.abort();
74201
- return refused("content_type", `the target at ${current.toString()} served a ${contentType} answer, which the proxy's text field cannot carry`);
74366
+ if (workerTail === "events/delivery" && method === "POST") {
74367
+ await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
74368
+ await answerJson2(response, HTTP_STATUS.ok, {});
74369
+ return;
74202
74370
  }
74203
- const read = await readCapped(hop.chunks);
74204
- if (read.tooLarge) {
74205
- controller.abort();
74206
- 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`);
74371
+ if (workerTail === "internal-events" && method === "POST") {
74372
+ const body = parseJsonObject2(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74373
+ const claimedEpoch = numberOf(body, "worker_epoch");
74374
+ if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
74375
+ await epochStale(response);
74376
+ return;
74377
+ }
74378
+ const events = body?.events;
74379
+ if (!Array.isArray(events)) {
74380
+ await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "an internal event batch is an array" } });
74381
+ return;
74382
+ }
74383
+ for (const event of events) {
74384
+ if (isRecord4(event)) {
74385
+ session.internalEvents.push({ cursor: deps.newUuid(), body: event });
74386
+ }
74387
+ }
74388
+ await answerJson2(response, HTTP_STATUS.ok, {});
74389
+ return;
74207
74390
  }
74208
- return { kind: "fetched", url: url2, destinationUrl: current.toString(), contentType: hop.contentType, text: read.text };
74209
- }
74210
- } finally {
74211
- clearTimeout(timer);
74212
- }
74213
- }
74214
- function realRcWebFetch(options) {
74215
- const plainAgent = new ExemptHttpAgent();
74216
- const tlsAgent = new ExemptTlsAgent();
74217
- const deps = {
74218
- dial: async (url2, signal) => await new Promise((resolve, reject) => {
74219
- const request7 = (url2.protocol === "https:" ? https5 : http6).request(
74220
- {
74221
- hostname: url2.hostname,
74222
- port: url2.port === "" ? void 0 : Number(url2.port),
74223
- method: "GET",
74224
- path: `${url2.pathname}${url2.search}`,
74225
- headers: { accept: "*/*", "accept-encoding": "identity", "user-agent": WEB_FETCH_USER_AGENT },
74226
- agent: url2.protocol === "https:" ? tlsAgent : plainAgent,
74227
- signal
74228
- },
74229
- (response) => {
74230
- const location = response.headers.location;
74231
- resolve({
74232
- status: response.statusCode ?? 0,
74233
- location: typeof location === "string" ? location : void 0,
74234
- contentType: typeof response.headers["content-type"] === "string" ? response.headers["content-type"] : void 0,
74235
- chunks: response
74236
- });
74391
+ if (workerTail === "internal-events" && method === "GET") {
74392
+ const url2 = new URL(request7.url, "http://127.0.0.1");
74393
+ const cursor3 = url2.searchParams.get("cursor");
74394
+ const from = cursor3 === null ? 0 : session.internalEvents.findIndex((event) => event.cursor === cursor3) + 1;
74395
+ const page = session.internalEvents.slice(from);
74396
+ await answerJson2(response, HTTP_STATUS.ok, { data: page.map((event) => event.body), next_cursor: page.length === 0 ? void 0 : page[page.length - 1]?.cursor });
74397
+ return;
74398
+ }
74399
+ if (workerTail === "diagnostics" && method === "POST") {
74400
+ await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
74401
+ await answerJson2(response, HTTP_STATUS.ok, {});
74402
+ return;
74403
+ }
74404
+ if (workerTail === "web-fetch" && method === "POST") {
74405
+ const body = parseJsonObject2(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74406
+ const target = nonEmptyString2(body, "url");
74407
+ if (target === void 0) {
74408
+ await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-fetch carries one url" } });
74409
+ return;
74237
74410
  }
74238
- );
74239
- request7.once("error", reject);
74240
- request7.end();
74241
- }),
74242
- lookup: async (hostname3) => (await dns2.promises.lookup(hostname3, { all: true })).map((entry) => entry.address),
74243
- allowPrivate: options.allowPrivate,
74244
- deadlineMs: RC_WEB_FETCH_DEADLINE_MS
74245
- };
74246
- return async (url2) => await fetchThroughWebProxy(url2, deps);
74247
- }
74248
-
74249
- // src/frontdoor/rcSelfHost.ts
74250
- var RC_SELF_HOST_ENV = "AGENT_SHIM_FRONTDOOR_RC_SELF_HOST";
74251
- function rcSelfHostFromEnv(env) {
74252
- return env[RC_SELF_HOST_ENV] === "1";
74253
- }
74254
- var RC_SELF_HOST_SCOPE_LIST = ["user:profile", "user:inference", "user:sessions:claude_code", "user:mcp_servers", "user:file_upload"];
74255
- var RC_SESSIONS_PATH_PREFIX2 = "/v1/code/sessions";
74256
- var RC_COMPAT_SESSIONS_PATH = "/v1/sessions";
74257
- var RC_SESSION_ID_PREFIX2 = "cse_";
74258
- var RC_SELF_HOST_API_BASE_URL = "https://api.anthropic.com";
74259
- var RC_SELF_HOST_OAUTH_HOST = "platform.claude.com";
74260
- var HTTP_CONFLICT = 409;
74261
- var MS_PER_SECOND7 = 1e3;
74262
- var HTTP_SERVICE_UNAVAILABLE = 503;
74263
- var RC_SELF_HOST_WORKER_JWT_TTL_SECONDS = 46800;
74264
- var RC_SELF_HOST_OAUTH_TOKEN_TTL_SECONDS = 3600;
74265
- var RC_SELF_HOST_KEEPALIVE_MS = 15e3;
74266
- var RC_SELF_HOST_RETENTION_MS = 6e5;
74267
- var RC_PRESENCE_REFRESH_SECONDS = 60;
74268
- var RC_SELF_HOST_MAX_BATCH_EVENTS = 100;
74269
- var RC_SELF_HOST_WORKER_EPOCH = 1;
74270
- var RC_STREAM_EVENT_NAME = "client_event";
74271
- var RC_SOURCE_WORKER = "worker";
74272
- var RC_SOURCE_CLIENT = "client";
74273
- var CONFLICT_REASON_HEADER2 = "x-ccr-conflict-reason";
74274
- var CONFLICT_REASON_EPOCH_STALE = "epoch_stale";
74275
- function isRecord5(value2) {
74276
- return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
74277
- }
74278
- function isWebSearchRefusal(value2) {
74279
- return !Array.isArray(value2);
74280
- }
74281
- function nonEmptyString2(record2, field) {
74282
- const value2 = record2 === void 0 ? void 0 : record2[field];
74283
- return typeof value2 === "string" && value2 !== "" ? value2 : void 0;
74284
- }
74285
- function numberOf(record2, field) {
74286
- const value2 = record2 === void 0 ? void 0 : record2[field];
74287
- if (typeof value2 === "number" && Number.isFinite(value2)) {
74288
- return value2;
74289
- }
74290
- if (typeof value2 === "string" && /^[0-9]+$/.test(value2)) {
74291
- return Number(value2);
74292
- }
74293
- return void 0;
74294
- }
74295
- function stringArray(record2, field) {
74296
- const value2 = record2 === void 0 ? void 0 : record2[field];
74297
- if (!Array.isArray(value2)) {
74298
- return void 0;
74299
- }
74300
- const members2 = [];
74301
- for (const member of value2) {
74302
- if (typeof member !== "string") {
74303
- return void 0;
74304
- }
74305
- members2.push(member);
74306
- }
74307
- return members2;
74308
- }
74309
- function singleHeader2(headers, name) {
74310
- const value2 = headers[name];
74311
- return typeof value2 === "string" ? value2 : void 0;
74312
- }
74313
- function bearerOf(headers) {
74314
- const presented = singleHeader2(headers, "authorization");
74315
- if (presented === void 0) {
74316
- return void 0;
74317
- }
74318
- return presented.slice(0, "bearer ".length).toLowerCase() === "bearer " ? presented.slice("bearer ".length) : void 0;
74319
- }
74320
- function presentationRefusal(headers) {
74321
- const presented = singleHeader2(headers, "authorization");
74322
- if (presented === void 0) {
74323
- return "absent";
74324
- }
74325
- if (presented.slice(0, "bearer ".length).toLowerCase() !== "bearer ") {
74326
- return "not-a-bearer";
74327
- }
74328
- const bearer = presented.slice("bearer ".length);
74329
- return bearer.slice(0, "bearer ".length).toLowerCase() === "bearer " ? "doubled-scheme-word" : "unrecognised";
74330
- }
74331
- async function readBody3(request7, capBytes) {
74332
- return await new Promise((resolve, reject) => {
74333
- const chunks = [];
74334
- let total = 0;
74335
- request7.on("data", (chunk) => {
74336
- total += chunk.length;
74337
- if (total > capBytes) {
74338
- request7.destroy();
74339
- reject(new Error("body too large"));
74411
+ const outcome = await deps.webFetch(target);
74412
+ if (outcome.kind === "fetched") {
74413
+ const answer = { url: target, destination_url: outcome.destinationUrl, text: outcome.text, ...outcome.contentType === void 0 ? {} : { content_type: outcome.contentType } };
74414
+ if (Buffer.byteLength(JSON.stringify(answer), "utf8") > RC_WEB_FETCH_MAX_BYTES) {
74415
+ deps.log?.(`rc selfhost ${session.id}: worker web-fetch of ${target} escaped past the reader cap when framed`);
74416
+ 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` } });
74417
+ return;
74418
+ }
74419
+ deps.log?.(`rc selfhost ${session.id}: worker web-fetch served ${target} as ${outcome.destinationUrl}`);
74420
+ await answerJson2(response, HTTP_STATUS.ok, answer);
74421
+ return;
74422
+ }
74423
+ deps.log?.(`rc selfhost ${session.id}: worker web-fetch of ${target} refused: ${outcome.errorType}`);
74424
+ await answerJson2(response, HTTP_STATUS.ok, { error: { error_type: outcome.errorType, error_message: outcome.errorMessage } });
74340
74425
  return;
74341
74426
  }
74342
- chunks.push(chunk);
74343
- });
74344
- request7.on("end", () => {
74345
- resolve(Buffer.concat(chunks).toString("utf8"));
74346
- });
74347
- request7.on("error", () => {
74348
- reject(new Error("the request body ended early"));
74349
- });
74350
- });
74351
- }
74352
- function parseJsonObject2(body) {
74353
- let parsed;
74354
- try {
74355
- parsed = JSON.parse(body);
74356
- } catch {
74357
- return void 0;
74358
- }
74359
- return isRecord5(parsed) ? parsed : void 0;
74360
- }
74361
- function mintWorkerJwt(randomToken, issuedAt) {
74362
- const encode3 = (value2) => Buffer.from(JSON.stringify(value2), "utf8").toString("base64url");
74363
- const header = encode3({ alg: "HS256", typ: "JWT" });
74364
- 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) });
74365
- return `${header}.${payload}.${Buffer.from(randomToken(), "utf8").toString("base64url")}`;
74366
- }
74367
- function growthBookFeatures() {
74368
- return {
74369
- tengu_ccr_bridge: { defaultValue: true },
74370
- tengu_bridge_repl_v2: { defaultValue: true },
74371
- tengu_bridge_repl_v2_cse_shim_enabled: { defaultValue: true },
74372
- tengu_bridge_min_version: { defaultValue: { minVersion: "0.0.0" } }
74373
- };
74374
- }
74375
- function createRcSelfHostSurface(deps) {
74376
- const sessions = /* @__PURE__ */ new Map();
74377
- const sweep = () => {
74378
- const now = deps.now();
74379
- for (const session of sessions.values()) {
74380
- while (session.events[0] !== void 0 && now - session.events[0].storedAt > RC_SELF_HOST_RETENTION_MS) {
74381
- const dropped = session.events.shift();
74382
- const uuid3 = dropped === void 0 ? void 0 : nonEmptyString2(dropped.payload, "uuid");
74383
- if (uuid3 !== void 0) {
74384
- session.seenPayloadUuids.delete(uuid3);
74427
+ if (workerTail === "web-search" && method === "POST") {
74428
+ const body = parseJsonObject2(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74429
+ const query = nonEmptyString2(body, "query");
74430
+ if (query === void 0) {
74431
+ await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-search carries one query" } });
74432
+ return;
74385
74433
  }
74434
+ const allowedDomains = stringArray(body, "allowed_domains");
74435
+ const blockedDomains = stringArray(body, "blocked_domains");
74436
+ if (body?.allowed_domains !== void 0 && allowedDomains === void 0 || body?.blocked_domains !== void 0 && blockedDomains === void 0) {
74437
+ await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "the domain filters of a worker web-search are string arrays" } });
74438
+ return;
74439
+ }
74440
+ const searchProfile = nonEmptyString2(body, "search_profile");
74441
+ const backend = deps.webSearch;
74442
+ if (backend === void 0) {
74443
+ deps.log?.(`rc selfhost ${session.id}: worker web-search of "${query}" refused: no search backend serves this door`);
74444
+ 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" } });
74445
+ return;
74446
+ }
74447
+ const answered = await backend({ query, ...allowedDomains === void 0 ? {} : { allowedDomains }, ...blockedDomains === void 0 ? {} : { blockedDomains }, ...searchProfile === void 0 ? {} : { searchProfile } });
74448
+ deps.log?.(`rc selfhost ${session.id}: worker web-search of "${query}" answered by its backend`);
74449
+ if (isWebSearchRefusal(answered)) {
74450
+ await answerJson2(response, HTTP_STATUS.ok, { results: [], error: { error_type: answered.errorType, error_message: answered.errorMessage } });
74451
+ return;
74452
+ }
74453
+ await answerJson2(response, HTTP_STATUS.ok, { results: answered.map((result) => ({ title: result.title ?? "", url: result.url, snippet: result.snippet ?? "" })) });
74454
+ return;
74386
74455
  }
74387
- if (session.workerStreams.size === 0 && session.clientStreams.size === 0 && now - session.lastTrafficAt > RC_SELF_HOST_RETENTION_MS) {
74388
- sessions.delete(session.id);
74389
- }
74456
+ await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the self-hosted Remote Control surface serves no worker path "${workerTail}"` } });
74457
+ return;
74390
74458
  }
74391
- };
74392
- const timer = setInterval(sweep, RC_SELF_HOST_RETENTION_MS);
74393
- timer.unref();
74394
- const keepalive = setInterval(() => {
74395
- for (const session of sessions.values()) {
74396
- for (const sink of [...session.workerStreams, ...session.clientStreams]) {
74397
- sink.write(": keep-alive\n\n");
74459
+ const clientRefusal = credentialRefusal(request7.headers);
74460
+ if (clientRefusal !== void 0) {
74461
+ await unauthorized(response, clientRefusal);
74462
+ return;
74463
+ }
74464
+ if (tail === "events" && method === "POST") {
74465
+ const body = parseJsonObject2(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74466
+ const events = body?.events;
74467
+ if (!Array.isArray(events) || events.length === 0 || events.length > RC_SELF_HOST_MAX_BATCH_EVENTS) {
74468
+ 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` } });
74469
+ return;
74470
+ }
74471
+ const results = [];
74472
+ for (const event of events) {
74473
+ if (!isRecord4(event) || !isRecord4(event.payload)) {
74474
+ await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "every event in a write is an object carrying one payload object" } });
74475
+ return;
74476
+ }
74477
+ const published = publish(session, RC_SOURCE_CLIENT, event.payload);
74478
+ results.push({ sequence_num: String(published.sequenceNum), duplicate: published.duplicate });
74398
74479
  }
74480
+ sweep();
74481
+ await answerJson2(response, HTTP_STATUS.ok, { results });
74482
+ return;
74399
74483
  }
74400
- }, RC_SELF_HOST_KEEPALIVE_MS);
74401
- keepalive.unref();
74402
- const presentsCredential = (headers) => {
74403
- const expected = deps.credentialRecord()?.accessToken;
74404
- const presented = bearerOf(headers);
74405
- return expected !== void 0 && presented !== void 0 && isLiveCapability(presented, [expected]);
74406
- };
74407
- const presentsWorkerJwt = (headers, session) => {
74408
- const presented = bearerOf(headers);
74409
- if (presented === void 0) {
74410
- return false;
74484
+ if (tail === "events" && method === "GET") {
74485
+ await answerJson2(response, HTTP_STATUS.ok, { data: session.events.map((event) => ({ event_id: event.eventId, event_type: nonEmptyString2(event.payload, "type") ?? "event", sequence_num: event.sequenceNum, source: event.source, payload: event.payload, created_at: event.createdAt })) });
74486
+ return;
74411
74487
  }
74412
- for (const minted of session.workerJwts) {
74413
- if (isLiveCapability(presented, [minted])) {
74414
- return true;
74415
- }
74488
+ if (tail === "events/stream" && method === "GET") {
74489
+ await holdStream(session, "clientStreams", () => true, request7, response);
74490
+ return;
74416
74491
  }
74417
- return false;
74418
- };
74419
- const credentialRefusal = (headers) => presentsCredential(headers) ? void 0 : presentationRefusal(headers);
74420
- const workerRefusal = (headers, session) => presentsWorkerJwt(headers, session) ? void 0 : presentationRefusal(headers);
74421
- const workerOrCredentialRefusal = (headers, session) => presentsWorkerJwt(headers, session) || presentsCredential(headers) ? void 0 : presentationRefusal(headers);
74422
- const frameOf = (event) => {
74423
- const envelope = {
74424
- event_id: event.eventId,
74425
- event_type: nonEmptyString2(event.payload, "type") ?? "event",
74426
- sequence_num: event.sequenceNum,
74427
- source: event.source,
74428
- payload: event.payload,
74429
- created_at: event.createdAt
74430
- };
74431
- return `event: ${RC_STREAM_EVENT_NAME}
74432
- id: ${String(event.sequenceNum)}
74433
- data: ${JSON.stringify(envelope)}
74434
-
74435
- `;
74492
+ if (tail === "client/presence" && method === "POST") {
74493
+ await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
74494
+ await answerJson2(response, HTTP_STATUS.ok, { refresh_after_seconds: RC_PRESENCE_REFRESH_SECONDS });
74495
+ return;
74496
+ }
74497
+ if (tail === "mark_read" && method === "POST") {
74498
+ await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
74499
+ await answerJson2(response, HTTP_STATUS.ok, {});
74500
+ return;
74501
+ }
74502
+ if (tail === "teleport-events" && method === "POST") {
74503
+ await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
74504
+ await answerJson2(response, HTTP_STATUS.ok, {});
74505
+ return;
74506
+ }
74507
+ await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the self-hosted Remote Control surface serves no path "${tail}"` } });
74436
74508
  };
74437
- const holdStream = async (session, which, wantsReplay, request7, response) => {
74438
- const url2 = new URL(request7.url, "http://127.0.0.1");
74439
- const cursor3 = numberOf({ value: url2.searchParams.get("from_sequence_num") }, "value") ?? numberOf({ value: singleHeader2(request7.headers, "last-event-id") }, "value");
74440
- const replay = cursor3 === void 0 ? [] : session.events.filter((event) => wantsReplay(event) && event.sequenceNum > cursor3);
74441
- response.start(HTTP_STATUS.ok, { "content-type": "text/event-stream", "cache-control": "no-cache" });
74442
- response.flush();
74443
- let pending = Promise.resolve();
74444
- const sink = {
74445
- write: (frame) => {
74446
- pending = pending.then(async () => {
74447
- await response.write(frame);
74448
- }).catch(() => {
74449
- });
74450
- },
74451
- retire: () => {
74452
- session[which].delete(sink);
74509
+ const recordMissing = () => deps.credentialRecord() === void 0;
74510
+ const route = {
74511
+ name: "rc-selfhost",
74512
+ headroomEligible: false,
74513
+ headroomUpstream: void 0,
74514
+ serve: async (request7, response) => {
74515
+ const method = request7.method.toUpperCase();
74516
+ const pathname = new URL(request7.url, "http://127.0.0.1").pathname;
74517
+ if (recordMissing()) {
74518
+ await noCredential(response);
74519
+ return;
74453
74520
  }
74454
- };
74455
- session[which].add(sink);
74456
- session.lastTrafficAt = deps.now();
74457
- for (const event of replay) {
74458
- sink.write(frameOf(event));
74459
- }
74460
- await new Promise((resolve) => {
74461
- request7.signal.addEventListener("abort", () => {
74462
- resolve(void 0);
74463
- }, { once: true });
74464
- });
74465
- sink.retire();
74466
- response.end();
74467
- };
74468
- const publish = (session, source, payload) => {
74469
- const uuid3 = nonEmptyString2(payload, "uuid");
74470
- const duplicate = uuid3 !== void 0 && session.seenPayloadUuids.has(uuid3);
74471
- session.sequenceNum += 1;
74472
- const event = {
74473
- sequenceNum: session.sequenceNum,
74474
- eventId: uuid3 ?? deps.newUuid(),
74475
- source,
74476
- // 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.
74477
- payload: source === RC_SOURCE_CLIENT ? { ...payload, server_received_wall_ms: deps.now() } : payload,
74478
- createdAt: new Date(deps.now()).toISOString(),
74479
- toWorker: source === RC_SOURCE_CLIENT,
74480
- storedAt: deps.now()
74481
- };
74482
- session.events.push(event);
74483
- if (uuid3 !== void 0) {
74484
- session.seenPayloadUuids.add(uuid3);
74485
- }
74486
- session.lastTrafficAt = event.storedAt;
74487
- const frame = frameOf(event);
74488
- if (event.toWorker) {
74489
- for (const sink of [...session.workerStreams]) {
74490
- sink.write(frame);
74491
- }
74492
- }
74493
- for (const sink of [...session.clientStreams]) {
74494
- sink.write(frame);
74495
- }
74496
- return { sequenceNum: event.sequenceNum, eventId: event.eventId, duplicate };
74497
- };
74498
- const answerJson2 = async (response, status, body, extraHeaders = {}) => {
74499
- const text2 = JSON.stringify(body);
74500
- response.start(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text2, "utf8")), ...extraHeaders });
74501
- await response.write(text2);
74502
- response.end();
74503
- };
74504
- const noCredential = async (response) => {
74505
- 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" } });
74506
- };
74507
- const epochStale = async (response) => {
74508
- 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 });
74509
- };
74510
- const refusalMessages = {
74511
- absent: "the self-hosted Remote Control surface authenticates by the Authorization header, and this call carried none",
74512
- "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",
74513
- "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",
74514
- unrecognised: "the self-hosted Remote Control surface did not recognise the presented credential"
74515
- };
74516
- const unauthorized = async (response, refusal) => {
74517
- await answerJson2(response, HTTP_STATUS.unauthorized, { type: "error", error: { type: "authentication_error", message: refusalMessages[refusal] } });
74518
- };
74519
- const serveSessionPath = async (session, tail, request7, response) => {
74520
- const method = request7.method.toUpperCase();
74521
- if (session === void 0) {
74522
- await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: "no such Remote Control session on this door" } });
74523
- return;
74524
- }
74525
- session.lastTrafficAt = deps.now();
74526
- if (tail === "") {
74527
- if (method !== "GET") {
74528
- await answerJson2(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the session read is a GET" } });
74529
- return;
74530
- }
74531
- await answerJson2(response, HTTP_STATUS.ok, { session: { id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), status: "active" } });
74532
- return;
74533
- }
74534
- if (tail === "bridge" && method === "POST") {
74535
- const refusal = credentialRefusal(request7.headers);
74536
- if (refusal !== void 0) {
74537
- await unauthorized(response, refusal);
74538
- return;
74539
- }
74540
- const workerJwt = mintWorkerJwt(deps.randomToken, deps.now());
74541
- session.workerJwts.add(workerJwt);
74542
- deps.log?.(`rc selfhost ${session.id}: bridge registration minted a worker credential`);
74543
- 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 });
74544
- return;
74545
- }
74546
- if (tail === "archive" && method === "POST") {
74547
- const refusal = credentialRefusal(request7.headers);
74548
- if (refusal !== void 0) {
74549
- await unauthorized(response, refusal);
74550
- return;
74551
- }
74552
- for (const sink of [...session.workerStreams, ...session.clientStreams]) {
74553
- sink.retire();
74554
- }
74555
- sessions.delete(session.id);
74556
- deps.log?.(`rc selfhost ${session.id}: archived at its client's request`);
74557
- await answerJson2(response, HTTP_STATUS.ok, {});
74558
- return;
74559
- }
74560
- if (tail === "worker" || tail.startsWith("worker/")) {
74561
- const workerTail = tail === "worker" ? "" : tail.slice("worker/".length);
74562
- const webProxy = workerTail === "web-fetch" || workerTail === "web-search";
74563
- const refusal = webProxy ? workerOrCredentialRefusal(request7.headers, session) : workerRefusal(request7.headers, session);
74564
- if (refusal !== void 0) {
74565
- await unauthorized(response, refusal);
74566
- return;
74567
- }
74568
- if (workerTail === "" && method === "GET") {
74569
- await answerJson2(response, HTTP_STATUS.ok, { worker: { external_metadata: session.externalMetadata ?? null } });
74570
- return;
74571
- }
74572
- if (workerTail === "" && method === "PUT") {
74573
- const body = parseJsonObject2(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74574
- const claimedEpoch = numberOf(body, "worker_epoch");
74575
- if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
74576
- await epochStale(response);
74521
+ if (pathname === RC_COMPAT_SESSIONS_PATH) {
74522
+ if (method !== "GET") {
74523
+ await answerJson2(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the compatibility session list is a GET" } });
74577
74524
  return;
74578
74525
  }
74579
- const status = nonEmptyString2(body, "worker_status");
74580
- if (status !== void 0) {
74581
- session.workerStatus = status;
74582
- }
74583
- if (isRecord5(body?.external_metadata)) {
74584
- session.externalMetadata = body.external_metadata;
74526
+ const listRefusal = credentialRefusal(request7.headers);
74527
+ if (listRefusal !== void 0) {
74528
+ await unauthorized(response, listRefusal);
74529
+ return;
74585
74530
  }
74586
- await answerJson2(response, HTTP_STATUS.ok, {});
74587
- return;
74588
- }
74589
- if (workerTail === "register" && method === "POST") {
74590
- await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
74591
- await answerJson2(response, HTTP_STATUS.ok, { worker_epoch: RC_SELF_HOST_WORKER_EPOCH });
74531
+ await answerJson2(response, HTTP_STATUS.ok, { data: [] });
74592
74532
  return;
74593
74533
  }
74594
- if (workerTail === "heartbeat" && method === "POST") {
74595
- await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
74596
- await answerJson2(response, HTTP_STATUS.ok, {});
74534
+ if (pathname !== RC_SESSIONS_PATH_PREFIX2 && !pathname.startsWith(`${RC_SESSIONS_PATH_PREFIX2}/`)) {
74535
+ await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the self-hosted Remote Control surface serves no path "${pathname}"` } });
74597
74536
  return;
74598
74537
  }
74599
- if (workerTail === "events" && method === "POST") {
74600
- const body = parseJsonObject2(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74601
- const claimedEpoch = numberOf(body, "worker_epoch");
74602
- if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
74603
- await epochStale(response);
74604
- return;
74605
- }
74606
- const events = body?.events;
74607
- if (!Array.isArray(events) || events.length === 0 || events.length > RC_SELF_HOST_MAX_BATCH_EVENTS) {
74608
- 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` } });
74538
+ if (pathname === RC_SESSIONS_PATH_PREFIX2) {
74539
+ if (method === "POST") {
74540
+ const createRefusal = credentialRefusal(request7.headers);
74541
+ if (createRefusal !== void 0) {
74542
+ await unauthorized(response, createRefusal);
74543
+ return;
74544
+ }
74545
+ const body = parseJsonObject2(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74546
+ const session = {
74547
+ id: `${RC_SESSION_ID_PREFIX2}${deps.newUuid()}`,
74548
+ createdAt: deps.now(),
74549
+ title: nonEmptyString2(body, "title"),
74550
+ workerJwts: /* @__PURE__ */ new Set(),
74551
+ sequenceNum: 0,
74552
+ events: [],
74553
+ seenPayloadUuids: /* @__PURE__ */ new Set(),
74554
+ workerStreams: /* @__PURE__ */ new Set(),
74555
+ clientStreams: /* @__PURE__ */ new Set(),
74556
+ internalEvents: [],
74557
+ workerStatus: void 0,
74558
+ externalMetadata: void 0,
74559
+ lastTrafficAt: deps.now()
74560
+ };
74561
+ sessions.set(session.id, session);
74562
+ deps.log?.(`rc selfhost ${session.id}: session created on the door's own surface`);
74563
+ await answerJson2(response, HTTP_STATUS.ok, { session: { id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), status: "active" } });
74609
74564
  return;
74610
74565
  }
74611
- const results = [];
74612
- for (const event of events) {
74613
- if (!isRecord5(event) || !isRecord5(event.payload)) {
74614
- 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" } });
74566
+ if (method === "GET") {
74567
+ const readRefusal = credentialRefusal(request7.headers);
74568
+ if (readRefusal !== void 0) {
74569
+ await unauthorized(response, readRefusal);
74615
74570
  return;
74616
74571
  }
74617
- const published = publish(session, RC_SOURCE_WORKER, event.payload);
74618
- results.push({ event_id: published.eventId, sequence_num: String(published.sequenceNum) });
74572
+ 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" })) });
74573
+ return;
74619
74574
  }
74620
- sweep();
74621
- await answerJson2(response, HTTP_STATUS.ok, { results });
74622
- return;
74623
- }
74624
- if (workerTail === "events/stream" && method === "GET") {
74625
- await holdStream(session, "workerStreams", (event) => event.toWorker, request7, response);
74575
+ await answerJson2(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the session collection is a POST (create) and a GET (list)" } });
74626
74576
  return;
74627
74577
  }
74628
- if (workerTail === "events/delivery" && method === "POST") {
74629
- await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
74630
- await answerJson2(response, HTTP_STATUS.ok, {});
74578
+ const rest = pathname.slice(`${RC_SESSIONS_PATH_PREFIX2}/`.length);
74579
+ const slash = rest.indexOf("/");
74580
+ const id = slash === -1 ? rest : rest.slice(0, slash);
74581
+ const tail = slash === -1 ? "" : rest.slice(slash + 1);
74582
+ if (!id.startsWith(RC_SESSION_ID_PREFIX2)) {
74583
+ await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: "the session family names one cse_-prefixed session id" } });
74631
74584
  return;
74632
74585
  }
74633
- if (workerTail === "internal-events" && method === "POST") {
74634
- const body = parseJsonObject2(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74635
- const claimedEpoch = numberOf(body, "worker_epoch");
74636
- if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
74637
- await epochStale(response);
74638
- return;
74639
- }
74640
- const events = body?.events;
74641
- if (!Array.isArray(events)) {
74642
- await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "an internal event batch is an array" } });
74643
- return;
74644
- }
74645
- for (const event of events) {
74646
- if (isRecord5(event)) {
74647
- session.internalEvents.push({ cursor: deps.newUuid(), body: event });
74586
+ await serveSessionPath(sessions.get(id), tail, request7, response);
74587
+ }
74588
+ };
74589
+ const answerServerJson = (response, status, body) => {
74590
+ const text2 = JSON.stringify(body);
74591
+ response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text2, "utf8")) });
74592
+ response.end(text2);
74593
+ };
74594
+ const local = {
74595
+ parsesHost: (host) => host === RC_SELF_HOST_OAUTH_HOST,
74596
+ serve: async (host, request7, response) => {
74597
+ const method = (request7.method ?? "").toUpperCase();
74598
+ const pathname = new URL(request7.url ?? "/", "http://127.0.0.1").pathname;
74599
+ if (host === RC_SELF_HOST_OAUTH_HOST) {
74600
+ if (method === "POST" && pathname === "/v1/oauth/token") {
74601
+ const body = parseJsonObject2(await readBody3(request7, CONTROL_BODY_CAP_BYTES));
74602
+ const record2 = deps.credentialRecord();
74603
+ if (record2 === void 0) {
74604
+ 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" });
74605
+ return true;
74606
+ }
74607
+ if (body?.grant_type !== void 0 && body.grant_type !== "refresh_token") {
74608
+ answerServerJson(response, HTTP_STATUS.badRequest, { error: "unsupported_grant_type" });
74609
+ return true;
74648
74610
  }
74649
- }
74650
- await answerJson2(response, HTTP_STATUS.ok, {});
74651
- return;
74652
- }
74653
- if (workerTail === "internal-events" && method === "GET") {
74654
- const url2 = new URL(request7.url, "http://127.0.0.1");
74655
- const cursor3 = url2.searchParams.get("cursor");
74656
- const from = cursor3 === null ? 0 : session.internalEvents.findIndex((event) => event.cursor === cursor3) + 1;
74657
- const page = session.internalEvents.slice(from);
74658
- await answerJson2(response, HTTP_STATUS.ok, { data: page.map((event) => event.body), next_cursor: page.length === 0 ? void 0 : page[page.length - 1]?.cursor });
74659
- return;
74660
- }
74661
- if (workerTail === "diagnostics" && method === "POST") {
74662
- await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
74663
- await answerJson2(response, HTTP_STATUS.ok, {});
74664
- return;
74665
- }
74666
- if (workerTail === "web-fetch" && method === "POST") {
74667
- const body = parseJsonObject2(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74668
- const target = nonEmptyString2(body, "url");
74669
- if (target === void 0) {
74670
- await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-fetch carries one url" } });
74671
- return;
74672
- }
74673
- const outcome = await deps.webFetch(target);
74674
- if (outcome.kind === "fetched") {
74675
- const answer = { url: target, destination_url: outcome.destinationUrl, text: outcome.text, ...outcome.contentType === void 0 ? {} : { content_type: outcome.contentType } };
74676
- if (Buffer.byteLength(JSON.stringify(answer), "utf8") > RC_WEB_FETCH_MAX_BYTES) {
74677
- deps.log?.(`rc selfhost ${session.id}: worker web-fetch of ${target} escaped past the reader cap when framed`);
74678
- 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` } });
74679
- return;
74680
- }
74681
- deps.log?.(`rc selfhost ${session.id}: worker web-fetch served ${target} as ${outcome.destinationUrl}`);
74682
- await answerJson2(response, HTTP_STATUS.ok, answer);
74683
- return;
74684
- }
74685
- deps.log?.(`rc selfhost ${session.id}: worker web-fetch of ${target} refused: ${outcome.errorType}`);
74686
- await answerJson2(response, HTTP_STATUS.ok, { error: { error_type: outcome.errorType, error_message: outcome.errorMessage } });
74687
- return;
74688
- }
74689
- if (workerTail === "web-search" && method === "POST") {
74690
- const body = parseJsonObject2(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74691
- const query = nonEmptyString2(body, "query");
74692
- if (query === void 0) {
74693
- await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-search carries one query" } });
74694
- return;
74695
- }
74696
- const allowedDomains = stringArray(body, "allowed_domains");
74697
- const blockedDomains = stringArray(body, "blocked_domains");
74698
- if (body?.allowed_domains !== void 0 && allowedDomains === void 0 || body?.blocked_domains !== void 0 && blockedDomains === void 0) {
74699
- 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" } });
74700
- return;
74701
- }
74702
- const searchProfile = nonEmptyString2(body, "search_profile");
74703
- const backend = deps.webSearch;
74704
- if (backend === void 0) {
74705
- deps.log?.(`rc selfhost ${session.id}: worker web-search of "${query}" refused: no search backend serves this door`);
74706
- 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" } });
74707
- return;
74708
- }
74709
- const answered = await backend({ query, ...allowedDomains === void 0 ? {} : { allowedDomains }, ...blockedDomains === void 0 ? {} : { blockedDomains }, ...searchProfile === void 0 ? {} : { searchProfile } });
74710
- deps.log?.(`rc selfhost ${session.id}: worker web-search of "${query}" answered by its backend`);
74711
- if (isWebSearchRefusal(answered)) {
74712
- await answerJson2(response, HTTP_STATUS.ok, { results: [], error: { error_type: answered.errorType, error_message: answered.errorMessage } });
74713
- return;
74714
- }
74715
- await answerJson2(response, HTTP_STATUS.ok, { results: answered.map((result) => ({ title: result.title ?? "", url: result.url, snippet: result.snippet ?? "" })) });
74716
- return;
74717
- }
74718
- await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the self-hosted Remote Control surface serves no worker path "${workerTail}"` } });
74719
- return;
74720
- }
74721
- const clientRefusal = credentialRefusal(request7.headers);
74722
- if (clientRefusal !== void 0) {
74723
- await unauthorized(response, clientRefusal);
74724
- return;
74725
- }
74726
- if (tail === "events" && method === "POST") {
74727
- const body = parseJsonObject2(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74728
- const events = body?.events;
74729
- if (!Array.isArray(events) || events.length === 0 || events.length > RC_SELF_HOST_MAX_BATCH_EVENTS) {
74730
- 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` } });
74731
- return;
74732
- }
74733
- const results = [];
74734
- for (const event of events) {
74735
- if (!isRecord5(event) || !isRecord5(event.payload)) {
74736
- 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" } });
74737
- return;
74738
- }
74739
- const published = publish(session, RC_SOURCE_CLIENT, event.payload);
74740
- results.push({ sequence_num: String(published.sequenceNum), duplicate: published.duplicate });
74741
- }
74742
- sweep();
74743
- await answerJson2(response, HTTP_STATUS.ok, { results });
74744
- return;
74745
- }
74746
- if (tail === "events" && method === "GET") {
74747
- await answerJson2(response, HTTP_STATUS.ok, { data: session.events.map((event) => ({ event_id: event.eventId, event_type: nonEmptyString2(event.payload, "type") ?? "event", sequence_num: event.sequenceNum, source: event.source, payload: event.payload, created_at: event.createdAt })) });
74748
- return;
74749
- }
74750
- if (tail === "events/stream" && method === "GET") {
74751
- await holdStream(session, "clientStreams", () => true, request7, response);
74752
- return;
74753
- }
74754
- if (tail === "client/presence" && method === "POST") {
74755
- await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
74756
- await answerJson2(response, HTTP_STATUS.ok, { refresh_after_seconds: RC_PRESENCE_REFRESH_SECONDS });
74757
- return;
74758
- }
74759
- if (tail === "mark_read" && method === "POST") {
74760
- await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
74761
- await answerJson2(response, HTTP_STATUS.ok, {});
74762
- return;
74763
- }
74764
- if (tail === "teleport-events" && method === "POST") {
74765
- await readBody3(request7.body, CONTROL_BODY_CAP_BYTES);
74766
- await answerJson2(response, HTTP_STATUS.ok, {});
74767
- return;
74768
- }
74769
- await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the self-hosted Remote Control surface serves no path "${tail}"` } });
74770
- };
74771
- const recordMissing = () => deps.credentialRecord() === void 0;
74772
- const route = {
74773
- name: "rc-selfhost",
74774
- headroomEligible: false,
74775
- headroomUpstream: void 0,
74776
- serve: async (request7, response) => {
74777
- const method = request7.method.toUpperCase();
74778
- const pathname = new URL(request7.url, "http://127.0.0.1").pathname;
74779
- if (recordMissing()) {
74780
- await noCredential(response);
74781
- return;
74782
- }
74783
- if (pathname === RC_COMPAT_SESSIONS_PATH) {
74784
- if (method !== "GET") {
74785
- await answerJson2(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the compatibility session list is a GET" } });
74786
- return;
74787
- }
74788
- const listRefusal = credentialRefusal(request7.headers);
74789
- if (listRefusal !== void 0) {
74790
- await unauthorized(response, listRefusal);
74791
- return;
74792
- }
74793
- await answerJson2(response, HTTP_STATUS.ok, { data: [] });
74794
- return;
74795
- }
74796
- if (pathname !== RC_SESSIONS_PATH_PREFIX2 && !pathname.startsWith(`${RC_SESSIONS_PATH_PREFIX2}/`)) {
74797
- await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the self-hosted Remote Control surface serves no path "${pathname}"` } });
74798
- return;
74799
- }
74800
- if (pathname === RC_SESSIONS_PATH_PREFIX2) {
74801
- if (method === "POST") {
74802
- const createRefusal = credentialRefusal(request7.headers);
74803
- if (createRefusal !== void 0) {
74804
- await unauthorized(response, createRefusal);
74805
- return;
74806
- }
74807
- const body = parseJsonObject2(await readBody3(request7.body, CONTROL_BODY_CAP_BYTES));
74808
- const session = {
74809
- id: `${RC_SESSION_ID_PREFIX2}${deps.newUuid()}`,
74810
- createdAt: deps.now(),
74811
- title: nonEmptyString2(body, "title"),
74812
- workerJwts: /* @__PURE__ */ new Set(),
74813
- sequenceNum: 0,
74814
- events: [],
74815
- seenPayloadUuids: /* @__PURE__ */ new Set(),
74816
- workerStreams: /* @__PURE__ */ new Set(),
74817
- clientStreams: /* @__PURE__ */ new Set(),
74818
- internalEvents: [],
74819
- workerStatus: void 0,
74820
- externalMetadata: void 0,
74821
- lastTrafficAt: deps.now()
74822
- };
74823
- sessions.set(session.id, session);
74824
- deps.log?.(`rc selfhost ${session.id}: session created on the door's own surface`);
74825
- await answerJson2(response, HTTP_STATUS.ok, { session: { id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), status: "active" } });
74826
- return;
74827
- }
74828
- if (method === "GET") {
74829
- const readRefusal = credentialRefusal(request7.headers);
74830
- if (readRefusal !== void 0) {
74831
- await unauthorized(response, readRefusal);
74832
- return;
74833
- }
74834
- 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" })) });
74835
- return;
74836
- }
74837
- await answerJson2(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the session collection is a POST (create) and a GET (list)" } });
74838
- return;
74839
- }
74840
- const rest = pathname.slice(`${RC_SESSIONS_PATH_PREFIX2}/`.length);
74841
- const slash = rest.indexOf("/");
74842
- const id = slash === -1 ? rest : rest.slice(0, slash);
74843
- const tail = slash === -1 ? "" : rest.slice(slash + 1);
74844
- if (!id.startsWith(RC_SESSION_ID_PREFIX2)) {
74845
- await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: "the session family names one cse_-prefixed session id" } });
74846
- return;
74847
- }
74848
- await serveSessionPath(sessions.get(id), tail, request7, response);
74849
- }
74850
- };
74851
- const answerServerJson = (response, status, body) => {
74852
- const text2 = JSON.stringify(body);
74853
- response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text2, "utf8")) });
74854
- response.end(text2);
74855
- };
74856
- const local = {
74857
- parsesHost: (host) => host === RC_SELF_HOST_OAUTH_HOST,
74858
- serve: async (host, request7, response) => {
74859
- const method = (request7.method ?? "").toUpperCase();
74860
- const pathname = new URL(request7.url ?? "/", "http://127.0.0.1").pathname;
74861
- if (host === RC_SELF_HOST_OAUTH_HOST) {
74862
- if (method === "POST" && pathname === "/v1/oauth/token") {
74863
- const body = parseJsonObject2(await readBody3(request7, CONTROL_BODY_CAP_BYTES));
74864
- const record2 = deps.credentialRecord();
74865
- if (record2 === void 0) {
74866
- 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" });
74867
- return true;
74868
- }
74869
- if (body?.grant_type !== void 0 && body.grant_type !== "refresh_token") {
74870
- answerServerJson(response, HTTP_STATUS.badRequest, { error: "unsupported_grant_type" });
74871
- return true;
74872
- }
74873
- 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(" ") });
74874
- return true;
74611
+ 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(" ") });
74612
+ return true;
74875
74613
  }
74876
74614
  if (method === "GET" && pathname === "/v1/oauth/hello") {
74877
74615
  answerServerJson(response, HTTP_STATUS.ok, {});
@@ -74938,95 +74676,960 @@ data: ${JSON.stringify(envelope)}
74938
74676
  };
74939
74677
  }
74940
74678
 
74941
- // src/frontdoor/rcSelfHostMint.ts
74942
- var import_node_path35 = __toESM(require("node:path"), 1);
74943
- var CREDENTIALS_FILE = ".credentials.json";
74944
- var CLAUDE_JSON2 = ".claude.json";
74945
- var RC_SELF_HOST_RECORD_DIR = "rc-selfhost";
74946
- var RC_SELF_HOST_RECORD_FILE = "credential.json";
74947
- var SELF_HOST_EMAIL = "selfhost@agent-shim.invalid";
74948
- var SELF_HOST_SUBSCRIPTION_TYPE = "max";
74949
- function isRecord6(value2) {
74950
- return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
74679
+ // src/frontdoor/rcSelfHostMint.ts
74680
+ var CREDENTIALS_FILE = ".credentials.json";
74681
+ var CLAUDE_JSON2 = ".claude.json";
74682
+ var RC_SELF_HOST_RECORD_DIR = "rc-selfhost";
74683
+ var RC_SELF_HOST_RECORD_FILE = "credential.json";
74684
+ var SELF_HOST_EMAIL = "selfhost@agent-shim.invalid";
74685
+ var SELF_HOST_SUBSCRIPTION_TYPE = "max";
74686
+ function isRecord5(value2) {
74687
+ return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
74688
+ }
74689
+ function readJsonObject(fs20, file2) {
74690
+ const raw = fs20.readFileUtf8(file2);
74691
+ if (raw === void 0) {
74692
+ return void 0;
74693
+ }
74694
+ let parsed;
74695
+ try {
74696
+ parsed = JSON.parse(raw);
74697
+ } catch {
74698
+ return void 0;
74699
+ }
74700
+ return isRecord5(parsed) ? parsed : void 0;
74701
+ }
74702
+ function readRcSelfHostRecord(fs20, frontdoorDir) {
74703
+ const parsed = readJsonObject(fs20, import_node_path34.default.join(frontdoorDir, RC_SELF_HOST_RECORD_DIR, RC_SELF_HOST_RECORD_FILE));
74704
+ if (parsed === void 0) {
74705
+ return void 0;
74706
+ }
74707
+ const { accessToken, refreshToken, organizationUuid, accountUuid } = parsed;
74708
+ if (typeof accessToken !== "string" || accessToken === "" || typeof refreshToken !== "string" || refreshToken === "" || typeof organizationUuid !== "string" || organizationUuid === "" || typeof accountUuid !== "string" || accountUuid === "") {
74709
+ return void 0;
74710
+ }
74711
+ return { accessToken, refreshToken, organizationUuid, accountUuid };
74712
+ }
74713
+ function mintRcSelfHostCredential(deps) {
74714
+ if (!isIdentityName(deps.identity)) {
74715
+ throw new Error(`"${deps.identity}" is not a valid identity name.`);
74716
+ }
74717
+ const identityDir = import_node_path34.default.join(deps.identitiesDir, deps.identity);
74718
+ const credentialsFile = import_node_path34.default.join(identityDir, CREDENTIALS_FILE);
74719
+ const claudeJsonFile = import_node_path34.default.join(identityDir, CLAUDE_JSON2);
74720
+ const recordDir = import_node_path34.default.join(deps.frontdoorDir, RC_SELF_HOST_RECORD_DIR);
74721
+ const recordFile = import_node_path34.default.join(recordDir, RC_SELF_HOST_RECORD_FILE);
74722
+ const previousRecord = readRcSelfHostRecord(deps.fs, deps.frontdoorDir);
74723
+ const existingCredentials = readJsonObject(deps.fs, credentialsFile);
74724
+ const existingOauth = isRecord5(existingCredentials?.claudeAiOauth) ? existingCredentials.claudeAiOauth : void 0;
74725
+ const existingToken = typeof existingOauth?.accessToken === "string" && existingOauth.accessToken !== "" ? existingOauth.accessToken : void 0;
74726
+ const existingIsOwn = existingToken !== void 0 && previousRecord?.accessToken === existingToken;
74727
+ if (existingToken !== void 0 && !existingIsOwn && !deps.force) {
74728
+ throw new Error(
74729
+ `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.`
74730
+ );
74731
+ }
74732
+ const accessToken = `sk-ant-oat${deps.randomToken()}`;
74733
+ const refreshToken = deps.randomToken();
74734
+ const organizationUuid = deps.newUuid();
74735
+ const accountUuid = deps.newUuid();
74736
+ const mintedAt = new Date(deps.now()).toISOString();
74737
+ deps.fs.mkdirPrivate(import_node_path34.default.dirname(credentialsFile));
74738
+ deps.fs.writeFilePrivate(credentialsFile, `${JSON.stringify({ ...existingCredentials, claudeAiOauth: { accessToken, refreshToken, expiresAt: null, scopes: [...RC_SELF_HOST_SCOPE_LIST], subscriptionType: SELF_HOST_SUBSCRIPTION_TYPE, rateLimitTier: null } }, null, 2)}
74739
+ `);
74740
+ const claudeJson = readJsonObject(deps.fs, claudeJsonFile) ?? {};
74741
+ const existingBlock = (value2) => isRecord5(value2) ? value2 : {};
74742
+ deps.fs.writeFilePrivate(claudeJsonFile, `${JSON.stringify({
74743
+ ...claudeJson,
74744
+ oauthAccount: {
74745
+ ...existingBlock(claudeJson.oauthAccount),
74746
+ accountUuid,
74747
+ emailAddress: SELF_HOST_EMAIL,
74748
+ organizationUuid,
74749
+ organizationName: "agent-shim self-hosted",
74750
+ // 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.
74751
+ billingType: "stripe_subscription",
74752
+ accountCreatedAt: mintedAt,
74753
+ subscriptionCreatedAt: mintedAt
74754
+ },
74755
+ cachedGrowthBookFeatures: {
74756
+ ...existingBlock(claudeJson.cachedGrowthBookFeatures),
74757
+ tengu_ccr_bridge: true,
74758
+ tengu_bridge_repl_v2: true,
74759
+ tengu_bridge_repl_v2_cse_shim_enabled: true
74760
+ }
74761
+ }, null, 2)}
74762
+ `);
74763
+ deps.fs.mkdirPrivate(recordDir);
74764
+ deps.fs.writeFilePrivate(recordFile, `${JSON.stringify({ accessToken, refreshToken, organizationUuid, accountUuid }, null, 2)}
74765
+ `);
74766
+ return { identity: deps.identity, organizationUuid, credentialsFile, claudeJsonFile, recordFile, replaced: existingIsOwn };
74767
+ }
74768
+
74769
+ // src/frontdoor/rcCommands.ts
74770
+ var MINTED_TOKEN_RANDOM_BYTES = 32;
74771
+ function reportControlWrite(what, session, result, json2, extra = {}) {
74772
+ if (!result.ok) {
74773
+ throw new Error(result.message);
74774
+ }
74775
+ if (json2 === true) {
74776
+ printJson({ session, ...result.requestId === void 0 ? {} : { request: result.requestId }, sequenceNums: result.sequenceNums, ...extra });
74777
+ return;
74778
+ }
74779
+ console.log(`${what} on ${session}${result.requestId === void 0 ? "" : `: request ${result.requestId}`} sequence_num ${result.sequenceNums.join(", ")}`);
74780
+ }
74781
+ function formatRcSessionList(sessions) {
74782
+ if (sessions.length === 0) {
74783
+ return ["no Remote Control sessions observed"];
74784
+ }
74785
+ return sessions.map((session) => `${session.id} created ${new Date(session.createdAt).toISOString()} last seen ${new Date(session.lastSeenAt).toISOString()}`);
74786
+ }
74787
+ function formatRcSessionStatus(statuses) {
74788
+ if (statuses.length === 0) {
74789
+ return ["no Remote Control sessions observed"];
74790
+ }
74791
+ const lines = [];
74792
+ for (const status of statuses) {
74793
+ lines.push(`${status.id} created ${new Date(status.createdAt).toISOString()} last seen ${new Date(status.lastSeenAt).toISOString()}`);
74794
+ const worker = status.workerState === void 0 && status.workerIdleSeconds === void 0 ? "not observed" : [
74795
+ status.workerState === void 0 ? "state unknown" : `state ${status.workerState.value} since ${new Date(status.workerState.observedAt).toISOString()}`,
74796
+ status.workerIdleSeconds === void 0 ? "idle unknown" : `idle ${String(status.workerIdleSeconds.value)} s as of ${new Date(status.workerIdleSeconds.observedAt).toISOString()}`
74797
+ ].join(", ");
74798
+ lines.push(` worker: ${worker}`);
74799
+ if (status.pending.length === 0) {
74800
+ lines.push(" pending: none");
74801
+ } else {
74802
+ lines.push(" pending:");
74803
+ for (const pending of status.pending) {
74804
+ lines.push(...formatRcPendingList([pending]).map((line) => ` ${line}`));
74805
+ }
74806
+ }
74807
+ }
74808
+ return lines;
74809
+ }
74810
+ function formatRcPendingList(pending) {
74811
+ if (pending.length === 0) {
74812
+ return ["no pending control requests observed"];
74813
+ }
74814
+ return pending.map((request7) => `${request7.sessionId} ${request7.requestId} ${request7.type}${request7.summary === "" ? "" : ` ${request7.summary}`} observed ${new Date(request7.observedAt).toISOString()}`);
74815
+ }
74816
+ function eventExcerpt(text2) {
74817
+ return text2.length <= RC_PENDING_SUMMARY_EXCERPT_CHARS ? text2 : `${text2.slice(0, RC_PENDING_SUMMARY_EXCERPT_CHARS)}...`;
74818
+ }
74819
+ function formatRcStreamEvent(event) {
74820
+ const { envelope } = event;
74821
+ const sketch = envelope.payload === void 0 ? "" : ` ${eventExcerpt(JSON.stringify(envelope.payload))}`;
74822
+ return `${event.session} ${envelope.event_type} sequence_num ${String(envelope.sequence_num)} source ${envelope.source}${sketch}`;
74823
+ }
74824
+ function formatRcSelfHostMint(result) {
74825
+ return [
74826
+ `identity: ${result.identity} (organisation ${result.organizationUuid})`,
74827
+ `credential: ${result.credentialsFile}${result.replaced ? " (replacing this door's previous mint)" : ""}`,
74828
+ `account block: ${result.claudeJsonFile} (oauthAccount and the feature-cache seed merged in)`,
74829
+ `door record: ${result.recordFile} (the copy the door's served surface authenticates against)`,
74830
+ "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"
74831
+ ];
74832
+ }
74833
+ function frontDoorRcControlFromState(fsPort, paths) {
74834
+ const state = readFrontDoorState(fsPort, paths.frontdoorStateFile);
74835
+ if (state?.port === void 0) {
74836
+ 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");
74837
+ }
74838
+ const ca = fsPort.readFileUtf8(paths.frontdoorCaCertFile);
74839
+ if (ca === void 0) {
74840
+ throw new Error(`the front door's CA certificate is missing at ${paths.frontdoorCaCertFile}, so its control listener cannot be authenticated`);
74841
+ }
74842
+ const token = fsPort.readFileUtf8(paths.frontdoorControlTokenFile)?.trim();
74843
+ if (token === void 0 || token === "") {
74844
+ throw new Error(`the serving front door's control token is missing at ${paths.frontdoorControlTokenFile}`);
74845
+ }
74846
+ return frontDoorRcControl(realRcControlTransport(state.port, ca), token);
74847
+ }
74848
+ function frontDoorRcApiFromState(fsPort, paths) {
74849
+ const state = readFrontDoorState(fsPort, paths.frontdoorStateFile);
74850
+ if (state?.port === void 0) {
74851
+ 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");
74852
+ }
74853
+ const ca = fsPort.readFileUtf8(paths.frontdoorCaCertFile);
74854
+ if (ca === void 0) {
74855
+ throw new Error(`the front door's CA certificate is missing at ${paths.frontdoorCaCertFile}, so its typed API cannot be authenticated`);
74856
+ }
74857
+ const token = fsPort.readFileUtf8(paths.frontdoorControlTokenFile)?.trim();
74858
+ if (token === void 0 || token === "") {
74859
+ throw new Error(`the serving front door's control token is missing at ${paths.frontdoorControlTokenFile}`);
74860
+ }
74861
+ return frontDoorRcApiClient(state.port, ca, token);
74862
+ }
74863
+ function registerRcCommand(frontdoor, paths) {
74864
+ const rc = withExamples(
74865
+ 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)."),
74866
+ ["agent-shim frontdoor rc list", "agent-shim frontdoor rc pending"]
74867
+ );
74868
+ withExamples(
74869
+ 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) => {
74870
+ const sessions = await frontDoorRcControlFromState(realFarmFs, paths).listSessions();
74871
+ if (options.json === true) {
74872
+ printJson({ sessions });
74873
+ return;
74874
+ }
74875
+ for (const line of formatRcSessionList(sessions)) {
74876
+ console.log(line);
74877
+ }
74878
+ }),
74879
+ ["agent-shim frontdoor rc list", "agent-shim frontdoor rc list --json"]
74880
+ );
74881
+ withExamples(
74882
+ 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) => {
74883
+ const statuses = await frontDoorRcControlFromState(realFarmFs, paths).statusOf(session);
74884
+ if (options.json === true) {
74885
+ printJson({ statuses });
74886
+ return;
74887
+ }
74888
+ for (const line of formatRcSessionStatus(statuses)) {
74889
+ console.log(line);
74890
+ }
74891
+ }),
74892
+ ["agent-shim frontdoor rc status", "agent-shim frontdoor rc status cse_00000000-0000-4000-8000-000000000000 --json"]
74893
+ );
74894
+ withExamples(
74895
+ 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) => {
74896
+ const pending = await frontDoorRcControlFromState(realFarmFs, paths).pendingOf(session);
74897
+ if (options.json === true) {
74898
+ printJson({ pending });
74899
+ return;
74900
+ }
74901
+ for (const line of formatRcPendingList(pending)) {
74902
+ console.log(line);
74903
+ }
74904
+ }),
74905
+ ["agent-shim frontdoor rc pending", "agent-shim frontdoor rc pending --json"]
74906
+ );
74907
+ withExamples(
74908
+ 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) => {
74909
+ const result = await frontDoorRcControlFromState(realFarmFs, paths).sendPrompt(options.session, options.text);
74910
+ if (!result.ok) {
74911
+ throw new Error(result.message);
74912
+ }
74913
+ if (options.json === true) {
74914
+ printJson({ session: options.session, sequenceNums: result.sequenceNums });
74915
+ return;
74916
+ }
74917
+ console.log(`sent to ${options.session}: sequence_num ${result.sequenceNums.join(", ")}`);
74918
+ }),
74919
+ ['agent-shim frontdoor rc send --session cse_00000000-0000-4000-8000-000000000000 --text "run the tests"']
74920
+ );
74921
+ withExamples(
74922
+ 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) => {
74923
+ if (options.approve === options.deny) {
74924
+ throw new UsageError("answer exactly one way: pass --approve or --deny, not both and not neither");
74925
+ }
74926
+ if (options.approve === true && options.text !== void 0) {
74927
+ throw new UsageError("--text is the denial message; an approval carries no text (the protocol's allow result has no message field)");
74928
+ }
74929
+ const result = await frontDoorRcControlFromState(realFarmFs, paths).answerRequest(options.session, options.request, { approve: options.approve === true, message: options.deny === true ? options.text : void 0 });
74930
+ if (!result.ok) {
74931
+ throw new Error(result.message);
74932
+ }
74933
+ if (options.json === true) {
74934
+ printJson({ session: options.session, request: options.request, sequenceNums: result.sequenceNums });
74935
+ return;
74936
+ }
74937
+ console.log(`${options.approve === true ? "approved" : "denied"} ${options.request} on ${options.session}: sequence_num ${result.sequenceNums.join(", ")}`);
74938
+ }),
74939
+ [
74940
+ "agent-shim frontdoor rc answer --session cse_00000000-0000-4000-8000-000000000000 --request req_00000000-0000-4000-8000-000000000000 --approve",
74941
+ 'agent-shim frontdoor rc answer --session cse_00000000-0000-4000-8000-000000000000 --request req_00000000-0000-4000-8000-000000000000 --deny --text "not today"'
74942
+ ]
74943
+ );
74944
+ withExamples(
74945
+ 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) => {
74946
+ reportControlWrite("interrupted", options.session, await frontDoorRcControlFromState(realFarmFs, paths).interruptSession(options.session), options.json);
74947
+ }),
74948
+ ["agent-shim frontdoor rc interrupt --session cse_00000000-0000-4000-8000-000000000000"]
74949
+ );
74950
+ withExamples(
74951
+ 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) => {
74952
+ reportControlWrite(`set model ${options.model}`, options.session, await frontDoorRcControlFromState(realFarmFs, paths).setModel(options.session, options.model), options.json, { model: options.model });
74953
+ }),
74954
+ ["agent-shim frontdoor rc set-model --session cse_00000000-0000-4000-8000-000000000000 --model claude-opus-5-5"]
74955
+ );
74956
+ withExamples(
74957
+ 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) => {
74958
+ if (!isRcPermissionMode(options.mode)) {
74959
+ throw new UsageError(`--mode must be one of the SDK's own permission modes (${RC_PERMISSION_MODES.join(", ")}), not "${options.mode}"`);
74960
+ }
74961
+ reportControlWrite(`set permission mode ${options.mode}`, options.session, await frontDoorRcControlFromState(realFarmFs, paths).setPermissionMode(options.session, options.mode), options.json, { mode: options.mode });
74962
+ }),
74963
+ [
74964
+ "agent-shim frontdoor rc set-permission-mode --session cse_00000000-0000-4000-8000-000000000000 --mode plan",
74965
+ "agent-shim frontdoor rc set-permission-mode --session cse_00000000-0000-4000-8000-000000000000 --mode acceptEdits"
74966
+ ]
74967
+ );
74968
+ withExamples(
74969
+ 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) => {
74970
+ reportControlWrite("ended", options.session, await frontDoorRcControlFromState(realFarmFs, paths).endSession(options.session, options.reason), options.json);
74971
+ }),
74972
+ ["agent-shim frontdoor rc end-session --session cse_00000000-0000-4000-8000-000000000000 --reason done for today"]
74973
+ );
74974
+ withExamples(
74975
+ 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) => {
74976
+ reportControlWrite("requested usage", options.session, await frontDoorRcControlFromState(realFarmFs, paths).getUsage(options.session, options.skipBehaviors === true ? true : void 0), options.json);
74977
+ }),
74978
+ ["agent-shim frontdoor rc get-usage --session cse_00000000-0000-4000-8000-000000000000 --skip-behaviors"]
74979
+ );
74980
+ withExamples(
74981
+ 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) => {
74982
+ if (options.detail !== void 0 && !isRcContextUsageDetail(options.detail)) {
74983
+ throw new UsageError(`--detail must be one of the SDK's own context-usage levels (${RC_CONTEXT_USAGE_DETAILS.join(", ")}), not "${options.detail}"`);
74984
+ }
74985
+ reportControlWrite("requested context usage", options.session, await frontDoorRcControlFromState(realFarmFs, paths).getContextUsage(options.session, options.detail), options.json);
74986
+ }),
74987
+ ["agent-shim frontdoor rc get-context-usage --session cse_00000000-0000-4000-8000-000000000000 --detail summary"]
74988
+ );
74989
+ withExamples(
74990
+ 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) => {
74991
+ if (options.maxBytes !== void 0 && !/^[1-9][0-9]*$/.test(options.maxBytes)) {
74992
+ throw new UsageError(`--max-bytes must be a positive whole number of bytes, not "${options.maxBytes}"`);
74993
+ }
74994
+ if (options.encoding !== void 0 && !isRcReadFileEncoding(options.encoding)) {
74995
+ throw new UsageError(`--encoding must be one of the SDK's own read-file encodings (${RC_READ_FILE_ENCODINGS.join(", ")}), not "${options.encoding}"`);
74996
+ }
74997
+ 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 });
74998
+ }),
74999
+ [
75000
+ "agent-shim frontdoor rc read-file --session cse_00000000-0000-4000-8000-000000000000 --path src/index.ts",
75001
+ "agent-shim frontdoor rc read-file --session cse_00000000-0000-4000-8000-000000000000 --path screenshot.png --encoding base64 --max-bytes 65536"
75002
+ ]
75003
+ );
75004
+ withExamples(
75005
+ 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) => {
75006
+ reportControlWrite(`requested file suggestions for "${options.query}"`, options.session, await frontDoorRcControlFromState(realFarmFs, paths).fileSuggestions(options.session, options.query), options.json);
75007
+ }),
75008
+ ['agent-shim frontdoor rc file-suggestions --session cse_00000000-0000-4000-8000-000000000000 --query "src/frontdoor/rc"']
75009
+ );
75010
+ withExamples(
75011
+ 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) => {
75012
+ reportControlWrite("sent a keep-alive", options.session, await frontDoorRcControlFromState(realFarmFs, paths).keepAlive(options.session), options.json);
75013
+ }),
75014
+ ["agent-shim frontdoor rc keep-alive --session cse_00000000-0000-4000-8000-000000000000"]
75015
+ );
75016
+ withExamples(
75017
+ 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) => {
75018
+ reportControlWrite("requested MCP status", options.session, await frontDoorRcControlFromState(realFarmFs, paths).mcpStatus(options.session), options.json);
75019
+ }),
75020
+ ["agent-shim frontdoor rc mcp-status --session cse_00000000-0000-4000-8000-000000000000"]
75021
+ );
75022
+ withExamples(
75023
+ 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) => {
75024
+ reportControlWrite(`requested a reconnect of MCP server ${options.server}`, options.session, await frontDoorRcControlFromState(realFarmFs, paths).mcpReconnect(options.session, options.server), options.json);
75025
+ }),
75026
+ ["agent-shim frontdoor rc mcp-reconnect --session cse_00000000-0000-4000-8000-000000000000 --server github"]
75027
+ );
75028
+ withExamples(
75029
+ 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) => {
75030
+ reportControlWrite(`requested authentication of MCP server ${options.server}`, options.session, await frontDoorRcControlFromState(realFarmFs, paths).mcpAuthenticate(options.session, options.server, options.redirectUri), options.json);
75031
+ }),
75032
+ ["agent-shim frontdoor rc mcp-authenticate --session cse_00000000-0000-4000-8000-000000000000 --server github --redirect-uri https://example.com/oauth/callback"]
75033
+ );
75034
+ withExamples(
75035
+ 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) => {
75036
+ reportControlWrite(`handed MCP server ${options.server} its OAuth callback`, options.session, await frontDoorRcControlFromState(realFarmFs, paths).mcpOAuthCallbackUrl(options.session, options.server, options.callbackUrl), options.json);
75037
+ }),
75038
+ ["agent-shim frontdoor rc mcp-oauth-callback-url --session cse_00000000-0000-4000-8000-000000000000 --server github --callback-url https://example.com/oauth/callback?code=x"]
75039
+ );
75040
+ withExamples(
75041
+ rc.command("watch").description("Print the observed sessions' client read stream events as they arrive, the approvals the door's held stream receives above all, until Ctrl-C leaves. Read-only.").argument("[session]", "One cse_ session id, as `frontdoor rc list` shows it; every observed session when omitted.").option("--json", "Print each event as one JSON line as it arrives.").action(async (session, options) => {
75042
+ const client = frontDoorRcApiFromState(realFarmFs, paths);
75043
+ const leave = new AbortController();
75044
+ const onInterrupt = () => {
75045
+ leave.abort();
75046
+ };
75047
+ process.once("SIGINT", onInterrupt);
75048
+ try {
75049
+ for await (const event of await client.rc.subscribe(session === void 0 ? {} : { session }, { signal: leave.signal })) {
75050
+ if (options.json === true) {
75051
+ console.log(JSON.stringify(event));
75052
+ continue;
75053
+ }
75054
+ console.log(formatRcStreamEvent(event));
75055
+ }
75056
+ } catch (error62) {
75057
+ if (!leave.signal.aborted) {
75058
+ throw error62;
75059
+ }
75060
+ } finally {
75061
+ process.off("SIGINT", onInterrupt);
75062
+ }
75063
+ }),
75064
+ ["agent-shim frontdoor rc watch", "agent-shim frontdoor rc watch cse_00000000-0000-4000-8000-000000000000 --json"]
75065
+ );
75066
+ const selfhost = withExamples(
75067
+ 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."),
75068
+ ["agent-shim frontdoor rc selfhost mint rig"]
75069
+ );
75070
+ withExamples(
75071
+ 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) => {
75072
+ const result = mintRcSelfHostCredential({
75073
+ fs: realFarmFs,
75074
+ identitiesDir: paths.identitiesDir,
75075
+ frontdoorDir: paths.frontdoorDir,
75076
+ identity,
75077
+ newUuid: import_node_crypto14.randomUUID,
75078
+ randomToken: () => (0, import_node_crypto14.randomBytes)(MINTED_TOKEN_RANDOM_BYTES).toString("base64url"),
75079
+ force: options.force === true,
75080
+ now: () => Date.now()
75081
+ });
75082
+ if (options.json === true) {
75083
+ printJson(result);
75084
+ return;
75085
+ }
75086
+ for (const line of formatRcSelfHostMint(result)) {
75087
+ console.log(line);
75088
+ }
75089
+ }),
75090
+ ["agent-shim frontdoor rc selfhost mint rig", "agent-shim frontdoor rc selfhost mint rig --force"]
75091
+ );
75092
+ }
75093
+
75094
+ // src/frontdoor/rcCredentialStore.ts
75095
+ var import_node_path35 = __toESM(require("node:path"), 1);
75096
+ function isSafeSessionId(sessionId) {
75097
+ return /^[A-Za-z0-9_-]+$/.test(sessionId);
75098
+ }
75099
+ function isRecord6(value2) {
75100
+ return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
75101
+ }
75102
+ function credentialOfJson(parsed) {
75103
+ if (!isRecord6(parsed)) {
75104
+ return void 0;
75105
+ }
75106
+ const { authorization, anthropicVersion, anthropicClientPlatform } = parsed;
75107
+ if (typeof authorization !== "string" || authorization === "") {
75108
+ return void 0;
75109
+ }
75110
+ return {
75111
+ authorization,
75112
+ anthropicVersion: typeof anthropicVersion === "string" ? anthropicVersion : void 0,
75113
+ anthropicClientPlatform: typeof anthropicClientPlatform === "string" ? anthropicClientPlatform : void 0
75114
+ };
75115
+ }
75116
+ function sequenceNumOfJson(value2) {
75117
+ return typeof value2 === "number" && Number.isInteger(value2) && value2 >= 0 ? value2 : void 0;
75118
+ }
75119
+ function createRcCredentialStore(fs20, dir) {
75120
+ fs20.mkdirPrivate(dir);
75121
+ const fileFor = (sessionId) => import_node_path35.default.join(dir, `${sessionId}.json`);
75122
+ const readParsed = (sessionId) => {
75123
+ const raw = fs20.readFileUtf8(fileFor(sessionId));
75124
+ if (raw === void 0) {
75125
+ return void 0;
75126
+ }
75127
+ let parsed;
75128
+ try {
75129
+ parsed = JSON.parse(raw);
75130
+ } catch {
75131
+ return void 0;
75132
+ }
75133
+ return isRecord6(parsed) ? parsed : void 0;
75134
+ };
75135
+ return {
75136
+ read: (sessionId) => {
75137
+ if (!isSafeSessionId(sessionId)) {
75138
+ return void 0;
75139
+ }
75140
+ return credentialOfJson(readParsed(sessionId));
75141
+ },
75142
+ write: (sessionId, credential) => {
75143
+ if (!isSafeSessionId(sessionId)) {
75144
+ return;
75145
+ }
75146
+ const cursor3 = sequenceNumOfJson(readParsed(sessionId)?.highestSequenceNum);
75147
+ const record2 = { ...credential, ...cursor3 === void 0 ? {} : { highestSequenceNum: cursor3 } };
75148
+ fs20.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(record2, null, 2)}
75149
+ `);
75150
+ },
75151
+ readCursor: (sessionId) => {
75152
+ if (!isSafeSessionId(sessionId)) {
75153
+ return void 0;
75154
+ }
75155
+ return sequenceNumOfJson(readParsed(sessionId)?.highestSequenceNum);
75156
+ },
75157
+ writeCursor: (sessionId, highestSequenceNum) => {
75158
+ if (!isSafeSessionId(sessionId)) {
75159
+ return;
75160
+ }
75161
+ const credential = credentialOfJson(readParsed(sessionId));
75162
+ const record2 = credential === void 0 ? { highestSequenceNum } : { ...credential, highestSequenceNum };
75163
+ fs20.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(record2, null, 2)}
75164
+ `);
75165
+ },
75166
+ remove: (sessionId) => {
75167
+ if (!isSafeSessionId(sessionId)) {
75168
+ return;
75169
+ }
75170
+ fs20.removeRecursive(fileFor(sessionId));
75171
+ }
75172
+ };
75173
+ }
75174
+
75175
+ // src/frontdoor/rcClientPage.ts
75176
+ var RC_CLIENT_PATH = "/__agent-shim/client";
75177
+ var RC_CLIENT_PAGE_SCRIPT = `'use strict';
75178
+ // 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.
75179
+ var API_PREFIX = '/__agent-shim/orpc';
75180
+ var TOKEN_KEY = 'agent-shim-rc-token';
75181
+ var token = null;
75182
+
75183
+ function apiBase() { return window.location.origin + API_PREFIX; }
75184
+
75185
+ // Calls one procedure: resolves the output's own value, rejects with the door's error message when it answered one.
75186
+ async function rpc(path, input) {
75187
+ var response = await fetch(apiBase() + '/' + path, {
75188
+ method: 'POST',
75189
+ headers: { 'content-type': 'application/json', authorization: 'Bearer ' + token },
75190
+ body: JSON.stringify({ json: input === undefined ? {} : input }),
75191
+ });
75192
+ var envelope = await response.json().catch(function () { return {}; });
75193
+ if (!response.ok) {
75194
+ var message = envelope && envelope.json && envelope.json.message ? envelope.json.message : ('HTTP ' + response.status);
75195
+ var error = new Error(message);
75196
+ error.status = response.status;
75197
+ throw error;
75198
+ }
75199
+ return envelope.json;
75200
+ }
75201
+
75202
+ // 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.
75203
+ function parseSseFrames(buffer) {
75204
+ var frames = [];
75205
+ var rest = buffer;
75206
+ for (;;) {
75207
+ var boundary = rest.indexOf('\\n\\n');
75208
+ if (boundary === -1) { break; }
75209
+ var raw = rest.slice(0, boundary);
75210
+ rest = rest.slice(boundary + 2);
75211
+ var frame = { event: 'message', id: '', data: '' };
75212
+ var lines = raw.split('\\n');
75213
+ for (var i = 0; i < lines.length; i++) {
75214
+ var line = lines[i];
75215
+ if (line === '' || line.charAt(0) === ':') { continue; }
75216
+ var colon = line.indexOf(':');
75217
+ var field = colon === -1 ? line : line.slice(0, colon);
75218
+ var value = colon === -1 ? '' : line.slice(colon + 1);
75219
+ if (value.charAt(0) === ' ') { value = value.slice(1); }
75220
+ if (field === 'event') { frame.event = value; }
75221
+ if (field === 'id') { frame.id = value; }
75222
+ if (field === 'data') { frame.data += (frame.data === '' ? '' : '\\n') + value; }
75223
+ }
75224
+ if (frame.data !== '') { frames.push(frame); }
75225
+ }
75226
+ return { frames: frames, rest: rest };
75227
+ }
75228
+
75229
+ // 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.
75230
+ function sequenceGap(tracker, event) {
75231
+ var previous = tracker[event.source];
75232
+ if (event.sequence > (previous === undefined ? 0 : previous)) { tracker[event.source] = event.sequence; }
75233
+ if (previous === undefined || event.sequence <= previous) { return 0; }
75234
+ return event.sequence - previous - 1;
75235
+ }
75236
+
75237
+ function shortId(id) { return id.length > 14 ? id.slice(0, 8) + '..' + id.slice(-4) : id; }
75238
+
75239
+ // A bounded sketch of any payload, the feed's fallback rendering: one JSON line, cut with a marker rather than silently.
75240
+ function sketch(value, cap) {
75241
+ var text;
75242
+ try { text = JSON.stringify(value); } catch (e) { text = String(value); }
75243
+ if (text === undefined) { text = 'undefined'; }
75244
+ return text.length > cap ? text.slice(0, cap) + '...' : text;
75245
+ }
75246
+
75247
+ // The headline text of one Remote Control stream event: 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.
75248
+ function rcLine(event) {
75249
+ var envelope = event.envelope;
75250
+ var payload = envelope.payload;
75251
+ var headline = '';
75252
+ if (payload !== undefined && payload !== null && typeof payload === 'object' && Array.isArray(payload.content)) {
75253
+ var parts = [];
75254
+ for (var i = 0; i < payload.content.length; i++) {
75255
+ var block = payload.content[i];
75256
+ if (block !== null && typeof block === 'object' && block.type === 'text' && typeof block.text === 'string') { parts.push(block.text); }
75257
+ }
75258
+ if (parts.length > 0) { headline = parts.join(' '); }
75259
+ }
75260
+ if (headline === '') {
75261
+ headline = envelope.event_type;
75262
+ if (payload !== undefined) { headline += ' ' + sketch(payload, 240); }
75263
+ }
75264
+ return new Date().toLocaleTimeString() + ' ' + shortId(event.session) + ' ' + envelope.event_type + ' ' + headline;
75265
+ }
75266
+
75267
+ function el(tag, className, text) {
75268
+ var node = document.createElement(tag);
75269
+ if (className) { node.className = className; }
75270
+ if (text !== undefined) { node.textContent = text; }
75271
+ return node;
75272
+ }
75273
+
75274
+ var ui = null;
75275
+
75276
+ function renderEvent(event, gap) {
75277
+ if (ui === null) { return; }
75278
+ var list = ui.eventList;
75279
+ if (gap > 0) {
75280
+ list.appendChild(el('div', 'event gap', 'dropped events: ' + String(gap) + ' on source ' + event.source + ' (the stream holds no replay)'));
75281
+ }
75282
+ var line;
75283
+ if (event.source === 'rc') {
75284
+ line = rcLine(event.payload);
75285
+ } else {
75286
+ line = new Date().toLocaleTimeString() + ' [' + event.source + '] ' + sketch(event.payload, 240);
75287
+ }
75288
+ list.appendChild(el('div', 'event', line));
75289
+ while (list.childElementCount > 500) { list.removeChild(list.firstChild); }
75290
+ list.scrollTop = list.scrollHeight;
75291
+ }
75292
+
75293
+ function renderSessions(statuses) {
75294
+ if (ui === null) { return; }
75295
+ var list = ui.sessionList;
75296
+ list.textContent = '';
75297
+ if (statuses.length === 0) {
75298
+ list.appendChild(el('div', 'event dim', 'no Remote Control sessions observed'));
75299
+ return;
75300
+ }
75301
+ for (var i = 0; i < statuses.length; i++) {
75302
+ var status = statuses[i];
75303
+ var worker = status.workerState === undefined ? 'worker unknown' : 'worker ' + status.workerState.value;
75304
+ var row = el('div', 'session' + (status.id === ui.selected ? ' selected' : ''));
75305
+ row.appendChild(el('div', null, shortId(status.id)));
75306
+ row.appendChild(el('div', 'dim', worker + (status.pending.length === 0 ? '' : ', ' + String(status.pending.length) + ' pending') + ', seen ' + new Date(status.lastSeenAt).toLocaleTimeString()));
75307
+ row.addEventListener('click', selectSession(status.id));
75308
+ list.appendChild(row);
75309
+ }
75310
+ }
75311
+
75312
+ function selectSession(id) {
75313
+ return function () {
75314
+ if (ui === null) { return; }
75315
+ ui.selected = ui.selected === id ? null : id;
75316
+ ui.promptTarget.textContent = ui.selected === null ? 'no session selected' : 'to ' + ui.selected;
75317
+ refreshSessions();
75318
+ refreshPending();
75319
+ };
75320
+ }
75321
+
75322
+ function renderPending(pending) {
75323
+ if (ui === null) { return; }
75324
+ var list = ui.pendingList;
75325
+ list.textContent = '';
75326
+ if (pending.length === 0) {
75327
+ list.appendChild(el('div', 'event dim', 'no control requests awaiting an answer'));
75328
+ return;
75329
+ }
75330
+ for (var i = 0; i < pending.length; i++) {
75331
+ var request = pending[i];
75332
+ var card = el('div', 'card');
75333
+ card.appendChild(el('div', null, request.type + ' ' + (request.summary === '' ? shortId(request.requestId) : request.summary)));
75334
+ card.appendChild(el('div', 'dim', shortId(request.sessionId) + ' ' + shortId(request.requestId) + ' observed ' + new Date(request.observedAt).toLocaleTimeString()));
75335
+ var row = el('div', 'row');
75336
+ var denial = el('input');
75337
+ denial.type = 'text';
75338
+ denial.placeholder = 'denial message (optional)';
75339
+ var approve = el('button', null, 'Approve');
75340
+ var deny = el('button', 'secondary', 'Deny');
75341
+ approve.addEventListener('click', answerRequest(request, true, denial));
75342
+ deny.addEventListener('click', answerRequest(request, false, denial));
75343
+ row.appendChild(denial);
75344
+ row.appendChild(approve);
75345
+ row.appendChild(deny);
75346
+ card.appendChild(row);
75347
+ list.appendChild(card);
75348
+ }
75349
+ }
75350
+
75351
+ function answerRequest(request, approve, denialInput) {
75352
+ return function () {
75353
+ var input = { session: request.sessionId, request: request.requestId, approve: approve };
75354
+ if (!approve && denialInput.value !== '') { input.text = denialInput.value; }
75355
+ rpc('rc/answer', input).then(function () {
75356
+ renderEvent({ source: 'page', sequence: 0, payload: { note: (approve ? 'approved' : 'denied') + ' ' + request.requestId } }, 0);
75357
+ refreshPending();
75358
+ }, function (error) { window.alert('answer failed: ' + error.message); });
75359
+ };
75360
+ }
75361
+
75362
+ function sendPrompt() {
75363
+ if (ui === null || ui.selected === null) { window.alert('select a session first'); return; }
75364
+ var text = ui.prompt.value;
75365
+ if (text === '') { return; }
75366
+ ui.send.disabled = true;
75367
+ rpc('rc/send', { session: ui.selected, text: text }).then(function (result) {
75368
+ ui.prompt.value = '';
75369
+ renderEvent({ source: 'page', sequence: 0, payload: { note: 'sent, sequence_num ' + result.sequenceNums.join(', ') } }, 0);
75370
+ }, function (error) { window.alert('send failed: ' + error.message); }).then(function () { ui.send.disabled = false; });
75371
+ }
75372
+
75373
+ function refreshSessions() {
75374
+ rpc('rc/status', {}).then(function (result) { renderSessions(result.statuses); }, authOrReport('rc/status'));
75375
+ }
75376
+
75377
+ function refreshPending() {
75378
+ rpc('rc/pending', ui !== null && ui.selected !== null ? { session: ui.selected } : {}).then(function (result) { renderPending(result.pending); }, authOrReport('rc/pending'));
75379
+ }
75380
+
75381
+ // 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.
75382
+ function authOrReport(what) {
75383
+ return function (error) {
75384
+ if (error.status === 401) {
75385
+ handleUnauthorized();
75386
+ return;
75387
+ }
75388
+ renderEvent({ source: 'page', sequence: 0, payload: { note: what + ' failed: ' + error.message } }, 0);
75389
+ };
75390
+ }
75391
+
75392
+ function handleUnauthorized() {
75393
+ window.sessionStorage.removeItem(TOKEN_KEY);
75394
+ if (feedAbort !== null) { feedAbort.abort(); }
75395
+ streamState('');
75396
+ showTokenForm('this door refused the token: it names a different door generation, paste the current one');
75397
+ }
75398
+
75399
+ function streamState(text) { if (ui !== null) { ui.streamState.textContent = text; } }
75400
+
75401
+ var feedAbort = null;
75402
+
75403
+ // 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.
75404
+ async function runFeed() {
75405
+ var tracker = {};
75406
+ for (;;) {
75407
+ if (feedAbort !== null) { feedAbort.abort(); }
75408
+ feedAbort = new AbortController();
75409
+ var filter = document.getElementById('source-filter').value;
75410
+ var input = filter === '' ? {} : { sources: [filter] };
75411
+ try {
75412
+ streamState('stream: connecting');
75413
+ var response = await fetch(apiBase() + '/events/subscribe', {
75414
+ method: 'POST',
75415
+ headers: { 'content-type': 'application/json', authorization: 'Bearer ' + token },
75416
+ body: JSON.stringify({ json: input }),
75417
+ signal: feedAbort.signal,
75418
+ });
75419
+ if (response.status === 401) { handleUnauthorized(); return; }
75420
+ if (!response.ok || response.body === null) { throw new Error('HTTP ' + String(response.status)); }
75421
+ streamState('stream: live');
75422
+ var reader = response.body.getReader();
75423
+ var decoder = new TextDecoder();
75424
+ var buffer = '';
75425
+ for (;;) {
75426
+ var chunk = await reader.read();
75427
+ if (chunk.done) { break; }
75428
+ buffer += decoder.decode(chunk.value, { stream: true });
75429
+ var parsed = parseSseFrames(buffer);
75430
+ buffer = parsed.rest;
75431
+ for (var i = 0; i < parsed.frames.length; i++) {
75432
+ var frame = parsed.frames[i];
75433
+ if (frame.event !== 'message') { continue; }
75434
+ var event;
75435
+ try { event = JSON.parse(frame.data).json; } catch (e) { continue; }
75436
+ if (event === undefined || event.source === undefined) { continue; }
75437
+ renderEvent(event, sequenceGap(tracker, event));
75438
+ }
75439
+ }
75440
+ } catch (error) {
75441
+ if (feedAbort !== null && feedAbort.signal.aborted) { return; }
75442
+ }
75443
+ streamState('stream: dropped, reconnecting');
75444
+ await new Promise(function (resolve) { window.setTimeout(resolve, 1000); });
75445
+ }
75446
+ }
75447
+
75448
+ function showTokenForm(message) {
75449
+ if (feedAbort !== null) { feedAbort.abort(); }
75450
+ if (ui !== null) { ui.main.classList.add('hidden'); }
75451
+ var form = document.getElementById('token-form');
75452
+ form.classList.remove('hidden');
75453
+ document.getElementById('token-error').textContent = message === undefined ? '' : message;
75454
+ document.getElementById('token-input').focus();
75455
+ }
75456
+
75457
+ function startPage(heldToken) {
75458
+ token = heldToken;
75459
+ ui = {
75460
+ main: document.getElementById('main'),
75461
+ eventList: document.getElementById('event-list'),
75462
+ sessionList: document.getElementById('session-list'),
75463
+ pendingList: document.getElementById('pending-list'),
75464
+ prompt: document.getElementById('prompt'),
75465
+ promptTarget: document.getElementById('prompt-target'),
75466
+ send: document.getElementById('send'),
75467
+ streamState: document.getElementById('stream-state'),
75468
+ selected: null,
75469
+ };
75470
+ document.getElementById('token-form').classList.add('hidden');
75471
+ ui.main.classList.remove('hidden');
75472
+ document.getElementById('refresh').addEventListener('click', function () { refreshSessions(); refreshPending(); });
75473
+ document.getElementById('send').addEventListener('click', sendPrompt);
75474
+ document.getElementById('source-filter').addEventListener('change', function () { runFeed(); });
75475
+ ui.prompt.addEventListener('keydown', function (event) {
75476
+ if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) { sendPrompt(); }
75477
+ });
75478
+ refreshSessions();
75479
+ refreshPending();
75480
+ window.setInterval(function () { refreshSessions(); refreshPending(); }, 4000);
75481
+ runFeed();
75482
+ document.getElementById('door-state').textContent = 'connected';
75483
+ }
75484
+
75485
+ function boot() {
75486
+ var held = null;
75487
+ var query = new URLSearchParams(window.location.search);
75488
+ var fromQuery = query.get('token');
75489
+ if (fromQuery !== null && fromQuery !== '') {
75490
+ window.sessionStorage.setItem(TOKEN_KEY, fromQuery);
75491
+ // 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.
75492
+ window.history.replaceState(null, '', window.location.pathname);
75493
+ held = fromQuery;
75494
+ } else {
75495
+ held = window.sessionStorage.getItem(TOKEN_KEY);
75496
+ }
75497
+ document.getElementById('token-save').addEventListener('click', function () {
75498
+ var value = document.getElementById('token-input').value.trim();
75499
+ if (value === '') { return; }
75500
+ window.sessionStorage.setItem(TOKEN_KEY, value);
75501
+ startPage(value);
75502
+ });
75503
+ document.getElementById('token-input').addEventListener('keydown', function (event) {
75504
+ if (event.key === 'Enter') { document.getElementById('token-save').click(); }
75505
+ });
75506
+ if (held !== null && held !== '') { startPage(held); }
75507
+ }
75508
+
75509
+ if (typeof document !== 'undefined' && document.getElementById('event-list') !== null) {
75510
+ window.addEventListener('DOMContentLoaded', boot);
74951
75511
  }
74952
- function readJsonObject(fs20, file2) {
74953
- const raw = fs20.readFileUtf8(file2);
74954
- if (raw === void 0) {
74955
- return void 0;
74956
- }
74957
- let parsed;
74958
- try {
74959
- parsed = JSON.parse(raw);
74960
- } catch {
74961
- return void 0;
74962
- }
74963
- return isRecord6(parsed) ? parsed : void 0;
75512
+ `;
75513
+ var RC_CLIENT_PAGE_HTML = `<!doctype html>
75514
+ <html lang="en">
75515
+ <head>
75516
+ <meta charset="utf-8">
75517
+ <meta name="viewport" content="width=device-width, initial-scale=1">
75518
+ <title>agent-shim Remote Control</title>
75519
+ <style>
75520
+ :root { color-scheme: dark; }
75521
+ * { box-sizing: border-box; }
75522
+ body { margin: 0; background: #101418; color: #d7dde3; font: 14px/1.45 ui-monospace, Menlo, Consolas, monospace; }
75523
+ header { display: flex; gap: 12px; align-items: baseline; padding: 10px 16px; background: #171d24; border-bottom: 1px solid #2a333d; }
75524
+ header h1 { font-size: 15px; margin: 0; font-weight: 600; }
75525
+ header .state { color: #8b98a5; }
75526
+ main { display: flex; gap: 1px; background: #2a333d; min-height: calc(100vh - 39px); }
75527
+ section { background: #101418; padding: 12px 16px; overflow-y: auto; }
75528
+ #sessions { flex: 0 0 320px; border-right: 1px solid #2a333d; }
75529
+ #right { flex: 1; display: flex; flex-direction: column; min-width: 0; }
75530
+ #feed { flex: 1; }
75531
+ #controls { border-top: 1px solid #2a333d; }
75532
+ h2 { font-size: 12px; text-transform: uppercase; letter-spacing: 0.08em; color: #8b98a5; margin: 8px 0 8px; font-weight: 600; }
75533
+ .session { padding: 6px 8px; border: 1px solid transparent; border-radius: 4px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
75534
+ .session:hover { background: #171d24; }
75535
+ .session.selected { border-color: #3d6ea5; background: #17222e; }
75536
+ .session .dim { color: #8b98a5; }
75537
+ .event { padding: 2px 0; white-space: pre-wrap; word-break: break-word; }
75538
+ .event .dim { color: #8b98a5; }
75539
+ .event.gap { color: #d29922; }
75540
+ .event.error { color: #e5534b; }
75541
+ textarea, input[type=text], select { background: #171d24; color: #d7dde3; border: 1px solid #2a333d; border-radius: 4px; padding: 6px 8px; font: inherit; width: 100%; }
75542
+ textarea { resize: vertical; min-height: 44px; }
75543
+ button { background: #1f4a7a; color: #d7dde3; border: 1px solid #3d6ea5; border-radius: 4px; padding: 5px 12px; font: inherit; cursor: pointer; }
75544
+ button.secondary { background: #2a333d; border-color: #3a444f; }
75545
+ button:disabled { opacity: 0.5; cursor: default; }
75546
+ .row { display: flex; gap: 8px; margin: 6px 0; align-items: center; }
75547
+ .card { border: 1px solid #3d6ea5; border-radius: 4px; padding: 8px; margin: 6px 0; background: #17222e; }
75548
+ .card .dim { color: #8b98a5; }
75549
+ #token-form { max-width: 480px; margin: 48px auto; }
75550
+ #token-form p { color: #8b98a5; }
75551
+ .hidden { display: none !important; }
75552
+ </style>
75553
+ </head>
75554
+ <body data-agent-shim-rc-client>
75555
+ <header>
75556
+ <h1>agent-shim Remote Control</h1>
75557
+ <span class="state" id="door-state">connecting</span>
75558
+ <span class="state" id="stream-state"></span>
75559
+ </header>
75560
+ <div id="token-form">
75561
+ <h2>This door's control token</h2>
75562
+ <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>
75563
+ <div class="row"><input type="text" id="token-input" placeholder="control token" autocomplete="off"><button id="token-save">Use</button></div>
75564
+ <p id="token-error" class="event error"></p>
75565
+ </div>
75566
+ <main class="hidden" id="main">
75567
+ <section id="sessions">
75568
+ <h2>Sessions <button class="secondary" id="refresh">Refresh</button></h2>
75569
+ <div id="session-list"></div>
75570
+ </section>
75571
+ <section id="right">
75572
+ <div id="feed">
75573
+ <h2>Feed <select id="source-filter"><option value="">all sources</option><option value="rc">Remote Control only</option></select></h2>
75574
+ <div id="event-list"></div>
75575
+ </div>
75576
+ <div id="controls">
75577
+ <h2>Pending approvals</h2>
75578
+ <div id="pending-list"></div>
75579
+ <h2>Send a prompt</h2>
75580
+ <div class="row"><textarea id="prompt" placeholder="Prompt for the selected session"></textarea></div>
75581
+ <div class="row"><span class="dim" id="prompt-target">no session selected</span><span style="flex:1"></span><button id="send">Send</button></div>
75582
+ </div>
75583
+ </section>
75584
+ </main>
75585
+ <script>
75586
+ ${RC_CLIENT_PAGE_SCRIPT}
75587
+ </script>
75588
+ </body>
75589
+ </html>
75590
+ `;
75591
+ function pageTokenOf(request7, url2) {
75592
+ const presented = request7.headers.authorization;
75593
+ const bearerPrefix = "bearer ".length;
75594
+ const header = typeof presented === "string" && presented.slice(0, bearerPrefix).toLowerCase() === "bearer " ? presented.slice(bearerPrefix) : void 0;
75595
+ const query = url2.searchParams.get("token") ?? void 0;
75596
+ return header ?? query;
74964
75597
  }
74965
- function readRcSelfHostRecord(fs20, frontdoorDir) {
74966
- const parsed = readJsonObject(fs20, import_node_path35.default.join(frontdoorDir, RC_SELF_HOST_RECORD_DIR, RC_SELF_HOST_RECORD_FILE));
74967
- if (parsed === void 0) {
74968
- return void 0;
74969
- }
74970
- const { accessToken, refreshToken, organizationUuid, accountUuid } = parsed;
74971
- if (typeof accessToken !== "string" || accessToken === "" || typeof refreshToken !== "string" || refreshToken === "" || typeof organizationUuid !== "string" || organizationUuid === "" || typeof accountUuid !== "string" || accountUuid === "") {
74972
- return void 0;
75598
+ function serveRcClientPage(expectedToken, request7, response) {
75599
+ const url2 = new URL(request7.url ?? "/", "https://door.invalid");
75600
+ if (url2.pathname !== RC_CLIENT_PATH && url2.pathname !== `${RC_CLIENT_PATH}/`) {
75601
+ response.writeHead(HTTP_STATUS.notFound, { "Content-Type": "text/plain" });
75602
+ response.end("no such page under the door's client prefix");
75603
+ return;
74973
75604
  }
74974
- return { accessToken, refreshToken, organizationUuid, accountUuid };
74975
- }
74976
- function mintRcSelfHostCredential(deps) {
74977
- if (!isIdentityName(deps.identity)) {
74978
- throw new Error(`"${deps.identity}" is not a valid identity name.`);
75605
+ if (request7.method !== "GET" && request7.method !== "HEAD") {
75606
+ response.writeHead(HTTP_STATUS.methodNotAllowed, { Allow: "GET, HEAD", "Content-Type": "text/plain" });
75607
+ response.end("the door's web client page is read-only");
75608
+ return;
74979
75609
  }
74980
- const identityDir = import_node_path35.default.join(deps.identitiesDir, deps.identity);
74981
- const credentialsFile = import_node_path35.default.join(identityDir, CREDENTIALS_FILE);
74982
- const claudeJsonFile = import_node_path35.default.join(identityDir, CLAUDE_JSON2);
74983
- const recordDir = import_node_path35.default.join(deps.frontdoorDir, RC_SELF_HOST_RECORD_DIR);
74984
- const recordFile = import_node_path35.default.join(recordDir, RC_SELF_HOST_RECORD_FILE);
74985
- const previousRecord = readRcSelfHostRecord(deps.fs, deps.frontdoorDir);
74986
- const existingCredentials = readJsonObject(deps.fs, credentialsFile);
74987
- const existingOauth = isRecord6(existingCredentials?.claudeAiOauth) ? existingCredentials.claudeAiOauth : void 0;
74988
- const existingToken = typeof existingOauth?.accessToken === "string" && existingOauth.accessToken !== "" ? existingOauth.accessToken : void 0;
74989
- const existingIsOwn = existingToken !== void 0 && previousRecord?.accessToken === existingToken;
74990
- if (existingToken !== void 0 && !existingIsOwn && !deps.force) {
74991
- throw new Error(
74992
- `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.`
74993
- );
75610
+ const token = pageTokenOf(request7, url2);
75611
+ if (token === void 0 || !isLiveCapability(token, [expectedToken])) {
75612
+ response.writeHead(HTTP_STATUS.unauthorized, { "Content-Type": "application/json" });
75613
+ 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" }));
75614
+ return;
74994
75615
  }
74995
- const accessToken = `sk-ant-oat${deps.randomToken()}`;
74996
- const refreshToken = deps.randomToken();
74997
- const organizationUuid = deps.newUuid();
74998
- const accountUuid = deps.newUuid();
74999
- const mintedAt = new Date(deps.now()).toISOString();
75000
- deps.fs.mkdirPrivate(import_node_path35.default.dirname(credentialsFile));
75001
- 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)}
75002
- `);
75003
- const claudeJson = readJsonObject(deps.fs, claudeJsonFile) ?? {};
75004
- const existingBlock = (value2) => isRecord6(value2) ? value2 : {};
75005
- deps.fs.writeFilePrivate(claudeJsonFile, `${JSON.stringify({
75006
- ...claudeJson,
75007
- oauthAccount: {
75008
- ...existingBlock(claudeJson.oauthAccount),
75009
- accountUuid,
75010
- emailAddress: SELF_HOST_EMAIL,
75011
- organizationUuid,
75012
- organizationName: "agent-shim self-hosted",
75013
- // 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.
75014
- billingType: "stripe_subscription",
75015
- accountCreatedAt: mintedAt,
75016
- subscriptionCreatedAt: mintedAt
75017
- },
75018
- cachedGrowthBookFeatures: {
75019
- ...existingBlock(claudeJson.cachedGrowthBookFeatures),
75020
- tengu_ccr_bridge: true,
75021
- tengu_bridge_repl_v2: true,
75022
- tengu_bridge_repl_v2_cse_shim_enabled: true
75616
+ response.writeHead(HTTP_STATUS.ok, {
75617
+ "Content-Type": "text/html; charset=utf-8",
75618
+ "Cache-Control": "no-store",
75619
+ "Referrer-Policy": "no-referrer",
75620
+ "Content-Security-Policy": "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'self'"
75621
+ });
75622
+ response.end(request7.method === "HEAD" ? void 0 : RC_CLIENT_PAGE_HTML);
75623
+ }
75624
+ function createRcClientPage(expectedToken) {
75625
+ return {
75626
+ pathPrefix: RC_CLIENT_PATH,
75627
+ handle: async (request7, response) => {
75628
+ serveRcClientPage(expectedToken, request7, response);
75629
+ await Promise.resolve();
75630
+ return { matched: true };
75023
75631
  }
75024
- }, null, 2)}
75025
- `);
75026
- deps.fs.mkdirPrivate(recordDir);
75027
- deps.fs.writeFilePrivate(recordFile, `${JSON.stringify({ accessToken, refreshToken, organizationUuid, accountUuid }, null, 2)}
75028
- `);
75029
- return { identity: deps.identity, organizationUuid, credentialsFile, claudeJsonFile, recordFile, replaced: existingIsOwn };
75632
+ };
75030
75633
  }
75031
75634
 
75032
75635
  // src/frontdoor/rcStream.ts
@@ -75649,7 +76252,6 @@ function appendLog(paths, line) {
75649
76252
  import_node_fs13.default.appendFileSync(paths.frontdoorLogPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
75650
76253
  `);
75651
76254
  }
75652
- var MINTED_TOKEN_RANDOM_BYTES = 32;
75653
76255
  function rcObservingResolver(resolve, tracker, hub) {
75654
76256
  return async (request7) => {
75655
76257
  const resolution = await resolve(request7);
@@ -75681,8 +76283,8 @@ function realFrontDoorSupervisorPorts(paths) {
75681
76283
  };
75682
76284
  const rcSelfHostSurface = rcSelfHost ? createRcSelfHostSurface({
75683
76285
  now: () => Date.now(),
75684
- newUuid: import_node_crypto14.randomUUID,
75685
- randomToken: () => (0, import_node_crypto14.randomBytes)(MINTED_TOKEN_RANDOM_BYTES).toString("base64url"),
76286
+ newUuid: import_node_crypto15.randomUUID,
76287
+ randomToken: () => (0, import_node_crypto15.randomBytes)(MINTED_TOKEN_RANDOM_BYTES).toString("base64url"),
75686
76288
  credentialRecord: () => readRcSelfHostRecord(realFarmFs, paths.frontdoorDir),
75687
76289
  webFetch: realRcWebFetch({ allowPrivate: rcWebFetchAllowsPrivateFromEnv(process.env) }),
75688
76290
  log
@@ -75701,7 +76303,7 @@ function realFrontDoorSupervisorPorts(paths) {
75701
76303
  saveSequenceNum: rcCredentialStore.writeCursor,
75702
76304
  dial: rcSelfHost ? lateRcStreamDial(rcSelfHostDialTarget) : realRcStreamDial(),
75703
76305
  fanout: rcFanout,
75704
- newClientId: import_node_crypto14.randomUUID,
76306
+ newClientId: import_node_crypto15.randomUUID,
75705
76307
  backoffMs: RC_STREAM_BACKOFF_MS,
75706
76308
  sleep: async (ms) => {
75707
76309
  await new Promise((resolve) => {
@@ -75712,7 +76314,7 @@ function realFrontDoorSupervisorPorts(paths) {
75712
76314
  appendLog(paths, `frontdoor ${String(process.pid)}: ${line}`);
75713
76315
  }
75714
76316
  });
75715
- const rcControlToken = (0, import_node_crypto14.randomUUID)();
76317
+ const rcControlToken = (0, import_node_crypto15.randomUUID)();
75716
76318
  const resolveRoute = rcObservingResolver(
75717
76319
  createProviderRouteResolver({
75718
76320
  fs: realFsPort,
@@ -75752,8 +76354,8 @@ function realFrontDoorSupervisorPorts(paths) {
75752
76354
  now: () => Date.now(),
75753
76355
  headroomSocket: () => headroomSocketTarget(realFarmFs, realHeadroomSocketTrust, paths),
75754
76356
  // The per-generation capability the direct listener demands: held only in this process's memory, so a loopback process that discovers the direct port still cannot use it.
75755
- hopSecret: (0, import_node_crypto14.randomUUID)(),
75756
- custody: createCredentialCustody(() => (0, import_node_crypto14.randomUUID)()),
76357
+ hopSecret: (0, import_node_crypto15.randomUUID)(),
76358
+ custody: createCredentialCustody(() => (0, import_node_crypto15.randomUUID)()),
75757
76359
  log
75758
76360
  });
75759
76361
  const onListenerError = (name) => (error62) => {
@@ -75774,12 +76376,22 @@ function realFrontDoorSupervisorPorts(paths) {
75774
76376
  startProviderListener: async (preferredPort) => {
75775
76377
  const authority = loadCa();
75776
76378
  const rcDial = rcSelfHost ? lateRcEventDial(rcSelfHostDialTarget) : realRcEventDial();
75777
- const rcInject = async (sessionId, text2) => await injectRcUserMessage({ credentialOf: rcTracker.credentialOf, noteSequenceNums: rcTracker.noteSequenceNums, dial: rcDial, newUuid: import_node_crypto14.randomUUID }, sessionId, text2);
76379
+ const rcInject = async (sessionId, text2) => await injectRcUserMessage({ credentialOf: rcTracker.credentialOf, noteSequenceNums: rcTracker.noteSequenceNums, dial: rcDial, newUuid: import_node_crypto15.randomUUID }, sessionId, text2);
75778
76380
  const rcAnswer = async (sessionId, requestId, decision) => await answerRcControlRequest({ credentialOf: rcTracker.credentialOf, pendingOf: (id) => rcTracker.pendingOf(id), completePending: rcTracker.completePending, noteSequenceNums: rcTracker.noteSequenceNums, dial: rcDial }, sessionId, requestId, decision);
75779
- const rcControlRequestDeps = { credentialOf: rcTracker.credentialOf, noteSequenceNums: rcTracker.noteSequenceNums, dial: rcDial, newUuid: import_node_crypto14.randomUUID };
76381
+ const rcControlRequestDeps = { credentialOf: rcTracker.credentialOf, noteSequenceNums: rcTracker.noteSequenceNums, dial: rcDial, newUuid: import_node_crypto15.randomUUID };
75780
76382
  const rcInterrupt = async (sessionId) => await interruptRcSession(rcControlRequestDeps, sessionId);
75781
76383
  const rcSetModel = async (sessionId, model) => await setRcSessionModel(rcControlRequestDeps, sessionId, model);
75782
76384
  const rcSetPermissionMode = async (sessionId, mode) => await setRcSessionPermissionMode(rcControlRequestDeps, sessionId, mode);
76385
+ const rcEndSession = async (sessionId, reason) => await endRcSession(rcControlRequestDeps, sessionId, reason);
76386
+ const rcGetUsage = async (sessionId, skipBehaviors) => await getRcSessionUsage(rcControlRequestDeps, sessionId, skipBehaviors);
76387
+ const rcGetContextUsage = async (sessionId, detail) => await getRcSessionContextUsage(rcControlRequestDeps, sessionId, detail);
76388
+ const rcReadFile = async (sessionId, filePath, options) => await readRcSessionFile(rcControlRequestDeps, sessionId, filePath, options);
76389
+ const rcFileSuggestions = async (sessionId, query) => await suggestRcSessionFiles(rcControlRequestDeps, sessionId, query);
76390
+ const rcKeepAlive = async (sessionId) => await sendRcKeepAlive(rcControlRequestDeps, sessionId);
76391
+ const rcMcpStatus = async (sessionId) => await getRcSessionMcpStatus(rcControlRequestDeps, sessionId);
76392
+ const rcMcpReconnect = async (sessionId, serverName) => await reconnectRcSessionMcpServer(rcControlRequestDeps, sessionId, serverName);
76393
+ const rcMcpAuthenticate = async (sessionId, serverName, redirectUri) => await authenticateRcSessionMcpServer(rcControlRequestDeps, sessionId, serverName, redirectUri);
76394
+ const rcMcpOAuthCallbackUrl = async (sessionId, serverName, callbackUrl) => await submitRcSessionMcpOAuthCallbackUrl(rcControlRequestDeps, sessionId, serverName, callbackUrl);
75783
76395
  const controlDeps = {
75784
76396
  usageSnapshots: () => listUsageSnapshots(realFarmFs, paths.usageSnapshotsDir),
75785
76397
  usageSnapshotOf: (identity) => readUsageSnapshot(realFarmFs, paths.usageSnapshotsDir, identity),
@@ -75797,7 +76409,7 @@ function realFrontDoorSupervisorPorts(paths) {
75797
76409
  },
75798
76410
  log,
75799
76411
  mintLeaf(authority, LOOPBACK_LEAF_NAMES, /* @__PURE__ */ new Date()),
75800
- createRcControlHandler({ expectedToken: rcControlToken, list: rcTracker.list, statusOf: rcTracker.statusOf, pendingOf: rcTracker.pendingOf, inject: rcInject, answer: rcAnswer, interrupt: rcInterrupt, setModel: rcSetModel, setPermissionMode: rcSetPermissionMode }),
76412
+ createRcControlHandler({ expectedToken: rcControlToken, list: rcTracker.list, statusOf: rcTracker.statusOf, pendingOf: rcTracker.pendingOf, inject: rcInject, answer: rcAnswer, interrupt: rcInterrupt, setModel: rcSetModel, setPermissionMode: rcSetPermissionMode, endSession: rcEndSession, getUsage: rcGetUsage, getContextUsage: rcGetContextUsage, readFile: rcReadFile, fileSuggestions: rcFileSuggestions, keepAlive: rcKeepAlive, mcpStatus: rcMcpStatus, mcpReconnect: rcMcpReconnect, mcpAuthenticate: rcMcpAuthenticate, mcpOAuthCallbackUrl: rcMcpOAuthCallbackUrl }),
75801
76413
  [
75802
76414
  createDoorApiNodeHandler({
75803
76415
  expectedToken: rcControlToken,
@@ -75809,6 +76421,16 @@ function realFrontDoorSupervisorPorts(paths) {
75809
76421
  interrupt: rcInterrupt,
75810
76422
  setModel: rcSetModel,
75811
76423
  setPermissionMode: rcSetPermissionMode,
76424
+ endSession: rcEndSession,
76425
+ getUsage: rcGetUsage,
76426
+ getContextUsage: rcGetContextUsage,
76427
+ readFile: rcReadFile,
76428
+ fileSuggestions: rcFileSuggestions,
76429
+ keepAlive: rcKeepAlive,
76430
+ mcpStatus: rcMcpStatus,
76431
+ mcpReconnect: rcMcpReconnect,
76432
+ mcpAuthenticate: rcMcpAuthenticate,
76433
+ mcpOAuthCallbackUrl: rcMcpOAuthCallbackUrl,
75812
76434
  fanout: rcFanout,
75813
76435
  ...controlDeps,
75814
76436
  events: doorEvents
@@ -75828,7 +76450,7 @@ function realFrontDoorSupervisorPorts(paths) {
75828
76450
  },
75829
76451
  startConnectListener: async (preferredPort) => {
75830
76452
  const wantedTransparentPort = process.env.AGENT_SHIM_TRANSPARENT_SURFACE === void 0 ? void 0 : Number(process.env.AGENT_SHIM_TRANSPARENT_SURFACE);
75831
- const transparentCapability = wantedTransparentPort === void 0 || Number.isNaN(wantedTransparentPort) ? void 0 : (0, import_node_crypto14.randomUUID)();
76453
+ const transparentCapability = wantedTransparentPort === void 0 || Number.isNaN(wantedTransparentPort) ? void 0 : (0, import_node_crypto15.randomUUID)();
75832
76454
  if (transparentCapability !== void 0) {
75833
76455
  writeFrontDoorSession(realFarmFs, paths.frontdoorSessionsDir, { pid: process.pid, startedAt: Date.now(), token: transparentCapability });
75834
76456
  }
@@ -75885,110 +76507,28 @@ function realFrontDoorSupervisorPorts(paths) {
75885
76507
  rcSelfHostSurface?.close();
75886
76508
  await server.close();
75887
76509
  }
75888
- };
75889
- },
75890
- startDirectListener: async (preferredPort) => {
75891
- const server = createFrontDoorServer(async (request7) => {
75892
- await serveRouted(request7, pipelines.direct);
75893
- }, log);
75894
- const handle = await listenFrontDoor(server, {
75895
- ...preferredPort === void 0 ? {} : { preferredPort },
75896
- onBound: (port) => {
75897
- directPort = port;
75898
- },
75899
- onError: onListenerError("direct")
75900
- });
75901
- return { port: handle.port, close: handle.close };
75902
- },
75903
- closeRcStreamHub: rcHub.close,
75904
- // The launch lifecycle publisher over the same backbone the typed API serves: the tick's registry facts go in here, and the door's own register, prune and end moments come out as source-tagged events.
75905
- observeLaunchRegistry: createLaunchEventPublisher(doorEvents, () => Date.now()).observe,
75906
- log: (line) => {
75907
- appendLog(paths, line);
75908
- }
75909
- };
75910
- }
75911
- function formatRcSessionList(sessions) {
75912
- if (sessions.length === 0) {
75913
- return ["no Remote Control sessions observed"];
75914
- }
75915
- return sessions.map((session) => `${session.id} created ${new Date(session.createdAt).toISOString()} last seen ${new Date(session.lastSeenAt).toISOString()}`);
75916
- }
75917
- function formatRcSessionStatus(statuses) {
75918
- if (statuses.length === 0) {
75919
- return ["no Remote Control sessions observed"];
75920
- }
75921
- const lines = [];
75922
- for (const status of statuses) {
75923
- lines.push(`${status.id} created ${new Date(status.createdAt).toISOString()} last seen ${new Date(status.lastSeenAt).toISOString()}`);
75924
- const worker = status.workerState === void 0 && status.workerIdleSeconds === void 0 ? "not observed" : [
75925
- status.workerState === void 0 ? "state unknown" : `state ${status.workerState.value} since ${new Date(status.workerState.observedAt).toISOString()}`,
75926
- status.workerIdleSeconds === void 0 ? "idle unknown" : `idle ${String(status.workerIdleSeconds.value)} s as of ${new Date(status.workerIdleSeconds.observedAt).toISOString()}`
75927
- ].join(", ");
75928
- lines.push(` worker: ${worker}`);
75929
- if (status.pending.length === 0) {
75930
- lines.push(" pending: none");
75931
- } else {
75932
- lines.push(" pending:");
75933
- for (const pending of status.pending) {
75934
- lines.push(...formatRcPendingList([pending]).map((line) => ` ${line}`));
75935
- }
76510
+ };
76511
+ },
76512
+ startDirectListener: async (preferredPort) => {
76513
+ const server = createFrontDoorServer(async (request7) => {
76514
+ await serveRouted(request7, pipelines.direct);
76515
+ }, log);
76516
+ const handle = await listenFrontDoor(server, {
76517
+ ...preferredPort === void 0 ? {} : { preferredPort },
76518
+ onBound: (port) => {
76519
+ directPort = port;
76520
+ },
76521
+ onError: onListenerError("direct")
76522
+ });
76523
+ return { port: handle.port, close: handle.close };
76524
+ },
76525
+ closeRcStreamHub: rcHub.close,
76526
+ // The launch lifecycle publisher over the same backbone the typed API serves: the tick's registry facts go in here, and the door's own register, prune and end moments come out as source-tagged events.
76527
+ observeLaunchRegistry: createLaunchEventPublisher(doorEvents, () => Date.now()).observe,
76528
+ log: (line) => {
76529
+ appendLog(paths, line);
75936
76530
  }
75937
- }
75938
- return lines;
75939
- }
75940
- function formatRcPendingList(pending) {
75941
- if (pending.length === 0) {
75942
- return ["no pending control requests observed"];
75943
- }
75944
- return pending.map((request7) => `${request7.sessionId} ${request7.requestId} ${request7.type}${request7.summary === "" ? "" : ` ${request7.summary}`} observed ${new Date(request7.observedAt).toISOString()}`);
75945
- }
75946
- function eventExcerpt(text2) {
75947
- return text2.length <= RC_PENDING_SUMMARY_EXCERPT_CHARS ? text2 : `${text2.slice(0, RC_PENDING_SUMMARY_EXCERPT_CHARS)}...`;
75948
- }
75949
- function formatRcStreamEvent(event) {
75950
- const { envelope } = event;
75951
- const sketch = envelope.payload === void 0 ? "" : ` ${eventExcerpt(JSON.stringify(envelope.payload))}`;
75952
- return `${event.session} ${envelope.event_type} sequence_num ${String(envelope.sequence_num)} source ${envelope.source}${sketch}`;
75953
- }
75954
- function formatRcSelfHostMint(result) {
75955
- return [
75956
- `identity: ${result.identity} (organisation ${result.organizationUuid})`,
75957
- `credential: ${result.credentialsFile}${result.replaced ? " (replacing this door's previous mint)" : ""}`,
75958
- `account block: ${result.claudeJsonFile} (oauthAccount and the feature-cache seed merged in)`,
75959
- `door record: ${result.recordFile} (the copy the door's served surface authenticates against)`,
75960
- "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"
75961
- ];
75962
- }
75963
- function frontDoorRcControlFromState(fsPort, paths) {
75964
- const state = readFrontDoorState(fsPort, paths.frontdoorStateFile);
75965
- if (state?.port === void 0) {
75966
- 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");
75967
- }
75968
- const ca = fsPort.readFileUtf8(paths.frontdoorCaCertFile);
75969
- if (ca === void 0) {
75970
- throw new Error(`the front door's CA certificate is missing at ${paths.frontdoorCaCertFile}, so its control listener cannot be authenticated`);
75971
- }
75972
- const token = fsPort.readFileUtf8(paths.frontdoorControlTokenFile)?.trim();
75973
- if (token === void 0 || token === "") {
75974
- throw new Error(`the serving front door's control token is missing at ${paths.frontdoorControlTokenFile}`);
75975
- }
75976
- return frontDoorRcControl(realRcControlTransport(state.port, ca), token);
75977
- }
75978
- function frontDoorRcApiFromState(fsPort, paths) {
75979
- const state = readFrontDoorState(fsPort, paths.frontdoorStateFile);
75980
- if (state?.port === void 0) {
75981
- 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");
75982
- }
75983
- const ca = fsPort.readFileUtf8(paths.frontdoorCaCertFile);
75984
- if (ca === void 0) {
75985
- throw new Error(`the front door's CA certificate is missing at ${paths.frontdoorCaCertFile}, so its typed API cannot be authenticated`);
75986
- }
75987
- const token = fsPort.readFileUtf8(paths.frontdoorControlTokenFile)?.trim();
75988
- if (token === void 0 || token === "") {
75989
- throw new Error(`the serving front door's control token is missing at ${paths.frontdoorControlTokenFile}`);
75990
- }
75991
- return frontDoorRcApiClient(state.port, ca, token);
76531
+ };
75992
76532
  }
75993
76533
  function registerFrontDoorCommand(program2, deps) {
75994
76534
  const { paths } = deps;
@@ -76008,186 +76548,7 @@ function registerFrontDoorCommand(program2, deps) {
76008
76548
  }),
76009
76549
  ["agent-shim frontdoor status", "agent-shim frontdoor status --json"]
76010
76550
  );
76011
- const rc = withExamples(
76012
- 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)."),
76013
- ["agent-shim frontdoor rc list", "agent-shim frontdoor rc pending"]
76014
- );
76015
- withExamples(
76016
- 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) => {
76017
- const sessions = await frontDoorRcControlFromState(realFarmFs, paths).listSessions();
76018
- if (options.json === true) {
76019
- printJson({ sessions });
76020
- return;
76021
- }
76022
- for (const line of formatRcSessionList(sessions)) {
76023
- console.log(line);
76024
- }
76025
- }),
76026
- ["agent-shim frontdoor rc list", "agent-shim frontdoor rc list --json"]
76027
- );
76028
- withExamples(
76029
- 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) => {
76030
- const statuses = await frontDoorRcControlFromState(realFarmFs, paths).statusOf(session);
76031
- if (options.json === true) {
76032
- printJson({ statuses });
76033
- return;
76034
- }
76035
- for (const line of formatRcSessionStatus(statuses)) {
76036
- console.log(line);
76037
- }
76038
- }),
76039
- ["agent-shim frontdoor rc status", "agent-shim frontdoor rc status cse_00000000-0000-4000-8000-000000000000 --json"]
76040
- );
76041
- withExamples(
76042
- 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) => {
76043
- const pending = await frontDoorRcControlFromState(realFarmFs, paths).pendingOf(session);
76044
- if (options.json === true) {
76045
- printJson({ pending });
76046
- return;
76047
- }
76048
- for (const line of formatRcPendingList(pending)) {
76049
- console.log(line);
76050
- }
76051
- }),
76052
- ["agent-shim frontdoor rc pending", "agent-shim frontdoor rc pending --json"]
76053
- );
76054
- withExamples(
76055
- 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) => {
76056
- const result = await frontDoorRcControlFromState(realFarmFs, paths).sendPrompt(options.session, options.text);
76057
- if (!result.ok) {
76058
- throw new Error(result.message);
76059
- }
76060
- if (options.json === true) {
76061
- printJson({ session: options.session, sequenceNums: result.sequenceNums });
76062
- return;
76063
- }
76064
- console.log(`sent to ${options.session}: sequence_num ${result.sequenceNums.join(", ")}`);
76065
- }),
76066
- ['agent-shim frontdoor rc send --session cse_00000000-0000-4000-8000-000000000000 --text "run the tests"']
76067
- );
76068
- withExamples(
76069
- 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) => {
76070
- if (options.approve === options.deny) {
76071
- throw new UsageError("answer exactly one way: pass --approve or --deny, not both and not neither");
76072
- }
76073
- if (options.approve === true && options.text !== void 0) {
76074
- throw new UsageError("--text is the denial message; an approval carries no text (the protocol's allow result has no message field)");
76075
- }
76076
- const result = await frontDoorRcControlFromState(realFarmFs, paths).answerRequest(options.session, options.request, { approve: options.approve === true, message: options.deny === true ? options.text : void 0 });
76077
- if (!result.ok) {
76078
- throw new Error(result.message);
76079
- }
76080
- if (options.json === true) {
76081
- printJson({ session: options.session, request: options.request, sequenceNums: result.sequenceNums });
76082
- return;
76083
- }
76084
- console.log(`${options.approve === true ? "approved" : "denied"} ${options.request} on ${options.session}: sequence_num ${result.sequenceNums.join(", ")}`);
76085
- }),
76086
- [
76087
- "agent-shim frontdoor rc answer --session cse_00000000-0000-4000-8000-000000000000 --request req_00000000-0000-4000-8000-000000000000 --approve",
76088
- 'agent-shim frontdoor rc answer --session cse_00000000-0000-4000-8000-000000000000 --request req_00000000-0000-4000-8000-000000000000 --deny --text "not today"'
76089
- ]
76090
- );
76091
- withExamples(
76092
- 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) => {
76093
- const result = await frontDoorRcControlFromState(realFarmFs, paths).interruptSession(options.session);
76094
- if (!result.ok) {
76095
- throw new Error(result.message);
76096
- }
76097
- if (options.json === true) {
76098
- printJson({ session: options.session, sequenceNums: result.sequenceNums });
76099
- return;
76100
- }
76101
- console.log(`interrupted ${options.session}: sequence_num ${result.sequenceNums.join(", ")}`);
76102
- }),
76103
- ["agent-shim frontdoor rc interrupt --session cse_00000000-0000-4000-8000-000000000000"]
76104
- );
76105
- withExamples(
76106
- 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) => {
76107
- const result = await frontDoorRcControlFromState(realFarmFs, paths).setModel(options.session, options.model);
76108
- if (!result.ok) {
76109
- throw new Error(result.message);
76110
- }
76111
- if (options.json === true) {
76112
- printJson({ session: options.session, model: options.model, sequenceNums: result.sequenceNums });
76113
- return;
76114
- }
76115
- console.log(`set ${options.session} to model ${options.model}: sequence_num ${result.sequenceNums.join(", ")}`);
76116
- }),
76117
- ["agent-shim frontdoor rc set-model --session cse_00000000-0000-4000-8000-000000000000 --model claude-opus-5-5"]
76118
- );
76119
- withExamples(
76120
- 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) => {
76121
- if (!isRcPermissionMode(options.mode)) {
76122
- throw new UsageError(`--mode must be one of the SDK's own permission modes (${RC_PERMISSION_MODES.join(", ")}), not "${options.mode}"`);
76123
- }
76124
- const result = await frontDoorRcControlFromState(realFarmFs, paths).setPermissionMode(options.session, options.mode);
76125
- if (!result.ok) {
76126
- throw new Error(result.message);
76127
- }
76128
- if (options.json === true) {
76129
- printJson({ session: options.session, mode: options.mode, sequenceNums: result.sequenceNums });
76130
- return;
76131
- }
76132
- console.log(`set ${options.session} to permission mode ${options.mode}: sequence_num ${result.sequenceNums.join(", ")}`);
76133
- }),
76134
- [
76135
- "agent-shim frontdoor rc set-permission-mode --session cse_00000000-0000-4000-8000-000000000000 --mode plan",
76136
- "agent-shim frontdoor rc set-permission-mode --session cse_00000000-0000-4000-8000-000000000000 --mode acceptEdits"
76137
- ]
76138
- );
76139
- withExamples(
76140
- rc.command("watch").description("Print the observed sessions' client read stream events as they arrive, the approvals the door's held stream receives above all, until Ctrl-C leaves. Read-only.").argument("[session]", "One cse_ session id, as `frontdoor rc list` shows it; every observed session when omitted.").option("--json", "Print each event as one JSON line as it arrives.").action(async (session, options) => {
76141
- const client = frontDoorRcApiFromState(realFarmFs, paths);
76142
- const leave = new AbortController();
76143
- const onInterrupt = () => {
76144
- leave.abort();
76145
- };
76146
- process.once("SIGINT", onInterrupt);
76147
- try {
76148
- for await (const event of await client.rc.subscribe(session === void 0 ? {} : { session }, { signal: leave.signal })) {
76149
- if (options.json === true) {
76150
- console.log(JSON.stringify(event));
76151
- continue;
76152
- }
76153
- console.log(formatRcStreamEvent(event));
76154
- }
76155
- } catch (error62) {
76156
- if (!leave.signal.aborted) {
76157
- throw error62;
76158
- }
76159
- } finally {
76160
- process.off("SIGINT", onInterrupt);
76161
- }
76162
- }),
76163
- ["agent-shim frontdoor rc watch", "agent-shim frontdoor rc watch cse_00000000-0000-4000-8000-000000000000 --json"]
76164
- );
76165
- const selfhost = withExamples(
76166
- 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."),
76167
- ["agent-shim frontdoor rc selfhost mint rig"]
76168
- );
76169
- withExamples(
76170
- 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) => {
76171
- const result = mintRcSelfHostCredential({
76172
- fs: realFarmFs,
76173
- identitiesDir: paths.identitiesDir,
76174
- frontdoorDir: paths.frontdoorDir,
76175
- identity,
76176
- newUuid: import_node_crypto14.randomUUID,
76177
- randomToken: () => (0, import_node_crypto14.randomBytes)(MINTED_TOKEN_RANDOM_BYTES).toString("base64url"),
76178
- force: options.force === true,
76179
- now: () => Date.now()
76180
- });
76181
- if (options.json === true) {
76182
- printJson(result);
76183
- return;
76184
- }
76185
- for (const line of formatRcSelfHostMint(result)) {
76186
- console.log(line);
76187
- }
76188
- }),
76189
- ["agent-shim frontdoor rc selfhost mint rig", "agent-shim frontdoor rc selfhost mint rig --force"]
76190
- );
76551
+ registerRcCommand(frontdoor, paths);
76191
76552
  program2.command("__frontdoor-supervisor", { hidden: true }).description("Internal: serve the front-door routing listeners. Started by the launcher; never run by hand.").allowUnknownOption().action(async () => {
76192
76553
  const idleShutdownMinutes = readGlobalConfig(paths)?.frontdoor?.idleShutdownMinutes ?? FRONTDOOR_DEFAULT_IDLE_SHUTDOWN_MINUTES;
76193
76554
  process.on("SIGTERM", () => {
@@ -76679,7 +77040,7 @@ var import_node_child_process6 = require("node:child_process");
76679
77040
  var import_node_fs15 = __toESM(require("node:fs"), 1);
76680
77041
 
76681
77042
  // src/frontdoor/ensure.ts
76682
- var import_node_crypto15 = require("node:crypto");
77043
+ var import_node_crypto16 = require("node:crypto");
76683
77044
  var FRONTDOOR_START_TIMEOUT_MS = 3e4;
76684
77045
  var FRONTDOOR_LAUNCHER_POLL_MS = 100;
76685
77046
  var FrontDoorStartError = class extends CliError {
@@ -76713,7 +77074,7 @@ function ensureFrontDoor(params) {
76713
77074
  continue;
76714
77075
  }
76715
77076
  if (verdict.ok) {
76716
- const token = (0, import_node_crypto15.randomUUID)();
77077
+ const token = (0, import_node_crypto16.randomUUID)();
76717
77078
  writeFrontDoorSession(ports.fs, paths.frontdoorSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), token });
76718
77079
  return { port: state.port, connectPort: state.connectPort, token };
76719
77080
  }
@@ -76817,7 +77178,7 @@ function probeFrontDoorSync(port, caPem) {
76817
77178
  }
76818
77179
 
76819
77180
  // src/frontdoor/trust.ts
76820
- var import_node_crypto16 = require("node:crypto");
77181
+ var import_node_crypto17 = require("node:crypto");
76821
77182
  var import_node_path38 = __toESM(require("node:path"), 1);
76822
77183
  var BUNDLE_NAME_HEX_DIGITS = 16;
76823
77184
  function resolveTrustBundle(params) {
@@ -76840,7 +77201,7 @@ function resolveTrustBundle(params) {
76840
77201
  }
76841
77202
  const combined = `${inheritedPem.trimEnd()}
76842
77203
  ${caPem}`;
76843
- const bundle = import_node_path38.default.join(bundlesDir, `${(0, import_node_crypto16.createHash)("sha256").update(combined).digest("hex").slice(0, BUNDLE_NAME_HEX_DIGITS)}.pem`);
77204
+ const bundle = import_node_path38.default.join(bundlesDir, `${(0, import_node_crypto17.createHash)("sha256").update(combined).digest("hex").slice(0, BUNDLE_NAME_HEX_DIGITS)}.pem`);
76844
77205
  if (!fs20.exists(bundle)) {
76845
77206
  fs20.mkdirp(bundlesDir);
76846
77207
  fs20.writeFileAtomic(bundle, combined);
@@ -77551,7 +77912,7 @@ var import_node_os7 = __toESM(require("node:os"), 1);
77551
77912
  var import_node_path41 = __toESM(require("node:path"), 1);
77552
77913
 
77553
77914
  // src/update/update.ts
77554
- var import_node_crypto17 = require("node:crypto");
77915
+ var import_node_crypto18 = require("node:crypto");
77555
77916
  var import_node_path40 = __toESM(require("node:path"), 1);
77556
77917
  var LATEST_RELEASE_URL = "https://github.com/ExaDev/agent-shim/releases/latest";
77557
77918
  var DOWNLOAD_BASE_URL = "https://github.com/ExaDev/agent-shim/releases/latest/download";
@@ -77706,11 +78067,11 @@ async function downloadAndInstall(ports, asset, target) {
77706
78067
  throw new UpdateDownloadError(`the downloaded files for ${asset} are missing from ${tempDir}`);
77707
78068
  }
77708
78069
  const expected = expectedSha256(sidecar);
77709
- const actual = (0, import_node_crypto17.createHash)("sha256").update(downloaded).digest("hex");
78070
+ const actual = (0, import_node_crypto18.createHash)("sha256").update(downloaded).digest("hex");
77710
78071
  if (expected === void 0 || expected !== actual) {
77711
78072
  throw new UpdateDownloadError(`checksum mismatch for ${asset} (expected ${expected ?? "(none)"}, got ${actual})`);
77712
78073
  }
77713
- const tempBinary = import_node_path40.default.join(import_node_path40.default.dirname(target), `.${import_node_path40.default.basename(target)}.${String(process.pid)}.${(0, import_node_crypto17.randomUUID)()}.tmp`);
78074
+ const tempBinary = import_node_path40.default.join(import_node_path40.default.dirname(target), `.${import_node_path40.default.basename(target)}.${String(process.pid)}.${(0, import_node_crypto18.randomUUID)()}.tmp`);
77714
78075
  ports.fs.writeFileBytes(tempBinary, downloaded);
77715
78076
  try {
77716
78077
  ports.fs.chmod(tempBinary, EXECUTABLE_MODE);
@@ -78219,10 +78580,10 @@ function buildProgram(deps) {
78219
78580
 
78220
78581
  // src/launchWiring.ts
78221
78582
  var import_node_os9 = __toESM(require("node:os"), 1);
78222
- var import_node_crypto19 = require("node:crypto");
78583
+ var import_node_crypto20 = require("node:crypto");
78223
78584
 
78224
78585
  // src/launcher.ts
78225
- var import_node_crypto18 = require("node:crypto");
78586
+ var import_node_crypto19 = require("node:crypto");
78226
78587
  var import_node_path43 = __toESM(require("node:path"), 1);
78227
78588
 
78228
78589
  // src/launcher/cliOverride.ts
@@ -78846,7 +79207,7 @@ function prepareLaunch(params) {
78846
79207
  ...identityCredential === void 0 ? {} : { identityCredential },
78847
79208
  ...frontDoor === void 0 ? {} : { frontdoor: frontDoor },
78848
79209
  ...headroom === void 0 ? {} : { headroom },
78849
- sessionId: (0, import_node_crypto18.randomUUID)()
79210
+ sessionId: (0, import_node_crypto19.randomUUID)()
78850
79211
  });
78851
79212
  const releases = [...headroom === void 0 || headroomPort === void 0 ? [] : [headroomPort.release], ...frontDoorRelease === void 0 ? [] : [frontDoorRelease]];
78852
79213
  const release = () => {
@@ -78893,7 +79254,7 @@ function buildFarmRuntime(paths, cwd) {
78893
79254
  ...cliOverride === void 0 ? {} : { cliOverride }
78894
79255
  }).input,
78895
79256
  now: () => Date.now(),
78896
- uniqueSuffix: `${String(process.pid)}.${(0, import_node_crypto19.randomUUID)()}`,
79257
+ uniqueSuffix: `${String(process.pid)}.${(0, import_node_crypto20.randomUUID)()}`,
78897
79258
  // Zombie-aware on purpose: a previous launcher that crashed out of a resync without releasing the lock may sit unreaped, still answering signal 0 as alive, and must read as a dead holder so this launch takes the lock over instead of timing out.
78898
79259
  lock: { pid: process.pid, isRunning: realIsProcessRunning, sleep: realSleepSync }
78899
79260
  },