agent-shim 8.3.0 → 8.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -61040,7 +61040,7 @@ function registerIdentityCommand(program2, deps) {
61040
61040
  // package.json
61041
61041
  var package_default = {
61042
61042
  name: "agent-shim",
61043
- version: "8.3.0",
61043
+ version: "8.4.0",
61044
61044
  description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
61045
61045
  license: "MIT",
61046
61046
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -64064,19 +64064,19 @@ function createFileCapture(file2, now = () => /* @__PURE__ */ new Date()) {
64064
64064
  if (budget <= 0) {
64065
64065
  return;
64066
64066
  }
64067
- const excerpt = chunk.subarray(0, Math.min(CHUNK_LOG_CAP_BYTES, budget));
64068
- stored[direction] += excerpt.length;
64069
- const text2 = excerpt.toString("utf8");
64070
- const faithful = Buffer.from(text2, "utf8").equals(excerpt);
64067
+ const excerpt2 = chunk.subarray(0, Math.min(CHUNK_LOG_CAP_BYTES, budget));
64068
+ stored[direction] += excerpt2.length;
64069
+ const text2 = excerpt2.toString("utf8");
64070
+ const faithful = Buffer.from(text2, "utf8").equals(excerpt2);
64071
64071
  append({
64072
64072
  kind: "stream-chunk",
64073
64073
  id,
64074
64074
  dir: direction,
64075
64075
  seq: sequences[direction],
64076
64076
  bytes: chunk.length,
64077
- stored: excerpt.length,
64078
- truncated: excerpt.length < chunk.length,
64079
- ...faithful ? { body: redactText(text2) } : { b64: excerpt.toString("base64") }
64077
+ stored: excerpt2.length,
64078
+ truncated: excerpt2.length < chunk.length,
64079
+ ...faithful ? { body: redactText(text2) } : { b64: excerpt2.toString("base64") }
64080
64080
  });
64081
64081
  },
64082
64082
  onEnd: () => {
@@ -64096,16 +64096,16 @@ function createFileCapture(file2, now = () => /* @__PURE__ */ new Date()) {
64096
64096
  if (budget <= 0) {
64097
64097
  return;
64098
64098
  }
64099
- const excerpt = incoming.subarray(0, Math.min(CHUNK_LOG_CAP_BYTES, budget));
64100
- logged[direction] += excerpt.length;
64099
+ const excerpt2 = incoming.subarray(0, Math.min(CHUNK_LOG_CAP_BYTES, budget));
64100
+ logged[direction] += excerpt2.length;
64101
64101
  append({
64102
64102
  kind: `${direction}-chunk`,
64103
64103
  id,
64104
64104
  seq: sequences[direction],
64105
64105
  bytes: incoming.length,
64106
- stored: excerpt.length,
64107
- truncated: excerpt.length < incoming.length,
64108
- body: redactBody(excerpt)
64106
+ stored: excerpt2.length,
64107
+ truncated: excerpt2.length < incoming.length,
64108
+ body: redactBody(excerpt2)
64109
64109
  });
64110
64110
  };
64111
64111
  return {
@@ -65202,6 +65202,11 @@ function realConnectEffects() {
65202
65202
  request7.on("data", (chunk) => {
65203
65203
  observer.onRequestChunk(chunk);
65204
65204
  });
65205
+ if (observer.onRequestEnd !== void 0) {
65206
+ request7.on("end", () => {
65207
+ observer.onRequestEnd?.();
65208
+ });
65209
+ }
65205
65210
  response.on("close", () => {
65206
65211
  observer.onEnd();
65207
65212
  });
@@ -65254,7 +65259,10 @@ async function readBody(request7) {
65254
65259
  }
65255
65260
  function createRcControlHandler(deps) {
65256
65261
  const sessionsPath = `${CONTROL_PATH_PREFIX}/sessions`;
65262
+ const statusPath = `${CONTROL_PATH_PREFIX}/status`;
65263
+ const pendingPath = `${CONTROL_PATH_PREFIX}/pending`;
65257
65264
  const injectPath = `${CONTROL_PATH_PREFIX}/inject`;
65265
+ const answerPath = `${CONTROL_PATH_PREFIX}/answer`;
65258
65266
  return (request7, response) => {
65259
65267
  const presented = request7.headers.authorization;
65260
65268
  const bearerPrefix = "bearer ".length;
@@ -65263,9 +65271,9 @@ function createRcControlHandler(deps) {
65263
65271
  answerJson(response, HTTP_STATUS.unauthorized, { error: "the front door's control routes demand this generation's control token as a Bearer credential" });
65264
65272
  return;
65265
65273
  }
65266
- const url2 = request7.url ?? "/";
65274
+ const url2 = new URL(request7.url ?? "/", "http://127.0.0.1");
65267
65275
  const handle = async () => {
65268
- if (url2 === sessionsPath) {
65276
+ if (url2.pathname === sessionsPath) {
65269
65277
  if (request7.method !== "GET") {
65270
65278
  answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the session list is a GET" });
65271
65279
  return;
@@ -65273,7 +65281,20 @@ function createRcControlHandler(deps) {
65273
65281
  answerJson(response, HTTP_STATUS.ok, { sessions: deps.list() });
65274
65282
  return;
65275
65283
  }
65276
- if (url2 === injectPath) {
65284
+ if (url2.pathname === statusPath || url2.pathname === pendingPath) {
65285
+ if (request7.method !== "GET") {
65286
+ answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the status and pending reads are GETs" });
65287
+ return;
65288
+ }
65289
+ const session = url2.searchParams.get("session") ?? void 0;
65290
+ if (session !== void 0 && !deps.statusOf(session).some((entry) => entry.id === session)) {
65291
+ 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)` });
65292
+ return;
65293
+ }
65294
+ answerJson(response, HTTP_STATUS.ok, url2.pathname === statusPath ? { statuses: deps.statusOf(session) } : { pending: deps.pendingOf(session) });
65295
+ return;
65296
+ }
65297
+ if (url2.pathname === injectPath) {
65277
65298
  if (request7.method !== "POST") {
65278
65299
  answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the inject operation is a POST" });
65279
65300
  return;
@@ -65297,7 +65318,36 @@ function createRcControlHandler(deps) {
65297
65318
  answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
65298
65319
  return;
65299
65320
  }
65300
- answerJson(response, HTTP_STATUS.notFound, { error: `no such control route: ${url2}` });
65321
+ if (url2.pathname === answerPath) {
65322
+ if (request7.method !== "POST") {
65323
+ answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the answer operation is a POST" });
65324
+ return;
65325
+ }
65326
+ let parsed;
65327
+ try {
65328
+ parsed = JSON.parse(await readBody(request7));
65329
+ } catch {
65330
+ answerJson(response, HTTP_STATUS.badRequest, { error: "the answer body is not readable JSON within the control body cap" });
65331
+ return;
65332
+ }
65333
+ 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 === "") {
65334
+ answerJson(response, HTTP_STATUS.badRequest, { error: "the answer body must be JSON naming a non-empty session id, request id, and an approve boolean" });
65335
+ return;
65336
+ }
65337
+ const text2 = "text" in parsed && typeof parsed.text === "string" ? parsed.text : void 0;
65338
+ if (parsed.approve && text2 !== void 0 && text2 !== "") {
65339
+ 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" });
65340
+ return;
65341
+ }
65342
+ const result = await deps.answer(parsed.session, parsed.request, { approve: parsed.approve, message: parsed.approve ? void 0 : text2 });
65343
+ if (result.ok) {
65344
+ answerJson(response, HTTP_STATUS.ok, { session: parsed.session, request: parsed.request, sequenceNums: result.sequenceNums });
65345
+ return;
65346
+ }
65347
+ answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
65348
+ return;
65349
+ }
65350
+ answerJson(response, HTTP_STATUS.notFound, { error: `no such control route: ${url2.pathname}` });
65301
65351
  };
65302
65352
  handle().catch((error62) => {
65303
65353
  answerJson(response, HTTP_STATUS.internalServerError, { error: `the control route failed: ${error62 instanceof Error ? error62.message : String(error62)}` });
@@ -65356,6 +65406,39 @@ function isRcSessionSummary(value) {
65356
65406
  }
65357
65407
  return typeof value.id === "string" && typeof value.createdAt === "number" && typeof value.lastSeenAt === "number";
65358
65408
  }
65409
+ function isRcWorkerFact(value, valueIs) {
65410
+ if (typeof value !== "object" || value === null) {
65411
+ return false;
65412
+ }
65413
+ if (!("value" in value) || !("observedAt" in value)) {
65414
+ return false;
65415
+ }
65416
+ return valueIs(value.value) && typeof value.observedAt === "number";
65417
+ }
65418
+ function isRcSessionStatus(value) {
65419
+ if (!isRcSessionSummary(value)) {
65420
+ return false;
65421
+ }
65422
+ if (!("pending" in value) || !Array.isArray(value.pending)) {
65423
+ return false;
65424
+ }
65425
+ if ("workerState" in value && value.workerState !== void 0 && !isRcWorkerFact(value.workerState, (candidate) => typeof candidate === "string")) {
65426
+ return false;
65427
+ }
65428
+ if ("workerIdleSeconds" in value && value.workerIdleSeconds !== void 0 && !isRcWorkerFact(value.workerIdleSeconds, (candidate) => typeof candidate === "number")) {
65429
+ return false;
65430
+ }
65431
+ return value.pending.every((candidate) => isRcPendingRequestSummary(candidate));
65432
+ }
65433
+ function isRcPendingRequestSummary(value) {
65434
+ if (typeof value !== "object" || value === null) {
65435
+ return false;
65436
+ }
65437
+ if (!("sessionId" in value) || !("requestId" in value) || !("type" in value) || !("summary" in value) || !("observedAt" in value)) {
65438
+ return false;
65439
+ }
65440
+ return typeof value.sessionId === "string" && typeof value.requestId === "string" && typeof value.type === "string" && typeof value.summary === "string" && typeof value.observedAt === "number";
65441
+ }
65359
65442
  function sequenceNumsOfBody(body) {
65360
65443
  let parsed;
65361
65444
  try {
@@ -65378,6 +65461,13 @@ function sequenceNumsOfBody(body) {
65378
65461
  }
65379
65462
  function frontDoorRcControl(transport, token) {
65380
65463
  const headers = { authorization: `Bearer ${token}` };
65464
+ const listAnswerOf = async (path41, what) => {
65465
+ try {
65466
+ return await transport.request({ method: "GET", path: path41, headers });
65467
+ } catch (error62) {
65468
+ throw new Error(`${what}: could not reach the front door's control listener: ${error62 instanceof Error ? error62.message : String(error62)}`, { cause: error62 });
65469
+ }
65470
+ };
65381
65471
  const sessionsOf = (answer, what) => {
65382
65472
  if (answer.status !== HTTP_STATUS.ok) {
65383
65473
  throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
@@ -65401,29 +65491,77 @@ function frontDoorRcControl(transport, token) {
65401
65491
  }
65402
65492
  return sessions;
65403
65493
  };
65404
- return {
65405
- listSessions: async () => sessionsOf(await transport.request({ method: "GET", path: `${CONTROL_PATH_PREFIX}/sessions`, headers }), "listing the door's Remote Control sessions"),
65406
- sendPrompt: async (sessionId, text2) => {
65407
- let answer;
65408
- try {
65409
- answer = await transport.request({
65410
- method: "POST",
65411
- path: `${CONTROL_PATH_PREFIX}/inject`,
65412
- headers: { ...headers, "content-type": "application/json" },
65413
- body: JSON.stringify({ session: sessionId, text: text2 })
65414
- });
65415
- } catch (error62) {
65416
- return { ok: false, message: `could not reach the front door's control listener: ${error62 instanceof Error ? error62.message : String(error62)}` };
65494
+ const statusesOf = (answer, what) => {
65495
+ if (answer.status !== HTTP_STATUS.ok) {
65496
+ throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
65497
+ }
65498
+ let parsed;
65499
+ try {
65500
+ parsed = JSON.parse(answer.body);
65501
+ } catch {
65502
+ throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
65503
+ }
65504
+ if (typeof parsed !== "object" || parsed === null || !("statuses" in parsed) || !Array.isArray(parsed.statuses)) {
65505
+ throw new Error(`${what}: the door's answer named no status list: ${answer.body}`);
65506
+ }
65507
+ const statuses = [];
65508
+ for (const value of parsed.statuses) {
65509
+ if (!isRcSessionStatus(value)) {
65510
+ throw new Error(`${what}: the door's answer named no status list: ${answer.body}`);
65417
65511
  }
65418
- if (answer.status === HTTP_STATUS.ok) {
65419
- const sequenceNums = sequenceNumsOfBody(answer.body);
65420
- if (sequenceNums === void 0) {
65421
- return { ok: false, message: `the door accepted the event but its answer named no sequence numbers: ${answer.body}` };
65422
- }
65423
- return { ok: true, sequenceNums };
65512
+ statuses.push(value);
65513
+ }
65514
+ return statuses;
65515
+ };
65516
+ const pendingOfAnswer = (answer, what) => {
65517
+ if (answer.status !== HTTP_STATUS.ok) {
65518
+ throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
65519
+ }
65520
+ let parsed;
65521
+ try {
65522
+ parsed = JSON.parse(answer.body);
65523
+ } catch {
65524
+ throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
65525
+ }
65526
+ if (typeof parsed !== "object" || parsed === null || !("pending" in parsed) || !Array.isArray(parsed.pending)) {
65527
+ throw new Error(`${what}: the door's answer named no pending list: ${answer.body}`);
65528
+ }
65529
+ const pending = [];
65530
+ for (const value of parsed.pending) {
65531
+ if (!isRcPendingRequestSummary(value)) {
65532
+ throw new Error(`${what}: the door's answer named no pending list: ${answer.body}`);
65424
65533
  }
65425
- return { ok: false, message: `injecting into ${sessionId} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}` };
65534
+ pending.push(value);
65426
65535
  }
65536
+ return pending;
65537
+ };
65538
+ const writeOf = async (path41, body, describeFailure) => {
65539
+ let answer;
65540
+ try {
65541
+ answer = await transport.request({ method: "POST", path: path41, headers: { ...headers, "content-type": "application/json" }, body });
65542
+ } catch (error62) {
65543
+ return { ok: false, message: `could not reach the front door's control listener: ${error62 instanceof Error ? error62.message : String(error62)}` };
65544
+ }
65545
+ if (answer.status === HTTP_STATUS.ok) {
65546
+ const sequenceNums = sequenceNumsOfBody(answer.body);
65547
+ if (sequenceNums === void 0) {
65548
+ return { ok: false, message: `the door accepted the event but its answer named no sequence numbers: ${answer.body}` };
65549
+ }
65550
+ return { ok: true, sequenceNums };
65551
+ }
65552
+ return { ok: false, message: describeFailure(answer.status, controlErrorOf(answer.body) ?? answer.body) };
65553
+ };
65554
+ const sessionQuery = (sessionId) => sessionId === void 0 ? "" : `?session=${encodeURIComponent(sessionId)}`;
65555
+ return {
65556
+ listSessions: async () => sessionsOf(await listAnswerOf(`${CONTROL_PATH_PREFIX}/sessions`, "listing the door's Remote Control sessions"), "listing the door's Remote Control sessions"),
65557
+ 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"),
65558
+ 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"),
65559
+ sendPrompt: async (sessionId, text2) => await writeOf(`${CONTROL_PATH_PREFIX}/inject`, JSON.stringify({ session: sessionId, text: text2 }), (status, message) => `injecting into ${sessionId} failed (HTTP ${String(status)}): ${message}`),
65560
+ answerRequest: async (sessionId, requestId, decision) => await writeOf(
65561
+ `${CONTROL_PATH_PREFIX}/answer`,
65562
+ JSON.stringify({ session: sessionId, request: requestId, approve: decision.approve, ...decision.approve || decision.message === void 0 ? {} : { text: decision.message } }),
65563
+ (status, message) => `answering control request ${requestId} on ${sessionId} failed (HTTP ${String(status)}): ${message}`
65564
+ )
65427
65565
  };
65428
65566
  }
65429
65567
 
@@ -66787,6 +66925,9 @@ var RC_SESSION_ID_PREFIX = "cse_";
66787
66925
  var RC_IDLE_EXPIRY_MS = 45e3;
66788
66926
  var RC_CREATE_RESPONSE_CAP_BYTES = 8192;
66789
66927
  var CONFLICT_REASON_HEADER = "x-ccr-conflict-reason";
66928
+ var RC_PENDING_DEADLINE_MS = 14e3;
66929
+ var RC_REQUEST_PARSE_CAP_BYTES = 262144;
66930
+ var RC_PENDING_SUMMARY_EXCERPT_CHARS = 300;
66790
66931
  var SUCCESS_STATUS_MAX_EXCLUSIVE = 300;
66791
66932
  function isSuccessful(status) {
66792
66933
  return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE;
@@ -66812,20 +66953,124 @@ function sessionCreateId(body) {
66812
66953
  const id = session.id;
66813
66954
  return typeof id === "string" && id.startsWith(RC_SESSION_ID_PREFIX) ? id : void 0;
66814
66955
  }
66956
+ function isRecord3(value) {
66957
+ return typeof value === "object" && value !== null && !Array.isArray(value);
66958
+ }
66959
+ function parseJsonObject(body) {
66960
+ let parsed;
66961
+ try {
66962
+ parsed = JSON.parse(body);
66963
+ } catch {
66964
+ return void 0;
66965
+ }
66966
+ return isRecord3(parsed) ? parsed : void 0;
66967
+ }
66968
+ function nonEmptyString(record2, field) {
66969
+ const value = record2 === void 0 ? void 0 : record2[field];
66970
+ return typeof value === "string" && value !== "" ? value : void 0;
66971
+ }
66972
+ function finiteNumber(record2, field) {
66973
+ const value = record2 === void 0 ? void 0 : record2[field];
66974
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
66975
+ }
66976
+ function excerpt(text2) {
66977
+ return text2.length <= RC_PENDING_SUMMARY_EXCERPT_CHARS ? text2 : `${text2.slice(0, RC_PENDING_SUMMARY_EXCERPT_CHARS)}...`;
66978
+ }
66979
+ function payloadEventsOf(body) {
66980
+ const parsed = parseJsonObject(body);
66981
+ const events = parsed === void 0 ? void 0 : parsed.events;
66982
+ if (!Array.isArray(events)) {
66983
+ return [];
66984
+ }
66985
+ const payloads = [];
66986
+ for (const event of events) {
66987
+ if (!isRecord3(event)) {
66988
+ continue;
66989
+ }
66990
+ const payload = event.payload;
66991
+ if (!isRecord3(payload)) {
66992
+ continue;
66993
+ }
66994
+ payloads.push(payload);
66995
+ }
66996
+ return payloads;
66997
+ }
66998
+ function pendingOfControlRequestPayload(payload) {
66999
+ if (payload.type !== "control_request") {
67000
+ return void 0;
67001
+ }
67002
+ const requestId = nonEmptyString(payload, "request_id") ?? nonEmptyString(payload, "uuid");
67003
+ if (requestId === void 0) {
67004
+ return void 0;
67005
+ }
67006
+ const request7 = isRecord3(payload.request) ? payload.request : void 0;
67007
+ const subtype = nonEmptyString(request7, "subtype");
67008
+ const type = subtype ?? "control_request";
67009
+ let summary = "";
67010
+ if (subtype === "can_use_tool") {
67011
+ const toolName = nonEmptyString(request7, "tool_name") ?? "unknown tool";
67012
+ const input2 = request7 === void 0 ? void 0 : request7.input;
67013
+ summary = input2 === void 0 ? toolName : `${toolName} ${excerpt(JSON.stringify(input2))}`;
67014
+ } else if (request7 !== void 0) {
67015
+ summary = excerpt(JSON.stringify(request7));
67016
+ }
67017
+ return { requestId, type, summary };
67018
+ }
67019
+ function answeredRequestIdsOfControlResponsePayload(payload) {
67020
+ if (payload.type !== "control_response") {
67021
+ return [];
67022
+ }
67023
+ const response = isRecord3(payload.response) ? payload.response : void 0;
67024
+ const id = nonEmptyString(response, "request_id") ?? nonEmptyString(payload, "request_id") ?? nonEmptyString(payload, "uuid");
67025
+ return id === void 0 ? [] : [id];
67026
+ }
67027
+ function parseRcRequestBody(kind, body) {
67028
+ if (kind === "heartbeat") {
67029
+ return { controlRequests: [], answeredRequestIds: [], idleSeconds: finiteNumber(parseJsonObject(body), "idle_seconds"), workerStatus: void 0 };
67030
+ }
67031
+ if (kind === "worker-registration") {
67032
+ return { controlRequests: [], answeredRequestIds: [], idleSeconds: void 0, workerStatus: nonEmptyString(parseJsonObject(body), "status") };
67033
+ }
67034
+ const controlRequests = [];
67035
+ const answeredRequestIds = [];
67036
+ for (const payload of payloadEventsOf(body)) {
67037
+ const pending = pendingOfControlRequestPayload(payload);
67038
+ if (pending !== void 0) {
67039
+ controlRequests.push({ requestId: pending.requestId, type: pending.type, summary: pending.summary });
67040
+ continue;
67041
+ }
67042
+ answeredRequestIds.push(...answeredRequestIdsOfControlResponsePayload(payload));
67043
+ }
67044
+ return { controlRequests, answeredRequestIds, idleSeconds: void 0, workerStatus: void 0 };
67045
+ }
66815
67046
  function createRcSessionTracker(deps) {
66816
67047
  const entries = /* @__PURE__ */ new Map();
66817
67048
  const expire = (id) => {
66818
67049
  entries.delete(id);
66819
67050
  };
67051
+ const expirePendings = (entry, now) => {
67052
+ for (const [requestId, pending] of entry.pending) {
67053
+ if (now - pending.observedAt >= RC_PENDING_DEADLINE_MS) {
67054
+ entry.pending.delete(requestId);
67055
+ }
67056
+ }
67057
+ };
66820
67058
  const sweep = () => {
66821
67059
  for (const [id, entry] of entries) {
66822
67060
  if (deps.now() - entry.lastSeenAt >= deps.idleMs) {
66823
67061
  expire(id);
67062
+ continue;
66824
67063
  }
67064
+ expirePendings(entry, deps.now());
66825
67065
  }
66826
67066
  };
66827
67067
  const timer = setInterval(sweep, deps.idleMs);
66828
67068
  timer.unref();
67069
+ const birth = (id, facts) => {
67070
+ const record2 = { id, createdAt: deps.now(), lastSeenAt: deps.now(), ...facts, pending: /* @__PURE__ */ new Map(), workerState: void 0, workerIdleSeconds: void 0 };
67071
+ entries.set(id, record2);
67072
+ return record2;
67073
+ };
66829
67074
  const observeExchange = (request7) => {
66830
67075
  const method = (request7.method ?? "").toUpperCase();
66831
67076
  const path41 = new URL(request7.url ?? "/", "http://127.0.0.1").pathname;
@@ -66842,30 +67087,30 @@ function createRcSessionTracker(deps) {
66842
67087
  if (method !== "POST") {
66843
67088
  return void 0;
66844
67089
  }
66845
- let accepted = false;
66846
- let buffered = 0;
67090
+ let accepted2 = false;
67091
+ let buffered2 = 0;
66847
67092
  let body = "";
66848
67093
  return {
66849
67094
  onResponse: (status) => {
66850
- accepted = isSuccessful(status);
67095
+ accepted2 = isSuccessful(status);
66851
67096
  },
66852
67097
  onBodyChunk: (chunk) => {
66853
- if (!accepted || buffered >= RC_CREATE_RESPONSE_CAP_BYTES) {
67098
+ if (!accepted2 || buffered2 >= RC_CREATE_RESPONSE_CAP_BYTES) {
66854
67099
  return;
66855
67100
  }
66856
67101
  const text2 = Buffer.from(chunk).toString("utf8");
66857
- body = body + text2.slice(0, RC_CREATE_RESPONSE_CAP_BYTES - buffered);
66858
- buffered = body.length;
67102
+ body = body + text2.slice(0, RC_CREATE_RESPONSE_CAP_BYTES - buffered2);
67103
+ buffered2 = body.length;
66859
67104
  },
66860
67105
  onEnd: () => {
66861
- if (!accepted) {
67106
+ if (!accepted2) {
66862
67107
  return;
66863
67108
  }
66864
67109
  const id2 = sessionCreateId(body);
66865
67110
  if (id2 === void 0) {
66866
67111
  return;
66867
67112
  }
66868
- entries.set(id2, { id: id2, createdAt: deps.now(), lastSeenAt: deps.now(), ...facts });
67113
+ birth(id2, facts);
66869
67114
  }
66870
67115
  };
66871
67116
  }
@@ -66874,21 +67119,69 @@ function createRcSessionTracker(deps) {
66874
67119
  if (id?.startsWith(RC_SESSION_ID_PREFIX) !== true) {
66875
67120
  return void 0;
66876
67121
  }
66877
- const entry = entries.get(id);
66878
- if (entry === void 0) {
66879
- entries.set(id, { id, createdAt: deps.now(), lastSeenAt: deps.now(), ...facts });
66880
- } else {
66881
- entry.lastSeenAt = deps.now();
66882
- entry.authorization = facts.authorization ?? entry.authorization;
66883
- entry.anthropicVersion = facts.anthropicVersion ?? entry.anthropicVersion;
66884
- entry.anthropicClientPlatform = facts.anthropicClientPlatform ?? entry.anthropicClientPlatform;
66885
- }
67122
+ const entry = entries.get(id) ?? birth(id, facts);
67123
+ entry.lastSeenAt = deps.now();
67124
+ entry.authorization = facts.authorization ?? entry.authorization;
67125
+ entry.anthropicVersion = facts.anthropicVersion ?? entry.anthropicVersion;
67126
+ entry.anthropicClientPlatform = facts.anthropicClientPlatform ?? entry.anthropicClientPlatform;
67127
+ 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;
67128
+ let chunks = [];
67129
+ let buffered = 0;
67130
+ let oversized = false;
67131
+ let requestEnded = false;
67132
+ let accepted;
67133
+ let filed = false;
67134
+ const file2 = () => {
67135
+ if (filed || accepted !== true || !requestEnded || bodyKind === void 0) {
67136
+ return;
67137
+ }
67138
+ filed = true;
67139
+ const factsOfBody = oversized ? void 0 : parseRcRequestBody(bodyKind, Buffer.concat(chunks).toString("utf8"));
67140
+ chunks = [];
67141
+ const entryNow = entries.get(id);
67142
+ if (entryNow === void 0 || factsOfBody === void 0) {
67143
+ return;
67144
+ }
67145
+ const now = deps.now();
67146
+ for (const controlRequest of factsOfBody.controlRequests) {
67147
+ entryNow.pending.set(controlRequest.requestId, { sessionId: id, ...controlRequest, observedAt: now });
67148
+ }
67149
+ for (const requestId of factsOfBody.answeredRequestIds) {
67150
+ entryNow.pending.delete(requestId);
67151
+ }
67152
+ if (factsOfBody.idleSeconds !== void 0) {
67153
+ entryNow.workerIdleSeconds = { value: factsOfBody.idleSeconds, observedAt: now };
67154
+ }
67155
+ if (factsOfBody.workerStatus !== void 0) {
67156
+ entryNow.workerState = { value: factsOfBody.workerStatus, observedAt: now };
67157
+ }
67158
+ };
66886
67159
  const archiving = method === "POST" && rest === `${id}/archive`;
66887
67160
  return {
67161
+ ...bodyKind === void 0 ? {} : {
67162
+ onRequestChunk: (chunk) => {
67163
+ if (oversized) {
67164
+ return;
67165
+ }
67166
+ chunks.push(Buffer.from(chunk));
67167
+ buffered += chunk.length;
67168
+ if (buffered > RC_REQUEST_PARSE_CAP_BYTES) {
67169
+ oversized = true;
67170
+ chunks = [];
67171
+ }
67172
+ },
67173
+ onRequestEnd: () => {
67174
+ requestEnded = true;
67175
+ file2();
67176
+ }
67177
+ },
66888
67178
  onResponse: (status, headers) => {
66889
67179
  if (singleHeader(headers, CONFLICT_REASON_HEADER) !== void 0 || archiving && isSuccessful(status)) {
66890
67180
  expire(id);
67181
+ return;
66891
67182
  }
67183
+ accepted = isSuccessful(status);
67184
+ file2();
66892
67185
  },
66893
67186
  onBodyChunk: () => {
66894
67187
  },
@@ -66896,16 +67189,31 @@ function createRcSessionTracker(deps) {
66896
67189
  }
66897
67190
  };
66898
67191
  };
67192
+ const summaryOf = ({ id, createdAt, lastSeenAt }) => ({ id, createdAt, lastSeenAt });
67193
+ const pendingSummaryOf = ({ sessionId, requestId, type, summary, observedAt: observedAt2 }) => ({ sessionId, requestId, type, summary, observedAt: observedAt2 });
67194
+ const sortedEntries = () => [...entries.values()].sort((left, right) => left.createdAt - right.createdAt || (left.id < right.id ? -1 : 1));
67195
+ const pendingSorted = (entry) => [...entry.pending.values()].sort((left, right) => left.observedAt - right.observedAt || (left.requestId < right.requestId ? -1 : 1)).map(pendingSummaryOf);
66899
67196
  return {
66900
67197
  observeExchange,
66901
67198
  list: () => {
66902
67199
  sweep();
66903
- return [...entries.values()].sort((left, right) => left.createdAt - right.createdAt || (left.id < right.id ? -1 : 1)).map(({ id, createdAt, lastSeenAt }) => ({ id, createdAt, lastSeenAt }));
67200
+ return sortedEntries().map(summaryOf);
67201
+ },
67202
+ statusOf: (sessionId) => {
67203
+ sweep();
67204
+ return sortedEntries().filter((entry) => sessionId === void 0 || entry.id === sessionId).map((entry) => ({ ...summaryOf(entry), workerState: entry.workerState, workerIdleSeconds: entry.workerIdleSeconds, pending: pendingSorted(entry) }));
67205
+ },
67206
+ pendingOf: (sessionId) => {
67207
+ sweep();
67208
+ return sortedEntries().filter((entry) => sessionId === void 0 || entry.id === sessionId).flatMap((entry) => pendingSorted(entry));
66904
67209
  },
66905
67210
  credentialOf: (sessionId) => {
66906
67211
  const entry = entries.get(sessionId);
66907
67212
  return entry === void 0 ? void 0 : { authorization: entry.authorization, anthropicVersion: entry.anthropicVersion, anthropicClientPlatform: entry.anthropicClientPlatform };
66908
67213
  },
67214
+ completePending: (sessionId, requestId) => {
67215
+ entries.get(sessionId)?.pending.delete(requestId);
67216
+ },
66909
67217
  close: () => {
66910
67218
  clearInterval(timer);
66911
67219
  }
@@ -66916,6 +67224,14 @@ function observingRoutedRoute(route, tracker) {
66916
67224
  ...route,
66917
67225
  serve: async (request7, response) => {
66918
67226
  const observed = tracker.observeExchange({ method: request7.method, url: request7.url, headers: request7.headers });
67227
+ if (observed?.onRequestChunk !== void 0) {
67228
+ request7.body.on("data", (chunk) => {
67229
+ observed.onRequestChunk?.(chunk);
67230
+ });
67231
+ request7.body.on("end", () => {
67232
+ observed.onRequestEnd?.();
67233
+ });
67234
+ }
66919
67235
  await route.serve(request7, observed === void 0 ? response : teeRoutedResponse(response, observed));
66920
67236
  }
66921
67237
  };
@@ -66982,7 +67298,7 @@ function sequenceNumsOf(body) {
66982
67298
  }
66983
67299
  return sequenceNums.length === 0 ? void 0 : sequenceNums;
66984
67300
  }
66985
- function rcInjectResultFromAnswer(answer) {
67301
+ function rcEventWriteResultFromAnswer(answer) {
66986
67302
  if (!isSuccessful(answer.status)) {
66987
67303
  if (answer.status === HTTP_STATUS.unauthorized) {
66988
67304
  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}` };
@@ -66995,11 +67311,7 @@ function rcInjectResultFromAnswer(answer) {
66995
67311
  }
66996
67312
  return { ok: true, sequenceNums };
66997
67313
  }
66998
- async function injectRcUserMessage(deps, sessionId, text2) {
66999
- const credential = deps.credentialOf(sessionId);
67000
- if (credential === void 0) {
67001
- 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)` };
67002
- }
67314
+ function rcWriteHeaders(credential) {
67003
67315
  const headers = { "content-type": "application/json" };
67004
67316
  if (credential.authorization !== void 0) {
67005
67317
  headers.authorization = credential.authorization;
@@ -67010,14 +67322,46 @@ async function injectRcUserMessage(deps, sessionId, text2) {
67010
67322
  if (credential.anthropicClientPlatform !== void 0) {
67011
67323
  headers["anthropic-client-platform"] = credential.anthropicClientPlatform;
67012
67324
  }
67325
+ return headers;
67326
+ }
67327
+ async function injectRcUserMessage(deps, sessionId, text2) {
67328
+ const credential = deps.credentialOf(sessionId);
67329
+ if (credential === void 0) {
67330
+ 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)` };
67331
+ }
67013
67332
  const body = JSON.stringify(buildRcEventWriteBody(buildRcUserMessagePayload(deps.newUuid(), sessionId, text2)));
67014
67333
  let answer;
67015
67334
  try {
67016
- answer = await deps.dial.writeEvents(sessionId, headers, body);
67335
+ answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
67017
67336
  } catch (error62) {
67018
67337
  return { ok: false, message: `the door could not reach the API host to inject into session ${sessionId}: ${error62 instanceof Error ? error62.message : String(error62)}` };
67019
67338
  }
67020
- return rcInjectResultFromAnswer(answer);
67339
+ return rcEventWriteResultFromAnswer(answer);
67340
+ }
67341
+ function buildRcControlResponsePayload(requestId, decision) {
67342
+ const result = decision.approve ? { behavior: "allow" } : { behavior: "deny", message: decision.message ?? "" };
67343
+ return { type: "control_response", response: { subtype: "success", request_id: requestId, response: result } };
67344
+ }
67345
+ async function answerRcControlRequest(deps, sessionId, requestId, decision) {
67346
+ const credential = deps.credentialOf(sessionId);
67347
+ if (credential === void 0) {
67348
+ 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)` };
67349
+ }
67350
+ if (!deps.pendingOf(sessionId).some((pending) => pending.requestId === requestId)) {
67351
+ 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` };
67352
+ }
67353
+ const body = JSON.stringify(buildRcEventWriteBody(buildRcControlResponsePayload(requestId, decision)));
67354
+ let answer;
67355
+ try {
67356
+ answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
67357
+ } catch (error62) {
67358
+ return { ok: false, message: `the door could not reach the API host to answer on session ${sessionId}: ${error62 instanceof Error ? error62.message : String(error62)}` };
67359
+ }
67360
+ const result = rcEventWriteResultFromAnswer(answer);
67361
+ if (result.ok) {
67362
+ deps.completePending(sessionId, requestId);
67363
+ }
67364
+ return result;
67021
67365
  }
67022
67366
 
67023
67367
  // src/usage/providerQuota.ts
@@ -67377,7 +67721,9 @@ function realFrontDoorSupervisorPorts(paths) {
67377
67721
  isRunning: realIsProcessRunning,
67378
67722
  startProviderListener: async (preferredPort) => {
67379
67723
  const authority = loadCa();
67380
- const rcInject = async (sessionId, text2) => await injectRcUserMessage({ credentialOf: rcTracker.credentialOf, dial: realRcEventDial(), newUuid: import_node_crypto11.randomUUID }, sessionId, text2);
67724
+ const rcDial = realRcEventDial();
67725
+ const rcInject = async (sessionId, text2) => await injectRcUserMessage({ credentialOf: rcTracker.credentialOf, dial: rcDial, newUuid: import_node_crypto11.randomUUID }, sessionId, text2);
67726
+ const rcAnswer = async (sessionId, requestId, decision) => await answerRcControlRequest({ credentialOf: rcTracker.credentialOf, pendingOf: (id) => rcTracker.pendingOf(id), completePending: rcTracker.completePending, dial: rcDial }, sessionId, requestId, decision);
67381
67727
  realFarmFs.mkdirp(paths.frontdoorDir);
67382
67728
  realFarmFs.writeFilePrivate(paths.frontdoorControlTokenFile, `${rcControlToken}
67383
67729
  `);
@@ -67387,7 +67733,7 @@ function realFrontDoorSupervisorPorts(paths) {
67387
67733
  },
67388
67734
  log,
67389
67735
  mintLeaf(authority, LOOPBACK_LEAF_NAMES, /* @__PURE__ */ new Date()),
67390
- createRcControlHandler({ expectedToken: rcControlToken, list: rcTracker.list, inject: rcInject })
67736
+ createRcControlHandler({ expectedToken: rcControlToken, list: rcTracker.list, statusOf: rcTracker.statusOf, pendingOf: rcTracker.pendingOf, inject: rcInject, answer: rcAnswer })
67391
67737
  );
67392
67738
  const handle = await listenFrontDoor(server, { ...preferredPort === void 0 ? {} : { preferredPort }, ca: authority.certPem, onError: onListenerError("provider") });
67393
67739
  return {
@@ -67516,6 +67862,35 @@ function formatRcSessionList(sessions) {
67516
67862
  }
67517
67863
  return sessions.map((session) => `${session.id} created ${new Date(session.createdAt).toISOString()} last seen ${new Date(session.lastSeenAt).toISOString()}`);
67518
67864
  }
67865
+ function formatRcSessionStatus(statuses) {
67866
+ if (statuses.length === 0) {
67867
+ return ["no Remote Control sessions observed"];
67868
+ }
67869
+ const lines = [];
67870
+ for (const status of statuses) {
67871
+ lines.push(`${status.id} created ${new Date(status.createdAt).toISOString()} last seen ${new Date(status.lastSeenAt).toISOString()}`);
67872
+ const worker = status.workerState === void 0 && status.workerIdleSeconds === void 0 ? "not observed" : [
67873
+ status.workerState === void 0 ? "state unknown" : `state ${status.workerState.value} since ${new Date(status.workerState.observedAt).toISOString()}`,
67874
+ status.workerIdleSeconds === void 0 ? "idle unknown" : `idle ${String(status.workerIdleSeconds.value)} s as of ${new Date(status.workerIdleSeconds.observedAt).toISOString()}`
67875
+ ].join(", ");
67876
+ lines.push(` worker: ${worker}`);
67877
+ if (status.pending.length === 0) {
67878
+ lines.push(" pending: none");
67879
+ } else {
67880
+ lines.push(" pending:");
67881
+ for (const pending of status.pending) {
67882
+ lines.push(...formatRcPendingList([pending]).map((line) => ` ${line}`));
67883
+ }
67884
+ }
67885
+ }
67886
+ return lines;
67887
+ }
67888
+ function formatRcPendingList(pending) {
67889
+ if (pending.length === 0) {
67890
+ return ["no pending control requests observed"];
67891
+ }
67892
+ return pending.map((request7) => `${request7.sessionId} ${request7.requestId} ${request7.type}${request7.summary === "" ? "" : ` ${request7.summary}`} observed ${new Date(request7.observedAt).toISOString()}`);
67893
+ }
67519
67894
  function frontDoorRcControlFromState(fsPort, paths) {
67520
67895
  const state = readFrontDoorState(fsPort, paths.frontdoorStateFile);
67521
67896
  if (state?.port === void 0) {
@@ -67549,9 +67924,10 @@ function registerFrontDoorCommand(program2, deps) {
67549
67924
  }),
67550
67925
  ["agent-shim frontdoor status", "agent-shim frontdoor status --json"]
67551
67926
  );
67552
- const rc = withExamples(frontdoor.command("rc").description("Observe the Remote Control sessions passing through the front door, and send a prompt into one."), [
67553
- "agent-shim frontdoor rc list"
67554
- ]);
67927
+ const rc = withExamples(
67928
+ frontdoor.command("rc").description("Observe the Remote Control sessions passing through the front door, send a prompt into one, and answer its pending control requests."),
67929
+ ["agent-shim frontdoor rc list", "agent-shim frontdoor rc pending"]
67930
+ );
67555
67931
  withExamples(
67556
67932
  rc.command("list").description("List the Remote Control sessions the front door has observed, with when each was created and last seen. Read-only.").option("--json", "Print the sessions as JSON.").action(async (options) => {
67557
67933
  const sessions = await frontDoorRcControlFromState(realFarmFs, paths).listSessions();
@@ -67565,6 +67941,32 @@ function registerFrontDoorCommand(program2, deps) {
67565
67941
  }),
67566
67942
  ["agent-shim frontdoor rc list", "agent-shim frontdoor rc list --json"]
67567
67943
  );
67944
+ withExamples(
67945
+ rc.command("status").description("Report each observed Remote Control session's worker state and idle from its latest observed heartbeat and registration, with its pending control requests. Read-only.").argument("[session]", "One cse_ session id, as `frontdoor rc list` shows it; every observed session when omitted.").option("--json", "Print the statuses as JSON.").action(async (session, options) => {
67946
+ const statuses = await frontDoorRcControlFromState(realFarmFs, paths).statusOf(session);
67947
+ if (options.json === true) {
67948
+ printJson({ statuses });
67949
+ return;
67950
+ }
67951
+ for (const line of formatRcSessionStatus(statuses)) {
67952
+ console.log(line);
67953
+ }
67954
+ }),
67955
+ ["agent-shim frontdoor rc status", "agent-shim frontdoor rc status cse_00000000-0000-4000-8000-000000000000 --json"]
67956
+ );
67957
+ withExamples(
67958
+ rc.command("pending").description("List the control requests (tool and plan approvals above all) the front door has observed and that are still awaiting an answer. Read-only.").argument("[session]", "One cse_ session id, as `frontdoor rc list` shows it; every observed session when omitted.").option("--json", "Print the pending requests as JSON.").action(async (session, options) => {
67959
+ const pending = await frontDoorRcControlFromState(realFarmFs, paths).pendingOf(session);
67960
+ if (options.json === true) {
67961
+ printJson({ pending });
67962
+ return;
67963
+ }
67964
+ for (const line of formatRcPendingList(pending)) {
67965
+ console.log(line);
67966
+ }
67967
+ }),
67968
+ ["agent-shim frontdoor rc pending", "agent-shim frontdoor rc pending --json"]
67969
+ );
67568
67970
  withExamples(
67569
67971
  rc.command("send").description("Send a text prompt into one observed Remote Control session, arriving as a message from an attached client would.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").requiredOption("--text <text>", "The prompt text to deliver.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
67570
67972
  const result = await frontDoorRcControlFromState(realFarmFs, paths).sendPrompt(options.session, options.text);
@@ -67579,6 +67981,29 @@ function registerFrontDoorCommand(program2, deps) {
67579
67981
  }),
67580
67982
  ['agent-shim frontdoor rc send --session cse_00000000-0000-4000-8000-000000000000 --text "run the tests"']
67581
67983
  );
67984
+ withExamples(
67985
+ rc.command("answer").description("Answer one pending control request on an observed session, approving or denying it as an attached client's approval would.").requiredOption("--session <id>", "The cse_ session id, as `frontdoor rc list` shows it.").requiredOption("--request <id>", "The control request's id, as `frontdoor rc pending` shows it.").option("--approve", "Approve the request.").option("--deny", "Deny the request.").option("--text <text>", "The denial message the worker sees; applies to --deny only.").option("--json", "Print the delivery result as JSON.").action(async (options) => {
67986
+ if (options.approve === options.deny) {
67987
+ throw new UsageError("answer exactly one way: pass --approve or --deny, not both and not neither");
67988
+ }
67989
+ if (options.approve === true && options.text !== void 0) {
67990
+ throw new UsageError("--text is the denial message; an approval carries no text (the protocol's allow result has no message field)");
67991
+ }
67992
+ const result = await frontDoorRcControlFromState(realFarmFs, paths).answerRequest(options.session, options.request, { approve: options.approve === true, message: options.deny === true ? options.text : void 0 });
67993
+ if (!result.ok) {
67994
+ throw new Error(result.message);
67995
+ }
67996
+ if (options.json === true) {
67997
+ printJson({ session: options.session, request: options.request, sequenceNums: result.sequenceNums });
67998
+ return;
67999
+ }
68000
+ console.log(`${options.approve === true ? "approved" : "denied"} ${options.request} on ${options.session}: sequence_num ${result.sequenceNums.join(", ")}`);
68001
+ }),
68002
+ [
68003
+ "agent-shim frontdoor rc answer --session cse_00000000-0000-4000-8000-000000000000 --request req_00000000-0000-4000-8000-000000000000 --approve",
68004
+ 'agent-shim frontdoor rc answer --session cse_00000000-0000-4000-8000-000000000000 --request req_00000000-0000-4000-8000-000000000000 --deny --text "not today"'
68005
+ ]
68006
+ );
67582
68007
  program2.command("__frontdoor-supervisor", { hidden: true }).description("Internal: serve the front-door routing listeners. Started by the launcher; never run by hand.").allowUnknownOption().action(async () => {
67583
68008
  const idleShutdownMinutes = readGlobalConfig(paths)?.frontdoor?.idleShutdownMinutes ?? FRONTDOOR_DEFAULT_IDLE_SHUTDOWN_MINUTES;
67584
68009
  process.on("SIGTERM", () => {