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/dist/index.mjs CHANGED
@@ -2449,6 +2449,7 @@ function pumpTapSession(secure, openUpstream, tap, prefix = Buffer.alloc(0)) {
2449
2449
  }
2450
2450
 
2451
2451
  // src/frontdoor/capability.ts
2452
+ import { createHash as createHash2, timingSafeEqual } from "node:crypto";
2452
2453
  var CONNECT_PROXY_USER = "agent-shim";
2453
2454
  var CONNECT_PROXY_REALM = "agent-shim front door";
2454
2455
  var BASIC_SCHEME = "basic";
@@ -2469,6 +2470,18 @@ function capabilityFromProxyAuthorization(value) {
2469
2470
  const password = userPass.slice(colon + 1);
2470
2471
  return password === "" ? void 0 : password;
2471
2472
  }
2473
+ function digest(token) {
2474
+ return createHash2("sha256").update(token, "utf8").digest();
2475
+ }
2476
+ function isLiveCapability(presented, live) {
2477
+ const presentedDigest = digest(presented);
2478
+ let matched = false;
2479
+ for (const token of live) {
2480
+ const equal = timingSafeEqual(presentedDigest, digest(token));
2481
+ matched = equal || matched;
2482
+ }
2483
+ return matched;
2484
+ }
2472
2485
 
2473
2486
  // src/frontdoor/route.ts
2474
2487
  var IDENTITY_HEADER = "x-agent-shim-identity";
@@ -2779,14 +2792,14 @@ function caParses(ca) {
2779
2792
  var TAP_ALPN_PROTOCOLS = ["h2", "http/1.1"];
2780
2793
  async function startConnectServer(config, effects, preferredPort) {
2781
2794
  const tunnelTokens = /* @__PURE__ */ new WeakMap();
2782
- const onUpgrade = (host) => (request3, socket, head) => {
2783
- config.capture?.upgrade(request3);
2795
+ const onUpgrade = (host) => (request4, socket, head) => {
2796
+ config.capture?.upgrade(request4);
2784
2797
  if (head.length > 0) {
2785
2798
  socket.pause();
2786
2799
  socket.unshift(head);
2787
2800
  }
2788
- const headText = `${request3.method ?? "GET"} ${request3.url ?? "/"} HTTP/1.1\r
2789
- ${request3.rawHeaders.reduce((accumulated, value, index) => index % 2 === 0 ? `${accumulated}${value}: ` : `${accumulated}${value}\r
2801
+ const headText = `${request4.method ?? "GET"} ${request4.url ?? "/"} HTTP/1.1\r
2802
+ ${request4.rawHeaders.reduce((accumulated, value, index) => index % 2 === 0 ? `${accumulated}${value}: ` : `${accumulated}${value}\r
2790
2803
  `, "")}\r
2791
2804
  `;
2792
2805
  pumpTapSession(socket, async (clientAlpn) => await effects.connectTlsUpstream(host, HTTPS_PORT, clientAlpn), config.capture?.tapStream?.(host), Buffer.from(headText, "utf8"));
@@ -2799,16 +2812,16 @@ ${request3.rawHeaders.reduce((accumulated, value, index) => index % 2 === 0 ? `$
2799
2812
  const tlsAcceptor2 = effects.createTlsAcceptor(config.leafFor(host), serveSecure2, TAP_ALPN_PROTOCOLS);
2800
2813
  return { host, serveSecure: serveSecure2, tlsAcceptor: tlsAcceptor2 };
2801
2814
  }
2802
- const handler = (request3, response) => {
2803
- if (host === config.routedHost && servedByPipeline(request3.url)) {
2804
- const tunnelToken = tunnelTokens.get(request3.socket);
2805
- if (tunnelToken !== void 0 && request3.headers[AUTH_HEADER] === void 0) {
2806
- request3.headers[AUTH_HEADER] = tunnelToken;
2815
+ const handler = (request4, response) => {
2816
+ if (host === config.routedHost && servedByPipeline(request4.url)) {
2817
+ const tunnelToken = tunnelTokens.get(request4.socket);
2818
+ if (tunnelToken !== void 0 && request4.headers[AUTH_HEADER] === void 0) {
2819
+ request4.headers[AUTH_HEADER] = tunnelToken;
2807
2820
  }
2808
- config.serveRouted(request3, response);
2821
+ config.serveRouted(request4, response);
2809
2822
  return;
2810
2823
  }
2811
- effects.forwardHttp(config.upstreamFor(host), request3, response, config.capture?.observePassthrough(request3));
2824
+ effects.forwardHttp(config.upstreamFor(host), request4, response, config.capture?.observePassthrough(request4));
2812
2825
  };
2813
2826
  const httpSession = effects.createHttpSession(handler, onUpgrade(host));
2814
2827
  const serveSecure = (secure, capability) => {
@@ -2850,7 +2863,7 @@ ${request3.rawHeaders.reduce((accumulated, value, index) => index % 2 === 0 ? `$
2850
2863
  }
2851
2864
  };
2852
2865
  }
2853
- function createConnectionLedger(limits, isLiveCapability) {
2866
+ function createConnectionLedger(limits, isLiveCapability2) {
2854
2867
  const pending = /* @__PURE__ */ new Map();
2855
2868
  const authenticated = /* @__PURE__ */ new Map();
2856
2869
  let tunnels = 0;
@@ -2863,7 +2876,7 @@ function createConnectionLedger(limits, isLiveCapability) {
2863
2876
  };
2864
2877
  const revalidation = setInterval(() => {
2865
2878
  for (const [capability, sockets] of authenticated) {
2866
- if (!isLiveCapability(capability)) {
2879
+ if (!isLiveCapability2(capability)) {
2867
2880
  for (const socket of sockets) {
2868
2881
  socket.destroy();
2869
2882
  }
@@ -3105,6 +3118,40 @@ var ExemptTlsAgent = class extends https.Agent {
3105
3118
  return tls.connect(tlsOptions);
3106
3119
  }
3107
3120
  };
3121
+ function realRcEventDial(target = { host: CONNECT_INTERCEPT_HOST, port: HTTPS_PORT }) {
3122
+ const agent = new ExemptTlsAgent();
3123
+ return {
3124
+ writeEvents: async (sessionId, headers, body) => await new Promise((resolve, reject) => {
3125
+ const options = {
3126
+ host: target.host,
3127
+ port: target.port,
3128
+ method: "POST",
3129
+ path: `/v1/code/sessions/${encodeURIComponent(sessionId)}/events`,
3130
+ // 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.
3131
+ headers: { ...headers, host: target.servername ?? target.host, "content-length": String(Buffer.byteLength(body, "utf8")) },
3132
+ agent
3133
+ };
3134
+ options.servername = target.servername ?? target.host;
3135
+ if (target.ca !== void 0) {
3136
+ options.ca = [...target.ca];
3137
+ }
3138
+ if (target.rejectUnauthorized !== void 0) {
3139
+ options.rejectUnauthorized = target.rejectUnauthorized;
3140
+ }
3141
+ const request4 = https.request(options, (response) => {
3142
+ const chunks = [];
3143
+ response.on("data", (chunk) => {
3144
+ chunks.push(chunk);
3145
+ });
3146
+ response.on("end", () => {
3147
+ resolve({ status: response.statusCode ?? HTTP_BAD_GATEWAY, body: Buffer.concat(chunks).toString("utf8") });
3148
+ });
3149
+ });
3150
+ request4.once("error", reject);
3151
+ request4.end(body);
3152
+ })
3153
+ };
3154
+ }
3108
3155
  function realConnectEffects() {
3109
3156
  const plainAgent = new ExemptHttpAgent();
3110
3157
  const tlsAgent = new ExemptTlsAgent();
@@ -3210,8 +3257,8 @@ function realConnectEffects() {
3210
3257
  socket.destroy();
3211
3258
  });
3212
3259
  if (onUpgrade !== void 0) {
3213
- server.on("upgrade", (request3, socket, head) => {
3214
- onUpgrade(request3, socket, head);
3260
+ server.on("upgrade", (request4, socket, head) => {
3261
+ onUpgrade(request4, socket, head);
3215
3262
  });
3216
3263
  }
3217
3264
  return {
@@ -3250,14 +3297,14 @@ function realConnectEffects() {
3250
3297
  });
3251
3298
  socket.once("error", reject);
3252
3299
  }),
3253
- forwardHttp: (target, request3, response, observer) => {
3254
- const headers = forwardableHeaders(request3.headers);
3300
+ forwardHttp: (target, request4, response, observer) => {
3301
+ const headers = forwardableHeaders(request4.headers);
3255
3302
  const options = {
3256
3303
  host: target.host,
3257
3304
  port: target.port,
3258
3305
  lookup: realAddressLookup,
3259
- method: request3.method,
3260
- path: request3.url,
3306
+ method: request4.method,
3307
+ path: request4.url,
3261
3308
  headers,
3262
3309
  agent: target.tls ? tlsAgent : plainAgent
3263
3310
  };
@@ -3292,15 +3339,20 @@ function realConnectEffects() {
3292
3339
  response.end(body);
3293
3340
  });
3294
3341
  if (observer !== void 0) {
3295
- request3.on("data", (chunk) => {
3342
+ request4.on("data", (chunk) => {
3296
3343
  observer.onRequestChunk(chunk);
3297
3344
  });
3345
+ if (observer.onRequestEnd !== void 0) {
3346
+ request4.on("end", () => {
3347
+ observer.onRequestEnd?.();
3348
+ });
3349
+ }
3298
3350
  response.on("close", () => {
3299
3351
  observer.onEnd();
3300
3352
  });
3301
3353
  }
3302
- request3.pipe(upstream);
3303
- request3.socket.setNoDelay(true);
3354
+ request4.pipe(upstream);
3355
+ request4.socket.setNoDelay(true);
3304
3356
  upstream.on("socket", (socket) => {
3305
3357
  socket.setNoDelay(true);
3306
3358
  });
@@ -3308,11 +3360,831 @@ function realConnectEffects() {
3308
3360
  };
3309
3361
  }
3310
3362
 
3363
+ // src/codex/http.ts
3364
+ var HTTP_STATUS = {
3365
+ ok: 200,
3366
+ badRequest: 400,
3367
+ unauthorized: 401,
3368
+ forbidden: 403,
3369
+ notFound: 404,
3370
+ methodNotAllowed: 405,
3371
+ payloadTooLarge: 413,
3372
+ tooManyRequests: 429,
3373
+ /** 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. */
3374
+ clientClosedRequest: 499,
3375
+ internalServerError: 500,
3376
+ badGateway: 502
3377
+ };
3378
+
3379
+ // src/frontdoor/rcSessions.ts
3380
+ var RC_SESSIONS_PATH_PREFIX = "/v1/code/sessions";
3381
+ var RC_SESSION_ID_PREFIX = "cse_";
3382
+ var RC_IDLE_EXPIRY_MS = 45e3;
3383
+ var RC_CREATE_RESPONSE_CAP_BYTES = 8192;
3384
+ var CONFLICT_REASON_HEADER = "x-ccr-conflict-reason";
3385
+ var RC_PENDING_DEADLINE_MS = 14e3;
3386
+ var RC_REQUEST_PARSE_CAP_BYTES = 262144;
3387
+ var RC_PENDING_SUMMARY_EXCERPT_CHARS = 300;
3388
+ var SUCCESS_STATUS_MAX_EXCLUSIVE = 300;
3389
+ function isSuccessful(status) {
3390
+ return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE;
3391
+ }
3392
+ function singleHeader(headers, name) {
3393
+ const value = headers[name];
3394
+ return typeof value === "string" ? value : void 0;
3395
+ }
3396
+ function sessionCreateId(body) {
3397
+ let parsed;
3398
+ try {
3399
+ parsed = JSON.parse(body);
3400
+ } catch {
3401
+ return void 0;
3402
+ }
3403
+ if (typeof parsed !== "object" || parsed === null || !("session" in parsed)) {
3404
+ return void 0;
3405
+ }
3406
+ const session = parsed.session;
3407
+ if (typeof session !== "object" || session === null || !("id" in session)) {
3408
+ return void 0;
3409
+ }
3410
+ const id = session.id;
3411
+ return typeof id === "string" && id.startsWith(RC_SESSION_ID_PREFIX) ? id : void 0;
3412
+ }
3413
+ function isRecord2(value) {
3414
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3415
+ }
3416
+ function parseJsonObject(body) {
3417
+ let parsed;
3418
+ try {
3419
+ parsed = JSON.parse(body);
3420
+ } catch {
3421
+ return void 0;
3422
+ }
3423
+ return isRecord2(parsed) ? parsed : void 0;
3424
+ }
3425
+ function nonEmptyString(record, field) {
3426
+ const value = record === void 0 ? void 0 : record[field];
3427
+ return typeof value === "string" && value !== "" ? value : void 0;
3428
+ }
3429
+ function finiteNumber(record, field) {
3430
+ const value = record === void 0 ? void 0 : record[field];
3431
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3432
+ }
3433
+ function excerpt(text) {
3434
+ return text.length <= RC_PENDING_SUMMARY_EXCERPT_CHARS ? text : `${text.slice(0, RC_PENDING_SUMMARY_EXCERPT_CHARS)}...`;
3435
+ }
3436
+ function payloadEventsOf(body) {
3437
+ const parsed = parseJsonObject(body);
3438
+ const events = parsed === void 0 ? void 0 : parsed.events;
3439
+ if (!Array.isArray(events)) {
3440
+ return [];
3441
+ }
3442
+ const payloads = [];
3443
+ for (const event of events) {
3444
+ if (!isRecord2(event)) {
3445
+ continue;
3446
+ }
3447
+ const payload = event.payload;
3448
+ if (!isRecord2(payload)) {
3449
+ continue;
3450
+ }
3451
+ payloads.push(payload);
3452
+ }
3453
+ return payloads;
3454
+ }
3455
+ function pendingOfControlRequestPayload(payload) {
3456
+ if (payload.type !== "control_request") {
3457
+ return void 0;
3458
+ }
3459
+ const requestId = nonEmptyString(payload, "request_id") ?? nonEmptyString(payload, "uuid");
3460
+ if (requestId === void 0) {
3461
+ return void 0;
3462
+ }
3463
+ const request4 = isRecord2(payload.request) ? payload.request : void 0;
3464
+ const subtype = nonEmptyString(request4, "subtype");
3465
+ const type = subtype ?? "control_request";
3466
+ let summary = "";
3467
+ if (subtype === "can_use_tool") {
3468
+ const toolName = nonEmptyString(request4, "tool_name") ?? "unknown tool";
3469
+ const input = request4 === void 0 ? void 0 : request4.input;
3470
+ summary = input === void 0 ? toolName : `${toolName} ${excerpt(JSON.stringify(input))}`;
3471
+ } else if (request4 !== void 0) {
3472
+ summary = excerpt(JSON.stringify(request4));
3473
+ }
3474
+ return { requestId, type, summary };
3475
+ }
3476
+ function answeredRequestIdsOfControlResponsePayload(payload) {
3477
+ if (payload.type !== "control_response") {
3478
+ return [];
3479
+ }
3480
+ const response = isRecord2(payload.response) ? payload.response : void 0;
3481
+ const id = nonEmptyString(response, "request_id") ?? nonEmptyString(payload, "request_id") ?? nonEmptyString(payload, "uuid");
3482
+ return id === void 0 ? [] : [id];
3483
+ }
3484
+ function parseRcRequestBody(kind, body) {
3485
+ if (kind === "heartbeat") {
3486
+ return { controlRequests: [], answeredRequestIds: [], idleSeconds: finiteNumber(parseJsonObject(body), "idle_seconds"), workerStatus: void 0 };
3487
+ }
3488
+ if (kind === "worker-registration") {
3489
+ return { controlRequests: [], answeredRequestIds: [], idleSeconds: void 0, workerStatus: nonEmptyString(parseJsonObject(body), "status") };
3490
+ }
3491
+ const controlRequests = [];
3492
+ const answeredRequestIds = [];
3493
+ for (const payload of payloadEventsOf(body)) {
3494
+ const pending = pendingOfControlRequestPayload(payload);
3495
+ if (pending !== void 0) {
3496
+ controlRequests.push({ requestId: pending.requestId, type: pending.type, summary: pending.summary });
3497
+ continue;
3498
+ }
3499
+ answeredRequestIds.push(...answeredRequestIdsOfControlResponsePayload(payload));
3500
+ }
3501
+ return { controlRequests, answeredRequestIds, idleSeconds: void 0, workerStatus: void 0 };
3502
+ }
3503
+ function createRcSessionTracker(deps) {
3504
+ const entries = /* @__PURE__ */ new Map();
3505
+ const expire = (id) => {
3506
+ entries.delete(id);
3507
+ };
3508
+ const expirePendings = (entry, now) => {
3509
+ for (const [requestId, pending] of entry.pending) {
3510
+ if (now - pending.observedAt >= RC_PENDING_DEADLINE_MS) {
3511
+ entry.pending.delete(requestId);
3512
+ }
3513
+ }
3514
+ };
3515
+ const sweep = () => {
3516
+ for (const [id, entry] of entries) {
3517
+ if (deps.now() - entry.lastSeenAt >= deps.idleMs) {
3518
+ expire(id);
3519
+ continue;
3520
+ }
3521
+ expirePendings(entry, deps.now());
3522
+ }
3523
+ };
3524
+ const timer = setInterval(sweep, deps.idleMs);
3525
+ timer.unref();
3526
+ const birth = (id, facts) => {
3527
+ const record = { id, createdAt: deps.now(), lastSeenAt: deps.now(), ...facts, pending: /* @__PURE__ */ new Map(), workerState: void 0, workerIdleSeconds: void 0 };
3528
+ entries.set(id, record);
3529
+ return record;
3530
+ };
3531
+ const observeExchange = (request4) => {
3532
+ const method = (request4.method ?? "").toUpperCase();
3533
+ const path29 = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
3534
+ if (!path29.startsWith(RC_SESSIONS_PATH_PREFIX)) {
3535
+ return void 0;
3536
+ }
3537
+ const tail = path29.slice(RC_SESSIONS_PATH_PREFIX.length);
3538
+ const facts = {
3539
+ authorization: singleHeader(request4.headers, "authorization"),
3540
+ anthropicVersion: singleHeader(request4.headers, "anthropic-version"),
3541
+ anthropicClientPlatform: singleHeader(request4.headers, "anthropic-client-platform")
3542
+ };
3543
+ if (tail === "") {
3544
+ if (method !== "POST") {
3545
+ return void 0;
3546
+ }
3547
+ let accepted2 = false;
3548
+ let buffered2 = 0;
3549
+ let body = "";
3550
+ return {
3551
+ onResponse: (status) => {
3552
+ accepted2 = isSuccessful(status);
3553
+ },
3554
+ onBodyChunk: (chunk) => {
3555
+ if (!accepted2 || buffered2 >= RC_CREATE_RESPONSE_CAP_BYTES) {
3556
+ return;
3557
+ }
3558
+ const text = Buffer.from(chunk).toString("utf8");
3559
+ body = body + text.slice(0, RC_CREATE_RESPONSE_CAP_BYTES - buffered2);
3560
+ buffered2 = body.length;
3561
+ },
3562
+ onEnd: () => {
3563
+ if (!accepted2) {
3564
+ return;
3565
+ }
3566
+ const id2 = sessionCreateId(body);
3567
+ if (id2 === void 0) {
3568
+ return;
3569
+ }
3570
+ birth(id2, facts);
3571
+ }
3572
+ };
3573
+ }
3574
+ const rest = tail.slice(1);
3575
+ const [id] = rest.split("/");
3576
+ if (id?.startsWith(RC_SESSION_ID_PREFIX) !== true) {
3577
+ return void 0;
3578
+ }
3579
+ const entry = entries.get(id) ?? birth(id, facts);
3580
+ entry.lastSeenAt = deps.now();
3581
+ entry.authorization = facts.authorization ?? entry.authorization;
3582
+ entry.anthropicVersion = facts.anthropicVersion ?? entry.anthropicVersion;
3583
+ entry.anthropicClientPlatform = facts.anthropicClientPlatform ?? entry.anthropicClientPlatform;
3584
+ 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;
3585
+ let chunks = [];
3586
+ let buffered = 0;
3587
+ let oversized = false;
3588
+ let requestEnded = false;
3589
+ let accepted;
3590
+ let filed = false;
3591
+ const file = () => {
3592
+ if (filed || accepted !== true || !requestEnded || bodyKind === void 0) {
3593
+ return;
3594
+ }
3595
+ filed = true;
3596
+ const factsOfBody = oversized ? void 0 : parseRcRequestBody(bodyKind, Buffer.concat(chunks).toString("utf8"));
3597
+ chunks = [];
3598
+ const entryNow = entries.get(id);
3599
+ if (entryNow === void 0 || factsOfBody === void 0) {
3600
+ return;
3601
+ }
3602
+ const now = deps.now();
3603
+ for (const controlRequest of factsOfBody.controlRequests) {
3604
+ entryNow.pending.set(controlRequest.requestId, { sessionId: id, ...controlRequest, observedAt: now });
3605
+ }
3606
+ for (const requestId of factsOfBody.answeredRequestIds) {
3607
+ entryNow.pending.delete(requestId);
3608
+ }
3609
+ if (factsOfBody.idleSeconds !== void 0) {
3610
+ entryNow.workerIdleSeconds = { value: factsOfBody.idleSeconds, observedAt: now };
3611
+ }
3612
+ if (factsOfBody.workerStatus !== void 0) {
3613
+ entryNow.workerState = { value: factsOfBody.workerStatus, observedAt: now };
3614
+ }
3615
+ };
3616
+ const archiving = method === "POST" && rest === `${id}/archive`;
3617
+ return {
3618
+ ...bodyKind === void 0 ? {} : {
3619
+ onRequestChunk: (chunk) => {
3620
+ if (oversized) {
3621
+ return;
3622
+ }
3623
+ chunks.push(Buffer.from(chunk));
3624
+ buffered += chunk.length;
3625
+ if (buffered > RC_REQUEST_PARSE_CAP_BYTES) {
3626
+ oversized = true;
3627
+ chunks = [];
3628
+ }
3629
+ },
3630
+ onRequestEnd: () => {
3631
+ requestEnded = true;
3632
+ file();
3633
+ }
3634
+ },
3635
+ onResponse: (status, headers) => {
3636
+ if (singleHeader(headers, CONFLICT_REASON_HEADER) !== void 0 || archiving && isSuccessful(status)) {
3637
+ expire(id);
3638
+ return;
3639
+ }
3640
+ accepted = isSuccessful(status);
3641
+ file();
3642
+ },
3643
+ onBodyChunk: () => {
3644
+ },
3645
+ onEnd: () => {
3646
+ }
3647
+ };
3648
+ };
3649
+ const summaryOf = ({ id, createdAt, lastSeenAt }) => ({ id, createdAt, lastSeenAt });
3650
+ const pendingSummaryOf = ({ sessionId, requestId, type, summary, observedAt }) => ({ sessionId, requestId, type, summary, observedAt });
3651
+ const sortedEntries = () => [...entries.values()].sort((left, right) => left.createdAt - right.createdAt || (left.id < right.id ? -1 : 1));
3652
+ const pendingSorted = (entry) => [...entry.pending.values()].sort((left, right) => left.observedAt - right.observedAt || (left.requestId < right.requestId ? -1 : 1)).map(pendingSummaryOf);
3653
+ return {
3654
+ observeExchange,
3655
+ list: () => {
3656
+ sweep();
3657
+ return sortedEntries().map(summaryOf);
3658
+ },
3659
+ statusOf: (sessionId) => {
3660
+ sweep();
3661
+ return sortedEntries().filter((entry) => sessionId === void 0 || entry.id === sessionId).map((entry) => ({ ...summaryOf(entry), workerState: entry.workerState, workerIdleSeconds: entry.workerIdleSeconds, pending: pendingSorted(entry) }));
3662
+ },
3663
+ pendingOf: (sessionId) => {
3664
+ sweep();
3665
+ return sortedEntries().filter((entry) => sessionId === void 0 || entry.id === sessionId).flatMap((entry) => pendingSorted(entry));
3666
+ },
3667
+ credentialOf: (sessionId) => {
3668
+ const entry = entries.get(sessionId);
3669
+ return entry === void 0 ? void 0 : { authorization: entry.authorization, anthropicVersion: entry.anthropicVersion, anthropicClientPlatform: entry.anthropicClientPlatform };
3670
+ },
3671
+ completePending: (sessionId, requestId) => {
3672
+ entries.get(sessionId)?.pending.delete(requestId);
3673
+ },
3674
+ close: () => {
3675
+ clearInterval(timer);
3676
+ }
3677
+ };
3678
+ }
3679
+ function observingRoutedRoute(route, tracker) {
3680
+ return {
3681
+ ...route,
3682
+ serve: async (request4, response) => {
3683
+ const observed = tracker.observeExchange({ method: request4.method, url: request4.url, headers: request4.headers });
3684
+ if (observed?.onRequestChunk !== void 0) {
3685
+ request4.body.on("data", (chunk) => {
3686
+ observed.onRequestChunk?.(chunk);
3687
+ });
3688
+ request4.body.on("end", () => {
3689
+ observed.onRequestEnd?.();
3690
+ });
3691
+ }
3692
+ await route.serve(request4, observed === void 0 ? response : teeRoutedResponse(response, observed));
3693
+ }
3694
+ };
3695
+ }
3696
+ function teeRoutedResponse(response, observer) {
3697
+ return {
3698
+ get headersSent() {
3699
+ return response.headersSent;
3700
+ },
3701
+ start: (status, headers) => {
3702
+ observer.onResponse(status, headers);
3703
+ response.start(status, headers);
3704
+ },
3705
+ flush: () => {
3706
+ response.flush();
3707
+ },
3708
+ write: async (chunk) => {
3709
+ observer.onBodyChunk(typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk);
3710
+ await response.write(chunk);
3711
+ },
3712
+ end: () => {
3713
+ response.end();
3714
+ observer.onEnd();
3715
+ },
3716
+ destroy: () => {
3717
+ response.destroy();
3718
+ observer.onEnd();
3719
+ }
3720
+ };
3721
+ }
3722
+ function rcObserverAsPassthrough(observer) {
3723
+ return {
3724
+ onRequestChunk: (chunk) => {
3725
+ observer.onRequestChunk?.(chunk);
3726
+ },
3727
+ ...observer.onRequestEnd === void 0 ? {} : {
3728
+ onRequestEnd: () => {
3729
+ observer.onRequestEnd?.();
3730
+ }
3731
+ },
3732
+ onResponse: (status, headers) => {
3733
+ observer.onResponse(status, headers);
3734
+ },
3735
+ onResponseChunk: (chunk) => {
3736
+ observer.onBodyChunk(chunk);
3737
+ },
3738
+ onEnd: () => {
3739
+ observer.onEnd();
3740
+ }
3741
+ };
3742
+ }
3743
+ function buildRcUserMessagePayload(uuid, sessionId, text) {
3744
+ return {
3745
+ type: "user",
3746
+ uuid,
3747
+ session_id: sessionId,
3748
+ parent_tool_use_id: null,
3749
+ message: { role: "user", content: text }
3750
+ };
3751
+ }
3752
+ function buildRcEventWriteBody(payload) {
3753
+ return { events: [{ payload }] };
3754
+ }
3755
+ function sequenceNumsOf(body) {
3756
+ let parsed;
3757
+ try {
3758
+ parsed = JSON.parse(body);
3759
+ } catch {
3760
+ return void 0;
3761
+ }
3762
+ if (typeof parsed !== "object" || parsed === null || !("results" in parsed) || !Array.isArray(parsed.results)) {
3763
+ return void 0;
3764
+ }
3765
+ const sequenceNums = [];
3766
+ const results = parsed.results;
3767
+ for (const result of results) {
3768
+ if (typeof result !== "object" || result === null || !("sequence_num" in result)) {
3769
+ return void 0;
3770
+ }
3771
+ const sequenceNum = result.sequence_num;
3772
+ if (typeof sequenceNum !== "number") {
3773
+ return void 0;
3774
+ }
3775
+ sequenceNums.push(sequenceNum);
3776
+ }
3777
+ return sequenceNums.length === 0 ? void 0 : sequenceNums;
3778
+ }
3779
+ function rcEventWriteResultFromAnswer(answer) {
3780
+ if (!isSuccessful(answer.status)) {
3781
+ if (answer.status === HTTP_STATUS.unauthorized) {
3782
+ 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}` };
3783
+ }
3784
+ return { ok: false, message: `the API answered HTTP ${String(answer.status)} to the injected event: ${answer.body}` };
3785
+ }
3786
+ const sequenceNums = sequenceNumsOf(answer.body);
3787
+ if (sequenceNums === void 0) {
3788
+ 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}` };
3789
+ }
3790
+ return { ok: true, sequenceNums };
3791
+ }
3792
+ function rcWriteHeaders(credential) {
3793
+ const headers = { "content-type": "application/json" };
3794
+ if (credential.authorization !== void 0) {
3795
+ headers.authorization = credential.authorization;
3796
+ }
3797
+ if (credential.anthropicVersion !== void 0) {
3798
+ headers["anthropic-version"] = credential.anthropicVersion;
3799
+ }
3800
+ if (credential.anthropicClientPlatform !== void 0) {
3801
+ headers["anthropic-client-platform"] = credential.anthropicClientPlatform;
3802
+ }
3803
+ return headers;
3804
+ }
3805
+ async function injectRcUserMessage(deps, sessionId, text) {
3806
+ const credential = deps.credentialOf(sessionId);
3807
+ if (credential === void 0) {
3808
+ 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)` };
3809
+ }
3810
+ const body = JSON.stringify(buildRcEventWriteBody(buildRcUserMessagePayload(deps.newUuid(), sessionId, text)));
3811
+ let answer;
3812
+ try {
3813
+ answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
3814
+ } catch (error) {
3815
+ return { ok: false, message: `the door could not reach the API host to inject into session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
3816
+ }
3817
+ return rcEventWriteResultFromAnswer(answer);
3818
+ }
3819
+ function buildRcControlResponsePayload(requestId, decision) {
3820
+ const result = decision.approve ? { behavior: "allow" } : { behavior: "deny", message: decision.message ?? "" };
3821
+ return { type: "control_response", response: { subtype: "success", request_id: requestId, response: result } };
3822
+ }
3823
+ async function answerRcControlRequest(deps, sessionId, requestId, decision) {
3824
+ const credential = deps.credentialOf(sessionId);
3825
+ if (credential === void 0) {
3826
+ 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)` };
3827
+ }
3828
+ if (!deps.pendingOf(sessionId).some((pending) => pending.requestId === requestId)) {
3829
+ 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` };
3830
+ }
3831
+ const body = JSON.stringify(buildRcEventWriteBody(buildRcControlResponsePayload(requestId, decision)));
3832
+ let answer;
3833
+ try {
3834
+ answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
3835
+ } catch (error) {
3836
+ return { ok: false, message: `the door could not reach the API host to answer on session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
3837
+ }
3838
+ const result = rcEventWriteResultFromAnswer(answer);
3839
+ if (result.ok) {
3840
+ deps.completePending(sessionId, requestId);
3841
+ }
3842
+ return result;
3843
+ }
3844
+
3845
+ // src/frontdoor/rcControl.ts
3846
+ import * as https2 from "node:https";
3847
+ var CONTROL_PATH_PREFIX = "/__agent-shim/rc";
3848
+ var CONTROL_BODY_CAP_BYTES = 10485760;
3849
+ function answerJson(response, status, body) {
3850
+ const text = JSON.stringify(body);
3851
+ response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text, "utf8")) });
3852
+ response.end(text);
3853
+ }
3854
+ async function readBody(request4) {
3855
+ return await new Promise((resolve, reject) => {
3856
+ const chunks = [];
3857
+ let total = 0;
3858
+ request4.on("data", (chunk) => {
3859
+ total += chunk.length;
3860
+ if (total > CONTROL_BODY_CAP_BYTES) {
3861
+ request4.destroy();
3862
+ reject(new Error("too large"));
3863
+ return;
3864
+ }
3865
+ chunks.push(chunk);
3866
+ });
3867
+ request4.on("end", () => {
3868
+ resolve(Buffer.concat(chunks).toString("utf8"));
3869
+ });
3870
+ request4.on("error", () => {
3871
+ reject(new Error("the request body ended early"));
3872
+ });
3873
+ });
3874
+ }
3875
+ function createRcControlHandler(deps) {
3876
+ const sessionsPath = `${CONTROL_PATH_PREFIX}/sessions`;
3877
+ const statusPath = `${CONTROL_PATH_PREFIX}/status`;
3878
+ const pendingPath = `${CONTROL_PATH_PREFIX}/pending`;
3879
+ const injectPath = `${CONTROL_PATH_PREFIX}/inject`;
3880
+ const answerPath = `${CONTROL_PATH_PREFIX}/answer`;
3881
+ return (request4, response) => {
3882
+ const presented = request4.headers.authorization;
3883
+ const bearerPrefix = "bearer ".length;
3884
+ const token = typeof presented === "string" && presented.slice(0, bearerPrefix).toLowerCase() === "bearer " ? presented.slice(bearerPrefix) : void 0;
3885
+ if (token === void 0 || !isLiveCapability(token, [deps.expectedToken])) {
3886
+ answerJson(response, HTTP_STATUS.unauthorized, { error: "the front door's control routes demand this generation's control token as a Bearer credential" });
3887
+ return;
3888
+ }
3889
+ const url = new URL(request4.url ?? "/", "http://127.0.0.1");
3890
+ const handle = async () => {
3891
+ if (url.pathname === sessionsPath) {
3892
+ if (request4.method !== "GET") {
3893
+ answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the session list is a GET" });
3894
+ return;
3895
+ }
3896
+ answerJson(response, HTTP_STATUS.ok, { sessions: deps.list() });
3897
+ return;
3898
+ }
3899
+ if (url.pathname === statusPath || url.pathname === pendingPath) {
3900
+ if (request4.method !== "GET") {
3901
+ answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the status and pending reads are GETs" });
3902
+ return;
3903
+ }
3904
+ const session = url.searchParams.get("session") ?? void 0;
3905
+ if (session !== void 0 && !deps.statusOf(session).some((entry) => entry.id === session)) {
3906
+ 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)` });
3907
+ return;
3908
+ }
3909
+ answerJson(response, HTTP_STATUS.ok, url.pathname === statusPath ? { statuses: deps.statusOf(session) } : { pending: deps.pendingOf(session) });
3910
+ return;
3911
+ }
3912
+ if (url.pathname === injectPath) {
3913
+ if (request4.method !== "POST") {
3914
+ answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the inject operation is a POST" });
3915
+ return;
3916
+ }
3917
+ let parsed;
3918
+ try {
3919
+ parsed = JSON.parse(await readBody(request4));
3920
+ } catch {
3921
+ answerJson(response, HTTP_STATUS.badRequest, { error: "the inject body is not readable JSON within the control body cap" });
3922
+ return;
3923
+ }
3924
+ if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("text" in parsed) || typeof parsed.session !== "string" || typeof parsed.text !== "string" || parsed.session === "" || parsed.text === "") {
3925
+ answerJson(response, HTTP_STATUS.badRequest, { error: "the inject body must be JSON naming a non-empty session id and text" });
3926
+ return;
3927
+ }
3928
+ const result = await deps.inject(parsed.session, parsed.text);
3929
+ if (result.ok) {
3930
+ answerJson(response, HTTP_STATUS.ok, { session: parsed.session, sequenceNums: result.sequenceNums });
3931
+ return;
3932
+ }
3933
+ answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
3934
+ return;
3935
+ }
3936
+ if (url.pathname === answerPath) {
3937
+ if (request4.method !== "POST") {
3938
+ answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the answer operation is a POST" });
3939
+ return;
3940
+ }
3941
+ let parsed;
3942
+ try {
3943
+ parsed = JSON.parse(await readBody(request4));
3944
+ } catch {
3945
+ answerJson(response, HTTP_STATUS.badRequest, { error: "the answer body is not readable JSON within the control body cap" });
3946
+ return;
3947
+ }
3948
+ 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 === "") {
3949
+ answerJson(response, HTTP_STATUS.badRequest, { error: "the answer body must be JSON naming a non-empty session id, request id, and an approve boolean" });
3950
+ return;
3951
+ }
3952
+ const text = "text" in parsed && typeof parsed.text === "string" ? parsed.text : void 0;
3953
+ if (parsed.approve && text !== void 0 && text !== "") {
3954
+ 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" });
3955
+ return;
3956
+ }
3957
+ const result = await deps.answer(parsed.session, parsed.request, { approve: parsed.approve, message: parsed.approve ? void 0 : text });
3958
+ if (result.ok) {
3959
+ answerJson(response, HTTP_STATUS.ok, { session: parsed.session, request: parsed.request, sequenceNums: result.sequenceNums });
3960
+ return;
3961
+ }
3962
+ answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
3963
+ return;
3964
+ }
3965
+ answerJson(response, HTTP_STATUS.notFound, { error: `no such control route: ${url.pathname}` });
3966
+ };
3967
+ handle().catch((error) => {
3968
+ answerJson(response, HTTP_STATUS.internalServerError, { error: `the control route failed: ${error instanceof Error ? error.message : String(error)}` });
3969
+ });
3970
+ };
3971
+ }
3972
+ function realRcControlTransport(port, ca) {
3973
+ return {
3974
+ request: async (options) => await new Promise((resolve, reject) => {
3975
+ const request4 = https2.request(
3976
+ {
3977
+ host: "127.0.0.1",
3978
+ port,
3979
+ method: options.method,
3980
+ path: options.path,
3981
+ headers: options.body === void 0 ? options.headers : { ...options.headers, "content-length": String(Buffer.byteLength(options.body, "utf8")) },
3982
+ agent: false,
3983
+ ca: [ca]
3984
+ },
3985
+ (response) => {
3986
+ const chunks = [];
3987
+ response.on("data", (chunk) => {
3988
+ chunks.push(chunk);
3989
+ });
3990
+ response.on("end", () => {
3991
+ resolve({ status: response.statusCode ?? 0, body: Buffer.concat(chunks).toString("utf8") });
3992
+ });
3993
+ }
3994
+ );
3995
+ request4.once("error", reject);
3996
+ if (options.body === void 0) {
3997
+ request4.end();
3998
+ } else {
3999
+ request4.end(options.body);
4000
+ }
4001
+ })
4002
+ };
4003
+ }
4004
+ function controlErrorOf(body) {
4005
+ try {
4006
+ const parsed = JSON.parse(body);
4007
+ if (typeof parsed === "object" && parsed !== null && "error" in parsed && typeof parsed.error === "string") {
4008
+ return parsed.error;
4009
+ }
4010
+ } catch {
4011
+ return body;
4012
+ }
4013
+ return void 0;
4014
+ }
4015
+ function isRcSessionSummary(value) {
4016
+ if (typeof value !== "object" || value === null) {
4017
+ return false;
4018
+ }
4019
+ if (!("id" in value) || !("createdAt" in value) || !("lastSeenAt" in value)) {
4020
+ return false;
4021
+ }
4022
+ return typeof value.id === "string" && typeof value.createdAt === "number" && typeof value.lastSeenAt === "number";
4023
+ }
4024
+ function isRcWorkerFact(value, valueIs) {
4025
+ if (typeof value !== "object" || value === null) {
4026
+ return false;
4027
+ }
4028
+ if (!("value" in value) || !("observedAt" in value)) {
4029
+ return false;
4030
+ }
4031
+ return valueIs(value.value) && typeof value.observedAt === "number";
4032
+ }
4033
+ function isRcSessionStatus(value) {
4034
+ if (!isRcSessionSummary(value)) {
4035
+ return false;
4036
+ }
4037
+ if (!("pending" in value) || !Array.isArray(value.pending)) {
4038
+ return false;
4039
+ }
4040
+ if ("workerState" in value && value.workerState !== void 0 && !isRcWorkerFact(value.workerState, (candidate) => typeof candidate === "string")) {
4041
+ return false;
4042
+ }
4043
+ if ("workerIdleSeconds" in value && value.workerIdleSeconds !== void 0 && !isRcWorkerFact(value.workerIdleSeconds, (candidate) => typeof candidate === "number")) {
4044
+ return false;
4045
+ }
4046
+ return value.pending.every((candidate) => isRcPendingRequestSummary(candidate));
4047
+ }
4048
+ function isRcPendingRequestSummary(value) {
4049
+ if (typeof value !== "object" || value === null) {
4050
+ return false;
4051
+ }
4052
+ if (!("sessionId" in value) || !("requestId" in value) || !("type" in value) || !("summary" in value) || !("observedAt" in value)) {
4053
+ return false;
4054
+ }
4055
+ return typeof value.sessionId === "string" && typeof value.requestId === "string" && typeof value.type === "string" && typeof value.summary === "string" && typeof value.observedAt === "number";
4056
+ }
4057
+ function sequenceNumsOfBody(body) {
4058
+ let parsed;
4059
+ try {
4060
+ parsed = JSON.parse(body);
4061
+ } catch {
4062
+ return void 0;
4063
+ }
4064
+ if (typeof parsed !== "object" || parsed === null || !("sequenceNums" in parsed) || !Array.isArray(parsed.sequenceNums)) {
4065
+ return void 0;
4066
+ }
4067
+ const sequenceNums = [];
4068
+ const values = parsed.sequenceNums;
4069
+ for (const value of values) {
4070
+ if (typeof value !== "number") {
4071
+ return void 0;
4072
+ }
4073
+ sequenceNums.push(value);
4074
+ }
4075
+ return sequenceNums;
4076
+ }
4077
+ function frontDoorRcControl(transport, token) {
4078
+ const headers = { authorization: `Bearer ${token}` };
4079
+ const listAnswerOf = async (path29, what) => {
4080
+ try {
4081
+ return await transport.request({ method: "GET", path: path29, headers });
4082
+ } catch (error) {
4083
+ throw new Error(`${what}: could not reach the front door's control listener: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
4084
+ }
4085
+ };
4086
+ const sessionsOf = (answer, what) => {
4087
+ if (answer.status !== HTTP_STATUS.ok) {
4088
+ throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
4089
+ }
4090
+ let parsed;
4091
+ try {
4092
+ parsed = JSON.parse(answer.body);
4093
+ } catch {
4094
+ throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
4095
+ }
4096
+ if (typeof parsed !== "object" || parsed === null || !("sessions" in parsed) || !Array.isArray(parsed.sessions)) {
4097
+ throw new Error(`${what}: the door's answer named no sessions list: ${answer.body}`);
4098
+ }
4099
+ const candidates = parsed.sessions;
4100
+ const sessions = [];
4101
+ for (const value of candidates) {
4102
+ if (!isRcSessionSummary(value)) {
4103
+ throw new Error(`${what}: the door's answer named no sessions list: ${answer.body}`);
4104
+ }
4105
+ sessions.push(value);
4106
+ }
4107
+ return sessions;
4108
+ };
4109
+ const statusesOf = (answer, what) => {
4110
+ if (answer.status !== HTTP_STATUS.ok) {
4111
+ throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
4112
+ }
4113
+ let parsed;
4114
+ try {
4115
+ parsed = JSON.parse(answer.body);
4116
+ } catch {
4117
+ throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
4118
+ }
4119
+ if (typeof parsed !== "object" || parsed === null || !("statuses" in parsed) || !Array.isArray(parsed.statuses)) {
4120
+ throw new Error(`${what}: the door's answer named no status list: ${answer.body}`);
4121
+ }
4122
+ const statuses = [];
4123
+ for (const value of parsed.statuses) {
4124
+ if (!isRcSessionStatus(value)) {
4125
+ throw new Error(`${what}: the door's answer named no status list: ${answer.body}`);
4126
+ }
4127
+ statuses.push(value);
4128
+ }
4129
+ return statuses;
4130
+ };
4131
+ const pendingOfAnswer = (answer, what) => {
4132
+ if (answer.status !== HTTP_STATUS.ok) {
4133
+ throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
4134
+ }
4135
+ let parsed;
4136
+ try {
4137
+ parsed = JSON.parse(answer.body);
4138
+ } catch {
4139
+ throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
4140
+ }
4141
+ if (typeof parsed !== "object" || parsed === null || !("pending" in parsed) || !Array.isArray(parsed.pending)) {
4142
+ throw new Error(`${what}: the door's answer named no pending list: ${answer.body}`);
4143
+ }
4144
+ const pending = [];
4145
+ for (const value of parsed.pending) {
4146
+ if (!isRcPendingRequestSummary(value)) {
4147
+ throw new Error(`${what}: the door's answer named no pending list: ${answer.body}`);
4148
+ }
4149
+ pending.push(value);
4150
+ }
4151
+ return pending;
4152
+ };
4153
+ const writeOf = async (path29, body, describeFailure) => {
4154
+ let answer;
4155
+ try {
4156
+ answer = await transport.request({ method: "POST", path: path29, headers: { ...headers, "content-type": "application/json" }, body });
4157
+ } catch (error) {
4158
+ return { ok: false, message: `could not reach the front door's control listener: ${error instanceof Error ? error.message : String(error)}` };
4159
+ }
4160
+ if (answer.status === HTTP_STATUS.ok) {
4161
+ const sequenceNums = sequenceNumsOfBody(answer.body);
4162
+ if (sequenceNums === void 0) {
4163
+ return { ok: false, message: `the door accepted the event but its answer named no sequence numbers: ${answer.body}` };
4164
+ }
4165
+ return { ok: true, sequenceNums };
4166
+ }
4167
+ return { ok: false, message: describeFailure(answer.status, controlErrorOf(answer.body) ?? answer.body) };
4168
+ };
4169
+ const sessionQuery = (sessionId) => sessionId === void 0 ? "" : `?session=${encodeURIComponent(sessionId)}`;
4170
+ return {
4171
+ listSessions: async () => sessionsOf(await listAnswerOf(`${CONTROL_PATH_PREFIX}/sessions`, "listing the door's Remote Control sessions"), "listing the door's Remote Control sessions"),
4172
+ 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"),
4173
+ 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"),
4174
+ 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}`),
4175
+ answerRequest: async (sessionId, requestId, decision) => await writeOf(
4176
+ `${CONTROL_PATH_PREFIX}/answer`,
4177
+ JSON.stringify({ session: sessionId, request: requestId, approve: decision.approve, ...decision.approve || decision.message === void 0 ? {} : { text: decision.message } }),
4178
+ (status, message) => `answering control request ${requestId} on ${sessionId} failed (HTTP ${String(status)}): ${message}`
4179
+ )
4180
+ };
4181
+ }
4182
+
3311
4183
  // src/frontdoor/ensure.ts
3312
4184
  import { randomUUID as randomUUID3 } from "node:crypto";
3313
4185
 
3314
4186
  // src/headroom/state.ts
3315
- import { createHash as createHash2 } from "node:crypto";
4187
+ import { createHash as createHash3 } from "node:crypto";
3316
4188
  import path8 from "node:path";
3317
4189
  import { z as z4 } from "zod";
3318
4190
  var HEADROOM_ANTHROPIC_UPSTREAM = "https://api.anthropic.com";
@@ -3373,7 +4245,7 @@ function headroomUpstreams(providers, frontDoorOrigin2) {
3373
4245
  return upstreams;
3374
4246
  }
3375
4247
  function hashAllowlist(allowlist) {
3376
- return createHash2("sha256").update(allowlist.join("\n")).digest("hex");
4248
+ return createHash3("sha256").update(allowlist.join("\n")).digest("hex");
3377
4249
  }
3378
4250
  function readAllProviders(fs12, providersDir) {
3379
4251
  const result = [];
@@ -3871,7 +4743,7 @@ function ensureHeadroom(params) {
3871
4743
  }
3872
4744
 
3873
4745
  // src/headroom/settings.ts
3874
- import { createHash as createHash3 } from "node:crypto";
4746
+ import { createHash as createHash4 } from "node:crypto";
3875
4747
  function settingsOf(config) {
3876
4748
  return {
3877
4749
  ...config.mode === void 0 ? {} : { mode: config.mode },
@@ -3896,7 +4768,7 @@ function settingsEnv(settings) {
3896
4768
  return settings.rolloutChannel === void 0 ? {} : { HEADROOM_ROLLOUT_CHANNEL: settings.rolloutChannel };
3897
4769
  }
3898
4770
  function hashSettings(settings) {
3899
- return createHash3("sha256").update(JSON.stringify([...settingsArgs(settings), JSON.stringify(settingsEnv(settings))])).digest("hex");
4771
+ return createHash4("sha256").update(JSON.stringify([...settingsArgs(settings), JSON.stringify(settingsEnv(settings))])).digest("hex");
3900
4772
  }
3901
4773
 
3902
4774
  // src/headroom/socket.ts
@@ -4327,6 +5199,7 @@ function buildLayoutPaths(root) {
4327
5199
  frontdoorCaBundlesDir: path12.join(root, "frontdoor", "ca", "bundles"),
4328
5200
  frontdoorStateFile: path12.join(root, "frontdoor", "state.json"),
4329
5201
  frontdoorLockFile: path12.join(root, "frontdoor", "start.lock"),
5202
+ frontdoorControlTokenFile: path12.join(root, "frontdoor", "control-token"),
4330
5203
  frontdoorSessionsDir: path12.join(root, "frontdoor", "sessions"),
4331
5204
  frontdoorLogPath: path12.join(root, "logs", "frontdoor.log"),
4332
5205
  usageDir: path12.join(root, "usage"),
@@ -4965,14 +5838,14 @@ function providerExists(paths, name) {
4965
5838
  var LEGACY_PROVIDER_FIELDS = ["tokenEnv", "tokenCommand", "authScheme"];
4966
5839
  var LEGACY_ENV_CREDENTIAL_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
4967
5840
  var LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
4968
- function isRecord2(value) {
5841
+ function isRecord3(value) {
4969
5842
  return typeof value === "object" && value !== null && !Array.isArray(value);
4970
5843
  }
4971
5844
  function legacyProviderConversion(raw) {
4972
- if (!isRecord2(raw) || "credential" in raw) {
5845
+ if (!isRecord3(raw) || "credential" in raw) {
4973
5846
  return void 0;
4974
5847
  }
4975
- const env = isRecord2(raw.env) ? raw.env : void 0;
5848
+ const env = isRecord3(raw.env) ? raw.env : void 0;
4976
5849
  const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
4977
5850
  const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
4978
5851
  if (fields.length === 0) {
@@ -5821,7 +6694,7 @@ function collectPoolPick(params) {
5821
6694
 
5822
6695
  // src/realPorts.ts
5823
6696
  import { spawn, spawnSync } from "node:child_process";
5824
- import { createHash as createHash4 } from "node:crypto";
6697
+ import { createHash as createHash5 } from "node:crypto";
5825
6698
  import fs7 from "node:fs";
5826
6699
  import os3 from "node:os";
5827
6700
  import path21 from "node:path";
@@ -6016,7 +6889,7 @@ var realFarmFs = {
6016
6889
  },
6017
6890
  hashFile(filePath) {
6018
6891
  try {
6019
- return createHash4("sha256").update(fs7.readFileSync(filePath)).digest("hex");
6892
+ return createHash5("sha256").update(fs7.readFileSync(filePath)).digest("hex");
6020
6893
  } catch (error) {
6021
6894
  if (isEnoent3(error) || isErrorWithCode(error, "EISDIR")) {
6022
6895
  return void 0;
@@ -8012,7 +8885,7 @@ function probeFrontDoorSync(port, caPem) {
8012
8885
  }
8013
8886
 
8014
8887
  // src/frontdoor/trust.ts
8015
- import { createHash as createHash5 } from "node:crypto";
8888
+ import { createHash as createHash6 } from "node:crypto";
8016
8889
  import path28 from "node:path";
8017
8890
  var BUNDLE_NAME_HEX_DIGITS = 16;
8018
8891
  function resolveTrustBundle(params) {
@@ -8035,7 +8908,7 @@ function resolveTrustBundle(params) {
8035
8908
  }
8036
8909
  const combined = `${inheritedPem.trimEnd()}
8037
8910
  ${caPem}`;
8038
- const bundle = path28.join(bundlesDir, `${createHash5("sha256").update(combined).digest("hex").slice(0, BUNDLE_NAME_HEX_DIGITS)}.pem`);
8911
+ const bundle = path28.join(bundlesDir, `${createHash6("sha256").update(combined).digest("hex").slice(0, BUNDLE_NAME_HEX_DIGITS)}.pem`);
8039
8912
  if (!fs12.exists(bundle)) {
8040
8913
  fs12.mkdirp(bundlesDir);
8041
8914
  fs12.writeFileAtomic(bundle, combined);
@@ -8178,6 +9051,7 @@ export {
8178
9051
  CONNECT_INTERCEPT_HOST,
8179
9052
  CONNECT_INTERCEPT_HOSTS,
8180
9053
  CONNECT_LIMITS,
9054
+ CONTROL_PATH_PREFIX,
8181
9055
  CategoryClassificationOverlaySchema,
8182
9056
  CategoryClassificationSchema,
8183
9057
  CategoryMapSchema,
@@ -8216,6 +9090,10 @@ export {
8216
9090
  ProviderKindMismatchError,
8217
9091
  ProviderNotFoundError,
8218
9092
  ProviderSchema,
9093
+ RC_IDLE_EXPIRY_MS,
9094
+ RC_PENDING_DEADLINE_MS,
9095
+ RC_REQUEST_PARSE_CAP_BYTES,
9096
+ RC_SESSIONS_PATH_PREFIX,
8219
9097
  ROUTED_PATH_PREFIX,
8220
9098
  UsageError,
8221
9099
  UsageSnapshotError,
@@ -8225,8 +9103,12 @@ export {
8225
9103
  addIdentity,
8226
9104
  addPool,
8227
9105
  addProvider,
9106
+ answerRcControlRequest,
8228
9107
  buildEntryFacts,
8229
9108
  buildLayoutPaths,
9109
+ buildRcControlResponsePayload,
9110
+ buildRcEventWriteBody,
9111
+ buildRcUserMessagePayload,
8230
9112
  carryOver,
8231
9113
  checkReportHasWarnings,
8232
9114
  checkReportToJson,
@@ -8234,6 +9116,8 @@ export {
8234
9116
  collectDoctorReport,
8235
9117
  createLeafCache,
8236
9118
  createProfile,
9119
+ createRcControlHandler,
9120
+ createRcSessionTracker,
8237
9121
  describeProviderEndpoint,
8238
9122
  detectAmbientCredential,
8239
9123
  ensureCa,
@@ -8244,8 +9128,10 @@ export {
8244
9128
  formatCheckReport,
8245
9129
  formatDoctorReport,
8246
9130
  forwardableHeaders,
9131
+ frontDoorRcControl,
8247
9132
  generateCa,
8248
9133
  identityExists,
9134
+ injectRcUserMessage,
8249
9135
  isIdentityDirectoryName,
8250
9136
  isInterceptedHost,
8251
9137
  listDirectoryRules,
@@ -8254,11 +9140,14 @@ export {
8254
9140
  listProviders,
8255
9141
  listUsageSnapshots,
8256
9142
  mintLeaf,
9143
+ observingRoutedRoute,
8257
9144
  parseConnectTarget,
8258
9145
  prepareClaudeLaunch,
8259
9146
  prepareLaunch,
8260
9147
  profileExists,
8261
9148
  providerExists,
9149
+ rcEventWriteResultFromAnswer,
9150
+ rcObserverAsPassthrough,
8262
9151
  readActiveIdentity,
8263
9152
  readDirectoryRules,
8264
9153
  readFarmManifest,
@@ -8270,6 +9159,8 @@ export {
8270
9159
  readUsageSnapshot,
8271
9160
  realConnectCertStore,
8272
9161
  realConnectEffects,
9162
+ realRcControlTransport,
9163
+ realRcEventDial,
8273
9164
  recoverFarm,
8274
9165
  recoveryDiagnostics,
8275
9166
  removeDirectoryRule,