agent-shim 8.2.0 → 8.4.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 +5 -0
- package/dist/cli.cjs +1623 -643
- package/dist/index.cjs +951 -42
- package/dist/index.mjs +924 -33
- package/dist/types/index.d.ts +314 -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,10 @@ __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_PENDING_DEADLINE_MS: () => RC_PENDING_DEADLINE_MS,
|
|
78
|
+
RC_REQUEST_PARSE_CAP_BYTES: () => RC_REQUEST_PARSE_CAP_BYTES,
|
|
79
|
+
RC_SESSIONS_PATH_PREFIX: () => RC_SESSIONS_PATH_PREFIX,
|
|
75
80
|
ROUTED_PATH_PREFIX: () => ROUTED_PATH_PREFIX,
|
|
76
81
|
UsageError: () => UsageError,
|
|
77
82
|
UsageSnapshotError: () => UsageSnapshotError,
|
|
@@ -81,8 +86,12 @@ __export(index_exports, {
|
|
|
81
86
|
addIdentity: () => addIdentity,
|
|
82
87
|
addPool: () => addPool,
|
|
83
88
|
addProvider: () => addProvider,
|
|
89
|
+
answerRcControlRequest: () => answerRcControlRequest,
|
|
84
90
|
buildEntryFacts: () => buildEntryFacts,
|
|
85
91
|
buildLayoutPaths: () => buildLayoutPaths,
|
|
92
|
+
buildRcControlResponsePayload: () => buildRcControlResponsePayload,
|
|
93
|
+
buildRcEventWriteBody: () => buildRcEventWriteBody,
|
|
94
|
+
buildRcUserMessagePayload: () => buildRcUserMessagePayload,
|
|
86
95
|
carryOver: () => carryOver,
|
|
87
96
|
checkReportHasWarnings: () => checkReportHasWarnings,
|
|
88
97
|
checkReportToJson: () => checkReportToJson,
|
|
@@ -90,6 +99,8 @@ __export(index_exports, {
|
|
|
90
99
|
collectDoctorReport: () => collectDoctorReport,
|
|
91
100
|
createLeafCache: () => createLeafCache,
|
|
92
101
|
createProfile: () => createProfile,
|
|
102
|
+
createRcControlHandler: () => createRcControlHandler,
|
|
103
|
+
createRcSessionTracker: () => createRcSessionTracker,
|
|
93
104
|
describeProviderEndpoint: () => describeProviderEndpoint,
|
|
94
105
|
detectAmbientCredential: () => detectAmbientCredential,
|
|
95
106
|
ensureCa: () => ensureCa,
|
|
@@ -100,8 +111,10 @@ __export(index_exports, {
|
|
|
100
111
|
formatCheckReport: () => formatCheckReport,
|
|
101
112
|
formatDoctorReport: () => formatDoctorReport,
|
|
102
113
|
forwardableHeaders: () => forwardableHeaders,
|
|
114
|
+
frontDoorRcControl: () => frontDoorRcControl,
|
|
103
115
|
generateCa: () => generateCa,
|
|
104
116
|
identityExists: () => identityExists,
|
|
117
|
+
injectRcUserMessage: () => injectRcUserMessage,
|
|
105
118
|
isIdentityDirectoryName: () => isIdentityDirectoryName,
|
|
106
119
|
isInterceptedHost: () => isInterceptedHost,
|
|
107
120
|
listDirectoryRules: () => listDirectoryRules,
|
|
@@ -110,11 +123,14 @@ __export(index_exports, {
|
|
|
110
123
|
listProviders: () => listProviders,
|
|
111
124
|
listUsageSnapshots: () => listUsageSnapshots,
|
|
112
125
|
mintLeaf: () => mintLeaf,
|
|
126
|
+
observingRoutedRoute: () => observingRoutedRoute,
|
|
113
127
|
parseConnectTarget: () => parseConnectTarget,
|
|
114
128
|
prepareClaudeLaunch: () => prepareClaudeLaunch,
|
|
115
129
|
prepareLaunch: () => prepareLaunch,
|
|
116
130
|
profileExists: () => profileExists,
|
|
117
131
|
providerExists: () => providerExists,
|
|
132
|
+
rcEventWriteResultFromAnswer: () => rcEventWriteResultFromAnswer,
|
|
133
|
+
rcObserverAsPassthrough: () => rcObserverAsPassthrough,
|
|
118
134
|
readActiveIdentity: () => readActiveIdentity,
|
|
119
135
|
readDirectoryRules: () => readDirectoryRules,
|
|
120
136
|
readFarmManifest: () => readFarmManifest,
|
|
@@ -126,6 +142,8 @@ __export(index_exports, {
|
|
|
126
142
|
readUsageSnapshot: () => readUsageSnapshot,
|
|
127
143
|
realConnectCertStore: () => realConnectCertStore,
|
|
128
144
|
realConnectEffects: () => realConnectEffects,
|
|
145
|
+
realRcControlTransport: () => realRcControlTransport,
|
|
146
|
+
realRcEventDial: () => realRcEventDial,
|
|
129
147
|
recoverFarm: () => recoverFarm,
|
|
130
148
|
recoveryDiagnostics: () => recoveryDiagnostics,
|
|
131
149
|
removeDirectoryRule: () => removeDirectoryRule,
|
|
@@ -2289,7 +2307,7 @@ function recoveryDiagnostics(recovery, identity) {
|
|
|
2289
2307
|
}
|
|
2290
2308
|
|
|
2291
2309
|
// src/frontdoor/connect.ts
|
|
2292
|
-
var
|
|
2310
|
+
var import_node_crypto5 = require("node:crypto");
|
|
2293
2311
|
var fs2 = __toESM(require("node:fs"), 1);
|
|
2294
2312
|
|
|
2295
2313
|
// src/frontdoor/x509.ts
|
|
@@ -2615,6 +2633,7 @@ function pumpTapSession(secure, openUpstream, tap, prefix = Buffer.alloc(0)) {
|
|
|
2615
2633
|
}
|
|
2616
2634
|
|
|
2617
2635
|
// src/frontdoor/capability.ts
|
|
2636
|
+
var import_node_crypto4 = require("node:crypto");
|
|
2618
2637
|
var CONNECT_PROXY_USER = "agent-shim";
|
|
2619
2638
|
var CONNECT_PROXY_REALM = "agent-shim front door";
|
|
2620
2639
|
var BASIC_SCHEME = "basic";
|
|
@@ -2635,6 +2654,18 @@ function capabilityFromProxyAuthorization(value) {
|
|
|
2635
2654
|
const password = userPass.slice(colon + 1);
|
|
2636
2655
|
return password === "" ? void 0 : password;
|
|
2637
2656
|
}
|
|
2657
|
+
function digest(token) {
|
|
2658
|
+
return (0, import_node_crypto4.createHash)("sha256").update(token, "utf8").digest();
|
|
2659
|
+
}
|
|
2660
|
+
function isLiveCapability(presented, live) {
|
|
2661
|
+
const presentedDigest = digest(presented);
|
|
2662
|
+
let matched = false;
|
|
2663
|
+
for (const token of live) {
|
|
2664
|
+
const equal = (0, import_node_crypto4.timingSafeEqual)(presentedDigest, digest(token));
|
|
2665
|
+
matched = equal || matched;
|
|
2666
|
+
}
|
|
2667
|
+
return matched;
|
|
2668
|
+
}
|
|
2638
2669
|
|
|
2639
2670
|
// src/frontdoor/route.ts
|
|
2640
2671
|
var IDENTITY_HEADER = "x-agent-shim-identity";
|
|
@@ -2935,8 +2966,8 @@ function ensureCa(store, generate) {
|
|
|
2935
2966
|
}
|
|
2936
2967
|
function caParses(ca) {
|
|
2937
2968
|
try {
|
|
2938
|
-
new
|
|
2939
|
-
(0,
|
|
2969
|
+
new import_node_crypto5.X509Certificate(ca.certPem);
|
|
2970
|
+
(0, import_node_crypto5.createPrivateKey)(ca.keyPem);
|
|
2940
2971
|
return true;
|
|
2941
2972
|
} catch {
|
|
2942
2973
|
return false;
|
|
@@ -2945,14 +2976,14 @@ function caParses(ca) {
|
|
|
2945
2976
|
var TAP_ALPN_PROTOCOLS = ["h2", "http/1.1"];
|
|
2946
2977
|
async function startConnectServer(config, effects, preferredPort) {
|
|
2947
2978
|
const tunnelTokens = /* @__PURE__ */ new WeakMap();
|
|
2948
|
-
const onUpgrade = (host) => (
|
|
2949
|
-
config.capture?.upgrade(
|
|
2979
|
+
const onUpgrade = (host) => (request4, socket, head) => {
|
|
2980
|
+
config.capture?.upgrade(request4);
|
|
2950
2981
|
if (head.length > 0) {
|
|
2951
2982
|
socket.pause();
|
|
2952
2983
|
socket.unshift(head);
|
|
2953
2984
|
}
|
|
2954
|
-
const headText = `${
|
|
2955
|
-
${
|
|
2985
|
+
const headText = `${request4.method ?? "GET"} ${request4.url ?? "/"} HTTP/1.1\r
|
|
2986
|
+
${request4.rawHeaders.reduce((accumulated, value, index) => index % 2 === 0 ? `${accumulated}${value}: ` : `${accumulated}${value}\r
|
|
2956
2987
|
`, "")}\r
|
|
2957
2988
|
`;
|
|
2958
2989
|
pumpTapSession(socket, async (clientAlpn) => await effects.connectTlsUpstream(host, HTTPS_PORT, clientAlpn), config.capture?.tapStream?.(host), Buffer.from(headText, "utf8"));
|
|
@@ -2965,16 +2996,16 @@ ${request3.rawHeaders.reduce((accumulated, value, index) => index % 2 === 0 ? `$
|
|
|
2965
2996
|
const tlsAcceptor2 = effects.createTlsAcceptor(config.leafFor(host), serveSecure2, TAP_ALPN_PROTOCOLS);
|
|
2966
2997
|
return { host, serveSecure: serveSecure2, tlsAcceptor: tlsAcceptor2 };
|
|
2967
2998
|
}
|
|
2968
|
-
const handler = (
|
|
2969
|
-
if (host === config.routedHost && servedByPipeline(
|
|
2970
|
-
const tunnelToken = tunnelTokens.get(
|
|
2971
|
-
if (tunnelToken !== void 0 &&
|
|
2972
|
-
|
|
2999
|
+
const handler = (request4, response) => {
|
|
3000
|
+
if (host === config.routedHost && servedByPipeline(request4.url)) {
|
|
3001
|
+
const tunnelToken = tunnelTokens.get(request4.socket);
|
|
3002
|
+
if (tunnelToken !== void 0 && request4.headers[AUTH_HEADER] === void 0) {
|
|
3003
|
+
request4.headers[AUTH_HEADER] = tunnelToken;
|
|
2973
3004
|
}
|
|
2974
|
-
config.serveRouted(
|
|
3005
|
+
config.serveRouted(request4, response);
|
|
2975
3006
|
return;
|
|
2976
3007
|
}
|
|
2977
|
-
effects.forwardHttp(config.upstreamFor(host),
|
|
3008
|
+
effects.forwardHttp(config.upstreamFor(host), request4, response, config.capture?.observePassthrough(request4));
|
|
2978
3009
|
};
|
|
2979
3010
|
const httpSession = effects.createHttpSession(handler, onUpgrade(host));
|
|
2980
3011
|
const serveSecure = (secure, capability) => {
|
|
@@ -3016,7 +3047,7 @@ ${request3.rawHeaders.reduce((accumulated, value, index) => index % 2 === 0 ? `$
|
|
|
3016
3047
|
}
|
|
3017
3048
|
};
|
|
3018
3049
|
}
|
|
3019
|
-
function createConnectionLedger(limits,
|
|
3050
|
+
function createConnectionLedger(limits, isLiveCapability2) {
|
|
3020
3051
|
const pending = /* @__PURE__ */ new Map();
|
|
3021
3052
|
const authenticated = /* @__PURE__ */ new Map();
|
|
3022
3053
|
let tunnels = 0;
|
|
@@ -3029,7 +3060,7 @@ function createConnectionLedger(limits, isLiveCapability) {
|
|
|
3029
3060
|
};
|
|
3030
3061
|
const revalidation = setInterval(() => {
|
|
3031
3062
|
for (const [capability, sockets] of authenticated) {
|
|
3032
|
-
if (!
|
|
3063
|
+
if (!isLiveCapability2(capability)) {
|
|
3033
3064
|
for (const socket of sockets) {
|
|
3034
3065
|
socket.destroy();
|
|
3035
3066
|
}
|
|
@@ -3271,6 +3302,40 @@ var ExemptTlsAgent = class extends https.Agent {
|
|
|
3271
3302
|
return tls.connect(tlsOptions);
|
|
3272
3303
|
}
|
|
3273
3304
|
};
|
|
3305
|
+
function realRcEventDial(target = { host: CONNECT_INTERCEPT_HOST, port: HTTPS_PORT }) {
|
|
3306
|
+
const agent = new ExemptTlsAgent();
|
|
3307
|
+
return {
|
|
3308
|
+
writeEvents: async (sessionId, headers, body) => await new Promise((resolve, reject) => {
|
|
3309
|
+
const options = {
|
|
3310
|
+
host: target.host,
|
|
3311
|
+
port: target.port,
|
|
3312
|
+
method: "POST",
|
|
3313
|
+
path: `/v1/code/sessions/${encodeURIComponent(sessionId)}/events`,
|
|
3314
|
+
// 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.
|
|
3315
|
+
headers: { ...headers, host: target.servername ?? target.host, "content-length": String(Buffer.byteLength(body, "utf8")) },
|
|
3316
|
+
agent
|
|
3317
|
+
};
|
|
3318
|
+
options.servername = target.servername ?? target.host;
|
|
3319
|
+
if (target.ca !== void 0) {
|
|
3320
|
+
options.ca = [...target.ca];
|
|
3321
|
+
}
|
|
3322
|
+
if (target.rejectUnauthorized !== void 0) {
|
|
3323
|
+
options.rejectUnauthorized = target.rejectUnauthorized;
|
|
3324
|
+
}
|
|
3325
|
+
const request4 = https.request(options, (response) => {
|
|
3326
|
+
const chunks = [];
|
|
3327
|
+
response.on("data", (chunk) => {
|
|
3328
|
+
chunks.push(chunk);
|
|
3329
|
+
});
|
|
3330
|
+
response.on("end", () => {
|
|
3331
|
+
resolve({ status: response.statusCode ?? HTTP_BAD_GATEWAY, body: Buffer.concat(chunks).toString("utf8") });
|
|
3332
|
+
});
|
|
3333
|
+
});
|
|
3334
|
+
request4.once("error", reject);
|
|
3335
|
+
request4.end(body);
|
|
3336
|
+
})
|
|
3337
|
+
};
|
|
3338
|
+
}
|
|
3274
3339
|
function realConnectEffects() {
|
|
3275
3340
|
const plainAgent = new ExemptHttpAgent();
|
|
3276
3341
|
const tlsAgent = new ExemptTlsAgent();
|
|
@@ -3376,8 +3441,8 @@ function realConnectEffects() {
|
|
|
3376
3441
|
socket.destroy();
|
|
3377
3442
|
});
|
|
3378
3443
|
if (onUpgrade !== void 0) {
|
|
3379
|
-
server.on("upgrade", (
|
|
3380
|
-
onUpgrade(
|
|
3444
|
+
server.on("upgrade", (request4, socket, head) => {
|
|
3445
|
+
onUpgrade(request4, socket, head);
|
|
3381
3446
|
});
|
|
3382
3447
|
}
|
|
3383
3448
|
return {
|
|
@@ -3416,14 +3481,14 @@ function realConnectEffects() {
|
|
|
3416
3481
|
});
|
|
3417
3482
|
socket.once("error", reject);
|
|
3418
3483
|
}),
|
|
3419
|
-
forwardHttp: (target,
|
|
3420
|
-
const headers = forwardableHeaders(
|
|
3484
|
+
forwardHttp: (target, request4, response, observer) => {
|
|
3485
|
+
const headers = forwardableHeaders(request4.headers);
|
|
3421
3486
|
const options = {
|
|
3422
3487
|
host: target.host,
|
|
3423
3488
|
port: target.port,
|
|
3424
3489
|
lookup: realAddressLookup,
|
|
3425
|
-
method:
|
|
3426
|
-
path:
|
|
3490
|
+
method: request4.method,
|
|
3491
|
+
path: request4.url,
|
|
3427
3492
|
headers,
|
|
3428
3493
|
agent: target.tls ? tlsAgent : plainAgent
|
|
3429
3494
|
};
|
|
@@ -3458,15 +3523,20 @@ function realConnectEffects() {
|
|
|
3458
3523
|
response.end(body);
|
|
3459
3524
|
});
|
|
3460
3525
|
if (observer !== void 0) {
|
|
3461
|
-
|
|
3526
|
+
request4.on("data", (chunk) => {
|
|
3462
3527
|
observer.onRequestChunk(chunk);
|
|
3463
3528
|
});
|
|
3529
|
+
if (observer.onRequestEnd !== void 0) {
|
|
3530
|
+
request4.on("end", () => {
|
|
3531
|
+
observer.onRequestEnd?.();
|
|
3532
|
+
});
|
|
3533
|
+
}
|
|
3464
3534
|
response.on("close", () => {
|
|
3465
3535
|
observer.onEnd();
|
|
3466
3536
|
});
|
|
3467
3537
|
}
|
|
3468
|
-
|
|
3469
|
-
|
|
3538
|
+
request4.pipe(upstream);
|
|
3539
|
+
request4.socket.setNoDelay(true);
|
|
3470
3540
|
upstream.on("socket", (socket) => {
|
|
3471
3541
|
socket.setNoDelay(true);
|
|
3472
3542
|
});
|
|
@@ -3474,11 +3544,831 @@ function realConnectEffects() {
|
|
|
3474
3544
|
};
|
|
3475
3545
|
}
|
|
3476
3546
|
|
|
3547
|
+
// src/codex/http.ts
|
|
3548
|
+
var HTTP_STATUS = {
|
|
3549
|
+
ok: 200,
|
|
3550
|
+
badRequest: 400,
|
|
3551
|
+
unauthorized: 401,
|
|
3552
|
+
forbidden: 403,
|
|
3553
|
+
notFound: 404,
|
|
3554
|
+
methodNotAllowed: 405,
|
|
3555
|
+
payloadTooLarge: 413,
|
|
3556
|
+
tooManyRequests: 429,
|
|
3557
|
+
/** 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. */
|
|
3558
|
+
clientClosedRequest: 499,
|
|
3559
|
+
internalServerError: 500,
|
|
3560
|
+
badGateway: 502
|
|
3561
|
+
};
|
|
3562
|
+
|
|
3563
|
+
// src/frontdoor/rcSessions.ts
|
|
3564
|
+
var RC_SESSIONS_PATH_PREFIX = "/v1/code/sessions";
|
|
3565
|
+
var RC_SESSION_ID_PREFIX = "cse_";
|
|
3566
|
+
var RC_IDLE_EXPIRY_MS = 45e3;
|
|
3567
|
+
var RC_CREATE_RESPONSE_CAP_BYTES = 8192;
|
|
3568
|
+
var CONFLICT_REASON_HEADER = "x-ccr-conflict-reason";
|
|
3569
|
+
var RC_PENDING_DEADLINE_MS = 14e3;
|
|
3570
|
+
var RC_REQUEST_PARSE_CAP_BYTES = 262144;
|
|
3571
|
+
var RC_PENDING_SUMMARY_EXCERPT_CHARS = 300;
|
|
3572
|
+
var SUCCESS_STATUS_MAX_EXCLUSIVE = 300;
|
|
3573
|
+
function isSuccessful(status) {
|
|
3574
|
+
return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE;
|
|
3575
|
+
}
|
|
3576
|
+
function singleHeader(headers, name) {
|
|
3577
|
+
const value = headers[name];
|
|
3578
|
+
return typeof value === "string" ? value : void 0;
|
|
3579
|
+
}
|
|
3580
|
+
function sessionCreateId(body) {
|
|
3581
|
+
let parsed;
|
|
3582
|
+
try {
|
|
3583
|
+
parsed = JSON.parse(body);
|
|
3584
|
+
} catch {
|
|
3585
|
+
return void 0;
|
|
3586
|
+
}
|
|
3587
|
+
if (typeof parsed !== "object" || parsed === null || !("session" in parsed)) {
|
|
3588
|
+
return void 0;
|
|
3589
|
+
}
|
|
3590
|
+
const session = parsed.session;
|
|
3591
|
+
if (typeof session !== "object" || session === null || !("id" in session)) {
|
|
3592
|
+
return void 0;
|
|
3593
|
+
}
|
|
3594
|
+
const id = session.id;
|
|
3595
|
+
return typeof id === "string" && id.startsWith(RC_SESSION_ID_PREFIX) ? id : void 0;
|
|
3596
|
+
}
|
|
3597
|
+
function isRecord2(value) {
|
|
3598
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3599
|
+
}
|
|
3600
|
+
function parseJsonObject(body) {
|
|
3601
|
+
let parsed;
|
|
3602
|
+
try {
|
|
3603
|
+
parsed = JSON.parse(body);
|
|
3604
|
+
} catch {
|
|
3605
|
+
return void 0;
|
|
3606
|
+
}
|
|
3607
|
+
return isRecord2(parsed) ? parsed : void 0;
|
|
3608
|
+
}
|
|
3609
|
+
function nonEmptyString(record, field) {
|
|
3610
|
+
const value = record === void 0 ? void 0 : record[field];
|
|
3611
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
3612
|
+
}
|
|
3613
|
+
function finiteNumber(record, field) {
|
|
3614
|
+
const value = record === void 0 ? void 0 : record[field];
|
|
3615
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3616
|
+
}
|
|
3617
|
+
function excerpt(text) {
|
|
3618
|
+
return text.length <= RC_PENDING_SUMMARY_EXCERPT_CHARS ? text : `${text.slice(0, RC_PENDING_SUMMARY_EXCERPT_CHARS)}...`;
|
|
3619
|
+
}
|
|
3620
|
+
function payloadEventsOf(body) {
|
|
3621
|
+
const parsed = parseJsonObject(body);
|
|
3622
|
+
const events = parsed === void 0 ? void 0 : parsed.events;
|
|
3623
|
+
if (!Array.isArray(events)) {
|
|
3624
|
+
return [];
|
|
3625
|
+
}
|
|
3626
|
+
const payloads = [];
|
|
3627
|
+
for (const event of events) {
|
|
3628
|
+
if (!isRecord2(event)) {
|
|
3629
|
+
continue;
|
|
3630
|
+
}
|
|
3631
|
+
const payload = event.payload;
|
|
3632
|
+
if (!isRecord2(payload)) {
|
|
3633
|
+
continue;
|
|
3634
|
+
}
|
|
3635
|
+
payloads.push(payload);
|
|
3636
|
+
}
|
|
3637
|
+
return payloads;
|
|
3638
|
+
}
|
|
3639
|
+
function pendingOfControlRequestPayload(payload) {
|
|
3640
|
+
if (payload.type !== "control_request") {
|
|
3641
|
+
return void 0;
|
|
3642
|
+
}
|
|
3643
|
+
const requestId = nonEmptyString(payload, "request_id") ?? nonEmptyString(payload, "uuid");
|
|
3644
|
+
if (requestId === void 0) {
|
|
3645
|
+
return void 0;
|
|
3646
|
+
}
|
|
3647
|
+
const request4 = isRecord2(payload.request) ? payload.request : void 0;
|
|
3648
|
+
const subtype = nonEmptyString(request4, "subtype");
|
|
3649
|
+
const type = subtype ?? "control_request";
|
|
3650
|
+
let summary = "";
|
|
3651
|
+
if (subtype === "can_use_tool") {
|
|
3652
|
+
const toolName = nonEmptyString(request4, "tool_name") ?? "unknown tool";
|
|
3653
|
+
const input = request4 === void 0 ? void 0 : request4.input;
|
|
3654
|
+
summary = input === void 0 ? toolName : `${toolName} ${excerpt(JSON.stringify(input))}`;
|
|
3655
|
+
} else if (request4 !== void 0) {
|
|
3656
|
+
summary = excerpt(JSON.stringify(request4));
|
|
3657
|
+
}
|
|
3658
|
+
return { requestId, type, summary };
|
|
3659
|
+
}
|
|
3660
|
+
function answeredRequestIdsOfControlResponsePayload(payload) {
|
|
3661
|
+
if (payload.type !== "control_response") {
|
|
3662
|
+
return [];
|
|
3663
|
+
}
|
|
3664
|
+
const response = isRecord2(payload.response) ? payload.response : void 0;
|
|
3665
|
+
const id = nonEmptyString(response, "request_id") ?? nonEmptyString(payload, "request_id") ?? nonEmptyString(payload, "uuid");
|
|
3666
|
+
return id === void 0 ? [] : [id];
|
|
3667
|
+
}
|
|
3668
|
+
function parseRcRequestBody(kind, body) {
|
|
3669
|
+
if (kind === "heartbeat") {
|
|
3670
|
+
return { controlRequests: [], answeredRequestIds: [], idleSeconds: finiteNumber(parseJsonObject(body), "idle_seconds"), workerStatus: void 0 };
|
|
3671
|
+
}
|
|
3672
|
+
if (kind === "worker-registration") {
|
|
3673
|
+
return { controlRequests: [], answeredRequestIds: [], idleSeconds: void 0, workerStatus: nonEmptyString(parseJsonObject(body), "status") };
|
|
3674
|
+
}
|
|
3675
|
+
const controlRequests = [];
|
|
3676
|
+
const answeredRequestIds = [];
|
|
3677
|
+
for (const payload of payloadEventsOf(body)) {
|
|
3678
|
+
const pending = pendingOfControlRequestPayload(payload);
|
|
3679
|
+
if (pending !== void 0) {
|
|
3680
|
+
controlRequests.push({ requestId: pending.requestId, type: pending.type, summary: pending.summary });
|
|
3681
|
+
continue;
|
|
3682
|
+
}
|
|
3683
|
+
answeredRequestIds.push(...answeredRequestIdsOfControlResponsePayload(payload));
|
|
3684
|
+
}
|
|
3685
|
+
return { controlRequests, answeredRequestIds, idleSeconds: void 0, workerStatus: void 0 };
|
|
3686
|
+
}
|
|
3687
|
+
function createRcSessionTracker(deps) {
|
|
3688
|
+
const entries = /* @__PURE__ */ new Map();
|
|
3689
|
+
const expire = (id) => {
|
|
3690
|
+
entries.delete(id);
|
|
3691
|
+
};
|
|
3692
|
+
const expirePendings = (entry, now) => {
|
|
3693
|
+
for (const [requestId, pending] of entry.pending) {
|
|
3694
|
+
if (now - pending.observedAt >= RC_PENDING_DEADLINE_MS) {
|
|
3695
|
+
entry.pending.delete(requestId);
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3698
|
+
};
|
|
3699
|
+
const sweep = () => {
|
|
3700
|
+
for (const [id, entry] of entries) {
|
|
3701
|
+
if (deps.now() - entry.lastSeenAt >= deps.idleMs) {
|
|
3702
|
+
expire(id);
|
|
3703
|
+
continue;
|
|
3704
|
+
}
|
|
3705
|
+
expirePendings(entry, deps.now());
|
|
3706
|
+
}
|
|
3707
|
+
};
|
|
3708
|
+
const timer = setInterval(sweep, deps.idleMs);
|
|
3709
|
+
timer.unref();
|
|
3710
|
+
const birth = (id, facts) => {
|
|
3711
|
+
const record = { id, createdAt: deps.now(), lastSeenAt: deps.now(), ...facts, pending: /* @__PURE__ */ new Map(), workerState: void 0, workerIdleSeconds: void 0 };
|
|
3712
|
+
entries.set(id, record);
|
|
3713
|
+
return record;
|
|
3714
|
+
};
|
|
3715
|
+
const observeExchange = (request4) => {
|
|
3716
|
+
const method = (request4.method ?? "").toUpperCase();
|
|
3717
|
+
const path29 = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
|
|
3718
|
+
if (!path29.startsWith(RC_SESSIONS_PATH_PREFIX)) {
|
|
3719
|
+
return void 0;
|
|
3720
|
+
}
|
|
3721
|
+
const tail = path29.slice(RC_SESSIONS_PATH_PREFIX.length);
|
|
3722
|
+
const facts = {
|
|
3723
|
+
authorization: singleHeader(request4.headers, "authorization"),
|
|
3724
|
+
anthropicVersion: singleHeader(request4.headers, "anthropic-version"),
|
|
3725
|
+
anthropicClientPlatform: singleHeader(request4.headers, "anthropic-client-platform")
|
|
3726
|
+
};
|
|
3727
|
+
if (tail === "") {
|
|
3728
|
+
if (method !== "POST") {
|
|
3729
|
+
return void 0;
|
|
3730
|
+
}
|
|
3731
|
+
let accepted2 = false;
|
|
3732
|
+
let buffered2 = 0;
|
|
3733
|
+
let body = "";
|
|
3734
|
+
return {
|
|
3735
|
+
onResponse: (status) => {
|
|
3736
|
+
accepted2 = isSuccessful(status);
|
|
3737
|
+
},
|
|
3738
|
+
onBodyChunk: (chunk) => {
|
|
3739
|
+
if (!accepted2 || buffered2 >= RC_CREATE_RESPONSE_CAP_BYTES) {
|
|
3740
|
+
return;
|
|
3741
|
+
}
|
|
3742
|
+
const text = Buffer.from(chunk).toString("utf8");
|
|
3743
|
+
body = body + text.slice(0, RC_CREATE_RESPONSE_CAP_BYTES - buffered2);
|
|
3744
|
+
buffered2 = body.length;
|
|
3745
|
+
},
|
|
3746
|
+
onEnd: () => {
|
|
3747
|
+
if (!accepted2) {
|
|
3748
|
+
return;
|
|
3749
|
+
}
|
|
3750
|
+
const id2 = sessionCreateId(body);
|
|
3751
|
+
if (id2 === void 0) {
|
|
3752
|
+
return;
|
|
3753
|
+
}
|
|
3754
|
+
birth(id2, facts);
|
|
3755
|
+
}
|
|
3756
|
+
};
|
|
3757
|
+
}
|
|
3758
|
+
const rest = tail.slice(1);
|
|
3759
|
+
const [id] = rest.split("/");
|
|
3760
|
+
if (id?.startsWith(RC_SESSION_ID_PREFIX) !== true) {
|
|
3761
|
+
return void 0;
|
|
3762
|
+
}
|
|
3763
|
+
const entry = entries.get(id) ?? birth(id, facts);
|
|
3764
|
+
entry.lastSeenAt = deps.now();
|
|
3765
|
+
entry.authorization = facts.authorization ?? entry.authorization;
|
|
3766
|
+
entry.anthropicVersion = facts.anthropicVersion ?? entry.anthropicVersion;
|
|
3767
|
+
entry.anthropicClientPlatform = facts.anthropicClientPlatform ?? entry.anthropicClientPlatform;
|
|
3768
|
+
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;
|
|
3769
|
+
let chunks = [];
|
|
3770
|
+
let buffered = 0;
|
|
3771
|
+
let oversized = false;
|
|
3772
|
+
let requestEnded = false;
|
|
3773
|
+
let accepted;
|
|
3774
|
+
let filed = false;
|
|
3775
|
+
const file = () => {
|
|
3776
|
+
if (filed || accepted !== true || !requestEnded || bodyKind === void 0) {
|
|
3777
|
+
return;
|
|
3778
|
+
}
|
|
3779
|
+
filed = true;
|
|
3780
|
+
const factsOfBody = oversized ? void 0 : parseRcRequestBody(bodyKind, Buffer.concat(chunks).toString("utf8"));
|
|
3781
|
+
chunks = [];
|
|
3782
|
+
const entryNow = entries.get(id);
|
|
3783
|
+
if (entryNow === void 0 || factsOfBody === void 0) {
|
|
3784
|
+
return;
|
|
3785
|
+
}
|
|
3786
|
+
const now = deps.now();
|
|
3787
|
+
for (const controlRequest of factsOfBody.controlRequests) {
|
|
3788
|
+
entryNow.pending.set(controlRequest.requestId, { sessionId: id, ...controlRequest, observedAt: now });
|
|
3789
|
+
}
|
|
3790
|
+
for (const requestId of factsOfBody.answeredRequestIds) {
|
|
3791
|
+
entryNow.pending.delete(requestId);
|
|
3792
|
+
}
|
|
3793
|
+
if (factsOfBody.idleSeconds !== void 0) {
|
|
3794
|
+
entryNow.workerIdleSeconds = { value: factsOfBody.idleSeconds, observedAt: now };
|
|
3795
|
+
}
|
|
3796
|
+
if (factsOfBody.workerStatus !== void 0) {
|
|
3797
|
+
entryNow.workerState = { value: factsOfBody.workerStatus, observedAt: now };
|
|
3798
|
+
}
|
|
3799
|
+
};
|
|
3800
|
+
const archiving = method === "POST" && rest === `${id}/archive`;
|
|
3801
|
+
return {
|
|
3802
|
+
...bodyKind === void 0 ? {} : {
|
|
3803
|
+
onRequestChunk: (chunk) => {
|
|
3804
|
+
if (oversized) {
|
|
3805
|
+
return;
|
|
3806
|
+
}
|
|
3807
|
+
chunks.push(Buffer.from(chunk));
|
|
3808
|
+
buffered += chunk.length;
|
|
3809
|
+
if (buffered > RC_REQUEST_PARSE_CAP_BYTES) {
|
|
3810
|
+
oversized = true;
|
|
3811
|
+
chunks = [];
|
|
3812
|
+
}
|
|
3813
|
+
},
|
|
3814
|
+
onRequestEnd: () => {
|
|
3815
|
+
requestEnded = true;
|
|
3816
|
+
file();
|
|
3817
|
+
}
|
|
3818
|
+
},
|
|
3819
|
+
onResponse: (status, headers) => {
|
|
3820
|
+
if (singleHeader(headers, CONFLICT_REASON_HEADER) !== void 0 || archiving && isSuccessful(status)) {
|
|
3821
|
+
expire(id);
|
|
3822
|
+
return;
|
|
3823
|
+
}
|
|
3824
|
+
accepted = isSuccessful(status);
|
|
3825
|
+
file();
|
|
3826
|
+
},
|
|
3827
|
+
onBodyChunk: () => {
|
|
3828
|
+
},
|
|
3829
|
+
onEnd: () => {
|
|
3830
|
+
}
|
|
3831
|
+
};
|
|
3832
|
+
};
|
|
3833
|
+
const summaryOf = ({ id, createdAt, lastSeenAt }) => ({ id, createdAt, lastSeenAt });
|
|
3834
|
+
const pendingSummaryOf = ({ sessionId, requestId, type, summary, observedAt }) => ({ sessionId, requestId, type, summary, observedAt });
|
|
3835
|
+
const sortedEntries = () => [...entries.values()].sort((left, right) => left.createdAt - right.createdAt || (left.id < right.id ? -1 : 1));
|
|
3836
|
+
const pendingSorted = (entry) => [...entry.pending.values()].sort((left, right) => left.observedAt - right.observedAt || (left.requestId < right.requestId ? -1 : 1)).map(pendingSummaryOf);
|
|
3837
|
+
return {
|
|
3838
|
+
observeExchange,
|
|
3839
|
+
list: () => {
|
|
3840
|
+
sweep();
|
|
3841
|
+
return sortedEntries().map(summaryOf);
|
|
3842
|
+
},
|
|
3843
|
+
statusOf: (sessionId) => {
|
|
3844
|
+
sweep();
|
|
3845
|
+
return sortedEntries().filter((entry) => sessionId === void 0 || entry.id === sessionId).map((entry) => ({ ...summaryOf(entry), workerState: entry.workerState, workerIdleSeconds: entry.workerIdleSeconds, pending: pendingSorted(entry) }));
|
|
3846
|
+
},
|
|
3847
|
+
pendingOf: (sessionId) => {
|
|
3848
|
+
sweep();
|
|
3849
|
+
return sortedEntries().filter((entry) => sessionId === void 0 || entry.id === sessionId).flatMap((entry) => pendingSorted(entry));
|
|
3850
|
+
},
|
|
3851
|
+
credentialOf: (sessionId) => {
|
|
3852
|
+
const entry = entries.get(sessionId);
|
|
3853
|
+
return entry === void 0 ? void 0 : { authorization: entry.authorization, anthropicVersion: entry.anthropicVersion, anthropicClientPlatform: entry.anthropicClientPlatform };
|
|
3854
|
+
},
|
|
3855
|
+
completePending: (sessionId, requestId) => {
|
|
3856
|
+
entries.get(sessionId)?.pending.delete(requestId);
|
|
3857
|
+
},
|
|
3858
|
+
close: () => {
|
|
3859
|
+
clearInterval(timer);
|
|
3860
|
+
}
|
|
3861
|
+
};
|
|
3862
|
+
}
|
|
3863
|
+
function observingRoutedRoute(route, tracker) {
|
|
3864
|
+
return {
|
|
3865
|
+
...route,
|
|
3866
|
+
serve: async (request4, response) => {
|
|
3867
|
+
const observed = tracker.observeExchange({ method: request4.method, url: request4.url, headers: request4.headers });
|
|
3868
|
+
if (observed?.onRequestChunk !== void 0) {
|
|
3869
|
+
request4.body.on("data", (chunk) => {
|
|
3870
|
+
observed.onRequestChunk?.(chunk);
|
|
3871
|
+
});
|
|
3872
|
+
request4.body.on("end", () => {
|
|
3873
|
+
observed.onRequestEnd?.();
|
|
3874
|
+
});
|
|
3875
|
+
}
|
|
3876
|
+
await route.serve(request4, observed === void 0 ? response : teeRoutedResponse(response, observed));
|
|
3877
|
+
}
|
|
3878
|
+
};
|
|
3879
|
+
}
|
|
3880
|
+
function teeRoutedResponse(response, observer) {
|
|
3881
|
+
return {
|
|
3882
|
+
get headersSent() {
|
|
3883
|
+
return response.headersSent;
|
|
3884
|
+
},
|
|
3885
|
+
start: (status, headers) => {
|
|
3886
|
+
observer.onResponse(status, headers);
|
|
3887
|
+
response.start(status, headers);
|
|
3888
|
+
},
|
|
3889
|
+
flush: () => {
|
|
3890
|
+
response.flush();
|
|
3891
|
+
},
|
|
3892
|
+
write: async (chunk) => {
|
|
3893
|
+
observer.onBodyChunk(typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk);
|
|
3894
|
+
await response.write(chunk);
|
|
3895
|
+
},
|
|
3896
|
+
end: () => {
|
|
3897
|
+
response.end();
|
|
3898
|
+
observer.onEnd();
|
|
3899
|
+
},
|
|
3900
|
+
destroy: () => {
|
|
3901
|
+
response.destroy();
|
|
3902
|
+
observer.onEnd();
|
|
3903
|
+
}
|
|
3904
|
+
};
|
|
3905
|
+
}
|
|
3906
|
+
function rcObserverAsPassthrough(observer) {
|
|
3907
|
+
return {
|
|
3908
|
+
onRequestChunk: (chunk) => {
|
|
3909
|
+
observer.onRequestChunk?.(chunk);
|
|
3910
|
+
},
|
|
3911
|
+
...observer.onRequestEnd === void 0 ? {} : {
|
|
3912
|
+
onRequestEnd: () => {
|
|
3913
|
+
observer.onRequestEnd?.();
|
|
3914
|
+
}
|
|
3915
|
+
},
|
|
3916
|
+
onResponse: (status, headers) => {
|
|
3917
|
+
observer.onResponse(status, headers);
|
|
3918
|
+
},
|
|
3919
|
+
onResponseChunk: (chunk) => {
|
|
3920
|
+
observer.onBodyChunk(chunk);
|
|
3921
|
+
},
|
|
3922
|
+
onEnd: () => {
|
|
3923
|
+
observer.onEnd();
|
|
3924
|
+
}
|
|
3925
|
+
};
|
|
3926
|
+
}
|
|
3927
|
+
function buildRcUserMessagePayload(uuid, sessionId, text) {
|
|
3928
|
+
return {
|
|
3929
|
+
type: "user",
|
|
3930
|
+
uuid,
|
|
3931
|
+
session_id: sessionId,
|
|
3932
|
+
parent_tool_use_id: null,
|
|
3933
|
+
message: { role: "user", content: text }
|
|
3934
|
+
};
|
|
3935
|
+
}
|
|
3936
|
+
function buildRcEventWriteBody(payload) {
|
|
3937
|
+
return { events: [{ payload }] };
|
|
3938
|
+
}
|
|
3939
|
+
function sequenceNumsOf(body) {
|
|
3940
|
+
let parsed;
|
|
3941
|
+
try {
|
|
3942
|
+
parsed = JSON.parse(body);
|
|
3943
|
+
} catch {
|
|
3944
|
+
return void 0;
|
|
3945
|
+
}
|
|
3946
|
+
if (typeof parsed !== "object" || parsed === null || !("results" in parsed) || !Array.isArray(parsed.results)) {
|
|
3947
|
+
return void 0;
|
|
3948
|
+
}
|
|
3949
|
+
const sequenceNums = [];
|
|
3950
|
+
const results = parsed.results;
|
|
3951
|
+
for (const result of results) {
|
|
3952
|
+
if (typeof result !== "object" || result === null || !("sequence_num" in result)) {
|
|
3953
|
+
return void 0;
|
|
3954
|
+
}
|
|
3955
|
+
const sequenceNum = result.sequence_num;
|
|
3956
|
+
if (typeof sequenceNum !== "number") {
|
|
3957
|
+
return void 0;
|
|
3958
|
+
}
|
|
3959
|
+
sequenceNums.push(sequenceNum);
|
|
3960
|
+
}
|
|
3961
|
+
return sequenceNums.length === 0 ? void 0 : sequenceNums;
|
|
3962
|
+
}
|
|
3963
|
+
function rcEventWriteResultFromAnswer(answer) {
|
|
3964
|
+
if (!isSuccessful(answer.status)) {
|
|
3965
|
+
if (answer.status === HTTP_STATUS.unauthorized) {
|
|
3966
|
+
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}` };
|
|
3967
|
+
}
|
|
3968
|
+
return { ok: false, message: `the API answered HTTP ${String(answer.status)} to the injected event: ${answer.body}` };
|
|
3969
|
+
}
|
|
3970
|
+
const sequenceNums = sequenceNumsOf(answer.body);
|
|
3971
|
+
if (sequenceNums === void 0) {
|
|
3972
|
+
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}` };
|
|
3973
|
+
}
|
|
3974
|
+
return { ok: true, sequenceNums };
|
|
3975
|
+
}
|
|
3976
|
+
function rcWriteHeaders(credential) {
|
|
3977
|
+
const headers = { "content-type": "application/json" };
|
|
3978
|
+
if (credential.authorization !== void 0) {
|
|
3979
|
+
headers.authorization = credential.authorization;
|
|
3980
|
+
}
|
|
3981
|
+
if (credential.anthropicVersion !== void 0) {
|
|
3982
|
+
headers["anthropic-version"] = credential.anthropicVersion;
|
|
3983
|
+
}
|
|
3984
|
+
if (credential.anthropicClientPlatform !== void 0) {
|
|
3985
|
+
headers["anthropic-client-platform"] = credential.anthropicClientPlatform;
|
|
3986
|
+
}
|
|
3987
|
+
return headers;
|
|
3988
|
+
}
|
|
3989
|
+
async function injectRcUserMessage(deps, sessionId, text) {
|
|
3990
|
+
const credential = deps.credentialOf(sessionId);
|
|
3991
|
+
if (credential === void 0) {
|
|
3992
|
+
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)` };
|
|
3993
|
+
}
|
|
3994
|
+
const body = JSON.stringify(buildRcEventWriteBody(buildRcUserMessagePayload(deps.newUuid(), sessionId, text)));
|
|
3995
|
+
let answer;
|
|
3996
|
+
try {
|
|
3997
|
+
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
3998
|
+
} catch (error) {
|
|
3999
|
+
return { ok: false, message: `the door could not reach the API host to inject into session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
4000
|
+
}
|
|
4001
|
+
return rcEventWriteResultFromAnswer(answer);
|
|
4002
|
+
}
|
|
4003
|
+
function buildRcControlResponsePayload(requestId, decision) {
|
|
4004
|
+
const result = decision.approve ? { behavior: "allow" } : { behavior: "deny", message: decision.message ?? "" };
|
|
4005
|
+
return { type: "control_response", response: { subtype: "success", request_id: requestId, response: result } };
|
|
4006
|
+
}
|
|
4007
|
+
async function answerRcControlRequest(deps, sessionId, requestId, decision) {
|
|
4008
|
+
const credential = deps.credentialOf(sessionId);
|
|
4009
|
+
if (credential === void 0) {
|
|
4010
|
+
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)` };
|
|
4011
|
+
}
|
|
4012
|
+
if (!deps.pendingOf(sessionId).some((pending) => pending.requestId === requestId)) {
|
|
4013
|
+
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` };
|
|
4014
|
+
}
|
|
4015
|
+
const body = JSON.stringify(buildRcEventWriteBody(buildRcControlResponsePayload(requestId, decision)));
|
|
4016
|
+
let answer;
|
|
4017
|
+
try {
|
|
4018
|
+
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
4019
|
+
} catch (error) {
|
|
4020
|
+
return { ok: false, message: `the door could not reach the API host to answer on session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
4021
|
+
}
|
|
4022
|
+
const result = rcEventWriteResultFromAnswer(answer);
|
|
4023
|
+
if (result.ok) {
|
|
4024
|
+
deps.completePending(sessionId, requestId);
|
|
4025
|
+
}
|
|
4026
|
+
return result;
|
|
4027
|
+
}
|
|
4028
|
+
|
|
4029
|
+
// src/frontdoor/rcControl.ts
|
|
4030
|
+
var https2 = __toESM(require("node:https"), 1);
|
|
4031
|
+
var CONTROL_PATH_PREFIX = "/__agent-shim/rc";
|
|
4032
|
+
var CONTROL_BODY_CAP_BYTES = 10485760;
|
|
4033
|
+
function answerJson(response, status, body) {
|
|
4034
|
+
const text = JSON.stringify(body);
|
|
4035
|
+
response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text, "utf8")) });
|
|
4036
|
+
response.end(text);
|
|
4037
|
+
}
|
|
4038
|
+
async function readBody(request4) {
|
|
4039
|
+
return await new Promise((resolve, reject) => {
|
|
4040
|
+
const chunks = [];
|
|
4041
|
+
let total = 0;
|
|
4042
|
+
request4.on("data", (chunk) => {
|
|
4043
|
+
total += chunk.length;
|
|
4044
|
+
if (total > CONTROL_BODY_CAP_BYTES) {
|
|
4045
|
+
request4.destroy();
|
|
4046
|
+
reject(new Error("too large"));
|
|
4047
|
+
return;
|
|
4048
|
+
}
|
|
4049
|
+
chunks.push(chunk);
|
|
4050
|
+
});
|
|
4051
|
+
request4.on("end", () => {
|
|
4052
|
+
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
4053
|
+
});
|
|
4054
|
+
request4.on("error", () => {
|
|
4055
|
+
reject(new Error("the request body ended early"));
|
|
4056
|
+
});
|
|
4057
|
+
});
|
|
4058
|
+
}
|
|
4059
|
+
function createRcControlHandler(deps) {
|
|
4060
|
+
const sessionsPath = `${CONTROL_PATH_PREFIX}/sessions`;
|
|
4061
|
+
const statusPath = `${CONTROL_PATH_PREFIX}/status`;
|
|
4062
|
+
const pendingPath = `${CONTROL_PATH_PREFIX}/pending`;
|
|
4063
|
+
const injectPath = `${CONTROL_PATH_PREFIX}/inject`;
|
|
4064
|
+
const answerPath = `${CONTROL_PATH_PREFIX}/answer`;
|
|
4065
|
+
return (request4, response) => {
|
|
4066
|
+
const presented = request4.headers.authorization;
|
|
4067
|
+
const bearerPrefix = "bearer ".length;
|
|
4068
|
+
const token = typeof presented === "string" && presented.slice(0, bearerPrefix).toLowerCase() === "bearer " ? presented.slice(bearerPrefix) : void 0;
|
|
4069
|
+
if (token === void 0 || !isLiveCapability(token, [deps.expectedToken])) {
|
|
4070
|
+
answerJson(response, HTTP_STATUS.unauthorized, { error: "the front door's control routes demand this generation's control token as a Bearer credential" });
|
|
4071
|
+
return;
|
|
4072
|
+
}
|
|
4073
|
+
const url = new URL(request4.url ?? "/", "http://127.0.0.1");
|
|
4074
|
+
const handle = async () => {
|
|
4075
|
+
if (url.pathname === sessionsPath) {
|
|
4076
|
+
if (request4.method !== "GET") {
|
|
4077
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the session list is a GET" });
|
|
4078
|
+
return;
|
|
4079
|
+
}
|
|
4080
|
+
answerJson(response, HTTP_STATUS.ok, { sessions: deps.list() });
|
|
4081
|
+
return;
|
|
4082
|
+
}
|
|
4083
|
+
if (url.pathname === statusPath || url.pathname === pendingPath) {
|
|
4084
|
+
if (request4.method !== "GET") {
|
|
4085
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the status and pending reads are GETs" });
|
|
4086
|
+
return;
|
|
4087
|
+
}
|
|
4088
|
+
const session = url.searchParams.get("session") ?? void 0;
|
|
4089
|
+
if (session !== void 0 && !deps.statusOf(session).some((entry) => entry.id === session)) {
|
|
4090
|
+
answerJson(response, HTTP_STATUS.notFound, { error: `the front door has not observed Remote Control session ${session}: 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)` });
|
|
4091
|
+
return;
|
|
4092
|
+
}
|
|
4093
|
+
answerJson(response, HTTP_STATUS.ok, url.pathname === statusPath ? { statuses: deps.statusOf(session) } : { pending: deps.pendingOf(session) });
|
|
4094
|
+
return;
|
|
4095
|
+
}
|
|
4096
|
+
if (url.pathname === injectPath) {
|
|
4097
|
+
if (request4.method !== "POST") {
|
|
4098
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the inject operation is a POST" });
|
|
4099
|
+
return;
|
|
4100
|
+
}
|
|
4101
|
+
let parsed;
|
|
4102
|
+
try {
|
|
4103
|
+
parsed = JSON.parse(await readBody(request4));
|
|
4104
|
+
} catch {
|
|
4105
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the inject body is not readable JSON within the control body cap" });
|
|
4106
|
+
return;
|
|
4107
|
+
}
|
|
4108
|
+
if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("text" in parsed) || typeof parsed.session !== "string" || typeof parsed.text !== "string" || parsed.session === "" || parsed.text === "") {
|
|
4109
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the inject body must be JSON naming a non-empty session id and text" });
|
|
4110
|
+
return;
|
|
4111
|
+
}
|
|
4112
|
+
const result = await deps.inject(parsed.session, parsed.text);
|
|
4113
|
+
if (result.ok) {
|
|
4114
|
+
answerJson(response, HTTP_STATUS.ok, { session: parsed.session, sequenceNums: result.sequenceNums });
|
|
4115
|
+
return;
|
|
4116
|
+
}
|
|
4117
|
+
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
4118
|
+
return;
|
|
4119
|
+
}
|
|
4120
|
+
if (url.pathname === answerPath) {
|
|
4121
|
+
if (request4.method !== "POST") {
|
|
4122
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the answer operation is a POST" });
|
|
4123
|
+
return;
|
|
4124
|
+
}
|
|
4125
|
+
let parsed;
|
|
4126
|
+
try {
|
|
4127
|
+
parsed = JSON.parse(await readBody(request4));
|
|
4128
|
+
} catch {
|
|
4129
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the answer body is not readable JSON within the control body cap" });
|
|
4130
|
+
return;
|
|
4131
|
+
}
|
|
4132
|
+
if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("request" in parsed) || !("approve" in parsed) || typeof parsed.session !== "string" || typeof parsed.request !== "string" || typeof parsed.approve !== "boolean" || parsed.session === "" || parsed.request === "") {
|
|
4133
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the answer body must be JSON naming a non-empty session id, request id, and an approve boolean" });
|
|
4134
|
+
return;
|
|
4135
|
+
}
|
|
4136
|
+
const text = "text" in parsed && typeof parsed.text === "string" ? parsed.text : void 0;
|
|
4137
|
+
if (parsed.approve && text !== void 0 && text !== "") {
|
|
4138
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "an approval carries no text (the protocol's allow result has no message field); text is the denial message" });
|
|
4139
|
+
return;
|
|
4140
|
+
}
|
|
4141
|
+
const result = await deps.answer(parsed.session, parsed.request, { approve: parsed.approve, message: parsed.approve ? void 0 : text });
|
|
4142
|
+
if (result.ok) {
|
|
4143
|
+
answerJson(response, HTTP_STATUS.ok, { session: parsed.session, request: parsed.request, sequenceNums: result.sequenceNums });
|
|
4144
|
+
return;
|
|
4145
|
+
}
|
|
4146
|
+
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
4147
|
+
return;
|
|
4148
|
+
}
|
|
4149
|
+
answerJson(response, HTTP_STATUS.notFound, { error: `no such control route: ${url.pathname}` });
|
|
4150
|
+
};
|
|
4151
|
+
handle().catch((error) => {
|
|
4152
|
+
answerJson(response, HTTP_STATUS.internalServerError, { error: `the control route failed: ${error instanceof Error ? error.message : String(error)}` });
|
|
4153
|
+
});
|
|
4154
|
+
};
|
|
4155
|
+
}
|
|
4156
|
+
function realRcControlTransport(port, ca) {
|
|
4157
|
+
return {
|
|
4158
|
+
request: async (options) => await new Promise((resolve, reject) => {
|
|
4159
|
+
const request4 = https2.request(
|
|
4160
|
+
{
|
|
4161
|
+
host: "127.0.0.1",
|
|
4162
|
+
port,
|
|
4163
|
+
method: options.method,
|
|
4164
|
+
path: options.path,
|
|
4165
|
+
headers: options.body === void 0 ? options.headers : { ...options.headers, "content-length": String(Buffer.byteLength(options.body, "utf8")) },
|
|
4166
|
+
agent: false,
|
|
4167
|
+
ca: [ca]
|
|
4168
|
+
},
|
|
4169
|
+
(response) => {
|
|
4170
|
+
const chunks = [];
|
|
4171
|
+
response.on("data", (chunk) => {
|
|
4172
|
+
chunks.push(chunk);
|
|
4173
|
+
});
|
|
4174
|
+
response.on("end", () => {
|
|
4175
|
+
resolve({ status: response.statusCode ?? 0, body: Buffer.concat(chunks).toString("utf8") });
|
|
4176
|
+
});
|
|
4177
|
+
}
|
|
4178
|
+
);
|
|
4179
|
+
request4.once("error", reject);
|
|
4180
|
+
if (options.body === void 0) {
|
|
4181
|
+
request4.end();
|
|
4182
|
+
} else {
|
|
4183
|
+
request4.end(options.body);
|
|
4184
|
+
}
|
|
4185
|
+
})
|
|
4186
|
+
};
|
|
4187
|
+
}
|
|
4188
|
+
function controlErrorOf(body) {
|
|
4189
|
+
try {
|
|
4190
|
+
const parsed = JSON.parse(body);
|
|
4191
|
+
if (typeof parsed === "object" && parsed !== null && "error" in parsed && typeof parsed.error === "string") {
|
|
4192
|
+
return parsed.error;
|
|
4193
|
+
}
|
|
4194
|
+
} catch {
|
|
4195
|
+
return body;
|
|
4196
|
+
}
|
|
4197
|
+
return void 0;
|
|
4198
|
+
}
|
|
4199
|
+
function isRcSessionSummary(value) {
|
|
4200
|
+
if (typeof value !== "object" || value === null) {
|
|
4201
|
+
return false;
|
|
4202
|
+
}
|
|
4203
|
+
if (!("id" in value) || !("createdAt" in value) || !("lastSeenAt" in value)) {
|
|
4204
|
+
return false;
|
|
4205
|
+
}
|
|
4206
|
+
return typeof value.id === "string" && typeof value.createdAt === "number" && typeof value.lastSeenAt === "number";
|
|
4207
|
+
}
|
|
4208
|
+
function isRcWorkerFact(value, valueIs) {
|
|
4209
|
+
if (typeof value !== "object" || value === null) {
|
|
4210
|
+
return false;
|
|
4211
|
+
}
|
|
4212
|
+
if (!("value" in value) || !("observedAt" in value)) {
|
|
4213
|
+
return false;
|
|
4214
|
+
}
|
|
4215
|
+
return valueIs(value.value) && typeof value.observedAt === "number";
|
|
4216
|
+
}
|
|
4217
|
+
function isRcSessionStatus(value) {
|
|
4218
|
+
if (!isRcSessionSummary(value)) {
|
|
4219
|
+
return false;
|
|
4220
|
+
}
|
|
4221
|
+
if (!("pending" in value) || !Array.isArray(value.pending)) {
|
|
4222
|
+
return false;
|
|
4223
|
+
}
|
|
4224
|
+
if ("workerState" in value && value.workerState !== void 0 && !isRcWorkerFact(value.workerState, (candidate) => typeof candidate === "string")) {
|
|
4225
|
+
return false;
|
|
4226
|
+
}
|
|
4227
|
+
if ("workerIdleSeconds" in value && value.workerIdleSeconds !== void 0 && !isRcWorkerFact(value.workerIdleSeconds, (candidate) => typeof candidate === "number")) {
|
|
4228
|
+
return false;
|
|
4229
|
+
}
|
|
4230
|
+
return value.pending.every((candidate) => isRcPendingRequestSummary(candidate));
|
|
4231
|
+
}
|
|
4232
|
+
function isRcPendingRequestSummary(value) {
|
|
4233
|
+
if (typeof value !== "object" || value === null) {
|
|
4234
|
+
return false;
|
|
4235
|
+
}
|
|
4236
|
+
if (!("sessionId" in value) || !("requestId" in value) || !("type" in value) || !("summary" in value) || !("observedAt" in value)) {
|
|
4237
|
+
return false;
|
|
4238
|
+
}
|
|
4239
|
+
return typeof value.sessionId === "string" && typeof value.requestId === "string" && typeof value.type === "string" && typeof value.summary === "string" && typeof value.observedAt === "number";
|
|
4240
|
+
}
|
|
4241
|
+
function sequenceNumsOfBody(body) {
|
|
4242
|
+
let parsed;
|
|
4243
|
+
try {
|
|
4244
|
+
parsed = JSON.parse(body);
|
|
4245
|
+
} catch {
|
|
4246
|
+
return void 0;
|
|
4247
|
+
}
|
|
4248
|
+
if (typeof parsed !== "object" || parsed === null || !("sequenceNums" in parsed) || !Array.isArray(parsed.sequenceNums)) {
|
|
4249
|
+
return void 0;
|
|
4250
|
+
}
|
|
4251
|
+
const sequenceNums = [];
|
|
4252
|
+
const values = parsed.sequenceNums;
|
|
4253
|
+
for (const value of values) {
|
|
4254
|
+
if (typeof value !== "number") {
|
|
4255
|
+
return void 0;
|
|
4256
|
+
}
|
|
4257
|
+
sequenceNums.push(value);
|
|
4258
|
+
}
|
|
4259
|
+
return sequenceNums;
|
|
4260
|
+
}
|
|
4261
|
+
function frontDoorRcControl(transport, token) {
|
|
4262
|
+
const headers = { authorization: `Bearer ${token}` };
|
|
4263
|
+
const listAnswerOf = async (path29, what) => {
|
|
4264
|
+
try {
|
|
4265
|
+
return await transport.request({ method: "GET", path: path29, headers });
|
|
4266
|
+
} catch (error) {
|
|
4267
|
+
throw new Error(`${what}: could not reach the front door's control listener: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
4268
|
+
}
|
|
4269
|
+
};
|
|
4270
|
+
const sessionsOf = (answer, what) => {
|
|
4271
|
+
if (answer.status !== HTTP_STATUS.ok) {
|
|
4272
|
+
throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
|
|
4273
|
+
}
|
|
4274
|
+
let parsed;
|
|
4275
|
+
try {
|
|
4276
|
+
parsed = JSON.parse(answer.body);
|
|
4277
|
+
} catch {
|
|
4278
|
+
throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
|
|
4279
|
+
}
|
|
4280
|
+
if (typeof parsed !== "object" || parsed === null || !("sessions" in parsed) || !Array.isArray(parsed.sessions)) {
|
|
4281
|
+
throw new Error(`${what}: the door's answer named no sessions list: ${answer.body}`);
|
|
4282
|
+
}
|
|
4283
|
+
const candidates = parsed.sessions;
|
|
4284
|
+
const sessions = [];
|
|
4285
|
+
for (const value of candidates) {
|
|
4286
|
+
if (!isRcSessionSummary(value)) {
|
|
4287
|
+
throw new Error(`${what}: the door's answer named no sessions list: ${answer.body}`);
|
|
4288
|
+
}
|
|
4289
|
+
sessions.push(value);
|
|
4290
|
+
}
|
|
4291
|
+
return sessions;
|
|
4292
|
+
};
|
|
4293
|
+
const statusesOf = (answer, what) => {
|
|
4294
|
+
if (answer.status !== HTTP_STATUS.ok) {
|
|
4295
|
+
throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
|
|
4296
|
+
}
|
|
4297
|
+
let parsed;
|
|
4298
|
+
try {
|
|
4299
|
+
parsed = JSON.parse(answer.body);
|
|
4300
|
+
} catch {
|
|
4301
|
+
throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
|
|
4302
|
+
}
|
|
4303
|
+
if (typeof parsed !== "object" || parsed === null || !("statuses" in parsed) || !Array.isArray(parsed.statuses)) {
|
|
4304
|
+
throw new Error(`${what}: the door's answer named no status list: ${answer.body}`);
|
|
4305
|
+
}
|
|
4306
|
+
const statuses = [];
|
|
4307
|
+
for (const value of parsed.statuses) {
|
|
4308
|
+
if (!isRcSessionStatus(value)) {
|
|
4309
|
+
throw new Error(`${what}: the door's answer named no status list: ${answer.body}`);
|
|
4310
|
+
}
|
|
4311
|
+
statuses.push(value);
|
|
4312
|
+
}
|
|
4313
|
+
return statuses;
|
|
4314
|
+
};
|
|
4315
|
+
const pendingOfAnswer = (answer, what) => {
|
|
4316
|
+
if (answer.status !== HTTP_STATUS.ok) {
|
|
4317
|
+
throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
|
|
4318
|
+
}
|
|
4319
|
+
let parsed;
|
|
4320
|
+
try {
|
|
4321
|
+
parsed = JSON.parse(answer.body);
|
|
4322
|
+
} catch {
|
|
4323
|
+
throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
|
|
4324
|
+
}
|
|
4325
|
+
if (typeof parsed !== "object" || parsed === null || !("pending" in parsed) || !Array.isArray(parsed.pending)) {
|
|
4326
|
+
throw new Error(`${what}: the door's answer named no pending list: ${answer.body}`);
|
|
4327
|
+
}
|
|
4328
|
+
const pending = [];
|
|
4329
|
+
for (const value of parsed.pending) {
|
|
4330
|
+
if (!isRcPendingRequestSummary(value)) {
|
|
4331
|
+
throw new Error(`${what}: the door's answer named no pending list: ${answer.body}`);
|
|
4332
|
+
}
|
|
4333
|
+
pending.push(value);
|
|
4334
|
+
}
|
|
4335
|
+
return pending;
|
|
4336
|
+
};
|
|
4337
|
+
const writeOf = async (path29, body, describeFailure) => {
|
|
4338
|
+
let answer;
|
|
4339
|
+
try {
|
|
4340
|
+
answer = await transport.request({ method: "POST", path: path29, headers: { ...headers, "content-type": "application/json" }, body });
|
|
4341
|
+
} catch (error) {
|
|
4342
|
+
return { ok: false, message: `could not reach the front door's control listener: ${error instanceof Error ? error.message : String(error)}` };
|
|
4343
|
+
}
|
|
4344
|
+
if (answer.status === HTTP_STATUS.ok) {
|
|
4345
|
+
const sequenceNums = sequenceNumsOfBody(answer.body);
|
|
4346
|
+
if (sequenceNums === void 0) {
|
|
4347
|
+
return { ok: false, message: `the door accepted the event but its answer named no sequence numbers: ${answer.body}` };
|
|
4348
|
+
}
|
|
4349
|
+
return { ok: true, sequenceNums };
|
|
4350
|
+
}
|
|
4351
|
+
return { ok: false, message: describeFailure(answer.status, controlErrorOf(answer.body) ?? answer.body) };
|
|
4352
|
+
};
|
|
4353
|
+
const sessionQuery = (sessionId) => sessionId === void 0 ? "" : `?session=${encodeURIComponent(sessionId)}`;
|
|
4354
|
+
return {
|
|
4355
|
+
listSessions: async () => sessionsOf(await listAnswerOf(`${CONTROL_PATH_PREFIX}/sessions`, "listing the door's Remote Control sessions"), "listing the door's Remote Control sessions"),
|
|
4356
|
+
statusOf: async (sessionId) => statusesOf(await listAnswerOf(`${CONTROL_PATH_PREFIX}/status${sessionQuery(sessionId)}`, "reading the door's Remote Control session status"), "reading the door's Remote Control session status"),
|
|
4357
|
+
pendingOf: async (sessionId) => pendingOfAnswer(await listAnswerOf(`${CONTROL_PATH_PREFIX}/pending${sessionQuery(sessionId)}`, "listing the door's pending Remote Control control requests"), "listing the door's pending Remote Control control requests"),
|
|
4358
|
+
sendPrompt: async (sessionId, text) => await writeOf(`${CONTROL_PATH_PREFIX}/inject`, JSON.stringify({ session: sessionId, text }), (status, message) => `injecting into ${sessionId} failed (HTTP ${String(status)}): ${message}`),
|
|
4359
|
+
answerRequest: async (sessionId, requestId, decision) => await writeOf(
|
|
4360
|
+
`${CONTROL_PATH_PREFIX}/answer`,
|
|
4361
|
+
JSON.stringify({ session: sessionId, request: requestId, approve: decision.approve, ...decision.approve || decision.message === void 0 ? {} : { text: decision.message } }),
|
|
4362
|
+
(status, message) => `answering control request ${requestId} on ${sessionId} failed (HTTP ${String(status)}): ${message}`
|
|
4363
|
+
)
|
|
4364
|
+
};
|
|
4365
|
+
}
|
|
4366
|
+
|
|
3477
4367
|
// src/frontdoor/ensure.ts
|
|
3478
|
-
var
|
|
4368
|
+
var import_node_crypto7 = require("node:crypto");
|
|
3479
4369
|
|
|
3480
4370
|
// src/headroom/state.ts
|
|
3481
|
-
var
|
|
4371
|
+
var import_node_crypto6 = require("node:crypto");
|
|
3482
4372
|
var import_node_path8 = __toESM(require("node:path"), 1);
|
|
3483
4373
|
var import_zod4 = require("zod");
|
|
3484
4374
|
var HEADROOM_ANTHROPIC_UPSTREAM = "https://api.anthropic.com";
|
|
@@ -3539,7 +4429,7 @@ function headroomUpstreams(providers, frontDoorOrigin2) {
|
|
|
3539
4429
|
return upstreams;
|
|
3540
4430
|
}
|
|
3541
4431
|
function hashAllowlist(allowlist) {
|
|
3542
|
-
return (0,
|
|
4432
|
+
return (0, import_node_crypto6.createHash)("sha256").update(allowlist.join("\n")).digest("hex");
|
|
3543
4433
|
}
|
|
3544
4434
|
function readAllProviders(fs12, providersDir) {
|
|
3545
4435
|
const result = [];
|
|
@@ -3638,7 +4528,7 @@ function ensureFrontDoor(params) {
|
|
|
3638
4528
|
continue;
|
|
3639
4529
|
}
|
|
3640
4530
|
if (verdict.ok) {
|
|
3641
|
-
const token = (0,
|
|
4531
|
+
const token = (0, import_node_crypto7.randomUUID)();
|
|
3642
4532
|
writeFrontDoorSession(ports.fs, paths.frontdoorSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), token });
|
|
3643
4533
|
return { port: state.port, connectPort: state.connectPort, token };
|
|
3644
4534
|
}
|
|
@@ -4037,7 +4927,7 @@ function ensureHeadroom(params) {
|
|
|
4037
4927
|
}
|
|
4038
4928
|
|
|
4039
4929
|
// src/headroom/settings.ts
|
|
4040
|
-
var
|
|
4930
|
+
var import_node_crypto8 = require("node:crypto");
|
|
4041
4931
|
function settingsOf(config) {
|
|
4042
4932
|
return {
|
|
4043
4933
|
...config.mode === void 0 ? {} : { mode: config.mode },
|
|
@@ -4062,7 +4952,7 @@ function settingsEnv(settings) {
|
|
|
4062
4952
|
return settings.rolloutChannel === void 0 ? {} : { HEADROOM_ROLLOUT_CHANNEL: settings.rolloutChannel };
|
|
4063
4953
|
}
|
|
4064
4954
|
function hashSettings(settings) {
|
|
4065
|
-
return (0,
|
|
4955
|
+
return (0, import_node_crypto8.createHash)("sha256").update(JSON.stringify([...settingsArgs(settings), JSON.stringify(settingsEnv(settings))])).digest("hex");
|
|
4066
4956
|
}
|
|
4067
4957
|
|
|
4068
4958
|
// src/headroom/socket.ts
|
|
@@ -4493,6 +5383,7 @@ function buildLayoutPaths(root) {
|
|
|
4493
5383
|
frontdoorCaBundlesDir: import_node_path12.default.join(root, "frontdoor", "ca", "bundles"),
|
|
4494
5384
|
frontdoorStateFile: import_node_path12.default.join(root, "frontdoor", "state.json"),
|
|
4495
5385
|
frontdoorLockFile: import_node_path12.default.join(root, "frontdoor", "start.lock"),
|
|
5386
|
+
frontdoorControlTokenFile: import_node_path12.default.join(root, "frontdoor", "control-token"),
|
|
4496
5387
|
frontdoorSessionsDir: import_node_path12.default.join(root, "frontdoor", "sessions"),
|
|
4497
5388
|
frontdoorLogPath: import_node_path12.default.join(root, "logs", "frontdoor.log"),
|
|
4498
5389
|
usageDir: import_node_path12.default.join(root, "usage"),
|
|
@@ -5131,14 +6022,14 @@ function providerExists(paths, name) {
|
|
|
5131
6022
|
var LEGACY_PROVIDER_FIELDS = ["tokenEnv", "tokenCommand", "authScheme"];
|
|
5132
6023
|
var LEGACY_ENV_CREDENTIAL_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
|
|
5133
6024
|
var LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
|
|
5134
|
-
function
|
|
6025
|
+
function isRecord3(value) {
|
|
5135
6026
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5136
6027
|
}
|
|
5137
6028
|
function legacyProviderConversion(raw) {
|
|
5138
|
-
if (!
|
|
6029
|
+
if (!isRecord3(raw) || "credential" in raw) {
|
|
5139
6030
|
return void 0;
|
|
5140
6031
|
}
|
|
5141
|
-
const env =
|
|
6032
|
+
const env = isRecord3(raw.env) ? raw.env : void 0;
|
|
5142
6033
|
const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
|
|
5143
6034
|
const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
|
|
5144
6035
|
if (fields.length === 0) {
|
|
@@ -5987,7 +6878,7 @@ function collectPoolPick(params) {
|
|
|
5987
6878
|
|
|
5988
6879
|
// src/realPorts.ts
|
|
5989
6880
|
var import_node_child_process = require("node:child_process");
|
|
5990
|
-
var
|
|
6881
|
+
var import_node_crypto9 = require("node:crypto");
|
|
5991
6882
|
var import_node_fs6 = __toESM(require("node:fs"), 1);
|
|
5992
6883
|
var import_node_os3 = __toESM(require("node:os"), 1);
|
|
5993
6884
|
var import_node_path21 = __toESM(require("node:path"), 1);
|
|
@@ -6182,7 +7073,7 @@ var realFarmFs = {
|
|
|
6182
7073
|
},
|
|
6183
7074
|
hashFile(filePath) {
|
|
6184
7075
|
try {
|
|
6185
|
-
return (0,
|
|
7076
|
+
return (0, import_node_crypto9.createHash)("sha256").update(import_node_fs6.default.readFileSync(filePath)).digest("hex");
|
|
6186
7077
|
} catch (error) {
|
|
6187
7078
|
if (isEnoent3(error) || isErrorWithCode(error, "EISDIR")) {
|
|
6188
7079
|
return void 0;
|
|
@@ -7375,7 +8266,7 @@ function collectDoctorReport(params) {
|
|
|
7375
8266
|
}
|
|
7376
8267
|
|
|
7377
8268
|
// src/launcher.ts
|
|
7378
|
-
var
|
|
8269
|
+
var import_node_crypto10 = require("node:crypto");
|
|
7379
8270
|
var import_node_path26 = __toESM(require("node:path"), 1);
|
|
7380
8271
|
|
|
7381
8272
|
// src/launcher/argv.ts
|
|
@@ -8017,7 +8908,7 @@ function prepareLaunch(params) {
|
|
|
8017
8908
|
...identityCredential === void 0 ? {} : { identityCredential },
|
|
8018
8909
|
...frontDoor === void 0 ? {} : { frontdoor: frontDoor },
|
|
8019
8910
|
...headroom === void 0 ? {} : { headroom },
|
|
8020
|
-
sessionId: (0,
|
|
8911
|
+
sessionId: (0, import_node_crypto10.randomUUID)()
|
|
8021
8912
|
});
|
|
8022
8913
|
const releases = [...headroom === void 0 || headroomPort === void 0 ? [] : [headroomPort.release], ...frontDoorRelease === void 0 ? [] : [frontDoorRelease]];
|
|
8023
8914
|
const release = () => {
|
|
@@ -8030,7 +8921,7 @@ function prepareLaunch(params) {
|
|
|
8030
8921
|
|
|
8031
8922
|
// src/launchWiring.ts
|
|
8032
8923
|
var import_node_os5 = __toESM(require("node:os"), 1);
|
|
8033
|
-
var
|
|
8924
|
+
var import_node_crypto12 = require("node:crypto");
|
|
8034
8925
|
|
|
8035
8926
|
// src/realCredentialCache.ts
|
|
8036
8927
|
var import_node_child_process2 = require("node:child_process");
|
|
@@ -8178,7 +9069,7 @@ function probeFrontDoorSync(port, caPem) {
|
|
|
8178
9069
|
}
|
|
8179
9070
|
|
|
8180
9071
|
// src/frontdoor/trust.ts
|
|
8181
|
-
var
|
|
9072
|
+
var import_node_crypto11 = require("node:crypto");
|
|
8182
9073
|
var import_node_path28 = __toESM(require("node:path"), 1);
|
|
8183
9074
|
var BUNDLE_NAME_HEX_DIGITS = 16;
|
|
8184
9075
|
function resolveTrustBundle(params) {
|
|
@@ -8201,7 +9092,7 @@ function resolveTrustBundle(params) {
|
|
|
8201
9092
|
}
|
|
8202
9093
|
const combined = `${inheritedPem.trimEnd()}
|
|
8203
9094
|
${caPem}`;
|
|
8204
|
-
const bundle = import_node_path28.default.join(bundlesDir, `${(0,
|
|
9095
|
+
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
9096
|
if (!fs12.exists(bundle)) {
|
|
8206
9097
|
fs12.mkdirp(bundlesDir);
|
|
8207
9098
|
fs12.writeFileAtomic(bundle, combined);
|
|
@@ -8280,7 +9171,7 @@ function buildFarmRuntime(paths, cwd) {
|
|
|
8280
9171
|
...cliOverride === void 0 ? {} : { cliOverride }
|
|
8281
9172
|
}).input,
|
|
8282
9173
|
now: () => Date.now(),
|
|
8283
|
-
uniqueSuffix: `${String(process.pid)}.${(0,
|
|
9174
|
+
uniqueSuffix: `${String(process.pid)}.${(0, import_node_crypto12.randomUUID)()}`,
|
|
8284
9175
|
// 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
9176
|
lock: { pid: process.pid, isRunning: realIsProcessRunning, sleep: realSleepSync }
|
|
8286
9177
|
},
|
|
@@ -8345,6 +9236,7 @@ function prepareClaudeLaunch(options) {
|
|
|
8345
9236
|
CONNECT_INTERCEPT_HOST,
|
|
8346
9237
|
CONNECT_INTERCEPT_HOSTS,
|
|
8347
9238
|
CONNECT_LIMITS,
|
|
9239
|
+
CONTROL_PATH_PREFIX,
|
|
8348
9240
|
CategoryClassificationOverlaySchema,
|
|
8349
9241
|
CategoryClassificationSchema,
|
|
8350
9242
|
CategoryMapSchema,
|
|
@@ -8383,6 +9275,10 @@ function prepareClaudeLaunch(options) {
|
|
|
8383
9275
|
ProviderKindMismatchError,
|
|
8384
9276
|
ProviderNotFoundError,
|
|
8385
9277
|
ProviderSchema,
|
|
9278
|
+
RC_IDLE_EXPIRY_MS,
|
|
9279
|
+
RC_PENDING_DEADLINE_MS,
|
|
9280
|
+
RC_REQUEST_PARSE_CAP_BYTES,
|
|
9281
|
+
RC_SESSIONS_PATH_PREFIX,
|
|
8386
9282
|
ROUTED_PATH_PREFIX,
|
|
8387
9283
|
UsageError,
|
|
8388
9284
|
UsageSnapshotError,
|
|
@@ -8392,8 +9288,12 @@ function prepareClaudeLaunch(options) {
|
|
|
8392
9288
|
addIdentity,
|
|
8393
9289
|
addPool,
|
|
8394
9290
|
addProvider,
|
|
9291
|
+
answerRcControlRequest,
|
|
8395
9292
|
buildEntryFacts,
|
|
8396
9293
|
buildLayoutPaths,
|
|
9294
|
+
buildRcControlResponsePayload,
|
|
9295
|
+
buildRcEventWriteBody,
|
|
9296
|
+
buildRcUserMessagePayload,
|
|
8397
9297
|
carryOver,
|
|
8398
9298
|
checkReportHasWarnings,
|
|
8399
9299
|
checkReportToJson,
|
|
@@ -8401,6 +9301,8 @@ function prepareClaudeLaunch(options) {
|
|
|
8401
9301
|
collectDoctorReport,
|
|
8402
9302
|
createLeafCache,
|
|
8403
9303
|
createProfile,
|
|
9304
|
+
createRcControlHandler,
|
|
9305
|
+
createRcSessionTracker,
|
|
8404
9306
|
describeProviderEndpoint,
|
|
8405
9307
|
detectAmbientCredential,
|
|
8406
9308
|
ensureCa,
|
|
@@ -8411,8 +9313,10 @@ function prepareClaudeLaunch(options) {
|
|
|
8411
9313
|
formatCheckReport,
|
|
8412
9314
|
formatDoctorReport,
|
|
8413
9315
|
forwardableHeaders,
|
|
9316
|
+
frontDoorRcControl,
|
|
8414
9317
|
generateCa,
|
|
8415
9318
|
identityExists,
|
|
9319
|
+
injectRcUserMessage,
|
|
8416
9320
|
isIdentityDirectoryName,
|
|
8417
9321
|
isInterceptedHost,
|
|
8418
9322
|
listDirectoryRules,
|
|
@@ -8421,11 +9325,14 @@ function prepareClaudeLaunch(options) {
|
|
|
8421
9325
|
listProviders,
|
|
8422
9326
|
listUsageSnapshots,
|
|
8423
9327
|
mintLeaf,
|
|
9328
|
+
observingRoutedRoute,
|
|
8424
9329
|
parseConnectTarget,
|
|
8425
9330
|
prepareClaudeLaunch,
|
|
8426
9331
|
prepareLaunch,
|
|
8427
9332
|
profileExists,
|
|
8428
9333
|
providerExists,
|
|
9334
|
+
rcEventWriteResultFromAnswer,
|
|
9335
|
+
rcObserverAsPassthrough,
|
|
8429
9336
|
readActiveIdentity,
|
|
8430
9337
|
readDirectoryRules,
|
|
8431
9338
|
readFarmManifest,
|
|
@@ -8437,6 +9344,8 @@ function prepareClaudeLaunch(options) {
|
|
|
8437
9344
|
readUsageSnapshot,
|
|
8438
9345
|
realConnectCertStore,
|
|
8439
9346
|
realConnectEffects,
|
|
9347
|
+
realRcControlTransport,
|
|
9348
|
+
realRcEventDial,
|
|
8440
9349
|
recoverFarm,
|
|
8441
9350
|
recoveryDiagnostics,
|
|
8442
9351
|
removeDirectoryRule,
|