agent-shim 8.1.0 → 8.3.0

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