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.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 import_node_crypto4 = require("node:crypto");
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 import_node_crypto4.X509Certificate(ca.certPem);
2939
- (0, import_node_crypto4.createPrivateKey)(ca.keyPem);
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) => (request3, socket, head) => {
2949
- config.capture?.upgrade(request3);
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 = `${request3.method ?? "GET"} ${request3.url ?? "/"} HTTP/1.1\r
2955
- ${request3.rawHeaders.reduce((accumulated, value, index) => index % 2 === 0 ? `${accumulated}${value}: ` : `${accumulated}${value}\r
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 = (request3, response) => {
2969
- if (host === config.routedHost && servedByPipeline(request3.url)) {
2970
- const tunnelToken = tunnelTokens.get(request3.socket);
2971
- if (tunnelToken !== void 0 && request3.headers[AUTH_HEADER] === void 0) {
2972
- request3.headers[AUTH_HEADER] = tunnelToken;
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(request3, response);
3005
+ config.serveRouted(request4, response);
2975
3006
  return;
2976
3007
  }
2977
- effects.forwardHttp(config.upstreamFor(host), request3, response, config.capture?.observePassthrough(request3));
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, isLiveCapability) {
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 (!isLiveCapability(capability)) {
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", (request3, socket, head) => {
3380
- onUpgrade(request3, socket, head);
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, request3, response, observer) => {
3420
- const headers = forwardableHeaders(request3.headers);
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: request3.method,
3426
- path: request3.url,
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
- request3.on("data", (chunk) => {
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
- request3.pipe(upstream);
3469
- request3.socket.setNoDelay(true);
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 import_node_crypto6 = require("node:crypto");
4368
+ var import_node_crypto7 = require("node:crypto");
3479
4369
 
3480
4370
  // src/headroom/state.ts
3481
- var import_node_crypto5 = require("node:crypto");
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, import_node_crypto5.createHash)("sha256").update(allowlist.join("\n")).digest("hex");
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, import_node_crypto6.randomUUID)();
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 import_node_crypto7 = require("node:crypto");
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, import_node_crypto7.createHash)("sha256").update(JSON.stringify([...settingsArgs(settings), JSON.stringify(settingsEnv(settings))])).digest("hex");
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 isRecord2(value) {
6025
+ function isRecord3(value) {
5135
6026
  return typeof value === "object" && value !== null && !Array.isArray(value);
5136
6027
  }
5137
6028
  function legacyProviderConversion(raw) {
5138
- if (!isRecord2(raw) || "credential" in raw) {
6029
+ if (!isRecord3(raw) || "credential" in raw) {
5139
6030
  return void 0;
5140
6031
  }
5141
- const env = isRecord2(raw.env) ? raw.env : void 0;
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 import_node_crypto8 = require("node:crypto");
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, import_node_crypto8.createHash)("sha256").update(import_node_fs6.default.readFileSync(filePath)).digest("hex");
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 import_node_crypto9 = require("node:crypto");
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, import_node_crypto9.randomUUID)()
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 import_node_crypto11 = require("node:crypto");
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 import_node_crypto10 = require("node:crypto");
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, import_node_crypto10.createHash)("sha256").update(combined).digest("hex").slice(0, BUNDLE_NAME_HEX_DIGITS)}.pem`);
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, import_node_crypto11.randomUUID)()}`,
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,