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.mjs CHANGED
@@ -3342,6 +3342,11 @@ function realConnectEffects() {
3342
3342
  request4.on("data", (chunk) => {
3343
3343
  observer.onRequestChunk(chunk);
3344
3344
  });
3345
+ if (observer.onRequestEnd !== void 0) {
3346
+ request4.on("end", () => {
3347
+ observer.onRequestEnd?.();
3348
+ });
3349
+ }
3345
3350
  response.on("close", () => {
3346
3351
  observer.onEnd();
3347
3352
  });
@@ -3377,6 +3382,9 @@ var RC_SESSION_ID_PREFIX = "cse_";
3377
3382
  var RC_IDLE_EXPIRY_MS = 45e3;
3378
3383
  var RC_CREATE_RESPONSE_CAP_BYTES = 8192;
3379
3384
  var CONFLICT_REASON_HEADER = "x-ccr-conflict-reason";
3385
+ var RC_PENDING_DEADLINE_MS = 14e3;
3386
+ var RC_REQUEST_PARSE_CAP_BYTES = 262144;
3387
+ var RC_PENDING_SUMMARY_EXCERPT_CHARS = 300;
3380
3388
  var SUCCESS_STATUS_MAX_EXCLUSIVE = 300;
3381
3389
  function isSuccessful(status) {
3382
3390
  return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE;
@@ -3402,20 +3410,124 @@ function sessionCreateId(body) {
3402
3410
  const id = session.id;
3403
3411
  return typeof id === "string" && id.startsWith(RC_SESSION_ID_PREFIX) ? id : void 0;
3404
3412
  }
3413
+ function isRecord2(value) {
3414
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3415
+ }
3416
+ function parseJsonObject(body) {
3417
+ let parsed;
3418
+ try {
3419
+ parsed = JSON.parse(body);
3420
+ } catch {
3421
+ return void 0;
3422
+ }
3423
+ return isRecord2(parsed) ? parsed : void 0;
3424
+ }
3425
+ function nonEmptyString(record, field) {
3426
+ const value = record === void 0 ? void 0 : record[field];
3427
+ return typeof value === "string" && value !== "" ? value : void 0;
3428
+ }
3429
+ function finiteNumber(record, field) {
3430
+ const value = record === void 0 ? void 0 : record[field];
3431
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3432
+ }
3433
+ function excerpt(text) {
3434
+ return text.length <= RC_PENDING_SUMMARY_EXCERPT_CHARS ? text : `${text.slice(0, RC_PENDING_SUMMARY_EXCERPT_CHARS)}...`;
3435
+ }
3436
+ function payloadEventsOf(body) {
3437
+ const parsed = parseJsonObject(body);
3438
+ const events = parsed === void 0 ? void 0 : parsed.events;
3439
+ if (!Array.isArray(events)) {
3440
+ return [];
3441
+ }
3442
+ const payloads = [];
3443
+ for (const event of events) {
3444
+ if (!isRecord2(event)) {
3445
+ continue;
3446
+ }
3447
+ const payload = event.payload;
3448
+ if (!isRecord2(payload)) {
3449
+ continue;
3450
+ }
3451
+ payloads.push(payload);
3452
+ }
3453
+ return payloads;
3454
+ }
3455
+ function pendingOfControlRequestPayload(payload) {
3456
+ if (payload.type !== "control_request") {
3457
+ return void 0;
3458
+ }
3459
+ const requestId = nonEmptyString(payload, "request_id") ?? nonEmptyString(payload, "uuid");
3460
+ if (requestId === void 0) {
3461
+ return void 0;
3462
+ }
3463
+ const request4 = isRecord2(payload.request) ? payload.request : void 0;
3464
+ const subtype = nonEmptyString(request4, "subtype");
3465
+ const type = subtype ?? "control_request";
3466
+ let summary = "";
3467
+ if (subtype === "can_use_tool") {
3468
+ const toolName = nonEmptyString(request4, "tool_name") ?? "unknown tool";
3469
+ const input = request4 === void 0 ? void 0 : request4.input;
3470
+ summary = input === void 0 ? toolName : `${toolName} ${excerpt(JSON.stringify(input))}`;
3471
+ } else if (request4 !== void 0) {
3472
+ summary = excerpt(JSON.stringify(request4));
3473
+ }
3474
+ return { requestId, type, summary };
3475
+ }
3476
+ function answeredRequestIdsOfControlResponsePayload(payload) {
3477
+ if (payload.type !== "control_response") {
3478
+ return [];
3479
+ }
3480
+ const response = isRecord2(payload.response) ? payload.response : void 0;
3481
+ const id = nonEmptyString(response, "request_id") ?? nonEmptyString(payload, "request_id") ?? nonEmptyString(payload, "uuid");
3482
+ return id === void 0 ? [] : [id];
3483
+ }
3484
+ function parseRcRequestBody(kind, body) {
3485
+ if (kind === "heartbeat") {
3486
+ return { controlRequests: [], answeredRequestIds: [], idleSeconds: finiteNumber(parseJsonObject(body), "idle_seconds"), workerStatus: void 0 };
3487
+ }
3488
+ if (kind === "worker-registration") {
3489
+ return { controlRequests: [], answeredRequestIds: [], idleSeconds: void 0, workerStatus: nonEmptyString(parseJsonObject(body), "status") };
3490
+ }
3491
+ const controlRequests = [];
3492
+ const answeredRequestIds = [];
3493
+ for (const payload of payloadEventsOf(body)) {
3494
+ const pending = pendingOfControlRequestPayload(payload);
3495
+ if (pending !== void 0) {
3496
+ controlRequests.push({ requestId: pending.requestId, type: pending.type, summary: pending.summary });
3497
+ continue;
3498
+ }
3499
+ answeredRequestIds.push(...answeredRequestIdsOfControlResponsePayload(payload));
3500
+ }
3501
+ return { controlRequests, answeredRequestIds, idleSeconds: void 0, workerStatus: void 0 };
3502
+ }
3405
3503
  function createRcSessionTracker(deps) {
3406
3504
  const entries = /* @__PURE__ */ new Map();
3407
3505
  const expire = (id) => {
3408
3506
  entries.delete(id);
3409
3507
  };
3508
+ const expirePendings = (entry, now) => {
3509
+ for (const [requestId, pending] of entry.pending) {
3510
+ if (now - pending.observedAt >= RC_PENDING_DEADLINE_MS) {
3511
+ entry.pending.delete(requestId);
3512
+ }
3513
+ }
3514
+ };
3410
3515
  const sweep = () => {
3411
3516
  for (const [id, entry] of entries) {
3412
3517
  if (deps.now() - entry.lastSeenAt >= deps.idleMs) {
3413
3518
  expire(id);
3519
+ continue;
3414
3520
  }
3521
+ expirePendings(entry, deps.now());
3415
3522
  }
3416
3523
  };
3417
3524
  const timer = setInterval(sweep, deps.idleMs);
3418
3525
  timer.unref();
3526
+ const birth = (id, facts) => {
3527
+ const record = { id, createdAt: deps.now(), lastSeenAt: deps.now(), ...facts, pending: /* @__PURE__ */ new Map(), workerState: void 0, workerIdleSeconds: void 0 };
3528
+ entries.set(id, record);
3529
+ return record;
3530
+ };
3419
3531
  const observeExchange = (request4) => {
3420
3532
  const method = (request4.method ?? "").toUpperCase();
3421
3533
  const path29 = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
@@ -3432,30 +3544,30 @@ function createRcSessionTracker(deps) {
3432
3544
  if (method !== "POST") {
3433
3545
  return void 0;
3434
3546
  }
3435
- let accepted = false;
3436
- let buffered = 0;
3547
+ let accepted2 = false;
3548
+ let buffered2 = 0;
3437
3549
  let body = "";
3438
3550
  return {
3439
3551
  onResponse: (status) => {
3440
- accepted = isSuccessful(status);
3552
+ accepted2 = isSuccessful(status);
3441
3553
  },
3442
3554
  onBodyChunk: (chunk) => {
3443
- if (!accepted || buffered >= RC_CREATE_RESPONSE_CAP_BYTES) {
3555
+ if (!accepted2 || buffered2 >= RC_CREATE_RESPONSE_CAP_BYTES) {
3444
3556
  return;
3445
3557
  }
3446
3558
  const text = Buffer.from(chunk).toString("utf8");
3447
- body = body + text.slice(0, RC_CREATE_RESPONSE_CAP_BYTES - buffered);
3448
- buffered = body.length;
3559
+ body = body + text.slice(0, RC_CREATE_RESPONSE_CAP_BYTES - buffered2);
3560
+ buffered2 = body.length;
3449
3561
  },
3450
3562
  onEnd: () => {
3451
- if (!accepted) {
3563
+ if (!accepted2) {
3452
3564
  return;
3453
3565
  }
3454
3566
  const id2 = sessionCreateId(body);
3455
3567
  if (id2 === void 0) {
3456
3568
  return;
3457
3569
  }
3458
- entries.set(id2, { id: id2, createdAt: deps.now(), lastSeenAt: deps.now(), ...facts });
3570
+ birth(id2, facts);
3459
3571
  }
3460
3572
  };
3461
3573
  }
@@ -3464,21 +3576,69 @@ function createRcSessionTracker(deps) {
3464
3576
  if (id?.startsWith(RC_SESSION_ID_PREFIX) !== true) {
3465
3577
  return void 0;
3466
3578
  }
3467
- const entry = entries.get(id);
3468
- if (entry === void 0) {
3469
- entries.set(id, { id, createdAt: deps.now(), lastSeenAt: deps.now(), ...facts });
3470
- } else {
3471
- entry.lastSeenAt = deps.now();
3472
- entry.authorization = facts.authorization ?? entry.authorization;
3473
- entry.anthropicVersion = facts.anthropicVersion ?? entry.anthropicVersion;
3474
- entry.anthropicClientPlatform = facts.anthropicClientPlatform ?? entry.anthropicClientPlatform;
3475
- }
3579
+ const entry = entries.get(id) ?? birth(id, facts);
3580
+ entry.lastSeenAt = deps.now();
3581
+ entry.authorization = facts.authorization ?? entry.authorization;
3582
+ entry.anthropicVersion = facts.anthropicVersion ?? entry.anthropicVersion;
3583
+ entry.anthropicClientPlatform = facts.anthropicClientPlatform ?? entry.anthropicClientPlatform;
3584
+ const bodyKind = method === "POST" && rest === `${id}/worker/events` ? "worker-events" : method === "POST" && rest === `${id}/events` ? "client-events" : method === "POST" && rest === `${id}/worker/heartbeat` ? "heartbeat" : method === "PUT" && rest === `${id}/worker` ? "worker-registration" : void 0;
3585
+ let chunks = [];
3586
+ let buffered = 0;
3587
+ let oversized = false;
3588
+ let requestEnded = false;
3589
+ let accepted;
3590
+ let filed = false;
3591
+ const file = () => {
3592
+ if (filed || accepted !== true || !requestEnded || bodyKind === void 0) {
3593
+ return;
3594
+ }
3595
+ filed = true;
3596
+ const factsOfBody = oversized ? void 0 : parseRcRequestBody(bodyKind, Buffer.concat(chunks).toString("utf8"));
3597
+ chunks = [];
3598
+ const entryNow = entries.get(id);
3599
+ if (entryNow === void 0 || factsOfBody === void 0) {
3600
+ return;
3601
+ }
3602
+ const now = deps.now();
3603
+ for (const controlRequest of factsOfBody.controlRequests) {
3604
+ entryNow.pending.set(controlRequest.requestId, { sessionId: id, ...controlRequest, observedAt: now });
3605
+ }
3606
+ for (const requestId of factsOfBody.answeredRequestIds) {
3607
+ entryNow.pending.delete(requestId);
3608
+ }
3609
+ if (factsOfBody.idleSeconds !== void 0) {
3610
+ entryNow.workerIdleSeconds = { value: factsOfBody.idleSeconds, observedAt: now };
3611
+ }
3612
+ if (factsOfBody.workerStatus !== void 0) {
3613
+ entryNow.workerState = { value: factsOfBody.workerStatus, observedAt: now };
3614
+ }
3615
+ };
3476
3616
  const archiving = method === "POST" && rest === `${id}/archive`;
3477
3617
  return {
3618
+ ...bodyKind === void 0 ? {} : {
3619
+ onRequestChunk: (chunk) => {
3620
+ if (oversized) {
3621
+ return;
3622
+ }
3623
+ chunks.push(Buffer.from(chunk));
3624
+ buffered += chunk.length;
3625
+ if (buffered > RC_REQUEST_PARSE_CAP_BYTES) {
3626
+ oversized = true;
3627
+ chunks = [];
3628
+ }
3629
+ },
3630
+ onRequestEnd: () => {
3631
+ requestEnded = true;
3632
+ file();
3633
+ }
3634
+ },
3478
3635
  onResponse: (status, headers) => {
3479
3636
  if (singleHeader(headers, CONFLICT_REASON_HEADER) !== void 0 || archiving && isSuccessful(status)) {
3480
3637
  expire(id);
3638
+ return;
3481
3639
  }
3640
+ accepted = isSuccessful(status);
3641
+ file();
3482
3642
  },
3483
3643
  onBodyChunk: () => {
3484
3644
  },
@@ -3486,16 +3646,31 @@ function createRcSessionTracker(deps) {
3486
3646
  }
3487
3647
  };
3488
3648
  };
3649
+ const summaryOf = ({ id, createdAt, lastSeenAt }) => ({ id, createdAt, lastSeenAt });
3650
+ const pendingSummaryOf = ({ sessionId, requestId, type, summary, observedAt }) => ({ sessionId, requestId, type, summary, observedAt });
3651
+ const sortedEntries = () => [...entries.values()].sort((left, right) => left.createdAt - right.createdAt || (left.id < right.id ? -1 : 1));
3652
+ const pendingSorted = (entry) => [...entry.pending.values()].sort((left, right) => left.observedAt - right.observedAt || (left.requestId < right.requestId ? -1 : 1)).map(pendingSummaryOf);
3489
3653
  return {
3490
3654
  observeExchange,
3491
3655
  list: () => {
3492
3656
  sweep();
3493
- return [...entries.values()].sort((left, right) => left.createdAt - right.createdAt || (left.id < right.id ? -1 : 1)).map(({ id, createdAt, lastSeenAt }) => ({ id, createdAt, lastSeenAt }));
3657
+ return sortedEntries().map(summaryOf);
3658
+ },
3659
+ statusOf: (sessionId) => {
3660
+ sweep();
3661
+ return sortedEntries().filter((entry) => sessionId === void 0 || entry.id === sessionId).map((entry) => ({ ...summaryOf(entry), workerState: entry.workerState, workerIdleSeconds: entry.workerIdleSeconds, pending: pendingSorted(entry) }));
3662
+ },
3663
+ pendingOf: (sessionId) => {
3664
+ sweep();
3665
+ return sortedEntries().filter((entry) => sessionId === void 0 || entry.id === sessionId).flatMap((entry) => pendingSorted(entry));
3494
3666
  },
3495
3667
  credentialOf: (sessionId) => {
3496
3668
  const entry = entries.get(sessionId);
3497
3669
  return entry === void 0 ? void 0 : { authorization: entry.authorization, anthropicVersion: entry.anthropicVersion, anthropicClientPlatform: entry.anthropicClientPlatform };
3498
3670
  },
3671
+ completePending: (sessionId, requestId) => {
3672
+ entries.get(sessionId)?.pending.delete(requestId);
3673
+ },
3499
3674
  close: () => {
3500
3675
  clearInterval(timer);
3501
3676
  }
@@ -3506,6 +3681,14 @@ function observingRoutedRoute(route, tracker) {
3506
3681
  ...route,
3507
3682
  serve: async (request4, response) => {
3508
3683
  const observed = tracker.observeExchange({ method: request4.method, url: request4.url, headers: request4.headers });
3684
+ if (observed?.onRequestChunk !== void 0) {
3685
+ request4.body.on("data", (chunk) => {
3686
+ observed.onRequestChunk?.(chunk);
3687
+ });
3688
+ request4.body.on("end", () => {
3689
+ observed.onRequestEnd?.();
3690
+ });
3691
+ }
3509
3692
  await route.serve(request4, observed === void 0 ? response : teeRoutedResponse(response, observed));
3510
3693
  }
3511
3694
  };
@@ -3538,9 +3721,13 @@ function teeRoutedResponse(response, observer) {
3538
3721
  }
3539
3722
  function rcObserverAsPassthrough(observer) {
3540
3723
  return {
3541
- // 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.
3542
- onRequestChunk: () => {
3543
- return;
3724
+ onRequestChunk: (chunk) => {
3725
+ observer.onRequestChunk?.(chunk);
3726
+ },
3727
+ ...observer.onRequestEnd === void 0 ? {} : {
3728
+ onRequestEnd: () => {
3729
+ observer.onRequestEnd?.();
3730
+ }
3544
3731
  },
3545
3732
  onResponse: (status, headers) => {
3546
3733
  observer.onResponse(status, headers);
@@ -3589,7 +3776,7 @@ function sequenceNumsOf(body) {
3589
3776
  }
3590
3777
  return sequenceNums.length === 0 ? void 0 : sequenceNums;
3591
3778
  }
3592
- function rcInjectResultFromAnswer(answer) {
3779
+ function rcEventWriteResultFromAnswer(answer) {
3593
3780
  if (!isSuccessful(answer.status)) {
3594
3781
  if (answer.status === HTTP_STATUS.unauthorized) {
3595
3782
  return { ok: false, message: `the API refused the observed Authorization bearer for this session (HTTP 401): the bearer went stale after the door last saw the session's traffic. The session's own client refreshes it on its next call, which the door will observe, so retry once Remote Control has been active again. API answer: ${answer.body}` };
@@ -3602,11 +3789,7 @@ function rcInjectResultFromAnswer(answer) {
3602
3789
  }
3603
3790
  return { ok: true, sequenceNums };
3604
3791
  }
3605
- async function injectRcUserMessage(deps, sessionId, text) {
3606
- const credential = deps.credentialOf(sessionId);
3607
- if (credential === void 0) {
3608
- 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)` };
3609
- }
3792
+ function rcWriteHeaders(credential) {
3610
3793
  const headers = { "content-type": "application/json" };
3611
3794
  if (credential.authorization !== void 0) {
3612
3795
  headers.authorization = credential.authorization;
@@ -3617,14 +3800,46 @@ async function injectRcUserMessage(deps, sessionId, text) {
3617
3800
  if (credential.anthropicClientPlatform !== void 0) {
3618
3801
  headers["anthropic-client-platform"] = credential.anthropicClientPlatform;
3619
3802
  }
3803
+ return headers;
3804
+ }
3805
+ async function injectRcUserMessage(deps, sessionId, text) {
3806
+ const credential = deps.credentialOf(sessionId);
3807
+ if (credential === void 0) {
3808
+ return { ok: false, message: `the front door has not observed Remote Control session ${sessionId}: it may never have passed through this door, or it ended or expired (an entry lives only a bounded idle period past its last observed traffic)` };
3809
+ }
3620
3810
  const body = JSON.stringify(buildRcEventWriteBody(buildRcUserMessagePayload(deps.newUuid(), sessionId, text)));
3621
3811
  let answer;
3622
3812
  try {
3623
- answer = await deps.dial.writeEvents(sessionId, headers, body);
3813
+ answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
3624
3814
  } catch (error) {
3625
3815
  return { ok: false, message: `the door could not reach the API host to inject into session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
3626
3816
  }
3627
- return rcInjectResultFromAnswer(answer);
3817
+ return rcEventWriteResultFromAnswer(answer);
3818
+ }
3819
+ function buildRcControlResponsePayload(requestId, decision) {
3820
+ const result = decision.approve ? { behavior: "allow" } : { behavior: "deny", message: decision.message ?? "" };
3821
+ return { type: "control_response", response: { subtype: "success", request_id: requestId, response: result } };
3822
+ }
3823
+ async function answerRcControlRequest(deps, sessionId, requestId, decision) {
3824
+ const credential = deps.credentialOf(sessionId);
3825
+ if (credential === void 0) {
3826
+ return { ok: false, message: `the front door has not observed Remote Control session ${sessionId}: it may never have passed through this door, or it ended or expired (an entry lives only a bounded idle period past its last observed traffic)` };
3827
+ }
3828
+ if (!deps.pendingOf(sessionId).some((pending) => pending.requestId === requestId)) {
3829
+ return { ok: false, message: `the front door has not observed control request ${requestId} pending on session ${sessionId}: it was answered already (by this door or by an attached client), it passed its answer deadline (the protocol's permission round-trip bound), or it never passed through this door` };
3830
+ }
3831
+ const body = JSON.stringify(buildRcEventWriteBody(buildRcControlResponsePayload(requestId, decision)));
3832
+ let answer;
3833
+ try {
3834
+ answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
3835
+ } catch (error) {
3836
+ return { ok: false, message: `the door could not reach the API host to answer on session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
3837
+ }
3838
+ const result = rcEventWriteResultFromAnswer(answer);
3839
+ if (result.ok) {
3840
+ deps.completePending(sessionId, requestId);
3841
+ }
3842
+ return result;
3628
3843
  }
3629
3844
 
3630
3845
  // src/frontdoor/rcControl.ts
@@ -3659,7 +3874,10 @@ async function readBody(request4) {
3659
3874
  }
3660
3875
  function createRcControlHandler(deps) {
3661
3876
  const sessionsPath = `${CONTROL_PATH_PREFIX}/sessions`;
3877
+ const statusPath = `${CONTROL_PATH_PREFIX}/status`;
3878
+ const pendingPath = `${CONTROL_PATH_PREFIX}/pending`;
3662
3879
  const injectPath = `${CONTROL_PATH_PREFIX}/inject`;
3880
+ const answerPath = `${CONTROL_PATH_PREFIX}/answer`;
3663
3881
  return (request4, response) => {
3664
3882
  const presented = request4.headers.authorization;
3665
3883
  const bearerPrefix = "bearer ".length;
@@ -3668,9 +3886,9 @@ function createRcControlHandler(deps) {
3668
3886
  answerJson(response, HTTP_STATUS.unauthorized, { error: "the front door's control routes demand this generation's control token as a Bearer credential" });
3669
3887
  return;
3670
3888
  }
3671
- const url = request4.url ?? "/";
3889
+ const url = new URL(request4.url ?? "/", "http://127.0.0.1");
3672
3890
  const handle = async () => {
3673
- if (url === sessionsPath) {
3891
+ if (url.pathname === sessionsPath) {
3674
3892
  if (request4.method !== "GET") {
3675
3893
  answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the session list is a GET" });
3676
3894
  return;
@@ -3678,7 +3896,20 @@ function createRcControlHandler(deps) {
3678
3896
  answerJson(response, HTTP_STATUS.ok, { sessions: deps.list() });
3679
3897
  return;
3680
3898
  }
3681
- if (url === injectPath) {
3899
+ if (url.pathname === statusPath || url.pathname === pendingPath) {
3900
+ if (request4.method !== "GET") {
3901
+ answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the status and pending reads are GETs" });
3902
+ return;
3903
+ }
3904
+ const session = url.searchParams.get("session") ?? void 0;
3905
+ if (session !== void 0 && !deps.statusOf(session).some((entry) => entry.id === session)) {
3906
+ answerJson(response, HTTP_STATUS.notFound, { error: `the front door has not observed Remote Control session ${session}: it may never have passed through this door, or it ended or expired (an entry lives only a bounded idle period past its last observed traffic)` });
3907
+ return;
3908
+ }
3909
+ answerJson(response, HTTP_STATUS.ok, url.pathname === statusPath ? { statuses: deps.statusOf(session) } : { pending: deps.pendingOf(session) });
3910
+ return;
3911
+ }
3912
+ if (url.pathname === injectPath) {
3682
3913
  if (request4.method !== "POST") {
3683
3914
  answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the inject operation is a POST" });
3684
3915
  return;
@@ -3702,7 +3933,36 @@ function createRcControlHandler(deps) {
3702
3933
  answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
3703
3934
  return;
3704
3935
  }
3705
- answerJson(response, HTTP_STATUS.notFound, { error: `no such control route: ${url}` });
3936
+ if (url.pathname === answerPath) {
3937
+ if (request4.method !== "POST") {
3938
+ answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the answer operation is a POST" });
3939
+ return;
3940
+ }
3941
+ let parsed;
3942
+ try {
3943
+ parsed = JSON.parse(await readBody(request4));
3944
+ } catch {
3945
+ answerJson(response, HTTP_STATUS.badRequest, { error: "the answer body is not readable JSON within the control body cap" });
3946
+ return;
3947
+ }
3948
+ if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("request" in parsed) || !("approve" in parsed) || typeof parsed.session !== "string" || typeof parsed.request !== "string" || typeof parsed.approve !== "boolean" || parsed.session === "" || parsed.request === "") {
3949
+ answerJson(response, HTTP_STATUS.badRequest, { error: "the answer body must be JSON naming a non-empty session id, request id, and an approve boolean" });
3950
+ return;
3951
+ }
3952
+ const text = "text" in parsed && typeof parsed.text === "string" ? parsed.text : void 0;
3953
+ if (parsed.approve && text !== void 0 && text !== "") {
3954
+ answerJson(response, HTTP_STATUS.badRequest, { error: "an approval carries no text (the protocol's allow result has no message field); text is the denial message" });
3955
+ return;
3956
+ }
3957
+ const result = await deps.answer(parsed.session, parsed.request, { approve: parsed.approve, message: parsed.approve ? void 0 : text });
3958
+ if (result.ok) {
3959
+ answerJson(response, HTTP_STATUS.ok, { session: parsed.session, request: parsed.request, sequenceNums: result.sequenceNums });
3960
+ return;
3961
+ }
3962
+ answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
3963
+ return;
3964
+ }
3965
+ answerJson(response, HTTP_STATUS.notFound, { error: `no such control route: ${url.pathname}` });
3706
3966
  };
3707
3967
  handle().catch((error) => {
3708
3968
  answerJson(response, HTTP_STATUS.internalServerError, { error: `the control route failed: ${error instanceof Error ? error.message : String(error)}` });
@@ -3761,6 +4021,39 @@ function isRcSessionSummary(value) {
3761
4021
  }
3762
4022
  return typeof value.id === "string" && typeof value.createdAt === "number" && typeof value.lastSeenAt === "number";
3763
4023
  }
4024
+ function isRcWorkerFact(value, valueIs) {
4025
+ if (typeof value !== "object" || value === null) {
4026
+ return false;
4027
+ }
4028
+ if (!("value" in value) || !("observedAt" in value)) {
4029
+ return false;
4030
+ }
4031
+ return valueIs(value.value) && typeof value.observedAt === "number";
4032
+ }
4033
+ function isRcSessionStatus(value) {
4034
+ if (!isRcSessionSummary(value)) {
4035
+ return false;
4036
+ }
4037
+ if (!("pending" in value) || !Array.isArray(value.pending)) {
4038
+ return false;
4039
+ }
4040
+ if ("workerState" in value && value.workerState !== void 0 && !isRcWorkerFact(value.workerState, (candidate) => typeof candidate === "string")) {
4041
+ return false;
4042
+ }
4043
+ if ("workerIdleSeconds" in value && value.workerIdleSeconds !== void 0 && !isRcWorkerFact(value.workerIdleSeconds, (candidate) => typeof candidate === "number")) {
4044
+ return false;
4045
+ }
4046
+ return value.pending.every((candidate) => isRcPendingRequestSummary(candidate));
4047
+ }
4048
+ function isRcPendingRequestSummary(value) {
4049
+ if (typeof value !== "object" || value === null) {
4050
+ return false;
4051
+ }
4052
+ if (!("sessionId" in value) || !("requestId" in value) || !("type" in value) || !("summary" in value) || !("observedAt" in value)) {
4053
+ return false;
4054
+ }
4055
+ return typeof value.sessionId === "string" && typeof value.requestId === "string" && typeof value.type === "string" && typeof value.summary === "string" && typeof value.observedAt === "number";
4056
+ }
3764
4057
  function sequenceNumsOfBody(body) {
3765
4058
  let parsed;
3766
4059
  try {
@@ -3783,6 +4076,13 @@ function sequenceNumsOfBody(body) {
3783
4076
  }
3784
4077
  function frontDoorRcControl(transport, token) {
3785
4078
  const headers = { authorization: `Bearer ${token}` };
4079
+ const listAnswerOf = async (path29, what) => {
4080
+ try {
4081
+ return await transport.request({ method: "GET", path: path29, headers });
4082
+ } catch (error) {
4083
+ throw new Error(`${what}: could not reach the front door's control listener: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
4084
+ }
4085
+ };
3786
4086
  const sessionsOf = (answer, what) => {
3787
4087
  if (answer.status !== HTTP_STATUS.ok) {
3788
4088
  throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
@@ -3806,29 +4106,77 @@ function frontDoorRcControl(transport, token) {
3806
4106
  }
3807
4107
  return sessions;
3808
4108
  };
3809
- return {
3810
- listSessions: async () => sessionsOf(await transport.request({ method: "GET", path: `${CONTROL_PATH_PREFIX}/sessions`, headers }), "listing the door's Remote Control sessions"),
3811
- sendPrompt: async (sessionId, text) => {
3812
- let answer;
3813
- try {
3814
- answer = await transport.request({
3815
- method: "POST",
3816
- path: `${CONTROL_PATH_PREFIX}/inject`,
3817
- headers: { ...headers, "content-type": "application/json" },
3818
- body: JSON.stringify({ session: sessionId, text })
3819
- });
3820
- } catch (error) {
3821
- return { ok: false, message: `could not reach the front door's control listener: ${error instanceof Error ? error.message : String(error)}` };
4109
+ const statusesOf = (answer, what) => {
4110
+ if (answer.status !== HTTP_STATUS.ok) {
4111
+ throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
4112
+ }
4113
+ let parsed;
4114
+ try {
4115
+ parsed = JSON.parse(answer.body);
4116
+ } catch {
4117
+ throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
4118
+ }
4119
+ if (typeof parsed !== "object" || parsed === null || !("statuses" in parsed) || !Array.isArray(parsed.statuses)) {
4120
+ throw new Error(`${what}: the door's answer named no status list: ${answer.body}`);
4121
+ }
4122
+ const statuses = [];
4123
+ for (const value of parsed.statuses) {
4124
+ if (!isRcSessionStatus(value)) {
4125
+ throw new Error(`${what}: the door's answer named no status list: ${answer.body}`);
3822
4126
  }
3823
- if (answer.status === HTTP_STATUS.ok) {
3824
- const sequenceNums = sequenceNumsOfBody(answer.body);
3825
- if (sequenceNums === void 0) {
3826
- return { ok: false, message: `the door accepted the event but its answer named no sequence numbers: ${answer.body}` };
3827
- }
3828
- return { ok: true, sequenceNums };
4127
+ statuses.push(value);
4128
+ }
4129
+ return statuses;
4130
+ };
4131
+ const pendingOfAnswer = (answer, what) => {
4132
+ if (answer.status !== HTTP_STATUS.ok) {
4133
+ throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
4134
+ }
4135
+ let parsed;
4136
+ try {
4137
+ parsed = JSON.parse(answer.body);
4138
+ } catch {
4139
+ throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
4140
+ }
4141
+ if (typeof parsed !== "object" || parsed === null || !("pending" in parsed) || !Array.isArray(parsed.pending)) {
4142
+ throw new Error(`${what}: the door's answer named no pending list: ${answer.body}`);
4143
+ }
4144
+ const pending = [];
4145
+ for (const value of parsed.pending) {
4146
+ if (!isRcPendingRequestSummary(value)) {
4147
+ throw new Error(`${what}: the door's answer named no pending list: ${answer.body}`);
3829
4148
  }
3830
- return { ok: false, message: `injecting into ${sessionId} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}` };
4149
+ pending.push(value);
3831
4150
  }
4151
+ return pending;
4152
+ };
4153
+ const writeOf = async (path29, body, describeFailure) => {
4154
+ let answer;
4155
+ try {
4156
+ answer = await transport.request({ method: "POST", path: path29, headers: { ...headers, "content-type": "application/json" }, body });
4157
+ } catch (error) {
4158
+ return { ok: false, message: `could not reach the front door's control listener: ${error instanceof Error ? error.message : String(error)}` };
4159
+ }
4160
+ if (answer.status === HTTP_STATUS.ok) {
4161
+ const sequenceNums = sequenceNumsOfBody(answer.body);
4162
+ if (sequenceNums === void 0) {
4163
+ return { ok: false, message: `the door accepted the event but its answer named no sequence numbers: ${answer.body}` };
4164
+ }
4165
+ return { ok: true, sequenceNums };
4166
+ }
4167
+ return { ok: false, message: describeFailure(answer.status, controlErrorOf(answer.body) ?? answer.body) };
4168
+ };
4169
+ const sessionQuery = (sessionId) => sessionId === void 0 ? "" : `?session=${encodeURIComponent(sessionId)}`;
4170
+ return {
4171
+ listSessions: async () => sessionsOf(await listAnswerOf(`${CONTROL_PATH_PREFIX}/sessions`, "listing the door's Remote Control sessions"), "listing the door's Remote Control sessions"),
4172
+ statusOf: async (sessionId) => statusesOf(await listAnswerOf(`${CONTROL_PATH_PREFIX}/status${sessionQuery(sessionId)}`, "reading the door's Remote Control session status"), "reading the door's Remote Control session status"),
4173
+ pendingOf: async (sessionId) => pendingOfAnswer(await listAnswerOf(`${CONTROL_PATH_PREFIX}/pending${sessionQuery(sessionId)}`, "listing the door's pending Remote Control control requests"), "listing the door's pending Remote Control control requests"),
4174
+ sendPrompt: async (sessionId, text) => await writeOf(`${CONTROL_PATH_PREFIX}/inject`, JSON.stringify({ session: sessionId, text }), (status, message) => `injecting into ${sessionId} failed (HTTP ${String(status)}): ${message}`),
4175
+ answerRequest: async (sessionId, requestId, decision) => await writeOf(
4176
+ `${CONTROL_PATH_PREFIX}/answer`,
4177
+ JSON.stringify({ session: sessionId, request: requestId, approve: decision.approve, ...decision.approve || decision.message === void 0 ? {} : { text: decision.message } }),
4178
+ (status, message) => `answering control request ${requestId} on ${sessionId} failed (HTTP ${String(status)}): ${message}`
4179
+ )
3832
4180
  };
3833
4181
  }
3834
4182
 
@@ -5490,14 +5838,14 @@ function providerExists(paths, name) {
5490
5838
  var LEGACY_PROVIDER_FIELDS = ["tokenEnv", "tokenCommand", "authScheme"];
5491
5839
  var LEGACY_ENV_CREDENTIAL_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
5492
5840
  var LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
5493
- function isRecord2(value) {
5841
+ function isRecord3(value) {
5494
5842
  return typeof value === "object" && value !== null && !Array.isArray(value);
5495
5843
  }
5496
5844
  function legacyProviderConversion(raw) {
5497
- if (!isRecord2(raw) || "credential" in raw) {
5845
+ if (!isRecord3(raw) || "credential" in raw) {
5498
5846
  return void 0;
5499
5847
  }
5500
- const env = isRecord2(raw.env) ? raw.env : void 0;
5848
+ const env = isRecord3(raw.env) ? raw.env : void 0;
5501
5849
  const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
5502
5850
  const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
5503
5851
  if (fields.length === 0) {
@@ -8743,6 +9091,8 @@ export {
8743
9091
  ProviderNotFoundError,
8744
9092
  ProviderSchema,
8745
9093
  RC_IDLE_EXPIRY_MS,
9094
+ RC_PENDING_DEADLINE_MS,
9095
+ RC_REQUEST_PARSE_CAP_BYTES,
8746
9096
  RC_SESSIONS_PATH_PREFIX,
8747
9097
  ROUTED_PATH_PREFIX,
8748
9098
  UsageError,
@@ -8753,8 +9103,10 @@ export {
8753
9103
  addIdentity,
8754
9104
  addPool,
8755
9105
  addProvider,
9106
+ answerRcControlRequest,
8756
9107
  buildEntryFacts,
8757
9108
  buildLayoutPaths,
9109
+ buildRcControlResponsePayload,
8758
9110
  buildRcEventWriteBody,
8759
9111
  buildRcUserMessagePayload,
8760
9112
  carryOver,
@@ -8794,7 +9146,7 @@ export {
8794
9146
  prepareLaunch,
8795
9147
  profileExists,
8796
9148
  providerExists,
8797
- rcInjectResultFromAnswer,
9149
+ rcEventWriteResultFromAnswer,
8798
9150
  rcObserverAsPassthrough,
8799
9151
  readActiveIdentity,
8800
9152
  readDirectoryRules,