agent-shim 8.5.1 → 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/cli.cjs +770 -734
- package/dist/index.cjs +71 -24
- package/dist/index.mjs +71 -24
- package/dist/types/index.d.ts +9 -3
- package/package.json +1 -1
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
4946
|
-
|
|
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);
|
|
@@ -6987,18 +7012,14 @@ function discoverClaudeBinary(options) {
|
|
|
6987
7012
|
version: highestVersion
|
|
6988
7013
|
};
|
|
6989
7014
|
}
|
|
6990
|
-
const ownInstallDirs = new Set(options.ownInstallDirs.map((dir) => import_node_path20.default.resolve(dir)));
|
|
6991
7015
|
for (const dir of options.pathDirs) {
|
|
6992
|
-
if (ownInstallDirs.has(import_node_path20.default.resolve(dir))) {
|
|
6993
|
-
continue;
|
|
6994
|
-
}
|
|
6995
7016
|
const found = options.findExecutableInDir(dir, "claude");
|
|
6996
|
-
if (found !== void 0) {
|
|
7017
|
+
if (found !== void 0 && !options.isOwnBinary(found)) {
|
|
6997
7018
|
return { path: found, source: "path-fallback" };
|
|
6998
7019
|
}
|
|
6999
7020
|
}
|
|
7000
7021
|
throw new Error(
|
|
7001
|
-
`Could not find a claude binary. Looked for genuinely-executable versioned binaries under "${versionsDir}" (missing, empty, or containing nothing executable) and for a "claude" executable on PATH (
|
|
7022
|
+
`Could not find a claude binary. Looked for genuinely-executable versioned binaries under "${versionsDir}" (missing, empty, or containing nothing executable) and for a "claude" executable on PATH (never this tool's own binary). Install Claude Code, or set the versions directory / PATH correctly, then try again.`
|
|
7002
7023
|
);
|
|
7003
7024
|
}
|
|
7004
7025
|
|
|
@@ -7274,10 +7295,11 @@ var realHeadroomSocketTrust = {
|
|
|
7274
7295
|
};
|
|
7275
7296
|
function realHeadroomPort(paths, options) {
|
|
7276
7297
|
return {
|
|
7277
|
-
ensure: () => {
|
|
7298
|
+
ensure: ({ routesProvider }) => {
|
|
7278
7299
|
const up = ensureHeadroom({
|
|
7279
7300
|
paths,
|
|
7280
7301
|
launcherPid: process.pid,
|
|
7302
|
+
...routesProvider ? { requiredAllowlistHash: () => hashAllowlist(currentHeadroomAllowlist(realFarmFs, paths)) } : {},
|
|
7281
7303
|
ports: {
|
|
7282
7304
|
fs: realFarmFs,
|
|
7283
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.
|
|
@@ -7396,12 +7418,18 @@ function realOwnExecutablePath() {
|
|
|
7396
7418
|
findExecutableInDir
|
|
7397
7419
|
});
|
|
7398
7420
|
}
|
|
7399
|
-
function
|
|
7421
|
+
function resolveContentSourcePath(env) {
|
|
7422
|
+
return env.isSea ? env.execPath : env.argv1 ?? env.execPath;
|
|
7423
|
+
}
|
|
7424
|
+
function realContentSourcePath() {
|
|
7425
|
+
return resolveContentSourcePath({ isSea: (0, import_node_sea.isSea)(), execPath: process.execPath, argv1: process.argv[1] });
|
|
7426
|
+
}
|
|
7427
|
+
function realResolveClaudeBinary(isOwnBinary) {
|
|
7400
7428
|
return () => discoverClaudeBinary({
|
|
7401
7429
|
listVersionsDir,
|
|
7402
7430
|
pathDirs: (process.env.PATH ?? "").split(import_node_path21.default.delimiter).filter((dir) => dir !== ""),
|
|
7403
7431
|
findExecutableInDir,
|
|
7404
|
-
|
|
7432
|
+
isOwnBinary
|
|
7405
7433
|
});
|
|
7406
7434
|
}
|
|
7407
7435
|
|
|
@@ -7817,13 +7845,32 @@ var ClaudeShimStateSchema = import_zod11.z.strictObject({
|
|
|
7817
7845
|
function commandFilename(ownExecutablePath, commandName) {
|
|
7818
7846
|
return ownExecutablePath.toLowerCase().endsWith(".exe") ? `${commandName}.exe` : commandName;
|
|
7819
7847
|
}
|
|
7820
|
-
function
|
|
7821
|
-
const dirs = [import_node_path23.default.dirname(ownExecutablePath)];
|
|
7848
|
+
function resolveOwnBinaryCheck(paths, contentSourcePath) {
|
|
7822
7849
|
const state = readJson(paths.claudeShimFile, ClaudeShimStateSchema);
|
|
7823
|
-
|
|
7824
|
-
|
|
7850
|
+
const recordedTarget = state === void 0 ? void 0 : import_node_path23.default.resolve(state.targetPath);
|
|
7851
|
+
let ownStat;
|
|
7852
|
+
return (candidate) => {
|
|
7853
|
+
if (recordedTarget !== void 0 && import_node_path23.default.resolve(candidate) === recordedTarget) {
|
|
7854
|
+
return true;
|
|
7855
|
+
}
|
|
7856
|
+
ownStat ??= statOrUndefined(contentSourcePath);
|
|
7857
|
+
const candidateStat = statOrUndefined(candidate);
|
|
7858
|
+
return ownStat !== void 0 && candidateStat !== void 0 && // A platform reporting no inode number for either file must not read every candidate as this tool.
|
|
7859
|
+
ownStat.ino !== 0 && candidateStat.ino !== 0 && candidateStat.dev === ownStat.dev && candidateStat.ino === ownStat.ino;
|
|
7860
|
+
};
|
|
7861
|
+
}
|
|
7862
|
+
function isEnoent4(error) {
|
|
7863
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
7864
|
+
}
|
|
7865
|
+
function statOrUndefined(target) {
|
|
7866
|
+
try {
|
|
7867
|
+
return import_node_fs7.default.statSync(target);
|
|
7868
|
+
} catch (error) {
|
|
7869
|
+
if (isEnoent4(error)) {
|
|
7870
|
+
return void 0;
|
|
7871
|
+
}
|
|
7872
|
+
throw error;
|
|
7825
7873
|
}
|
|
7826
|
-
return dirs;
|
|
7827
7874
|
}
|
|
7828
7875
|
var nodeLinkFs = { link: import_node_fs7.default.linkSync, copyFile: import_node_fs7.default.copyFileSync, chmod: import_node_fs7.default.chmodSync };
|
|
7829
7876
|
function findPathShadow(params) {
|
|
@@ -8264,7 +8311,7 @@ function collectDoctorReport(params) {
|
|
|
8264
8311
|
const ownExecutablePath = realOwnExecutablePath();
|
|
8265
8312
|
let binaryDiscovery;
|
|
8266
8313
|
try {
|
|
8267
|
-
const binary = realResolveClaudeBinary(
|
|
8314
|
+
const binary = realResolveClaudeBinary(resolveOwnBinaryCheck(paths, realContentSourcePath()))();
|
|
8268
8315
|
binaryDiscovery = { ok: true, binary };
|
|
8269
8316
|
} catch (error) {
|
|
8270
8317
|
binaryDiscovery = { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
@@ -8936,7 +8983,7 @@ function prepareLaunch(params) {
|
|
|
8936
8983
|
let headroom;
|
|
8937
8984
|
const headroomPort = params.headroom;
|
|
8938
8985
|
if (resolvedFlags.headroom && headroomPort !== void 0) {
|
|
8939
|
-
headroom = headroomPort.ensure();
|
|
8986
|
+
headroom = headroomPort.ensure({ routesProvider: resolvedProvider !== void 0 });
|
|
8940
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}`;
|
|
8941
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})`);
|
|
8942
8989
|
} else if (resolvedFlags.headroom) {
|
|
@@ -9240,7 +9287,7 @@ function realPrepareLaunchParams(paths, options) {
|
|
|
9240
9287
|
fs: realFsPort,
|
|
9241
9288
|
proc: options.proc,
|
|
9242
9289
|
log: options.log,
|
|
9243
|
-
resolveClaudeBinary: realResolveClaudeBinary(
|
|
9290
|
+
resolveClaudeBinary: realResolveClaudeBinary(resolveOwnBinaryCheck(paths, realContentSourcePath())),
|
|
9244
9291
|
farm: farm.runtime,
|
|
9245
9292
|
headroom: realHeadroomPort(paths, { spawnDaemon: options.spawnDaemon, cwd: farm.runtime.cwd }),
|
|
9246
9293
|
frontdoor: realFrontDoorPort(paths, options.spawnDaemon),
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
4762
|
-
|
|
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);
|
|
@@ -6803,18 +6828,14 @@ function discoverClaudeBinary(options) {
|
|
|
6803
6828
|
version: highestVersion
|
|
6804
6829
|
};
|
|
6805
6830
|
}
|
|
6806
|
-
const ownInstallDirs = new Set(options.ownInstallDirs.map((dir) => path20.resolve(dir)));
|
|
6807
6831
|
for (const dir of options.pathDirs) {
|
|
6808
|
-
if (ownInstallDirs.has(path20.resolve(dir))) {
|
|
6809
|
-
continue;
|
|
6810
|
-
}
|
|
6811
6832
|
const found = options.findExecutableInDir(dir, "claude");
|
|
6812
|
-
if (found !== void 0) {
|
|
6833
|
+
if (found !== void 0 && !options.isOwnBinary(found)) {
|
|
6813
6834
|
return { path: found, source: "path-fallback" };
|
|
6814
6835
|
}
|
|
6815
6836
|
}
|
|
6816
6837
|
throw new Error(
|
|
6817
|
-
`Could not find a claude binary. Looked for genuinely-executable versioned binaries under "${versionsDir}" (missing, empty, or containing nothing executable) and for a "claude" executable on PATH (
|
|
6838
|
+
`Could not find a claude binary. Looked for genuinely-executable versioned binaries under "${versionsDir}" (missing, empty, or containing nothing executable) and for a "claude" executable on PATH (never this tool's own binary). Install Claude Code, or set the versions directory / PATH correctly, then try again.`
|
|
6818
6839
|
);
|
|
6819
6840
|
}
|
|
6820
6841
|
|
|
@@ -7090,10 +7111,11 @@ var realHeadroomSocketTrust = {
|
|
|
7090
7111
|
};
|
|
7091
7112
|
function realHeadroomPort(paths, options) {
|
|
7092
7113
|
return {
|
|
7093
|
-
ensure: () => {
|
|
7114
|
+
ensure: ({ routesProvider }) => {
|
|
7094
7115
|
const up = ensureHeadroom({
|
|
7095
7116
|
paths,
|
|
7096
7117
|
launcherPid: process.pid,
|
|
7118
|
+
...routesProvider ? { requiredAllowlistHash: () => hashAllowlist(currentHeadroomAllowlist(realFarmFs, paths)) } : {},
|
|
7097
7119
|
ports: {
|
|
7098
7120
|
fs: realFarmFs,
|
|
7099
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.
|
|
@@ -7212,12 +7234,18 @@ function realOwnExecutablePath() {
|
|
|
7212
7234
|
findExecutableInDir
|
|
7213
7235
|
});
|
|
7214
7236
|
}
|
|
7215
|
-
function
|
|
7237
|
+
function resolveContentSourcePath(env) {
|
|
7238
|
+
return env.isSea ? env.execPath : env.argv1 ?? env.execPath;
|
|
7239
|
+
}
|
|
7240
|
+
function realContentSourcePath() {
|
|
7241
|
+
return resolveContentSourcePath({ isSea: isSea(), execPath: process.execPath, argv1: process.argv[1] });
|
|
7242
|
+
}
|
|
7243
|
+
function realResolveClaudeBinary(isOwnBinary) {
|
|
7216
7244
|
return () => discoverClaudeBinary({
|
|
7217
7245
|
listVersionsDir,
|
|
7218
7246
|
pathDirs: (process.env.PATH ?? "").split(path21.delimiter).filter((dir) => dir !== ""),
|
|
7219
7247
|
findExecutableInDir,
|
|
7220
|
-
|
|
7248
|
+
isOwnBinary
|
|
7221
7249
|
});
|
|
7222
7250
|
}
|
|
7223
7251
|
|
|
@@ -7633,13 +7661,32 @@ var ClaudeShimStateSchema = z11.strictObject({
|
|
|
7633
7661
|
function commandFilename(ownExecutablePath, commandName) {
|
|
7634
7662
|
return ownExecutablePath.toLowerCase().endsWith(".exe") ? `${commandName}.exe` : commandName;
|
|
7635
7663
|
}
|
|
7636
|
-
function
|
|
7637
|
-
const dirs = [path23.dirname(ownExecutablePath)];
|
|
7664
|
+
function resolveOwnBinaryCheck(paths, contentSourcePath) {
|
|
7638
7665
|
const state = readJson(paths.claudeShimFile, ClaudeShimStateSchema);
|
|
7639
|
-
|
|
7640
|
-
|
|
7666
|
+
const recordedTarget = state === void 0 ? void 0 : path23.resolve(state.targetPath);
|
|
7667
|
+
let ownStat;
|
|
7668
|
+
return (candidate) => {
|
|
7669
|
+
if (recordedTarget !== void 0 && path23.resolve(candidate) === recordedTarget) {
|
|
7670
|
+
return true;
|
|
7671
|
+
}
|
|
7672
|
+
ownStat ??= statOrUndefined(contentSourcePath);
|
|
7673
|
+
const candidateStat = statOrUndefined(candidate);
|
|
7674
|
+
return ownStat !== void 0 && candidateStat !== void 0 && // A platform reporting no inode number for either file must not read every candidate as this tool.
|
|
7675
|
+
ownStat.ino !== 0 && candidateStat.ino !== 0 && candidateStat.dev === ownStat.dev && candidateStat.ino === ownStat.ino;
|
|
7676
|
+
};
|
|
7677
|
+
}
|
|
7678
|
+
function isEnoent4(error) {
|
|
7679
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
7680
|
+
}
|
|
7681
|
+
function statOrUndefined(target) {
|
|
7682
|
+
try {
|
|
7683
|
+
return fs8.statSync(target);
|
|
7684
|
+
} catch (error) {
|
|
7685
|
+
if (isEnoent4(error)) {
|
|
7686
|
+
return void 0;
|
|
7687
|
+
}
|
|
7688
|
+
throw error;
|
|
7641
7689
|
}
|
|
7642
|
-
return dirs;
|
|
7643
7690
|
}
|
|
7644
7691
|
var nodeLinkFs = { link: fs8.linkSync, copyFile: fs8.copyFileSync, chmod: fs8.chmodSync };
|
|
7645
7692
|
function findPathShadow(params) {
|
|
@@ -8080,7 +8127,7 @@ function collectDoctorReport(params) {
|
|
|
8080
8127
|
const ownExecutablePath = realOwnExecutablePath();
|
|
8081
8128
|
let binaryDiscovery;
|
|
8082
8129
|
try {
|
|
8083
|
-
const binary = realResolveClaudeBinary(
|
|
8130
|
+
const binary = realResolveClaudeBinary(resolveOwnBinaryCheck(paths, realContentSourcePath()))();
|
|
8084
8131
|
binaryDiscovery = { ok: true, binary };
|
|
8085
8132
|
} catch (error) {
|
|
8086
8133
|
binaryDiscovery = { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
@@ -8752,7 +8799,7 @@ function prepareLaunch(params) {
|
|
|
8752
8799
|
let headroom;
|
|
8753
8800
|
const headroomPort = params.headroom;
|
|
8754
8801
|
if (resolvedFlags.headroom && headroomPort !== void 0) {
|
|
8755
|
-
headroom = headroomPort.ensure();
|
|
8802
|
+
headroom = headroomPort.ensure({ routesProvider: resolvedProvider !== void 0 });
|
|
8756
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}`;
|
|
8757
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})`);
|
|
8758
8805
|
} else if (resolvedFlags.headroom) {
|
|
@@ -9056,7 +9103,7 @@ function realPrepareLaunchParams(paths, options) {
|
|
|
9056
9103
|
fs: realFsPort,
|
|
9057
9104
|
proc: options.proc,
|
|
9058
9105
|
log: options.log,
|
|
9059
|
-
resolveClaudeBinary: realResolveClaudeBinary(
|
|
9106
|
+
resolveClaudeBinary: realResolveClaudeBinary(resolveOwnBinaryCheck(paths, realContentSourcePath())),
|
|
9060
9107
|
farm: farm.runtime,
|
|
9061
9108
|
headroom: realHeadroomPort(paths, { spawnDaemon: options.spawnDaemon, cwd: farm.runtime.cwd }),
|
|
9062
9109
|
frontdoor: realFrontDoorPort(paths, options.spawnDaemon),
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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: (
|
|
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.
|
|
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>",
|