agent-shim 8.5.5 → 8.6.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.cjs CHANGED
@@ -75,6 +75,7 @@ __export(index_exports, {
75
75
  ProviderSchema: () => ProviderSchema,
76
76
  RC_IDLE_EXPIRY_MS: () => RC_IDLE_EXPIRY_MS,
77
77
  RC_PENDING_DEADLINE_MS: () => RC_PENDING_DEADLINE_MS,
78
+ RC_PERMISSION_MODES: () => RC_PERMISSION_MODES,
78
79
  RC_REQUEST_PARSE_CAP_BYTES: () => RC_REQUEST_PARSE_CAP_BYTES,
79
80
  RC_SESSIONS_PATH_PREFIX: () => RC_SESSIONS_PATH_PREFIX,
80
81
  ROUTED_PATH_PREFIX: () => ROUTED_PATH_PREFIX,
@@ -91,6 +92,9 @@ __export(index_exports, {
91
92
  buildLayoutPaths: () => buildLayoutPaths,
92
93
  buildRcControlResponsePayload: () => buildRcControlResponsePayload,
93
94
  buildRcEventWriteBody: () => buildRcEventWriteBody,
95
+ buildRcInterruptPayload: () => buildRcInterruptPayload,
96
+ buildRcSetModelPayload: () => buildRcSetModelPayload,
97
+ buildRcSetPermissionModePayload: () => buildRcSetPermissionModePayload,
94
98
  buildRcUserMessagePayload: () => buildRcUserMessagePayload,
95
99
  carryOver: () => carryOver,
96
100
  checkReportHasWarnings: () => checkReportHasWarnings,
@@ -115,8 +119,10 @@ __export(index_exports, {
115
119
  generateCa: () => generateCa,
116
120
  identityExists: () => identityExists,
117
121
  injectRcUserMessage: () => injectRcUserMessage,
122
+ interruptRcSession: () => interruptRcSession,
118
123
  isIdentityDirectoryName: () => isIdentityDirectoryName,
119
124
  isInterceptedHost: () => isInterceptedHost,
125
+ isRcPermissionMode: () => isRcPermissionMode,
120
126
  listDirectoryRules: () => listDirectoryRules,
121
127
  listIdentities: () => listIdentities,
122
128
  listProfiles: () => listProfiles,
@@ -172,6 +178,8 @@ __export(index_exports, {
172
178
  setProfileEntries: () => setProfileEntries,
173
179
  setProfileLaunchFlags: () => setProfileLaunchFlags,
174
180
  setProfileMetadata: () => setProfileMetadata,
181
+ setRcSessionModel: () => setRcSessionModel,
182
+ setRcSessionPermissionMode: () => setRcSessionPermissionMode,
175
183
  snapshotPath: () => snapshotPath,
176
184
  startConnectServer: () => startConnectServer,
177
185
  topLevelNames: () => topLevelNames,
@@ -4070,6 +4078,19 @@ function buildRcControlResponsePayload(requestId, decision) {
4070
4078
  const result = decision.approve ? { behavior: "allow" } : { behavior: "deny", message: decision.message ?? "" };
4071
4079
  return { type: "control_response", response: { subtype: "success", request_id: requestId, response: result } };
4072
4080
  }
4081
+ var RC_PERMISSION_MODES = ["default", "acceptEdits", "bypassPermissions", "plan", "dontAsk", "auto"];
4082
+ function isRcPermissionMode(value) {
4083
+ return RC_PERMISSION_MODES.some((mode) => mode === value);
4084
+ }
4085
+ function buildRcInterruptPayload(requestId) {
4086
+ return { type: "control_request", request_id: requestId, request: { subtype: "interrupt" } };
4087
+ }
4088
+ function buildRcSetModelPayload(requestId, model) {
4089
+ return { type: "control_request", request_id: requestId, request: { subtype: "set_model", model } };
4090
+ }
4091
+ function buildRcSetPermissionModePayload(requestId, mode) {
4092
+ return { type: "control_request", request_id: requestId, request: { subtype: "set_permission_mode", mode } };
4093
+ }
4073
4094
  async function answerRcControlRequest(deps, sessionId, requestId, decision) {
4074
4095
  const credential = deps.credentialOf(sessionId);
4075
4096
  if (credential === void 0) {
@@ -4094,9 +4115,64 @@ async function answerRcControlRequest(deps, sessionId, requestId, decision) {
4094
4115
  }
4095
4116
  return result;
4096
4117
  }
4118
+ async function interruptRcSession(deps, sessionId) {
4119
+ const credential = deps.credentialOf(sessionId);
4120
+ if (credential === void 0) {
4121
+ return { ok: false, message: `the front door has not observed Remote Control session ${sessionId}: it may never have passed through this door, or it ended or expired (an entry lives only a bounded idle period past its last observed traffic)` };
4122
+ }
4123
+ if (credential.authorization === void 0) {
4124
+ return noOAuthCredentialResult(sessionId);
4125
+ }
4126
+ const body = JSON.stringify(buildRcEventWriteBody(buildRcInterruptPayload(deps.newUuid())));
4127
+ let answer;
4128
+ try {
4129
+ answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
4130
+ } catch (error) {
4131
+ return { ok: false, message: `the door could not reach the API host to interrupt session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
4132
+ }
4133
+ return rcEventWriteResultFromAnswer(answer);
4134
+ }
4135
+ async function setRcSessionModel(deps, sessionId, model) {
4136
+ if (model === "") {
4137
+ 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" };
4138
+ }
4139
+ const credential = deps.credentialOf(sessionId);
4140
+ if (credential === void 0) {
4141
+ return { ok: false, message: `the front door has not observed Remote Control session ${sessionId}: it may never have passed through this door, or it ended or expired (an entry lives only a bounded idle period past its last observed traffic)` };
4142
+ }
4143
+ if (credential.authorization === void 0) {
4144
+ return noOAuthCredentialResult(sessionId);
4145
+ }
4146
+ const body = JSON.stringify(buildRcEventWriteBody(buildRcSetModelPayload(deps.newUuid(), model)));
4147
+ let answer;
4148
+ try {
4149
+ answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
4150
+ } catch (error) {
4151
+ 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)}` };
4152
+ }
4153
+ return rcEventWriteResultFromAnswer(answer);
4154
+ }
4155
+ async function setRcSessionPermissionMode(deps, sessionId, mode) {
4156
+ const credential = deps.credentialOf(sessionId);
4157
+ if (credential === void 0) {
4158
+ return { ok: false, message: `the front door has not observed Remote Control session ${sessionId}: it may never have passed through this door, or it ended or expired (an entry lives only a bounded idle period past its last observed traffic)` };
4159
+ }
4160
+ if (credential.authorization === void 0) {
4161
+ return noOAuthCredentialResult(sessionId);
4162
+ }
4163
+ const body = JSON.stringify(buildRcEventWriteBody(buildRcSetPermissionModePayload(deps.newUuid(), mode)));
4164
+ let answer;
4165
+ try {
4166
+ answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
4167
+ } catch (error) {
4168
+ 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)}` };
4169
+ }
4170
+ return rcEventWriteResultFromAnswer(answer);
4171
+ }
4097
4172
 
4098
4173
  // src/frontdoor/rcControl.ts
4099
4174
  var https2 = __toESM(require("node:https"), 1);
4175
+ var RC_PERMISSION_MODE_LIST = RC_PERMISSION_MODES.join(", ");
4100
4176
  var CONTROL_PATH_PREFIX = "/__agent-shim/rc";
4101
4177
  var CONTROL_BODY_CAP_BYTES = 10485760;
4102
4178
  function answerJson(response, status, body) {
@@ -4131,6 +4207,9 @@ function createRcControlHandler(deps) {
4131
4207
  const pendingPath = `${CONTROL_PATH_PREFIX}/pending`;
4132
4208
  const injectPath = `${CONTROL_PATH_PREFIX}/inject`;
4133
4209
  const answerPath = `${CONTROL_PATH_PREFIX}/answer`;
4210
+ const interruptPath = `${CONTROL_PATH_PREFIX}/interrupt`;
4211
+ const setModelPath = `${CONTROL_PATH_PREFIX}/set-model`;
4212
+ const setPermissionModePath = `${CONTROL_PATH_PREFIX}/set-permission-mode`;
4134
4213
  return (request4, response) => {
4135
4214
  const presented = request4.headers.authorization;
4136
4215
  const bearerPrefix = "bearer ".length;
@@ -4215,6 +4294,78 @@ function createRcControlHandler(deps) {
4215
4294
  answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
4216
4295
  return;
4217
4296
  }
4297
+ if (url.pathname === interruptPath) {
4298
+ if (request4.method !== "POST") {
4299
+ answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the interrupt operation is a POST" });
4300
+ return;
4301
+ }
4302
+ let parsed;
4303
+ try {
4304
+ parsed = JSON.parse(await readBody(request4));
4305
+ } catch {
4306
+ answerJson(response, HTTP_STATUS.badRequest, { error: "the interrupt body is not readable JSON within the control body cap" });
4307
+ return;
4308
+ }
4309
+ if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || typeof parsed.session !== "string" || parsed.session === "") {
4310
+ answerJson(response, HTTP_STATUS.badRequest, { error: "the interrupt body must be JSON naming a non-empty session id" });
4311
+ return;
4312
+ }
4313
+ const result = await deps.interrupt(parsed.session);
4314
+ if (result.ok) {
4315
+ answerJson(response, HTTP_STATUS.ok, { session: parsed.session, sequenceNums: result.sequenceNums });
4316
+ return;
4317
+ }
4318
+ answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
4319
+ return;
4320
+ }
4321
+ if (url.pathname === setModelPath) {
4322
+ if (request4.method !== "POST") {
4323
+ answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the set-model operation is a POST" });
4324
+ return;
4325
+ }
4326
+ let parsed;
4327
+ try {
4328
+ parsed = JSON.parse(await readBody(request4));
4329
+ } catch {
4330
+ answerJson(response, HTTP_STATUS.badRequest, { error: "the set-model body is not readable JSON within the control body cap" });
4331
+ return;
4332
+ }
4333
+ if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("model" in parsed) || typeof parsed.session !== "string" || typeof parsed.model !== "string" || parsed.session === "" || parsed.model === "") {
4334
+ answerJson(response, HTTP_STATUS.badRequest, { error: "the set-model body must be JSON naming a non-empty session id and model id" });
4335
+ return;
4336
+ }
4337
+ const result = await deps.setModel(parsed.session, parsed.model);
4338
+ if (result.ok) {
4339
+ answerJson(response, HTTP_STATUS.ok, { session: parsed.session, model: parsed.model, sequenceNums: result.sequenceNums });
4340
+ return;
4341
+ }
4342
+ answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
4343
+ return;
4344
+ }
4345
+ if (url.pathname === setPermissionModePath) {
4346
+ if (request4.method !== "POST") {
4347
+ answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the set-permission-mode operation is a POST" });
4348
+ return;
4349
+ }
4350
+ let parsed;
4351
+ try {
4352
+ parsed = JSON.parse(await readBody(request4));
4353
+ } catch {
4354
+ answerJson(response, HTTP_STATUS.badRequest, { error: "the set-permission-mode body is not readable JSON within the control body cap" });
4355
+ return;
4356
+ }
4357
+ if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("mode" in parsed) || typeof parsed.session !== "string" || parsed.session === "" || !isRcPermissionMode(parsed.mode)) {
4358
+ 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})` });
4359
+ return;
4360
+ }
4361
+ const result = await deps.setPermissionMode(parsed.session, parsed.mode);
4362
+ if (result.ok) {
4363
+ answerJson(response, HTTP_STATUS.ok, { session: parsed.session, mode: parsed.mode, sequenceNums: result.sequenceNums });
4364
+ return;
4365
+ }
4366
+ answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
4367
+ return;
4368
+ }
4218
4369
  answerJson(response, HTTP_STATUS.notFound, { error: `no such control route: ${url.pathname}` });
4219
4370
  };
4220
4371
  handle().catch((error) => {
@@ -4429,7 +4580,10 @@ function frontDoorRcControl(transport, token) {
4429
4580
  `${CONTROL_PATH_PREFIX}/answer`,
4430
4581
  JSON.stringify({ session: sessionId, request: requestId, approve: decision.approve, ...decision.approve || decision.message === void 0 ? {} : { text: decision.message } }),
4431
4582
  (status, message) => `answering control request ${requestId} on ${sessionId} failed (HTTP ${String(status)}): ${message}`
4432
- )
4583
+ ),
4584
+ interruptSession: async (sessionId) => await writeOf(`${CONTROL_PATH_PREFIX}/interrupt`, JSON.stringify({ session: sessionId }), (status, message) => `interrupting ${sessionId} failed (HTTP ${String(status)}): ${message}`),
4585
+ 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}`),
4586
+ 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}`)
4433
4587
  };
4434
4588
  }
4435
4589
 
@@ -4589,7 +4743,7 @@ function ensureFrontDoor(params) {
4589
4743
  ports.stopSupervisor(supervisorPid);
4590
4744
  while (ports.isRunning(supervisorPid)) {
4591
4745
  if (ports.now() >= deadline) {
4592
- throw new FrontDoorStartError(`agent-shim: the front door (pid ${String(supervisorPid)}, protocol ${String(state.protocol ?? 0)}) is older than this launcher (protocol ${String(FRONT_DOOR_PROTOCOL)}) and did not exit when asked. Daemon log: ${paths.frontdoorLogPath}`);
4746
+ throw new FrontDoorStartError(`the front door (pid ${String(supervisorPid)}, protocol ${String(state.protocol ?? 0)}) is older than this launcher (protocol ${String(FRONT_DOOR_PROTOCOL)}) and did not exit when asked. Daemon log: ${paths.frontdoorLogPath}`);
4593
4747
  }
4594
4748
  ports.sleep(FRONTDOOR_LAUNCHER_POLL_MS);
4595
4749
  }
@@ -4603,13 +4757,13 @@ function ensureFrontDoor(params) {
4603
4757
  }
4604
4758
  lastRejection = `the listener on 127.0.0.1:${String(state.port)} (recorded for pid ${String(supervisorPid)}) failed authentication: ${verdict.reason}`;
4605
4759
  if (spawned) {
4606
- throw new FrontDoorStartError(`agent-shim: ${lastRejection}; refusing to send this session's credentials to it. Daemon log: ${paths.frontdoorLogPath}`);
4760
+ throw new FrontDoorStartError(`${lastRejection}; refusing to send this session's credentials to it. Daemon log: ${paths.frontdoorLogPath}`);
4607
4761
  }
4608
4762
  distrusted.add(supervisorPid);
4609
4763
  continue;
4610
4764
  }
4611
4765
  if (state?.lastError !== void 0) {
4612
- throw new FrontDoorStartError(`agent-shim: the front door reported a fatal error and is not serving: ${state.lastError} (daemon log: ${paths.frontdoorLogPath})`);
4766
+ throw new FrontDoorStartError(`the front door reported a fatal error and is not serving: ${state.lastError} (daemon log: ${paths.frontdoorLogPath})`);
4613
4767
  }
4614
4768
  } else if (!spawned) {
4615
4769
  ports.fs.mkdirp(paths.frontdoorDir);
@@ -4629,7 +4783,7 @@ function ensureFrontDoor(params) {
4629
4783
  if (ports.now() >= deadline) {
4630
4784
  const lastError = state?.lastError;
4631
4785
  throw new FrontDoorStartError(
4632
- `agent-shim: the front door did not become ready within ${String(FRONTDOOR_START_TIMEOUT_MS)}ms` + (lastError === void 0 ? "" : ` (last error: ${lastError})`) + (lastRejection === void 0 ? "" : ` (${lastRejection})`) + `. Daemon log: ${paths.frontdoorLogPath}`
4786
+ `the front door did not become ready within ${String(FRONTDOOR_START_TIMEOUT_MS)}ms` + (lastError === void 0 ? "" : ` (last error: ${lastError})`) + (lastRejection === void 0 ? "" : ` (${lastRejection})`) + `. Daemon log: ${paths.frontdoorLogPath}`
4633
4787
  );
4634
4788
  }
4635
4789
  ports.sleep(FRONTDOOR_LAUNCHER_POLL_MS);
@@ -4968,14 +5122,14 @@ function ensureHeadroom(params) {
4968
5122
  const holders = sessionsForSupervisor(ports.fs, paths.headroomSessionsDir, state.supervisorPid).filter((session) => ports.isRunning(session.pid));
4969
5123
  if (holders.length > 0) {
4970
5124
  throw new HeadroomStartError(
4971
- `agent-shim: the running headroom daemon was started before this launch's front door address and would refuse every request this launch sends through it. It restarts once the launches using it end (pid ${holders.map((session) => String(session.pid)).join(", ")}); end them and launch again, or launch with --no-headroom.`
5125
+ `the running headroom daemon was started before this launch's front door address and would refuse every request this launch sends through it. It restarts once the launches using it end (pid ${holders.map((session) => String(session.pid)).join(", ")}); end them and launch again, or launch with --no-headroom.`
4972
5126
  );
4973
5127
  }
4974
5128
  awaitingAllowlist = true;
4975
5129
  }
4976
5130
  if (state.lastError !== void 0) {
4977
5131
  throw new HeadroomStartError(
4978
- `agent-shim: the headroom daemon reported a fatal error and is not serving: ${state.lastError} (daemon log: ${paths.headroomLogPath})`
5132
+ `the headroom daemon reported a fatal error and is not serving: ${state.lastError} (daemon log: ${paths.headroomLogPath})`
4979
5133
  );
4980
5134
  }
4981
5135
  } else if (!spawnedSupervisor) {
@@ -4999,7 +5153,7 @@ function ensureHeadroom(params) {
4999
5153
  if (ports.now() >= deadline) {
5000
5154
  const lastError = state?.lastError;
5001
5155
  throw new HeadroomStartError(
5002
- `agent-shim: the headroom daemon did not become ready within ${String(HEADROOM_START_TIMEOUT_MS)}ms` + (lastError === void 0 ? "" : ` (last error: ${lastError})`) + (awaitingAllowlist ? " (the running daemon's allowlist does not admit this launch's front door, and it did not restart)" : "") + `. Daemon log: ${paths.headroomLogPath}`
5156
+ `the headroom daemon did not become ready within ${String(HEADROOM_START_TIMEOUT_MS)}ms` + (lastError === void 0 ? "" : ` (last error: ${lastError})`) + (awaitingAllowlist ? " (the running daemon's allowlist does not admit this launch's front door, and it did not restart)" : "") + `. Daemon log: ${paths.headroomLogPath}`
5003
5157
  );
5004
5158
  }
5005
5159
  ports.sleep(HEADROOM_LAUNCHER_POLL_MS);
@@ -9379,6 +9533,7 @@ function prepareClaudeLaunch(options) {
9379
9533
  ProviderSchema,
9380
9534
  RC_IDLE_EXPIRY_MS,
9381
9535
  RC_PENDING_DEADLINE_MS,
9536
+ RC_PERMISSION_MODES,
9382
9537
  RC_REQUEST_PARSE_CAP_BYTES,
9383
9538
  RC_SESSIONS_PATH_PREFIX,
9384
9539
  ROUTED_PATH_PREFIX,
@@ -9395,6 +9550,9 @@ function prepareClaudeLaunch(options) {
9395
9550
  buildLayoutPaths,
9396
9551
  buildRcControlResponsePayload,
9397
9552
  buildRcEventWriteBody,
9553
+ buildRcInterruptPayload,
9554
+ buildRcSetModelPayload,
9555
+ buildRcSetPermissionModePayload,
9398
9556
  buildRcUserMessagePayload,
9399
9557
  carryOver,
9400
9558
  checkReportHasWarnings,
@@ -9419,8 +9577,10 @@ function prepareClaudeLaunch(options) {
9419
9577
  generateCa,
9420
9578
  identityExists,
9421
9579
  injectRcUserMessage,
9580
+ interruptRcSession,
9422
9581
  isIdentityDirectoryName,
9423
9582
  isInterceptedHost,
9583
+ isRcPermissionMode,
9424
9584
  listDirectoryRules,
9425
9585
  listIdentities,
9426
9586
  listProfiles,
@@ -9476,6 +9636,8 @@ function prepareClaudeLaunch(options) {
9476
9636
  setProfileEntries,
9477
9637
  setProfileLaunchFlags,
9478
9638
  setProfileMetadata,
9639
+ setRcSessionModel,
9640
+ setRcSessionPermissionMode,
9479
9641
  snapshotPath,
9480
9642
  startConnectServer,
9481
9643
  topLevelNames,
package/dist/index.mjs CHANGED
@@ -3886,6 +3886,19 @@ function buildRcControlResponsePayload(requestId, decision) {
3886
3886
  const result = decision.approve ? { behavior: "allow" } : { behavior: "deny", message: decision.message ?? "" };
3887
3887
  return { type: "control_response", response: { subtype: "success", request_id: requestId, response: result } };
3888
3888
  }
3889
+ var RC_PERMISSION_MODES = ["default", "acceptEdits", "bypassPermissions", "plan", "dontAsk", "auto"];
3890
+ function isRcPermissionMode(value) {
3891
+ return RC_PERMISSION_MODES.some((mode) => mode === value);
3892
+ }
3893
+ function buildRcInterruptPayload(requestId) {
3894
+ return { type: "control_request", request_id: requestId, request: { subtype: "interrupt" } };
3895
+ }
3896
+ function buildRcSetModelPayload(requestId, model) {
3897
+ return { type: "control_request", request_id: requestId, request: { subtype: "set_model", model } };
3898
+ }
3899
+ function buildRcSetPermissionModePayload(requestId, mode) {
3900
+ return { type: "control_request", request_id: requestId, request: { subtype: "set_permission_mode", mode } };
3901
+ }
3889
3902
  async function answerRcControlRequest(deps, sessionId, requestId, decision) {
3890
3903
  const credential = deps.credentialOf(sessionId);
3891
3904
  if (credential === void 0) {
@@ -3910,9 +3923,64 @@ async function answerRcControlRequest(deps, sessionId, requestId, decision) {
3910
3923
  }
3911
3924
  return result;
3912
3925
  }
3926
+ async function interruptRcSession(deps, sessionId) {
3927
+ const credential = deps.credentialOf(sessionId);
3928
+ if (credential === void 0) {
3929
+ return { ok: false, message: `the front door has not observed Remote Control session ${sessionId}: it may never have passed through this door, or it ended or expired (an entry lives only a bounded idle period past its last observed traffic)` };
3930
+ }
3931
+ if (credential.authorization === void 0) {
3932
+ return noOAuthCredentialResult(sessionId);
3933
+ }
3934
+ const body = JSON.stringify(buildRcEventWriteBody(buildRcInterruptPayload(deps.newUuid())));
3935
+ let answer;
3936
+ try {
3937
+ answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
3938
+ } catch (error) {
3939
+ return { ok: false, message: `the door could not reach the API host to interrupt session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
3940
+ }
3941
+ return rcEventWriteResultFromAnswer(answer);
3942
+ }
3943
+ async function setRcSessionModel(deps, sessionId, model) {
3944
+ if (model === "") {
3945
+ 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" };
3946
+ }
3947
+ const credential = deps.credentialOf(sessionId);
3948
+ if (credential === void 0) {
3949
+ return { ok: false, message: `the front door has not observed Remote Control session ${sessionId}: it may never have passed through this door, or it ended or expired (an entry lives only a bounded idle period past its last observed traffic)` };
3950
+ }
3951
+ if (credential.authorization === void 0) {
3952
+ return noOAuthCredentialResult(sessionId);
3953
+ }
3954
+ const body = JSON.stringify(buildRcEventWriteBody(buildRcSetModelPayload(deps.newUuid(), model)));
3955
+ let answer;
3956
+ try {
3957
+ answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
3958
+ } catch (error) {
3959
+ 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)}` };
3960
+ }
3961
+ return rcEventWriteResultFromAnswer(answer);
3962
+ }
3963
+ async function setRcSessionPermissionMode(deps, sessionId, mode) {
3964
+ const credential = deps.credentialOf(sessionId);
3965
+ if (credential === void 0) {
3966
+ return { ok: false, message: `the front door has not observed Remote Control session ${sessionId}: it may never have passed through this door, or it ended or expired (an entry lives only a bounded idle period past its last observed traffic)` };
3967
+ }
3968
+ if (credential.authorization === void 0) {
3969
+ return noOAuthCredentialResult(sessionId);
3970
+ }
3971
+ const body = JSON.stringify(buildRcEventWriteBody(buildRcSetPermissionModePayload(deps.newUuid(), mode)));
3972
+ let answer;
3973
+ try {
3974
+ answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
3975
+ } catch (error) {
3976
+ 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)}` };
3977
+ }
3978
+ return rcEventWriteResultFromAnswer(answer);
3979
+ }
3913
3980
 
3914
3981
  // src/frontdoor/rcControl.ts
3915
3982
  import * as https2 from "node:https";
3983
+ var RC_PERMISSION_MODE_LIST = RC_PERMISSION_MODES.join(", ");
3916
3984
  var CONTROL_PATH_PREFIX = "/__agent-shim/rc";
3917
3985
  var CONTROL_BODY_CAP_BYTES = 10485760;
3918
3986
  function answerJson(response, status, body) {
@@ -3947,6 +4015,9 @@ function createRcControlHandler(deps) {
3947
4015
  const pendingPath = `${CONTROL_PATH_PREFIX}/pending`;
3948
4016
  const injectPath = `${CONTROL_PATH_PREFIX}/inject`;
3949
4017
  const answerPath = `${CONTROL_PATH_PREFIX}/answer`;
4018
+ const interruptPath = `${CONTROL_PATH_PREFIX}/interrupt`;
4019
+ const setModelPath = `${CONTROL_PATH_PREFIX}/set-model`;
4020
+ const setPermissionModePath = `${CONTROL_PATH_PREFIX}/set-permission-mode`;
3950
4021
  return (request4, response) => {
3951
4022
  const presented = request4.headers.authorization;
3952
4023
  const bearerPrefix = "bearer ".length;
@@ -4031,6 +4102,78 @@ function createRcControlHandler(deps) {
4031
4102
  answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
4032
4103
  return;
4033
4104
  }
4105
+ if (url.pathname === interruptPath) {
4106
+ if (request4.method !== "POST") {
4107
+ answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the interrupt operation is a POST" });
4108
+ return;
4109
+ }
4110
+ let parsed;
4111
+ try {
4112
+ parsed = JSON.parse(await readBody(request4));
4113
+ } catch {
4114
+ answerJson(response, HTTP_STATUS.badRequest, { error: "the interrupt body is not readable JSON within the control body cap" });
4115
+ return;
4116
+ }
4117
+ if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || typeof parsed.session !== "string" || parsed.session === "") {
4118
+ answerJson(response, HTTP_STATUS.badRequest, { error: "the interrupt body must be JSON naming a non-empty session id" });
4119
+ return;
4120
+ }
4121
+ const result = await deps.interrupt(parsed.session);
4122
+ if (result.ok) {
4123
+ answerJson(response, HTTP_STATUS.ok, { session: parsed.session, sequenceNums: result.sequenceNums });
4124
+ return;
4125
+ }
4126
+ answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
4127
+ return;
4128
+ }
4129
+ if (url.pathname === setModelPath) {
4130
+ if (request4.method !== "POST") {
4131
+ answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the set-model operation is a POST" });
4132
+ return;
4133
+ }
4134
+ let parsed;
4135
+ try {
4136
+ parsed = JSON.parse(await readBody(request4));
4137
+ } catch {
4138
+ answerJson(response, HTTP_STATUS.badRequest, { error: "the set-model body is not readable JSON within the control body cap" });
4139
+ return;
4140
+ }
4141
+ if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("model" in parsed) || typeof parsed.session !== "string" || typeof parsed.model !== "string" || parsed.session === "" || parsed.model === "") {
4142
+ answerJson(response, HTTP_STATUS.badRequest, { error: "the set-model body must be JSON naming a non-empty session id and model id" });
4143
+ return;
4144
+ }
4145
+ const result = await deps.setModel(parsed.session, parsed.model);
4146
+ if (result.ok) {
4147
+ answerJson(response, HTTP_STATUS.ok, { session: parsed.session, model: parsed.model, sequenceNums: result.sequenceNums });
4148
+ return;
4149
+ }
4150
+ answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
4151
+ return;
4152
+ }
4153
+ if (url.pathname === setPermissionModePath) {
4154
+ if (request4.method !== "POST") {
4155
+ answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the set-permission-mode operation is a POST" });
4156
+ return;
4157
+ }
4158
+ let parsed;
4159
+ try {
4160
+ parsed = JSON.parse(await readBody(request4));
4161
+ } catch {
4162
+ answerJson(response, HTTP_STATUS.badRequest, { error: "the set-permission-mode body is not readable JSON within the control body cap" });
4163
+ return;
4164
+ }
4165
+ if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("mode" in parsed) || typeof parsed.session !== "string" || parsed.session === "" || !isRcPermissionMode(parsed.mode)) {
4166
+ 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})` });
4167
+ return;
4168
+ }
4169
+ const result = await deps.setPermissionMode(parsed.session, parsed.mode);
4170
+ if (result.ok) {
4171
+ answerJson(response, HTTP_STATUS.ok, { session: parsed.session, mode: parsed.mode, sequenceNums: result.sequenceNums });
4172
+ return;
4173
+ }
4174
+ answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
4175
+ return;
4176
+ }
4034
4177
  answerJson(response, HTTP_STATUS.notFound, { error: `no such control route: ${url.pathname}` });
4035
4178
  };
4036
4179
  handle().catch((error) => {
@@ -4245,7 +4388,10 @@ function frontDoorRcControl(transport, token) {
4245
4388
  `${CONTROL_PATH_PREFIX}/answer`,
4246
4389
  JSON.stringify({ session: sessionId, request: requestId, approve: decision.approve, ...decision.approve || decision.message === void 0 ? {} : { text: decision.message } }),
4247
4390
  (status, message) => `answering control request ${requestId} on ${sessionId} failed (HTTP ${String(status)}): ${message}`
4248
- )
4391
+ ),
4392
+ interruptSession: async (sessionId) => await writeOf(`${CONTROL_PATH_PREFIX}/interrupt`, JSON.stringify({ session: sessionId }), (status, message) => `interrupting ${sessionId} failed (HTTP ${String(status)}): ${message}`),
4393
+ 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}`),
4394
+ 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}`)
4249
4395
  };
4250
4396
  }
4251
4397
 
@@ -4405,7 +4551,7 @@ function ensureFrontDoor(params) {
4405
4551
  ports.stopSupervisor(supervisorPid);
4406
4552
  while (ports.isRunning(supervisorPid)) {
4407
4553
  if (ports.now() >= deadline) {
4408
- throw new FrontDoorStartError(`agent-shim: the front door (pid ${String(supervisorPid)}, protocol ${String(state.protocol ?? 0)}) is older than this launcher (protocol ${String(FRONT_DOOR_PROTOCOL)}) and did not exit when asked. Daemon log: ${paths.frontdoorLogPath}`);
4554
+ throw new FrontDoorStartError(`the front door (pid ${String(supervisorPid)}, protocol ${String(state.protocol ?? 0)}) is older than this launcher (protocol ${String(FRONT_DOOR_PROTOCOL)}) and did not exit when asked. Daemon log: ${paths.frontdoorLogPath}`);
4409
4555
  }
4410
4556
  ports.sleep(FRONTDOOR_LAUNCHER_POLL_MS);
4411
4557
  }
@@ -4419,13 +4565,13 @@ function ensureFrontDoor(params) {
4419
4565
  }
4420
4566
  lastRejection = `the listener on 127.0.0.1:${String(state.port)} (recorded for pid ${String(supervisorPid)}) failed authentication: ${verdict.reason}`;
4421
4567
  if (spawned) {
4422
- throw new FrontDoorStartError(`agent-shim: ${lastRejection}; refusing to send this session's credentials to it. Daemon log: ${paths.frontdoorLogPath}`);
4568
+ throw new FrontDoorStartError(`${lastRejection}; refusing to send this session's credentials to it. Daemon log: ${paths.frontdoorLogPath}`);
4423
4569
  }
4424
4570
  distrusted.add(supervisorPid);
4425
4571
  continue;
4426
4572
  }
4427
4573
  if (state?.lastError !== void 0) {
4428
- throw new FrontDoorStartError(`agent-shim: the front door reported a fatal error and is not serving: ${state.lastError} (daemon log: ${paths.frontdoorLogPath})`);
4574
+ throw new FrontDoorStartError(`the front door reported a fatal error and is not serving: ${state.lastError} (daemon log: ${paths.frontdoorLogPath})`);
4429
4575
  }
4430
4576
  } else if (!spawned) {
4431
4577
  ports.fs.mkdirp(paths.frontdoorDir);
@@ -4445,7 +4591,7 @@ function ensureFrontDoor(params) {
4445
4591
  if (ports.now() >= deadline) {
4446
4592
  const lastError = state?.lastError;
4447
4593
  throw new FrontDoorStartError(
4448
- `agent-shim: the front door did not become ready within ${String(FRONTDOOR_START_TIMEOUT_MS)}ms` + (lastError === void 0 ? "" : ` (last error: ${lastError})`) + (lastRejection === void 0 ? "" : ` (${lastRejection})`) + `. Daemon log: ${paths.frontdoorLogPath}`
4594
+ `the front door did not become ready within ${String(FRONTDOOR_START_TIMEOUT_MS)}ms` + (lastError === void 0 ? "" : ` (last error: ${lastError})`) + (lastRejection === void 0 ? "" : ` (${lastRejection})`) + `. Daemon log: ${paths.frontdoorLogPath}`
4449
4595
  );
4450
4596
  }
4451
4597
  ports.sleep(FRONTDOOR_LAUNCHER_POLL_MS);
@@ -4784,14 +4930,14 @@ function ensureHeadroom(params) {
4784
4930
  const holders = sessionsForSupervisor(ports.fs, paths.headroomSessionsDir, state.supervisorPid).filter((session) => ports.isRunning(session.pid));
4785
4931
  if (holders.length > 0) {
4786
4932
  throw new HeadroomStartError(
4787
- `agent-shim: the running headroom daemon was started before this launch's front door address and would refuse every request this launch sends through it. It restarts once the launches using it end (pid ${holders.map((session) => String(session.pid)).join(", ")}); end them and launch again, or launch with --no-headroom.`
4933
+ `the running headroom daemon was started before this launch's front door address and would refuse every request this launch sends through it. It restarts once the launches using it end (pid ${holders.map((session) => String(session.pid)).join(", ")}); end them and launch again, or launch with --no-headroom.`
4788
4934
  );
4789
4935
  }
4790
4936
  awaitingAllowlist = true;
4791
4937
  }
4792
4938
  if (state.lastError !== void 0) {
4793
4939
  throw new HeadroomStartError(
4794
- `agent-shim: the headroom daemon reported a fatal error and is not serving: ${state.lastError} (daemon log: ${paths.headroomLogPath})`
4940
+ `the headroom daemon reported a fatal error and is not serving: ${state.lastError} (daemon log: ${paths.headroomLogPath})`
4795
4941
  );
4796
4942
  }
4797
4943
  } else if (!spawnedSupervisor) {
@@ -4815,7 +4961,7 @@ function ensureHeadroom(params) {
4815
4961
  if (ports.now() >= deadline) {
4816
4962
  const lastError = state?.lastError;
4817
4963
  throw new HeadroomStartError(
4818
- `agent-shim: the headroom daemon did not become ready within ${String(HEADROOM_START_TIMEOUT_MS)}ms` + (lastError === void 0 ? "" : ` (last error: ${lastError})`) + (awaitingAllowlist ? " (the running daemon's allowlist does not admit this launch's front door, and it did not restart)" : "") + `. Daemon log: ${paths.headroomLogPath}`
4964
+ `the headroom daemon did not become ready within ${String(HEADROOM_START_TIMEOUT_MS)}ms` + (lastError === void 0 ? "" : ` (last error: ${lastError})`) + (awaitingAllowlist ? " (the running daemon's allowlist does not admit this launch's front door, and it did not restart)" : "") + `. Daemon log: ${paths.headroomLogPath}`
4819
4965
  );
4820
4966
  }
4821
4967
  ports.sleep(HEADROOM_LAUNCHER_POLL_MS);
@@ -9194,6 +9340,7 @@ export {
9194
9340
  ProviderSchema,
9195
9341
  RC_IDLE_EXPIRY_MS,
9196
9342
  RC_PENDING_DEADLINE_MS,
9343
+ RC_PERMISSION_MODES,
9197
9344
  RC_REQUEST_PARSE_CAP_BYTES,
9198
9345
  RC_SESSIONS_PATH_PREFIX,
9199
9346
  ROUTED_PATH_PREFIX,
@@ -9210,6 +9357,9 @@ export {
9210
9357
  buildLayoutPaths,
9211
9358
  buildRcControlResponsePayload,
9212
9359
  buildRcEventWriteBody,
9360
+ buildRcInterruptPayload,
9361
+ buildRcSetModelPayload,
9362
+ buildRcSetPermissionModePayload,
9213
9363
  buildRcUserMessagePayload,
9214
9364
  carryOver,
9215
9365
  checkReportHasWarnings,
@@ -9234,8 +9384,10 @@ export {
9234
9384
  generateCa,
9235
9385
  identityExists,
9236
9386
  injectRcUserMessage,
9387
+ interruptRcSession,
9237
9388
  isIdentityDirectoryName,
9238
9389
  isInterceptedHost,
9390
+ isRcPermissionMode,
9239
9391
  listDirectoryRules,
9240
9392
  listIdentities,
9241
9393
  listProfiles,
@@ -9291,6 +9443,8 @@ export {
9291
9443
  setProfileEntries,
9292
9444
  setProfileLaunchFlags,
9293
9445
  setProfileMetadata,
9446
+ setRcSessionModel,
9447
+ setRcSessionPermissionMode,
9294
9448
  snapshotPath,
9295
9449
  startConnectServer,
9296
9450
  topLevelNames,