agent-shim 7.13.1 → 7.14.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 +95 -27
- package/dist/index.cjs +70 -7
- package/dist/index.mjs +70 -7
- package/dist/types/index.d.ts +2789 -40
- package/package.json +3 -1
- package/dist/types/checkReport.d.ts +0 -154
- package/dist/types/claudeShim.d.ts +0 -114
- package/dist/types/cli/bool.d.ts +0 -8
- package/dist/types/cli/commandDeps.d.ts +0 -39
- package/dist/types/cli/envBool.d.ts +0 -11
- package/dist/types/cli/pairs.d.ts +0 -19
- package/dist/types/cliError.d.ts +0 -27
- package/dist/types/codex/http.d.ts +0 -15
- package/dist/types/config/classify.d.ts +0 -49
- package/dist/types/config/load.d.ts +0 -42
- package/dist/types/config/schema.d.ts +0 -746
- package/dist/types/config/store.d.ts +0 -46
- package/dist/types/configProfilesStore.d.ts +0 -61
- package/dist/types/configure.d.ts +0 -170
- package/dist/types/credential.d.ts +0 -136
- package/dist/types/credentialCache.d.ts +0 -46
- package/dist/types/directoryRulesStore.d.ts +0 -43
- package/dist/types/doctorReport.d.ts +0 -117
- package/dist/types/frontdoor/capability.d.ts +0 -19
- package/dist/types/frontdoor/capture.d.ts +0 -58
- package/dist/types/frontdoor/connect.d.ts +0 -209
- package/dist/types/frontdoor/connectEffects.d.ts +0 -3
- package/dist/types/frontdoor/custody.d.ts +0 -32
- package/dist/types/frontdoor/ensure.d.ts +0 -36
- package/dist/types/frontdoor/headroomHop.d.ts +0 -22
- package/dist/types/frontdoor/pipeline.d.ts +0 -109
- package/dist/types/frontdoor/probe.d.ts +0 -13
- package/dist/types/frontdoor/realFrontDoorPort.d.ts +0 -5
- package/dist/types/frontdoor/route.d.ts +0 -112
- package/dist/types/frontdoor/server.d.ts +0 -44
- package/dist/types/frontdoor/state.d.ts +0 -57
- package/dist/types/frontdoor/supervisor.d.ts +0 -48
- package/dist/types/frontdoor/tap.d.ts +0 -9
- package/dist/types/frontdoor/trust.d.ts +0 -30
- package/dist/types/frontdoor/x509.d.ts +0 -11
- package/dist/types/headroom/ensure.d.ts +0 -31
- package/dist/types/headroom/headers.d.ts +0 -11
- package/dist/types/headroom/settings.d.ts +0 -13
- package/dist/types/headroom/source.d.ts +0 -8
- package/dist/types/headroom/state.d.ts +0 -77
- package/dist/types/headroom/supervisor.d.ts +0 -107
- package/dist/types/identityStore.d.ts +0 -85
- package/dist/types/launchWiring.d.ts +0 -49
- package/dist/types/launcher/argv.d.ts +0 -42
- package/dist/types/launcher/cascade.d.ts +0 -42
- package/dist/types/launcher/cliOverride.d.ts +0 -38
- package/dist/types/launcher/extraFlags.d.ts +0 -6
- package/dist/types/launcher/farm.d.ts +0 -168
- package/dist/types/launcher/flags.d.ts +0 -96
- package/dist/types/launcher/guard.d.ts +0 -57
- package/dist/types/launcher/identity.d.ts +0 -95
- package/dist/types/launcher/lock.d.ts +0 -41
- package/dist/types/launcher/pool.d.ts +0 -44
- package/dist/types/launcher/ports.d.ts +0 -135
- package/dist/types/launcher/spawn.d.ts +0 -20
- package/dist/types/launcher.d.ts +0 -98
- package/dist/types/legacy.d.ts +0 -29
- package/dist/types/pathNorm.d.ts +0 -14
- package/dist/types/paths.d.ts +0 -79
- package/dist/types/poolPickReport.d.ts +0 -44
- package/dist/types/poolStore.d.ts +0 -18
- package/dist/types/providersStore.d.ts +0 -137
- package/dist/types/realCredentialCache.d.ts +0 -11
- package/dist/types/realPorts.d.ts +0 -109
- package/dist/types/resolve/conditions.d.ts +0 -37
- package/dist/types/resolve/decide.d.ts +0 -45
- package/dist/types/resolve/extends.d.ts +0 -41
- package/dist/types/resolve/flatten.d.ts +0 -13
- package/dist/types/resolve/match.d.ts +0 -58
- package/dist/types/resolve/pipeline.d.ts +0 -33
- package/dist/types/resolve/plan.d.ts +0 -57
- package/dist/types/resolve/projects.d.ts +0 -61
- package/dist/types/resolve/reconcile.d.ts +0 -80
- package/dist/types/resolve/types.d.ts +0 -159
- package/dist/types/resolve/walk.d.ts +0 -85
- package/dist/types/resolve/walkPolicy.d.ts +0 -15
- package/dist/types/usage/account.d.ts +0 -20
- package/dist/types/usage/middleware.d.ts +0 -17
- package/dist/types/usage/pick.d.ts +0 -62
- package/dist/types/usage/plan.d.ts +0 -18
- package/dist/types/usage/poolPick.d.ts +0 -31
- package/dist/types/usage/preflight.d.ts +0 -20
- package/dist/types/usage/rateLimit.d.ts +0 -25
- package/dist/types/usage/read.d.ts +0 -70
- package/dist/types/usage/scan.d.ts +0 -24
- package/dist/types/usage/schema.d.ts +0 -318
- package/dist/types/usage/store.d.ts +0 -43
- package/dist/types/versionDiscovery.d.ts +0 -47
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import type { IncomingHttpHeaders, IncomingMessage } from "node:http";
|
|
2
|
-
/**
|
|
3
|
-
* The agent-shim-internal headers the launcher injects through `ANTHROPIC_CUSTOM_HEADERS` so the front door can identify and route a request's session. None of them may ever leave the machine: the identity step strips every one before a route sees the request, so no upstream, and not even the headroom hop, learns what they say.
|
|
4
|
-
*/
|
|
5
|
-
export declare const INTERNAL_HEADER_NAMES: readonly ["x-agent-shim-identity", "x-agent-shim-session", "x-agent-shim-headroom", "x-agent-shim-auth", "x-agent-shim-hop", "x-agent-shim-hop-id", "x-headroom-project-id", "x-headroom-base-url"];
|
|
6
|
-
/** Carries the launching identity's name (the `@name` the launch resolved), set by the launcher and stripped at the door. */
|
|
7
|
-
export declare const IDENTITY_HEADER = "x-agent-shim-identity";
|
|
8
|
-
/** Carries one launch's session id, generated by the launcher and stripped at the door; response middleware keys its observations by it. */
|
|
9
|
-
export declare const SESSION_HEADER = "x-agent-shim-session";
|
|
10
|
-
/** Set to `1` when the launch resolved headroom on, so the front door knows this session's requests must take the headroom hop; stripped at the door. */
|
|
11
|
-
export declare const HEADROOM_FLAG_HEADER = "x-agent-shim-headroom";
|
|
12
|
-
/**
|
|
13
|
-
* Carries the project identity headroom scopes its memory state to (the git repository root of the launch directory). Also injected by the launcher and stripped at the door: the headroom hop re-sets it on its own request, so it reaches headroom and nowhere else.
|
|
14
|
-
*/
|
|
15
|
-
export declare const PROJECT_ID_HEADER = "x-headroom-project-id";
|
|
16
|
-
/**
|
|
17
|
-
* Headroom's per-request upstream selector, which the child used to carry and the door now owns: the door strips any inbound copy (a leftover from an older launcher's environment would otherwise redirect the daemon behind the door's back) and its hop sets it fresh when a route names an upstream.
|
|
18
|
-
*/
|
|
19
|
-
export declare const HEADROOM_BASE_URL_HEADER = "x-headroom-base-url";
|
|
20
|
-
/**
|
|
21
|
-
* The per-launch capability a client-facing listener requires before it routes anything: a random token the launcher generates, hands the child through `ANTHROPIC_CUSTOM_HEADERS`, and records in the door's session registry, so a loopback process that never launched through agent-shim cannot spend the session's credentials or quota. Stripped at the door like every internal header.
|
|
22
|
-
*/
|
|
23
|
-
export declare const AUTH_HEADER = "x-agent-shim-auth";
|
|
24
|
-
/**
|
|
25
|
-
* The capability the direct listener requires: a per-generation random secret held only in the door process's memory, set by the headroom hop on the requests it forwards back. A loopback process that finds the direct port still cannot use it, and nothing on disk ever holds the secret.
|
|
26
|
-
*/
|
|
27
|
-
export declare const HOP_SECRET_HEADER = "x-agent-shim-hop";
|
|
28
|
-
/**
|
|
29
|
-
* Names the credential custody record one headroom hop holds: a random id the hop sets on the request it hands headroom, alongside placeholders where the real credential headers were. The direct listener exchanges a live id (and only for the provider the record was made for) for the real headers before routing, so the credential itself never crosses headroom or the plain-HTTP leg back.
|
|
30
|
-
*/
|
|
31
|
-
export declare const HOP_ID_HEADER = "x-agent-shim-hop-id";
|
|
32
|
-
/** What the identity step learned about one request from the launcher-injected headers. */
|
|
33
|
-
export interface SessionIdentity {
|
|
34
|
-
/** The identity name the launch resolved, when it resolved one. */
|
|
35
|
-
readonly identity: string | undefined;
|
|
36
|
-
/** The per-launch session id, when the launcher injected one. */
|
|
37
|
-
readonly sessionId: string | undefined;
|
|
38
|
-
/** Whether this launch resolved headroom on, so its requests must pass through the headroom hop before their route. */
|
|
39
|
-
readonly headroom: boolean;
|
|
40
|
-
/** The project identity headroom scopes memory to, re-set on the headroom hop and sent nowhere else. */
|
|
41
|
-
readonly projectId: string | undefined;
|
|
42
|
-
}
|
|
43
|
-
/** The outcome of the pipeline's first step: what the session is, and the headers that may actually leave the machine. */
|
|
44
|
-
export interface IdentifiedRequest {
|
|
45
|
-
readonly session: SessionIdentity;
|
|
46
|
-
/**
|
|
47
|
-
* The request's headers with every internal header removed. A route forwards these, and only these, onwards: the identity and session headers are for the front door alone and must never reach an upstream, the headroom hop, or a translator's backend call.
|
|
48
|
-
*/
|
|
49
|
-
readonly forwardableHeaders: IncomingHttpHeaders;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* The identity step: splits a request's headers into what the front door may learn (the session) and what may leave the machine (everything else). Internal header names are matched case-insensitively, which is how the transport already normalises them.
|
|
53
|
-
*/
|
|
54
|
-
export declare function identifyRequest(headers: Readonly<IncomingHttpHeaders>): IdentifiedRequest;
|
|
55
|
-
/** The path prefix every provider-scoped request sits under: a routed session's base URL is the front door's address plus `/providers/<name>`, so one listener serves any number of providers, each with its own credential and translation settings. */
|
|
56
|
-
export declare const PROVIDER_PATH_PREFIX = "/providers/";
|
|
57
|
-
/** The base URL a provider's sessions are pointed at: HTTPS, because the child sends its provider credential on every request and only a listener presenting a leaf signed by agent-shim's CA may receive it. */
|
|
58
|
-
export declare function providerBaseUrl(port: number, provider: string): string;
|
|
59
|
-
/** The bare origin of a loopback listener, which is what a headroom hop is told to forward to: headroom appends the client's own request path to it. */
|
|
60
|
-
export declare function directOrigin(port: number): string;
|
|
61
|
-
/** Splits `/providers/<name>/<rest>` into the provider name and the rest, or undefined for any other path. */
|
|
62
|
-
export declare function parseProviderPath(path: string): {
|
|
63
|
-
readonly provider: string;
|
|
64
|
-
readonly rest: string;
|
|
65
|
-
} | undefined;
|
|
66
|
-
/** One request as a route sees it: the transport's own request and abort signal, plus what the identity step resolved. */
|
|
67
|
-
export interface RoutedRequest {
|
|
68
|
-
readonly method: string;
|
|
69
|
-
/** The request target exactly as received, path and query together. */
|
|
70
|
-
readonly url: string;
|
|
71
|
-
/** The request's headers with every internal header already stripped: exactly what a route may forward onwards. */
|
|
72
|
-
readonly headers: IncomingHttpHeaders;
|
|
73
|
-
/** The request's body stream: a route either pipes it onward untouched or reads it whole, never both. */
|
|
74
|
-
readonly body: IncomingMessage;
|
|
75
|
-
/** Aborted the moment the client goes away; a route aborts its own upstream work with it. */
|
|
76
|
-
readonly signal: AbortSignal;
|
|
77
|
-
readonly session: SessionIdentity;
|
|
78
|
-
}
|
|
79
|
-
/** The response half a route writes through, so the pipeline can run response middleware at the head and apply backpressure uniformly. */
|
|
80
|
-
export interface RoutedResponse {
|
|
81
|
-
/** Whether the head has already been written. */
|
|
82
|
-
readonly headersSent: boolean;
|
|
83
|
-
/** Writes the response head, running every response observer first. Must be called exactly once, before any write. */
|
|
84
|
-
readonly start: (status: number, headers: Readonly<Record<string, string>>) => void;
|
|
85
|
-
/** 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. */
|
|
86
|
-
readonly flush: () => void;
|
|
87
|
-
/**
|
|
88
|
-
* 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.
|
|
89
|
-
*/
|
|
90
|
-
readonly write: (chunk: string | Uint8Array) => Promise<void>;
|
|
91
|
-
/** Ends the response. */
|
|
92
|
-
readonly end: () => void;
|
|
93
|
-
/** Drops the connection: an upstream failure after the head went out, where no status can be sent any more. */
|
|
94
|
-
readonly destroy: () => void;
|
|
95
|
-
}
|
|
96
|
-
/** One pluggable destination the front door routes a request to. */
|
|
97
|
-
export interface FrontDoorRoute {
|
|
98
|
-
/** The route's name, for logs and for response middleware context. */
|
|
99
|
-
readonly name: string;
|
|
100
|
-
/**
|
|
101
|
-
* 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.
|
|
102
|
-
*/
|
|
103
|
-
readonly headroomEligible: boolean;
|
|
104
|
-
/**
|
|
105
|
-
* 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.
|
|
106
|
-
*/
|
|
107
|
-
readonly headroomUpstream: string | undefined;
|
|
108
|
-
/**
|
|
109
|
-
* 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.
|
|
110
|
-
*/
|
|
111
|
-
readonly serve: (request: RoutedRequest, response: RoutedResponse) => Promise<void>;
|
|
112
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import http from "node:http";
|
|
2
|
-
import type { IncomingMessage } from "node:http";
|
|
3
|
-
import https from "node:https";
|
|
4
|
-
import type { LeafCert } from "./connect";
|
|
5
|
-
import type { PipelineRequest } from "./pipeline";
|
|
6
|
-
/** A front-door listener: plain HTTP for the direct listener only headroom reaches, HTTPS for the provider listener every provider session's child connects to. */
|
|
7
|
-
export type FrontDoorServer = http.Server | https.Server;
|
|
8
|
-
/**
|
|
9
|
-
* A front-door listener: the transport a routed session's requests arrive on. It turns each Node request into a `PipelineRequest` (the raw request plus an abort signal that fires the moment the client goes away before the response finished) and hands it to the pipeline; the pipeline owns identification, admission, middleware and routing.
|
|
10
|
-
*
|
|
11
|
-
* With `tls`, the listener serves HTTPS with that leaf, which is how the provider listener authenticates itself to the child: the leaf is signed by agent-shim's CA, whose key only the owning user can read, and the only CA the child trusts for a 127.0.0.1 certificate is that one (no public CA issues certificates for a loopback address), so a process that merely binds the port cannot complete a handshake the child accepts and never receives the request (credentials and capability included). Without `tls` it serves plain HTTP, which only the direct listener does: nothing that reaches it carries a real credential (see the credential custody).
|
|
12
|
-
*
|
|
13
|
-
* The disconnect is read from the response's `close` event, not the request's: the request emits `close` as soon as its body has been read, long before the response ends. `GET /healthz` is answered here, before the pipeline, because a readiness probe is not a routed session and carries no session headers or capability.
|
|
14
|
-
*/
|
|
15
|
-
export declare function createFrontDoorServer(pipeline: (request: PipelineRequest) => Promise<void>, log: (line: string) => void, tls?: LeafCert): FrontDoorServer;
|
|
16
|
-
/**
|
|
17
|
-
* Probes a listener's health endpoint once: the check the supervisor's start and the tests use to know the door is actually serving, not merely bound. With `ca`, the probe speaks HTTPS and accepts only a certificate chaining to that CA for 127.0.0.1, so a healthy answer also proves who is answering; without it, plain HTTP. No capability is ever sent: the health endpoint needs none.
|
|
18
|
-
*/
|
|
19
|
-
export declare function frontDoorHealthy(port: number, ca?: string): Promise<boolean>;
|
|
20
|
-
/** How a listener is bound. */
|
|
21
|
-
interface ListenOptions {
|
|
22
|
-
/** The sticky port to try first; any free port is used when it is taken or absent. */
|
|
23
|
-
readonly preferredPort?: number;
|
|
24
|
-
/** Receives the actual port the moment the bind succeeds, before the health probe. */
|
|
25
|
-
readonly onBound?: (port: number) => void;
|
|
26
|
-
/** The CA a TLS listener's leaf chains to, so its own start-up probe verifies the handshake a child will make; omitted for a plain-HTTP listener. */
|
|
27
|
-
readonly ca?: string;
|
|
28
|
-
/**
|
|
29
|
-
* Called for any error the listener emits once it is bound and serving. Attached only after the bind has settled: a server emits the sticky port's EADDRINUSE as an ordinary `error` event, and a handler attached before the bind would treat the fallback the bind is about to make as fatal.
|
|
30
|
-
*/
|
|
31
|
-
readonly onError?: (error: Error) => void;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Binds the listener on `preferredPort` when it is free and any free port otherwise (bind, do not probe: check-then-bind races), probing its own health endpoint before resolving.
|
|
35
|
-
*/
|
|
36
|
-
export declare function listenFrontDoor(server: FrontDoorServer, options?: Readonly<ListenOptions>): Promise<{
|
|
37
|
-
readonly port: number;
|
|
38
|
-
readonly close: () => Promise<void>;
|
|
39
|
-
}>;
|
|
40
|
-
/**
|
|
41
|
-
* Iterates a forwarded response's body as typed chunks. Node types the async iteration of an `IncomingMessage` as `any`, so every consumer narrows here once rather than each carrying its own assertion.
|
|
42
|
-
*/
|
|
43
|
-
export declare function upstreamChunks(stream: IncomingMessage): AsyncGenerator<Uint8Array>;
|
|
44
|
-
export {};
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import type { HeadroomFs } from "../headroom/state";
|
|
3
|
-
/**
|
|
4
|
-
* The launcher-to-door wire protocol this release speaks: the headers a launch's child presents, the capability registry's record format, and the routes. Bump it when a change would stop a door of this release serving launches made by an older one, or an older door serving this release's launches.
|
|
5
|
-
*
|
|
6
|
-
* The rule that makes a bump safe: a door accepts every lower protocol's launches (protocol 1 accepts the former `x-claude-use-*` header names), and a launcher replaces a door whose protocol is lower than its own, never one that is higher. Replacing is safe because restarts are what the door is designed for: the capability registry is on disk and the listeners keep their sticky ports, so launches already running reach the replacement on the addresses they froze at launch.
|
|
7
|
-
*/
|
|
8
|
-
export declare const FRONT_DOOR_PROTOCOL = 1;
|
|
9
|
-
/** The record held in one front-door session-registry file: the launcher's pid plus the per-launch capability token its child presents on every request. */
|
|
10
|
-
declare const FrontDoorSessionSchema: z.ZodObject<{
|
|
11
|
-
pid: z.ZodNumber;
|
|
12
|
-
startedAt: z.ZodNumber;
|
|
13
|
-
token: z.ZodString;
|
|
14
|
-
}, z.core.$strict>;
|
|
15
|
-
export type FrontDoorSession = z.infer<typeof FrontDoorSessionSchema>;
|
|
16
|
-
/**
|
|
17
|
-
* The front-door supervisor's state.json under `<home>/frontdoor/`. Every field is optional because the file exists in stages, exactly like headroom's and the old codex daemon's: a fresh supervisor writes its own pid before the listener is up, and a shut-down front door leaves only the sticky `lastPort` and any `lastError` worth surfacing.
|
|
18
|
-
*/
|
|
19
|
-
declare const FrontDoorStateSchema: z.ZodObject<{
|
|
20
|
-
protocol: z.ZodOptional<z.ZodNumber>;
|
|
21
|
-
supervisorPid: z.ZodOptional<z.ZodNumber>;
|
|
22
|
-
port: z.ZodOptional<z.ZodNumber>;
|
|
23
|
-
lastPort: z.ZodOptional<z.ZodNumber>;
|
|
24
|
-
connectPort: z.ZodOptional<z.ZodNumber>;
|
|
25
|
-
lastConnectPort: z.ZodOptional<z.ZodNumber>;
|
|
26
|
-
directPort: z.ZodOptional<z.ZodNumber>;
|
|
27
|
-
lastDirectPort: z.ZodOptional<z.ZodNumber>;
|
|
28
|
-
lastError: z.ZodOptional<z.ZodString>;
|
|
29
|
-
}, z.core.$strict>;
|
|
30
|
-
export type FrontDoorState = z.infer<typeof FrontDoorStateSchema>;
|
|
31
|
-
/** Reads state.json, or undefined when absent or malformed: a malformed file is overwritten by the next supervisor rather than failing every launch over one bad write. */
|
|
32
|
-
export declare function readFrontDoorState(fs: HeadroomFs, stateFile: string): FrontDoorState | undefined;
|
|
33
|
-
/** Writes state.json. A plain write, for the reason headroom's is: the file is advisory coordination read tolerantly, and the exclusive-create start lock is the actual mutual exclusion. */
|
|
34
|
-
export declare function writeFrontDoorState(fs: HeadroomFs, stateFile: string, state: Readonly<FrontDoorState>): void;
|
|
35
|
-
/**
|
|
36
|
-
* The origin the headroom hop forwards routed traffic back to (the direct listener's), or undefined before the front door has ever served: what the headroom allowlist must admit for a provider routed through headroom, since headroom reaches the door's routes at exactly this address.
|
|
37
|
-
*/
|
|
38
|
-
export declare function frontDoorOrigin(state: FrontDoorState | undefined): string | undefined;
|
|
39
|
-
/**
|
|
40
|
-
* Writes one launch's session record: the registry entry that both keeps the door from idling out and holds the token that launch's requests must present. The token is a bearer capability, so the directory is created owner-only (0700) and the record written owner-only (0600): on a multi-user host another local account must be able neither to list nor to read it, since presenting it to a listener would spend the victim's provider credential or Codex login.
|
|
41
|
-
*/
|
|
42
|
-
export declare function writeFrontDoorSession(fs: HeadroomFs, sessionsDir: string, session: Readonly<FrontDoorSession>): void;
|
|
43
|
-
/** A registered launch as status and the idle decision see it: the pid and start time, never the token, so nothing that prints or serialises a session can leak the capability. */
|
|
44
|
-
export type FrontDoorSessionSummary = Pick<FrontDoorSession, "pid" | "startedAt">;
|
|
45
|
-
/**
|
|
46
|
-
* Every registered launch, unreadable or malformed files skipped, sorted by pid. Read through `FrontDoorSessionSchema`, which carries the token: headroom's token-less `listSessions` is strict and rejects every front-door record, which would make a live session invisible to the supervisor's idle decision and close the listeners (releasing the sticky ports) under a running child.
|
|
47
|
-
*/
|
|
48
|
-
export declare function listFrontDoorSessions(fs: HeadroomFs, sessionsDir: string): readonly FrontDoorSessionSummary[];
|
|
49
|
-
/** Removes one launcher's registry entry, and with it that launch's capability. Idempotent. */
|
|
50
|
-
export declare function removeFrontDoorSession(fs: HeadroomFs, sessionsDir: string, pid: number): void;
|
|
51
|
-
/** Removes every entry whose launcher is no longer running, so a dead launch's capability stops being accepted and cannot keep the door awake. The predicate must be zombie-aware, as for headroom's. */
|
|
52
|
-
export declare function pruneDeadFrontDoorSessions(fs: HeadroomFs, sessionsDir: string, isRunning: (pid: number) => boolean): readonly number[];
|
|
53
|
-
/**
|
|
54
|
-
* Every live session's token, read fresh: what a client-facing listener checks a request's capability against. Malformed files are skipped for the same reason `readFrontDoorState` tolerates them.
|
|
55
|
-
*/
|
|
56
|
-
export declare function liveSessionTokens(fs: HeadroomFs, sessionsDir: string): ReadonlySet<string>;
|
|
57
|
-
export {};
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import type { HeadroomFs } from "../headroom/state";
|
|
2
|
-
import type { LayoutPaths } from "../paths";
|
|
3
|
-
/** A bound front-door listener in this process, and how to stop it. */
|
|
4
|
-
interface FrontDoorListenerHandle {
|
|
5
|
-
readonly port: number;
|
|
6
|
-
/** Stops accepting, ends every live connection, and resolves once the port is released. */
|
|
7
|
-
readonly close: () => Promise<void>;
|
|
8
|
-
}
|
|
9
|
-
/** Every effect the front-door supervisor performs, injected so its whole lifecycle runs against fakes: no real process, port, clock or listener in a unit test. */
|
|
10
|
-
export interface FrontDoorSupervisorPorts {
|
|
11
|
-
readonly fs: HeadroomFs;
|
|
12
|
-
readonly paths: LayoutPaths;
|
|
13
|
-
readonly ownPid: number;
|
|
14
|
-
readonly now: () => number;
|
|
15
|
-
/** An event-loop-yielding wait, for the same reason headroom's is: nothing else would ever run between ticks. */
|
|
16
|
-
readonly sleep: (ms: number) => Promise<void>;
|
|
17
|
-
/** Zombie-aware liveness, the notion every coordination decision in this project uses. */
|
|
18
|
-
readonly isRunning: (pid: number) => boolean;
|
|
19
|
-
/**
|
|
20
|
-
* Starts the provider listener in this process on `preferredPort` (falling back to any free port when the sticky one is taken, decided at bind time rather than by a racy check-then-bind probe) and resolves once it has bound and answered its own health probe over TLS. This is the HTTPS listener every provider session's base URL points at, serving a loopback leaf signed by agent-shim's CA.
|
|
21
|
-
*/
|
|
22
|
-
readonly startProviderListener: (preferredPort: number | undefined) => Promise<FrontDoorListenerHandle>;
|
|
23
|
-
/**
|
|
24
|
-
* Starts the CONNECT surface in this process on `preferredPort` (same bind-time fallback): the TLS-terminating listener an OAuth session's HTTPS_PROXY points at. Bound before any state names it, so an OAuth launch never finds one listener up without the other.
|
|
25
|
-
*/
|
|
26
|
-
readonly startConnectListener: (preferredPort: number | undefined) => Promise<FrontDoorListenerHandle>;
|
|
27
|
-
/**
|
|
28
|
-
* Starts the direct listener on `preferredPort` (same bind-time fallback): the same routes as the provider listener, minus the headroom hop, over plain HTTP because headroom is what connects to it. Headroom forwards routed traffic back here, which is what keeps the hop from looping, and the port is sticky like the others because headroom's allowlist admits its exact origin. It admits only the hop's own requests and holds no credential until it redeems one from the hop's custody.
|
|
29
|
-
*/
|
|
30
|
-
readonly startDirectListener: (preferredPort: number | undefined) => Promise<FrontDoorListenerHandle>;
|
|
31
|
-
readonly log: (line: string) => void;
|
|
32
|
-
}
|
|
33
|
-
/** Options for `runFrontDoorSupervisor`. */
|
|
34
|
-
export interface RunFrontDoorSupervisorOptions {
|
|
35
|
-
/** Bounds the loop's iterations so a test can observe steady state; such a run returns `FRONTDOOR_SUPERVISOR_STILL_RUNNING`. */
|
|
36
|
-
readonly tickLimit?: number;
|
|
37
|
-
}
|
|
38
|
-
/** Returned by a tick-bounded run that reached its limit while still serving. Never an exit code. */
|
|
39
|
-
export declare const FRONTDOOR_SUPERVISOR_STILL_RUNNING = -1;
|
|
40
|
-
/** The supervisor's tick rate for the idle clock. */
|
|
41
|
-
export declare const FRONTDOOR_POLL_MS = 1000;
|
|
42
|
-
/**
|
|
43
|
-
* Runs the front-door supervisor: binds all three listeners (the HTTPS provider listener, the CONNECT surface and the plain-HTTP direct listener, each on its sticky port), records the serving state, and shuts down after `idleShutdownMinutes` with an empty session registry. Returns the exit code: 0 for an idle shutdown, 1 for a fatal start failure.
|
|
44
|
-
*
|
|
45
|
-
* The listeners live in this process, so there is no child to keep alive: a crash of this process is a crash of every routed session's door at once, and recovery is the next launch's ensure spawning a replacement on the same sticky ports, which is exactly the failure model the issue asks to settle with tests. Keeping the headroom daemon alive is the headroom supervisor's job, not this one's; the two daemons idle out independently.
|
|
46
|
-
*/
|
|
47
|
-
export declare function runFrontDoorSupervisor(idleShutdownMinutes: number, ports: FrontDoorSupervisorPorts, options?: RunFrontDoorSupervisorOptions): Promise<number>;
|
|
48
|
-
export {};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type * as net from "node:net";
|
|
2
|
-
import type { Duplex } from "node:stream";
|
|
3
|
-
import type { StreamTap } from "./capture";
|
|
4
|
-
/**
|
|
5
|
-
* Pumps one already-TLS-terminated stream byte for byte to its real host over a fresh TLS connection, teeing every chunk in both directions into `tap` when one is given. This is the instrumentation for a host whose protocol the surface does not parse: the claude.ai control plane carries Remote Control on a channel that mixes shapes (observed under capture: an HTTP/1.1-shaped token request first, then binary HTTP/2 frames around it), so parsing it as either protocol would destroy or distort it. Pass everything through unchanged and record, never interpret: the frames are decoded offline, and a protocol the door does not understand keeps working exactly as the blind tunnel served it.
|
|
6
|
-
*
|
|
7
|
-
* Lifecycle mirrors `blindTunnel`: whoever goes away first takes the other side with it, so neither half of a tap session ever outlives its peer. The tap's listeners ride beside the pipes, additive like every other observer in this surface, so a recording failure fails loudly rather than silently stopping a diagnostic.
|
|
8
|
-
*/
|
|
9
|
-
export declare function pumpTapSession(secure: Duplex, openUpstream: (clientAlpn: string | undefined) => Promise<net.Socket>, tap: StreamTap | undefined, prefix?: Readonly<Buffer>): void;
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/** The file effects resolving a trust bundle needs, injected so the decision runs against fakes. */
|
|
2
|
-
export interface TrustBundleFs {
|
|
3
|
-
/** Reads a file as UTF-8 text; throws when it cannot be read. */
|
|
4
|
-
readonly readFileUtf8: (file: string) => string;
|
|
5
|
-
/** Whether a file exists. */
|
|
6
|
-
readonly exists: (file: string) => boolean;
|
|
7
|
-
/** Creates a directory and any missing parents. */
|
|
8
|
-
readonly mkdirp: (dir: string) => void;
|
|
9
|
-
/** Writes a file atomically (a reader never sees a partial bundle). */
|
|
10
|
-
readonly writeFileAtomic: (file: string, contents: string) => void;
|
|
11
|
-
}
|
|
12
|
-
/** The CA bundle a routed child is pointed at through `NODE_EXTRA_CA_CERTS`, and why it differs from what the parent environment set, when it does. */
|
|
13
|
-
export interface TrustBundle {
|
|
14
|
-
readonly path: string;
|
|
15
|
-
readonly warning?: string;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Decides the `NODE_EXTRA_CA_CERTS` a routed child gets. Node reads exactly one file from that variable, so a user who already set it (a corporate proxy's CA, say) would lose that trust if the launcher simply pointed it at agent-shim's CA. Instead:
|
|
19
|
-
*
|
|
20
|
-
* - nothing inherited: agent-shim's CA file itself;
|
|
21
|
-
* - an inherited file that already contains agent-shim's CA (a launch from inside a routed session): that file, unchanged;
|
|
22
|
-
* - any other readable inherited file: a combined bundle (the inherited certificates, then agent-shim's CA) written once under `bundlesDir`, named by its content hash so concurrent launches agree on it and a live child's file is never rewritten under it;
|
|
23
|
-
* - an inherited file that cannot be read: agent-shim's CA alone, with a warning naming the file. Node itself would have ignored that file with a warning, so the child loses no trust it would otherwise have had.
|
|
24
|
-
*/
|
|
25
|
-
export declare function resolveTrustBundle(params: {
|
|
26
|
-
readonly caCertFile: string;
|
|
27
|
-
readonly bundlesDir: string;
|
|
28
|
-
readonly inherited: string | undefined;
|
|
29
|
-
readonly fs: TrustBundleFs;
|
|
30
|
-
}): TrustBundle;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/** An IP address as the 4 or 16 octets an iPAddress GeneralName carries. */
|
|
2
|
-
export declare function ipBytes(address: string): Buffer;
|
|
3
|
-
/** A key pair in the PEM forms agent-shim persists. */
|
|
4
|
-
export interface PemKeyPair {
|
|
5
|
-
readonly certPem: string;
|
|
6
|
-
readonly keyPem: string;
|
|
7
|
-
}
|
|
8
|
-
/** Generates a self-signed CA certificate, CA-only by basicConstraints and keyUsage, with a random serial. */
|
|
9
|
-
export declare function issueCaCertificate(commonName: string, notBefore: Readonly<Date>, notAfter: Readonly<Date>): PemKeyPair;
|
|
10
|
-
/** Issues a server-auth leaf under a CA: its own RSA key, the first name as the common name, every name in the subjectAltName (an IP address as an iPAddress entry, because TLS clients match an IP host only against those). */
|
|
11
|
-
export declare function issueLeafCertificate(ca: PemKeyPair, names: readonly string[], notBefore: Readonly<Date>, notAfter: Readonly<Date>): PemKeyPair;
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { CliError } from "../cliError";
|
|
2
|
-
import type { LayoutPaths } from "../paths";
|
|
3
|
-
import { type HeadroomFs } from "./state";
|
|
4
|
-
/** Raised when the daemon cannot be brought up in time, or reports a fatal `lastError`: a launch that asked for headroom must never silently fall back to a direct connection. */
|
|
5
|
-
export declare class HeadroomStartError extends CliError {
|
|
6
|
-
constructor(message: string);
|
|
7
|
-
}
|
|
8
|
-
/** Every effect the ensure step performs, injected so it runs against fakes in tests. */
|
|
9
|
-
export interface EnsureHeadroomPorts {
|
|
10
|
-
readonly fs: HeadroomFs;
|
|
11
|
-
/** Zombie-aware liveness: a supervisor or daemon that exited without being reaped still answers signal 0 as alive, but will never serve a request or write state, so it must read as dead here (see `realIsProcessRunning`). */
|
|
12
|
-
readonly isRunning: (pid: number) => boolean;
|
|
13
|
-
readonly now: () => number;
|
|
14
|
-
readonly sleep: (ms: number) => void;
|
|
15
|
-
/** Spawns the detached supervisor process that owns the headroom daemon, returning its pid. */
|
|
16
|
-
readonly spawnSupervisor: (paths: LayoutPaths) => number;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Brings the headroom daemon up for this launch, or finds it already running, and registers this launcher pid in its session registry (the fact that keeps the daemon alive and defers drift restarts until this launch is done).
|
|
20
|
-
*
|
|
21
|
-
* The exclusive-create start lock decides which of several concurrent launches spawns the one supervisor: its holder spawns and keeps waiting like everyone else, everyone else waits on state.json, and a lock whose holder has died is removed and retried. Readiness is "state names a live supervisor, a live headroom pid, and a port", which the supervisor only writes after its own `/readyz` probe has passed, so polling state alone never mistakes a bound-but-not-ready port for a usable daemon.
|
|
22
|
-
*
|
|
23
|
-
* Throws `HeadroomStartError` (naming the daemon log path and any recorded `lastError`) when the daemon is not up within `HEADROOM_START_TIMEOUT_MS`, or when a live supervisor has recorded a fatal error.
|
|
24
|
-
*/
|
|
25
|
-
export declare function ensureHeadroom(params: {
|
|
26
|
-
readonly paths: LayoutPaths;
|
|
27
|
-
readonly launcherPid: number;
|
|
28
|
-
readonly ports: EnsureHeadroomPorts;
|
|
29
|
-
}): {
|
|
30
|
-
readonly port: number;
|
|
31
|
-
};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/** One `ANTHROPIC_CUSTOM_HEADERS` entry as the child environment carries it. */
|
|
2
|
-
export interface CustomHeaderEntry {
|
|
3
|
-
readonly name: string;
|
|
4
|
-
readonly value: string;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Merges `ANTHROPIC_CUSTOM_HEADERS` blocks: every string in `existing` (as Claude Code writes them, `Name: Value` entries separated by newlines) parsed in order, then every entry of `additions` set on top, so a later block replaces an earlier block's value for the same header name while leaving every other entry untouched. The result is re-serialised in first-seen order with additions updated in place.
|
|
8
|
-
*
|
|
9
|
-
* Blank lines are skipped on parse; a header name keeps its own case exactly as written, since header names are case-insensitive on the wire but a statusline or log reading this value back should see what was set.
|
|
10
|
-
*/
|
|
11
|
-
export declare function mergeAnthropicCustomHeaders(existing: readonly (string | undefined)[], additions: readonly CustomHeaderEntry[]): string;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { HeadroomGlobalConfig } from "../config/schema";
|
|
2
|
-
/** The token-saving settings of the `headroom` config block, without its install and lifecycle fields (`source`, `idleShutdownMinutes`). */
|
|
3
|
-
export type HeadroomSettings = Pick<HeadroomGlobalConfig, "mode" | "targetRatio" | "ccr" | "rolloutChannel" | "interceptToolResults" | "readMaturation">;
|
|
4
|
-
/** Picks the settings out of a headroom config block, leaving unset fields unset. */
|
|
5
|
-
export declare function settingsOf(config: Readonly<HeadroomGlobalConfig>): HeadroomSettings;
|
|
6
|
-
/**
|
|
7
|
-
* The `headroom proxy` arguments the settings ask for, after the supervisor's own `--host` and `--port`. Only settings that are set produce a flag, so an empty settings object leaves headroom on its own defaults. `ccr: "default"` is headroom's default and likewise adds nothing.
|
|
8
|
-
*/
|
|
9
|
-
export declare function settingsArgs(settings: Readonly<HeadroomSettings>): string[];
|
|
10
|
-
/** The environment the settings add to the daemon's: only the rollout channel, which headroom reads from `HEADROOM_ROLLOUT_CHANNEL` and has no flag for. */
|
|
11
|
-
export declare function settingsEnv(settings: Readonly<HeadroomSettings>): Record<string, string>;
|
|
12
|
-
/** A stable hash of the settings, so drift detection compares one short string against what the running daemon was started with. The same settings hash the same whatever order the config file wrote its keys in. */
|
|
13
|
-
export declare function hashSettings(settings: Readonly<HeadroomSettings>): string;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Whether an install spec resolves through a git ref that can move underneath it. A full commit SHA never moves; a branch name, a tag a maintainer can re-point, or no ref at all (the remote's default branch) can, and a rebased, renamed or deleted branch changes or breaks every fresh install. A registry requirement is not a git source.
|
|
3
|
-
*/
|
|
4
|
-
export declare function isMovingGitSource(spec: string): boolean;
|
|
5
|
-
/** The full commit SHA an install spec pins, lowercased, or undefined when the spec is a registry requirement or follows a moving ref. */
|
|
6
|
-
export declare function pinnedGitCommit(spec: string): string | undefined;
|
|
7
|
-
/** The commit an install was built from, read from the text of its `direct_url.json`, or undefined when the file records no VCS commit (a registry install) or is not valid JSON. */
|
|
8
|
-
export declare function parseInstalledCommit(directUrlJson: string): string | undefined;
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { type Provider } from "../config/schema";
|
|
3
|
-
import type { FarmFs } from "../launcher/ports";
|
|
4
|
-
/** The upstream headroom serves when no provider's base URL claims the request: Claude Code's own API. */
|
|
5
|
-
export declare const HEADROOM_ANTHROPIC_UPSTREAM = "https://api.anthropic.com";
|
|
6
|
-
/**
|
|
7
|
-
* The headroom supervisor's state.json under `<home>/headroom/`. Every field is optional because the file exists in stages: a fresh supervisor writes only `supervisorPid` before the daemon is up, and a shut-down daemon leaves the file with everything cleared except any `lastError` worth surfacing.
|
|
8
|
-
*/
|
|
9
|
-
export declare const HeadroomStateSchema: z.ZodObject<{
|
|
10
|
-
supervisorPid: z.ZodOptional<z.ZodNumber>;
|
|
11
|
-
headroomPid: z.ZodOptional<z.ZodNumber>;
|
|
12
|
-
port: z.ZodOptional<z.ZodNumber>;
|
|
13
|
-
lastPort: z.ZodOptional<z.ZodNumber>;
|
|
14
|
-
version: z.ZodOptional<z.ZodString>;
|
|
15
|
-
allowlistHash: z.ZodOptional<z.ZodString>;
|
|
16
|
-
settingsHash: z.ZodOptional<z.ZodString>;
|
|
17
|
-
installedSource: z.ZodOptional<z.ZodString>;
|
|
18
|
-
lastError: z.ZodOptional<z.ZodString>;
|
|
19
|
-
}, z.core.$strict>;
|
|
20
|
-
export type HeadroomState = z.infer<typeof HeadroomStateSchema>;
|
|
21
|
-
/** 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. */
|
|
22
|
-
export type HeadroomFs = Pick<FarmFs, "mkdirp" | "mkdirPrivate" | "readFileUtf8" | "writeFileUtf8" | "writeFilePrivate" | "writeFileExclusive" | "readdir" | "removeRecursive">;
|
|
23
|
-
/**
|
|
24
|
-
* The record held in one session-registry file. `supervisorPid` names the supervisor whose daemon the launch connected to: several supervisors can be alive at once (a superseded one keeps serving the sessions it already has), and each must judge idle shutdown and drift restarts by its own sessions only, or none would ever retire.
|
|
25
|
-
*/
|
|
26
|
-
declare const HeadroomSessionSchema: z.ZodObject<{
|
|
27
|
-
pid: z.ZodNumber;
|
|
28
|
-
startedAt: z.ZodNumber;
|
|
29
|
-
supervisorPid: z.ZodNumber;
|
|
30
|
-
}, z.core.$strict>;
|
|
31
|
-
export type HeadroomSession = z.infer<typeof HeadroomSessionSchema>;
|
|
32
|
-
/** The exclusive-create start lock's recorded holder. */
|
|
33
|
-
declare const HeadroomLockSchema: z.ZodObject<{
|
|
34
|
-
pid: z.ZodNumber;
|
|
35
|
-
at: z.ZodNumber;
|
|
36
|
-
}, z.core.$strict>;
|
|
37
|
-
export type HeadroomLock = z.infer<typeof HeadroomLockSchema>;
|
|
38
|
-
/** Reads state.json, or undefined when absent. A malformed file is treated as absent: the lock-and-spawn flow below overwrites it with a fresh supervisor's state rather than crashing every future launch over one bad write. */
|
|
39
|
-
export declare function readHeadroomState(fs: HeadroomFs, stateFile: string): HeadroomState | undefined;
|
|
40
|
-
/**
|
|
41
|
-
* Writes state.json atomically (a temporary sibling renamed into place), so a concurrent reader never sees a partial file. A reader treats a malformed file as absent, and a launcher that finds no supervisor in state spawns one, so a torn write read mid-flight started a redundant supervisor and daemon.
|
|
42
|
-
*/
|
|
43
|
-
export declare function writeHeadroomState(fs: HeadroomFs, stateFile: string, state: Readonly<HeadroomState>): void;
|
|
44
|
-
/** The allowlist the daemon is started with: every provider's base URL plus Claude Code's own API, so a session with no provider selected reaches the real Anthropic upstream through the same proxy. */
|
|
45
|
-
export declare function headroomAllowlist(providers: readonly {
|
|
46
|
-
readonly baseUrl: string;
|
|
47
|
-
}[]): readonly string[];
|
|
48
|
-
/**
|
|
49
|
-
* The upstreams a set of providers routes to through headroom: every `http` provider's base URL, plus the front door's direct origin once the door has ever served. A provider session routed through headroom is forwarded by the door's hop back through its own direct listener (whatever route serves there, the in-process translator or the pass-through), so headroom must admit that address like any provider's; the direct port is sticky, so its origin stays stable across restarts. Before the front door has ever served there is no address to admit, and the first routed launch starts it before bringing headroom up, so a freshly started headroom daemon already sees it.
|
|
50
|
-
*/
|
|
51
|
-
export declare function headroomUpstreams(providers: readonly Provider[], frontDoorOrigin: string | undefined): readonly {
|
|
52
|
-
readonly baseUrl: string;
|
|
53
|
-
}[];
|
|
54
|
-
/** A stable hash of an allowlist, so drift detection compares one short string instead of recomputing set equality against a running process it cannot interrogate. */
|
|
55
|
-
export declare function hashAllowlist(allowlist: readonly string[]): string;
|
|
56
|
-
/** Every provider definition under `providersDir`, best-effort: unreadable files are skipped, because the allowlist's job is to open every upstream a launch might use, not to fail a launch over one broken file. */
|
|
57
|
-
export declare function readAllProviders(fs: HeadroomFs, providersDir: string): readonly {
|
|
58
|
-
name: string;
|
|
59
|
-
provider: Provider;
|
|
60
|
-
}[];
|
|
61
|
-
/** One live session-registry entry, or undefined when the launcher pid has no file. */
|
|
62
|
-
export declare function readSession(fs: HeadroomFs, sessionsDir: string, pid: number): HeadroomSession | undefined;
|
|
63
|
-
/** Registers this launch in the session registry: the fact the daemon must keep serving, and the fact whose disappearance starts the idle shutdown clock. */
|
|
64
|
-
export declare function writeSession(fs: HeadroomFs, sessionsDir: string, session: Readonly<HeadroomSession>): void;
|
|
65
|
-
/** Removes a launcher's registry entry. Idempotent: an absent entry is not an error, because the supervisor's own pruning may have removed it first. */
|
|
66
|
-
export declare function removeSession(fs: HeadroomFs, sessionsDir: string, pid: number): void;
|
|
67
|
-
/** Every registry entry on disk, unreadable files skipped, sorted by pid: what `headroom status` prints and what the supervisor's idle and drift decisions read. */
|
|
68
|
-
export declare function listSessions(fs: HeadroomFs, sessionsDir: string): readonly HeadroomSession[];
|
|
69
|
-
/** The registry entries recorded against one supervisor: the sessions whose daemon that supervisor is responsible for keeping up. */
|
|
70
|
-
export declare function sessionsForSupervisor(fs: HeadroomFs, sessionsDir: string, supervisorPid: number): readonly HeadroomSession[];
|
|
71
|
-
/**
|
|
72
|
-
* Removes every registry entry whose pid is no longer running: a launcher that died without releasing its entry must not keep the daemon awake or block a drift restart forever. The predicate must be zombie-aware (see `realIsProcessRunning`): a launcher that exited but was never reaped still answers signal 0 as alive, which would keep its session registered indefinitely.
|
|
73
|
-
*/
|
|
74
|
-
export declare function pruneDeadSessions(fs: HeadroomFs, sessionsDir: string, isRunning: (pid: number) => boolean): readonly number[];
|
|
75
|
-
/** Reads the start lock, or undefined when absent or unreadable. */
|
|
76
|
-
export declare function readStartLock(fs: HeadroomFs, lockFile: string): HeadroomLock | undefined;
|
|
77
|
-
export {};
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { type HeadroomGlobalConfig } from "../config/schema";
|
|
2
|
-
import type { LayoutPaths } from "../paths";
|
|
3
|
-
import { type HeadroomSettings } from "./settings";
|
|
4
|
-
import { type HeadroomFs } from "./state";
|
|
5
|
-
/** Everything the supervisor needs to run the daemon, resolved from the global config and the filesystem before it starts. */
|
|
6
|
-
export interface HeadroomSupervisorConfig {
|
|
7
|
-
/** The install spec as configured (`headroom.source`), already defaulted. */
|
|
8
|
-
readonly source: string;
|
|
9
|
-
/** `headroom.idleShutdownMinutes`, already defaulted. */
|
|
10
|
-
readonly idleShutdownMinutes: number;
|
|
11
|
-
/** The token-saving settings (`mode`, `targetRatio`, `ccr`, the rollout channel and its opt-ins), as configured; unset fields leave headroom on its own defaults. */
|
|
12
|
-
readonly settings: HeadroomSettings;
|
|
13
|
-
}
|
|
14
|
-
/** How the supervisor resolves the configured source against its defaults. */
|
|
15
|
-
export declare function resolveSupervisorConfig(configured: Readonly<HeadroomGlobalConfig>): HeadroomSupervisorConfig;
|
|
16
|
-
/**
|
|
17
|
-
* Every effect the supervisor performs, injected so the whole lifecycle is testable against fakes: no real process, port, clock, or HTTP call ever happens in a unit test.
|
|
18
|
-
*/
|
|
19
|
-
export interface SupervisorPorts {
|
|
20
|
-
readonly fs: HeadroomFs;
|
|
21
|
-
readonly paths: LayoutPaths;
|
|
22
|
-
readonly ownPid: number;
|
|
23
|
-
readonly now: () => number;
|
|
24
|
-
/**
|
|
25
|
-
* Waits `ms`, yielding to the event loop while it does. Deliberately NOT a synchronous Atomics.wait-style sleep: the real supervisor learns of its child's death through the ChildProcess exit event, and a blocking sleep would stop the event loop from ever delivering it (the exact failure that left an unreaped zombie answering liveness checks as alive).
|
|
26
|
-
*/
|
|
27
|
-
readonly sleep: (ms: number) => Promise<void>;
|
|
28
|
-
/**
|
|
29
|
-
* Zombie-aware liveness, and for a daemon this supervisor spawned, exit-event-backed: the implementation keeps the spawned ChildProcess handle, consumes its exit event (which is also what reaps it), and reports the pid as not running from that moment. A bare signal-0 check is not enough, because a defunct process still answers it.
|
|
30
|
-
*/
|
|
31
|
-
readonly isRunning: (pid: number) => boolean;
|
|
32
|
-
/** Returns a free loopback port. Async because the only reliable way to reserve one is to bind and release a socket. */
|
|
33
|
-
readonly freePort: () => Promise<number>;
|
|
34
|
-
/** Whether nothing is currently listening on one specific loopback port: the check that decides whether the sticky `lastPort` can be reused. The same bind-and-release technique `freePort` uses, applied to a number the caller already cares about. */
|
|
35
|
-
readonly isPortFree: (port: number) => Promise<boolean>;
|
|
36
|
-
/**
|
|
37
|
-
* Starts `headroom proxy` bound to `port` with the given allowlist, returning its pid. Output goes to the daemon log. The implementation must keep the ChildProcess handle and attach an exit listener (detaching the process is fine; unref'ing a child you still need events from is not, since without the listener nothing reaps it and it lingers as a zombie).
|
|
38
|
-
*/
|
|
39
|
-
readonly spawnHeadroom: (port: number, allowlist: readonly string[], settings: Readonly<HeadroomSettings>) => number;
|
|
40
|
-
/** Reads the headroom config block as it stands on disk now. The supervisor calls it every tick, so a changed `source` or setting is noticed while the daemon runs and applied by the same quiet-registry restart an allowlist change gets. */
|
|
41
|
-
readonly readConfig: () => HeadroomSupervisorConfig;
|
|
42
|
-
/** Stops a process the supervisor owns, escalating SIGTERM to SIGKILL on a bounded timeout (see `stopSupervisedProcess`). */
|
|
43
|
-
readonly stopProcess: (pid: number) => void;
|
|
44
|
-
/** True once `GET /readyz` on the port succeeds. */
|
|
45
|
-
readonly ready: (port: number) => Promise<boolean>;
|
|
46
|
-
/** Runs `uv tool install <spec>`, logging output to the daemon log. Returns ok=false with the error when `uv` is absent or the install fails. */
|
|
47
|
-
readonly install: (spec: string) => {
|
|
48
|
-
readonly ok: boolean;
|
|
49
|
-
readonly error?: string;
|
|
50
|
-
};
|
|
51
|
-
/** The installed `headroom --version` output, or undefined when the binary is not on PATH. */
|
|
52
|
-
readonly headroomVersion: () => string | undefined;
|
|
53
|
-
/** The commit the installed headroom was built from, as the install itself records it (`direct_url.json`), or undefined when it was not installed from a git source. */
|
|
54
|
-
readonly installedCommit: () => string | undefined;
|
|
55
|
-
readonly log: (line: string) => void;
|
|
56
|
-
}
|
|
57
|
-
/** First retry delay after a failed start. Half a second keeps a transient blip invisible to the next launch. */
|
|
58
|
-
export declare const HEADROOM_BACKOFF_BASE_MS = 500;
|
|
59
|
-
/** Ceiling for the retry delay: with no live sessions there is no user waiting, so half a minute between attempts is the most the daemon log should grow. */
|
|
60
|
-
export declare const HEADROOM_BACKOFF_CAP_MS = 30000;
|
|
61
|
-
/** Consecutive failed starts before the supervisor gives up, records `lastError`, and exits: a proxy that cannot come up five times in a row is broken, not unlucky. */
|
|
62
|
-
export declare const HEADROOM_START_RETRY_BUDGET = 5;
|
|
63
|
-
/** The supervisor's own tick rate for crash detection, drift checks, and idle shutdown. */
|
|
64
|
-
export declare const HEADROOM_POLL_MS = 1000;
|
|
65
|
-
/** The process-control primitives the escalating stop needs, injected so the escalation itself is unit-testable against a process that ignores SIGTERM. */
|
|
66
|
-
export interface StopProcessPrimitives {
|
|
67
|
-
/** Delivers a signal; throws when the target is already gone. */
|
|
68
|
-
readonly signal: (pid: number, signal: NodeJS.Signals) => void;
|
|
69
|
-
/** Zombie-aware liveness: a defunct process is not running. */
|
|
70
|
-
readonly isRunning: (pid: number) => boolean;
|
|
71
|
-
/** Synchronous wait between liveness polls; blocking is correct here, since nothing productive can happen until the target is gone or the grace expires. */
|
|
72
|
-
readonly waitMs: (ms: number) => void;
|
|
73
|
-
readonly now: () => number;
|
|
74
|
-
}
|
|
75
|
-
/** How `stopSupervisedProcess` ended. `still-running` means even SIGKILL did not clear the pid within its grace (an uninterruptible process); callers log it loudly. */
|
|
76
|
-
export type StopOutcome = "exited-on-term" | "killed" | "still-running";
|
|
77
|
-
/** Grace after SIGTERM before escalating to SIGKILL: enough for a proxy to drain an in-flight request, short enough that a restart is not visibly stalled. This proxy's server has been observed to ignore SIGTERM outright, so the escalation is not optional. */
|
|
78
|
-
export declare const TERM_GRACE_MS = 1500;
|
|
79
|
-
/** Grace after SIGKILL before declaring the process unkillable. SIGKILL is immediate at the kernel level; this only bounds the wait for the scheduler to reap it. */
|
|
80
|
-
export declare const KILL_GRACE_MS = 1500;
|
|
81
|
-
/**
|
|
82
|
-
* Stops a process the supervisor owns: SIGTERM, wait up to `TERM_GRACE_MS`; SIGKILL, wait up to `KILL_GRACE_MS`; then report. Liveness during the waits must be zombie-aware: an unreaped target still answers signal 0, which would read as "survived SIGKILL".
|
|
83
|
-
*/
|
|
84
|
-
export declare function stopSupervisedProcess(pid: number, primitives: StopProcessPrimitives): StopOutcome;
|
|
85
|
-
/** The backoff for attempt `attempt` (1-based): base doubled per failure, capped. */
|
|
86
|
-
export declare function backoffForAttempt(attempt: number): number;
|
|
87
|
-
/**
|
|
88
|
-
* Whether an installed `headroom --version` string satisfies the configured source. A source carrying a PEP 440 specifier (`headroom>=0.39`, `headroom==0.39.1`) is checked against the version; anything else (a bare git URL, the default) carries no version to check and is satisfied by any install, with drift handled by comparing `installedSource` instead.
|
|
89
|
-
*/
|
|
90
|
-
export declare function versionSatisfies(version: string | undefined, source: string): boolean;
|
|
91
|
-
/** Options for `runSupervisor`. */
|
|
92
|
-
export interface RunSupervisorOptions {
|
|
93
|
-
/**
|
|
94
|
-
* Bounds the supervisor loop's iterations. The real supervisor runs until it exits on its own (idle shutdown or fatal error); a bounded run is how tests observe steady-state behaviour (drift deferral, restart-on-crash, pruning) without an infinite loop, and such a run returns `HEADROOM_SUPERVISOR_STILL_RUNNING` instead of an exit code.
|
|
95
|
-
*/
|
|
96
|
-
readonly tickLimit?: number;
|
|
97
|
-
}
|
|
98
|
-
/** Returned by a tick-bounded `runSupervisor` that reached its limit while still managing the daemon: not an exit code, and never produced without `tickLimit`. */
|
|
99
|
-
export declare const HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
|
|
100
|
-
/**
|
|
101
|
-
* Runs the headroom supervisor loop: install the daemon, start it, keep it alive (for crash or drift), and stop it after the configured idle period with an empty session registry. Returns the process exit code (0 for an idle shutdown, 1 for a fatal setup failure).
|
|
102
|
-
*
|
|
103
|
-
* Every effect flows through `ports`, so the whole loop is unit-testable with a fake clock, filesystem, and processes. The loop awaits its sleeps, which in the real implementation run on a timer: between ticks the event loop turns, the spawned child's exit event is delivered (and the child thereby reaped), and the next tick's liveness check reads the truth.
|
|
104
|
-
*/
|
|
105
|
-
export declare function runSupervisor(initialConfig: HeadroomSupervisorConfig, ports: SupervisorPorts, options?: RunSupervisorOptions): Promise<number>;
|
|
106
|
-
/** The allowlist as it stands right now: every provider's upstream (the front door's origin for a codex provider, which headroom forwards codex traffic back to) plus Claude Code's own API. */
|
|
107
|
-
export declare function currentHeadroomAllowlist(fs: HeadroomFs, paths: LayoutPaths): readonly string[];
|