agent-shim 8.5.2 → 8.5.3

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
@@ -2732,7 +2732,7 @@ function readFrontDoorState(fs12, stateFile) {
2732
2732
  }
2733
2733
  function writeFrontDoorState(fs12, stateFile, state) {
2734
2734
  fs12.mkdirp(import_node_path7.default.dirname(stateFile));
2735
- fs12.writeFileUtf8(stateFile, `${JSON.stringify(state, null, 2)}
2735
+ fs12.writeFilePrivate(stateFile, `${JSON.stringify(state, null, 2)}
2736
2736
  `);
2737
2737
  }
2738
2738
  function frontDoorOrigin(state) {
@@ -3629,6 +3629,11 @@ function singleHeader(headers, name) {
3629
3629
  const value = headers[name];
3630
3630
  return typeof value === "string" ? value : void 0;
3631
3631
  }
3632
+ var OAUTH_AUTHORIZATION_PREFIX = "Bearer sk-ant-oat";
3633
+ function oauthAuthorizationOf(headers) {
3634
+ const value = singleHeader(headers, "authorization");
3635
+ return value?.startsWith(OAUTH_AUTHORIZATION_PREFIX) === true ? value : void 0;
3636
+ }
3632
3637
  function sessionCreateId(body) {
3633
3638
  let parsed;
3634
3639
  try {
@@ -3772,7 +3777,7 @@ function createRcSessionTracker(deps) {
3772
3777
  }
3773
3778
  const tail = path29.slice(RC_SESSIONS_PATH_PREFIX.length);
3774
3779
  const facts = {
3775
- authorization: singleHeader(request4.headers, "authorization"),
3780
+ oauthAuthorization: oauthAuthorizationOf(request4.headers),
3776
3781
  anthropicVersion: singleHeader(request4.headers, "anthropic-version"),
3777
3782
  anthropicClientPlatform: singleHeader(request4.headers, "anthropic-client-platform")
3778
3783
  };
@@ -3814,7 +3819,7 @@ function createRcSessionTracker(deps) {
3814
3819
  }
3815
3820
  const entry = entries.get(id) ?? birth(id, facts);
3816
3821
  entry.lastSeenAt = deps.now();
3817
- entry.authorization = facts.authorization ?? entry.authorization;
3822
+ entry.oauthAuthorization = facts.oauthAuthorization ?? entry.oauthAuthorization;
3818
3823
  entry.anthropicVersion = facts.anthropicVersion ?? entry.anthropicVersion;
3819
3824
  entry.anthropicClientPlatform = facts.anthropicClientPlatform ?? entry.anthropicClientPlatform;
3820
3825
  const bodyKind = method === "POST" && rest === `${id}/worker/events` ? "worker-events" : method === "POST" && rest === `${id}/events` ? "client-events" : method === "POST" && rest === `${id}/worker/heartbeat` ? "heartbeat" : method === "PUT" && rest === `${id}/worker` ? "worker-registration" : void 0;
@@ -3902,7 +3907,7 @@ function createRcSessionTracker(deps) {
3902
3907
  },
3903
3908
  credentialOf: (sessionId) => {
3904
3909
  const entry = entries.get(sessionId);
3905
- return entry === void 0 ? void 0 : { authorization: entry.authorization, anthropicVersion: entry.anthropicVersion, anthropicClientPlatform: entry.anthropicClientPlatform };
3910
+ return entry === void 0 ? void 0 : { authorization: entry.oauthAuthorization, anthropicVersion: entry.anthropicVersion, anthropicClientPlatform: entry.anthropicClientPlatform };
3906
3911
  },
3907
3912
  completePending: (sessionId, requestId) => {
3908
3913
  entries.get(sessionId)?.pending.delete(requestId);
@@ -4025,6 +4030,9 @@ function rcEventWriteResultFromAnswer(answer) {
4025
4030
  }
4026
4031
  return { ok: true, sequenceNums };
4027
4032
  }
4033
+ function noOAuthCredentialResult(sessionId) {
4034
+ return { ok: false, message: `no claude.ai OAuth bearer has been observed for session ${sessionId}: only worker calls crossed this door, and the worker JWT they carry is answered 401 on the client half. Reconnect Remote Control through this door and the create's own bearer will be observed` };
4035
+ }
4028
4036
  function rcWriteHeaders(credential) {
4029
4037
  const headers = { "content-type": "application/json" };
4030
4038
  if (credential.authorization !== void 0) {
@@ -4043,6 +4051,9 @@ async function injectRcUserMessage(deps, sessionId, text) {
4043
4051
  if (credential === void 0) {
4044
4052
  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)` };
4045
4053
  }
4054
+ if (credential.authorization === void 0) {
4055
+ return noOAuthCredentialResult(sessionId);
4056
+ }
4046
4057
  const body = JSON.stringify(buildRcEventWriteBody(buildRcUserMessagePayload(deps.newUuid(), sessionId, text)));
4047
4058
  let answer;
4048
4059
  try {
@@ -4061,6 +4072,9 @@ async function answerRcControlRequest(deps, sessionId, requestId, decision) {
4061
4072
  if (credential === void 0) {
4062
4073
  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)` };
4063
4074
  }
4075
+ if (credential.authorization === void 0) {
4076
+ return noOAuthCredentialResult(sessionId);
4077
+ }
4064
4078
  if (!deps.pendingOf(sessionId).some((pending) => pending.requestId === requestId)) {
4065
4079
  return { ok: false, message: `the front door has not observed control request ${requestId} pending on session ${sessionId}: it was answered already (by this door or by an attached client), it passed its answer deadline (the protocol's permission round-trip bound), or it never passed through this door` };
4066
4080
  }
@@ -4937,13 +4951,24 @@ function ensureHeadroom(params) {
4937
4951
  const { paths, ports } = params;
4938
4952
  const deadline = ports.now() + HEADROOM_START_TIMEOUT_MS;
4939
4953
  let spawnedSupervisor = false;
4954
+ let awaitingAllowlist = false;
4940
4955
  for (; ; ) {
4941
4956
  const state = readHeadroomState(ports.fs, paths.headroomStateFile);
4942
4957
  if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
4943
4958
  const daemonUp = state.socketPath !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
4944
4959
  if (daemonUp && state.socketPath !== void 0) {
4945
- writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
4946
- return { socketPath: state.socketPath };
4960
+ const required = params.requiredAllowlistHash?.();
4961
+ if (required === void 0 || state.allowlistHash === required) {
4962
+ writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
4963
+ return { socketPath: state.socketPath };
4964
+ }
4965
+ const holders = sessionsForSupervisor(ports.fs, paths.headroomSessionsDir, state.supervisorPid).filter((session) => ports.isRunning(session.pid));
4966
+ if (holders.length > 0) {
4967
+ throw new HeadroomStartError(
4968
+ `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.`
4969
+ );
4970
+ }
4971
+ awaitingAllowlist = true;
4947
4972
  }
4948
4973
  if (state.lastError !== void 0) {
4949
4974
  throw new HeadroomStartError(
@@ -4971,7 +4996,7 @@ function ensureHeadroom(params) {
4971
4996
  if (ports.now() >= deadline) {
4972
4997
  const lastError = state?.lastError;
4973
4998
  throw new HeadroomStartError(
4974
- `agent-shim: the headroom daemon did not become ready within ${String(HEADROOM_START_TIMEOUT_MS)}ms` + (lastError === void 0 ? "" : ` (last error: ${lastError})`) + `. Daemon log: ${paths.headroomLogPath}`
4999
+ `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}`
4975
5000
  );
4976
5001
  }
4977
5002
  ports.sleep(HEADROOM_LAUNCHER_POLL_MS);
@@ -7270,10 +7295,11 @@ var realHeadroomSocketTrust = {
7270
7295
  };
7271
7296
  function realHeadroomPort(paths, options) {
7272
7297
  return {
7273
- ensure: () => {
7298
+ ensure: ({ routesProvider }) => {
7274
7299
  const up = ensureHeadroom({
7275
7300
  paths,
7276
7301
  launcherPid: process.pid,
7302
+ ...routesProvider ? { requiredAllowlistHash: () => hashAllowlist(currentHeadroomAllowlist(realFarmFs, paths)) } : {},
7277
7303
  ports: {
7278
7304
  fs: realFarmFs,
7279
7305
  // Zombie-aware on purpose: a supervisor that died while still a child of this launcher sits unreaped until the launcher itself exits, and a defunct supervisor answering signal 0 as alive would stretch every launch to the full start timeout.
@@ -8957,7 +8983,7 @@ function prepareLaunch(params) {
8957
8983
  let headroom;
8958
8984
  const headroomPort = params.headroom;
8959
8985
  if (resolvedFlags.headroom && headroomPort !== void 0) {
8960
- headroom = headroomPort.ensure();
8986
+ headroom = headroomPort.ensure({ routesProvider: resolvedProvider !== void 0 });
8961
8987
  const via = resolvedProvider === void 0 ? `OAuth via the door's CONNECT surface on 127.0.0.1:${String(frontDoor?.connectPort ?? 0)}` : `provider ${resolvedProvider.name}`;
8962
8988
  log.info(`agent-shim: routing through the front door on 127.0.0.1:${String(frontDoor?.port ?? 0)} with headroom on unix socket ${headroom.socketPath} (${via}, project ${headroom.projectId})`);
8963
8989
  } else if (resolvedFlags.headroom) {
package/dist/index.mjs CHANGED
@@ -2548,7 +2548,7 @@ function readFrontDoorState(fs12, stateFile) {
2548
2548
  }
2549
2549
  function writeFrontDoorState(fs12, stateFile, state) {
2550
2550
  fs12.mkdirp(path7.dirname(stateFile));
2551
- fs12.writeFileUtf8(stateFile, `${JSON.stringify(state, null, 2)}
2551
+ fs12.writeFilePrivate(stateFile, `${JSON.stringify(state, null, 2)}
2552
2552
  `);
2553
2553
  }
2554
2554
  function frontDoorOrigin(state) {
@@ -3445,6 +3445,11 @@ function singleHeader(headers, name) {
3445
3445
  const value = headers[name];
3446
3446
  return typeof value === "string" ? value : void 0;
3447
3447
  }
3448
+ var OAUTH_AUTHORIZATION_PREFIX = "Bearer sk-ant-oat";
3449
+ function oauthAuthorizationOf(headers) {
3450
+ const value = singleHeader(headers, "authorization");
3451
+ return value?.startsWith(OAUTH_AUTHORIZATION_PREFIX) === true ? value : void 0;
3452
+ }
3448
3453
  function sessionCreateId(body) {
3449
3454
  let parsed;
3450
3455
  try {
@@ -3588,7 +3593,7 @@ function createRcSessionTracker(deps) {
3588
3593
  }
3589
3594
  const tail = path29.slice(RC_SESSIONS_PATH_PREFIX.length);
3590
3595
  const facts = {
3591
- authorization: singleHeader(request4.headers, "authorization"),
3596
+ oauthAuthorization: oauthAuthorizationOf(request4.headers),
3592
3597
  anthropicVersion: singleHeader(request4.headers, "anthropic-version"),
3593
3598
  anthropicClientPlatform: singleHeader(request4.headers, "anthropic-client-platform")
3594
3599
  };
@@ -3630,7 +3635,7 @@ function createRcSessionTracker(deps) {
3630
3635
  }
3631
3636
  const entry = entries.get(id) ?? birth(id, facts);
3632
3637
  entry.lastSeenAt = deps.now();
3633
- entry.authorization = facts.authorization ?? entry.authorization;
3638
+ entry.oauthAuthorization = facts.oauthAuthorization ?? entry.oauthAuthorization;
3634
3639
  entry.anthropicVersion = facts.anthropicVersion ?? entry.anthropicVersion;
3635
3640
  entry.anthropicClientPlatform = facts.anthropicClientPlatform ?? entry.anthropicClientPlatform;
3636
3641
  const bodyKind = method === "POST" && rest === `${id}/worker/events` ? "worker-events" : method === "POST" && rest === `${id}/events` ? "client-events" : method === "POST" && rest === `${id}/worker/heartbeat` ? "heartbeat" : method === "PUT" && rest === `${id}/worker` ? "worker-registration" : void 0;
@@ -3718,7 +3723,7 @@ function createRcSessionTracker(deps) {
3718
3723
  },
3719
3724
  credentialOf: (sessionId) => {
3720
3725
  const entry = entries.get(sessionId);
3721
- return entry === void 0 ? void 0 : { authorization: entry.authorization, anthropicVersion: entry.anthropicVersion, anthropicClientPlatform: entry.anthropicClientPlatform };
3726
+ return entry === void 0 ? void 0 : { authorization: entry.oauthAuthorization, anthropicVersion: entry.anthropicVersion, anthropicClientPlatform: entry.anthropicClientPlatform };
3722
3727
  },
3723
3728
  completePending: (sessionId, requestId) => {
3724
3729
  entries.get(sessionId)?.pending.delete(requestId);
@@ -3841,6 +3846,9 @@ function rcEventWriteResultFromAnswer(answer) {
3841
3846
  }
3842
3847
  return { ok: true, sequenceNums };
3843
3848
  }
3849
+ function noOAuthCredentialResult(sessionId) {
3850
+ return { ok: false, message: `no claude.ai OAuth bearer has been observed for session ${sessionId}: only worker calls crossed this door, and the worker JWT they carry is answered 401 on the client half. Reconnect Remote Control through this door and the create's own bearer will be observed` };
3851
+ }
3844
3852
  function rcWriteHeaders(credential) {
3845
3853
  const headers = { "content-type": "application/json" };
3846
3854
  if (credential.authorization !== void 0) {
@@ -3859,6 +3867,9 @@ async function injectRcUserMessage(deps, sessionId, text) {
3859
3867
  if (credential === void 0) {
3860
3868
  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)` };
3861
3869
  }
3870
+ if (credential.authorization === void 0) {
3871
+ return noOAuthCredentialResult(sessionId);
3872
+ }
3862
3873
  const body = JSON.stringify(buildRcEventWriteBody(buildRcUserMessagePayload(deps.newUuid(), sessionId, text)));
3863
3874
  let answer;
3864
3875
  try {
@@ -3877,6 +3888,9 @@ async function answerRcControlRequest(deps, sessionId, requestId, decision) {
3877
3888
  if (credential === void 0) {
3878
3889
  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)` };
3879
3890
  }
3891
+ if (credential.authorization === void 0) {
3892
+ return noOAuthCredentialResult(sessionId);
3893
+ }
3880
3894
  if (!deps.pendingOf(sessionId).some((pending) => pending.requestId === requestId)) {
3881
3895
  return { ok: false, message: `the front door has not observed control request ${requestId} pending on session ${sessionId}: it was answered already (by this door or by an attached client), it passed its answer deadline (the protocol's permission round-trip bound), or it never passed through this door` };
3882
3896
  }
@@ -4753,13 +4767,24 @@ function ensureHeadroom(params) {
4753
4767
  const { paths, ports } = params;
4754
4768
  const deadline = ports.now() + HEADROOM_START_TIMEOUT_MS;
4755
4769
  let spawnedSupervisor = false;
4770
+ let awaitingAllowlist = false;
4756
4771
  for (; ; ) {
4757
4772
  const state = readHeadroomState(ports.fs, paths.headroomStateFile);
4758
4773
  if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
4759
4774
  const daemonUp = state.socketPath !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
4760
4775
  if (daemonUp && state.socketPath !== void 0) {
4761
- writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
4762
- return { socketPath: state.socketPath };
4776
+ const required = params.requiredAllowlistHash?.();
4777
+ if (required === void 0 || state.allowlistHash === required) {
4778
+ writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
4779
+ return { socketPath: state.socketPath };
4780
+ }
4781
+ const holders = sessionsForSupervisor(ports.fs, paths.headroomSessionsDir, state.supervisorPid).filter((session) => ports.isRunning(session.pid));
4782
+ if (holders.length > 0) {
4783
+ throw new HeadroomStartError(
4784
+ `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.`
4785
+ );
4786
+ }
4787
+ awaitingAllowlist = true;
4763
4788
  }
4764
4789
  if (state.lastError !== void 0) {
4765
4790
  throw new HeadroomStartError(
@@ -4787,7 +4812,7 @@ function ensureHeadroom(params) {
4787
4812
  if (ports.now() >= deadline) {
4788
4813
  const lastError = state?.lastError;
4789
4814
  throw new HeadroomStartError(
4790
- `agent-shim: the headroom daemon did not become ready within ${String(HEADROOM_START_TIMEOUT_MS)}ms` + (lastError === void 0 ? "" : ` (last error: ${lastError})`) + `. Daemon log: ${paths.headroomLogPath}`
4815
+ `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}`
4791
4816
  );
4792
4817
  }
4793
4818
  ports.sleep(HEADROOM_LAUNCHER_POLL_MS);
@@ -7086,10 +7111,11 @@ var realHeadroomSocketTrust = {
7086
7111
  };
7087
7112
  function realHeadroomPort(paths, options) {
7088
7113
  return {
7089
- ensure: () => {
7114
+ ensure: ({ routesProvider }) => {
7090
7115
  const up = ensureHeadroom({
7091
7116
  paths,
7092
7117
  launcherPid: process.pid,
7118
+ ...routesProvider ? { requiredAllowlistHash: () => hashAllowlist(currentHeadroomAllowlist(realFarmFs, paths)) } : {},
7093
7119
  ports: {
7094
7120
  fs: realFarmFs,
7095
7121
  // Zombie-aware on purpose: a supervisor that died while still a child of this launcher sits unreaped until the launcher itself exits, and a defunct supervisor answering signal 0 as alive would stretch every launch to the full start timeout.
@@ -8773,7 +8799,7 @@ function prepareLaunch(params) {
8773
8799
  let headroom;
8774
8800
  const headroomPort = params.headroom;
8775
8801
  if (resolvedFlags.headroom && headroomPort !== void 0) {
8776
- headroom = headroomPort.ensure();
8802
+ headroom = headroomPort.ensure({ routesProvider: resolvedProvider !== void 0 });
8777
8803
  const via = resolvedProvider === void 0 ? `OAuth via the door's CONNECT surface on 127.0.0.1:${String(frontDoor?.connectPort ?? 0)}` : `provider ${resolvedProvider.name}`;
8778
8804
  log.info(`agent-shim: routing through the front door on 127.0.0.1:${String(frontDoor?.port ?? 0)} with headroom on unix socket ${headroom.socketPath} (${via}, project ${headroom.projectId})`);
8779
8805
  } else if (resolvedFlags.headroom) {
@@ -1171,10 +1171,12 @@ interface HeadroomUp {
1171
1171
  /**
1172
1172
  * Everything the launcher needs from the headroom daemon, injected so its daemon-supervision side effects stay behind a port and the launcher's own logic stays pure over it (`src/realPorts.ts` wires the real one; tests wire fakes).
1173
1173
  *
1174
- * `ensure` brings the daemon up (spawning the detached supervisor when nothing healthy is running) and registers this launch in its session registry; `release` removes that registration when the spawned `claude` exits.
1174
+ * `ensure` brings the daemon up (spawning the detached supervisor when nothing healthy is running) and registers this launch in its session registry; `release` removes that registration when the spawned `claude` exits. `routesProvider` says the launch sends a provider's traffic through the daemon, which needs the daemon to have been started with the front door's current address on its allowlist: `ensure` then waits for a restart that brings it, or refuses when live sessions hold the stale daemon up, instead of registering against a daemon that would reject every request.
1175
1175
  */
1176
1176
  interface HeadroomPort {
1177
- readonly ensure: () => HeadroomUp;
1177
+ readonly ensure: (options: Readonly<{
1178
+ routesProvider: boolean;
1179
+ }>) => HeadroomUp;
1178
1180
  readonly release: () => void;
1179
1181
  }
1180
1182
  /**
@@ -1767,7 +1769,7 @@ declare function rcObserverAsPassthrough(observer: RcExchangeObserver): Passthro
1767
1769
  declare function buildRcUserMessagePayload(uuid: string, sessionId: string, text: string): Record<string, unknown>;
1768
1770
  /** Wraps payload event(s) in the write body the endpoint takes: `{"events": [{"payload": {...}}]}`. */
1769
1771
  declare function buildRcEventWriteBody(payload: Record<string, unknown>): Record<string, unknown>;
1770
- /** The headers a client-half write sends: the observed credential and protocol values, replayed verbatim, never invented. */
1772
+ /** The headers a client-half write sends: the observed OAuth-kind credential and protocol values, replayed verbatim, never invented. */
1771
1773
  interface RcWriteHeaders {
1772
1774
  readonly authorization: string | undefined;
1773
1775
  readonly anthropicVersion: string | undefined;
@@ -2115,6 +2117,10 @@ declare function ensureHeadroom(params: {
2115
2117
  readonly paths: LayoutPaths;
2116
2118
  readonly launcherPid: number;
2117
2119
  readonly ports: EnsureHeadroomPorts;
2120
+ /**
2121
+ * The hash of the allowlist the daemon must have been started with for this launch to work, read fresh on every poll because the front door's address it contains can change while the launch waits. Given for a launch that routes a provider through headroom, whose requests come back to the front door's direct origin and are refused by a daemon that was started without it; absent for a launch that only needs Claude Code's own API, which every daemon admits.
2122
+ */
2123
+ readonly requiredAllowlistHash?: () => string;
2118
2124
  }): {
2119
2125
  readonly socketPath: string;
2120
2126
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-shim",
3
- "version": "8.5.2",
3
+ "version": "8.5.3",
4
4
  "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.",
5
5
  "license": "MIT",
6
6
  "author": "Joseph Mearman <joseph@mearman.co.uk>",