agent-shim 8.22.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
@@ -368,6 +368,11 @@ var FrontDoorGlobalConfigSchema = z.strictObject({
368
368
  /** How long the front door may sit with no registered sessions before it closes and exits. Defaults to FRONTDOOR_DEFAULT_IDLE_SHUTDOWN_MINUTES. */
369
369
  idleShutdownMinutes: z.number().int().positive().optional()
370
370
  });
371
+ var UPDATE_MODES = ["off", "notify", "auto"];
372
+ var UpdateGlobalConfigSchema = z.strictObject({
373
+ /** What a launch does about a newer release of agent-shim itself; see `UPDATE_MODES`. Absent means `off`. */
374
+ mode: z.enum(UPDATE_MODES).optional()
375
+ });
371
376
  var GlobalConfigSchema = z.strictObject({
372
377
  $schema: z.string().optional(),
373
378
  defaultConfigProfile: z.string().min(1).optional(),
@@ -377,6 +382,7 @@ var GlobalConfigSchema = z.strictObject({
377
382
  launch: LaunchSchema.optional(),
378
383
  headroom: HeadroomGlobalConfigSchema.optional(),
379
384
  frontdoor: FrontDoorGlobalConfigSchema.optional(),
385
+ update: UpdateGlobalConfigSchema.optional(),
380
386
  pools: z.record(PoolNameSchema, PoolSchema).optional()
381
387
  });
382
388
  var IdentitySchema = z.strictObject({
@@ -3624,6 +3630,8 @@ function rcObserverAsPassthrough(observer) {
3624
3630
  }
3625
3631
  };
3626
3632
  }
3633
+
3634
+ // src/frontdoor/rcWrites.ts
3627
3635
  function buildRcUserMessagePayload(uuid, sessionId, text) {
3628
3636
  return {
3629
3637
  type: "user",
@@ -3724,14 +3732,55 @@ var RC_PERMISSION_MODES = ["default", "acceptEdits", "bypassPermissions", "plan"
3724
3732
  function isRcPermissionMode(value) {
3725
3733
  return RC_PERMISSION_MODES.some((mode) => mode === value);
3726
3734
  }
3735
+ function buildRcControlRequestPayload(requestId, request4) {
3736
+ return { type: "control_request", request_id: requestId, request: request4 };
3737
+ }
3727
3738
  function buildRcInterruptPayload(requestId) {
3728
- return { type: "control_request", request_id: requestId, request: { subtype: "interrupt" } };
3739
+ return buildRcControlRequestPayload(requestId, { subtype: "interrupt" });
3729
3740
  }
3730
3741
  function buildRcSetModelPayload(requestId, model) {
3731
- return { type: "control_request", request_id: requestId, request: { subtype: "set_model", model } };
3742
+ return buildRcControlRequestPayload(requestId, { subtype: "set_model", model });
3732
3743
  }
3733
3744
  function buildRcSetPermissionModePayload(requestId, mode) {
3734
- 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" };
3735
3784
  }
3736
3785
  async function answerRcControlRequest(deps, sessionId, requestId, decision) {
3737
3786
  const credential = deps.credentialOf(sessionId);
@@ -3758,7 +3807,7 @@ async function answerRcControlRequest(deps, sessionId, requestId, decision) {
3758
3807
  }
3759
3808
  return result;
3760
3809
  }
3761
- async function interruptRcSession(deps, sessionId) {
3810
+ async function deliverRcClientPayload(deps, sessionId, payload, action) {
3762
3811
  const credential = deps.credentialOf(sessionId);
3763
3812
  if (credential === void 0) {
3764
3813
  return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
@@ -3766,12 +3815,12 @@ async function interruptRcSession(deps, sessionId) {
3766
3815
  if (credential.authorization === void 0) {
3767
3816
  return noOAuthCredentialResult(sessionId);
3768
3817
  }
3769
- const body = JSON.stringify(buildRcEventWriteBody(buildRcInterruptPayload(deps.newUuid())));
3818
+ const body = JSON.stringify(buildRcEventWriteBody(payload));
3770
3819
  let answer;
3771
3820
  try {
3772
3821
  answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
3773
3822
  } catch (error) {
3774
- 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)}` };
3775
3824
  }
3776
3825
  const result = rcEventWriteResultFromAnswer(answer);
3777
3826
  if (result.ok) {
@@ -3779,50 +3828,89 @@ async function interruptRcSession(deps, sessionId) {
3779
3828
  }
3780
3829
  return result;
3781
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
+ }
3782
3836
  async function setRcSessionModel(deps, sessionId, model) {
3783
3837
  if (model === "") {
3784
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" };
3785
3839
  }
3786
- const credential = deps.credentialOf(sessionId);
3787
- if (credential === void 0) {
3788
- return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
3789
- }
3790
- if (credential.authorization === void 0) {
3791
- 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" };
3792
3852
  }
3793
- const body = JSON.stringify(buildRcEventWriteBody(buildRcSetModelPayload(deps.newUuid(), model)));
3794
- let answer;
3795
- try {
3796
- answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
3797
- } catch (error) {
3798
- 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" };
3799
3870
  }
3800
- const result = rcEventWriteResultFromAnswer(answer);
3801
- if (result.ok) {
3802
- 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" };
3803
3873
  }
3804
- 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;
3805
3877
  }
3806
- async function setRcSessionPermissionMode(deps, sessionId, mode) {
3807
- const credential = deps.credentialOf(sessionId);
3808
- if (credential === void 0) {
3809
- return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
3810
- }
3811
- if (credential.authorization === void 0) {
3812
- 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" };
3813
3891
  }
3814
- const body = JSON.stringify(buildRcEventWriteBody(buildRcSetPermissionModePayload(deps.newUuid(), mode)));
3815
- let answer;
3816
- try {
3817
- answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
3818
- } catch (error) {
3819
- 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" };
3820
3899
  }
3821
- const result = rcEventWriteResultFromAnswer(answer);
3822
- if (result.ok) {
3823
- 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" };
3824
3907
  }
3825
- 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`);
3826
3914
  }
3827
3915
 
3828
3916
  // src/frontdoor/rcControl.ts
@@ -3834,6 +3922,25 @@ function answerJson(response, status, body) {
3834
3922
  response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text, "utf8")) });
3835
3923
  response.end(text);
3836
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
+ }
3837
3944
  async function readBody(request4) {
3838
3945
  return await new Promise((resolve, reject) => {
3839
3946
  const chunks = [];
@@ -3861,9 +3968,127 @@ function createRcControlHandler(deps) {
3861
3968
  const pendingPath = `${CONTROL_PATH_PREFIX}/pending`;
3862
3969
  const injectPath = `${CONTROL_PATH_PREFIX}/inject`;
3863
3970
  const answerPath = `${CONTROL_PATH_PREFIX}/answer`;
3864
- const interruptPath = `${CONTROL_PATH_PREFIX}/interrupt`;
3865
- const setModelPath = `${CONTROL_PATH_PREFIX}/set-model`;
3866
- 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
+ ];
3867
4092
  return (request4, response) => {
3868
4093
  const presented = request4.headers.authorization;
3869
4094
  const bearerPrefix = "bearer ".length;
@@ -3948,73 +4173,33 @@ function createRcControlHandler(deps) {
3948
4173
  answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
3949
4174
  return;
3950
4175
  }
3951
- if (url.pathname === interruptPath) {
3952
- if (request4.method !== "POST") {
3953
- answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the interrupt operation is a POST" });
3954
- return;
3955
- }
3956
- let parsed;
3957
- try {
3958
- parsed = JSON.parse(await readBody(request4));
3959
- } catch {
3960
- answerJson(response, HTTP_STATUS.badRequest, { error: "the interrupt body is not readable JSON within the control body cap" });
3961
- return;
3962
- }
3963
- if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || typeof parsed.session !== "string" || parsed.session === "") {
3964
- answerJson(response, HTTP_STATUS.badRequest, { error: "the interrupt body must be JSON naming a non-empty session id" });
3965
- return;
3966
- }
3967
- const result = await deps.interrupt(parsed.session);
3968
- if (result.ok) {
3969
- answerJson(response, HTTP_STATUS.ok, { session: parsed.session, sequenceNums: result.sequenceNums });
3970
- return;
4176
+ for (const route of controlWrites) {
4177
+ if (url.pathname !== `${CONTROL_PATH_PREFIX}/${route.path}`) {
4178
+ continue;
3971
4179
  }
3972
- answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
3973
- return;
3974
- }
3975
- if (url.pathname === setModelPath) {
3976
4180
  if (request4.method !== "POST") {
3977
- 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` });
3978
4182
  return;
3979
4183
  }
3980
4184
  let parsed;
3981
4185
  try {
3982
4186
  parsed = JSON.parse(await readBody(request4));
3983
4187
  } catch {
3984
- answerJson(response, HTTP_STATUS.badRequest, { error: "the set-model body is not readable JSON within the control body cap" });
3985
- return;
3986
- }
3987
- if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("model" in parsed) || typeof parsed.session !== "string" || typeof parsed.model !== "string" || parsed.session === "" || parsed.model === "") {
3988
- answerJson(response, HTTP_STATUS.badRequest, { error: "the set-model body must be JSON naming a non-empty session id and model id" });
4188
+ answerJson(response, HTTP_STATUS.badRequest, { error: `the ${route.name} body is not readable JSON within the control body cap` });
3989
4189
  return;
3990
4190
  }
3991
- const result = await deps.setModel(parsed.session, parsed.model);
3992
- if (result.ok) {
3993
- answerJson(response, HTTP_STATUS.ok, { session: parsed.session, model: parsed.model, sequenceNums: result.sequenceNums });
3994
- return;
3995
- }
3996
- answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
3997
- return;
3998
- }
3999
- if (url.pathname === setPermissionModePath) {
4000
- if (request4.method !== "POST") {
4001
- answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the set-permission-mode operation is a POST" });
4191
+ if (!isJsonObject(parsed)) {
4192
+ answerJson(response, HTTP_STATUS.badRequest, { error: `the ${route.name} body must be a JSON object` });
4002
4193
  return;
4003
4194
  }
4004
- let parsed;
4005
- try {
4006
- parsed = JSON.parse(await readBody(request4));
4007
- } catch {
4008
- answerJson(response, HTTP_STATUS.badRequest, { error: "the set-permission-mode body is not readable JSON within the control body cap" });
4195
+ const read = route.read(parsed);
4196
+ if ("error" in read) {
4197
+ answerJson(response, HTTP_STATUS.badRequest, { error: read.error });
4009
4198
  return;
4010
4199
  }
4011
- if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("mode" in parsed) || typeof parsed.session !== "string" || parsed.session === "" || !isRcPermissionMode(parsed.mode)) {
4012
- 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})` });
4013
- return;
4014
- }
4015
- const result = await deps.setPermissionMode(parsed.session, parsed.mode);
4200
+ const result = await read.run();
4016
4201
  if (result.ok) {
4017
- 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 });
4018
4203
  return;
4019
4204
  }
4020
4205
  answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
@@ -4132,6 +4317,19 @@ function sequenceNumsOfBody(body) {
4132
4317
  }
4133
4318
  return sequenceNums;
4134
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
+ }
4135
4333
  function frontDoorRcControl(transport, token) {
4136
4334
  const headers = { authorization: `Bearer ${token}` };
4137
4335
  const listAnswerOf = async (path32, what) => {
@@ -4220,7 +4418,8 @@ function frontDoorRcControl(transport, token) {
4220
4418
  if (sequenceNums === void 0) {
4221
4419
  return { ok: false, message: `the door accepted the event but its answer named no sequence numbers: ${answer.body}` };
4222
4420
  }
4223
- return { ok: true, sequenceNums };
4421
+ const requestId = requestIdOfBody(answer.body);
4422
+ return requestId === void 0 ? { ok: true, sequenceNums } : { ok: true, sequenceNums, requestId };
4224
4423
  }
4225
4424
  return { ok: false, message: describeFailure(answer.status, controlErrorOf(answer.body) ?? answer.body) };
4226
4425
  };
@@ -4237,7 +4436,21 @@ function frontDoorRcControl(transport, token) {
4237
4436
  ),
4238
4437
  interruptSession: async (sessionId) => await writeOf(`${CONTROL_PATH_PREFIX}/interrupt`, JSON.stringify({ session: sessionId }), (status, message) => `interrupting ${sessionId} failed (HTTP ${String(status)}): ${message}`),
4239
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}`),
4240
- 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}`)
4241
4454
  };
4242
4455
  }
4243
4456
 
@@ -4859,6 +5072,48 @@ var RcSetPermissionModeInputSchema = z4.strictObject({
4859
5072
  session: z4.string().min(1),
4860
5073
  mode: RcPermissionModeSchema
4861
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
+ });
4862
5117
  var RcListOutputSchema = z4.strictObject({ sessions: z4.readonly(z4.array(RcSessionSummarySchema)) });
4863
5118
  var RcStatusOutputSchema = z4.strictObject({ statuses: z4.readonly(z4.array(RcSessionStatusSchema)) });
4864
5119
  var RcPendingOutputSchema = z4.strictObject({ pending: z4.readonly(z4.array(RcPendingRequestSummarySchema)) });
@@ -4866,6 +5121,11 @@ var RcWriteOutputSchema = z4.strictObject({
4866
5121
  session: z4.string(),
4867
5122
  sequenceNums: z4.readonly(z4.array(z4.number()))
4868
5123
  });
5124
+ var RcControlWriteOutputSchema = z4.strictObject({
5125
+ session: z4.string(),
5126
+ request: z4.string(),
5127
+ sequenceNums: z4.readonly(z4.array(z4.number()))
5128
+ });
4869
5129
  var RcAnswerOutputSchema = z4.strictObject({
4870
5130
  session: z4.string(),
4871
5131
  request: z4.string(),
@@ -4894,6 +5154,15 @@ function createRcApiRouter(deps) {
4894
5154
  }
4895
5155
  return result.sequenceNums;
4896
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
+ };
4897
5166
  return {
4898
5167
  rc: {
4899
5168
  list: authed.output(RcListOutputSchema).handler(() => ({ sessions: deps.list() })),
@@ -4916,9 +5185,19 @@ function createRcApiRouter(deps) {
4916
5185
  const decision = { approve: input.approve, message: input.approve || input.text === void 0 ? void 0 : input.text };
4917
5186
  return { session: input.session, request: input.request, sequenceNums: delivered(input.session, await deps.answer(input.session, input.request, decision)) };
4918
5187
  }),
4919
- interrupt: authed.input(RcInterruptInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.interrupt(input.session)) })),
4920
- setModel: authed.input(RcSetModelInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.setModel(input.session, input.model)) })),
4921
- 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)) })),
4922
5201
  subscribe: authed.input(RcSessionQuerySchema).output(eventIterator(RcStreamEventSchema)).handler(async function* ({ input, signal }) {
4923
5202
  const queue = [];
4924
5203
  let wake;
@@ -11185,6 +11464,106 @@ function removeDirectoryRule(paths, rulePath) {
11185
11464
  import { randomUUID as randomUUID4 } from "node:crypto";
11186
11465
  import path30 from "node:path";
11187
11466
 
11467
+ // package.json
11468
+ var package_default = {
11469
+ name: "agent-shim",
11470
+ version: "8.24.0",
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.",
11472
+ license: "MIT",
11473
+ author: "Joseph Mearman <joseph@mearman.co.uk>",
11474
+ repository: {
11475
+ type: "git",
11476
+ url: "git+https://github.com/ExaDev/agent-shim.git"
11477
+ },
11478
+ homepage: "https://github.com/ExaDev/agent-shim#readme",
11479
+ bugs: "https://github.com/ExaDev/agent-shim/issues",
11480
+ keywords: [
11481
+ "claude-code",
11482
+ "claude",
11483
+ "cli"
11484
+ ],
11485
+ packageManager: "pnpm@12.4.1+sha512.2e81e399d73fe8390dab25e06aa788ab7a5908248d2f5a370f82b481147a6a7a367bf8048f9a6fdb6460f21a66f0542dedb8b94ca2c8723596741920b1656d4c",
11486
+ type: "module",
11487
+ engines: {
11488
+ node: "^22.18 || >=24"
11489
+ },
11490
+ bin: {
11491
+ "agent-shim": "dist/cli.cjs",
11492
+ "claude-use": "dist/cli.cjs"
11493
+ },
11494
+ files: [
11495
+ "dist/cli.cjs",
11496
+ "dist/index.mjs",
11497
+ "dist/index.cjs",
11498
+ "dist/types"
11499
+ ],
11500
+ scripts: {
11501
+ prepare: "husky && node scripts/build.mts --bundle-only",
11502
+ typecheck: "turbo run _typecheck",
11503
+ _typecheck: "tsc --noEmit",
11504
+ lint: "turbo run _lint",
11505
+ _lint: "eslint . --cache --cache-location .eslintcache --max-warnings 0",
11506
+ test: "turbo run _test",
11507
+ _test: "vitest run",
11508
+ "test:coverage": "turbo run _test:coverage",
11509
+ "_test:coverage": "vitest run --coverage",
11510
+ knip: "turbo run _knip",
11511
+ _knip: "knip --cache",
11512
+ build: "turbo run _build",
11513
+ _build: "node scripts/build.mts",
11514
+ "build:bundle": "node scripts/build.mts --bundle-only",
11515
+ prepublishOnly: "pnpm build:bundle",
11516
+ schema: "node scripts/gen-schema.mts"
11517
+ },
11518
+ dependencies: {
11519
+ "@clack/prompts": "^1.8.0",
11520
+ "@orpc/client": "1.15.4",
11521
+ "@orpc/server": "1.15.4",
11522
+ commander: "^15.0.0",
11523
+ cosmiconfig: "^10.0.1",
11524
+ picomatch: "^4.0.7",
11525
+ undici: "7.29.1",
11526
+ zod: "^4.6.1"
11527
+ },
11528
+ devDependencies: {
11529
+ "@commitlint/cli": "^21.2.2",
11530
+ "@commitlint/config-conventional": "^21.2.2",
11531
+ "@exadev/eslint-config": "2.12.1",
11532
+ "@semantic-release/changelog": "^7.0.0",
11533
+ "@semantic-release/git": "^11.0.1",
11534
+ "@semantic-release/npm": "^13.1.5",
11535
+ "@types/node": "^26.5.1",
11536
+ "@types/picomatch": "^4.0.3",
11537
+ "@vitest/coverage-v8": "^4.1.11",
11538
+ "conventional-changelog-conventionalcommits": "^10.4.0",
11539
+ esbuild: "^0.28.2",
11540
+ eslint: "^10.10.0",
11541
+ globals: "^17.12.0",
11542
+ husky: "^9.1.7",
11543
+ jiti: "^2.7.0",
11544
+ knip: "^6.35.1",
11545
+ "lint-staged": "^17.5.0",
11546
+ rollup: "4.64.0",
11547
+ "rollup-plugin-dts": "6.5.1",
11548
+ "semantic-release": "^25.0.9",
11549
+ turbo: "^2.11.5",
11550
+ typescript: "^6.0.3",
11551
+ "typescript-eslint": "^8.70.0",
11552
+ vitest: "^4.1.11"
11553
+ },
11554
+ main: "dist/index.cjs",
11555
+ module: "dist/index.mjs",
11556
+ types: "dist/types/index.d.ts",
11557
+ exports: {
11558
+ ".": {
11559
+ types: "./dist/types/index.d.ts",
11560
+ import: "./dist/index.mjs",
11561
+ require: "./dist/index.cjs"
11562
+ },
11563
+ "./package.json": "./package.json"
11564
+ }
11565
+ };
11566
+
11188
11567
  // src/launcher/argv.ts
11189
11568
  var ConflictingIdentityError = class extends UsageError {
11190
11569
  constructor(positional, flag) {
@@ -11657,6 +12036,88 @@ function spawnClaude(params) {
11657
12036
  return params.proc.exit(exitCodeFor(result));
11658
12037
  }
11659
12038
 
12039
+ // src/update/update.ts
12040
+ var LATEST_RELEASE_URL = "https://github.com/ExaDev/agent-shim/releases/latest";
12041
+ function compareVersions3(left, right) {
12042
+ const parse = (version) => {
12043
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version);
12044
+ if (match === null) {
12045
+ throw new Error(`not a plain release version: ${version}`);
12046
+ }
12047
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
12048
+ };
12049
+ const leftParts = parse(left);
12050
+ const rightParts = parse(right);
12051
+ return leftParts.map((part, index) => part - (rightParts[index] ?? 0)).find((difference) => difference !== 0) ?? 0;
12052
+ }
12053
+ function releaseTagFromUrl(url) {
12054
+ const segment = url.split("?")[0]?.split("/").at(-1);
12055
+ const candidate = segment?.startsWith("v") === true ? segment.slice(1) : void 0;
12056
+ return candidate !== void 0 && /^\d+\.\d+\.\d+$/.test(candidate) ? candidate : void 0;
12057
+ }
12058
+
12059
+ // src/update/launchHook.ts
12060
+ var UPDATE_CHECK_COOLDOWN_MS = 864e5;
12061
+ function resolveUpdateMode(globalConfig) {
12062
+ return globalConfig?.update?.mode ?? "off";
12063
+ }
12064
+ function updateCheckDue(contents, nowMs, cooldownMs = UPDATE_CHECK_COOLDOWN_MS) {
12065
+ if (contents === void 0) {
12066
+ return true;
12067
+ }
12068
+ const checkedAt = Date.parse(contents.trim());
12069
+ if (Number.isNaN(checkedAt)) {
12070
+ return true;
12071
+ }
12072
+ const age = nowMs - checkedAt;
12073
+ return age < 0 || age >= cooldownMs;
12074
+ }
12075
+ var NOOP_HANDLE = { markChildStarted: () => void 0 };
12076
+ function updateNotifyLine(currentVersion, latest) {
12077
+ return `agent-shim: update available: ${currentVersion} -> ${latest} (run \`agent-shim update\` to install it)`;
12078
+ }
12079
+ function runLaunchUpdateCheck(params) {
12080
+ const { mode, checkPath, currentVersion, port } = params;
12081
+ if (mode === "off" || port === void 0) {
12082
+ return NOOP_HANDLE;
12083
+ }
12084
+ let childStarted = false;
12085
+ try {
12086
+ if (!updateCheckDue(port.readStamp(checkPath), port.now())) {
12087
+ return NOOP_HANDLE;
12088
+ }
12089
+ port.writeStamp(checkPath, new Date(port.now()).toISOString());
12090
+ } catch {
12091
+ return NOOP_HANDLE;
12092
+ }
12093
+ const answer = (url) => {
12094
+ try {
12095
+ const latest = releaseTagFromUrl(url);
12096
+ if (latest === void 0 || compareVersions3(latest, currentVersion) <= 0) {
12097
+ return;
12098
+ }
12099
+ if (mode === "auto") {
12100
+ port.spawnDetached(["update"]);
12101
+ }
12102
+ if (!childStarted) {
12103
+ port.writeErr(updateNotifyLine(currentVersion, latest));
12104
+ }
12105
+ } catch {
12106
+ }
12107
+ };
12108
+ port.effectiveUrl(LATEST_RELEASE_URL).then(
12109
+ (url) => {
12110
+ answer(url);
12111
+ },
12112
+ () => void 0
12113
+ );
12114
+ return {
12115
+ markChildStarted: () => {
12116
+ childStarted = true;
12117
+ }
12118
+ };
12119
+ }
12120
+
11660
12121
  // src/launcher.ts
11661
12122
  function resolveIdentityCredential(identity, credential, env, port) {
11662
12123
  if (port === void 0) {
@@ -11822,6 +12283,12 @@ function prepareLaunch(params) {
11822
12283
  log.info(
11823
12284
  `agent-shim: identity ${identityDecision.name ?? "(none)"} (${identityDecision.source}${poolExplanation === void 0 ? "" : `, ${poolExplanation}`}), config profile ${configProfileDecision.name ?? "(none)"} (${configProfileDecision.source})` + (resolvedProvider === void 0 ? "" : `, provider ${resolvedProvider.name} (credential ${resolvedProvider.credential.target} from ${describeSource(resolvedProvider.credential.source)})`) + (identityCredential === void 0 ? "" : `, identity credential ${identityCredential.target} from ${describeSource(identityCredential.source)}`)
11824
12285
  );
12286
+ const updateCheck = runLaunchUpdateCheck({
12287
+ mode: params.updateMode ?? "off",
12288
+ checkPath: path30.join(paths.root, "update.check"),
12289
+ currentVersion: package_default.version,
12290
+ port: params.update
12291
+ });
11825
12292
  let cascadeLaunch;
11826
12293
  if (farmContext !== void 0) {
11827
12294
  const { farm: resyncFarmRuntime, identity: resyncIdentity, cascade } = farmContext;
@@ -11927,11 +12394,12 @@ function prepareLaunch(params) {
11927
12394
  registered();
11928
12395
  }
11929
12396
  };
11930
- return { bin: discovered.path, args: finalArgv, env: finalEnv, release };
12397
+ return { bin: discovered.path, args: finalArgv, env: finalEnv, release, markChildStarted: updateCheck.markChildStarted };
11931
12398
  }
11932
12399
  function runLauncher(params) {
11933
12400
  const plan = prepareLaunch(params);
11934
12401
  try {
12402
+ plan.markChildStarted();
11935
12403
  spawnClaude({ bin: plan.bin, args: plan.args, env: plan.env, spawn: params.spawn, proc: params.proc, beforeExit: plan.release });
11936
12404
  } finally {
11937
12405
  plan.release();
@@ -12094,6 +12562,7 @@ function buildFarmRuntime(paths, cwd) {
12094
12562
  const loaded = loadCascadeInput({ paths, home, cwd, read });
12095
12563
  const selections = readDirectorySelections(loaded);
12096
12564
  const git = resolveGitBranch(realRunPort, cwd);
12565
+ const updateMode = resolveUpdateMode(loaded.globalConfig);
12097
12566
  return {
12098
12567
  runtime: {
12099
12568
  fs: realFarmFs,
@@ -12119,7 +12588,8 @@ function buildFarmRuntime(paths, cwd) {
12119
12588
  ...selections.identity === void 0 ? {} : { directoryIdentity: selections.identity },
12120
12589
  ...selections.configProfile === void 0 ? {} : { directoryConfigProfile: selections.configProfile },
12121
12590
  ...loaded.globalConfig?.defaultConfigProfile === void 0 ? {} : { globalDefaultConfigProfile: loaded.globalConfig.defaultConfigProfile },
12122
- ...loaded.globalConfig?.pools === void 0 ? {} : { pools: loaded.globalConfig.pools }
12591
+ ...loaded.globalConfig?.pools === void 0 ? {} : { pools: loaded.globalConfig.pools },
12592
+ ...updateMode === "off" ? {} : { updateMode }
12123
12593
  };
12124
12594
  }
12125
12595
  function realClaudeBinaryResolver(paths) {
@@ -12141,6 +12611,7 @@ function realPrepareLaunchParams(paths, options) {
12141
12611
  ...farm.directoryConfigProfile === void 0 ? {} : { directoryRuleConfigProfile: farm.directoryConfigProfile },
12142
12612
  ...farm.globalDefaultConfigProfile === void 0 ? {} : { globalDefaultConfigProfile: farm.globalDefaultConfigProfile },
12143
12613
  ...farm.pools === void 0 ? {} : { pools: farm.pools },
12614
+ ...farm.updateMode === void 0 ? {} : { updateMode: farm.updateMode },
12144
12615
  ...options.allowMissingConfigProfile === void 0 ? {} : { allowMissingConfigProfile: options.allowMissingConfigProfile }
12145
12616
  };
12146
12617
  }
@@ -12238,10 +12709,12 @@ export {
12238
12709
  ProviderKindMismatchError,
12239
12710
  ProviderNotFoundError,
12240
12711
  ProviderSchema,
12712
+ RC_CONTEXT_USAGE_DETAILS,
12241
12713
  RC_IDLE_EXPIRY_MS,
12242
12714
  RC_ORPC_PATH_PREFIX,
12243
12715
  RC_PENDING_DEADLINE_MS,
12244
12716
  RC_PERMISSION_MODES,
12717
+ RC_READ_FILE_ENCODINGS,
12245
12718
  RC_REQUEST_PARSE_CAP_BYTES,
12246
12719
  RC_SELF_HOST_ENV,
12247
12720
  RC_SELF_HOST_KEEPALIVE_MS,
@@ -12272,11 +12745,23 @@ export {
12272
12745
  addPool,
12273
12746
  addProvider,
12274
12747
  answerRcControlRequest,
12748
+ authenticateRcSessionMcpServer,
12275
12749
  buildEntryFacts,
12276
12750
  buildLayoutPaths,
12751
+ buildRcControlRequestPayload,
12277
12752
  buildRcControlResponsePayload,
12753
+ buildRcEndSessionPayload,
12278
12754
  buildRcEventWriteBody,
12755
+ buildRcFileSuggestionsPayload,
12756
+ buildRcGetContextUsagePayload,
12757
+ buildRcGetUsagePayload,
12279
12758
  buildRcInterruptPayload,
12759
+ buildRcKeepAlivePayload,
12760
+ buildRcMcpAuthenticatePayload,
12761
+ buildRcMcpOAuthCallbackUrlPayload,
12762
+ buildRcMcpReconnectPayload,
12763
+ buildRcMcpStatusPayload,
12764
+ buildRcReadFilePayload,
12280
12765
  buildRcSetModelPayload,
12281
12766
  buildRcSetPermissionModePayload,
12282
12767
  buildRcUserMessagePayload,
@@ -12309,6 +12794,7 @@ export {
12309
12794
  doorApiAuth,
12310
12795
  doorApiNodeHandlerOf,
12311
12796
  effectiveWindow,
12797
+ endRcSession,
12312
12798
  ensureCa,
12313
12799
  ensureFrontDoor,
12314
12800
  ensureHeadroom,
@@ -12325,6 +12811,9 @@ export {
12325
12811
  frontDoorRcApiClient,
12326
12812
  frontDoorRcControl,
12327
12813
  generateCa,
12814
+ getRcSessionContextUsage,
12815
+ getRcSessionMcpStatus,
12816
+ getRcSessionUsage,
12328
12817
  headroomSocketTarget,
12329
12818
  identityExists,
12330
12819
  injectRcUserMessage,
@@ -12332,7 +12821,9 @@ export {
12332
12821
  isIdentityDirectoryName,
12333
12822
  isInterceptedHost,
12334
12823
  isPrivateAddress,
12824
+ isRcContextUsageDetail,
12335
12825
  isRcPermissionMode,
12826
+ isRcReadFileEncoding,
12336
12827
  lateRcEventDial,
12337
12828
  lateRcStreamDial,
12338
12829
  listDirectoryRules,
@@ -12366,6 +12857,7 @@ export {
12366
12857
  readProfile,
12367
12858
  readProvider,
12368
12859
  readRcSelfHostRecord,
12860
+ readRcSessionFile,
12369
12861
  readUsageSnapshot,
12370
12862
  realConnectCertStore,
12371
12863
  realConnectEffects,
@@ -12373,6 +12865,7 @@ export {
12373
12865
  realRcEventDial,
12374
12866
  realRcStreamDial,
12375
12867
  realRcWebFetch,
12868
+ reconnectRcSessionMcpServer,
12376
12869
  recoverFarm,
12377
12870
  recoveryDiagnostics,
12378
12871
  removeDirectoryRule,
@@ -12392,6 +12885,7 @@ export {
12392
12885
  runFrontDoorSupervisor,
12393
12886
  runLauncher,
12394
12887
  runSupervisor,
12888
+ sendRcKeepAlive,
12395
12889
  servedByPipeline,
12396
12890
  setAllowAmbientCredential,
12397
12891
  setDefaultConfigProfile,
@@ -12407,6 +12901,8 @@ export {
12407
12901
  snapshotPath,
12408
12902
  spawnClaude,
12409
12903
  startConnectServer,
12904
+ submitRcSessionMcpOAuthCallbackUrl,
12905
+ suggestRcSessionFiles,
12410
12906
  topLevelNames,
12411
12907
  updateDirectoryRule,
12412
12908
  updateProvider,