agent-shim 8.3.0 → 8.4.1

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
@@ -74,6 +74,8 @@ __export(index_exports, {
74
74
  ProviderNotFoundError: () => ProviderNotFoundError,
75
75
  ProviderSchema: () => ProviderSchema,
76
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,
77
79
  RC_SESSIONS_PATH_PREFIX: () => RC_SESSIONS_PATH_PREFIX,
78
80
  ROUTED_PATH_PREFIX: () => ROUTED_PATH_PREFIX,
79
81
  UsageError: () => UsageError,
@@ -84,8 +86,10 @@ __export(index_exports, {
84
86
  addIdentity: () => addIdentity,
85
87
  addPool: () => addPool,
86
88
  addProvider: () => addProvider,
89
+ answerRcControlRequest: () => answerRcControlRequest,
87
90
  buildEntryFacts: () => buildEntryFacts,
88
91
  buildLayoutPaths: () => buildLayoutPaths,
92
+ buildRcControlResponsePayload: () => buildRcControlResponsePayload,
89
93
  buildRcEventWriteBody: () => buildRcEventWriteBody,
90
94
  buildRcUserMessagePayload: () => buildRcUserMessagePayload,
91
95
  carryOver: () => carryOver,
@@ -125,7 +129,7 @@ __export(index_exports, {
125
129
  prepareLaunch: () => prepareLaunch,
126
130
  profileExists: () => profileExists,
127
131
  providerExists: () => providerExists,
128
- rcInjectResultFromAnswer: () => rcInjectResultFromAnswer,
132
+ rcEventWriteResultFromAnswer: () => rcEventWriteResultFromAnswer,
129
133
  rcObserverAsPassthrough: () => rcObserverAsPassthrough,
130
134
  readActiveIdentity: () => readActiveIdentity,
131
135
  readDirectoryRules: () => readDirectoryRules,
@@ -3522,6 +3526,11 @@ function realConnectEffects() {
3522
3526
  request4.on("data", (chunk) => {
3523
3527
  observer.onRequestChunk(chunk);
3524
3528
  });
3529
+ if (observer.onRequestEnd !== void 0) {
3530
+ request4.on("end", () => {
3531
+ observer.onRequestEnd?.();
3532
+ });
3533
+ }
3525
3534
  response.on("close", () => {
3526
3535
  observer.onEnd();
3527
3536
  });
@@ -3557,6 +3566,9 @@ var RC_SESSION_ID_PREFIX = "cse_";
3557
3566
  var RC_IDLE_EXPIRY_MS = 45e3;
3558
3567
  var RC_CREATE_RESPONSE_CAP_BYTES = 8192;
3559
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;
3560
3572
  var SUCCESS_STATUS_MAX_EXCLUSIVE = 300;
3561
3573
  function isSuccessful(status) {
3562
3574
  return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE;
@@ -3582,20 +3594,124 @@ function sessionCreateId(body) {
3582
3594
  const id = session.id;
3583
3595
  return typeof id === "string" && id.startsWith(RC_SESSION_ID_PREFIX) ? id : void 0;
3584
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
+ }
3585
3687
  function createRcSessionTracker(deps) {
3586
3688
  const entries = /* @__PURE__ */ new Map();
3587
3689
  const expire = (id) => {
3588
3690
  entries.delete(id);
3589
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
+ };
3590
3699
  const sweep = () => {
3591
3700
  for (const [id, entry] of entries) {
3592
3701
  if (deps.now() - entry.lastSeenAt >= deps.idleMs) {
3593
3702
  expire(id);
3703
+ continue;
3594
3704
  }
3705
+ expirePendings(entry, deps.now());
3595
3706
  }
3596
3707
  };
3597
3708
  const timer = setInterval(sweep, deps.idleMs);
3598
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
+ };
3599
3715
  const observeExchange = (request4) => {
3600
3716
  const method = (request4.method ?? "").toUpperCase();
3601
3717
  const path29 = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
@@ -3612,30 +3728,30 @@ function createRcSessionTracker(deps) {
3612
3728
  if (method !== "POST") {
3613
3729
  return void 0;
3614
3730
  }
3615
- let accepted = false;
3616
- let buffered = 0;
3731
+ let accepted2 = false;
3732
+ let buffered2 = 0;
3617
3733
  let body = "";
3618
3734
  return {
3619
3735
  onResponse: (status) => {
3620
- accepted = isSuccessful(status);
3736
+ accepted2 = isSuccessful(status);
3621
3737
  },
3622
3738
  onBodyChunk: (chunk) => {
3623
- if (!accepted || buffered >= RC_CREATE_RESPONSE_CAP_BYTES) {
3739
+ if (!accepted2 || buffered2 >= RC_CREATE_RESPONSE_CAP_BYTES) {
3624
3740
  return;
3625
3741
  }
3626
3742
  const text = Buffer.from(chunk).toString("utf8");
3627
- body = body + text.slice(0, RC_CREATE_RESPONSE_CAP_BYTES - buffered);
3628
- buffered = body.length;
3743
+ body = body + text.slice(0, RC_CREATE_RESPONSE_CAP_BYTES - buffered2);
3744
+ buffered2 = body.length;
3629
3745
  },
3630
3746
  onEnd: () => {
3631
- if (!accepted) {
3747
+ if (!accepted2) {
3632
3748
  return;
3633
3749
  }
3634
3750
  const id2 = sessionCreateId(body);
3635
3751
  if (id2 === void 0) {
3636
3752
  return;
3637
3753
  }
3638
- entries.set(id2, { id: id2, createdAt: deps.now(), lastSeenAt: deps.now(), ...facts });
3754
+ birth(id2, facts);
3639
3755
  }
3640
3756
  };
3641
3757
  }
@@ -3644,21 +3760,69 @@ function createRcSessionTracker(deps) {
3644
3760
  if (id?.startsWith(RC_SESSION_ID_PREFIX) !== true) {
3645
3761
  return void 0;
3646
3762
  }
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
- }
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
+ };
3656
3800
  const archiving = method === "POST" && rest === `${id}/archive`;
3657
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
+ },
3658
3819
  onResponse: (status, headers) => {
3659
3820
  if (singleHeader(headers, CONFLICT_REASON_HEADER) !== void 0 || archiving && isSuccessful(status)) {
3660
3821
  expire(id);
3822
+ return;
3661
3823
  }
3824
+ accepted = isSuccessful(status);
3825
+ file();
3662
3826
  },
3663
3827
  onBodyChunk: () => {
3664
3828
  },
@@ -3666,16 +3830,31 @@ function createRcSessionTracker(deps) {
3666
3830
  }
3667
3831
  };
3668
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);
3669
3837
  return {
3670
3838
  observeExchange,
3671
3839
  list: () => {
3672
3840
  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 }));
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));
3674
3850
  },
3675
3851
  credentialOf: (sessionId) => {
3676
3852
  const entry = entries.get(sessionId);
3677
3853
  return entry === void 0 ? void 0 : { authorization: entry.authorization, anthropicVersion: entry.anthropicVersion, anthropicClientPlatform: entry.anthropicClientPlatform };
3678
3854
  },
3855
+ completePending: (sessionId, requestId) => {
3856
+ entries.get(sessionId)?.pending.delete(requestId);
3857
+ },
3679
3858
  close: () => {
3680
3859
  clearInterval(timer);
3681
3860
  }
@@ -3686,6 +3865,14 @@ function observingRoutedRoute(route, tracker) {
3686
3865
  ...route,
3687
3866
  serve: async (request4, response) => {
3688
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
+ }
3689
3876
  await route.serve(request4, observed === void 0 ? response : teeRoutedResponse(response, observed));
3690
3877
  }
3691
3878
  };
@@ -3718,9 +3905,13 @@ function teeRoutedResponse(response, observer) {
3718
3905
  }
3719
3906
  function rcObserverAsPassthrough(observer) {
3720
3907
  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;
3908
+ onRequestChunk: (chunk) => {
3909
+ observer.onRequestChunk?.(chunk);
3910
+ },
3911
+ ...observer.onRequestEnd === void 0 ? {} : {
3912
+ onRequestEnd: () => {
3913
+ observer.onRequestEnd?.();
3914
+ }
3724
3915
  },
3725
3916
  onResponse: (status, headers) => {
3726
3917
  observer.onResponse(status, headers);
@@ -3769,7 +3960,7 @@ function sequenceNumsOf(body) {
3769
3960
  }
3770
3961
  return sequenceNums.length === 0 ? void 0 : sequenceNums;
3771
3962
  }
3772
- function rcInjectResultFromAnswer(answer) {
3963
+ function rcEventWriteResultFromAnswer(answer) {
3773
3964
  if (!isSuccessful(answer.status)) {
3774
3965
  if (answer.status === HTTP_STATUS.unauthorized) {
3775
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}` };
@@ -3782,11 +3973,7 @@ function rcInjectResultFromAnswer(answer) {
3782
3973
  }
3783
3974
  return { ok: true, sequenceNums };
3784
3975
  }
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
- }
3976
+ function rcWriteHeaders(credential) {
3790
3977
  const headers = { "content-type": "application/json" };
3791
3978
  if (credential.authorization !== void 0) {
3792
3979
  headers.authorization = credential.authorization;
@@ -3797,14 +3984,46 @@ async function injectRcUserMessage(deps, sessionId, text) {
3797
3984
  if (credential.anthropicClientPlatform !== void 0) {
3798
3985
  headers["anthropic-client-platform"] = credential.anthropicClientPlatform;
3799
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
+ }
3800
3994
  const body = JSON.stringify(buildRcEventWriteBody(buildRcUserMessagePayload(deps.newUuid(), sessionId, text)));
3801
3995
  let answer;
3802
3996
  try {
3803
- answer = await deps.dial.writeEvents(sessionId, headers, body);
3997
+ answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
3804
3998
  } catch (error) {
3805
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)}` };
3806
4000
  }
3807
- return rcInjectResultFromAnswer(answer);
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;
3808
4027
  }
3809
4028
 
3810
4029
  // src/frontdoor/rcControl.ts
@@ -3839,7 +4058,10 @@ async function readBody(request4) {
3839
4058
  }
3840
4059
  function createRcControlHandler(deps) {
3841
4060
  const sessionsPath = `${CONTROL_PATH_PREFIX}/sessions`;
4061
+ const statusPath = `${CONTROL_PATH_PREFIX}/status`;
4062
+ const pendingPath = `${CONTROL_PATH_PREFIX}/pending`;
3842
4063
  const injectPath = `${CONTROL_PATH_PREFIX}/inject`;
4064
+ const answerPath = `${CONTROL_PATH_PREFIX}/answer`;
3843
4065
  return (request4, response) => {
3844
4066
  const presented = request4.headers.authorization;
3845
4067
  const bearerPrefix = "bearer ".length;
@@ -3848,9 +4070,9 @@ function createRcControlHandler(deps) {
3848
4070
  answerJson(response, HTTP_STATUS.unauthorized, { error: "the front door's control routes demand this generation's control token as a Bearer credential" });
3849
4071
  return;
3850
4072
  }
3851
- const url = request4.url ?? "/";
4073
+ const url = new URL(request4.url ?? "/", "http://127.0.0.1");
3852
4074
  const handle = async () => {
3853
- if (url === sessionsPath) {
4075
+ if (url.pathname === sessionsPath) {
3854
4076
  if (request4.method !== "GET") {
3855
4077
  answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the session list is a GET" });
3856
4078
  return;
@@ -3858,7 +4080,20 @@ function createRcControlHandler(deps) {
3858
4080
  answerJson(response, HTTP_STATUS.ok, { sessions: deps.list() });
3859
4081
  return;
3860
4082
  }
3861
- if (url === injectPath) {
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) {
3862
4097
  if (request4.method !== "POST") {
3863
4098
  answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the inject operation is a POST" });
3864
4099
  return;
@@ -3882,7 +4117,36 @@ function createRcControlHandler(deps) {
3882
4117
  answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
3883
4118
  return;
3884
4119
  }
3885
- answerJson(response, HTTP_STATUS.notFound, { error: `no such control route: ${url}` });
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}` });
3886
4150
  };
3887
4151
  handle().catch((error) => {
3888
4152
  answerJson(response, HTTP_STATUS.internalServerError, { error: `the control route failed: ${error instanceof Error ? error.message : String(error)}` });
@@ -3941,6 +4205,39 @@ function isRcSessionSummary(value) {
3941
4205
  }
3942
4206
  return typeof value.id === "string" && typeof value.createdAt === "number" && typeof value.lastSeenAt === "number";
3943
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
+ }
3944
4241
  function sequenceNumsOfBody(body) {
3945
4242
  let parsed;
3946
4243
  try {
@@ -3963,6 +4260,13 @@ function sequenceNumsOfBody(body) {
3963
4260
  }
3964
4261
  function frontDoorRcControl(transport, token) {
3965
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
+ };
3966
4270
  const sessionsOf = (answer, what) => {
3967
4271
  if (answer.status !== HTTP_STATUS.ok) {
3968
4272
  throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
@@ -3986,29 +4290,77 @@ function frontDoorRcControl(transport, token) {
3986
4290
  }
3987
4291
  return sessions;
3988
4292
  };
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)}` };
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}`);
4002
4310
  }
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 };
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}`);
4009
4332
  }
4010
- return { ok: false, message: `injecting into ${sessionId} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}` };
4333
+ pending.push(value);
4011
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
+ )
4012
4364
  };
4013
4365
  }
4014
4366
 
@@ -5670,14 +6022,14 @@ function providerExists(paths, name) {
5670
6022
  var LEGACY_PROVIDER_FIELDS = ["tokenEnv", "tokenCommand", "authScheme"];
5671
6023
  var LEGACY_ENV_CREDENTIAL_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
5672
6024
  var LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
5673
- function isRecord2(value) {
6025
+ function isRecord3(value) {
5674
6026
  return typeof value === "object" && value !== null && !Array.isArray(value);
5675
6027
  }
5676
6028
  function legacyProviderConversion(raw) {
5677
- if (!isRecord2(raw) || "credential" in raw) {
6029
+ if (!isRecord3(raw) || "credential" in raw) {
5678
6030
  return void 0;
5679
6031
  }
5680
- const env = isRecord2(raw.env) ? raw.env : void 0;
6032
+ const env = isRecord3(raw.env) ? raw.env : void 0;
5681
6033
  const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
5682
6034
  const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
5683
6035
  if (fields.length === 0) {
@@ -8924,6 +9276,8 @@ function prepareClaudeLaunch(options) {
8924
9276
  ProviderNotFoundError,
8925
9277
  ProviderSchema,
8926
9278
  RC_IDLE_EXPIRY_MS,
9279
+ RC_PENDING_DEADLINE_MS,
9280
+ RC_REQUEST_PARSE_CAP_BYTES,
8927
9281
  RC_SESSIONS_PATH_PREFIX,
8928
9282
  ROUTED_PATH_PREFIX,
8929
9283
  UsageError,
@@ -8934,8 +9288,10 @@ function prepareClaudeLaunch(options) {
8934
9288
  addIdentity,
8935
9289
  addPool,
8936
9290
  addProvider,
9291
+ answerRcControlRequest,
8937
9292
  buildEntryFacts,
8938
9293
  buildLayoutPaths,
9294
+ buildRcControlResponsePayload,
8939
9295
  buildRcEventWriteBody,
8940
9296
  buildRcUserMessagePayload,
8941
9297
  carryOver,
@@ -8975,7 +9331,7 @@ function prepareClaudeLaunch(options) {
8975
9331
  prepareLaunch,
8976
9332
  profileExists,
8977
9333
  providerExists,
8978
- rcInjectResultFromAnswer,
9334
+ rcEventWriteResultFromAnswer,
8979
9335
  rcObserverAsPassthrough,
8980
9336
  readActiveIdentity,
8981
9337
  readDirectoryRules,