agent-shim 8.2.0 → 8.3.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/README.md +2 -0
- package/dist/cli.cjs +1186 -631
- package/dist/index.cjs +592 -39
- package/dist/index.mjs +569 -30
- package/dist/types/index.d.ts +231 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -34,6 +34,7 @@ __export(index_exports, {
|
|
|
34
34
|
CONNECT_INTERCEPT_HOST: () => CONNECT_INTERCEPT_HOST,
|
|
35
35
|
CONNECT_INTERCEPT_HOSTS: () => CONNECT_INTERCEPT_HOSTS,
|
|
36
36
|
CONNECT_LIMITS: () => CONNECT_LIMITS,
|
|
37
|
+
CONTROL_PATH_PREFIX: () => CONTROL_PATH_PREFIX,
|
|
37
38
|
CategoryClassificationOverlaySchema: () => CategoryClassificationOverlaySchema,
|
|
38
39
|
CategoryClassificationSchema: () => CategoryClassificationSchema,
|
|
39
40
|
CategoryMapSchema: () => CategoryMapSchema,
|
|
@@ -72,6 +73,8 @@ __export(index_exports, {
|
|
|
72
73
|
ProviderKindMismatchError: () => ProviderKindMismatchError,
|
|
73
74
|
ProviderNotFoundError: () => ProviderNotFoundError,
|
|
74
75
|
ProviderSchema: () => ProviderSchema,
|
|
76
|
+
RC_IDLE_EXPIRY_MS: () => RC_IDLE_EXPIRY_MS,
|
|
77
|
+
RC_SESSIONS_PATH_PREFIX: () => RC_SESSIONS_PATH_PREFIX,
|
|
75
78
|
ROUTED_PATH_PREFIX: () => ROUTED_PATH_PREFIX,
|
|
76
79
|
UsageError: () => UsageError,
|
|
77
80
|
UsageSnapshotError: () => UsageSnapshotError,
|
|
@@ -83,6 +86,8 @@ __export(index_exports, {
|
|
|
83
86
|
addProvider: () => addProvider,
|
|
84
87
|
buildEntryFacts: () => buildEntryFacts,
|
|
85
88
|
buildLayoutPaths: () => buildLayoutPaths,
|
|
89
|
+
buildRcEventWriteBody: () => buildRcEventWriteBody,
|
|
90
|
+
buildRcUserMessagePayload: () => buildRcUserMessagePayload,
|
|
86
91
|
carryOver: () => carryOver,
|
|
87
92
|
checkReportHasWarnings: () => checkReportHasWarnings,
|
|
88
93
|
checkReportToJson: () => checkReportToJson,
|
|
@@ -90,6 +95,8 @@ __export(index_exports, {
|
|
|
90
95
|
collectDoctorReport: () => collectDoctorReport,
|
|
91
96
|
createLeafCache: () => createLeafCache,
|
|
92
97
|
createProfile: () => createProfile,
|
|
98
|
+
createRcControlHandler: () => createRcControlHandler,
|
|
99
|
+
createRcSessionTracker: () => createRcSessionTracker,
|
|
93
100
|
describeProviderEndpoint: () => describeProviderEndpoint,
|
|
94
101
|
detectAmbientCredential: () => detectAmbientCredential,
|
|
95
102
|
ensureCa: () => ensureCa,
|
|
@@ -100,8 +107,10 @@ __export(index_exports, {
|
|
|
100
107
|
formatCheckReport: () => formatCheckReport,
|
|
101
108
|
formatDoctorReport: () => formatDoctorReport,
|
|
102
109
|
forwardableHeaders: () => forwardableHeaders,
|
|
110
|
+
frontDoorRcControl: () => frontDoorRcControl,
|
|
103
111
|
generateCa: () => generateCa,
|
|
104
112
|
identityExists: () => identityExists,
|
|
113
|
+
injectRcUserMessage: () => injectRcUserMessage,
|
|
105
114
|
isIdentityDirectoryName: () => isIdentityDirectoryName,
|
|
106
115
|
isInterceptedHost: () => isInterceptedHost,
|
|
107
116
|
listDirectoryRules: () => listDirectoryRules,
|
|
@@ -110,11 +119,14 @@ __export(index_exports, {
|
|
|
110
119
|
listProviders: () => listProviders,
|
|
111
120
|
listUsageSnapshots: () => listUsageSnapshots,
|
|
112
121
|
mintLeaf: () => mintLeaf,
|
|
122
|
+
observingRoutedRoute: () => observingRoutedRoute,
|
|
113
123
|
parseConnectTarget: () => parseConnectTarget,
|
|
114
124
|
prepareClaudeLaunch: () => prepareClaudeLaunch,
|
|
115
125
|
prepareLaunch: () => prepareLaunch,
|
|
116
126
|
profileExists: () => profileExists,
|
|
117
127
|
providerExists: () => providerExists,
|
|
128
|
+
rcInjectResultFromAnswer: () => rcInjectResultFromAnswer,
|
|
129
|
+
rcObserverAsPassthrough: () => rcObserverAsPassthrough,
|
|
118
130
|
readActiveIdentity: () => readActiveIdentity,
|
|
119
131
|
readDirectoryRules: () => readDirectoryRules,
|
|
120
132
|
readFarmManifest: () => readFarmManifest,
|
|
@@ -126,6 +138,8 @@ __export(index_exports, {
|
|
|
126
138
|
readUsageSnapshot: () => readUsageSnapshot,
|
|
127
139
|
realConnectCertStore: () => realConnectCertStore,
|
|
128
140
|
realConnectEffects: () => realConnectEffects,
|
|
141
|
+
realRcControlTransport: () => realRcControlTransport,
|
|
142
|
+
realRcEventDial: () => realRcEventDial,
|
|
129
143
|
recoverFarm: () => recoverFarm,
|
|
130
144
|
recoveryDiagnostics: () => recoveryDiagnostics,
|
|
131
145
|
removeDirectoryRule: () => removeDirectoryRule,
|
|
@@ -2289,7 +2303,7 @@ function recoveryDiagnostics(recovery, identity) {
|
|
|
2289
2303
|
}
|
|
2290
2304
|
|
|
2291
2305
|
// src/frontdoor/connect.ts
|
|
2292
|
-
var
|
|
2306
|
+
var import_node_crypto5 = require("node:crypto");
|
|
2293
2307
|
var fs2 = __toESM(require("node:fs"), 1);
|
|
2294
2308
|
|
|
2295
2309
|
// src/frontdoor/x509.ts
|
|
@@ -2615,6 +2629,7 @@ function pumpTapSession(secure, openUpstream, tap, prefix = Buffer.alloc(0)) {
|
|
|
2615
2629
|
}
|
|
2616
2630
|
|
|
2617
2631
|
// src/frontdoor/capability.ts
|
|
2632
|
+
var import_node_crypto4 = require("node:crypto");
|
|
2618
2633
|
var CONNECT_PROXY_USER = "agent-shim";
|
|
2619
2634
|
var CONNECT_PROXY_REALM = "agent-shim front door";
|
|
2620
2635
|
var BASIC_SCHEME = "basic";
|
|
@@ -2635,6 +2650,18 @@ function capabilityFromProxyAuthorization(value) {
|
|
|
2635
2650
|
const password = userPass.slice(colon + 1);
|
|
2636
2651
|
return password === "" ? void 0 : password;
|
|
2637
2652
|
}
|
|
2653
|
+
function digest(token) {
|
|
2654
|
+
return (0, import_node_crypto4.createHash)("sha256").update(token, "utf8").digest();
|
|
2655
|
+
}
|
|
2656
|
+
function isLiveCapability(presented, live) {
|
|
2657
|
+
const presentedDigest = digest(presented);
|
|
2658
|
+
let matched = false;
|
|
2659
|
+
for (const token of live) {
|
|
2660
|
+
const equal = (0, import_node_crypto4.timingSafeEqual)(presentedDigest, digest(token));
|
|
2661
|
+
matched = equal || matched;
|
|
2662
|
+
}
|
|
2663
|
+
return matched;
|
|
2664
|
+
}
|
|
2638
2665
|
|
|
2639
2666
|
// src/frontdoor/route.ts
|
|
2640
2667
|
var IDENTITY_HEADER = "x-agent-shim-identity";
|
|
@@ -2935,8 +2962,8 @@ function ensureCa(store, generate) {
|
|
|
2935
2962
|
}
|
|
2936
2963
|
function caParses(ca) {
|
|
2937
2964
|
try {
|
|
2938
|
-
new
|
|
2939
|
-
(0,
|
|
2965
|
+
new import_node_crypto5.X509Certificate(ca.certPem);
|
|
2966
|
+
(0, import_node_crypto5.createPrivateKey)(ca.keyPem);
|
|
2940
2967
|
return true;
|
|
2941
2968
|
} catch {
|
|
2942
2969
|
return false;
|
|
@@ -2945,14 +2972,14 @@ function caParses(ca) {
|
|
|
2945
2972
|
var TAP_ALPN_PROTOCOLS = ["h2", "http/1.1"];
|
|
2946
2973
|
async function startConnectServer(config, effects, preferredPort) {
|
|
2947
2974
|
const tunnelTokens = /* @__PURE__ */ new WeakMap();
|
|
2948
|
-
const onUpgrade = (host) => (
|
|
2949
|
-
config.capture?.upgrade(
|
|
2975
|
+
const onUpgrade = (host) => (request4, socket, head) => {
|
|
2976
|
+
config.capture?.upgrade(request4);
|
|
2950
2977
|
if (head.length > 0) {
|
|
2951
2978
|
socket.pause();
|
|
2952
2979
|
socket.unshift(head);
|
|
2953
2980
|
}
|
|
2954
|
-
const headText = `${
|
|
2955
|
-
${
|
|
2981
|
+
const headText = `${request4.method ?? "GET"} ${request4.url ?? "/"} HTTP/1.1\r
|
|
2982
|
+
${request4.rawHeaders.reduce((accumulated, value, index) => index % 2 === 0 ? `${accumulated}${value}: ` : `${accumulated}${value}\r
|
|
2956
2983
|
`, "")}\r
|
|
2957
2984
|
`;
|
|
2958
2985
|
pumpTapSession(socket, async (clientAlpn) => await effects.connectTlsUpstream(host, HTTPS_PORT, clientAlpn), config.capture?.tapStream?.(host), Buffer.from(headText, "utf8"));
|
|
@@ -2965,16 +2992,16 @@ ${request3.rawHeaders.reduce((accumulated, value, index) => index % 2 === 0 ? `$
|
|
|
2965
2992
|
const tlsAcceptor2 = effects.createTlsAcceptor(config.leafFor(host), serveSecure2, TAP_ALPN_PROTOCOLS);
|
|
2966
2993
|
return { host, serveSecure: serveSecure2, tlsAcceptor: tlsAcceptor2 };
|
|
2967
2994
|
}
|
|
2968
|
-
const handler = (
|
|
2969
|
-
if (host === config.routedHost && servedByPipeline(
|
|
2970
|
-
const tunnelToken = tunnelTokens.get(
|
|
2971
|
-
if (tunnelToken !== void 0 &&
|
|
2972
|
-
|
|
2995
|
+
const handler = (request4, response) => {
|
|
2996
|
+
if (host === config.routedHost && servedByPipeline(request4.url)) {
|
|
2997
|
+
const tunnelToken = tunnelTokens.get(request4.socket);
|
|
2998
|
+
if (tunnelToken !== void 0 && request4.headers[AUTH_HEADER] === void 0) {
|
|
2999
|
+
request4.headers[AUTH_HEADER] = tunnelToken;
|
|
2973
3000
|
}
|
|
2974
|
-
config.serveRouted(
|
|
3001
|
+
config.serveRouted(request4, response);
|
|
2975
3002
|
return;
|
|
2976
3003
|
}
|
|
2977
|
-
effects.forwardHttp(config.upstreamFor(host),
|
|
3004
|
+
effects.forwardHttp(config.upstreamFor(host), request4, response, config.capture?.observePassthrough(request4));
|
|
2978
3005
|
};
|
|
2979
3006
|
const httpSession = effects.createHttpSession(handler, onUpgrade(host));
|
|
2980
3007
|
const serveSecure = (secure, capability) => {
|
|
@@ -3016,7 +3043,7 @@ ${request3.rawHeaders.reduce((accumulated, value, index) => index % 2 === 0 ? `$
|
|
|
3016
3043
|
}
|
|
3017
3044
|
};
|
|
3018
3045
|
}
|
|
3019
|
-
function createConnectionLedger(limits,
|
|
3046
|
+
function createConnectionLedger(limits, isLiveCapability2) {
|
|
3020
3047
|
const pending = /* @__PURE__ */ new Map();
|
|
3021
3048
|
const authenticated = /* @__PURE__ */ new Map();
|
|
3022
3049
|
let tunnels = 0;
|
|
@@ -3029,7 +3056,7 @@ function createConnectionLedger(limits, isLiveCapability) {
|
|
|
3029
3056
|
};
|
|
3030
3057
|
const revalidation = setInterval(() => {
|
|
3031
3058
|
for (const [capability, sockets] of authenticated) {
|
|
3032
|
-
if (!
|
|
3059
|
+
if (!isLiveCapability2(capability)) {
|
|
3033
3060
|
for (const socket of sockets) {
|
|
3034
3061
|
socket.destroy();
|
|
3035
3062
|
}
|
|
@@ -3271,6 +3298,40 @@ var ExemptTlsAgent = class extends https.Agent {
|
|
|
3271
3298
|
return tls.connect(tlsOptions);
|
|
3272
3299
|
}
|
|
3273
3300
|
};
|
|
3301
|
+
function realRcEventDial(target = { host: CONNECT_INTERCEPT_HOST, port: HTTPS_PORT }) {
|
|
3302
|
+
const agent = new ExemptTlsAgent();
|
|
3303
|
+
return {
|
|
3304
|
+
writeEvents: async (sessionId, headers, body) => await new Promise((resolve, reject) => {
|
|
3305
|
+
const options = {
|
|
3306
|
+
host: target.host,
|
|
3307
|
+
port: target.port,
|
|
3308
|
+
method: "POST",
|
|
3309
|
+
path: `/v1/code/sessions/${encodeURIComponent(sessionId)}/events`,
|
|
3310
|
+
// The Host header names the host whose API this is, not the address dialled: a redirected test dials a loopback stand-in while still speaking to the API host by name, exactly as the forwarded paths present their SNI.
|
|
3311
|
+
headers: { ...headers, host: target.servername ?? target.host, "content-length": String(Buffer.byteLength(body, "utf8")) },
|
|
3312
|
+
agent
|
|
3313
|
+
};
|
|
3314
|
+
options.servername = target.servername ?? target.host;
|
|
3315
|
+
if (target.ca !== void 0) {
|
|
3316
|
+
options.ca = [...target.ca];
|
|
3317
|
+
}
|
|
3318
|
+
if (target.rejectUnauthorized !== void 0) {
|
|
3319
|
+
options.rejectUnauthorized = target.rejectUnauthorized;
|
|
3320
|
+
}
|
|
3321
|
+
const request4 = https.request(options, (response) => {
|
|
3322
|
+
const chunks = [];
|
|
3323
|
+
response.on("data", (chunk) => {
|
|
3324
|
+
chunks.push(chunk);
|
|
3325
|
+
});
|
|
3326
|
+
response.on("end", () => {
|
|
3327
|
+
resolve({ status: response.statusCode ?? HTTP_BAD_GATEWAY, body: Buffer.concat(chunks).toString("utf8") });
|
|
3328
|
+
});
|
|
3329
|
+
});
|
|
3330
|
+
request4.once("error", reject);
|
|
3331
|
+
request4.end(body);
|
|
3332
|
+
})
|
|
3333
|
+
};
|
|
3334
|
+
}
|
|
3274
3335
|
function realConnectEffects() {
|
|
3275
3336
|
const plainAgent = new ExemptHttpAgent();
|
|
3276
3337
|
const tlsAgent = new ExemptTlsAgent();
|
|
@@ -3376,8 +3437,8 @@ function realConnectEffects() {
|
|
|
3376
3437
|
socket.destroy();
|
|
3377
3438
|
});
|
|
3378
3439
|
if (onUpgrade !== void 0) {
|
|
3379
|
-
server.on("upgrade", (
|
|
3380
|
-
onUpgrade(
|
|
3440
|
+
server.on("upgrade", (request4, socket, head) => {
|
|
3441
|
+
onUpgrade(request4, socket, head);
|
|
3381
3442
|
});
|
|
3382
3443
|
}
|
|
3383
3444
|
return {
|
|
@@ -3416,14 +3477,14 @@ function realConnectEffects() {
|
|
|
3416
3477
|
});
|
|
3417
3478
|
socket.once("error", reject);
|
|
3418
3479
|
}),
|
|
3419
|
-
forwardHttp: (target,
|
|
3420
|
-
const headers = forwardableHeaders(
|
|
3480
|
+
forwardHttp: (target, request4, response, observer) => {
|
|
3481
|
+
const headers = forwardableHeaders(request4.headers);
|
|
3421
3482
|
const options = {
|
|
3422
3483
|
host: target.host,
|
|
3423
3484
|
port: target.port,
|
|
3424
3485
|
lookup: realAddressLookup,
|
|
3425
|
-
method:
|
|
3426
|
-
path:
|
|
3486
|
+
method: request4.method,
|
|
3487
|
+
path: request4.url,
|
|
3427
3488
|
headers,
|
|
3428
3489
|
agent: target.tls ? tlsAgent : plainAgent
|
|
3429
3490
|
};
|
|
@@ -3458,15 +3519,15 @@ function realConnectEffects() {
|
|
|
3458
3519
|
response.end(body);
|
|
3459
3520
|
});
|
|
3460
3521
|
if (observer !== void 0) {
|
|
3461
|
-
|
|
3522
|
+
request4.on("data", (chunk) => {
|
|
3462
3523
|
observer.onRequestChunk(chunk);
|
|
3463
3524
|
});
|
|
3464
3525
|
response.on("close", () => {
|
|
3465
3526
|
observer.onEnd();
|
|
3466
3527
|
});
|
|
3467
3528
|
}
|
|
3468
|
-
|
|
3469
|
-
|
|
3529
|
+
request4.pipe(upstream);
|
|
3530
|
+
request4.socket.setNoDelay(true);
|
|
3470
3531
|
upstream.on("socket", (socket) => {
|
|
3471
3532
|
socket.setNoDelay(true);
|
|
3472
3533
|
});
|
|
@@ -3474,11 +3535,488 @@ function realConnectEffects() {
|
|
|
3474
3535
|
};
|
|
3475
3536
|
}
|
|
3476
3537
|
|
|
3538
|
+
// src/codex/http.ts
|
|
3539
|
+
var HTTP_STATUS = {
|
|
3540
|
+
ok: 200,
|
|
3541
|
+
badRequest: 400,
|
|
3542
|
+
unauthorized: 401,
|
|
3543
|
+
forbidden: 403,
|
|
3544
|
+
notFound: 404,
|
|
3545
|
+
methodNotAllowed: 405,
|
|
3546
|
+
payloadTooLarge: 413,
|
|
3547
|
+
tooManyRequests: 429,
|
|
3548
|
+
/** Not sent to anyone: the client is gone. Recorded for a response abandoned because the client disconnected, following the convention nginx uses for the same case. */
|
|
3549
|
+
clientClosedRequest: 499,
|
|
3550
|
+
internalServerError: 500,
|
|
3551
|
+
badGateway: 502
|
|
3552
|
+
};
|
|
3553
|
+
|
|
3554
|
+
// src/frontdoor/rcSessions.ts
|
|
3555
|
+
var RC_SESSIONS_PATH_PREFIX = "/v1/code/sessions";
|
|
3556
|
+
var RC_SESSION_ID_PREFIX = "cse_";
|
|
3557
|
+
var RC_IDLE_EXPIRY_MS = 45e3;
|
|
3558
|
+
var RC_CREATE_RESPONSE_CAP_BYTES = 8192;
|
|
3559
|
+
var CONFLICT_REASON_HEADER = "x-ccr-conflict-reason";
|
|
3560
|
+
var SUCCESS_STATUS_MAX_EXCLUSIVE = 300;
|
|
3561
|
+
function isSuccessful(status) {
|
|
3562
|
+
return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE;
|
|
3563
|
+
}
|
|
3564
|
+
function singleHeader(headers, name) {
|
|
3565
|
+
const value = headers[name];
|
|
3566
|
+
return typeof value === "string" ? value : void 0;
|
|
3567
|
+
}
|
|
3568
|
+
function sessionCreateId(body) {
|
|
3569
|
+
let parsed;
|
|
3570
|
+
try {
|
|
3571
|
+
parsed = JSON.parse(body);
|
|
3572
|
+
} catch {
|
|
3573
|
+
return void 0;
|
|
3574
|
+
}
|
|
3575
|
+
if (typeof parsed !== "object" || parsed === null || !("session" in parsed)) {
|
|
3576
|
+
return void 0;
|
|
3577
|
+
}
|
|
3578
|
+
const session = parsed.session;
|
|
3579
|
+
if (typeof session !== "object" || session === null || !("id" in session)) {
|
|
3580
|
+
return void 0;
|
|
3581
|
+
}
|
|
3582
|
+
const id = session.id;
|
|
3583
|
+
return typeof id === "string" && id.startsWith(RC_SESSION_ID_PREFIX) ? id : void 0;
|
|
3584
|
+
}
|
|
3585
|
+
function createRcSessionTracker(deps) {
|
|
3586
|
+
const entries = /* @__PURE__ */ new Map();
|
|
3587
|
+
const expire = (id) => {
|
|
3588
|
+
entries.delete(id);
|
|
3589
|
+
};
|
|
3590
|
+
const sweep = () => {
|
|
3591
|
+
for (const [id, entry] of entries) {
|
|
3592
|
+
if (deps.now() - entry.lastSeenAt >= deps.idleMs) {
|
|
3593
|
+
expire(id);
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
};
|
|
3597
|
+
const timer = setInterval(sweep, deps.idleMs);
|
|
3598
|
+
timer.unref();
|
|
3599
|
+
const observeExchange = (request4) => {
|
|
3600
|
+
const method = (request4.method ?? "").toUpperCase();
|
|
3601
|
+
const path29 = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
|
|
3602
|
+
if (!path29.startsWith(RC_SESSIONS_PATH_PREFIX)) {
|
|
3603
|
+
return void 0;
|
|
3604
|
+
}
|
|
3605
|
+
const tail = path29.slice(RC_SESSIONS_PATH_PREFIX.length);
|
|
3606
|
+
const facts = {
|
|
3607
|
+
authorization: singleHeader(request4.headers, "authorization"),
|
|
3608
|
+
anthropicVersion: singleHeader(request4.headers, "anthropic-version"),
|
|
3609
|
+
anthropicClientPlatform: singleHeader(request4.headers, "anthropic-client-platform")
|
|
3610
|
+
};
|
|
3611
|
+
if (tail === "") {
|
|
3612
|
+
if (method !== "POST") {
|
|
3613
|
+
return void 0;
|
|
3614
|
+
}
|
|
3615
|
+
let accepted = false;
|
|
3616
|
+
let buffered = 0;
|
|
3617
|
+
let body = "";
|
|
3618
|
+
return {
|
|
3619
|
+
onResponse: (status) => {
|
|
3620
|
+
accepted = isSuccessful(status);
|
|
3621
|
+
},
|
|
3622
|
+
onBodyChunk: (chunk) => {
|
|
3623
|
+
if (!accepted || buffered >= RC_CREATE_RESPONSE_CAP_BYTES) {
|
|
3624
|
+
return;
|
|
3625
|
+
}
|
|
3626
|
+
const text = Buffer.from(chunk).toString("utf8");
|
|
3627
|
+
body = body + text.slice(0, RC_CREATE_RESPONSE_CAP_BYTES - buffered);
|
|
3628
|
+
buffered = body.length;
|
|
3629
|
+
},
|
|
3630
|
+
onEnd: () => {
|
|
3631
|
+
if (!accepted) {
|
|
3632
|
+
return;
|
|
3633
|
+
}
|
|
3634
|
+
const id2 = sessionCreateId(body);
|
|
3635
|
+
if (id2 === void 0) {
|
|
3636
|
+
return;
|
|
3637
|
+
}
|
|
3638
|
+
entries.set(id2, { id: id2, createdAt: deps.now(), lastSeenAt: deps.now(), ...facts });
|
|
3639
|
+
}
|
|
3640
|
+
};
|
|
3641
|
+
}
|
|
3642
|
+
const rest = tail.slice(1);
|
|
3643
|
+
const [id] = rest.split("/");
|
|
3644
|
+
if (id?.startsWith(RC_SESSION_ID_PREFIX) !== true) {
|
|
3645
|
+
return void 0;
|
|
3646
|
+
}
|
|
3647
|
+
const entry = entries.get(id);
|
|
3648
|
+
if (entry === void 0) {
|
|
3649
|
+
entries.set(id, { id, createdAt: deps.now(), lastSeenAt: deps.now(), ...facts });
|
|
3650
|
+
} else {
|
|
3651
|
+
entry.lastSeenAt = deps.now();
|
|
3652
|
+
entry.authorization = facts.authorization ?? entry.authorization;
|
|
3653
|
+
entry.anthropicVersion = facts.anthropicVersion ?? entry.anthropicVersion;
|
|
3654
|
+
entry.anthropicClientPlatform = facts.anthropicClientPlatform ?? entry.anthropicClientPlatform;
|
|
3655
|
+
}
|
|
3656
|
+
const archiving = method === "POST" && rest === `${id}/archive`;
|
|
3657
|
+
return {
|
|
3658
|
+
onResponse: (status, headers) => {
|
|
3659
|
+
if (singleHeader(headers, CONFLICT_REASON_HEADER) !== void 0 || archiving && isSuccessful(status)) {
|
|
3660
|
+
expire(id);
|
|
3661
|
+
}
|
|
3662
|
+
},
|
|
3663
|
+
onBodyChunk: () => {
|
|
3664
|
+
},
|
|
3665
|
+
onEnd: () => {
|
|
3666
|
+
}
|
|
3667
|
+
};
|
|
3668
|
+
};
|
|
3669
|
+
return {
|
|
3670
|
+
observeExchange,
|
|
3671
|
+
list: () => {
|
|
3672
|
+
sweep();
|
|
3673
|
+
return [...entries.values()].sort((left, right) => left.createdAt - right.createdAt || (left.id < right.id ? -1 : 1)).map(({ id, createdAt, lastSeenAt }) => ({ id, createdAt, lastSeenAt }));
|
|
3674
|
+
},
|
|
3675
|
+
credentialOf: (sessionId) => {
|
|
3676
|
+
const entry = entries.get(sessionId);
|
|
3677
|
+
return entry === void 0 ? void 0 : { authorization: entry.authorization, anthropicVersion: entry.anthropicVersion, anthropicClientPlatform: entry.anthropicClientPlatform };
|
|
3678
|
+
},
|
|
3679
|
+
close: () => {
|
|
3680
|
+
clearInterval(timer);
|
|
3681
|
+
}
|
|
3682
|
+
};
|
|
3683
|
+
}
|
|
3684
|
+
function observingRoutedRoute(route, tracker) {
|
|
3685
|
+
return {
|
|
3686
|
+
...route,
|
|
3687
|
+
serve: async (request4, response) => {
|
|
3688
|
+
const observed = tracker.observeExchange({ method: request4.method, url: request4.url, headers: request4.headers });
|
|
3689
|
+
await route.serve(request4, observed === void 0 ? response : teeRoutedResponse(response, observed));
|
|
3690
|
+
}
|
|
3691
|
+
};
|
|
3692
|
+
}
|
|
3693
|
+
function teeRoutedResponse(response, observer) {
|
|
3694
|
+
return {
|
|
3695
|
+
get headersSent() {
|
|
3696
|
+
return response.headersSent;
|
|
3697
|
+
},
|
|
3698
|
+
start: (status, headers) => {
|
|
3699
|
+
observer.onResponse(status, headers);
|
|
3700
|
+
response.start(status, headers);
|
|
3701
|
+
},
|
|
3702
|
+
flush: () => {
|
|
3703
|
+
response.flush();
|
|
3704
|
+
},
|
|
3705
|
+
write: async (chunk) => {
|
|
3706
|
+
observer.onBodyChunk(typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk);
|
|
3707
|
+
await response.write(chunk);
|
|
3708
|
+
},
|
|
3709
|
+
end: () => {
|
|
3710
|
+
response.end();
|
|
3711
|
+
observer.onEnd();
|
|
3712
|
+
},
|
|
3713
|
+
destroy: () => {
|
|
3714
|
+
response.destroy();
|
|
3715
|
+
observer.onEnd();
|
|
3716
|
+
}
|
|
3717
|
+
};
|
|
3718
|
+
}
|
|
3719
|
+
function rcObserverAsPassthrough(observer) {
|
|
3720
|
+
return {
|
|
3721
|
+
// The request body carries nothing the tracker reads: the credential and the session id are in the request's headers and path, already handed over with the request.
|
|
3722
|
+
onRequestChunk: () => {
|
|
3723
|
+
return;
|
|
3724
|
+
},
|
|
3725
|
+
onResponse: (status, headers) => {
|
|
3726
|
+
observer.onResponse(status, headers);
|
|
3727
|
+
},
|
|
3728
|
+
onResponseChunk: (chunk) => {
|
|
3729
|
+
observer.onBodyChunk(chunk);
|
|
3730
|
+
},
|
|
3731
|
+
onEnd: () => {
|
|
3732
|
+
observer.onEnd();
|
|
3733
|
+
}
|
|
3734
|
+
};
|
|
3735
|
+
}
|
|
3736
|
+
function buildRcUserMessagePayload(uuid, sessionId, text) {
|
|
3737
|
+
return {
|
|
3738
|
+
type: "user",
|
|
3739
|
+
uuid,
|
|
3740
|
+
session_id: sessionId,
|
|
3741
|
+
parent_tool_use_id: null,
|
|
3742
|
+
message: { role: "user", content: text }
|
|
3743
|
+
};
|
|
3744
|
+
}
|
|
3745
|
+
function buildRcEventWriteBody(payload) {
|
|
3746
|
+
return { events: [{ payload }] };
|
|
3747
|
+
}
|
|
3748
|
+
function sequenceNumsOf(body) {
|
|
3749
|
+
let parsed;
|
|
3750
|
+
try {
|
|
3751
|
+
parsed = JSON.parse(body);
|
|
3752
|
+
} catch {
|
|
3753
|
+
return void 0;
|
|
3754
|
+
}
|
|
3755
|
+
if (typeof parsed !== "object" || parsed === null || !("results" in parsed) || !Array.isArray(parsed.results)) {
|
|
3756
|
+
return void 0;
|
|
3757
|
+
}
|
|
3758
|
+
const sequenceNums = [];
|
|
3759
|
+
const results = parsed.results;
|
|
3760
|
+
for (const result of results) {
|
|
3761
|
+
if (typeof result !== "object" || result === null || !("sequence_num" in result)) {
|
|
3762
|
+
return void 0;
|
|
3763
|
+
}
|
|
3764
|
+
const sequenceNum = result.sequence_num;
|
|
3765
|
+
if (typeof sequenceNum !== "number") {
|
|
3766
|
+
return void 0;
|
|
3767
|
+
}
|
|
3768
|
+
sequenceNums.push(sequenceNum);
|
|
3769
|
+
}
|
|
3770
|
+
return sequenceNums.length === 0 ? void 0 : sequenceNums;
|
|
3771
|
+
}
|
|
3772
|
+
function rcInjectResultFromAnswer(answer) {
|
|
3773
|
+
if (!isSuccessful(answer.status)) {
|
|
3774
|
+
if (answer.status === HTTP_STATUS.unauthorized) {
|
|
3775
|
+
return { ok: false, message: `the API refused the observed Authorization bearer for this session (HTTP 401): the bearer went stale after the door last saw the session's traffic. The session's own client refreshes it on its next call, which the door will observe, so retry once Remote Control has been active again. API answer: ${answer.body}` };
|
|
3776
|
+
}
|
|
3777
|
+
return { ok: false, message: `the API answered HTTP ${String(answer.status)} to the injected event: ${answer.body}` };
|
|
3778
|
+
}
|
|
3779
|
+
const sequenceNums = sequenceNumsOf(answer.body);
|
|
3780
|
+
if (sequenceNums === void 0) {
|
|
3781
|
+
return { ok: false, message: `the API accepted the injected event but its answer named no results[].sequence_num, so delivery cannot be confirmed: ${answer.body}` };
|
|
3782
|
+
}
|
|
3783
|
+
return { ok: true, sequenceNums };
|
|
3784
|
+
}
|
|
3785
|
+
async function injectRcUserMessage(deps, sessionId, text) {
|
|
3786
|
+
const credential = deps.credentialOf(sessionId);
|
|
3787
|
+
if (credential === void 0) {
|
|
3788
|
+
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)` };
|
|
3789
|
+
}
|
|
3790
|
+
const headers = { "content-type": "application/json" };
|
|
3791
|
+
if (credential.authorization !== void 0) {
|
|
3792
|
+
headers.authorization = credential.authorization;
|
|
3793
|
+
}
|
|
3794
|
+
if (credential.anthropicVersion !== void 0) {
|
|
3795
|
+
headers["anthropic-version"] = credential.anthropicVersion;
|
|
3796
|
+
}
|
|
3797
|
+
if (credential.anthropicClientPlatform !== void 0) {
|
|
3798
|
+
headers["anthropic-client-platform"] = credential.anthropicClientPlatform;
|
|
3799
|
+
}
|
|
3800
|
+
const body = JSON.stringify(buildRcEventWriteBody(buildRcUserMessagePayload(deps.newUuid(), sessionId, text)));
|
|
3801
|
+
let answer;
|
|
3802
|
+
try {
|
|
3803
|
+
answer = await deps.dial.writeEvents(sessionId, headers, body);
|
|
3804
|
+
} catch (error) {
|
|
3805
|
+
return { ok: false, message: `the door could not reach the API host to inject into session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
3806
|
+
}
|
|
3807
|
+
return rcInjectResultFromAnswer(answer);
|
|
3808
|
+
}
|
|
3809
|
+
|
|
3810
|
+
// src/frontdoor/rcControl.ts
|
|
3811
|
+
var https2 = __toESM(require("node:https"), 1);
|
|
3812
|
+
var CONTROL_PATH_PREFIX = "/__agent-shim/rc";
|
|
3813
|
+
var CONTROL_BODY_CAP_BYTES = 10485760;
|
|
3814
|
+
function answerJson(response, status, body) {
|
|
3815
|
+
const text = JSON.stringify(body);
|
|
3816
|
+
response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text, "utf8")) });
|
|
3817
|
+
response.end(text);
|
|
3818
|
+
}
|
|
3819
|
+
async function readBody(request4) {
|
|
3820
|
+
return await new Promise((resolve, reject) => {
|
|
3821
|
+
const chunks = [];
|
|
3822
|
+
let total = 0;
|
|
3823
|
+
request4.on("data", (chunk) => {
|
|
3824
|
+
total += chunk.length;
|
|
3825
|
+
if (total > CONTROL_BODY_CAP_BYTES) {
|
|
3826
|
+
request4.destroy();
|
|
3827
|
+
reject(new Error("too large"));
|
|
3828
|
+
return;
|
|
3829
|
+
}
|
|
3830
|
+
chunks.push(chunk);
|
|
3831
|
+
});
|
|
3832
|
+
request4.on("end", () => {
|
|
3833
|
+
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
3834
|
+
});
|
|
3835
|
+
request4.on("error", () => {
|
|
3836
|
+
reject(new Error("the request body ended early"));
|
|
3837
|
+
});
|
|
3838
|
+
});
|
|
3839
|
+
}
|
|
3840
|
+
function createRcControlHandler(deps) {
|
|
3841
|
+
const sessionsPath = `${CONTROL_PATH_PREFIX}/sessions`;
|
|
3842
|
+
const injectPath = `${CONTROL_PATH_PREFIX}/inject`;
|
|
3843
|
+
return (request4, response) => {
|
|
3844
|
+
const presented = request4.headers.authorization;
|
|
3845
|
+
const bearerPrefix = "bearer ".length;
|
|
3846
|
+
const token = typeof presented === "string" && presented.slice(0, bearerPrefix).toLowerCase() === "bearer " ? presented.slice(bearerPrefix) : void 0;
|
|
3847
|
+
if (token === void 0 || !isLiveCapability(token, [deps.expectedToken])) {
|
|
3848
|
+
answerJson(response, HTTP_STATUS.unauthorized, { error: "the front door's control routes demand this generation's control token as a Bearer credential" });
|
|
3849
|
+
return;
|
|
3850
|
+
}
|
|
3851
|
+
const url = request4.url ?? "/";
|
|
3852
|
+
const handle = async () => {
|
|
3853
|
+
if (url === sessionsPath) {
|
|
3854
|
+
if (request4.method !== "GET") {
|
|
3855
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the session list is a GET" });
|
|
3856
|
+
return;
|
|
3857
|
+
}
|
|
3858
|
+
answerJson(response, HTTP_STATUS.ok, { sessions: deps.list() });
|
|
3859
|
+
return;
|
|
3860
|
+
}
|
|
3861
|
+
if (url === injectPath) {
|
|
3862
|
+
if (request4.method !== "POST") {
|
|
3863
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the inject operation is a POST" });
|
|
3864
|
+
return;
|
|
3865
|
+
}
|
|
3866
|
+
let parsed;
|
|
3867
|
+
try {
|
|
3868
|
+
parsed = JSON.parse(await readBody(request4));
|
|
3869
|
+
} catch {
|
|
3870
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the inject body is not readable JSON within the control body cap" });
|
|
3871
|
+
return;
|
|
3872
|
+
}
|
|
3873
|
+
if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("text" in parsed) || typeof parsed.session !== "string" || typeof parsed.text !== "string" || parsed.session === "" || parsed.text === "") {
|
|
3874
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the inject body must be JSON naming a non-empty session id and text" });
|
|
3875
|
+
return;
|
|
3876
|
+
}
|
|
3877
|
+
const result = await deps.inject(parsed.session, parsed.text);
|
|
3878
|
+
if (result.ok) {
|
|
3879
|
+
answerJson(response, HTTP_STATUS.ok, { session: parsed.session, sequenceNums: result.sequenceNums });
|
|
3880
|
+
return;
|
|
3881
|
+
}
|
|
3882
|
+
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
3883
|
+
return;
|
|
3884
|
+
}
|
|
3885
|
+
answerJson(response, HTTP_STATUS.notFound, { error: `no such control route: ${url}` });
|
|
3886
|
+
};
|
|
3887
|
+
handle().catch((error) => {
|
|
3888
|
+
answerJson(response, HTTP_STATUS.internalServerError, { error: `the control route failed: ${error instanceof Error ? error.message : String(error)}` });
|
|
3889
|
+
});
|
|
3890
|
+
};
|
|
3891
|
+
}
|
|
3892
|
+
function realRcControlTransport(port, ca) {
|
|
3893
|
+
return {
|
|
3894
|
+
request: async (options) => await new Promise((resolve, reject) => {
|
|
3895
|
+
const request4 = https2.request(
|
|
3896
|
+
{
|
|
3897
|
+
host: "127.0.0.1",
|
|
3898
|
+
port,
|
|
3899
|
+
method: options.method,
|
|
3900
|
+
path: options.path,
|
|
3901
|
+
headers: options.body === void 0 ? options.headers : { ...options.headers, "content-length": String(Buffer.byteLength(options.body, "utf8")) },
|
|
3902
|
+
agent: false,
|
|
3903
|
+
ca: [ca]
|
|
3904
|
+
},
|
|
3905
|
+
(response) => {
|
|
3906
|
+
const chunks = [];
|
|
3907
|
+
response.on("data", (chunk) => {
|
|
3908
|
+
chunks.push(chunk);
|
|
3909
|
+
});
|
|
3910
|
+
response.on("end", () => {
|
|
3911
|
+
resolve({ status: response.statusCode ?? 0, body: Buffer.concat(chunks).toString("utf8") });
|
|
3912
|
+
});
|
|
3913
|
+
}
|
|
3914
|
+
);
|
|
3915
|
+
request4.once("error", reject);
|
|
3916
|
+
if (options.body === void 0) {
|
|
3917
|
+
request4.end();
|
|
3918
|
+
} else {
|
|
3919
|
+
request4.end(options.body);
|
|
3920
|
+
}
|
|
3921
|
+
})
|
|
3922
|
+
};
|
|
3923
|
+
}
|
|
3924
|
+
function controlErrorOf(body) {
|
|
3925
|
+
try {
|
|
3926
|
+
const parsed = JSON.parse(body);
|
|
3927
|
+
if (typeof parsed === "object" && parsed !== null && "error" in parsed && typeof parsed.error === "string") {
|
|
3928
|
+
return parsed.error;
|
|
3929
|
+
}
|
|
3930
|
+
} catch {
|
|
3931
|
+
return body;
|
|
3932
|
+
}
|
|
3933
|
+
return void 0;
|
|
3934
|
+
}
|
|
3935
|
+
function isRcSessionSummary(value) {
|
|
3936
|
+
if (typeof value !== "object" || value === null) {
|
|
3937
|
+
return false;
|
|
3938
|
+
}
|
|
3939
|
+
if (!("id" in value) || !("createdAt" in value) || !("lastSeenAt" in value)) {
|
|
3940
|
+
return false;
|
|
3941
|
+
}
|
|
3942
|
+
return typeof value.id === "string" && typeof value.createdAt === "number" && typeof value.lastSeenAt === "number";
|
|
3943
|
+
}
|
|
3944
|
+
function sequenceNumsOfBody(body) {
|
|
3945
|
+
let parsed;
|
|
3946
|
+
try {
|
|
3947
|
+
parsed = JSON.parse(body);
|
|
3948
|
+
} catch {
|
|
3949
|
+
return void 0;
|
|
3950
|
+
}
|
|
3951
|
+
if (typeof parsed !== "object" || parsed === null || !("sequenceNums" in parsed) || !Array.isArray(parsed.sequenceNums)) {
|
|
3952
|
+
return void 0;
|
|
3953
|
+
}
|
|
3954
|
+
const sequenceNums = [];
|
|
3955
|
+
const values = parsed.sequenceNums;
|
|
3956
|
+
for (const value of values) {
|
|
3957
|
+
if (typeof value !== "number") {
|
|
3958
|
+
return void 0;
|
|
3959
|
+
}
|
|
3960
|
+
sequenceNums.push(value);
|
|
3961
|
+
}
|
|
3962
|
+
return sequenceNums;
|
|
3963
|
+
}
|
|
3964
|
+
function frontDoorRcControl(transport, token) {
|
|
3965
|
+
const headers = { authorization: `Bearer ${token}` };
|
|
3966
|
+
const sessionsOf = (answer, what) => {
|
|
3967
|
+
if (answer.status !== HTTP_STATUS.ok) {
|
|
3968
|
+
throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
|
|
3969
|
+
}
|
|
3970
|
+
let parsed;
|
|
3971
|
+
try {
|
|
3972
|
+
parsed = JSON.parse(answer.body);
|
|
3973
|
+
} catch {
|
|
3974
|
+
throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
|
|
3975
|
+
}
|
|
3976
|
+
if (typeof parsed !== "object" || parsed === null || !("sessions" in parsed) || !Array.isArray(parsed.sessions)) {
|
|
3977
|
+
throw new Error(`${what}: the door's answer named no sessions list: ${answer.body}`);
|
|
3978
|
+
}
|
|
3979
|
+
const candidates = parsed.sessions;
|
|
3980
|
+
const sessions = [];
|
|
3981
|
+
for (const value of candidates) {
|
|
3982
|
+
if (!isRcSessionSummary(value)) {
|
|
3983
|
+
throw new Error(`${what}: the door's answer named no sessions list: ${answer.body}`);
|
|
3984
|
+
}
|
|
3985
|
+
sessions.push(value);
|
|
3986
|
+
}
|
|
3987
|
+
return sessions;
|
|
3988
|
+
};
|
|
3989
|
+
return {
|
|
3990
|
+
listSessions: async () => sessionsOf(await transport.request({ method: "GET", path: `${CONTROL_PATH_PREFIX}/sessions`, headers }), "listing the door's Remote Control sessions"),
|
|
3991
|
+
sendPrompt: async (sessionId, text) => {
|
|
3992
|
+
let answer;
|
|
3993
|
+
try {
|
|
3994
|
+
answer = await transport.request({
|
|
3995
|
+
method: "POST",
|
|
3996
|
+
path: `${CONTROL_PATH_PREFIX}/inject`,
|
|
3997
|
+
headers: { ...headers, "content-type": "application/json" },
|
|
3998
|
+
body: JSON.stringify({ session: sessionId, text })
|
|
3999
|
+
});
|
|
4000
|
+
} catch (error) {
|
|
4001
|
+
return { ok: false, message: `could not reach the front door's control listener: ${error instanceof Error ? error.message : String(error)}` };
|
|
4002
|
+
}
|
|
4003
|
+
if (answer.status === HTTP_STATUS.ok) {
|
|
4004
|
+
const sequenceNums = sequenceNumsOfBody(answer.body);
|
|
4005
|
+
if (sequenceNums === void 0) {
|
|
4006
|
+
return { ok: false, message: `the door accepted the event but its answer named no sequence numbers: ${answer.body}` };
|
|
4007
|
+
}
|
|
4008
|
+
return { ok: true, sequenceNums };
|
|
4009
|
+
}
|
|
4010
|
+
return { ok: false, message: `injecting into ${sessionId} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}` };
|
|
4011
|
+
}
|
|
4012
|
+
};
|
|
4013
|
+
}
|
|
4014
|
+
|
|
3477
4015
|
// src/frontdoor/ensure.ts
|
|
3478
|
-
var
|
|
4016
|
+
var import_node_crypto7 = require("node:crypto");
|
|
3479
4017
|
|
|
3480
4018
|
// src/headroom/state.ts
|
|
3481
|
-
var
|
|
4019
|
+
var import_node_crypto6 = require("node:crypto");
|
|
3482
4020
|
var import_node_path8 = __toESM(require("node:path"), 1);
|
|
3483
4021
|
var import_zod4 = require("zod");
|
|
3484
4022
|
var HEADROOM_ANTHROPIC_UPSTREAM = "https://api.anthropic.com";
|
|
@@ -3539,7 +4077,7 @@ function headroomUpstreams(providers, frontDoorOrigin2) {
|
|
|
3539
4077
|
return upstreams;
|
|
3540
4078
|
}
|
|
3541
4079
|
function hashAllowlist(allowlist) {
|
|
3542
|
-
return (0,
|
|
4080
|
+
return (0, import_node_crypto6.createHash)("sha256").update(allowlist.join("\n")).digest("hex");
|
|
3543
4081
|
}
|
|
3544
4082
|
function readAllProviders(fs12, providersDir) {
|
|
3545
4083
|
const result = [];
|
|
@@ -3638,7 +4176,7 @@ function ensureFrontDoor(params) {
|
|
|
3638
4176
|
continue;
|
|
3639
4177
|
}
|
|
3640
4178
|
if (verdict.ok) {
|
|
3641
|
-
const token = (0,
|
|
4179
|
+
const token = (0, import_node_crypto7.randomUUID)();
|
|
3642
4180
|
writeFrontDoorSession(ports.fs, paths.frontdoorSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), token });
|
|
3643
4181
|
return { port: state.port, connectPort: state.connectPort, token };
|
|
3644
4182
|
}
|
|
@@ -4037,7 +4575,7 @@ function ensureHeadroom(params) {
|
|
|
4037
4575
|
}
|
|
4038
4576
|
|
|
4039
4577
|
// src/headroom/settings.ts
|
|
4040
|
-
var
|
|
4578
|
+
var import_node_crypto8 = require("node:crypto");
|
|
4041
4579
|
function settingsOf(config) {
|
|
4042
4580
|
return {
|
|
4043
4581
|
...config.mode === void 0 ? {} : { mode: config.mode },
|
|
@@ -4062,7 +4600,7 @@ function settingsEnv(settings) {
|
|
|
4062
4600
|
return settings.rolloutChannel === void 0 ? {} : { HEADROOM_ROLLOUT_CHANNEL: settings.rolloutChannel };
|
|
4063
4601
|
}
|
|
4064
4602
|
function hashSettings(settings) {
|
|
4065
|
-
return (0,
|
|
4603
|
+
return (0, import_node_crypto8.createHash)("sha256").update(JSON.stringify([...settingsArgs(settings), JSON.stringify(settingsEnv(settings))])).digest("hex");
|
|
4066
4604
|
}
|
|
4067
4605
|
|
|
4068
4606
|
// src/headroom/socket.ts
|
|
@@ -4493,6 +5031,7 @@ function buildLayoutPaths(root) {
|
|
|
4493
5031
|
frontdoorCaBundlesDir: import_node_path12.default.join(root, "frontdoor", "ca", "bundles"),
|
|
4494
5032
|
frontdoorStateFile: import_node_path12.default.join(root, "frontdoor", "state.json"),
|
|
4495
5033
|
frontdoorLockFile: import_node_path12.default.join(root, "frontdoor", "start.lock"),
|
|
5034
|
+
frontdoorControlTokenFile: import_node_path12.default.join(root, "frontdoor", "control-token"),
|
|
4496
5035
|
frontdoorSessionsDir: import_node_path12.default.join(root, "frontdoor", "sessions"),
|
|
4497
5036
|
frontdoorLogPath: import_node_path12.default.join(root, "logs", "frontdoor.log"),
|
|
4498
5037
|
usageDir: import_node_path12.default.join(root, "usage"),
|
|
@@ -5987,7 +6526,7 @@ function collectPoolPick(params) {
|
|
|
5987
6526
|
|
|
5988
6527
|
// src/realPorts.ts
|
|
5989
6528
|
var import_node_child_process = require("node:child_process");
|
|
5990
|
-
var
|
|
6529
|
+
var import_node_crypto9 = require("node:crypto");
|
|
5991
6530
|
var import_node_fs6 = __toESM(require("node:fs"), 1);
|
|
5992
6531
|
var import_node_os3 = __toESM(require("node:os"), 1);
|
|
5993
6532
|
var import_node_path21 = __toESM(require("node:path"), 1);
|
|
@@ -6182,7 +6721,7 @@ var realFarmFs = {
|
|
|
6182
6721
|
},
|
|
6183
6722
|
hashFile(filePath) {
|
|
6184
6723
|
try {
|
|
6185
|
-
return (0,
|
|
6724
|
+
return (0, import_node_crypto9.createHash)("sha256").update(import_node_fs6.default.readFileSync(filePath)).digest("hex");
|
|
6186
6725
|
} catch (error) {
|
|
6187
6726
|
if (isEnoent3(error) || isErrorWithCode(error, "EISDIR")) {
|
|
6188
6727
|
return void 0;
|
|
@@ -7375,7 +7914,7 @@ function collectDoctorReport(params) {
|
|
|
7375
7914
|
}
|
|
7376
7915
|
|
|
7377
7916
|
// src/launcher.ts
|
|
7378
|
-
var
|
|
7917
|
+
var import_node_crypto10 = require("node:crypto");
|
|
7379
7918
|
var import_node_path26 = __toESM(require("node:path"), 1);
|
|
7380
7919
|
|
|
7381
7920
|
// src/launcher/argv.ts
|
|
@@ -8017,7 +8556,7 @@ function prepareLaunch(params) {
|
|
|
8017
8556
|
...identityCredential === void 0 ? {} : { identityCredential },
|
|
8018
8557
|
...frontDoor === void 0 ? {} : { frontdoor: frontDoor },
|
|
8019
8558
|
...headroom === void 0 ? {} : { headroom },
|
|
8020
|
-
sessionId: (0,
|
|
8559
|
+
sessionId: (0, import_node_crypto10.randomUUID)()
|
|
8021
8560
|
});
|
|
8022
8561
|
const releases = [...headroom === void 0 || headroomPort === void 0 ? [] : [headroomPort.release], ...frontDoorRelease === void 0 ? [] : [frontDoorRelease]];
|
|
8023
8562
|
const release = () => {
|
|
@@ -8030,7 +8569,7 @@ function prepareLaunch(params) {
|
|
|
8030
8569
|
|
|
8031
8570
|
// src/launchWiring.ts
|
|
8032
8571
|
var import_node_os5 = __toESM(require("node:os"), 1);
|
|
8033
|
-
var
|
|
8572
|
+
var import_node_crypto12 = require("node:crypto");
|
|
8034
8573
|
|
|
8035
8574
|
// src/realCredentialCache.ts
|
|
8036
8575
|
var import_node_child_process2 = require("node:child_process");
|
|
@@ -8178,7 +8717,7 @@ function probeFrontDoorSync(port, caPem) {
|
|
|
8178
8717
|
}
|
|
8179
8718
|
|
|
8180
8719
|
// src/frontdoor/trust.ts
|
|
8181
|
-
var
|
|
8720
|
+
var import_node_crypto11 = require("node:crypto");
|
|
8182
8721
|
var import_node_path28 = __toESM(require("node:path"), 1);
|
|
8183
8722
|
var BUNDLE_NAME_HEX_DIGITS = 16;
|
|
8184
8723
|
function resolveTrustBundle(params) {
|
|
@@ -8201,7 +8740,7 @@ function resolveTrustBundle(params) {
|
|
|
8201
8740
|
}
|
|
8202
8741
|
const combined = `${inheritedPem.trimEnd()}
|
|
8203
8742
|
${caPem}`;
|
|
8204
|
-
const bundle = import_node_path28.default.join(bundlesDir, `${(0,
|
|
8743
|
+
const bundle = import_node_path28.default.join(bundlesDir, `${(0, import_node_crypto11.createHash)("sha256").update(combined).digest("hex").slice(0, BUNDLE_NAME_HEX_DIGITS)}.pem`);
|
|
8205
8744
|
if (!fs12.exists(bundle)) {
|
|
8206
8745
|
fs12.mkdirp(bundlesDir);
|
|
8207
8746
|
fs12.writeFileAtomic(bundle, combined);
|
|
@@ -8280,7 +8819,7 @@ function buildFarmRuntime(paths, cwd) {
|
|
|
8280
8819
|
...cliOverride === void 0 ? {} : { cliOverride }
|
|
8281
8820
|
}).input,
|
|
8282
8821
|
now: () => Date.now(),
|
|
8283
|
-
uniqueSuffix: `${String(process.pid)}.${(0,
|
|
8822
|
+
uniqueSuffix: `${String(process.pid)}.${(0, import_node_crypto12.randomUUID)()}`,
|
|
8284
8823
|
// Zombie-aware on purpose: a previous launcher that crashed out of a resync without releasing the lock may sit unreaped, still answering signal 0 as alive, and must read as a dead holder so this launch takes the lock over instead of timing out.
|
|
8285
8824
|
lock: { pid: process.pid, isRunning: realIsProcessRunning, sleep: realSleepSync }
|
|
8286
8825
|
},
|
|
@@ -8345,6 +8884,7 @@ function prepareClaudeLaunch(options) {
|
|
|
8345
8884
|
CONNECT_INTERCEPT_HOST,
|
|
8346
8885
|
CONNECT_INTERCEPT_HOSTS,
|
|
8347
8886
|
CONNECT_LIMITS,
|
|
8887
|
+
CONTROL_PATH_PREFIX,
|
|
8348
8888
|
CategoryClassificationOverlaySchema,
|
|
8349
8889
|
CategoryClassificationSchema,
|
|
8350
8890
|
CategoryMapSchema,
|
|
@@ -8383,6 +8923,8 @@ function prepareClaudeLaunch(options) {
|
|
|
8383
8923
|
ProviderKindMismatchError,
|
|
8384
8924
|
ProviderNotFoundError,
|
|
8385
8925
|
ProviderSchema,
|
|
8926
|
+
RC_IDLE_EXPIRY_MS,
|
|
8927
|
+
RC_SESSIONS_PATH_PREFIX,
|
|
8386
8928
|
ROUTED_PATH_PREFIX,
|
|
8387
8929
|
UsageError,
|
|
8388
8930
|
UsageSnapshotError,
|
|
@@ -8394,6 +8936,8 @@ function prepareClaudeLaunch(options) {
|
|
|
8394
8936
|
addProvider,
|
|
8395
8937
|
buildEntryFacts,
|
|
8396
8938
|
buildLayoutPaths,
|
|
8939
|
+
buildRcEventWriteBody,
|
|
8940
|
+
buildRcUserMessagePayload,
|
|
8397
8941
|
carryOver,
|
|
8398
8942
|
checkReportHasWarnings,
|
|
8399
8943
|
checkReportToJson,
|
|
@@ -8401,6 +8945,8 @@ function prepareClaudeLaunch(options) {
|
|
|
8401
8945
|
collectDoctorReport,
|
|
8402
8946
|
createLeafCache,
|
|
8403
8947
|
createProfile,
|
|
8948
|
+
createRcControlHandler,
|
|
8949
|
+
createRcSessionTracker,
|
|
8404
8950
|
describeProviderEndpoint,
|
|
8405
8951
|
detectAmbientCredential,
|
|
8406
8952
|
ensureCa,
|
|
@@ -8411,8 +8957,10 @@ function prepareClaudeLaunch(options) {
|
|
|
8411
8957
|
formatCheckReport,
|
|
8412
8958
|
formatDoctorReport,
|
|
8413
8959
|
forwardableHeaders,
|
|
8960
|
+
frontDoorRcControl,
|
|
8414
8961
|
generateCa,
|
|
8415
8962
|
identityExists,
|
|
8963
|
+
injectRcUserMessage,
|
|
8416
8964
|
isIdentityDirectoryName,
|
|
8417
8965
|
isInterceptedHost,
|
|
8418
8966
|
listDirectoryRules,
|
|
@@ -8421,11 +8969,14 @@ function prepareClaudeLaunch(options) {
|
|
|
8421
8969
|
listProviders,
|
|
8422
8970
|
listUsageSnapshots,
|
|
8423
8971
|
mintLeaf,
|
|
8972
|
+
observingRoutedRoute,
|
|
8424
8973
|
parseConnectTarget,
|
|
8425
8974
|
prepareClaudeLaunch,
|
|
8426
8975
|
prepareLaunch,
|
|
8427
8976
|
profileExists,
|
|
8428
8977
|
providerExists,
|
|
8978
|
+
rcInjectResultFromAnswer,
|
|
8979
|
+
rcObserverAsPassthrough,
|
|
8429
8980
|
readActiveIdentity,
|
|
8430
8981
|
readDirectoryRules,
|
|
8431
8982
|
readFarmManifest,
|
|
@@ -8437,6 +8988,8 @@ function prepareClaudeLaunch(options) {
|
|
|
8437
8988
|
readUsageSnapshot,
|
|
8438
8989
|
realConnectCertStore,
|
|
8439
8990
|
realConnectEffects,
|
|
8991
|
+
realRcControlTransport,
|
|
8992
|
+
realRcEventDial,
|
|
8440
8993
|
recoverFarm,
|
|
8441
8994
|
recoveryDiagnostics,
|
|
8442
8995
|
removeDirectoryRule,
|