agent-shim 8.2.0 → 8.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -692,6 +692,10 @@ interface LayoutPaths {
692
692
  readonly frontdoorStateFile: string;
693
693
  /** Path to the exclusive-create marker guarding "who spawns the front-door supervisor" so concurrent launches start at most one. */
694
694
  readonly frontdoorLockFile: string;
695
+ /**
696
+ * Path to the serving door's control token, mode 0600: the random bearer capability this generation's Remote Control control routes demand, rewritten on every door start. Only its owner can read it, which is what authenticates a CLI invocation to the door that minted it.
697
+ */
698
+ readonly frontdoorControlTokenFile: string;
695
699
  /** Directory holding one `<launcher-pid>.json` session-registry entry per live launch routed through the front door. */
696
700
  readonly frontdoorSessionsDir: string;
697
701
  /** Path to the front door's log: one line per lifecycle event and routed failure, never a token. */
@@ -1366,6 +1370,8 @@ interface CapturedRequestShape {
1366
1370
  /** The callbacks one piped request's forwarding drives, all optional callers except, so a fake or a partial observer fits the same seam. */
1367
1371
  interface PassthroughObserver {
1368
1372
  readonly onRequestChunk: (chunk: Buffer) => void;
1373
+ /** The request body ended, when the observer cares about the body as a whole (the Remote Control tracker's adapter); optional because the capture, which records chunk excerpts as they arrive, never needs it. */
1374
+ readonly onRequestEnd?: () => void;
1369
1375
  readonly onResponse: (status: number, headers: IncomingHttpHeaders) => void;
1370
1376
  readonly onResponseChunk: (chunk: Buffer) => void;
1371
1377
  readonly onEnd: () => void;
@@ -1594,8 +1600,314 @@ declare function realConnectCertStore(paths: {
1594
1600
  readonly frontdoorCaKeyFile: string;
1595
1601
  }): ConnectCertStore;
1596
1602
 
1603
+ /** What the identity step learned about one request from the launcher-injected headers. */
1604
+ interface SessionIdentity {
1605
+ /** The identity name the launch resolved, when it resolved one. */
1606
+ readonly identity: string | undefined;
1607
+ /** The per-launch session id, when the launcher injected one. */
1608
+ readonly sessionId: string | undefined;
1609
+ /** Whether this launch resolved headroom on, so its requests must pass through the headroom hop before their route. */
1610
+ readonly headroom: boolean;
1611
+ /** The project identity headroom scopes memory to, re-set on the headroom hop and sent nowhere else. */
1612
+ readonly projectId: string | undefined;
1613
+ }
1614
+ /** One request as a route sees it: the transport's own request and abort signal, plus what the identity step resolved. */
1615
+ interface RoutedRequest {
1616
+ readonly method: string;
1617
+ /** The request target exactly as received, path and query together. */
1618
+ readonly url: string;
1619
+ /** The request's headers with every internal header already stripped: exactly what a route may forward onwards. */
1620
+ readonly headers: IncomingHttpHeaders;
1621
+ /** The request's body stream: a route either pipes it onward untouched or reads it whole, never both. */
1622
+ readonly body: IncomingMessage;
1623
+ /** Aborted the moment the client goes away; a route aborts its own upstream work with it. */
1624
+ readonly signal: AbortSignal;
1625
+ readonly session: SessionIdentity;
1626
+ }
1627
+ /** The response half a route writes through, so the pipeline can run response middleware at the head and apply backpressure uniformly. */
1628
+ interface RoutedResponse {
1629
+ /** Whether the head has already been written. */
1630
+ readonly headersSent: boolean;
1631
+ /** Writes the response head, running every response observer first. Must be called exactly once, before any write. */
1632
+ readonly start: (status: number, headers: Readonly<Record<string, string>>) => void;
1633
+ /** Sends the head on its way before any body chunk exists: a streaming response's client should see its headers the moment they are known. */
1634
+ readonly flush: () => void;
1635
+ /**
1636
+ * Writes one body chunk, waiting for the socket to take it when the client is slow (backpressure: a slow client slows the route, and its upstream, instead of growing a buffer without bound). Resolves early, without writing, once the client is gone.
1637
+ */
1638
+ readonly write: (chunk: string | Uint8Array) => Promise<void>;
1639
+ /** Ends the response. */
1640
+ readonly end: () => void;
1641
+ /** Drops the connection: an upstream failure after the head went out, where no status can be sent any more. */
1642
+ readonly destroy: () => void;
1643
+ }
1644
+ /** One pluggable destination the front door routes a request to. */
1645
+ interface FrontDoorRoute {
1646
+ /** The route's name, for logs and for response middleware context. */
1647
+ readonly name: string;
1648
+ /**
1649
+ * Whether the headroom hop may sit in front of this route. Headroom needs the Anthropic-shaped request, so a protocol translator built on an Anthropic-compatible surface opts in, and a route whose request shape headroom could not understand opts out.
1650
+ */
1651
+ readonly headroomEligible: boolean;
1652
+ /**
1653
+ * The upstream headroom is told to use (its per-request base URL header) when the hop sits in front of this route. When defined it is always this door's direct listener, which is what lets the hop keep the route's credential in custody and the direct listener restore it; a route must never name any other address here. Undefined leaves headroom's own default upstream in charge, which is Claude Code's API.
1654
+ */
1655
+ readonly headroomUpstream: string | undefined;
1656
+ /**
1657
+ * Serves the request directly: headroom has already been applied, or does not apply to this route. Writes the response through `response` and resolves once the response has ended.
1658
+ */
1659
+ readonly serve: (request: RoutedRequest, response: RoutedResponse) => Promise<void>;
1660
+ }
1661
+
1662
+ /**
1663
+ * Remote Control session tracking and the client-half writes the front door performs: the record of the `cse_` sessions whose traffic crosses its terminated API-host session, the prompt injected into one, and the answer delivered to one of its pending control requests.
1664
+ *
1665
+ * The tracker is observational: the door already terminates the API host's TLS, so it sees the CLI's own `POST /v1/code/sessions` (whose answer carries the new session's `cse_` id) and every recurring `/v1/code/sessions/...` call (heartbeat, presence), each carrying the session's Authorization bearer and the protocol headers a client-half replay needs. Per session the tracker retains the latest of each, because those calls recur and the freshest value is the live one. It also reads the request bodies of the exchanges whose payloads matter: the worker's event batches (whose `control_request` payloads, `can_use_tool` above all, are the approvals awaiting an answer), the worker heartbeats (whose bodies carry `idle_seconds`), the worker registration (whose body carries the worker state), and the client-half event writes (whose `control_response` payloads complete a pending request). Everything is held in this process's memory only: nothing here is written to a log, to the capture, or to disk, and the summary the control surface lists never includes the credential. The capture's existing redaction is untouched; a capture and this tracker observe the same exchanges through entirely separate paths.
1666
+ *
1667
+ * Both writes replay the protocol's documented client half rather than splicing frames into any relayed stream: `POST /v1/code/sessions/{id}/events` with one payload event (a user message, or a `control_response` answering an observed request by echoing its id), authenticated by the observed bearer and the observed protocol headers, sent from the door over its own interception-proof dials. Sequence numbers therefore come from the real service and the event is mirrored to every attached client, exactly as a message typed into claude.ai would be.
1668
+ */
1669
+ /** Every Remote Control exchange on the API host sits under this path prefix. */
1670
+ declare const RC_SESSIONS_PATH_PREFIX = "/v1/code/sessions";
1671
+ /**
1672
+ * How long an entry survives without any observed exchange for its session. Derived from the protocol's own liveness, not chosen: the worker heartbeats every 20 s (the `current_interval_seconds` our own captured heartbeat body carries), so a live session produces observable traffic at least that often, and the transport's documented liveness bound is the 45 s stream timeout, which outlasts two missed heartbeat intervals (40 s) precisely so a momentarily quiet session is not dropped. An entry idle past 45 s therefore names a session the protocol itself has given up on, which makes 45 s the exact bound where retaining it stops describing anything live.
1673
+ */
1674
+ declare const RC_IDLE_EXPIRY_MS = 45000;
1675
+ /**
1676
+ * How long a control request stays answerable once observed. The protocol's own permission round-trip is documented at roughly 10 to 14 seconds end to end, so the ceiling of that window is the exact bound after which the worker has already given up waiting: answering later would write a `control_response` nothing is waiting for. A request exactly at the bound is already too late, which is why expiry is `>=` and not `>`.
1677
+ */
1678
+ declare const RC_PENDING_DEADLINE_MS = 14000;
1679
+ /**
1680
+ * How much of one request body the tracker ever holds while parsing it. The protocol's own batch cap is 10 MiB, but that bound exists to admit bulk transcript events the tracker has no use for; what it reads out of a body (the control requests, the heartbeat's idle, the worker's state) is one control exchange's worth of JSON, the same notion of "a full exchange's body" the capture's per-exchange budget (`STREAM_LOG_CAP_BYTES`, 256 KiB) was sized for, so one budget serves both here. A body past it is skipped, never failed: the exchange flows on untouched and simply records nothing.
1681
+ */
1682
+ declare const RC_REQUEST_PARSE_CAP_BYTES = 262144;
1683
+ /** One pending control request as the control surface lists it: identification, type, summary and timing, never any credential. */
1684
+ interface RcPendingRequestSummary {
1685
+ readonly sessionId: string;
1686
+ /** The id a matching `control_response` must echo: the payload's `request_id`, the field the protocol's trust rule names. */
1687
+ readonly requestId: string;
1688
+ /** The request's subtype (`can_use_tool` and kin), or the payload discriminator when the subtype is absent. */
1689
+ readonly type: string;
1690
+ /** A short human-readable sketch: the tool name and a bounded excerpt of its input for `can_use_tool`. */
1691
+ readonly summary: string;
1692
+ /** When the request was observed, in epoch milliseconds from the injected clock. */
1693
+ readonly observedAt: number;
1694
+ }
1695
+ /** One observed fact about the worker, with the instant of the exchange that carried it. */
1696
+ interface RcWorkerFact<T> {
1697
+ readonly value: T;
1698
+ readonly observedAt: number;
1699
+ }
1700
+ /** One observed session as the status surface reports it: the list summary plus what the worker's own exchanges carried and the requests awaiting an answer, never the credential. */
1701
+ interface RcSessionStatus extends RcSessionSummary {
1702
+ /** The worker state the latest worker registration (`PUT /{id}/worker`) named, when one was observed. */
1703
+ readonly workerState: RcWorkerFact<string> | undefined;
1704
+ /** The idle the latest heartbeat carried, when one was observed. */
1705
+ readonly workerIdleSeconds: RcWorkerFact<number> | undefined;
1706
+ /** The session's control requests awaiting an answer, oldest-observed first. */
1707
+ readonly pending: readonly RcPendingRequestSummary[];
1708
+ }
1709
+ /** One observed session as the control surface lists it: the identification and timing only, never the credential. */
1710
+ interface RcSessionSummary {
1711
+ readonly id: string;
1712
+ /** When the session's create exchange completed, in epoch milliseconds from the injected clock. */
1713
+ readonly createdAt: number;
1714
+ /** When traffic for this session was last observed, in epoch milliseconds from the injected clock. */
1715
+ readonly lastSeenAt: number;
1716
+ }
1717
+ /** The request facts one observed exchange arrives with. */
1718
+ interface RcObservedRequest {
1719
+ readonly method?: string;
1720
+ readonly url?: string;
1721
+ readonly headers: Readonly<IncomingHttpHeaders>;
1722
+ }
1723
+ /** What one observed exchange drives, in arrival order: the request body chunks and their end (present only for the exchanges whose request payloads the tracker reads), then the response's head, each body chunk, and the end exactly once. */
1724
+ interface RcExchangeObserver {
1725
+ /** Feeds one request body chunk, when this exchange's request body carries something the tracker reads; absent otherwise, so an adapter never attaches a body listener for the exchanges that do not. */
1726
+ readonly onRequestChunk?: (chunk: Uint8Array) => void;
1727
+ /** The request body ended: the buffered body is parsed here, and its facts filed once the response is known to be a success. */
1728
+ readonly onRequestEnd?: () => void;
1729
+ readonly onResponse: (status: number, headers: Readonly<IncomingHttpHeaders>) => void;
1730
+ readonly onBodyChunk: (chunk: Uint8Array) => void;
1731
+ readonly onEnd: () => void;
1732
+ }
1733
+ /** Everything the tracker needs, injected so its decisions and lifecycle run against fakes in unit tests. */
1734
+ interface RcSessionTrackerDeps {
1735
+ readonly now: () => number;
1736
+ /** How long an entry may sit unobserved before it is expired; production passes `RC_IDLE_EXPIRY_MS`, tests pass small values so a deadline is reached in milliseconds. */
1737
+ readonly idleMs: number;
1738
+ }
1739
+ /** The live record of observed Remote Control sessions. */
1740
+ interface RcSessionTracker {
1741
+ /** Feeds one request/response exchange in: returns the observer its request and response drive when the request belongs to the Remote Control surface, or undefined when it does not, in which case nothing is retained and neither side needs observing. */
1742
+ readonly observeExchange: (request: RcObservedRequest) => RcExchangeObserver | undefined;
1743
+ /** Every live entry, idle ones expired first, sorted oldest-created first. */
1744
+ readonly list: () => readonly RcSessionSummary[];
1745
+ /** Every live entry as the status surface reports it, with the same sweeping and order as `list`, filtered to one session when named. */
1746
+ readonly statusOf: (sessionId?: string) => readonly RcSessionStatus[];
1747
+ /** Every control request awaiting an answer, deadline-expired ones dropped first, oldest-observed first, filtered to one session when named. */
1748
+ readonly pendingOf: (sessionId?: string) => readonly RcPendingRequestSummary[];
1749
+ /** The observed credential and protocol headers for one session, the client-half writes' accessor: present only while the session is tracked, and never surfaced anywhere a summary, log or capture goes. */
1750
+ readonly credentialOf: (sessionId: string) => RcObservedCredential | undefined;
1751
+ /** Retires one pending control request as answered: what a successful answer write calls, so the request stops being listed the moment it is answered. */
1752
+ readonly completePending: (sessionId: string, requestId: string) => void;
1753
+ /** Stops the idle sweep timer. The entries themselves are unreachable the moment the door drops them. */
1754
+ readonly close: () => void;
1755
+ }
1756
+ /** Creates the tracker. One per door process; everything it holds dies with it. */
1757
+ declare function createRcSessionTracker(deps: RcSessionTrackerDeps): RcSessionTracker;
1758
+ /**
1759
+ * Wraps one route so the tracker sees every exchange the route serves: the request's facts are handed over first, and when they are Remote Control facts the request's body chunks and the response the route writes are teed to the tracker's observer while flowing on unchanged. Used on the door's route resolution, so the bare `/v1/` pass-through an OAuth session's Remote Control calls ride is where observation happens.
1760
+ */
1761
+ declare function observingRoutedRoute(route: FrontDoorRoute, tracker: RcSessionTracker): FrontDoorRoute;
1762
+ /** Adapts the tracker's exchange observer to the passthrough forwarding's observer shape, so the connect test world (which forwards rather than routes) observes through the same tracker. */
1763
+ declare function rcObserverAsPassthrough(observer: RcExchangeObserver): PassthroughObserver;
1764
+ /**
1765
+ * Builds the user-message payload event for one injected prompt: the Agent SDK stream-json `user` message shape (`type`, `uuid`, `session_id`, `parent_tool_use_id`, `message` with a `user` role), the same envelope a claude.ai client sends as a turn. The content is the plain string form, which is the shape validated end to end against the real API by the third-party client survey that settled this protocol.
1766
+ */
1767
+ declare function buildRcUserMessagePayload(uuid: string, sessionId: string, text: string): Record<string, unknown>;
1768
+ /** Wraps payload event(s) in the write body the endpoint takes: `{"events": [{"payload": {...}}]}`. */
1769
+ declare function buildRcEventWriteBody(payload: Record<string, unknown>): Record<string, unknown>;
1770
+ /** The headers a client-half write sends: the observed credential and protocol values, replayed verbatim, never invented. */
1771
+ interface RcWriteHeaders {
1772
+ readonly authorization: string | undefined;
1773
+ readonly anthropicVersion: string | undefined;
1774
+ readonly anthropicClientPlatform: string | undefined;
1775
+ }
1776
+ /** One answer from the API host the write path dials: the status and the whole body. This dial only ever reads small JSON answers, never a stream, so the body needs no cap of its own. */
1777
+ interface RcDialAnswer {
1778
+ readonly status: number;
1779
+ readonly body: string;
1780
+ }
1781
+ /** The one network effect a client-half write performs, injected so the pure logic runs against fakes: POST the write body to the session's events endpoint. Production dials the real API host over the door's interception-proof agent; tests redirect to a local stand-in. */
1782
+ interface RcEventDial {
1783
+ readonly writeEvents: (sessionId: string, headers: Readonly<Record<string, string>>, body: string) => Promise<RcDialAnswer>;
1784
+ }
1785
+ /** What one client-half write produced: the sequence numbers the real service assigned, or a verbose failure message ready to show a user. */
1786
+ type RcEventWriteResult = {
1787
+ readonly ok: true;
1788
+ readonly sequenceNums: readonly number[];
1789
+ } | {
1790
+ readonly ok: false;
1791
+ readonly message: string;
1792
+ };
1793
+ /** Maps one dial answer to the write result: an accepted write yields its sequence numbers; anything else yields a verbose message, with the 401 case naming the stale observed bearer as the cause. */
1794
+ declare function rcEventWriteResultFromAnswer(answer: RcDialAnswer): RcEventWriteResult;
1795
+ /** The credential and protocol headers the tracker holds for one session, the client-half writes' accessor: present only while the session is tracked, and never surfaced anywhere a summary, log or capture goes. */
1796
+ type RcObservedCredential = RcWriteHeaders;
1797
+ /** Everything one injection needs, injected so the decision logic runs against fakes in unit tests. */
1798
+ interface RcInjectDeps {
1799
+ /** Reads the observed credential for a session: the tracker's accessor, in production. */
1800
+ readonly credentialOf: (sessionId: string) => RcObservedCredential | undefined;
1801
+ /** The dial to the real API host. */
1802
+ readonly dial: RcEventDial;
1803
+ /** Mints the payload event's uuid: a v4 UUID or better in production. */
1804
+ readonly newUuid: () => string;
1805
+ }
1806
+ /**
1807
+ * Injects one user-message prompt into an observed session: the documented client-half write, `POST /v1/code/sessions/{id}/events`, authenticated by the observed bearer and carrying the observed protocol headers. The result is the sequence numbers the real service assigned, or a verbose failure. A dial that cannot reach the API host is reported rather than thrown, so a caller with no exception handling still shows the user exactly what failed.
1808
+ */
1809
+ declare function injectRcUserMessage(deps: RcInjectDeps, sessionId: string, text: string): Promise<RcEventWriteResult>;
1810
+ /** The decision an answer carries: approve or deny, with the message a denial shows the worker (the protocol's allow result has no text field, so an approval carries none). */
1811
+ interface RcAnswerDecision {
1812
+ readonly approve: boolean;
1813
+ /** The denial message; undefined means the protocol's empty default. Present only with a denial. */
1814
+ readonly message: string | undefined;
1815
+ }
1816
+ /**
1817
+ * Builds the `control_response` payload event for one answer: the SDK's response envelope, `subtype: "success"` with the request's id echoed in `request_id`, carrying the permission result the worker is waiting for (`behavior: "allow"`, or `"deny"` with its message).
1818
+ */
1819
+ declare function buildRcControlResponsePayload(requestId: string, decision: RcAnswerDecision): Record<string, unknown>;
1820
+ /** Everything one answer needs, injected so the decision logic runs against fakes in unit tests. */
1821
+ interface RcAnswerDeps {
1822
+ /** Reads the observed credential for a session: the tracker's accessor, in production. */
1823
+ readonly credentialOf: (sessionId: string) => RcObservedCredential | undefined;
1824
+ /** Reads the session's pending control requests: the tracker's accessor, in production, so an answer is refused unless the request is still waiting. */
1825
+ readonly pendingOf: (sessionId: string) => readonly RcPendingRequestSummary[];
1826
+ /** Retires a pending request as answered: the tracker's accessor, in production, called only once the write is confirmed delivered. */
1827
+ readonly completePending: (sessionId: string, requestId: string) => void;
1828
+ /** The dial to the real API host. */
1829
+ readonly dial: RcEventDial;
1830
+ }
1831
+ /**
1832
+ * Answers one pending control request on an observed session: the same documented client-half write the inject path performs, carrying a `control_response` that echoes the request's id, authenticated by the observed bearer and protocol headers. The request must still be pending (an answered or deadline-expired request is refused verbosely, since writing a response nothing waits for would mislead the caller), and a successful write retires it. A dial that cannot reach the API host is reported rather than thrown.
1833
+ */
1834
+ declare function answerRcControlRequest(deps: RcAnswerDeps, sessionId: string, requestId: string, decision: RcAnswerDecision): Promise<RcEventWriteResult>;
1835
+
1836
+ /** Where the Remote Control inject path dials: the API host's own address by default, over TLS with the door's interception-proof agent (real-name DNS resolution and the pf-exempt source-port range), so an injected event cannot be looped back into the door's own transparent surface. A test redirects the dial at a local stand-in presenting the host's name, exactly as the forwarding targets are redirected. */
1837
+ interface RcDialTarget {
1838
+ readonly host: string;
1839
+ readonly port: number;
1840
+ /** The SNI name to present; defaults to `host`. */
1841
+ readonly servername?: string;
1842
+ /** Extra trust anchors for a stand-in's certificate, when it is not system-trusted. */
1843
+ readonly ca?: readonly string[];
1844
+ /** Overrides certificate verification; production leaves it at the Node default. */
1845
+ readonly rejectUnauthorized?: boolean;
1846
+ }
1847
+ /**
1848
+ * The real `RcEventDial`: one keep-alive TLS agent of the door's own exempt kind, so every injected event rides the same interception-proof dials the door's forwarded traffic does. The dial only ever reads small JSON answers (an event write's result, or an error), never a stream.
1849
+ */
1850
+ declare function realRcEventDial(target?: RcDialTarget): RcEventDial;
1597
1851
  declare function realConnectEffects(): ConnectEffects;
1598
1852
 
1853
+ /**
1854
+ * The Remote Control control surface: the small HTTP namespace the serving door answers on its provider listener, next to `/healthz`, through which `agent-shim frontdoor rc` lists the observed sessions, reports each one's status and pending control requests, injects a prompt into one, and answers one of its pending requests.
1855
+ *
1856
+ * This rides the listener `frontdoor status` already knows the door by (its address comes from the same state file, its trust anchor from the same CA path) rather than any second channel. The routes sit under one prefix the routed pipeline never serves, and every request must present the serving generation's control token as a Bearer credential: a fresh random value per door start, written owner-only under the front door's state directory so only this user's CLI can read it, and checked in constant time like every other capability the door accepts. An inject or answer request is carried out by the door itself over its interception-proof dials, which is the whole point: the CLI never dials the API host directly, so nothing about the write depends on the caller's own network path.
1857
+ */
1858
+ /** The one path prefix every Remote Control control route sits under, answered before the routed pipeline like `/healthz` is. */
1859
+ declare const CONTROL_PATH_PREFIX = "/__agent-shim/rc";
1860
+ /** Everything the control handler needs, injected so it serves against fakes in unit tests. */
1861
+ interface RcControlHandlerDeps {
1862
+ /** This generation's control token: the value the door wrote owner-only for its CLI to present. */
1863
+ readonly expectedToken: string;
1864
+ /** The observed sessions as the tracker lists them. */
1865
+ readonly list: () => readonly RcSessionSummary[];
1866
+ /** The observed sessions as the tracker reports their status, filtered to one when named. */
1867
+ readonly statusOf: (sessionId?: string) => readonly RcSessionStatus[];
1868
+ /** The control requests awaiting an answer, filtered to one session when named. */
1869
+ readonly pendingOf: (sessionId?: string) => readonly RcPendingRequestSummary[];
1870
+ /** The inject operation, already wired to the tracker and the door's API-host dial. */
1871
+ readonly inject: (sessionId: string, text: string) => Promise<RcEventWriteResult>;
1872
+ /** The answer operation, already wired to the tracker and the door's API-host dial. */
1873
+ readonly answer: (sessionId: string, requestId: string, decision: RcAnswerDecision) => Promise<RcEventWriteResult>;
1874
+ }
1875
+ /**
1876
+ * Builds the control handler the provider listener serves under `CONTROL_PATH_PREFIX`. Every route demands the generation's control token first, so nothing about the sessions (their ids, their timing, and most of all the prompts and approvals carried into them) is reachable by a process that did not read the owner-only token file.
1877
+ */
1878
+ declare function createRcControlHandler(deps: RcControlHandlerDeps): (request: IncomingMessage, response: ServerResponse) => void;
1879
+ /** One answer from the door's control listener. */
1880
+ interface RcControlAnswer {
1881
+ readonly status: number;
1882
+ readonly body: string;
1883
+ }
1884
+ /** The one transport the control client needs, injected so the client runs against a local plain-HTTP server in tests and the door's real TLS listener in production. */
1885
+ interface RcControlTransport {
1886
+ readonly request: (options: {
1887
+ readonly method: "GET" | "POST";
1888
+ readonly path: string;
1889
+ readonly headers: Readonly<Record<string, string>>;
1890
+ readonly body?: string;
1891
+ }) => Promise<RcControlAnswer>;
1892
+ }
1893
+ /** The real transport: HTTPS to the door's provider listener on loopback, trusting only the CA file the door's own state names, so a process merely holding the port cannot answer as the door. */
1894
+ declare function realRcControlTransport(port: number, ca: string): RcControlTransport;
1895
+ /** The door-facing client the `frontdoor rc` verbs use: the session list, the status and pending reads, and the inject and answer operations, over the injected transport. */
1896
+ interface FrontDoorRcControl {
1897
+ /** The observed sessions, oldest-created first. Throws with the door's verbose reason when the list cannot be had. */
1898
+ readonly listSessions: () => Promise<readonly RcSessionSummary[]>;
1899
+ /** The observed sessions' status, oldest-created first and filtered to one when named. Throws with the door's verbose reason when the read cannot be had, including a named session the door has not observed. */
1900
+ readonly statusOf: (sessionId?: string) => Promise<readonly RcSessionStatus[]>;
1901
+ /** The control requests awaiting an answer, oldest-observed first and filtered to one session when named. Throws with the door's verbose reason when the read cannot be had. */
1902
+ readonly pendingOf: (sessionId?: string) => Promise<readonly RcPendingRequestSummary[]>;
1903
+ /** Injects one prompt, returning the sequence numbers the real service assigned or the verbose failure. */
1904
+ readonly sendPrompt: (sessionId: string, text: string) => Promise<RcEventWriteResult>;
1905
+ /** Answers one pending control request, returning the sequence numbers the real service assigned or the verbose failure. */
1906
+ readonly answerRequest: (sessionId: string, requestId: string, decision: RcAnswerDecision) => Promise<RcEventWriteResult>;
1907
+ }
1908
+ /** Builds the control client: every request presents the control token, and a non-2xx answer becomes the verbose message the door sent with it. */
1909
+ declare function frontDoorRcControl(transport: RcControlTransport, token: string): FrontDoorRcControl;
1910
+
1599
1911
  /** The filesystem operations headroom coordination needs: the same shape as `FarmFs`, injected so every decision over state, sessions, and the start lock runs against an in-memory fake in tests. */
1600
1912
  type HeadroomFs = Pick<FarmFs, "mkdirp" | "mkdirPrivate" | "readFileUtf8" | "writeFileUtf8" | "writeFilePrivate" | "writeFileExclusive" | "readdir" | "removeRecursive">;
1601
1913
 
@@ -2813,5 +3125,5 @@ interface PrepareClaudeLaunchOptions {
2813
3125
  */
2814
3126
  declare function prepareClaudeLaunch(options: PrepareClaudeLaunchOptions): LaunchPlan;
2815
3127
 
2816
- export { AMBIENT_CREDENTIAL_VARS, CONNECT_INTERCEPT_HOST, CONNECT_INTERCEPT_HOSTS, CONNECT_LIMITS, CategoryClassificationOverlaySchema, CategoryClassificationSchema, CategoryMapSchema, CliError, ConfigProfileSchema, CredentialCacheSchema, CredentialSchema, CredentialSourceSchema, DirectoryRuleAlreadyExistsError, DirectoryRuleMissingTargetError, DirectoryRuleNotFoundError, DirectoryRuleSchema, DirectoryRulesSchema, EXIT_FAILURE, EXIT_USAGE, EntryValueSchema, FARM_MANIFEST_FILENAME, FrontDoorStartError, GlobalConfigSchema, HeadroomStartError, IdentityAlreadyExistsError, IdentityNotFoundError, IdentitySchema, InvalidCategoryNameError, InvalidIdentityNameError, InvalidProviderNameError, LaunchRefusedError, LegacyProviderFileError, PoolNameSchema, PoolNotFoundError, PoolSchema, PortableConfigSchema, ProfileAlreadyExistsError, ProfileNotFoundError, ProviderAlreadyExistsError, ProviderKindMismatchError, ProviderNotFoundError, ProviderSchema, ROUTED_PATH_PREFIX, UsageError, UsageSnapshotError, UsageSnapshotSchema, WhenSchema, addDirectoryRule, addIdentity, addPool, addProvider, buildEntryFacts, buildLayoutPaths, carryOver, checkReportHasWarnings, checkReportToJson, collectCheckReport, collectDoctorReport, createLeafCache, createProfile, describeProviderEndpoint, detectAmbientCredential, ensureCa, ensureFrontDoor, ensureHeadroom, evaluateAmbientCredentialGuard, formatAmbientCredentialGuardMessage, formatCheckReport, formatDoctorReport, forwardableHeaders, generateCa, identityExists, isIdentityDirectoryName, isInterceptedHost, listDirectoryRules, listIdentities, listProfiles, listProviders, listUsageSnapshots, mintLeaf, parseConnectTarget, prepareClaudeLaunch, prepareLaunch, profileExists, providerExists, readActiveIdentity, readDirectoryRules, readFarmManifest, readGlobalConfig, readIdentity, readPools, readProfile, readProvider, readUsageSnapshot, realConnectCertStore, realConnectEffects, recoverFarm, recoveryDiagnostics, removeDirectoryRule, removeIdentity, removePool, removeProfile, removeProvider, requirePool, resolveAgentShimHome, resolveClaudeHome, resolveDecisions, resolveLayoutPaths, resolveSupervisorConfig, resyncFarm, runCheck, runDoctor, runFrontDoorSupervisor, runSupervisor, servedByPipeline, setAllowAmbientCredential, setDefaultConfigProfile, setGlobalDefaultProfile, setIdentityCredential, setPool, setProfileCategories, setProfileEntries, setProfileLaunchFlags, setProfileMetadata, snapshotPath, startConnectServer, topLevelNames, updateDirectoryRule, updateProvider, useIdentity, writeDirectoryRules };
2817
- export type { AddDirectoryRuleOptions, AddProviderInput, AmbientCredentialDetection, AmbientCredentialGuardResult, AmbientCredentialVar, BuildEntryFactsParams, CaMaterial, CarryOverParams, CarryOverResult, CategoryMap, CheckReport, CollectCheckReportParams, CollectDoctorReportParams, CompiledRule, ConfigProfile, ConnectCertStore, ConnectEffects, ConnectLimits, ConnectServerConfig, ConnectServerHandle, ConnectTarget, Credential, CredentialSource, Decision, Diagnostic, DirectoryRule, DirectoryRules, DoctorReport, EliminatedRule, EnsureFrontDoorPorts, EnsureHeadroomPorts, EntryFact, EntryFacts, EntryValue, EvaluateAmbientCredentialGuardParams, FlattenedCascade, FrontDoorSupervisorPorts, GlobalConfig, HeadroomSupervisorConfig, Identity, IdentityCredentialChange, IdentityListEntry, IdentityListing, InjectedCredential, LaunchPlan, Layer, LayerId, LayoutPaths, LeafCert, Pool, PortableConfig, PrepareClaudeLaunchOptions, PrepareLaunchParams, ProfileListEntry, Provider, ProviderListEntry, RecoverFarmParams, RecoveryResult, ResolveDecisionsInput, ResolvedState, ResyncFarmParams, ResyncFarmResult, RunCheckParams, RunDoctorParams, RunFrontDoorSupervisorOptions, RunSupervisorOptions, SupervisorPorts, UnreadableIdentityListEntry, UpdateDirectoryRuleOptions, UpdateProviderInput, UsageReadFs, UsageSnapshot, WhenCondition };
3128
+ export { AMBIENT_CREDENTIAL_VARS, CONNECT_INTERCEPT_HOST, CONNECT_INTERCEPT_HOSTS, CONNECT_LIMITS, CONTROL_PATH_PREFIX, CategoryClassificationOverlaySchema, CategoryClassificationSchema, CategoryMapSchema, CliError, ConfigProfileSchema, CredentialCacheSchema, CredentialSchema, CredentialSourceSchema, DirectoryRuleAlreadyExistsError, DirectoryRuleMissingTargetError, DirectoryRuleNotFoundError, DirectoryRuleSchema, DirectoryRulesSchema, EXIT_FAILURE, EXIT_USAGE, EntryValueSchema, FARM_MANIFEST_FILENAME, FrontDoorStartError, GlobalConfigSchema, HeadroomStartError, IdentityAlreadyExistsError, IdentityNotFoundError, IdentitySchema, InvalidCategoryNameError, InvalidIdentityNameError, InvalidProviderNameError, LaunchRefusedError, LegacyProviderFileError, PoolNameSchema, PoolNotFoundError, PoolSchema, PortableConfigSchema, ProfileAlreadyExistsError, ProfileNotFoundError, ProviderAlreadyExistsError, ProviderKindMismatchError, ProviderNotFoundError, ProviderSchema, RC_IDLE_EXPIRY_MS, RC_PENDING_DEADLINE_MS, RC_REQUEST_PARSE_CAP_BYTES, RC_SESSIONS_PATH_PREFIX, ROUTED_PATH_PREFIX, UsageError, UsageSnapshotError, UsageSnapshotSchema, WhenSchema, addDirectoryRule, addIdentity, addPool, addProvider, answerRcControlRequest, buildEntryFacts, buildLayoutPaths, buildRcControlResponsePayload, buildRcEventWriteBody, buildRcUserMessagePayload, carryOver, checkReportHasWarnings, checkReportToJson, collectCheckReport, collectDoctorReport, createLeafCache, createProfile, createRcControlHandler, createRcSessionTracker, describeProviderEndpoint, detectAmbientCredential, ensureCa, ensureFrontDoor, ensureHeadroom, evaluateAmbientCredentialGuard, formatAmbientCredentialGuardMessage, formatCheckReport, formatDoctorReport, forwardableHeaders, frontDoorRcControl, generateCa, identityExists, injectRcUserMessage, isIdentityDirectoryName, isInterceptedHost, listDirectoryRules, listIdentities, listProfiles, listProviders, listUsageSnapshots, mintLeaf, observingRoutedRoute, parseConnectTarget, prepareClaudeLaunch, prepareLaunch, profileExists, providerExists, rcEventWriteResultFromAnswer, rcObserverAsPassthrough, readActiveIdentity, readDirectoryRules, readFarmManifest, readGlobalConfig, readIdentity, readPools, readProfile, readProvider, readUsageSnapshot, realConnectCertStore, realConnectEffects, realRcControlTransport, realRcEventDial, recoverFarm, recoveryDiagnostics, removeDirectoryRule, removeIdentity, removePool, removeProfile, removeProvider, requirePool, resolveAgentShimHome, resolveClaudeHome, resolveDecisions, resolveLayoutPaths, resolveSupervisorConfig, resyncFarm, runCheck, runDoctor, runFrontDoorSupervisor, runSupervisor, servedByPipeline, setAllowAmbientCredential, setDefaultConfigProfile, setGlobalDefaultProfile, setIdentityCredential, setPool, setProfileCategories, setProfileEntries, setProfileLaunchFlags, setProfileMetadata, snapshotPath, startConnectServer, topLevelNames, updateDirectoryRule, updateProvider, useIdentity, writeDirectoryRules };
3129
+ export type { AddDirectoryRuleOptions, AddProviderInput, AmbientCredentialDetection, AmbientCredentialGuardResult, AmbientCredentialVar, BuildEntryFactsParams, CaMaterial, CarryOverParams, CarryOverResult, CategoryMap, CheckReport, CollectCheckReportParams, CollectDoctorReportParams, CompiledRule, ConfigProfile, ConnectCertStore, ConnectEffects, ConnectLimits, ConnectServerConfig, ConnectServerHandle, ConnectTarget, Credential, CredentialSource, Decision, Diagnostic, DirectoryRule, DirectoryRules, DoctorReport, EliminatedRule, EnsureFrontDoorPorts, EnsureHeadroomPorts, EntryFact, EntryFacts, EntryValue, EvaluateAmbientCredentialGuardParams, FlattenedCascade, FrontDoorRcControl, FrontDoorSupervisorPorts, GlobalConfig, HeadroomSupervisorConfig, Identity, IdentityCredentialChange, IdentityListEntry, IdentityListing, InjectedCredential, LaunchPlan, Layer, LayerId, LayoutPaths, LeafCert, Pool, PortableConfig, PrepareClaudeLaunchOptions, PrepareLaunchParams, ProfileListEntry, Provider, ProviderListEntry, RcAnswerDecision, RcAnswerDeps, RcControlAnswer, RcControlHandlerDeps, RcControlTransport, RcDialAnswer, RcDialTarget, RcEventDial, RcEventWriteResult, RcExchangeObserver, RcInjectDeps, RcObservedCredential, RcObservedRequest, RcPendingRequestSummary, RcSessionStatus, RcSessionSummary, RcSessionTracker, RcSessionTrackerDeps, RcWorkerFact, RcWriteHeaders, RecoverFarmParams, RecoveryResult, ResolveDecisionsInput, ResolvedState, ResyncFarmParams, ResyncFarmResult, RunCheckParams, RunDoctorParams, RunFrontDoorSupervisorOptions, RunSupervisorOptions, SupervisorPorts, UnreadableIdentityListEntry, UpdateDirectoryRuleOptions, UpdateProviderInput, UsageReadFs, UsageSnapshot, WhenCondition };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-shim",
3
- "version": "8.2.0",
3
+ "version": "8.4.0",
4
4
  "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.",
5
5
  "license": "MIT",
6
6
  "author": "Joseph Mearman <joseph@mearman.co.uk>",