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/index.mjs CHANGED
@@ -3630,6 +3630,8 @@ function rcObserverAsPassthrough(observer) {
3630
3630
  }
3631
3631
  };
3632
3632
  }
3633
+
3634
+ // src/frontdoor/rcWrites.ts
3633
3635
  function buildRcUserMessagePayload(uuid, sessionId, text) {
3634
3636
  return {
3635
3637
  type: "user",
@@ -3730,14 +3732,55 @@ var RC_PERMISSION_MODES = ["default", "acceptEdits", "bypassPermissions", "plan"
3730
3732
  function isRcPermissionMode(value) {
3731
3733
  return RC_PERMISSION_MODES.some((mode) => mode === value);
3732
3734
  }
3735
+ function buildRcControlRequestPayload(requestId, request4) {
3736
+ return { type: "control_request", request_id: requestId, request: request4 };
3737
+ }
3733
3738
  function buildRcInterruptPayload(requestId) {
3734
- return { type: "control_request", request_id: requestId, request: { subtype: "interrupt" } };
3739
+ return buildRcControlRequestPayload(requestId, { subtype: "interrupt" });
3735
3740
  }
3736
3741
  function buildRcSetModelPayload(requestId, model) {
3737
- return { type: "control_request", request_id: requestId, request: { subtype: "set_model", model } };
3742
+ return buildRcControlRequestPayload(requestId, { subtype: "set_model", model });
3738
3743
  }
3739
3744
  function buildRcSetPermissionModePayload(requestId, mode) {
3740
- return { type: "control_request", request_id: requestId, request: { subtype: "set_permission_mode", mode } };
3745
+ return buildRcControlRequestPayload(requestId, { subtype: "set_permission_mode", mode });
3746
+ }
3747
+ function buildRcEndSessionPayload(requestId, reason) {
3748
+ return buildRcControlRequestPayload(requestId, { subtype: "end_session", ...reason === void 0 ? {} : { reason } });
3749
+ }
3750
+ function buildRcGetUsagePayload(requestId, skipBehaviors) {
3751
+ return buildRcControlRequestPayload(requestId, { subtype: "get_usage", ...skipBehaviors === true ? { skip_behaviors: true } : {} });
3752
+ }
3753
+ var RC_CONTEXT_USAGE_DETAILS = ["summary", "full"];
3754
+ function isRcContextUsageDetail(value) {
3755
+ return RC_CONTEXT_USAGE_DETAILS.some((detail) => detail === value);
3756
+ }
3757
+ function buildRcGetContextUsagePayload(requestId, detail) {
3758
+ return buildRcControlRequestPayload(requestId, { subtype: "get_context_usage", ...detail === void 0 ? {} : { detail } });
3759
+ }
3760
+ var RC_READ_FILE_ENCODINGS = ["utf-8", "base64"];
3761
+ function isRcReadFileEncoding(value) {
3762
+ return RC_READ_FILE_ENCODINGS.some((encoding) => encoding === value);
3763
+ }
3764
+ function buildRcReadFilePayload(requestId, path32, options) {
3765
+ return buildRcControlRequestPayload(requestId, { subtype: "read_file", path: path32, ...options?.maxBytes === void 0 ? {} : { max_bytes: options.maxBytes }, ...options?.encoding === void 0 ? {} : { encoding: options.encoding } });
3766
+ }
3767
+ function buildRcFileSuggestionsPayload(requestId, query) {
3768
+ return buildRcControlRequestPayload(requestId, { subtype: "file_suggestions", query });
3769
+ }
3770
+ function buildRcMcpStatusPayload(requestId) {
3771
+ return buildRcControlRequestPayload(requestId, { subtype: "mcp_status" });
3772
+ }
3773
+ function buildRcMcpReconnectPayload(requestId, serverName) {
3774
+ return buildRcControlRequestPayload(requestId, { subtype: "mcp_reconnect", serverName });
3775
+ }
3776
+ function buildRcMcpAuthenticatePayload(requestId, serverName, redirectUri) {
3777
+ return buildRcControlRequestPayload(requestId, { subtype: "mcp_authenticate", serverName, redirectUri });
3778
+ }
3779
+ function buildRcMcpOAuthCallbackUrlPayload(requestId, serverName, callbackUrl) {
3780
+ return buildRcControlRequestPayload(requestId, { subtype: "mcp_oauth_callback_url", serverName, callbackUrl });
3781
+ }
3782
+ function buildRcKeepAlivePayload() {
3783
+ return { type: "keep_alive" };
3741
3784
  }
3742
3785
  async function answerRcControlRequest(deps, sessionId, requestId, decision) {
3743
3786
  const credential = deps.credentialOf(sessionId);
@@ -3764,7 +3807,7 @@ async function answerRcControlRequest(deps, sessionId, requestId, decision) {
3764
3807
  }
3765
3808
  return result;
3766
3809
  }
3767
- async function interruptRcSession(deps, sessionId) {
3810
+ async function deliverRcClientPayload(deps, sessionId, payload, action) {
3768
3811
  const credential = deps.credentialOf(sessionId);
3769
3812
  if (credential === void 0) {
3770
3813
  return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
@@ -3772,12 +3815,12 @@ async function interruptRcSession(deps, sessionId) {
3772
3815
  if (credential.authorization === void 0) {
3773
3816
  return noOAuthCredentialResult(sessionId);
3774
3817
  }
3775
- const body = JSON.stringify(buildRcEventWriteBody(buildRcInterruptPayload(deps.newUuid())));
3818
+ const body = JSON.stringify(buildRcEventWriteBody(payload));
3776
3819
  let answer;
3777
3820
  try {
3778
3821
  answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
3779
3822
  } catch (error) {
3780
- return { ok: false, message: `the door could not reach the API host to interrupt session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
3823
+ return { ok: false, message: `the door could not reach the API host to ${action}: ${error instanceof Error ? error.message : String(error)}` };
3781
3824
  }
3782
3825
  const result = rcEventWriteResultFromAnswer(answer);
3783
3826
  if (result.ok) {
@@ -3785,50 +3828,89 @@ async function interruptRcSession(deps, sessionId) {
3785
3828
  }
3786
3829
  return result;
3787
3830
  }
3831
+ async function interruptRcSession(deps, sessionId) {
3832
+ const requestId = deps.newUuid();
3833
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcInterruptPayload(requestId), `interrupt session ${sessionId}`);
3834
+ return result.ok ? { ...result, requestId } : result;
3835
+ }
3788
3836
  async function setRcSessionModel(deps, sessionId, model) {
3789
3837
  if (model === "") {
3790
3838
  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" };
3791
3839
  }
3792
- const credential = deps.credentialOf(sessionId);
3793
- if (credential === void 0) {
3794
- return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
3795
- }
3796
- if (credential.authorization === void 0) {
3797
- return noOAuthCredentialResult(sessionId);
3840
+ const requestId = deps.newUuid();
3841
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcSetModelPayload(requestId, model), `set the model on session ${sessionId}`);
3842
+ return result.ok ? { ...result, requestId } : result;
3843
+ }
3844
+ async function setRcSessionPermissionMode(deps, sessionId, mode) {
3845
+ const requestId = deps.newUuid();
3846
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcSetPermissionModePayload(requestId, mode), `set the permission mode on session ${sessionId}`);
3847
+ return result.ok ? { ...result, requestId } : result;
3848
+ }
3849
+ async function endRcSession(deps, sessionId, reason) {
3850
+ if (reason === "") {
3851
+ 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" };
3798
3852
  }
3799
- const body = JSON.stringify(buildRcEventWriteBody(buildRcSetModelPayload(deps.newUuid(), model)));
3800
- let answer;
3801
- try {
3802
- answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
3803
- } catch (error) {
3804
- return { ok: false, message: `the door could not reach the API host to set the model on session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
3853
+ const requestId = deps.newUuid();
3854
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcEndSessionPayload(requestId, reason), `end session ${sessionId}`);
3855
+ return result.ok ? { ...result, requestId } : result;
3856
+ }
3857
+ async function getRcSessionUsage(deps, sessionId, skipBehaviors) {
3858
+ const requestId = deps.newUuid();
3859
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcGetUsagePayload(requestId, skipBehaviors), `request the usage of session ${sessionId}`);
3860
+ return result.ok ? { ...result, requestId } : result;
3861
+ }
3862
+ async function getRcSessionContextUsage(deps, sessionId, detail) {
3863
+ const requestId = deps.newUuid();
3864
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcGetContextUsagePayload(requestId, detail), `request the context usage of session ${sessionId}`);
3865
+ return result.ok ? { ...result, requestId } : result;
3866
+ }
3867
+ async function readRcSessionFile(deps, sessionId, path32, options) {
3868
+ if (path32 === "") {
3869
+ 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" };
3805
3870
  }
3806
- const result = rcEventWriteResultFromAnswer(answer);
3807
- if (result.ok) {
3808
- deps.noteSequenceNums(sessionId, result.sequenceNums);
3871
+ if (options?.maxBytes !== void 0 && (!Number.isInteger(options.maxBytes) || options.maxBytes <= 0)) {
3872
+ 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" };
3809
3873
  }
3810
- return result;
3874
+ const requestId = deps.newUuid();
3875
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcReadFilePayload(requestId, path32, options), `read ${path32} from session ${sessionId}`);
3876
+ return result.ok ? { ...result, requestId } : result;
3811
3877
  }
3812
- async function setRcSessionPermissionMode(deps, sessionId, mode) {
3813
- const credential = deps.credentialOf(sessionId);
3814
- if (credential === void 0) {
3815
- return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
3816
- }
3817
- if (credential.authorization === void 0) {
3818
- return noOAuthCredentialResult(sessionId);
3878
+ async function suggestRcSessionFiles(deps, sessionId, query) {
3879
+ const requestId = deps.newUuid();
3880
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcFileSuggestionsPayload(requestId, query), `request file suggestions from session ${sessionId}`);
3881
+ return result.ok ? { ...result, requestId } : result;
3882
+ }
3883
+ async function getRcSessionMcpStatus(deps, sessionId) {
3884
+ const requestId = deps.newUuid();
3885
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcMcpStatusPayload(requestId), `request the MCP status of session ${sessionId}`);
3886
+ return result.ok ? { ...result, requestId } : result;
3887
+ }
3888
+ async function reconnectRcSessionMcpServer(deps, sessionId, serverName) {
3889
+ if (serverName === "") {
3890
+ 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" };
3819
3891
  }
3820
- const body = JSON.stringify(buildRcEventWriteBody(buildRcSetPermissionModePayload(deps.newUuid(), mode)));
3821
- let answer;
3822
- try {
3823
- answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
3824
- } catch (error) {
3825
- return { ok: false, message: `the door could not reach the API host to set the permission mode on session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
3892
+ const requestId = deps.newUuid();
3893
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcMcpReconnectPayload(requestId, serverName), `reconnect MCP server ${serverName} on session ${sessionId}`);
3894
+ return result.ok ? { ...result, requestId } : result;
3895
+ }
3896
+ async function authenticateRcSessionMcpServer(deps, sessionId, serverName, redirectUri) {
3897
+ if (serverName === "" || redirectUri === "") {
3898
+ 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" };
3826
3899
  }
3827
- const result = rcEventWriteResultFromAnswer(answer);
3828
- if (result.ok) {
3829
- deps.noteSequenceNums(sessionId, result.sequenceNums);
3900
+ const requestId = deps.newUuid();
3901
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcMcpAuthenticatePayload(requestId, serverName, redirectUri), `authenticate MCP server ${serverName} on session ${sessionId}`);
3902
+ return result.ok ? { ...result, requestId } : result;
3903
+ }
3904
+ async function submitRcSessionMcpOAuthCallbackUrl(deps, sessionId, serverName, callbackUrl) {
3905
+ if (serverName === "" || callbackUrl === "") {
3906
+ 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" };
3830
3907
  }
3831
- return result;
3908
+ const requestId = deps.newUuid();
3909
+ const result = await deliverRcClientPayload(deps, sessionId, buildRcMcpOAuthCallbackUrlPayload(requestId, serverName, callbackUrl), `hand MCP server ${serverName}'s OAuth callback to session ${sessionId}`);
3910
+ return result.ok ? { ...result, requestId } : result;
3911
+ }
3912
+ async function sendRcKeepAlive(deps, sessionId) {
3913
+ return await deliverRcClientPayload(deps, sessionId, buildRcKeepAlivePayload(), `keep session ${sessionId} alive`);
3832
3914
  }
3833
3915
 
3834
3916
  // src/frontdoor/rcControl.ts
@@ -3840,6 +3922,25 @@ function answerJson(response, status, body) {
3840
3922
  response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text, "utf8")) });
3841
3923
  response.end(text);
3842
3924
  }
3925
+ function isJsonObject(value) {
3926
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3927
+ }
3928
+ function bodyString(body, field) {
3929
+ const value = body[field];
3930
+ return typeof value === "string" && value !== "" ? value : void 0;
3931
+ }
3932
+ function bodyStringAllowingEmpty(body, field) {
3933
+ const value = body[field];
3934
+ return typeof value === "string" ? value : void 0;
3935
+ }
3936
+ function bodyBoolean(body, field) {
3937
+ const value = body[field];
3938
+ return typeof value === "boolean" ? value : void 0;
3939
+ }
3940
+ function bodyPositiveInteger(body, field) {
3941
+ const value = body[field];
3942
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
3943
+ }
3843
3944
  async function readBody(request4) {
3844
3945
  return await new Promise((resolve, reject) => {
3845
3946
  const chunks = [];
@@ -3867,9 +3968,127 @@ function createRcControlHandler(deps) {
3867
3968
  const pendingPath = `${CONTROL_PATH_PREFIX}/pending`;
3868
3969
  const injectPath = `${CONTROL_PATH_PREFIX}/inject`;
3869
3970
  const answerPath = `${CONTROL_PATH_PREFIX}/answer`;
3870
- const interruptPath = `${CONTROL_PATH_PREFIX}/interrupt`;
3871
- const setModelPath = `${CONTROL_PATH_PREFIX}/set-model`;
3872
- const setPermissionModePath = `${CONTROL_PATH_PREFIX}/set-permission-mode`;
3971
+ const controlWrites = [
3972
+ {
3973
+ name: "interrupt",
3974
+ path: "interrupt",
3975
+ read: (body) => {
3976
+ const session = bodyString(body, "session");
3977
+ return session === void 0 ? { error: "the interrupt body must be JSON naming a non-empty session id" } : { session, run: async () => await deps.interrupt(session) };
3978
+ }
3979
+ },
3980
+ {
3981
+ name: "set-model",
3982
+ path: "set-model",
3983
+ read: (body) => {
3984
+ const session = bodyString(body, "session");
3985
+ const model = bodyString(body, "model");
3986
+ 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) };
3987
+ }
3988
+ },
3989
+ {
3990
+ name: "set-permission-mode",
3991
+ path: "set-permission-mode",
3992
+ read: (body) => {
3993
+ const session = bodyString(body, "session");
3994
+ const mode = body.mode;
3995
+ 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) };
3996
+ }
3997
+ },
3998
+ {
3999
+ name: "end-session",
4000
+ path: "end-session",
4001
+ read: (body) => {
4002
+ const session = bodyString(body, "session");
4003
+ const reason = "reason" in body ? bodyString(body, "reason") : void 0;
4004
+ 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) };
4005
+ }
4006
+ },
4007
+ {
4008
+ name: "get-usage",
4009
+ path: "get-usage",
4010
+ read: (body) => {
4011
+ const session = bodyString(body, "session");
4012
+ const skipBehaviors = "skipBehaviors" in body ? bodyBoolean(body, "skipBehaviors") : void 0;
4013
+ 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) };
4014
+ }
4015
+ },
4016
+ {
4017
+ name: "get-context-usage",
4018
+ path: "get-context-usage",
4019
+ read: (body) => {
4020
+ const session = bodyString(body, "session");
4021
+ const detail = "detail" in body && isRcContextUsageDetail(body.detail) ? body.detail : void 0;
4022
+ 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) };
4023
+ }
4024
+ },
4025
+ {
4026
+ name: "read-file",
4027
+ path: "read-file",
4028
+ read: (body) => {
4029
+ const session = bodyString(body, "session");
4030
+ const path32 = bodyString(body, "path");
4031
+ const maxBytes = "maxBytes" in body ? bodyPositiveInteger(body, "maxBytes") : void 0;
4032
+ const encoding = "encoding" in body && isRcReadFileEncoding(body.encoding) ? body.encoding : void 0;
4033
+ const refused2 = session === void 0 || path32 === void 0 || "maxBytes" in body && maxBytes === void 0 || "encoding" in body && encoding === void 0;
4034
+ return refused2 ? { error: `the read-file body must be JSON naming a non-empty session id and path, maxBytes only as a positive whole number or absent, and an encoding the SDK's own type permits (${RC_READ_FILE_ENCODINGS.join(", ")}) or none` } : { session, run: async () => await deps.readFile(session, path32, { ...maxBytes === void 0 ? {} : { maxBytes }, ...encoding === void 0 ? {} : { encoding } }) };
4035
+ }
4036
+ },
4037
+ {
4038
+ name: "file-suggestions",
4039
+ path: "file-suggestions",
4040
+ read: (body) => {
4041
+ const session = bodyString(body, "session");
4042
+ const query = bodyStringAllowingEmpty(body, "query");
4043
+ 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) };
4044
+ }
4045
+ },
4046
+ {
4047
+ name: "keep-alive",
4048
+ path: "keep-alive",
4049
+ read: (body) => {
4050
+ const session = bodyString(body, "session");
4051
+ 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) };
4052
+ }
4053
+ },
4054
+ {
4055
+ name: "mcp-status",
4056
+ path: "mcp-status",
4057
+ read: (body) => {
4058
+ const session = bodyString(body, "session");
4059
+ 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) };
4060
+ }
4061
+ },
4062
+ {
4063
+ name: "mcp-reconnect",
4064
+ path: "mcp-reconnect",
4065
+ read: (body) => {
4066
+ const session = bodyString(body, "session");
4067
+ const serverName = bodyString(body, "serverName");
4068
+ 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) };
4069
+ }
4070
+ },
4071
+ {
4072
+ name: "mcp-authenticate",
4073
+ path: "mcp-authenticate",
4074
+ read: (body) => {
4075
+ const session = bodyString(body, "session");
4076
+ const serverName = bodyString(body, "serverName");
4077
+ const redirectUri = bodyString(body, "redirectUri");
4078
+ 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) };
4079
+ }
4080
+ },
4081
+ {
4082
+ name: "mcp-oauth-callback-url",
4083
+ path: "mcp-oauth-callback-url",
4084
+ read: (body) => {
4085
+ const session = bodyString(body, "session");
4086
+ const serverName = bodyString(body, "serverName");
4087
+ const callbackUrl = bodyString(body, "callbackUrl");
4088
+ return session === void 0 || serverName === void 0 || callbackUrl === void 0 ? { error: "the mcp-oauth-callback-url body must be JSON naming a non-empty session id, server name, and callback URL" } : { session, run: async () => await deps.mcpOAuthCallbackUrl(session, serverName, callbackUrl) };
4089
+ }
4090
+ }
4091
+ ];
3873
4092
  return (request4, response) => {
3874
4093
  const presented = request4.headers.authorization;
3875
4094
  const bearerPrefix = "bearer ".length;
@@ -3954,73 +4173,33 @@ function createRcControlHandler(deps) {
3954
4173
  answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
3955
4174
  return;
3956
4175
  }
3957
- if (url.pathname === interruptPath) {
3958
- if (request4.method !== "POST") {
3959
- answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the interrupt operation is a POST" });
3960
- return;
3961
- }
3962
- let parsed;
3963
- try {
3964
- parsed = JSON.parse(await readBody(request4));
3965
- } catch {
3966
- answerJson(response, HTTP_STATUS.badRequest, { error: "the interrupt body is not readable JSON within the control body cap" });
3967
- return;
3968
- }
3969
- if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || typeof parsed.session !== "string" || parsed.session === "") {
3970
- answerJson(response, HTTP_STATUS.badRequest, { error: "the interrupt body must be JSON naming a non-empty session id" });
3971
- return;
3972
- }
3973
- const result = await deps.interrupt(parsed.session);
3974
- if (result.ok) {
3975
- answerJson(response, HTTP_STATUS.ok, { session: parsed.session, sequenceNums: result.sequenceNums });
3976
- return;
4176
+ for (const route of controlWrites) {
4177
+ if (url.pathname !== `${CONTROL_PATH_PREFIX}/${route.path}`) {
4178
+ continue;
3977
4179
  }
3978
- answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
3979
- return;
3980
- }
3981
- if (url.pathname === setModelPath) {
3982
4180
  if (request4.method !== "POST") {
3983
- answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the set-model operation is a POST" });
4181
+ answerJson(response, HTTP_STATUS.methodNotAllowed, { error: `the ${route.name} operation is a POST` });
3984
4182
  return;
3985
4183
  }
3986
4184
  let parsed;
3987
4185
  try {
3988
4186
  parsed = JSON.parse(await readBody(request4));
3989
4187
  } catch {
3990
- answerJson(response, HTTP_STATUS.badRequest, { error: "the set-model body is not readable JSON within the control body cap" });
4188
+ answerJson(response, HTTP_STATUS.badRequest, { error: `the ${route.name} body is not readable JSON within the control body cap` });
3991
4189
  return;
3992
4190
  }
3993
- if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("model" in parsed) || typeof parsed.session !== "string" || typeof parsed.model !== "string" || parsed.session === "" || parsed.model === "") {
3994
- answerJson(response, HTTP_STATUS.badRequest, { error: "the set-model body must be JSON naming a non-empty session id and model id" });
4191
+ if (!isJsonObject(parsed)) {
4192
+ answerJson(response, HTTP_STATUS.badRequest, { error: `the ${route.name} body must be a JSON object` });
3995
4193
  return;
3996
4194
  }
3997
- const result = await deps.setModel(parsed.session, parsed.model);
3998
- if (result.ok) {
3999
- answerJson(response, HTTP_STATUS.ok, { session: parsed.session, model: parsed.model, sequenceNums: result.sequenceNums });
4195
+ const read = route.read(parsed);
4196
+ if ("error" in read) {
4197
+ answerJson(response, HTTP_STATUS.badRequest, { error: read.error });
4000
4198
  return;
4001
4199
  }
4002
- answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
4003
- return;
4004
- }
4005
- if (url.pathname === setPermissionModePath) {
4006
- if (request4.method !== "POST") {
4007
- answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the set-permission-mode operation is a POST" });
4008
- return;
4009
- }
4010
- let parsed;
4011
- try {
4012
- parsed = JSON.parse(await readBody(request4));
4013
- } catch {
4014
- answerJson(response, HTTP_STATUS.badRequest, { error: "the set-permission-mode body is not readable JSON within the control body cap" });
4015
- return;
4016
- }
4017
- if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("mode" in parsed) || typeof parsed.session !== "string" || parsed.session === "" || !isRcPermissionMode(parsed.mode)) {
4018
- 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})` });
4019
- return;
4020
- }
4021
- const result = await deps.setPermissionMode(parsed.session, parsed.mode);
4200
+ const result = await read.run();
4022
4201
  if (result.ok) {
4023
- answerJson(response, HTTP_STATUS.ok, { session: parsed.session, mode: parsed.mode, sequenceNums: result.sequenceNums });
4202
+ answerJson(response, HTTP_STATUS.ok, { session: read.session, ...result.requestId === void 0 ? {} : { request: result.requestId }, sequenceNums: result.sequenceNums });
4024
4203
  return;
4025
4204
  }
4026
4205
  answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
@@ -4138,6 +4317,19 @@ function sequenceNumsOfBody(body) {
4138
4317
  }
4139
4318
  return sequenceNums;
4140
4319
  }
4320
+ function requestIdOfBody(body) {
4321
+ let parsed;
4322
+ try {
4323
+ parsed = JSON.parse(body);
4324
+ } catch {
4325
+ return void 0;
4326
+ }
4327
+ if (typeof parsed !== "object" || parsed === null || !("request" in parsed)) {
4328
+ return void 0;
4329
+ }
4330
+ const value = parsed.request;
4331
+ return typeof value === "string" && value !== "" ? value : void 0;
4332
+ }
4141
4333
  function frontDoorRcControl(transport, token) {
4142
4334
  const headers = { authorization: `Bearer ${token}` };
4143
4335
  const listAnswerOf = async (path32, what) => {
@@ -4226,7 +4418,8 @@ function frontDoorRcControl(transport, token) {
4226
4418
  if (sequenceNums === void 0) {
4227
4419
  return { ok: false, message: `the door accepted the event but its answer named no sequence numbers: ${answer.body}` };
4228
4420
  }
4229
- return { ok: true, sequenceNums };
4421
+ const requestId = requestIdOfBody(answer.body);
4422
+ return requestId === void 0 ? { ok: true, sequenceNums } : { ok: true, sequenceNums, requestId };
4230
4423
  }
4231
4424
  return { ok: false, message: describeFailure(answer.status, controlErrorOf(answer.body) ?? answer.body) };
4232
4425
  };
@@ -4243,7 +4436,21 @@ function frontDoorRcControl(transport, token) {
4243
4436
  ),
4244
4437
  interruptSession: async (sessionId) => await writeOf(`${CONTROL_PATH_PREFIX}/interrupt`, JSON.stringify({ session: sessionId }), (status, message) => `interrupting ${sessionId} failed (HTTP ${String(status)}): ${message}`),
4245
4438
  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}`),
4246
- 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}`)
4439
+ 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}`),
4440
+ 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}`),
4441
+ 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}`),
4442
+ 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}`),
4443
+ readFile: async (sessionId, path32, options) => await writeOf(
4444
+ `${CONTROL_PATH_PREFIX}/read-file`,
4445
+ JSON.stringify({ session: sessionId, path: path32, ...options?.maxBytes === void 0 ? {} : { maxBytes: options.maxBytes }, ...options?.encoding === void 0 ? {} : { encoding: options.encoding } }),
4446
+ (status, message) => `reading ${path32} from ${sessionId} failed (HTTP ${String(status)}): ${message}`
4447
+ ),
4448
+ 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}`),
4449
+ keepAlive: async (sessionId) => await writeOf(`${CONTROL_PATH_PREFIX}/keep-alive`, JSON.stringify({ session: sessionId }), (status, message) => `keeping ${sessionId} alive failed (HTTP ${String(status)}): ${message}`),
4450
+ mcpStatus: async (sessionId) => await writeOf(`${CONTROL_PATH_PREFIX}/mcp-status`, JSON.stringify({ session: sessionId }), (status, message) => `requesting the MCP status of ${sessionId} failed (HTTP ${String(status)}): ${message}`),
4451
+ mcpReconnect: async (sessionId, serverName) => await writeOf(`${CONTROL_PATH_PREFIX}/mcp-reconnect`, JSON.stringify({ session: sessionId, serverName }), (status, message) => `reconnecting MCP server ${serverName} on ${sessionId} failed (HTTP ${String(status)}): ${message}`),
4452
+ mcpAuthenticate: async (sessionId, serverName, redirectUri) => await writeOf(`${CONTROL_PATH_PREFIX}/mcp-authenticate`, JSON.stringify({ session: sessionId, serverName, redirectUri }), (status, message) => `authenticating MCP server ${serverName} on ${sessionId} failed (HTTP ${String(status)}): ${message}`),
4453
+ mcpOAuthCallbackUrl: async (sessionId, serverName, callbackUrl) => await writeOf(`${CONTROL_PATH_PREFIX}/mcp-oauth-callback-url`, JSON.stringify({ session: sessionId, serverName, callbackUrl }), (status, message) => `handing MCP server ${serverName}'s OAuth callback to ${sessionId} failed (HTTP ${String(status)}): ${message}`)
4247
4454
  };
4248
4455
  }
4249
4456
 
@@ -4865,6 +5072,48 @@ var RcSetPermissionModeInputSchema = z4.strictObject({
4865
5072
  session: z4.string().min(1),
4866
5073
  mode: RcPermissionModeSchema
4867
5074
  });
5075
+ var RcEndSessionInputSchema = z4.strictObject({
5076
+ session: z4.string().min(1),
5077
+ reason: z4.string().min(1).optional()
5078
+ });
5079
+ var RcGetUsageInputSchema = z4.strictObject({
5080
+ session: z4.string().min(1),
5081
+ skipBehaviors: z4.boolean().optional()
5082
+ });
5083
+ var RcGetContextUsageInputSchema = z4.strictObject({
5084
+ session: z4.string().min(1),
5085
+ detail: z4.enum(RC_CONTEXT_USAGE_DETAILS).optional()
5086
+ });
5087
+ var RcReadFileInputSchema = z4.strictObject({
5088
+ session: z4.string().min(1),
5089
+ path: z4.string().min(1),
5090
+ maxBytes: z4.number().int().positive().optional(),
5091
+ encoding: z4.enum(RC_READ_FILE_ENCODINGS).optional()
5092
+ });
5093
+ var RcFileSuggestionsInputSchema = z4.strictObject({
5094
+ session: z4.string().min(1),
5095
+ query: z4.string()
5096
+ });
5097
+ var RcKeepAliveInputSchema = z4.strictObject({
5098
+ session: z4.string().min(1)
5099
+ });
5100
+ var RcMcpStatusInputSchema = z4.strictObject({
5101
+ session: z4.string().min(1)
5102
+ });
5103
+ var RcMcpReconnectInputSchema = z4.strictObject({
5104
+ session: z4.string().min(1),
5105
+ serverName: z4.string().min(1)
5106
+ });
5107
+ var RcMcpAuthenticateInputSchema = z4.strictObject({
5108
+ session: z4.string().min(1),
5109
+ serverName: z4.string().min(1),
5110
+ redirectUri: z4.string().min(1)
5111
+ });
5112
+ var RcMcpOAuthCallbackUrlInputSchema = z4.strictObject({
5113
+ session: z4.string().min(1),
5114
+ serverName: z4.string().min(1),
5115
+ callbackUrl: z4.string().min(1)
5116
+ });
4868
5117
  var RcListOutputSchema = z4.strictObject({ sessions: z4.readonly(z4.array(RcSessionSummarySchema)) });
4869
5118
  var RcStatusOutputSchema = z4.strictObject({ statuses: z4.readonly(z4.array(RcSessionStatusSchema)) });
4870
5119
  var RcPendingOutputSchema = z4.strictObject({ pending: z4.readonly(z4.array(RcPendingRequestSummarySchema)) });
@@ -4872,6 +5121,11 @@ var RcWriteOutputSchema = z4.strictObject({
4872
5121
  session: z4.string(),
4873
5122
  sequenceNums: z4.readonly(z4.array(z4.number()))
4874
5123
  });
5124
+ var RcControlWriteOutputSchema = z4.strictObject({
5125
+ session: z4.string(),
5126
+ request: z4.string(),
5127
+ sequenceNums: z4.readonly(z4.array(z4.number()))
5128
+ });
4875
5129
  var RcAnswerOutputSchema = z4.strictObject({
4876
5130
  session: z4.string(),
4877
5131
  request: z4.string(),
@@ -4900,6 +5154,15 @@ function createRcApiRouter(deps) {
4900
5154
  }
4901
5155
  return result.sequenceNums;
4902
5156
  };
5157
+ const deliveredControl = (sessionId, result) => {
5158
+ if (!result.ok) {
5159
+ throw new ORPCError("BAD_GATEWAY", { message: result.message });
5160
+ }
5161
+ if (result.requestId === void 0) {
5162
+ 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" });
5163
+ }
5164
+ return { request: result.requestId, sequenceNums: result.sequenceNums };
5165
+ };
4903
5166
  return {
4904
5167
  rc: {
4905
5168
  list: authed.output(RcListOutputSchema).handler(() => ({ sessions: deps.list() })),
@@ -4922,9 +5185,19 @@ function createRcApiRouter(deps) {
4922
5185
  const decision = { approve: input.approve, message: input.approve || input.text === void 0 ? void 0 : input.text };
4923
5186
  return { session: input.session, request: input.request, sequenceNums: delivered(input.session, await deps.answer(input.session, input.request, decision)) };
4924
5187
  }),
4925
- interrupt: authed.input(RcInterruptInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.interrupt(input.session)) })),
4926
- setModel: authed.input(RcSetModelInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.setModel(input.session, input.model)) })),
4927
- setPermissionMode: authed.input(RcSetPermissionModeInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.setPermissionMode(input.session, input.mode)) })),
5188
+ interrupt: authed.input(RcInterruptInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.interrupt(input.session)) })),
5189
+ setModel: authed.input(RcSetModelInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.setModel(input.session, input.model)) })),
5190
+ setPermissionMode: authed.input(RcSetPermissionModeInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.setPermissionMode(input.session, input.mode)) })),
5191
+ endSession: authed.input(RcEndSessionInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.endSession(input.session, input.reason)) })),
5192
+ getUsage: authed.input(RcGetUsageInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.getUsage(input.session, input.skipBehaviors)) })),
5193
+ getContextUsage: authed.input(RcGetContextUsageInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.getContextUsage(input.session, input.detail)) })),
5194
+ readFile: authed.input(RcReadFileInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.readFile(input.session, input.path, { ...input.maxBytes === void 0 ? {} : { maxBytes: input.maxBytes }, ...input.encoding === void 0 ? {} : { encoding: input.encoding } })) })),
5195
+ fileSuggestions: authed.input(RcFileSuggestionsInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.fileSuggestions(input.session, input.query)) })),
5196
+ keepAlive: authed.input(RcKeepAliveInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.keepAlive(input.session)) })),
5197
+ mcpStatus: authed.input(RcMcpStatusInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpStatus(input.session)) })),
5198
+ mcpReconnect: authed.input(RcMcpReconnectInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpReconnect(input.session, input.serverName)) })),
5199
+ mcpAuthenticate: authed.input(RcMcpAuthenticateInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpAuthenticate(input.session, input.serverName, input.redirectUri)) })),
5200
+ mcpOAuthCallbackUrl: authed.input(RcMcpOAuthCallbackUrlInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpOAuthCallbackUrl(input.session, input.serverName, input.callbackUrl)) })),
4928
5201
  subscribe: authed.input(RcSessionQuerySchema).output(eventIterator(RcStreamEventSchema)).handler(async function* ({ input, signal }) {
4929
5202
  const queue = [];
4930
5203
  let wake;
@@ -11194,7 +11467,7 @@ import path30 from "node:path";
11194
11467
  // package.json
11195
11468
  var package_default = {
11196
11469
  name: "agent-shim",
11197
- version: "8.23.0",
11470
+ version: "8.24.0",
11198
11471
  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.",
11199
11472
  license: "MIT",
11200
11473
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -12436,10 +12709,12 @@ export {
12436
12709
  ProviderKindMismatchError,
12437
12710
  ProviderNotFoundError,
12438
12711
  ProviderSchema,
12712
+ RC_CONTEXT_USAGE_DETAILS,
12439
12713
  RC_IDLE_EXPIRY_MS,
12440
12714
  RC_ORPC_PATH_PREFIX,
12441
12715
  RC_PENDING_DEADLINE_MS,
12442
12716
  RC_PERMISSION_MODES,
12717
+ RC_READ_FILE_ENCODINGS,
12443
12718
  RC_REQUEST_PARSE_CAP_BYTES,
12444
12719
  RC_SELF_HOST_ENV,
12445
12720
  RC_SELF_HOST_KEEPALIVE_MS,
@@ -12470,11 +12745,23 @@ export {
12470
12745
  addPool,
12471
12746
  addProvider,
12472
12747
  answerRcControlRequest,
12748
+ authenticateRcSessionMcpServer,
12473
12749
  buildEntryFacts,
12474
12750
  buildLayoutPaths,
12751
+ buildRcControlRequestPayload,
12475
12752
  buildRcControlResponsePayload,
12753
+ buildRcEndSessionPayload,
12476
12754
  buildRcEventWriteBody,
12755
+ buildRcFileSuggestionsPayload,
12756
+ buildRcGetContextUsagePayload,
12757
+ buildRcGetUsagePayload,
12477
12758
  buildRcInterruptPayload,
12759
+ buildRcKeepAlivePayload,
12760
+ buildRcMcpAuthenticatePayload,
12761
+ buildRcMcpOAuthCallbackUrlPayload,
12762
+ buildRcMcpReconnectPayload,
12763
+ buildRcMcpStatusPayload,
12764
+ buildRcReadFilePayload,
12478
12765
  buildRcSetModelPayload,
12479
12766
  buildRcSetPermissionModePayload,
12480
12767
  buildRcUserMessagePayload,
@@ -12507,6 +12794,7 @@ export {
12507
12794
  doorApiAuth,
12508
12795
  doorApiNodeHandlerOf,
12509
12796
  effectiveWindow,
12797
+ endRcSession,
12510
12798
  ensureCa,
12511
12799
  ensureFrontDoor,
12512
12800
  ensureHeadroom,
@@ -12523,6 +12811,9 @@ export {
12523
12811
  frontDoorRcApiClient,
12524
12812
  frontDoorRcControl,
12525
12813
  generateCa,
12814
+ getRcSessionContextUsage,
12815
+ getRcSessionMcpStatus,
12816
+ getRcSessionUsage,
12526
12817
  headroomSocketTarget,
12527
12818
  identityExists,
12528
12819
  injectRcUserMessage,
@@ -12530,7 +12821,9 @@ export {
12530
12821
  isIdentityDirectoryName,
12531
12822
  isInterceptedHost,
12532
12823
  isPrivateAddress,
12824
+ isRcContextUsageDetail,
12533
12825
  isRcPermissionMode,
12826
+ isRcReadFileEncoding,
12534
12827
  lateRcEventDial,
12535
12828
  lateRcStreamDial,
12536
12829
  listDirectoryRules,
@@ -12564,6 +12857,7 @@ export {
12564
12857
  readProfile,
12565
12858
  readProvider,
12566
12859
  readRcSelfHostRecord,
12860
+ readRcSessionFile,
12567
12861
  readUsageSnapshot,
12568
12862
  realConnectCertStore,
12569
12863
  realConnectEffects,
@@ -12571,6 +12865,7 @@ export {
12571
12865
  realRcEventDial,
12572
12866
  realRcStreamDial,
12573
12867
  realRcWebFetch,
12868
+ reconnectRcSessionMcpServer,
12574
12869
  recoverFarm,
12575
12870
  recoveryDiagnostics,
12576
12871
  removeDirectoryRule,
@@ -12590,6 +12885,7 @@ export {
12590
12885
  runFrontDoorSupervisor,
12591
12886
  runLauncher,
12592
12887
  runSupervisor,
12888
+ sendRcKeepAlive,
12593
12889
  servedByPipeline,
12594
12890
  setAllowAmbientCredential,
12595
12891
  setDefaultConfigProfile,
@@ -12605,6 +12901,8 @@ export {
12605
12901
  snapshotPath,
12606
12902
  spawnClaude,
12607
12903
  startConnectServer,
12904
+ submitRcSessionMcpOAuthCallbackUrl,
12905
+ suggestRcSessionFiles,
12608
12906
  topLevelNames,
12609
12907
  updateDirectoryRule,
12610
12908
  updateProvider,