agent-shim 8.40.0 → 8.41.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/index.cjs CHANGED
@@ -279,6 +279,7 @@ __export(index_exports, {
279
279
  resolveAgentShimHome: () => resolveAgentShimHome,
280
280
  resolveClaudeHome: () => resolveClaudeHome,
281
281
  resolveDecisions: () => resolveDecisions,
282
+ resolveLaunchSelection: () => resolveLaunchSelection,
282
283
  resolveLayoutPaths: () => resolveLayoutPaths,
283
284
  resolveSupervisorConfig: () => resolveSupervisorConfig,
284
285
  resyncFarm: () => resyncFarm,
@@ -5427,7 +5428,7 @@ var import_undici = require("undici");
5427
5428
  // package.json
5428
5429
  var package_default = {
5429
5430
  name: "agent-shim",
5430
- version: "8.40.0",
5431
+ version: "8.41.0",
5431
5432
  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.",
5432
5433
  license: "MIT",
5433
5434
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -5861,7 +5862,10 @@ function frontDoorRcApiClient(port, ca, token) {
5861
5862
  var import_client3 = require("@orpc/client");
5862
5863
 
5863
5864
  // src/checkReport.ts
5864
- var import_node_os4 = __toESM(require("node:os"), 1);
5865
+ var import_node_os5 = __toESM(require("node:os"), 1);
5866
+
5867
+ // src/launchSelection.ts
5868
+ var import_node_os3 = __toESM(require("node:os"), 1);
5865
5869
 
5866
5870
  // src/launcher/cascade.ts
5867
5871
  var import_node_path11 = __toESM(require("node:path"), 1);
@@ -6033,319 +6037,127 @@ function loadIdentity(identitiesDir, name, fs12) {
6033
6037
  return loadConfigFile(import_node_path12.default.join(identitiesDir, name, "identity.json"), IdentitySchema, fs12.readConfigFile);
6034
6038
  }
6035
6039
 
6036
- // src/paths.ts
6037
- var import_node_fs2 = __toESM(require("node:fs"), 1);
6038
- var import_node_os2 = __toESM(require("node:os"), 1);
6039
- var import_node_path14 = __toESM(require("node:path"), 1);
6040
-
6041
- // src/headroom/state.ts
6042
- var import_node_crypto6 = require("node:crypto");
6043
- var import_node_path13 = __toESM(require("node:path"), 1);
6044
- var import_zod5 = require("zod");
6045
- var HEADROOM_ANTHROPIC_UPSTREAM = "https://api.anthropic.com";
6046
- var HEADROOM_STATE_SCHEMA_VERSION = 2;
6047
- var HeadroomStateSchema = import_zod5.z.strictObject({
6048
- /** The supervisor process owning the daemon. Alive means someone is keeping headroom running. */
6049
- supervisorPid: import_zod5.z.number().int().positive().optional(),
6050
- /** The headroom proxy process itself. Absent while the supervisor is between restarts. */
6051
- headroomPid: import_zod5.z.number().int().positive().optional(),
6052
- /**
6053
- * The unix socket the proxy serves on, inside the owner-only socket directory. Absent until the proxy has passed its readiness check, so "socketPath is set" is itself the ready signal a launcher polls for. The front door reads it on every hop, so a restart (which keeps the generation's path) is followed without anything being relaunched.
6054
- */
6055
- socketPath: import_zod5.z.string().min(1).optional(),
6056
- /** The `headroom --version` output of the running install. */
6057
- version: import_zod5.z.string().optional(),
6058
- /** Hash of the allowlist the running proxy was started with, so a provider-file change is detected as drift. */
6059
- allowlistHash: import_zod5.z.string().optional(),
6060
- /** Hash of the token-saving settings the running proxy was started with, so a settings change is detected as drift. */
6061
- settingsHash: import_zod5.z.string().optional(),
6062
- /** The install spec the running install was last installed from, so a `source` config change is detected as drift. */
6063
- installedSource: import_zod5.z.string().optional(),
6064
- /** The last fatal error, kept across shutdowns so `headroom status` can explain a daemon that is not running. */
6065
- lastError: import_zod5.z.string().optional()
6066
- });
6067
- var HeadroomSessionSchema = import_zod5.z.strictObject({ pid: import_zod5.z.number().int().positive(), startedAt: import_zod5.z.number(), supervisorPid: import_zod5.z.number().int().positive() });
6068
- var HeadroomLockSchema = import_zod5.z.strictObject({ pid: import_zod5.z.number().int().positive(), at: import_zod5.z.number() });
6069
- function parseJson(raw, schema) {
6070
- if (raw === void 0 || raw.trim() === "") {
6071
- return void 0;
6072
- }
6073
- const parsed = schema.safeParse(JSON.parse(raw));
6074
- return parsed.success ? parsed.data : void 0;
6075
- }
6076
- function readHeadroomState(fs12, stateFile) {
6077
- try {
6078
- return parseJson(fs12.readFileUtf8(stateFile), HeadroomStateSchema);
6079
- } catch {
6080
- return void 0;
6081
- }
6082
- }
6083
- function writeHeadroomState(fs12, stateFile, state) {
6084
- fs12.mkdirp(import_node_path13.default.dirname(stateFile));
6085
- fs12.writeFilePrivate(stateFile, `${JSON.stringify(state, null, 2)}
6086
- `);
6087
- }
6088
- function headroomAllowlist(providers) {
6089
- const urls = /* @__PURE__ */ new Set([HEADROOM_ANTHROPIC_UPSTREAM]);
6090
- for (const provider of providers) {
6091
- urls.add(provider.baseUrl);
6092
- }
6093
- return [...urls].sort();
6094
- }
6095
- function headroomUpstreams(providers, frontDoorOrigin2) {
6096
- const upstreams = providers.filter((provider) => !isCodexProvider(provider)).map((provider) => ({ baseUrl: provider.baseUrl }));
6097
- if (providers.length > 0 && frontDoorOrigin2 !== void 0) {
6098
- upstreams.push({ baseUrl: frontDoorOrigin2 });
6099
- }
6100
- return upstreams;
6101
- }
6102
- function hashAllowlist(allowlist) {
6103
- return (0, import_node_crypto6.createHash)("sha256").update(allowlist.join("\n")).digest("hex");
6104
- }
6105
- function readAllProviders(fs12, providersDir) {
6106
- const result = [];
6107
- for (const name of fs12.readdir(providersDir)) {
6108
- if (!name.endsWith(".json")) {
6109
- continue;
6110
- }
6111
- try {
6112
- const parsed = parseJson(fs12.readFileUtf8(import_node_path13.default.join(providersDir, name)), ProviderSchema);
6113
- if (parsed !== void 0) {
6114
- result.push({ name: name.slice(0, -".json".length), provider: parsed });
6115
- }
6116
- } catch {
6117
- continue;
6118
- }
6119
- }
6120
- return result;
6121
- }
6122
- function writeSession(fs12, sessionsDir, session) {
6123
- fs12.mkdirp(sessionsDir);
6124
- fs12.writeFileUtf8(import_node_path13.default.join(sessionsDir, `${String(session.pid)}.json`), `${JSON.stringify(session, null, 2)}
6125
- `);
6126
- }
6127
- function removeSession(fs12, sessionsDir, pid) {
6128
- fs12.removeRecursive(import_node_path13.default.join(sessionsDir, `${String(pid)}.json`));
6129
- }
6130
- function listSessions(fs12, sessionsDir) {
6131
- const sessions = [];
6132
- for (const name of fs12.readdir(sessionsDir)) {
6133
- if (!name.endsWith(".json")) {
6134
- continue;
6135
- }
6136
- try {
6137
- const parsed = parseJson(fs12.readFileUtf8(import_node_path13.default.join(sessionsDir, name)), HeadroomSessionSchema);
6138
- if (parsed !== void 0) {
6139
- sessions.push(parsed);
6140
- }
6141
- } catch {
6142
- continue;
6143
- }
6144
- }
6145
- return sessions.sort((a, b) => a.pid - b.pid);
6146
- }
6147
- function sessionsForSupervisor(fs12, sessionsDir, supervisorPid) {
6148
- return listSessions(fs12, sessionsDir).filter((session) => session.supervisorPid === supervisorPid);
6149
- }
6150
- function pruneDeadSessions(fs12, sessionsDir, isRunning) {
6151
- const removed = [];
6152
- for (const session of listSessions(fs12, sessionsDir)) {
6153
- if (!isRunning(session.pid)) {
6154
- removeSession(fs12, sessionsDir, session.pid);
6155
- removed.push(session.pid);
6156
- }
6157
- }
6158
- return removed;
6159
- }
6160
- function readStartLock(fs12, lockFile) {
6161
- try {
6162
- return parseJson(fs12.readFileUtf8(lockFile), HeadroomLockSchema);
6163
- } catch {
6164
- return void 0;
6165
- }
6166
- }
6167
-
6168
- // src/paths.ts
6169
- function resolveAgentShimHome(env = process.env, home = import_node_os2.default.homedir(), isDirectory = isExistingDirectory) {
6170
- const fromEnv = env.AGENT_SHIM_HOME;
6171
- if (fromEnv !== void 0 && fromEnv !== "") {
6172
- return fromEnv;
6173
- }
6174
- const current = import_node_path14.default.join(home, HOME_DIRNAME);
6175
- const legacy = import_node_path14.default.join(home, LEGACY_HOME_DIRNAME);
6176
- return !isDirectory(current) && isDirectory(legacy) ? legacy : current;
6177
- }
6178
- function isExistingDirectory(candidate) {
6179
- return import_node_fs2.default.statSync(candidate, { throwIfNoEntry: false })?.isDirectory() ?? false;
6180
- }
6181
- function resolveClaudeHome() {
6182
- const fromEnv = process.env.AGENT_SHIM_CLAUDE_HOME;
6183
- if (fromEnv !== void 0 && fromEnv !== "") {
6184
- return fromEnv;
6185
- }
6186
- return import_node_path14.default.join(import_node_os2.default.homedir(), ".claude");
6187
- }
6188
- function buildLayoutPaths(root) {
6189
- return {
6190
- root,
6191
- identitiesDir: import_node_path14.default.join(root, "identities"),
6192
- configProfilesDir: import_node_path14.default.join(root, "config-profiles"),
6193
- providersDir: import_node_path14.default.join(root, "providers"),
6194
- directoryRulesFile: import_node_path14.default.join(root, "directory-rules.json"),
6195
- activeIdentityFile: import_node_path14.default.join(root, "active-identity"),
6196
- globalConfigFile: import_node_path14.default.join(root, "config.json"),
6197
- categoriesLocalFile: import_node_path14.default.join(root, "categories.local.json"),
6198
- claudeShimFile: import_node_path14.default.join(root, "claude-shim.json"),
6199
- headroomDir: import_node_path14.default.join(root, "headroom"),
6200
- headroomStateFile: import_node_path14.default.join(root, "headroom", `state.v${String(HEADROOM_STATE_SCHEMA_VERSION)}.json`),
6201
- headroomSocketDir: import_node_path14.default.join(root, "headroom", "run"),
6202
- headroomLockFile: import_node_path14.default.join(root, "headroom", "start.lock"),
6203
- headroomSessionsDir: import_node_path14.default.join(root, "headroom", "sessions"),
6204
- logsDir: import_node_path14.default.join(root, "logs"),
6205
- headroomLogPath: import_node_path14.default.join(root, "logs", "headroom.log"),
6206
- frontdoorDir: import_node_path14.default.join(root, "frontdoor"),
6207
- frontdoorCaDir: import_node_path14.default.join(root, "frontdoor", "ca"),
6208
- frontdoorCaCertFile: import_node_path14.default.join(root, "frontdoor", "ca", "ca.pem"),
6209
- frontdoorCaKeyFile: import_node_path14.default.join(root, "frontdoor", "ca", "ca.key"),
6210
- frontdoorCaBundlesDir: import_node_path14.default.join(root, "frontdoor", "ca", "bundles"),
6211
- chatgptSignInFile: import_node_path14.default.join(root, "codex", "chatgpt-sign-in.json"),
6212
- frontdoorStateFile: import_node_path14.default.join(root, "frontdoor", "state.json"),
6213
- frontdoorLockFile: import_node_path14.default.join(root, "frontdoor", "start.lock"),
6214
- frontdoorControlTokenFile: import_node_path14.default.join(root, "frontdoor", "control-token"),
6215
- frontdoorSessionsDir: import_node_path14.default.join(root, "frontdoor", "sessions"),
6216
- frontdoorRcCredentialsDir: import_node_path14.default.join(root, "frontdoor", "rc-credentials"),
6217
- frontdoorLogPath: import_node_path14.default.join(root, "logs", "frontdoor.log"),
6218
- usageDir: import_node_path14.default.join(root, "usage"),
6219
- usageLogDir: import_node_path14.default.join(root, "usage", "log"),
6220
- usageSnapshotsDir: import_node_path14.default.join(root, "usage", "snapshots"),
6221
- usagePicksFile: import_node_path14.default.join(root, "usage", "picks.json")
6222
- };
6223
- }
6224
- function resolveLayoutPaths() {
6225
- return buildLayoutPaths(resolveAgentShimHome());
6226
- }
6227
-
6228
6040
  // src/usage/poolPick.ts
6229
- var import_node_path17 = __toESM(require("node:path"), 1);
6230
- var import_zod9 = require("zod");
6041
+ var import_node_path15 = __toESM(require("node:path"), 1);
6042
+ var import_zod8 = require("zod");
6231
6043
 
6232
6044
  // src/usage/account.ts
6233
- var import_node_path15 = __toESM(require("node:path"), 1);
6045
+ var import_node_path13 = __toESM(require("node:path"), 1);
6234
6046
 
6235
6047
  // src/usage/schema.ts
6236
- var import_zod6 = require("zod");
6048
+ var import_zod5 = require("zod");
6237
6049
  var USAGE_SCHEMA_VERSION = 1;
6238
- var VersionSchema = import_zod6.z.literal(USAGE_SCHEMA_VERSION);
6239
- var InstantSchema = import_zod6.z.iso.datetime();
6240
- var TokenCountSchema = import_zod6.z.number().int().nonnegative();
6241
- var TokenUsageSchema = import_zod6.z.strictObject({
6050
+ var VersionSchema = import_zod5.z.literal(USAGE_SCHEMA_VERSION);
6051
+ var InstantSchema = import_zod5.z.iso.datetime();
6052
+ var TokenCountSchema = import_zod5.z.number().int().nonnegative();
6053
+ var TokenUsageSchema = import_zod5.z.strictObject({
6242
6054
  inputTokens: TokenCountSchema.optional(),
6243
6055
  outputTokens: TokenCountSchema.optional(),
6244
6056
  cacheCreationInputTokens: TokenCountSchema.optional(),
6245
6057
  cacheReadInputTokens: TokenCountSchema.optional()
6246
6058
  });
6247
6059
  var LIMIT_KINDS = ["rate-limited", "quota-exhausted"];
6248
- var LimitClassificationSchema = import_zod6.z.strictObject({
6249
- kind: import_zod6.z.enum(LIMIT_KINDS),
6060
+ var LimitClassificationSchema = import_zod5.z.strictObject({
6061
+ kind: import_zod5.z.enum(LIMIT_KINDS),
6250
6062
  /** Seconds to wait before retrying, from the response's `retry-after`. */
6251
- retryAfterSeconds: import_zod6.z.number().nonnegative().optional(),
6063
+ retryAfterSeconds: import_zod5.z.number().nonnegative().optional(),
6252
6064
  /** When the limiting window resets, from the upstream's own reset header. */
6253
6065
  resetAt: InstantSchema.optional(),
6254
6066
  /** The window the upstream named as the binding one (`anthropic-ratelimit-unified-representative-claim`), such as `five_hour` or `seven_day`. */
6255
- window: import_zod6.z.string().optional(),
6067
+ window: import_zod5.z.string().optional(),
6256
6068
  /** The signals the classification rests on, each `<name>=<value>` from a response header or the error body's `type` or `code`. Never the error message. */
6257
- evidence: import_zod6.z.array(import_zod6.z.string())
6069
+ evidence: import_zod5.z.array(import_zod5.z.string())
6258
6070
  });
6259
- var UsageRecordSchema = import_zod6.z.strictObject({
6071
+ var UsageRecordSchema = import_zod5.z.strictObject({
6260
6072
  schemaVersion: VersionSchema,
6261
6073
  /** When the front door received the request. */
6262
6074
  at: InstantSchema,
6263
6075
  /** The launching identity, when the launch resolved one. */
6264
- identity: import_zod6.z.string().optional(),
6076
+ identity: import_zod5.z.string().optional(),
6265
6077
  /** `anthropic` for an OAuth session's request to Claude Code's own API, otherwise the provider's name. */
6266
- provider: import_zod6.z.string(),
6078
+ provider: import_zod5.z.string(),
6267
6079
  /** The launch's session id, generated per launch. */
6268
- sessionId: import_zod6.z.string().optional(),
6080
+ sessionId: import_zod5.z.string().optional(),
6269
6081
  /** The project the launch ran in (its git repository root), when the launch sent one: headroom launches do. */
6270
- project: import_zod6.z.string().optional(),
6082
+ project: import_zod5.z.string().optional(),
6271
6083
  /** The front-door route that served the request. */
6272
- route: import_zod6.z.string(),
6273
- method: import_zod6.z.string(),
6084
+ route: import_zod5.z.string(),
6085
+ method: import_zod5.z.string(),
6274
6086
  /** The API path as the upstream sees it (`/v1/messages`), without the provider prefix or a query string. */
6275
- endpoint: import_zod6.z.string(),
6087
+ endpoint: import_zod5.z.string(),
6276
6088
  /** The model the response named. */
6277
- model: import_zod6.z.string().optional(),
6278
- status: import_zod6.z.number().int(),
6089
+ model: import_zod5.z.string().optional(),
6090
+ status: import_zod5.z.number().int(),
6279
6091
  /** Milliseconds from the request's arrival to its response head. */
6280
- latencyMs: import_zod6.z.number().int().nonnegative(),
6092
+ latencyMs: import_zod5.z.number().int().nonnegative(),
6281
6093
  /** Milliseconds from the request's arrival to the end of its response body. */
6282
- durationMs: import_zod6.z.number().int().nonnegative(),
6094
+ durationMs: import_zod5.z.number().int().nonnegative(),
6283
6095
  /** Whether the client received the whole body. */
6284
- outcome: import_zod6.z.enum(["completed", "aborted"]),
6096
+ outcome: import_zod5.z.enum(["completed", "aborted"]),
6285
6097
  usage: TokenUsageSchema.optional(),
6286
6098
  /** The response's quota and rate-limit headers, by name (see `isRateLimitHeader`). */
6287
- rateLimitHeaders: import_zod6.z.record(import_zod6.z.string(), import_zod6.z.string()).optional(),
6099
+ rateLimitHeaders: import_zod5.z.record(import_zod5.z.string(), import_zod5.z.string()).optional(),
6288
6100
  limit: LimitClassificationSchema.optional(),
6289
6101
  /** The upstream's request id (`request-id`), for matching a record to the provider's own logs. */
6290
- requestId: import_zod6.z.string().optional()
6102
+ requestId: import_zod5.z.string().optional()
6291
6103
  });
6292
- var QuotaWindowSchema = import_zod6.z.strictObject({
6104
+ var QuotaWindowSchema = import_zod5.z.strictObject({
6293
6105
  /** The fraction of the window used, as the upstream reports it (0 to 1). */
6294
- utilization: import_zod6.z.number().nonnegative().optional(),
6106
+ utilization: import_zod5.z.number().nonnegative().optional(),
6295
6107
  resetsAt: InstantSchema.optional(),
6296
6108
  /** The window's own status (`allowed`, `allowed_warning`, `rejected`). */
6297
- status: import_zod6.z.string().optional()
6109
+ status: import_zod5.z.string().optional()
6298
6110
  });
6299
- var UnifiedRateLimitSchema = import_zod6.z.strictObject({
6111
+ var UnifiedRateLimitSchema = import_zod5.z.strictObject({
6300
6112
  /** The overall status: `allowed`, `allowed_warning` or `rejected`. */
6301
- status: import_zod6.z.string().optional(),
6113
+ status: import_zod5.z.string().optional(),
6302
6114
  fiveHour: QuotaWindowSchema.optional(),
6303
6115
  sevenDay: QuotaWindowSchema.optional(),
6304
6116
  /** The window that currently binds. */
6305
- representativeClaim: import_zod6.z.string().optional(),
6117
+ representativeClaim: import_zod5.z.string().optional(),
6306
6118
  /** When the binding window resets. */
6307
6119
  resetAt: InstantSchema.optional(),
6308
6120
  /** Whether usage beyond the plan (extra usage) is available: `allowed` or `rejected`. */
6309
- overageStatus: import_zod6.z.string().optional()
6121
+ overageStatus: import_zod5.z.string().optional()
6310
6122
  });
6311
- var RateLimitStateSchema = import_zod6.z.strictObject({
6123
+ var RateLimitStateSchema = import_zod5.z.strictObject({
6312
6124
  observedAt: InstantSchema,
6313
6125
  /** Every quota and rate-limit header on that response, by name. */
6314
- headers: import_zod6.z.record(import_zod6.z.string(), import_zod6.z.string()),
6126
+ headers: import_zod5.z.record(import_zod5.z.string(), import_zod5.z.string()),
6315
6127
  unified: UnifiedRateLimitSchema.optional()
6316
6128
  });
6317
6129
  var LimitEventSchema = LimitClassificationSchema.extend({
6318
6130
  observedAt: InstantSchema,
6319
- status: import_zod6.z.number().int()
6131
+ status: import_zod5.z.number().int()
6320
6132
  });
6321
- var ProviderQuotaWindowSchema = import_zod6.z.strictObject({
6133
+ var ProviderQuotaWindowSchema = import_zod5.z.strictObject({
6322
6134
  /** What the window measures, in the provider's terms (`tokens`, `tool-calls`). */
6323
- measures: import_zod6.z.string(),
6135
+ measures: import_zod5.z.string(),
6324
6136
  /** The window's length in milliseconds, when the provider's unit is one this project knows; absent for an unrecognised unit, with `period` carrying the raw code. */
6325
- periodMs: import_zod6.z.number().int().positive().optional(),
6137
+ periodMs: import_zod5.z.number().int().positive().optional(),
6326
6138
  /** The provider's own description of the period when `periodMs` could not be derived, such as `unit 4 x 1`. */
6327
- period: import_zod6.z.string().optional(),
6139
+ period: import_zod5.z.string().optional(),
6328
6140
  /** The fraction of the window used (0 to 1). */
6329
- utilization: import_zod6.z.number().nonnegative(),
6141
+ utilization: import_zod5.z.number().nonnegative(),
6330
6142
  resetsAt: InstantSchema.optional(),
6331
6143
  /** Absolute counts, when the provider reports them for this window. */
6332
- limit: import_zod6.z.number().nonnegative().optional(),
6333
- used: import_zod6.z.number().nonnegative().optional(),
6334
- remaining: import_zod6.z.number().nonnegative().optional()
6144
+ limit: import_zod5.z.number().nonnegative().optional(),
6145
+ used: import_zod5.z.number().nonnegative().optional(),
6146
+ remaining: import_zod5.z.number().nonnegative().optional()
6335
6147
  });
6336
- var ProviderQuotaSchema = import_zod6.z.strictObject({
6148
+ var ProviderQuotaSchema = import_zod5.z.strictObject({
6337
6149
  observedAt: InstantSchema,
6338
6150
  /** Which usage endpoint produced it (`z.ai`). */
6339
- source: import_zod6.z.string(),
6151
+ source: import_zod5.z.string(),
6340
6152
  /** The subscription level the endpoint reports, when it does. */
6341
- level: import_zod6.z.string().optional(),
6342
- windows: import_zod6.z.array(ProviderQuotaWindowSchema)
6153
+ level: import_zod5.z.string().optional(),
6154
+ windows: import_zod5.z.array(ProviderQuotaWindowSchema)
6343
6155
  });
6344
- var ProviderUsageStateSchema = import_zod6.z.strictObject({
6156
+ var ProviderUsageStateSchema = import_zod5.z.strictObject({
6345
6157
  /** When the provider last served a request for the identity through the front door. Absent while the state holds only a rate-limit state fetched from the provider's usage endpoint, for an identity that has made no request. */
6346
6158
  lastRequestAt: InstantSchema.optional(),
6347
- lastStatus: import_zod6.z.number().int().optional(),
6348
- lastModel: import_zod6.z.string().optional(),
6159
+ lastStatus: import_zod5.z.number().int().optional(),
6160
+ lastModel: import_zod5.z.string().optional(),
6349
6161
  /** The latest rate-limit state, from the last response that carried any rate-limit header. */
6350
6162
  rateLimit: RateLimitStateSchema.optional(),
6351
6163
  /** The latest classified refusal. It stays after later successes, so a reader compares its `resetAt` (or `observedAt` plus `retryAfterSeconds`) with the current time. */
@@ -6353,31 +6165,31 @@ var ProviderUsageStateSchema = import_zod6.z.strictObject({
6353
6165
  /** The latest quota the provider's usage endpoint reported, for providers whose quota is not visible in response headers. */
6354
6166
  quota: ProviderQuotaSchema.optional()
6355
6167
  });
6356
- var AccountMetadataSchema = import_zod6.z.strictObject({
6357
- accountUuid: import_zod6.z.string().optional(),
6358
- emailAddress: import_zod6.z.string().optional(),
6359
- displayName: import_zod6.z.string().optional(),
6360
- organizationUuid: import_zod6.z.string().optional(),
6361
- organizationName: import_zod6.z.string().optional(),
6362
- organizationType: import_zod6.z.string().optional(),
6363
- organizationRole: import_zod6.z.string().optional(),
6364
- workspaceRole: import_zod6.z.string().optional(),
6365
- billingType: import_zod6.z.string().optional(),
6366
- seatTier: import_zod6.z.string().optional(),
6367
- organizationRateLimitTier: import_zod6.z.string().optional(),
6368
- userRateLimitTier: import_zod6.z.string().optional(),
6369
- hasExtraUsageEnabled: import_zod6.z.boolean().optional(),
6370
- subscriptionCreatedAt: import_zod6.z.string().optional(),
6371
- accountCreatedAt: import_zod6.z.string().optional()
6168
+ var AccountMetadataSchema = import_zod5.z.strictObject({
6169
+ accountUuid: import_zod5.z.string().optional(),
6170
+ emailAddress: import_zod5.z.string().optional(),
6171
+ displayName: import_zod5.z.string().optional(),
6172
+ organizationUuid: import_zod5.z.string().optional(),
6173
+ organizationName: import_zod5.z.string().optional(),
6174
+ organizationType: import_zod5.z.string().optional(),
6175
+ organizationRole: import_zod5.z.string().optional(),
6176
+ workspaceRole: import_zod5.z.string().optional(),
6177
+ billingType: import_zod5.z.string().optional(),
6178
+ seatTier: import_zod5.z.string().optional(),
6179
+ organizationRateLimitTier: import_zod5.z.string().optional(),
6180
+ userRateLimitTier: import_zod5.z.string().optional(),
6181
+ hasExtraUsageEnabled: import_zod5.z.boolean().optional(),
6182
+ subscriptionCreatedAt: import_zod5.z.string().optional(),
6183
+ accountCreatedAt: import_zod5.z.string().optional()
6372
6184
  });
6373
- var UsageSnapshotSchema = import_zod6.z.strictObject({
6185
+ var UsageSnapshotSchema = import_zod5.z.strictObject({
6374
6186
  schemaVersion: VersionSchema,
6375
- identity: import_zod6.z.string(),
6187
+ identity: import_zod5.z.string(),
6376
6188
  updatedAt: InstantSchema,
6377
6189
  /** The identity's account metadata as of `updatedAt`. */
6378
6190
  account: AccountMetadataSchema.optional(),
6379
6191
  /** Latest state per provider, keyed by provider name (`anthropic` for OAuth sessions). */
6380
- providers: import_zod6.z.record(import_zod6.z.string(), ProviderUsageStateSchema)
6192
+ providers: import_zod5.z.record(import_zod5.z.string(), ProviderUsageStateSchema)
6381
6193
  });
6382
6194
 
6383
6195
  // src/usage/account.ts
@@ -6397,7 +6209,7 @@ function isIdentityName(name) {
6397
6209
  return IdentitySchema.shape.name.safeParse(name).success;
6398
6210
  }
6399
6211
  function claudeJsonPath(identitiesDir, identity) {
6400
- return import_node_path15.default.join(identitiesDir, identity, CLAUDE_JSON);
6212
+ return import_node_path13.default.join(identitiesDir, identity, CLAUDE_JSON);
6401
6213
  }
6402
6214
  function readAccountMetadata(fs12, identitiesDir, identity) {
6403
6215
  if (!isIdentityName(identity)) {
@@ -6436,20 +6248,20 @@ function readAccountMetadata(fs12, identitiesDir, identity) {
6436
6248
  }
6437
6249
 
6438
6250
  // src/usage/plan.ts
6439
- var import_zod7 = require("zod");
6440
- var PlanClassSchema = import_zod7.z.union([
6441
- import_zod7.z.strictObject({
6442
- kind: import_zod7.z.literal("subscription"),
6251
+ var import_zod6 = require("zod");
6252
+ var PlanClassSchema = import_zod6.z.union([
6253
+ import_zod6.z.strictObject({
6254
+ kind: import_zod6.z.literal("subscription"),
6443
6255
  /** The plan's size as a multiple of the smallest subscription tier (Max 5x is 5, Max 20x is 20). */
6444
- capacity: import_zod7.z.number().positive(),
6256
+ capacity: import_zod6.z.number().positive(),
6445
6257
  /** False when the tier string named no multiplier and no tier this project knows, so `capacity` is the neutral 1 rather than a reading. */
6446
- recognised: import_zod7.z.boolean(),
6258
+ recognised: import_zod6.z.boolean(),
6447
6259
  /** The tier string the classification rests on, when the account had one. */
6448
- tier: import_zod7.z.string().optional()
6260
+ tier: import_zod6.z.string().optional()
6449
6261
  }),
6450
- import_zod7.z.strictObject({
6451
- kind: import_zod7.z.literal("pay-per-use"),
6452
- tier: import_zod7.z.string().optional()
6262
+ import_zod6.z.strictObject({
6263
+ kind: import_zod6.z.literal("pay-per-use"),
6264
+ tier: import_zod6.z.string().optional()
6453
6265
  })
6454
6266
  ]);
6455
6267
  var TIER_MULTIPLIER = /(?:^|_)(\d+)x$/;
@@ -6518,17 +6330,17 @@ function parseUnifiedRateLimit(headers) {
6518
6330
  }
6519
6331
 
6520
6332
  // src/usage/scan.ts
6521
- var import_zod8 = require("zod");
6522
- var AnthropicUsageSchema = import_zod8.z.looseObject({
6523
- input_tokens: import_zod8.z.number().int().nonnegative().nullish(),
6524
- output_tokens: import_zod8.z.number().int().nonnegative().nullish(),
6525
- cache_creation_input_tokens: import_zod8.z.number().int().nonnegative().nullish(),
6526
- cache_read_input_tokens: import_zod8.z.number().int().nonnegative().nullish()
6333
+ var import_zod7 = require("zod");
6334
+ var AnthropicUsageSchema = import_zod7.z.looseObject({
6335
+ input_tokens: import_zod7.z.number().int().nonnegative().nullish(),
6336
+ output_tokens: import_zod7.z.number().int().nonnegative().nullish(),
6337
+ cache_creation_input_tokens: import_zod7.z.number().int().nonnegative().nullish(),
6338
+ cache_read_input_tokens: import_zod7.z.number().int().nonnegative().nullish()
6527
6339
  });
6528
- var ErrorObjectSchema = import_zod8.z.looseObject({
6529
- type: import_zod8.z.string().optional(),
6530
- code: import_zod8.z.union([import_zod8.z.string(), import_zod8.z.number()]).optional(),
6531
- message: import_zod8.z.string().optional()
6340
+ var ErrorObjectSchema = import_zod7.z.looseObject({
6341
+ type: import_zod7.z.string().optional(),
6342
+ code: import_zod7.z.union([import_zod7.z.string(), import_zod7.z.number()]).optional(),
6343
+ message: import_zod7.z.string().optional()
6532
6344
  });
6533
6345
 
6534
6346
  // src/usage/middleware.ts
@@ -6815,7 +6627,7 @@ function rankPool(input) {
6815
6627
  }
6816
6628
 
6817
6629
  // src/usage/read.ts
6818
- var import_node_path16 = __toESM(require("node:path"), 1);
6630
+ var import_node_path14 = __toESM(require("node:path"), 1);
6819
6631
  var SEGMENT_RE = /^(\d{4}-\d{2}-\d{2})\.(\d+)\.jsonl$/;
6820
6632
  var DAY_MS = 864e5;
6821
6633
  function listLogSegments(fs12, logDir) {
@@ -6836,7 +6648,7 @@ function readUsageLog(fs12, logDir, options = {}) {
6836
6648
  if (sinceMs !== void 0 && segment.endsAt <= sinceMs) {
6837
6649
  continue;
6838
6650
  }
6839
- const contents = fs12.readFileUtf8(import_node_path16.default.join(logDir, segment.name));
6651
+ const contents = fs12.readFileUtf8(import_node_path14.default.join(logDir, segment.name));
6840
6652
  if (contents === void 0) {
6841
6653
  continue;
6842
6654
  }
@@ -6878,7 +6690,7 @@ function snapshotPath(snapshotsDir, identity) {
6878
6690
  if (!isIdentityName(identity)) {
6879
6691
  throw new Error(`"${identity}" is not a valid identity name.`);
6880
6692
  }
6881
- return import_node_path16.default.join(snapshotsDir, `${identity}.json`);
6693
+ return import_node_path14.default.join(snapshotsDir, `${identity}.json`);
6882
6694
  }
6883
6695
  function readUsageSnapshot(fs12, snapshotsDir, identity) {
6884
6696
  const file = snapshotPath(snapshotsDir, identity);
@@ -6925,10 +6737,10 @@ var PoolGraphError = class extends CliError {
6925
6737
  }
6926
6738
  };
6927
6739
  var SCHEMA_VERSION = 1;
6928
- var PickEntrySchema = import_zod9.z.strictObject({ identity: import_zod9.z.string().min(1), at: import_zod9.z.iso.datetime() });
6929
- var PicksFileSchema = import_zod9.z.strictObject({
6930
- schemaVersion: import_zod9.z.literal(SCHEMA_VERSION),
6931
- picks: import_zod9.z.record(import_zod9.z.string().min(1), PickEntrySchema)
6740
+ var PickEntrySchema = import_zod8.z.strictObject({ identity: import_zod8.z.string().min(1), at: import_zod8.z.iso.datetime() });
6741
+ var PicksFileSchema = import_zod8.z.strictObject({
6742
+ schemaVersion: import_zod8.z.literal(SCHEMA_VERSION),
6743
+ picks: import_zod8.z.record(import_zod8.z.string().min(1), PickEntrySchema)
6932
6744
  });
6933
6745
  function errorMessage(error) {
6934
6746
  return error instanceof Error ? error.message : String(error);
@@ -7056,7 +6868,7 @@ function recordStickyPick(fs12, picksFile, directory, identity, nowMs) {
7056
6868
  const current = readPicksFile(fs12, picksFile).file.picks;
7057
6869
  const kept = Object.entries(current).filter(([, entry]) => nowMs - Date.parse(entry.at) <= USAGE_RETENTION_MS);
7058
6870
  const next = { schemaVersion: SCHEMA_VERSION, picks: { ...Object.fromEntries(kept), [directory]: { identity, at: new Date(nowMs).toISOString() } } };
7059
- fs12.mkdirPrivate(import_node_path17.default.dirname(picksFile));
6871
+ fs12.mkdirPrivate(import_node_path15.default.dirname(picksFile));
7060
6872
  fs12.writeFilePrivate(picksFile, `${JSON.stringify(next, null, 2)}
7061
6873
  `);
7062
6874
  }
@@ -7098,8 +6910,8 @@ function collectPoolPick(params) {
7098
6910
  }
7099
6911
 
7100
6912
  // src/configProfilesStore.ts
7101
- var import_node_fs3 = __toESM(require("node:fs"), 1);
7102
- var import_node_path18 = __toESM(require("node:path"), 1);
6913
+ var import_node_fs2 = __toESM(require("node:fs"), 1);
6914
+ var import_node_path16 = __toESM(require("node:path"), 1);
7103
6915
  var ProfileNotFoundError = class extends CliError {
7104
6916
  constructor(profileName) {
7105
6917
  super(`No configuration profile named "${profileName}". Run \`agent-shim profile add ${profileName}\` first.`);
@@ -7125,10 +6937,10 @@ var InvalidCategoryNameError = class extends CliError {
7125
6937
  categoryName;
7126
6938
  };
7127
6939
  function profileJsonPath(paths, name) {
7128
- return import_node_path18.default.join(paths.configProfilesDir, `${name}.json`);
6940
+ return import_node_path16.default.join(paths.configProfilesDir, `${name}.json`);
7129
6941
  }
7130
6942
  function profileExists(paths, name) {
7131
- return import_node_fs3.default.existsSync(profileJsonPath(paths, name));
6943
+ return import_node_fs2.default.existsSync(profileJsonPath(paths, name));
7132
6944
  }
7133
6945
  function requireProfileExists(paths, name) {
7134
6946
  if (!profileExists(paths, name)) {
@@ -7154,10 +6966,10 @@ function createProfile(paths, name, extendsList, description) {
7154
6966
  return parsed.data;
7155
6967
  }
7156
6968
  function listProfiles(paths) {
7157
- if (!import_node_fs3.default.existsSync(paths.configProfilesDir)) {
6969
+ if (!import_node_fs2.default.existsSync(paths.configProfilesDir)) {
7158
6970
  return [];
7159
6971
  }
7160
- const names = import_node_fs3.default.readdirSync(paths.configProfilesDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length)).sort();
6972
+ const names = import_node_fs2.default.readdirSync(paths.configProfilesDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length)).sort();
7161
6973
  const result = [];
7162
6974
  for (const name of names) {
7163
6975
  const profile = readProfile(paths, name);
@@ -7220,7 +7032,7 @@ function setProfileMetadata(paths, name, patch) {
7220
7032
  }
7221
7033
  function removeProfile(paths, name) {
7222
7034
  requireProfileExists(paths, name);
7223
- import_node_fs3.default.rmSync(profileJsonPath(paths, name));
7035
+ import_node_fs2.default.rmSync(profileJsonPath(paths, name));
7224
7036
  }
7225
7037
 
7226
7038
  // src/poolStore.ts
@@ -7322,1450 +7134,1684 @@ function removePool(paths, name) {
7322
7134
  writePools(paths, remaining);
7323
7135
  }
7324
7136
 
7325
- // src/realCredentialCache.ts
7137
+ // src/realPorts.ts
7326
7138
  var import_node_child_process = require("node:child_process");
7327
- var import_node_fs4 = __toESM(require("node:fs"), 1);
7139
+ var import_node_crypto8 = require("node:crypto");
7140
+ var import_node_fs3 = __toESM(require("node:fs"), 1);
7141
+ var import_node_os2 = __toESM(require("node:os"), 1);
7328
7142
  var import_node_path19 = __toESM(require("node:path"), 1);
7143
+ var import_node_sea = require("node:sea");
7329
7144
 
7330
- // src/credentialCache.ts
7331
- var import_zod10 = require("zod");
7332
- var MS_PER_UNIT = { s: 1e3, m: 6e4, h: 36e5, d: 864e5 };
7333
- var CachedCredentialSchema = import_zod10.z.strictObject({
7334
- token: import_zod10.z.string().min(1),
7335
- fetchedAt: import_zod10.z.number().int().nonnegative(),
7336
- source: CredentialSourceSchema
7145
+ // src/headroom/state.ts
7146
+ var import_node_crypto6 = require("node:crypto");
7147
+ var import_node_path17 = __toESM(require("node:path"), 1);
7148
+ var import_zod9 = require("zod");
7149
+ var HEADROOM_ANTHROPIC_UPSTREAM = "https://api.anthropic.com";
7150
+ var HEADROOM_STATE_SCHEMA_VERSION = 2;
7151
+ var HeadroomStateSchema = import_zod9.z.strictObject({
7152
+ /** The supervisor process owning the daemon. Alive means someone is keeping headroom running. */
7153
+ supervisorPid: import_zod9.z.number().int().positive().optional(),
7154
+ /** The headroom proxy process itself. Absent while the supervisor is between restarts. */
7155
+ headroomPid: import_zod9.z.number().int().positive().optional(),
7156
+ /**
7157
+ * The unix socket the proxy serves on, inside the owner-only socket directory. Absent until the proxy has passed its readiness check, so "socketPath is set" is itself the ready signal a launcher polls for. The front door reads it on every hop, so a restart (which keeps the generation's path) is followed without anything being relaunched.
7158
+ */
7159
+ socketPath: import_zod9.z.string().min(1).optional(),
7160
+ /** The `headroom --version` output of the running install. */
7161
+ version: import_zod9.z.string().optional(),
7162
+ /** Hash of the allowlist the running proxy was started with, so a provider-file change is detected as drift. */
7163
+ allowlistHash: import_zod9.z.string().optional(),
7164
+ /** Hash of the token-saving settings the running proxy was started with, so a settings change is detected as drift. */
7165
+ settingsHash: import_zod9.z.string().optional(),
7166
+ /** The install spec the running install was last installed from, so a `source` config change is detected as drift. */
7167
+ installedSource: import_zod9.z.string().optional(),
7168
+ /** The last fatal error, kept across shutdowns so `headroom status` can explain a daemon that is not running. */
7169
+ lastError: import_zod9.z.string().optional()
7337
7170
  });
7338
- function effectiveStore(cache, platform) {
7339
- return cache.store ?? (platform === "darwin" ? "keychain" : "file");
7340
- }
7341
- function ttlMs(cache) {
7342
- if (cache.ttl === void 0) {
7171
+ var HeadroomSessionSchema = import_zod9.z.strictObject({ pid: import_zod9.z.number().int().positive(), startedAt: import_zod9.z.number(), supervisorPid: import_zod9.z.number().int().positive() });
7172
+ var HeadroomLockSchema = import_zod9.z.strictObject({ pid: import_zod9.z.number().int().positive(), at: import_zod9.z.number() });
7173
+ function parseJson(raw, schema) {
7174
+ if (raw === void 0 || raw.trim() === "") {
7343
7175
  return void 0;
7344
7176
  }
7345
- const multiplier = MS_PER_UNIT[cache.ttl.slice(-1)];
7346
- if (multiplier === void 0) {
7347
- throw new Error(`credential cache ttl "${cache.ttl}" has no recognised unit`);
7348
- }
7349
- return Number(cache.ttl.slice(0, -1)) * multiplier;
7177
+ const parsed = schema.safeParse(JSON.parse(raw));
7178
+ return parsed.success ? parsed.data : void 0;
7350
7179
  }
7351
- function isFresh(entry, cache, now) {
7352
- const limit = ttlMs(cache);
7353
- if (entry.fetchedAt > now) {
7354
- return false;
7180
+ function readHeadroomState(fs12, stateFile) {
7181
+ try {
7182
+ return parseJson(fs12.readFileUtf8(stateFile), HeadroomStateSchema);
7183
+ } catch {
7184
+ return void 0;
7355
7185
  }
7356
- return limit === void 0 || now - entry.fetchedAt < limit;
7357
7186
  }
7358
- function cacheAgeMs(entry, now) {
7359
- return Math.max(0, now - entry.fetchedAt);
7187
+ function writeHeadroomState(fs12, stateFile, state) {
7188
+ fs12.mkdirp(import_node_path17.default.dirname(stateFile));
7189
+ fs12.writeFilePrivate(stateFile, `${JSON.stringify(state, null, 2)}
7190
+ `);
7360
7191
  }
7361
- function describeCachedCredential(params) {
7362
- const store = effectiveStore(params.cache, params.platform);
7363
- let entry;
7364
- try {
7365
- entry = params.port.read(store, params.owner);
7366
- } catch (error) {
7367
- return { store, status: "unreadable", reason: error instanceof Error ? error.message : String(error) };
7368
- }
7369
- if (entry === void 0) {
7370
- return { store, status: "empty" };
7192
+ function headroomAllowlist(providers) {
7193
+ const urls = /* @__PURE__ */ new Set([HEADROOM_ANTHROPIC_UPSTREAM]);
7194
+ for (const provider of providers) {
7195
+ urls.add(provider.baseUrl);
7371
7196
  }
7372
- const limit = ttlMs(params.cache);
7373
- const ageMs = cacheAgeMs(entry, params.nowMs);
7374
- return {
7375
- store,
7376
- status: isFresh(entry, params.cache, params.nowMs) ? "fresh" : "expired",
7377
- ageMs,
7378
- ...limit === void 0 ? {} : { expiresInMs: limit - ageMs }
7379
- };
7197
+ return [...urls].sort();
7380
7198
  }
7381
- function formatCachedCredentialState(state) {
7382
- if (state.status === "empty") {
7383
- return `no cached entry in the ${state.store} store yet`;
7384
- }
7385
- if (state.status === "unreadable") {
7386
- return `the ${state.store} store cannot be read here: ${state.reason}`;
7387
- }
7388
- if (state.status === "expired" && (state.expiresInMs === void 0 || state.expiresInMs > 0)) {
7389
- return "cached with a timestamp in the future, treated as expired";
7199
+ function headroomUpstreams(providers, frontDoorOrigin2) {
7200
+ const upstreams = providers.filter((provider) => !isCodexProvider(provider)).map((provider) => ({ baseUrl: provider.baseUrl }));
7201
+ if (providers.length > 0 && frontDoorOrigin2 !== void 0) {
7202
+ upstreams.push({ baseUrl: frontDoorOrigin2 });
7390
7203
  }
7391
- const age = `cached ${formatAge(state.ageMs)} ago`;
7392
- if (state.expiresInMs === void 0) {
7393
- return `${age}, no expiry`;
7204
+ return upstreams;
7205
+ }
7206
+ function hashAllowlist(allowlist) {
7207
+ return (0, import_node_crypto6.createHash)("sha256").update(allowlist.join("\n")).digest("hex");
7208
+ }
7209
+ function readAllProviders(fs12, providersDir) {
7210
+ const result = [];
7211
+ for (const name of fs12.readdir(providersDir)) {
7212
+ if (!name.endsWith(".json")) {
7213
+ continue;
7214
+ }
7215
+ try {
7216
+ const parsed = parseJson(fs12.readFileUtf8(import_node_path17.default.join(providersDir, name)), ProviderSchema);
7217
+ if (parsed !== void 0) {
7218
+ result.push({ name: name.slice(0, -".json".length), provider: parsed });
7219
+ }
7220
+ } catch {
7221
+ continue;
7222
+ }
7394
7223
  }
7395
- return state.status === "fresh" ? `${age}, expires in ${formatAge(state.expiresInMs)}` : `${age}, expired ${formatAge(-state.expiresInMs)} ago`;
7224
+ return result;
7396
7225
  }
7397
-
7398
- // src/realCredentialCache.ts
7399
- var KEYCHAIN_CACHE_SERVICE = "agent-shim-credential-cache";
7400
- var CACHE_DIR_MODE = 448;
7401
- var CACHE_FILE_MODE = 384;
7402
- function cacheFileNameFor(owner) {
7403
- return `${owner.replace(/[^A-Za-z0-9._-]/g, "_")}.json`;
7226
+ function writeSession(fs12, sessionsDir, session) {
7227
+ fs12.mkdirp(sessionsDir);
7228
+ fs12.writeFileUtf8(import_node_path17.default.join(sessionsDir, `${String(session.pid)}.json`), `${JSON.stringify(session, null, 2)}
7229
+ `);
7404
7230
  }
7405
- function parseEntry(raw) {
7406
- let json;
7231
+ function removeSession(fs12, sessionsDir, pid) {
7232
+ fs12.removeRecursive(import_node_path17.default.join(sessionsDir, `${String(pid)}.json`));
7233
+ }
7234
+ function listSessions(fs12, sessionsDir) {
7235
+ const sessions = [];
7236
+ for (const name of fs12.readdir(sessionsDir)) {
7237
+ if (!name.endsWith(".json")) {
7238
+ continue;
7239
+ }
7240
+ try {
7241
+ const parsed = parseJson(fs12.readFileUtf8(import_node_path17.default.join(sessionsDir, name)), HeadroomSessionSchema);
7242
+ if (parsed !== void 0) {
7243
+ sessions.push(parsed);
7244
+ }
7245
+ } catch {
7246
+ continue;
7247
+ }
7248
+ }
7249
+ return sessions.sort((a, b) => a.pid - b.pid);
7250
+ }
7251
+ function sessionsForSupervisor(fs12, sessionsDir, supervisorPid) {
7252
+ return listSessions(fs12, sessionsDir).filter((session) => session.supervisorPid === supervisorPid);
7253
+ }
7254
+ function pruneDeadSessions(fs12, sessionsDir, isRunning) {
7255
+ const removed = [];
7256
+ for (const session of listSessions(fs12, sessionsDir)) {
7257
+ if (!isRunning(session.pid)) {
7258
+ removeSession(fs12, sessionsDir, session.pid);
7259
+ removed.push(session.pid);
7260
+ }
7261
+ }
7262
+ return removed;
7263
+ }
7264
+ function readStartLock(fs12, lockFile) {
7407
7265
  try {
7408
- json = JSON.parse(raw);
7266
+ return parseJson(fs12.readFileUtf8(lockFile), HeadroomLockSchema);
7409
7267
  } catch {
7410
7268
  return void 0;
7411
7269
  }
7412
- const parsed = CachedCredentialSchema.safeParse(json);
7413
- return parsed.success ? parsed.data : void 0;
7414
7270
  }
7415
- function requireKeychain() {
7416
- if (process.platform !== "darwin") {
7417
- throw new Error("the keychain credential store needs macOS; use --credential-cache-store file");
7271
+
7272
+ // src/headroom/ensure.ts
7273
+ var HEADROOM_START_TIMEOUT_MS = 12e4;
7274
+ var HEADROOM_LAUNCHER_POLL_MS = 250;
7275
+ var HeadroomStartError = class extends CliError {
7276
+ constructor(message) {
7277
+ super(message);
7278
+ this.name = "HeadroomStartError";
7418
7279
  }
7419
- }
7420
- function security(args, stdin) {
7421
- const result = (0, import_node_child_process.spawnSync)("security", args, { encoding: "utf8", ...stdin === void 0 ? {} : { input: stdin }, stdio: ["pipe", "pipe", "ignore"] });
7422
- return { status: result.status, stdout: result.stdout };
7423
- }
7424
- function createRealCredentialCache(root) {
7425
- const dir = import_node_path19.default.join(root, "credential-cache");
7426
- const filePath = (owner) => import_node_path19.default.join(dir, cacheFileNameFor(owner));
7427
- return {
7428
- read(store, owner) {
7429
- if (store === "file") {
7430
- try {
7431
- return parseEntry(import_node_fs4.default.readFileSync(filePath(owner), "utf8"));
7432
- } catch {
7433
- return void 0;
7280
+ };
7281
+ function ensureHeadroom(params) {
7282
+ const { paths, ports } = params;
7283
+ const deadline = ports.now() + HEADROOM_START_TIMEOUT_MS;
7284
+ let spawnedSupervisor = false;
7285
+ let awaitingAllowlist = false;
7286
+ for (; ; ) {
7287
+ const state = readHeadroomState(ports.fs, paths.headroomStateFile);
7288
+ if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
7289
+ const daemonUp = state.socketPath !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
7290
+ if (daemonUp && state.socketPath !== void 0) {
7291
+ const required = params.requiredAllowlistHash?.();
7292
+ if (required === void 0 || state.allowlistHash === required) {
7293
+ writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
7294
+ return { socketPath: state.socketPath };
7434
7295
  }
7296
+ const holders = sessionsForSupervisor(ports.fs, paths.headroomSessionsDir, state.supervisorPid).filter((session) => ports.isRunning(session.pid));
7297
+ if (holders.length > 0) {
7298
+ throw new HeadroomStartError(
7299
+ `the running headroom daemon was started before this launch's front door address and would refuse every request this launch sends through it. It restarts once the launches using it end (pid ${holders.map((session) => String(session.pid)).join(", ")}); end them and launch again, or launch with --no-headroom.`
7300
+ );
7301
+ }
7302
+ awaitingAllowlist = true;
7435
7303
  }
7436
- requireKeychain();
7437
- const result = security(["find-generic-password", "-s", KEYCHAIN_CACHE_SERVICE, "-a", owner, "-w"]);
7438
- return result.status === 0 ? parseEntry(Buffer.from(result.stdout.trim(), "base64").toString("utf8")) : void 0;
7439
- },
7440
- write(store, owner, entry) {
7441
- const serialised = JSON.stringify(entry);
7442
- if (store === "file") {
7443
- import_node_fs4.default.mkdirSync(dir, { recursive: true, mode: CACHE_DIR_MODE });
7444
- import_node_fs4.default.chmodSync(dir, CACHE_DIR_MODE);
7445
- const target = filePath(owner);
7446
- const temp = `${target}.${String(process.pid)}.tmp`;
7447
- import_node_fs4.default.writeFileSync(temp, serialised, { mode: CACHE_FILE_MODE });
7448
- import_node_fs4.default.renameSync(temp, target);
7449
- return;
7450
- }
7451
- requireKeychain();
7452
- const encoded = Buffer.from(serialised, "utf8").toString("base64");
7453
- const result = security(["-i"], `add-generic-password -U -s ${KEYCHAIN_CACHE_SERVICE} -a ${JSON.stringify(owner)} -w ${encoded}
7454
- `);
7455
- if (result.status !== 0) {
7456
- throw new Error(`could not write the keychain credential cache for ${owner}`);
7304
+ if (state.lastError !== void 0) {
7305
+ throw new HeadroomStartError(
7306
+ `the headroom daemon reported a fatal error and is not serving: ${state.lastError} (daemon log: ${paths.headroomLogPath})`
7307
+ );
7457
7308
  }
7458
- },
7459
- remove(store, owner) {
7460
- if (store === "file") {
7461
- import_node_fs4.default.rmSync(filePath(owner), { force: true });
7462
- return;
7309
+ } else if (!spawnedSupervisor) {
7310
+ ports.fs.mkdirp(paths.headroomDir);
7311
+ const created = ports.fs.writeFileExclusive(
7312
+ paths.headroomLockFile,
7313
+ `${JSON.stringify({ pid: params.launcherPid, at: ports.now() })}
7314
+ `
7315
+ );
7316
+ if (created) {
7317
+ ports.spawnSupervisor(paths);
7318
+ spawnedSupervisor = true;
7319
+ } else {
7320
+ const lock = readStartLock(ports.fs, paths.headroomLockFile);
7321
+ if (lock === void 0 || !ports.isRunning(lock.pid)) {
7322
+ ports.fs.removeRecursive(paths.headroomLockFile);
7323
+ continue;
7324
+ }
7463
7325
  }
7464
- requireKeychain();
7465
- security(["delete-generic-password", "-s", KEYCHAIN_CACHE_SERVICE, "-a", owner]);
7466
7326
  }
7327
+ if (ports.now() >= deadline) {
7328
+ const lastError = state?.lastError;
7329
+ throw new HeadroomStartError(
7330
+ `the headroom daemon did not become ready within ${String(HEADROOM_START_TIMEOUT_MS)}ms` + (lastError === void 0 ? "" : ` (last error: ${lastError})`) + (awaitingAllowlist ? " (the running daemon's allowlist does not admit this launch's front door, and it did not restart)" : "") + `. Daemon log: ${paths.headroomLogPath}`
7331
+ );
7332
+ }
7333
+ ports.sleep(HEADROOM_LAUNCHER_POLL_MS);
7334
+ }
7335
+ }
7336
+
7337
+ // src/headroom/settings.ts
7338
+ var import_node_crypto7 = require("node:crypto");
7339
+ function settingsOf(config) {
7340
+ return {
7341
+ ...config.mode === void 0 ? {} : { mode: config.mode },
7342
+ ...config.targetRatio === void 0 ? {} : { targetRatio: config.targetRatio },
7343
+ ...config.ccr === void 0 ? {} : { ccr: config.ccr },
7344
+ ...config.rolloutChannel === void 0 ? {} : { rolloutChannel: config.rolloutChannel },
7345
+ ...config.interceptToolResults === void 0 ? {} : { interceptToolResults: config.interceptToolResults },
7346
+ ...config.readMaturation === void 0 ? {} : { readMaturation: config.readMaturation }
7467
7347
  };
7468
7348
  }
7469
- function realCredentialCacheEnv(paths) {
7470
- return { port: createRealCredentialCache(paths.root), platform: process.platform, now: () => Date.now() };
7349
+ function settingsArgs(settings) {
7350
+ return [
7351
+ ...settings.mode === void 0 ? [] : ["--mode", settings.mode],
7352
+ ...settings.targetRatio === void 0 ? [] : ["--target-ratio", String(settings.targetRatio)],
7353
+ ...settings.ccr === "lossless" ? ["--lossless"] : [],
7354
+ ...settings.ccr === "none" ? ["--no-ccr"] : [],
7355
+ ...settings.interceptToolResults === true ? ["--intercept-tool-results"] : [],
7356
+ ...settings.readMaturation === true ? ["--read-maturation"] : []
7357
+ ];
7358
+ }
7359
+ function settingsEnv(settings) {
7360
+ return settings.rolloutChannel === void 0 ? {} : { HEADROOM_ROLLOUT_CHANNEL: settings.rolloutChannel };
7361
+ }
7362
+ function hashSettings(settings) {
7363
+ return (0, import_node_crypto7.createHash)("sha256").update(JSON.stringify([...settingsArgs(settings), JSON.stringify(settingsEnv(settings))])).digest("hex");
7471
7364
  }
7472
7365
 
7473
- // src/providersStore.ts
7474
- var import_node_fs5 = __toESM(require("node:fs"), 1);
7475
- var import_node_path20 = __toESM(require("node:path"), 1);
7476
-
7477
- // src/credential.ts
7478
- var CREDENTIAL_UNAVAILABLE_EXIT = 64;
7479
- var CREDENTIAL_COMMAND_TIMEOUT_MS = 1e4;
7480
- var CREDENTIAL_INTERACTIVE_TIMEOUT_MS = 12e4;
7481
- var GROUP_OTHER_PERMISSION_BITS = 63;
7482
- var PERMISSION_BITS = 511;
7366
+ // src/headroom/socket.ts
7367
+ var import_node_path18 = __toESM(require("node:path"), 1);
7368
+ var GROUP_OR_OTHER_BITS = 63;
7483
7369
  var OCTAL_RADIX = 8;
7484
- function commandArgv(source) {
7485
- if ("command" in source) {
7486
- return source.command;
7487
- }
7488
- if ("op" in source) {
7489
- return ["op", "read", source.op];
7490
- }
7491
- const account = source.keychain.account;
7492
- return ["security", "find-generic-password", "-s", source.keychain.service, ...account === void 0 ? [] : ["-a", account], "-w"];
7370
+ var MODE_DIGITS = 4;
7371
+ var HEADROOM_SOCKET_SUFFIX = ".sock";
7372
+ var HEADROOM_SOCKET_NAME = /^(\d+)\.sock$/;
7373
+ var UNIX_SOCKET_PATH_MAX_BYTES = 103;
7374
+ var PLATFORMS_WITHOUT_UNIX_SOCKETS = ["win32"];
7375
+ function headroomSocketPath(paths, supervisorPid) {
7376
+ return import_node_path18.default.join(paths.headroomSocketDir, `${String(supervisorPid)}${HEADROOM_SOCKET_SUFFIX}`);
7493
7377
  }
7494
- function isInteractiveSource(source, env) {
7495
- if ("command" in source || "keychain" in source) {
7496
- return source.interactive ?? false;
7497
- }
7498
- if ("op" in source) {
7499
- return source.interactive ?? (env.OP_SERVICE_ACCOUNT_TOKEN ?? "") === "";
7500
- }
7501
- return false;
7378
+ function platformRefusal(platform) {
7379
+ return PLATFORMS_WITHOUT_UNIX_SOCKETS.includes(platform) ? `headroom routing needs a unix domain socket, which headroom cannot serve on ${platform}; agent-shim never falls back to a TCP port for this hop` : void 0;
7502
7380
  }
7503
- function summariseSource(source) {
7504
- if ("env" in source) {
7505
- return { kind: "env", variable: source.env };
7381
+ function ownerOnlyRefusal(label, target, stat, expected, uid) {
7382
+ if (stat === void 0) {
7383
+ return `the headroom ${label} ${target} does not exist`;
7506
7384
  }
7507
- if ("file" in source) {
7508
- return { kind: "file", path: source.file };
7385
+ if (stat.kind === "symlink") {
7386
+ return `the headroom ${label} ${target} is a symlink; it must be the real ${expected === "dir" ? "directory" : "socket"}`;
7509
7387
  }
7510
- if ("command" in source) {
7511
- return { kind: "command", program: source.command[0] };
7388
+ if (stat.kind !== expected) {
7389
+ return `the headroom ${label} ${target} is not a ${expected === "dir" ? "directory" : "socket"}`;
7512
7390
  }
7513
- if ("op" in source) {
7514
- return { kind: "op", reference: source.op };
7391
+ if (stat.uid !== uid) {
7392
+ return `the headroom ${label} ${target} is owned by uid ${String(stat.uid)}, not by this user (uid ${String(uid)})`;
7515
7393
  }
7516
- if ("keychain" in source) {
7517
- return {
7518
- kind: "keychain",
7519
- service: source.keychain.service,
7520
- ...source.keychain.account === void 0 ? {} : { account: source.keychain.account }
7521
- };
7394
+ if ((stat.mode & GROUP_OR_OTHER_BITS) !== 0) {
7395
+ return `the headroom ${label} ${target} has mode ${stat.mode.toString(OCTAL_RADIX).padStart(MODE_DIGITS, "0")}, which lets other users reach it; it must be accessible to its owner only`;
7522
7396
  }
7523
- return { kind: "literal" };
7524
- }
7525
- function summariseCredential(credential) {
7526
- return {
7527
- target: credential.target ?? "bearer",
7528
- sources: credential.sources.map(summariseSource),
7529
- ...credential.cache === void 0 ? {} : { cache: credential.cache }
7530
- };
7397
+ return void 0;
7531
7398
  }
7532
- function formatSourceSummary(summary) {
7533
- switch (summary.kind) {
7534
- case "env":
7535
- return `env ${summary.variable}`;
7536
- case "file":
7537
- return `file ${summary.path}`;
7538
- case "command":
7539
- return `command ${summary.program}`;
7540
- case "op":
7541
- return `op ${summary.reference}`;
7542
- case "keychain":
7543
- return `keychain ${summary.service}${summary.account === void 0 ? "" : ` (account ${summary.account})`}`;
7544
- case "literal":
7545
- return "literal (non-secret placeholder)";
7546
- default:
7547
- return summary;
7399
+ function verifyHeadroomSocket(socketPath, ports) {
7400
+ const unsupported = platformRefusal(ports.platform);
7401
+ if (unsupported !== void 0) {
7402
+ return { refused: unsupported };
7548
7403
  }
7404
+ const uid = ports.currentUid();
7405
+ const directory = import_node_path18.default.dirname(socketPath);
7406
+ const refusal = ownerOnlyRefusal("socket directory", directory, ports.lstat(directory), "dir", uid) ?? ownerOnlyRefusal("socket", socketPath, ports.lstat(socketPath), "socket", uid);
7407
+ return refusal === void 0 ? { socketPath } : { refused: refusal };
7549
7408
  }
7550
- function describeSource(source) {
7551
- return formatSourceSummary(summariseSource(source));
7552
- }
7553
- function formatCredentialSummary(summary) {
7554
- const cache = summary.cache === void 0 ? "" : `, cached ${summary.cache.ttl === void 0 ? "with no expiry" : `for ${summary.cache.ttl}`}${summary.cache.store === void 0 ? "" : ` in the ${summary.cache.store} store`}`;
7555
- return `${summary.target} from ${summary.sources.map(formatSourceSummary).join(", then ")}${cache}`;
7556
- }
7557
- function describeCredential(credential) {
7558
- return formatCredentialSummary(summariseCredential(credential));
7559
- }
7560
- function trySource(source, params) {
7561
- const none = (reason, warn = false) => ({ ok: false, reason, warn });
7562
- if ("env" in source) {
7563
- const value = params.env[source.env];
7564
- return value === void 0 || value === "" ? none("is unset or empty") : { ok: true, token: value };
7565
- }
7566
- if ("literal" in source) {
7567
- return { ok: true, token: source.literal };
7568
- }
7569
- if ("file" in source) {
7570
- const read = params.port.readSecretFile(source.file);
7571
- if (!read.found) {
7572
- return none("does not exist");
7573
- }
7574
- if (read.mode !== void 0 && (read.mode & GROUP_OTHER_PERMISSION_BITS) !== 0) {
7575
- return none(
7576
- `is readable or writable by group or others (mode ${(read.mode & PERMISSION_BITS).toString(OCTAL_RADIX)}); run \`chmod 600 ${source.file}\``,
7577
- true
7578
- );
7579
- }
7580
- const token2 = read.content.trim();
7581
- return token2 === "" ? none("is empty") : { ok: true, token: token2 };
7582
- }
7583
- const argv = commandArgv(source);
7584
- const interactive = isInteractiveSource(source, params.env);
7585
- if (interactive && !params.port.personPresent()) {
7586
- return none("needs a person to approve it, but there is no terminal or desktop session");
7409
+ function prepareHeadroomSocket(fs12, ports, paths, supervisorPid) {
7410
+ const unsupported = platformRefusal(ports.platform);
7411
+ if (unsupported !== void 0) {
7412
+ return { refused: unsupported };
7587
7413
  }
7588
- const timeoutMs = source.timeoutMs ?? (interactive ? CREDENTIAL_INTERACTIVE_TIMEOUT_MS : CREDENTIAL_COMMAND_TIMEOUT_MS);
7589
- const result = params.port.runCommand(argv, { timeoutMs, interactive });
7590
- if (result.timedOut) {
7591
- return none(`timed out after ${String(timeoutMs)}ms`);
7414
+ const socketPath = headroomSocketPath(paths, supervisorPid);
7415
+ const length = Buffer.byteLength(socketPath, "utf8");
7416
+ if (length > UNIX_SOCKET_PATH_MAX_BYTES) {
7417
+ return {
7418
+ refused: `the headroom socket path ${socketPath} is ${String(length)} bytes, longer than the ${String(UNIX_SOCKET_PATH_MAX_BYTES)} a unix socket path can hold; set AGENT_SHIM_HOME to a shorter directory`
7419
+ };
7592
7420
  }
7593
- if (result.status !== 0) {
7594
- const stderr = result.stderr.trim();
7595
- const outcome = result.status === null ? "could not be run or was killed by a signal" : `exited with status ${String(result.status)}`;
7596
- return none(`${outcome}${stderr === "" ? "" : `: ${stderr}`}`);
7421
+ if (ports.lstat(paths.headroomSocketDir)?.kind === "symlink") {
7422
+ return { refused: `the headroom socket directory ${paths.headroomSocketDir} is a symlink; it must be the real directory` };
7597
7423
  }
7598
- const token = result.stdout.trim();
7599
- return token === "" ? none("printed no token") : { ok: true, token };
7600
- }
7601
- function warmCommandFor(subject) {
7602
- const [kind, name] = subject.split(" ");
7603
- return kind === "provider" ? `agent-shim credential warm --provider ${name ?? ""}` : `agent-shim credential warm ${name ?? ""}`;
7424
+ fs12.mkdirPrivate(paths.headroomSocketDir);
7425
+ const refusal = ownerOnlyRefusal("socket directory", paths.headroomSocketDir, ports.lstat(paths.headroomSocketDir), "dir", ports.currentUid());
7426
+ return refusal === void 0 ? { socketPath } : { refused: refusal };
7604
7427
  }
7605
- function resolveCredential(params) {
7606
- const target = params.credential.target ?? "bearer";
7607
- const cacheBlock = params.credential.cache;
7608
- const cache = cacheBlock === void 0 ? void 0 : params.port.cache;
7609
- const store = cacheBlock === void 0 || cache === void 0 ? void 0 : effectiveStore(cacheBlock, cache.platform);
7610
- if (cacheBlock !== void 0 && cache !== void 0 && store !== void 0 && params.refresh !== true) {
7611
- const entry = cache.port.read(store, params.subject);
7612
- if (entry !== void 0 && isFresh(entry, cacheBlock, cache.now())) {
7613
- return { ok: true, credential: { target, token: entry.token, source: entry.source, warnings: [], cachedAt: entry.fetchedAt } };
7428
+ function pruneStaleHeadroomSockets(fs12, paths, isRunning) {
7429
+ const removed = [];
7430
+ for (const name of fs12.readdir(paths.headroomSocketDir)) {
7431
+ const pid = HEADROOM_SOCKET_NAME.exec(name)?.[1];
7432
+ if (pid === void 0 || isRunning(Number.parseInt(pid, 10))) {
7433
+ continue;
7614
7434
  }
7435
+ fs12.removeRecursive(import_node_path18.default.join(paths.headroomSocketDir, name));
7436
+ removed.push(name);
7615
7437
  }
7616
- const attempts = [];
7617
- const warnings = [];
7618
- for (const source of params.credential.sources) {
7619
- const outcome = trySource(source, params);
7620
- if (outcome.ok) {
7621
- if (cache !== void 0 && store !== void 0) {
7622
- cache.port.write(store, params.subject, { token: outcome.token, fetchedAt: cache.now(), source });
7623
- }
7624
- return { ok: true, credential: { target, token: outcome.token, source, warnings } };
7625
- }
7626
- const line = `${describeSource(source)} ${outcome.reason}`;
7627
- attempts.push(line);
7628
- if (outcome.warn) {
7629
- warnings.push(`agent-shim: ${params.subject}: skipped ${line}`);
7630
- }
7438
+ return removed;
7439
+ }
7440
+
7441
+ // src/headroom/source.ts
7442
+ var import_zod10 = require("zod");
7443
+ var GIT_SPEC_RE = /git\+[^@\s]+(?:@([^\s#]+))?/;
7444
+ var COMMIT_SHA_RE = /^[0-9a-f]{40}$/i;
7445
+ var DirectUrlSchema = import_zod10.z.object({ vcs_info: import_zod10.z.object({ commit_id: import_zod10.z.string() }) });
7446
+ function isMovingGitSource(spec) {
7447
+ const match = GIT_SPEC_RE.exec(spec);
7448
+ if (match === null) {
7449
+ return false;
7631
7450
  }
7632
- const hint = cacheBlock === void 0 ? "" : `; fill the cache from a terminal with \`${warmCommandFor(params.subject)}\``;
7633
- return { ok: false, message: `agent-shim: ${params.subject} has no usable credential: ${attempts.join("; ")}${hint}` };
7451
+ const ref = match[1];
7452
+ return ref === void 0 || !COMMIT_SHA_RE.test(ref);
7634
7453
  }
7635
- function credentialVariables(target, token) {
7454
+ function pinnedGitCommit(spec) {
7455
+ const ref = GIT_SPEC_RE.exec(spec)?.[1];
7456
+ return ref !== void 0 && COMMIT_SHA_RE.test(ref) ? ref.toLowerCase() : void 0;
7457
+ }
7458
+
7459
+ // src/headroom/supervisor.ts
7460
+ function resolveSupervisorConfig(configured) {
7636
7461
  return {
7637
- ANTHROPIC_AUTH_TOKEN: target === "bearer" ? token : void 0,
7638
- ANTHROPIC_API_KEY: target === "apiKey" ? token : void 0,
7639
- CLAUDE_CODE_OAUTH_TOKEN: target === "oauthToken" ? token : void 0
7462
+ source: configured.source ?? HEADROOM_DEFAULT_SOURCE,
7463
+ idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES,
7464
+ settings: settingsOf(configured)
7640
7465
  };
7641
7466
  }
7642
-
7643
- // src/providersStore.ts
7644
- var ProviderNotFoundError = class extends CliError {
7645
- constructor(name) {
7646
- super(`No provider named "${name}". Run \`agent-shim provider add ${name}\` first.`);
7647
- this.name = name;
7648
- this.name = "ProviderNotFoundError";
7467
+ var HEADROOM_BACKOFF_BASE_MS = 500;
7468
+ var HEADROOM_BACKOFF_CAP_MS = 3e4;
7469
+ var HEADROOM_START_RETRY_BUDGET = 5;
7470
+ var HEADROOM_READY_TIMEOUT_MS = 15e3;
7471
+ var HEADROOM_POLL_MS = 1e3;
7472
+ var MS_PER_MINUTE2 = 6e4;
7473
+ function backoffForAttempt(attempt) {
7474
+ return Math.min(HEADROOM_BACKOFF_CAP_MS, HEADROOM_BACKOFF_BASE_MS * 2 ** (attempt - 1));
7475
+ }
7476
+ function versionSatisfies(version, source) {
7477
+ if (version === void 0) {
7478
+ return false;
7649
7479
  }
7650
- name;
7651
- };
7652
- var ProviderAlreadyExistsError = class extends CliError {
7653
- constructor(providerName) {
7654
- super(`A provider named "${providerName}" already exists.`);
7655
- this.providerName = providerName;
7656
- this.name = "ProviderAlreadyExistsError";
7480
+ const specifier = /==|>=|<=|!=|>|<|~=/.exec(source);
7481
+ if (specifier === null) {
7482
+ return true;
7657
7483
  }
7658
- providerName;
7659
- };
7660
- var InvalidProviderNameError = class extends CliError {
7661
- constructor(attemptedName) {
7662
- super(
7663
- `"${attemptedName}" is not a valid provider name. Provider names must start with a letter or number and may then contain letters, numbers, dots, hyphens, underscores, and at signs.`
7664
- );
7665
- this.attemptedName = attemptedName;
7666
- this.name = "InvalidProviderNameError";
7484
+ const installed = parseVersion(version);
7485
+ const wanted = parseVersion(source.slice(specifier.index + specifier[0].length));
7486
+ if (installed === void 0 || wanted === void 0) {
7487
+ return true;
7667
7488
  }
7668
- attemptedName;
7669
- };
7670
- var PROVIDER_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._@-]*$/;
7671
- function providerJsonPath(paths, name) {
7672
- return import_node_path20.default.join(paths.providersDir, `${name}.json`);
7673
- }
7674
- function providerExists(paths, name) {
7675
- return import_node_fs5.default.existsSync(providerJsonPath(paths, name));
7676
- }
7677
- var LEGACY_PROVIDER_FIELDS = ["tokenEnv", "tokenCommand", "authScheme"];
7678
- var LEGACY_ENV_CREDENTIAL_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
7679
- var LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
7680
- function isRecord4(value) {
7681
- return typeof value === "object" && value !== null && !Array.isArray(value);
7682
- }
7683
- function legacyProviderConversion(raw) {
7684
- if (!isRecord4(raw) || "credential" in raw) {
7685
- return void 0;
7489
+ const cmp = compareVersions2(installed, wanted);
7490
+ switch (specifier[0]) {
7491
+ case "==":
7492
+ return cmp === 0;
7493
+ case "!=":
7494
+ return cmp !== 0;
7495
+ case ">=":
7496
+ return cmp >= 0;
7497
+ case "<=":
7498
+ return cmp <= 0;
7499
+ case ">":
7500
+ return cmp > 0;
7501
+ case "<":
7502
+ return cmp < 0;
7503
+ case "~=":
7504
+ return compareVersions2(installed.slice(0, Math.max(0, wanted.length - 1)), wanted.slice(0, Math.max(0, wanted.length - 1))) === 0 && cmp >= 0;
7505
+ default:
7506
+ return true;
7686
7507
  }
7687
- const env = isRecord4(raw.env) ? raw.env : void 0;
7688
- const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
7689
- const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
7690
- if (fields.length === 0) {
7508
+ }
7509
+ function parseVersion(input) {
7510
+ const match = /(\d+)(\.\d+)*/.exec(input);
7511
+ if (match === null) {
7691
7512
  return void 0;
7692
7513
  }
7693
- const sources = [];
7694
- if (typeof raw.tokenEnv === "string") {
7695
- sources.push({ env: raw.tokenEnv });
7696
- }
7697
- if (Array.isArray(raw.tokenCommand)) {
7698
- sources.push({ command: raw.tokenCommand });
7514
+ return match[0].split(".").map((part) => Number.parseInt(part, 10));
7515
+ }
7516
+ function compareVersions2(a, b) {
7517
+ for (let index = 0; index < Math.max(a.length, b.length); index += 1) {
7518
+ const left = a[index] ?? 0;
7519
+ const right = b[index] ?? 0;
7520
+ if (left !== right) {
7521
+ return left - right;
7522
+ }
7699
7523
  }
7700
- if (typeof env?.ANTHROPIC_AUTH_TOKEN === "string" && env.ANTHROPIC_AUTH_TOKEN !== "") {
7701
- sources.push({ literal: LEGACY_LITERAL_PLACEHOLDER });
7524
+ return 0;
7525
+ }
7526
+ var HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
7527
+ async function runSupervisor(initialConfig, ports, options = {}) {
7528
+ const { fs: fs12, paths } = ports;
7529
+ let config = initialConfig;
7530
+ const previousState = readHeadroomState(fs12, paths.headroomStateFile);
7531
+ if (previousState?.supervisorPid !== void 0 && previousState.supervisorPid !== ports.ownPid && ports.isRunning(previousState.supervisorPid)) {
7532
+ ports.log(`agent-shim headroom supervisor ${String(ports.ownPid)}: supervisor ${String(previousState.supervisorPid)} already owns the daemon; exiting`);
7533
+ return 0;
7702
7534
  }
7703
- const remainingEnv = env === void 0 ? void 0 : Object.fromEntries(Object.entries(env).filter(([key]) => !LEGACY_ENV_CREDENTIAL_KEYS.includes(key)));
7704
- const replacedKeys = [...LEGACY_PROVIDER_FIELDS, "env"];
7705
- const rest = Object.fromEntries(Object.entries(raw).filter(([key]) => !replacedKeys.includes(key)));
7706
- return {
7707
- fields,
7708
- replacement: {
7709
- ...rest,
7710
- credential: { sources, ...raw.authScheme === "apiKey" ? { target: "apiKey" } : {} },
7711
- ...remainingEnv === void 0 || Object.keys(remainingEnv).length === 0 ? {} : { env: remainingEnv }
7535
+ let installedSource = previousState?.installedSource;
7536
+ const ownsState = () => {
7537
+ const owner = readHeadroomState(fs12, paths.headroomStateFile)?.supervisorPid;
7538
+ return owner === void 0 || owner === ports.ownPid || !ports.isRunning(owner);
7539
+ };
7540
+ const writeOwnState = (state) => {
7541
+ if (!ownsState()) {
7542
+ return false;
7712
7543
  }
7544
+ writeHeadroomState(fs12, paths.headroomStateFile, state);
7545
+ return true;
7713
7546
  };
7714
- }
7715
- var LegacyProviderFileError = class extends CliError {
7716
- constructor(filePath, conversion) {
7717
- super(
7718
- `${filePath} uses ${conversion.fields.join(", ")}, which a credential block replaced. Rewrite it as:
7719
- ` + JSON.stringify(conversion.replacement, null, 2)
7720
- );
7721
- this.filePath = filePath;
7722
- this.conversion = conversion;
7723
- this.name = "LegacyProviderFileError";
7547
+ const fail = (message) => {
7548
+ if (writeOwnState({ lastError: message })) {
7549
+ fs12.removeRecursive(paths.headroomLockFile);
7550
+ }
7551
+ ports.log(`agent-shim headroom supervisor: ${message}`);
7552
+ return 1;
7553
+ };
7554
+ const ensureInstalled = () => {
7555
+ const version2 = ports.headroomVersion();
7556
+ const versionOk = version2 !== void 0 && versionSatisfies(version2, config.source);
7557
+ const pinned = pinnedGitCommit(config.source);
7558
+ const commitOk = pinned === void 0 || ports.installedCommit() === pinned;
7559
+ if (versionOk && commitOk && (installedSource === void 0 || installedSource === config.source)) {
7560
+ installedSource = config.source;
7561
+ return true;
7562
+ }
7563
+ ports.log(`agent-shim headroom supervisor: installing headroom from ${config.source}`);
7564
+ const result = ports.install(config.source);
7565
+ if (!result.ok) {
7566
+ return false;
7567
+ }
7568
+ installedSource = config.source;
7569
+ return true;
7570
+ };
7571
+ const socket = prepareHeadroomSocket(fs12, ports.socketTrust, paths, ports.ownPid);
7572
+ if (socket.refused !== void 0) {
7573
+ return fail(socket.refused);
7724
7574
  }
7725
- filePath;
7726
- conversion;
7727
- };
7728
- function parseProviderFile(filePath, raw) {
7729
- const conversion = legacyProviderConversion(raw);
7730
- if (conversion !== void 0) {
7731
- throw new LegacyProviderFileError(filePath, conversion);
7575
+ const { socketPath } = socket;
7576
+ pruneStaleHeadroomSockets(fs12, paths, ports.isRunning);
7577
+ if (!ensureInstalled()) {
7578
+ return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
7732
7579
  }
7733
- const parsed = ProviderSchema.safeParse(raw);
7734
- if (!parsed.success) {
7735
- throw new ConfigValidationError(filePath, parsed.error.issues);
7580
+ const version = ports.headroomVersion() ?? "unknown";
7581
+ writeOwnState({ supervisorPid: ports.ownPid, version, installedSource });
7582
+ ports.log(`agent-shim headroom supervisor ${String(ports.ownPid)}: managing headroom on allowlist [${allowlistOf(ports).join(", ")}]`);
7583
+ const orphanPid = previousState?.headroomPid;
7584
+ if (orphanPid !== void 0 && ports.isRunning(orphanPid)) {
7585
+ ports.log(`agent-shim headroom supervisor: stopping daemon pid ${String(orphanPid)} left behind by the previous supervisor`);
7586
+ ports.stopProcess(orphanPid);
7587
+ }
7588
+ let headroomPid;
7589
+ let runningHash;
7590
+ let runningSettingsHash;
7591
+ let consecutiveFailures = 0;
7592
+ let idleSince;
7593
+ let superseded = false;
7594
+ let ticks = 0;
7595
+ for (; ; ) {
7596
+ if (options.tickLimit !== void 0 && ticks >= options.tickLimit) {
7597
+ return HEADROOM_SUPERVISOR_STILL_RUNNING;
7598
+ }
7599
+ ticks += 1;
7600
+ config = ports.readConfig();
7601
+ const settingsHash = hashSettings(config.settings);
7602
+ const allowlist = allowlistOf(ports);
7603
+ const allowlistHash = hashAllowlist(allowlist);
7604
+ pruneDeadSessions(fs12, paths.headroomSessionsDir, ports.isRunning);
7605
+ const ownsNow = ownsState();
7606
+ if (!ownsNow && !superseded) {
7607
+ ports.log("agent-shim headroom supervisor: another supervisor now owns the state file; serving this daemon's own sessions until they end");
7608
+ }
7609
+ superseded = !ownsNow;
7610
+ const crashed = headroomPid !== void 0 && !ports.isRunning(headroomPid);
7611
+ if (headroomPid === void 0 || crashed) {
7612
+ if (crashed) {
7613
+ ports.log(`agent-shim headroom supervisor: headroom pid ${String(headroomPid)} died`);
7614
+ headroomPid = void 0;
7615
+ runningHash = void 0;
7616
+ runningSettingsHash = void 0;
7617
+ writeOwnState({
7618
+ supervisorPid: ports.ownPid,
7619
+ version,
7620
+ installedSource
7621
+ });
7622
+ }
7623
+ if (consecutiveFailures >= HEADROOM_START_RETRY_BUDGET) {
7624
+ return fail(`headroom failed to become ready ${String(consecutiveFailures)} times in a row; giving up`);
7625
+ }
7626
+ if (installedSource !== config.source && !ensureInstalled()) {
7627
+ return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
7628
+ }
7629
+ const pid = ports.spawnHeadroom(socketPath, allowlist, config.settings);
7630
+ const ready = await waitUntilReady(ports, socketPath);
7631
+ if (ready) {
7632
+ consecutiveFailures = 0;
7633
+ headroomPid = pid;
7634
+ runningHash = allowlistHash;
7635
+ runningSettingsHash = settingsHash;
7636
+ const recorded = writeOwnState({
7637
+ supervisorPid: ports.ownPid,
7638
+ headroomPid: pid,
7639
+ socketPath,
7640
+ version,
7641
+ allowlistHash,
7642
+ settingsHash,
7643
+ installedSource: config.source
7644
+ });
7645
+ if (recorded) {
7646
+ fs12.removeRecursive(paths.headroomLockFile);
7647
+ }
7648
+ idleSince = void 0;
7649
+ ports.log(`agent-shim headroom supervisor: headroom pid ${String(pid)} ready on unix socket ${socketPath}`);
7650
+ } else {
7651
+ ports.stopProcess(pid);
7652
+ consecutiveFailures += 1;
7653
+ ports.log(
7654
+ `agent-shim headroom supervisor: headroom did not become ready on unix socket ${socketPath} (attempt ${String(consecutiveFailures)} of ${String(HEADROOM_START_RETRY_BUDGET)})`
7655
+ );
7656
+ await ports.sleep(backoffForAttempt(consecutiveFailures));
7657
+ continue;
7658
+ }
7659
+ } else {
7660
+ if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
7661
+ if (sessionsForSupervisor(fs12, paths.headroomSessionsDir, ports.ownPid).length === 0) {
7662
+ ports.log("agent-shim headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
7663
+ ports.stopProcess(headroomPid);
7664
+ headroomPid = void 0;
7665
+ runningHash = void 0;
7666
+ runningSettingsHash = void 0;
7667
+ continue;
7668
+ }
7669
+ }
7670
+ const sessions = sessionsForSupervisor(fs12, paths.headroomSessionsDir, ports.ownPid);
7671
+ if (sessions.length === 0) {
7672
+ idleSince ??= ports.now();
7673
+ const idleMs = ports.now() - idleSince;
7674
+ if (idleMs >= config.idleShutdownMinutes * MS_PER_MINUTE2) {
7675
+ ports.log(
7676
+ `agent-shim headroom supervisor: no sessions for ${String(config.idleShutdownMinutes)} minute(s); stopping headroom and exiting`
7677
+ );
7678
+ ports.stopProcess(headroomPid);
7679
+ writeOwnState({ version });
7680
+ return 0;
7681
+ }
7682
+ } else {
7683
+ idleSince = void 0;
7684
+ }
7685
+ }
7686
+ await ports.sleep(HEADROOM_POLL_MS);
7736
7687
  }
7737
- return parsed.data;
7738
7688
  }
7739
- function readProvider(paths, name) {
7740
- const filePath = providerJsonPath(paths, name);
7741
- if (!import_node_fs5.default.existsSync(filePath)) {
7742
- return void 0;
7689
+ function currentHeadroomAllowlist(fs12, paths) {
7690
+ const providers = readAllProviders(fs12, paths.providersDir).map((entry) => entry.provider);
7691
+ return headroomAllowlist(headroomUpstreams(providers, frontDoorOrigin(readFrontDoorState(fs12, paths.frontdoorStateFile))));
7692
+ }
7693
+ function allowlistOf(ports) {
7694
+ return currentHeadroomAllowlist(ports.fs, ports.paths);
7695
+ }
7696
+ async function waitUntilReady(ports, socketPath) {
7697
+ const deadline = ports.now() + HEADROOM_READY_TIMEOUT_MS;
7698
+ for (; ; ) {
7699
+ if (await ports.ready(socketPath)) {
7700
+ return true;
7701
+ }
7702
+ if (ports.now() >= deadline) {
7703
+ return false;
7704
+ }
7705
+ await ports.sleep(HEADROOM_POLL_MS);
7743
7706
  }
7744
- const raw = JSON.parse(import_node_fs5.default.readFileSync(filePath, "utf8"));
7745
- return parseProviderFile(filePath, raw);
7746
7707
  }
7747
- function loadProvider(providersDir, name, port) {
7748
- const filePath = import_node_path20.default.join(providersDir, `${name}.json`);
7749
- const raw = port.readConfigFile(filePath);
7750
- return raw === void 0 ? void 0 : parseProviderFile(filePath, raw);
7708
+
7709
+ // src/realPorts.ts
7710
+ function isEnoent3(error) {
7711
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
7751
7712
  }
7752
- function listProviders(paths) {
7753
- const names = listProviderNames(
7754
- (dir) => import_node_fs5.default.readdirSync(dir),
7755
- paths.providersDir
7756
- );
7757
- const result = [];
7758
- for (const name of names) {
7759
- let provider;
7713
+ var realFsPort = {
7714
+ readFileUtf8(filePath) {
7760
7715
  try {
7761
- provider = readProvider(paths, name);
7716
+ return import_node_fs3.default.readFileSync(filePath, "utf8");
7762
7717
  } catch (error) {
7763
- if (!(error instanceof ConfigValidationError || error instanceof LegacyProviderFileError)) {
7764
- throw error;
7718
+ if (isEnoent3(error)) {
7719
+ return void 0;
7765
7720
  }
7766
- console.error(`agent-shim: providers/${name}.json is invalid and was skipped: ${error.message}`);
7767
- continue;
7721
+ throw error;
7768
7722
  }
7769
- if (provider !== void 0) {
7770
- result.push({ name, provider });
7723
+ },
7724
+ readConfigFile: cosmiconfigReader(),
7725
+ readdir(dirPath) {
7726
+ try {
7727
+ return import_node_fs3.default.readdirSync(dirPath);
7728
+ } catch (error) {
7729
+ if (isEnoent3(error)) {
7730
+ return [];
7731
+ }
7732
+ throw error;
7771
7733
  }
7772
7734
  }
7773
- return result;
7735
+ };
7736
+ function isErrorWithCode(error, code) {
7737
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
7774
7738
  }
7775
- function listProviderNames(readdir, providersDir) {
7776
- let entries;
7777
- try {
7778
- entries = readdir(providersDir);
7779
- } catch {
7780
- return [];
7739
+ var OWNER_ONLY_DIR_MODE = 448;
7740
+ var OWNER_ONLY_FILE_MODE = 384;
7741
+ var PERMISSION_BITS = 511;
7742
+ var realFarmFs = {
7743
+ lstat(filePath) {
7744
+ let stat;
7745
+ try {
7746
+ stat = import_node_fs3.default.lstatSync(filePath);
7747
+ } catch (error) {
7748
+ if (isEnoent3(error)) {
7749
+ return void 0;
7750
+ }
7751
+ throw error;
7752
+ }
7753
+ const kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "dir" : "file";
7754
+ return { kind, mtimeMs: stat.mtimeMs, sizeBytes: stat.size };
7755
+ },
7756
+ readdir(dirPath) {
7757
+ try {
7758
+ return import_node_fs3.default.readdirSync(dirPath);
7759
+ } catch (error) {
7760
+ if (isEnoent3(error) || isErrorWithCode(error, "ENOTDIR")) {
7761
+ return [];
7762
+ }
7763
+ throw error;
7764
+ }
7765
+ },
7766
+ mkdirp(dirPath) {
7767
+ import_node_fs3.default.mkdirSync(dirPath, { recursive: true });
7768
+ },
7769
+ symlink(target, linkPath) {
7770
+ import_node_fs3.default.symlinkSync(target, linkPath);
7771
+ },
7772
+ rename(from, to) {
7773
+ import_node_fs3.default.renameSync(from, to);
7774
+ },
7775
+ removeRecursive(targetPath) {
7776
+ import_node_fs3.default.rmSync(targetPath, { recursive: true, force: true });
7777
+ },
7778
+ copyRecursive(from, to) {
7779
+ import_node_fs3.default.cpSync(from, to, { recursive: true, force: true, verbatimSymlinks: true });
7780
+ },
7781
+ readFileUtf8(filePath) {
7782
+ try {
7783
+ return import_node_fs3.default.readFileSync(filePath, "utf8");
7784
+ } catch (error) {
7785
+ if (isEnoent3(error)) {
7786
+ return void 0;
7787
+ }
7788
+ throw error;
7789
+ }
7790
+ },
7791
+ writeFileUtf8(filePath, contents) {
7792
+ import_node_fs3.default.writeFileSync(filePath, contents, "utf8");
7793
+ },
7794
+ mkdirPrivate(dirPath) {
7795
+ import_node_fs3.default.mkdirSync(dirPath, { recursive: true, mode: OWNER_ONLY_DIR_MODE });
7796
+ import_node_fs3.default.chmodSync(dirPath, OWNER_ONLY_DIR_MODE);
7797
+ },
7798
+ writeFilePrivate(filePath, contents) {
7799
+ const temp = `${filePath}.${String(process.pid)}.tmp`;
7800
+ import_node_fs3.default.writeFileSync(temp, contents, { encoding: "utf8", mode: OWNER_ONLY_FILE_MODE });
7801
+ import_node_fs3.default.chmodSync(temp, OWNER_ONLY_FILE_MODE);
7802
+ import_node_fs3.default.renameSync(temp, filePath);
7803
+ },
7804
+ appendFilePrivate(filePath, contents) {
7805
+ const descriptor = import_node_fs3.default.openSync(filePath, "a", OWNER_ONLY_FILE_MODE);
7806
+ try {
7807
+ if ((import_node_fs3.default.fstatSync(descriptor).mode & PERMISSION_BITS) !== OWNER_ONLY_FILE_MODE) {
7808
+ import_node_fs3.default.fchmodSync(descriptor, OWNER_ONLY_FILE_MODE);
7809
+ }
7810
+ const bytes = Buffer.from(contents, "utf8");
7811
+ let written = 0;
7812
+ while (written < bytes.length) {
7813
+ written += import_node_fs3.default.writeSync(descriptor, bytes, written, bytes.length - written);
7814
+ }
7815
+ } finally {
7816
+ import_node_fs3.default.closeSync(descriptor);
7817
+ }
7818
+ },
7819
+ writeFileExclusive(filePath, contents) {
7820
+ try {
7821
+ import_node_fs3.default.writeFileSync(filePath, contents, { encoding: "utf8", flag: "wx" });
7822
+ return true;
7823
+ } catch (error) {
7824
+ if (isErrorWithCode(error, "EEXIST")) {
7825
+ return false;
7826
+ }
7827
+ throw error;
7828
+ }
7829
+ },
7830
+ hashFile(filePath) {
7831
+ try {
7832
+ return (0, import_node_crypto8.createHash)("sha256").update(import_node_fs3.default.readFileSync(filePath)).digest("hex");
7833
+ } catch (error) {
7834
+ if (isEnoent3(error) || isErrorWithCode(error, "EISDIR")) {
7835
+ return void 0;
7836
+ }
7837
+ throw error;
7838
+ }
7781
7839
  }
7782
- return entries.filter((entry) => entry.endsWith(".json")).map((entry) => entry.slice(0, -".json".length)).sort();
7840
+ };
7841
+ var INT32_BYTE_LENGTH = 4;
7842
+ function realSleepSync(ms) {
7843
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(INT32_BYTE_LENGTH)), 0, 0, ms);
7783
7844
  }
7784
- function describeProviderEndpoint(provider) {
7785
- return isCodexProvider(provider) ? "codex translation daemon" : provider.baseUrl;
7845
+ function realIsProcessAlive(pid) {
7846
+ try {
7847
+ process.kill(pid, 0);
7848
+ return true;
7849
+ } catch (error) {
7850
+ return isErrorWithCode(error, "EPERM");
7851
+ }
7786
7852
  }
7787
- var ProviderKindMismatchError = class extends UsageError {
7788
- constructor(message) {
7789
- super(message);
7790
- this.name = "ProviderKindMismatchError";
7853
+ function realIsProcessZombie(pid) {
7854
+ const result = (0, import_node_child_process.spawnSync)("ps", ["-o", "stat=", "-p", String(pid)], { encoding: "utf8" });
7855
+ return result.status === 0 && result.stdout.trim().startsWith("Z");
7856
+ }
7857
+ function realIsProcessRunning(pid) {
7858
+ return realIsProcessAlive(pid) && !realIsProcessZombie(pid);
7859
+ }
7860
+ var realRunPort = {
7861
+ run(command, args) {
7862
+ const result = (0, import_node_child_process.spawnSync)(command, [...args], { encoding: "utf8" });
7863
+ return { status: result.status, stdout: result.stdout, stderr: result.stderr };
7791
7864
  }
7792
7865
  };
7793
- function isEmptyCodexConfig(config) {
7794
- return config === void 0 || config.defaultModel === void 0 && config.effort === void 0 && config.login === void 0 && (config.models === void 0 || Object.keys(config.models).length === 0);
7866
+ function expandHome2(filePath) {
7867
+ return filePath.startsWith("~/") ? import_node_path19.default.join(import_node_os2.default.homedir(), filePath.slice(1)) : filePath;
7795
7868
  }
7796
- function addProvider(paths, name, input) {
7797
- if (providerExists(paths, name)) {
7798
- throw new ProviderAlreadyExistsError(name);
7799
- }
7800
- if (!PROVIDER_NAME_RE.test(name)) {
7801
- throw new InvalidProviderNameError(name);
7802
- }
7803
- const kind = input.kind ?? "http";
7804
- if (kind === "codex" && input.baseUrl !== void 0) {
7805
- throw new ProviderKindMismatchError("A codex provider has no base URL: the launcher points its sessions at the front-door daemon. Drop --base-url.");
7806
- }
7807
- if (kind === "http" && input.baseUrl === void 0) {
7808
- throw new ProviderKindMismatchError("An http provider needs --base-url.");
7809
- }
7810
- if (kind === "http" && !isEmptyCodexConfig(input.codex)) {
7811
- throw new ProviderKindMismatchError("Codex settings apply only to a codex provider (--kind codex).");
7869
+ var MACOS_GUI_SESSION_MANAGER = "Aqua";
7870
+ function hasDesktopSession() {
7871
+ if (process.platform === "darwin") {
7872
+ const result = (0, import_node_child_process.spawnSync)("launchctl", ["managername"], { encoding: "utf8" });
7873
+ return result.status === 0 && result.stdout.trim() === MACOS_GUI_SESSION_MANAGER;
7812
7874
  }
7813
- const shared = {
7814
- displayName: input.displayName,
7815
- credential: { sources: input.sources, ...input.target === void 0 ? {} : { target: input.target } },
7816
- ...input.env === void 0 || Object.keys(input.env).length === 0 ? {} : { env: input.env }
7817
- };
7818
- const parsed = ProviderSchema.safeParse(
7819
- kind === "codex" ? { kind, ...shared, ...isEmptyCodexConfig(input.codex) ? {} : { codex: input.codex } } : { ...shared, baseUrl: input.baseUrl }
7820
- );
7821
- if (!parsed.success) {
7822
- throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
7875
+ if (process.platform === "win32") {
7876
+ return false;
7823
7877
  }
7824
- writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
7825
- return parsed.data;
7878
+ return (process.env.DISPLAY ?? "") !== "" || (process.env.WAYLAND_DISPLAY ?? "") !== "";
7826
7879
  }
7827
- function updateProvider(paths, name, input) {
7828
- const existing = readProvider(paths, name);
7829
- if (existing === void 0) {
7830
- throw new ProviderNotFoundError(name);
7831
- }
7832
- const unset = new Set(input.unsetEnv);
7833
- const env = Object.fromEntries(Object.entries({ ...existing.env, ...input.env }).filter(([key]) => !unset.has(key)));
7834
- const target = input.target ?? existing.credential.target;
7835
- const cache = input.cache === void 0 ? existing.credential.cache : input.cache === false ? void 0 : input.cache;
7836
- if (isCodexProvider(existing) && input.baseUrl !== void 0) {
7837
- throw new ProviderKindMismatchError(`Provider "${name}" is a codex provider, which has no base URL.`);
7838
- }
7839
- if (!isCodexProvider(existing) && !isEmptyCodexConfig(input.codex)) {
7840
- throw new ProviderKindMismatchError(`Provider "${name}" is an http provider; codex settings apply only to a codex provider.`);
7880
+ var realCredentialPort = {
7881
+ readSecretFile(filePath) {
7882
+ const resolved = expandHome2(filePath);
7883
+ let content;
7884
+ let stat;
7885
+ try {
7886
+ stat = import_node_fs3.default.statSync(resolved);
7887
+ content = import_node_fs3.default.readFileSync(resolved, "utf8");
7888
+ } catch (error) {
7889
+ if (isEnoent3(error)) {
7890
+ return { found: false };
7891
+ }
7892
+ throw error;
7893
+ }
7894
+ return { found: true, content, mode: process.platform === "win32" ? void 0 : stat.mode };
7895
+ },
7896
+ runCommand(argv, options) {
7897
+ const [command, ...args] = argv;
7898
+ const result = (0, import_node_child_process.spawnSync)(command, args, {
7899
+ encoding: "utf8",
7900
+ timeout: options.timeoutMs,
7901
+ stdio: options.interactive ? ["inherit", "pipe", "inherit"] : ["ignore", "pipe", "pipe"]
7902
+ });
7903
+ if (result.error !== void 0) {
7904
+ const timedOut = isErrorWithCode(result.error, "ETIMEDOUT");
7905
+ return { status: result.status, stdout: "", stderr: timedOut ? "" : result.error.message, timedOut };
7906
+ }
7907
+ return { status: result.status, stdout: result.stdout, stderr: options.interactive ? "" : result.stderr, timedOut: false };
7908
+ },
7909
+ personPresent: () => process.stdin.isTTY || hasDesktopSession()
7910
+ };
7911
+ function resolveGitBranch(run, cwd) {
7912
+ const result = run.run("git", ["-C", cwd, "rev-parse", "--abbrev-ref", "HEAD"]);
7913
+ if (result.status !== 0) {
7914
+ return {};
7841
7915
  }
7842
- const shared = {
7843
- ...input.displayName === void 0 ? {} : { displayName: input.displayName },
7844
- credential: {
7845
- sources: input.sources ?? existing.credential.sources,
7846
- ...target === void 0 ? {} : { target },
7847
- ...cache === void 0 ? {} : { cache }
7848
- },
7849
- env: Object.keys(env).length === 0 ? void 0 : env
7850
- };
7851
- let candidate;
7852
- if (isCodexProvider(existing)) {
7853
- const codex = {
7854
- ...existing.codex,
7855
- ...input.codex,
7856
- ...existing.codex?.models === void 0 && input.codex?.models === void 0 ? {} : { models: { ...existing.codex?.models, ...input.codex?.models } }
7857
- };
7858
- candidate = { ...existing, ...shared, codex: isEmptyCodexConfig(codex) ? void 0 : codex };
7859
- } else {
7860
- candidate = { ...existing, ...shared, ...input.baseUrl === void 0 ? {} : { baseUrl: input.baseUrl } };
7916
+ const name = result.stdout.trim();
7917
+ if (name === "") {
7918
+ return {};
7861
7919
  }
7862
- const parsed = ProviderSchema.safeParse(candidate);
7863
- if (!parsed.success) {
7864
- throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
7920
+ if (name === "HEAD") {
7921
+ return { branchDetached: true };
7865
7922
  }
7866
- writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
7867
- return parsed.data;
7923
+ return { branch: name, branchDetached: false };
7868
7924
  }
7869
- function removeProvider(paths, name) {
7870
- if (!providerExists(paths, name)) {
7871
- throw new ProviderNotFoundError(name);
7925
+ function resolveGitRoot(run, cwd) {
7926
+ const result = run.run("git", ["-C", cwd, "rev-parse", "--show-toplevel"]);
7927
+ if (result.status !== 0) {
7928
+ return void 0;
7872
7929
  }
7873
- import_node_fs5.default.rmSync(providerJsonPath(paths, name));
7930
+ const root = result.stdout.trim();
7931
+ return root === "" ? void 0 : root;
7874
7932
  }
7875
- function cascadeProviderName(cascade) {
7876
- let name;
7877
- for (const layer of assembleCascade(cascade).layers) {
7878
- if (layer.launch?.provider !== void 0) {
7879
- name = layer.launch.provider;
7933
+ function detachedDaemonSpawnOptions(logFd, env) {
7934
+ return { detached: true, stdio: ["ignore", logFd, logFd], env };
7935
+ }
7936
+ function spawnDaemonThrough(executable) {
7937
+ return (paths, subcommand, logPath) => spawnDetachedDaemon({ command: executable, args: [subcommand] }, paths, subcommand, logPath);
7938
+ }
7939
+ function spawnDaemonThroughScript(script) {
7940
+ return (paths, subcommand, logPath) => spawnDetachedDaemon({ command: process.execPath, args: [script, subcommand] }, paths, subcommand, logPath);
7941
+ }
7942
+ function spawnDetachedDaemon(invocation, paths, subcommand, logPath) {
7943
+ import_node_fs3.default.mkdirSync(import_node_path19.default.dirname(logPath), { recursive: true });
7944
+ const logFd = import_node_fs3.default.openSync(logPath, "a");
7945
+ try {
7946
+ const child = (0, import_node_child_process.spawn)(invocation.command, invocation.args, detachedDaemonSpawnOptions(logFd, { ...process.env, AGENT_SHIM_HOME: paths.root }));
7947
+ child.unref();
7948
+ if (child.pid === void 0) {
7949
+ throw new Error(`spawning ${subcommand} returned no pid`);
7880
7950
  }
7951
+ return child.pid;
7952
+ } finally {
7953
+ import_node_fs3.default.closeSync(logFd);
7881
7954
  }
7882
- return name;
7883
7955
  }
7884
- function resolveProvider(params) {
7885
- const cliName = params.cliProvider !== void 0 && params.cliProvider !== "" ? params.cliProvider : void 0;
7886
- const name = cliName ?? (params.cascade === void 0 ? void 0 : cascadeProviderName(params.cascade));
7887
- if (name === void 0) {
7888
- return void 0;
7889
- }
7890
- const definition = loadProvider(params.paths.providersDir, name, params.port);
7891
- if (definition === void 0) {
7892
- const known = listProviderNames(params.port.readdir, params.paths.providersDir);
7893
- return {
7894
- ok: false,
7895
- status: 1,
7896
- message: `agent-shim: no provider named "${name}". ` + (known.length > 0 ? `Known providers: ${known.join(", ")}.` : "No providers are defined yet; run `agent-shim provider add`.")
7897
- };
7898
- }
7899
- if (params.credentials === void 0) {
7900
- return { ok: false, status: 1, message: `agent-shim: provider ${name} needs its credential resolved, but this launcher has no credential port wired` };
7901
- }
7902
- const resolution = resolveCredential({ credential: definition.credential, env: params.env, port: params.credentials, subject: `provider ${name}` });
7903
- if (!resolution.ok) {
7904
- return { ok: false, status: CREDENTIAL_UNAVAILABLE_EXIT, message: resolution.message };
7905
- }
7906
- return { ok: true, provider: { name, definition, credential: resolution.credential }, warnings: resolution.credential.warnings };
7956
+ function spawnHeadroomSupervisor(paths, spawnDaemon) {
7957
+ return spawnDaemon(paths, "__headroom-supervisor", paths.headroomLogPath);
7907
7958
  }
7908
-
7909
- // src/realPorts.ts
7910
- var import_node_child_process2 = require("node:child_process");
7911
- var import_node_crypto8 = require("node:crypto");
7912
- var import_node_fs6 = __toESM(require("node:fs"), 1);
7913
- var import_node_os3 = __toESM(require("node:os"), 1);
7914
- var import_node_path22 = __toESM(require("node:path"), 1);
7915
- var import_node_sea = require("node:sea");
7916
-
7917
- // src/headroom/ensure.ts
7918
- var HEADROOM_START_TIMEOUT_MS = 12e4;
7919
- var HEADROOM_LAUNCHER_POLL_MS = 250;
7920
- var HeadroomStartError = class extends CliError {
7921
- constructor(message) {
7922
- super(message);
7923
- this.name = "HeadroomStartError";
7924
- }
7925
- };
7926
- function ensureHeadroom(params) {
7927
- const { paths, ports } = params;
7928
- const deadline = ports.now() + HEADROOM_START_TIMEOUT_MS;
7929
- let spawnedSupervisor = false;
7930
- let awaitingAllowlist = false;
7931
- for (; ; ) {
7932
- const state = readHeadroomState(ports.fs, paths.headroomStateFile);
7933
- if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
7934
- const daemonUp = state.socketPath !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
7935
- if (daemonUp && state.socketPath !== void 0) {
7936
- const required = params.requiredAllowlistHash?.();
7937
- if (required === void 0 || state.allowlistHash === required) {
7938
- writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
7939
- return { socketPath: state.socketPath };
7940
- }
7941
- const holders = sessionsForSupervisor(ports.fs, paths.headroomSessionsDir, state.supervisorPid).filter((session) => ports.isRunning(session.pid));
7942
- if (holders.length > 0) {
7943
- throw new HeadroomStartError(
7944
- `the running headroom daemon was started before this launch's front door address and would refuse every request this launch sends through it. It restarts once the launches using it end (pid ${holders.map((session) => String(session.pid)).join(", ")}); end them and launch again, or launch with --no-headroom.`
7945
- );
7946
- }
7947
- awaitingAllowlist = true;
7948
- }
7949
- if (state.lastError !== void 0) {
7950
- throw new HeadroomStartError(
7951
- `the headroom daemon reported a fatal error and is not serving: ${state.lastError} (daemon log: ${paths.headroomLogPath})`
7952
- );
7953
- }
7954
- } else if (!spawnedSupervisor) {
7955
- ports.fs.mkdirp(paths.headroomDir);
7956
- const created = ports.fs.writeFileExclusive(
7957
- paths.headroomLockFile,
7958
- `${JSON.stringify({ pid: params.launcherPid, at: ports.now() })}
7959
- `
7960
- );
7961
- if (created) {
7962
- ports.spawnSupervisor(paths);
7963
- spawnedSupervisor = true;
7964
- } else {
7965
- const lock = readStartLock(ports.fs, paths.headroomLockFile);
7966
- if (lock === void 0 || !ports.isRunning(lock.pid)) {
7967
- ports.fs.removeRecursive(paths.headroomLockFile);
7968
- continue;
7969
- }
7970
- }
7959
+ var SOCKET_PERMISSION_BITS = 511;
7960
+ var realHeadroomSocketTrust = {
7961
+ platform: process.platform,
7962
+ currentUid: () => {
7963
+ if (process.getuid === void 0) {
7964
+ throw new Error(`no POSIX user id on ${process.platform}`);
7971
7965
  }
7972
- if (ports.now() >= deadline) {
7973
- const lastError = state?.lastError;
7974
- throw new HeadroomStartError(
7975
- `the headroom daemon did not become ready within ${String(HEADROOM_START_TIMEOUT_MS)}ms` + (lastError === void 0 ? "" : ` (last error: ${lastError})`) + (awaitingAllowlist ? " (the running daemon's allowlist does not admit this launch's front door, and it did not restart)" : "") + `. Daemon log: ${paths.headroomLogPath}`
7976
- );
7966
+ return process.getuid();
7967
+ },
7968
+ lstat: (target) => {
7969
+ let stat;
7970
+ try {
7971
+ stat = import_node_fs3.default.lstatSync(target);
7972
+ } catch (error) {
7973
+ if (isEnoent3(error)) {
7974
+ return void 0;
7975
+ }
7976
+ throw error;
7977
7977
  }
7978
- ports.sleep(HEADROOM_LAUNCHER_POLL_MS);
7978
+ const kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "dir" : stat.isSocket() ? "socket" : "other";
7979
+ return { kind, uid: stat.uid, mode: stat.mode & SOCKET_PERMISSION_BITS };
7979
7980
  }
7980
- }
7981
-
7982
- // src/headroom/settings.ts
7983
- var import_node_crypto7 = require("node:crypto");
7984
- function settingsOf(config) {
7981
+ };
7982
+ function realHeadroomPort(paths, options) {
7985
7983
  return {
7986
- ...config.mode === void 0 ? {} : { mode: config.mode },
7987
- ...config.targetRatio === void 0 ? {} : { targetRatio: config.targetRatio },
7988
- ...config.ccr === void 0 ? {} : { ccr: config.ccr },
7989
- ...config.rolloutChannel === void 0 ? {} : { rolloutChannel: config.rolloutChannel },
7990
- ...config.interceptToolResults === void 0 ? {} : { interceptToolResults: config.interceptToolResults },
7991
- ...config.readMaturation === void 0 ? {} : { readMaturation: config.readMaturation }
7984
+ ensure: ({ routesProvider }) => {
7985
+ const up = ensureHeadroom({
7986
+ paths,
7987
+ launcherPid: process.pid,
7988
+ ...routesProvider ? { requiredAllowlistHash: () => hashAllowlist(currentHeadroomAllowlist(realFarmFs, paths)) } : {},
7989
+ ports: {
7990
+ fs: realFarmFs,
7991
+ // Zombie-aware on purpose: a supervisor that died while still a child of this launcher sits unreaped until the launcher itself exits, and a defunct supervisor answering signal 0 as alive would stretch every launch to the full start timeout.
7992
+ isRunning: realIsProcessRunning,
7993
+ now: () => Date.now(),
7994
+ sleep: realSleepSync,
7995
+ spawnSupervisor: (layout) => spawnHeadroomSupervisor(layout, options.spawnDaemon)
7996
+ }
7997
+ });
7998
+ return {
7999
+ socketPath: up.socketPath,
8000
+ projectId: resolveGitRoot(realRunPort, options.cwd) ?? options.cwd
8001
+ };
8002
+ },
8003
+ release: () => {
8004
+ removeSession(realFarmFs, paths.headroomSessionsDir, process.pid);
8005
+ }
7992
8006
  };
7993
8007
  }
7994
- function settingsArgs(settings) {
7995
- return [
7996
- ...settings.mode === void 0 ? [] : ["--mode", settings.mode],
7997
- ...settings.targetRatio === void 0 ? [] : ["--target-ratio", String(settings.targetRatio)],
7998
- ...settings.ccr === "lossless" ? ["--lossless"] : [],
7999
- ...settings.ccr === "none" ? ["--no-ccr"] : [],
8000
- ...settings.interceptToolResults === true ? ["--intercept-tool-results"] : [],
8001
- ...settings.readMaturation === true ? ["--read-maturation"] : []
8002
- ];
8008
+ var realProcPort = {
8009
+ env: process.env,
8010
+ argv: process.argv.slice(2),
8011
+ exit: (code) => process.exit(code)
8012
+ };
8013
+ var EXECUTE_BITS_MASK = 73;
8014
+ function listVersionsDir(dir) {
8015
+ let entries;
8016
+ try {
8017
+ entries = import_node_fs3.default.readdirSync(dir, { withFileTypes: true });
8018
+ } catch (error) {
8019
+ if (isEnoent3(error)) {
8020
+ return [];
8021
+ }
8022
+ throw error;
8023
+ }
8024
+ return entries.filter((entry) => entry.isFile()).map((entry) => {
8025
+ const fullPath = import_node_path19.default.join(dir, entry.name);
8026
+ const stat = import_node_fs3.default.statSync(fullPath);
8027
+ return {
8028
+ name: entry.name,
8029
+ isFile: true,
8030
+ isExecutable: (stat.mode & EXECUTE_BITS_MASK) !== 0,
8031
+ sizeBytes: stat.size
8032
+ };
8033
+ });
8003
8034
  }
8004
- function settingsEnv(settings) {
8005
- return settings.rolloutChannel === void 0 ? {} : { HEADROOM_ROLLOUT_CHANNEL: settings.rolloutChannel };
8035
+ var WINDOWS_DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.PS1";
8036
+ function windowsPathExtensions(pathext) {
8037
+ return (pathext ?? WINDOWS_DEFAULT_PATHEXT).split(";").map((ext) => ext.trim()).filter((ext) => ext.length > 0);
8006
8038
  }
8007
- function hashSettings(settings) {
8008
- return (0, import_node_crypto7.createHash)("sha256").update(JSON.stringify([...settingsArgs(settings), JSON.stringify(settingsEnv(settings))])).digest("hex");
8039
+ function resolveExecutableCandidate(dir, name, env) {
8040
+ const candidate = import_node_path19.default.join(dir, name);
8041
+ if (env.platform === "win32") {
8042
+ const extensions = windowsPathExtensions(env.pathext);
8043
+ const alreadyHasRecognisedExtension = extensions.some((ext) => candidate.toLowerCase().endsWith(ext.toLowerCase()));
8044
+ if (alreadyHasRecognisedExtension) {
8045
+ return env.statFileMode(candidate) !== void 0 ? candidate : void 0;
8046
+ }
8047
+ for (const ext of extensions) {
8048
+ const withExt = candidate + ext;
8049
+ if (env.statFileMode(withExt) !== void 0) {
8050
+ return withExt;
8051
+ }
8052
+ }
8053
+ return void 0;
8054
+ }
8055
+ const mode = env.statFileMode(candidate);
8056
+ return mode !== void 0 && (mode & EXECUTE_BITS_MASK) !== 0 ? candidate : void 0;
8009
8057
  }
8010
-
8011
- // src/headroom/socket.ts
8012
- var import_node_path21 = __toESM(require("node:path"), 1);
8013
- var GROUP_OR_OTHER_BITS = 63;
8014
- var OCTAL_RADIX2 = 8;
8015
- var MODE_DIGITS = 4;
8016
- var HEADROOM_SOCKET_SUFFIX = ".sock";
8017
- var HEADROOM_SOCKET_NAME = /^(\d+)\.sock$/;
8018
- var UNIX_SOCKET_PATH_MAX_BYTES = 103;
8019
- var PLATFORMS_WITHOUT_UNIX_SOCKETS = ["win32"];
8020
- function headroomSocketPath(paths, supervisorPid) {
8021
- return import_node_path21.default.join(paths.headroomSocketDir, `${String(supervisorPid)}${HEADROOM_SOCKET_SUFFIX}`);
8058
+ function realStatFileMode(candidate) {
8059
+ try {
8060
+ const stat = import_node_fs3.default.statSync(candidate);
8061
+ return stat.isFile() ? stat.mode : void 0;
8062
+ } catch (error) {
8063
+ if (isEnoent3(error)) {
8064
+ return void 0;
8065
+ }
8066
+ throw error;
8067
+ }
8022
8068
  }
8023
- function platformRefusal(platform) {
8024
- return PLATFORMS_WITHOUT_UNIX_SOCKETS.includes(platform) ? `headroom routing needs a unix domain socket, which headroom cannot serve on ${platform}; agent-shim never falls back to a TCP port for this hop` : void 0;
8069
+ function findExecutableInDir(dir, name) {
8070
+ return resolveExecutableCandidate(dir, name, {
8071
+ platform: process.platform,
8072
+ pathext: process.env.PATHEXT,
8073
+ statFileMode: realStatFileMode
8074
+ });
8025
8075
  }
8026
- function ownerOnlyRefusal(label, target, stat, expected, uid) {
8027
- if (stat === void 0) {
8028
- return `the headroom ${label} ${target} does not exist`;
8029
- }
8030
- if (stat.kind === "symlink") {
8031
- return `the headroom ${label} ${target} is a symlink; it must be the real ${expected === "dir" ? "directory" : "socket"}`;
8032
- }
8033
- if (stat.kind !== expected) {
8034
- return `the headroom ${label} ${target} is not a ${expected === "dir" ? "directory" : "socket"}`;
8035
- }
8036
- if (stat.uid !== uid) {
8037
- return `the headroom ${label} ${target} is owned by uid ${String(stat.uid)}, not by this user (uid ${String(uid)})`;
8038
- }
8039
- if ((stat.mode & GROUP_OR_OTHER_BITS) !== 0) {
8040
- return `the headroom ${label} ${target} has mode ${stat.mode.toString(OCTAL_RADIX2).padStart(MODE_DIGITS, "0")}, which lets other users reach it; it must be accessible to its owner only`;
8076
+ function searchPathForExecutable(pathDirs, name, findExecutable) {
8077
+ for (const dir of pathDirs) {
8078
+ const found = findExecutable(dir, name);
8079
+ if (found !== void 0) {
8080
+ return found;
8081
+ }
8041
8082
  }
8042
8083
  return void 0;
8043
8084
  }
8044
- function verifyHeadroomSocket(socketPath, ports) {
8045
- const unsupported = platformRefusal(ports.platform);
8046
- if (unsupported !== void 0) {
8047
- return { refused: unsupported };
8085
+ function resolveOwnExecutablePath(env) {
8086
+ if (env.argv1 !== void 0 && !env.argv1.includes("/") && !env.argv1.includes("\\")) {
8087
+ return searchPathForExecutable(env.pathDirs, env.argv1, env.findExecutableInDir) ?? env.execPath;
8048
8088
  }
8049
- const uid = ports.currentUid();
8050
- const directory = import_node_path21.default.dirname(socketPath);
8051
- const refusal = ownerOnlyRefusal("socket directory", directory, ports.lstat(directory), "dir", uid) ?? ownerOnlyRefusal("socket", socketPath, ports.lstat(socketPath), "socket", uid);
8052
- return refusal === void 0 ? { socketPath } : { refused: refusal };
8053
- }
8054
- function prepareHeadroomSocket(fs12, ports, paths, supervisorPid) {
8055
- const unsupported = platformRefusal(ports.platform);
8056
- if (unsupported !== void 0) {
8057
- return { refused: unsupported };
8058
- }
8059
- const socketPath = headroomSocketPath(paths, supervisorPid);
8060
- const length = Buffer.byteLength(socketPath, "utf8");
8061
- if (length > UNIX_SOCKET_PATH_MAX_BYTES) {
8062
- return {
8063
- refused: `the headroom socket path ${socketPath} is ${String(length)} bytes, longer than the ${String(UNIX_SOCKET_PATH_MAX_BYTES)} a unix socket path can hold; set AGENT_SHIM_HOME to a shorter directory`
8064
- };
8065
- }
8066
- if (ports.lstat(paths.headroomSocketDir)?.kind === "symlink") {
8067
- return { refused: `the headroom socket directory ${paths.headroomSocketDir} is a symlink; it must be the real directory` };
8089
+ const candidate = env.argv1 === void 0 ? env.execPath : import_node_path19.default.resolve(env.cwd, env.argv1);
8090
+ const candidateDir = import_node_path19.default.dirname(candidate);
8091
+ const candidateDirOnPath = env.pathDirs.some((dir) => import_node_path19.default.resolve(dir) === candidateDir);
8092
+ if (candidateDirOnPath) {
8093
+ return candidate;
8068
8094
  }
8069
- fs12.mkdirPrivate(paths.headroomSocketDir);
8070
- const refusal = ownerOnlyRefusal("socket directory", paths.headroomSocketDir, ports.lstat(paths.headroomSocketDir), "dir", ports.currentUid());
8071
- return refusal === void 0 ? { socketPath } : { refused: refusal };
8095
+ const viaPath = searchPathForExecutable(env.pathDirs, import_node_path19.default.basename(candidate), env.findExecutableInDir);
8096
+ return viaPath ?? candidate;
8072
8097
  }
8073
- function pruneStaleHeadroomSockets(fs12, paths, isRunning) {
8074
- const removed = [];
8075
- for (const name of fs12.readdir(paths.headroomSocketDir)) {
8076
- const pid = HEADROOM_SOCKET_NAME.exec(name)?.[1];
8077
- if (pid === void 0 || isRunning(Number.parseInt(pid, 10))) {
8078
- continue;
8098
+ function realOwnExecutablePath() {
8099
+ return resolveOwnExecutablePath({
8100
+ argv1: process.argv[1],
8101
+ execPath: process.execPath,
8102
+ cwd: process.cwd(),
8103
+ pathDirs: (process.env.PATH ?? "").split(import_node_path19.default.delimiter).filter((dir) => dir !== ""),
8104
+ findExecutableInDir
8105
+ });
8106
+ }
8107
+ function resolveContentSourcePath(env) {
8108
+ return env.isSea ? env.execPath : env.argv1 ?? env.execPath;
8109
+ }
8110
+ function realContentSourcePath() {
8111
+ return resolveContentSourcePath({ isSea: (0, import_node_sea.isSea)(), execPath: process.execPath, argv1: process.argv[1] });
8112
+ }
8113
+ function realResolveClaudeBinary(isOwnBinary) {
8114
+ return (request4) => discoverClaudeBinary({
8115
+ listVersionsDir,
8116
+ pathDirs: (process.env.PATH ?? "").split(import_node_path19.default.delimiter).filter((dir) => dir !== ""),
8117
+ findExecutableInDir,
8118
+ isOwnBinary,
8119
+ ...request4?.version === void 0 ? {} : { version: request4.version }
8120
+ });
8121
+ }
8122
+ function realInstalledClaudeVersions() {
8123
+ return installedVersions(listVersionsDir(defaultVersionsDir()));
8124
+ }
8125
+
8126
+ // src/launchSelection.ts
8127
+ function selectLaunch(params, loaded) {
8128
+ const { paths, cwd } = params;
8129
+ const selections = readDirectorySelections(loaded);
8130
+ const decidedIdentity = decideIdentity({
8131
+ env: params.env,
8132
+ argv0Identity: params.identity,
8133
+ directoryPinnedIdentity: selections.identity,
8134
+ readActiveIdentityFile: () => {
8135
+ const raw = realFsPort.readFileUtf8(paths.activeIdentityFile);
8136
+ if (raw === void 0) {
8137
+ return void 0;
8138
+ }
8139
+ const trimmed = raw.trim();
8140
+ return trimmed === "" ? void 0 : trimmed;
8079
8141
  }
8080
- fs12.removeRecursive(import_node_path21.default.join(paths.headroomSocketDir, name));
8081
- removed.push(name);
8142
+ });
8143
+ let poolPick;
8144
+ if (decidedIdentity.pool !== void 0) {
8145
+ const pools = loaded.globalConfig?.pools ?? {};
8146
+ if (pools[decidedIdentity.pool] === void 0) {
8147
+ throw new PoolNotFoundError(decidedIdentity.pool);
8148
+ }
8149
+ poolPick = collectPoolPick({ paths, fs: realFsPort, usageFs: realFarmFs, poolName: decidedIdentity.pool, pools, directory: cwd, nowMs: Date.now() });
8082
8150
  }
8083
- return removed;
8151
+ const identity = poolPick?.pick === void 0 ? decidedIdentity : { ...decidedIdentity, name: poolPick.pick };
8152
+ const loadedIdentity = identity.name === void 0 ? void 0 : loadIdentity(paths.identitiesDir, identity.name, realFsPort);
8153
+ const configProfile = decideConfigProfile({
8154
+ env: params.env,
8155
+ ...params.configProfile === void 0 ? {} : { cliFlagConfigProfile: params.configProfile },
8156
+ directoryRuleConfigProfile: selections.configProfile,
8157
+ identityDefaultConfigProfile: loadedIdentity?.config.defaultConfigProfile,
8158
+ globalDefaultConfigProfile: loaded.globalConfig?.defaultConfigProfile
8159
+ });
8160
+ return {
8161
+ selection: { identity, ...poolPick === void 0 ? {} : { poolPick }, configProfile },
8162
+ ...loadedIdentity === void 0 ? {} : { identityConfig: loadedIdentity.config }
8163
+ };
8164
+ }
8165
+ function resolveLaunchSelection(params) {
8166
+ const loaded = loadCascadeInput({ paths: params.paths, home: import_node_os3.default.homedir(), cwd: params.cwd, read: cosmiconfigReader() });
8167
+ return selectLaunch(params, loaded).selection;
8084
8168
  }
8085
8169
 
8086
- // src/headroom/source.ts
8087
- var import_zod11 = require("zod");
8088
- var GIT_SPEC_RE = /git\+[^@\s]+(?:@([^\s#]+))?/;
8089
- var COMMIT_SHA_RE = /^[0-9a-f]{40}$/i;
8090
- var DirectUrlSchema = import_zod11.z.object({ vcs_info: import_zod11.z.object({ commit_id: import_zod11.z.string() }) });
8091
- function isMovingGitSource(spec) {
8092
- const match = GIT_SPEC_RE.exec(spec);
8093
- if (match === null) {
8094
- return false;
8170
+ // src/paths.ts
8171
+ var import_node_fs4 = __toESM(require("node:fs"), 1);
8172
+ var import_node_os4 = __toESM(require("node:os"), 1);
8173
+ var import_node_path20 = __toESM(require("node:path"), 1);
8174
+ function resolveAgentShimHome(env = process.env, home = import_node_os4.default.homedir(), isDirectory = isExistingDirectory) {
8175
+ const fromEnv = env.AGENT_SHIM_HOME;
8176
+ if (fromEnv !== void 0 && fromEnv !== "") {
8177
+ return fromEnv;
8095
8178
  }
8096
- const ref = match[1];
8097
- return ref === void 0 || !COMMIT_SHA_RE.test(ref);
8179
+ const current = import_node_path20.default.join(home, HOME_DIRNAME);
8180
+ const legacy = import_node_path20.default.join(home, LEGACY_HOME_DIRNAME);
8181
+ return !isDirectory(current) && isDirectory(legacy) ? legacy : current;
8098
8182
  }
8099
- function pinnedGitCommit(spec) {
8100
- const ref = GIT_SPEC_RE.exec(spec)?.[1];
8101
- return ref !== void 0 && COMMIT_SHA_RE.test(ref) ? ref.toLowerCase() : void 0;
8183
+ function isExistingDirectory(candidate) {
8184
+ return import_node_fs4.default.statSync(candidate, { throwIfNoEntry: false })?.isDirectory() ?? false;
8102
8185
  }
8103
-
8104
- // src/headroom/supervisor.ts
8105
- function resolveSupervisorConfig(configured) {
8186
+ function resolveClaudeHome() {
8187
+ const fromEnv = process.env.AGENT_SHIM_CLAUDE_HOME;
8188
+ if (fromEnv !== void 0 && fromEnv !== "") {
8189
+ return fromEnv;
8190
+ }
8191
+ return import_node_path20.default.join(import_node_os4.default.homedir(), ".claude");
8192
+ }
8193
+ function buildLayoutPaths(root) {
8106
8194
  return {
8107
- source: configured.source ?? HEADROOM_DEFAULT_SOURCE,
8108
- idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES,
8109
- settings: settingsOf(configured)
8195
+ root,
8196
+ identitiesDir: import_node_path20.default.join(root, "identities"),
8197
+ configProfilesDir: import_node_path20.default.join(root, "config-profiles"),
8198
+ providersDir: import_node_path20.default.join(root, "providers"),
8199
+ directoryRulesFile: import_node_path20.default.join(root, "directory-rules.json"),
8200
+ activeIdentityFile: import_node_path20.default.join(root, "active-identity"),
8201
+ globalConfigFile: import_node_path20.default.join(root, "config.json"),
8202
+ categoriesLocalFile: import_node_path20.default.join(root, "categories.local.json"),
8203
+ claudeShimFile: import_node_path20.default.join(root, "claude-shim.json"),
8204
+ headroomDir: import_node_path20.default.join(root, "headroom"),
8205
+ headroomStateFile: import_node_path20.default.join(root, "headroom", `state.v${String(HEADROOM_STATE_SCHEMA_VERSION)}.json`),
8206
+ headroomSocketDir: import_node_path20.default.join(root, "headroom", "run"),
8207
+ headroomLockFile: import_node_path20.default.join(root, "headroom", "start.lock"),
8208
+ headroomSessionsDir: import_node_path20.default.join(root, "headroom", "sessions"),
8209
+ logsDir: import_node_path20.default.join(root, "logs"),
8210
+ headroomLogPath: import_node_path20.default.join(root, "logs", "headroom.log"),
8211
+ frontdoorDir: import_node_path20.default.join(root, "frontdoor"),
8212
+ frontdoorCaDir: import_node_path20.default.join(root, "frontdoor", "ca"),
8213
+ frontdoorCaCertFile: import_node_path20.default.join(root, "frontdoor", "ca", "ca.pem"),
8214
+ frontdoorCaKeyFile: import_node_path20.default.join(root, "frontdoor", "ca", "ca.key"),
8215
+ frontdoorCaBundlesDir: import_node_path20.default.join(root, "frontdoor", "ca", "bundles"),
8216
+ chatgptSignInFile: import_node_path20.default.join(root, "codex", "chatgpt-sign-in.json"),
8217
+ frontdoorStateFile: import_node_path20.default.join(root, "frontdoor", "state.json"),
8218
+ frontdoorLockFile: import_node_path20.default.join(root, "frontdoor", "start.lock"),
8219
+ frontdoorControlTokenFile: import_node_path20.default.join(root, "frontdoor", "control-token"),
8220
+ frontdoorSessionsDir: import_node_path20.default.join(root, "frontdoor", "sessions"),
8221
+ frontdoorRcCredentialsDir: import_node_path20.default.join(root, "frontdoor", "rc-credentials"),
8222
+ frontdoorLogPath: import_node_path20.default.join(root, "logs", "frontdoor.log"),
8223
+ usageDir: import_node_path20.default.join(root, "usage"),
8224
+ usageLogDir: import_node_path20.default.join(root, "usage", "log"),
8225
+ usageSnapshotsDir: import_node_path20.default.join(root, "usage", "snapshots"),
8226
+ usagePicksFile: import_node_path20.default.join(root, "usage", "picks.json")
8110
8227
  };
8111
8228
  }
8112
- var HEADROOM_BACKOFF_BASE_MS = 500;
8113
- var HEADROOM_BACKOFF_CAP_MS = 3e4;
8114
- var HEADROOM_START_RETRY_BUDGET = 5;
8115
- var HEADROOM_READY_TIMEOUT_MS = 15e3;
8116
- var HEADROOM_POLL_MS = 1e3;
8117
- var MS_PER_MINUTE2 = 6e4;
8118
- function backoffForAttempt(attempt) {
8119
- return Math.min(HEADROOM_BACKOFF_CAP_MS, HEADROOM_BACKOFF_BASE_MS * 2 ** (attempt - 1));
8229
+ function resolveLayoutPaths() {
8230
+ return buildLayoutPaths(resolveAgentShimHome());
8120
8231
  }
8121
- function versionSatisfies(version, source) {
8122
- if (version === void 0) {
8123
- return false;
8124
- }
8125
- const specifier = /==|>=|<=|!=|>|<|~=/.exec(source);
8126
- if (specifier === null) {
8127
- return true;
8128
- }
8129
- const installed = parseVersion(version);
8130
- const wanted = parseVersion(source.slice(specifier.index + specifier[0].length));
8131
- if (installed === void 0 || wanted === void 0) {
8132
- return true;
8133
- }
8134
- const cmp = compareVersions2(installed, wanted);
8135
- switch (specifier[0]) {
8136
- case "==":
8137
- return cmp === 0;
8138
- case "!=":
8139
- return cmp !== 0;
8140
- case ">=":
8141
- return cmp >= 0;
8142
- case "<=":
8143
- return cmp <= 0;
8144
- case ">":
8145
- return cmp > 0;
8146
- case "<":
8147
- return cmp < 0;
8148
- case "~=":
8149
- return compareVersions2(installed.slice(0, Math.max(0, wanted.length - 1)), wanted.slice(0, Math.max(0, wanted.length - 1))) === 0 && cmp >= 0;
8150
- default:
8151
- return true;
8152
- }
8232
+
8233
+ // src/realCredentialCache.ts
8234
+ var import_node_child_process2 = require("node:child_process");
8235
+ var import_node_fs5 = __toESM(require("node:fs"), 1);
8236
+ var import_node_path21 = __toESM(require("node:path"), 1);
8237
+
8238
+ // src/credentialCache.ts
8239
+ var import_zod11 = require("zod");
8240
+ var MS_PER_UNIT = { s: 1e3, m: 6e4, h: 36e5, d: 864e5 };
8241
+ var CachedCredentialSchema = import_zod11.z.strictObject({
8242
+ token: import_zod11.z.string().min(1),
8243
+ fetchedAt: import_zod11.z.number().int().nonnegative(),
8244
+ source: CredentialSourceSchema
8245
+ });
8246
+ function effectiveStore(cache, platform) {
8247
+ return cache.store ?? (platform === "darwin" ? "keychain" : "file");
8153
8248
  }
8154
- function parseVersion(input) {
8155
- const match = /(\d+)(\.\d+)*/.exec(input);
8156
- if (match === null) {
8249
+ function ttlMs(cache) {
8250
+ if (cache.ttl === void 0) {
8157
8251
  return void 0;
8158
8252
  }
8159
- return match[0].split(".").map((part) => Number.parseInt(part, 10));
8253
+ const multiplier = MS_PER_UNIT[cache.ttl.slice(-1)];
8254
+ if (multiplier === void 0) {
8255
+ throw new Error(`credential cache ttl "${cache.ttl}" has no recognised unit`);
8256
+ }
8257
+ return Number(cache.ttl.slice(0, -1)) * multiplier;
8160
8258
  }
8161
- function compareVersions2(a, b) {
8162
- for (let index = 0; index < Math.max(a.length, b.length); index += 1) {
8163
- const left = a[index] ?? 0;
8164
- const right = b[index] ?? 0;
8165
- if (left !== right) {
8166
- return left - right;
8167
- }
8259
+ function isFresh(entry, cache, now) {
8260
+ const limit = ttlMs(cache);
8261
+ if (entry.fetchedAt > now) {
8262
+ return false;
8168
8263
  }
8169
- return 0;
8264
+ return limit === void 0 || now - entry.fetchedAt < limit;
8170
8265
  }
8171
- var HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
8172
- async function runSupervisor(initialConfig, ports, options = {}) {
8173
- const { fs: fs12, paths } = ports;
8174
- let config = initialConfig;
8175
- const previousState = readHeadroomState(fs12, paths.headroomStateFile);
8176
- if (previousState?.supervisorPid !== void 0 && previousState.supervisorPid !== ports.ownPid && ports.isRunning(previousState.supervisorPid)) {
8177
- ports.log(`agent-shim headroom supervisor ${String(ports.ownPid)}: supervisor ${String(previousState.supervisorPid)} already owns the daemon; exiting`);
8178
- return 0;
8266
+ function cacheAgeMs(entry, now) {
8267
+ return Math.max(0, now - entry.fetchedAt);
8268
+ }
8269
+ function describeCachedCredential(params) {
8270
+ const store = effectiveStore(params.cache, params.platform);
8271
+ let entry;
8272
+ try {
8273
+ entry = params.port.read(store, params.owner);
8274
+ } catch (error) {
8275
+ return { store, status: "unreadable", reason: error instanceof Error ? error.message : String(error) };
8179
8276
  }
8180
- let installedSource = previousState?.installedSource;
8181
- const ownsState = () => {
8182
- const owner = readHeadroomState(fs12, paths.headroomStateFile)?.supervisorPid;
8183
- return owner === void 0 || owner === ports.ownPid || !ports.isRunning(owner);
8184
- };
8185
- const writeOwnState = (state) => {
8186
- if (!ownsState()) {
8187
- return false;
8188
- }
8189
- writeHeadroomState(fs12, paths.headroomStateFile, state);
8190
- return true;
8191
- };
8192
- const fail = (message) => {
8193
- if (writeOwnState({ lastError: message })) {
8194
- fs12.removeRecursive(paths.headroomLockFile);
8195
- }
8196
- ports.log(`agent-shim headroom supervisor: ${message}`);
8197
- return 1;
8198
- };
8199
- const ensureInstalled = () => {
8200
- const version2 = ports.headroomVersion();
8201
- const versionOk = version2 !== void 0 && versionSatisfies(version2, config.source);
8202
- const pinned = pinnedGitCommit(config.source);
8203
- const commitOk = pinned === void 0 || ports.installedCommit() === pinned;
8204
- if (versionOk && commitOk && (installedSource === void 0 || installedSource === config.source)) {
8205
- installedSource = config.source;
8206
- return true;
8207
- }
8208
- ports.log(`agent-shim headroom supervisor: installing headroom from ${config.source}`);
8209
- const result = ports.install(config.source);
8210
- if (!result.ok) {
8211
- return false;
8212
- }
8213
- installedSource = config.source;
8214
- return true;
8277
+ if (entry === void 0) {
8278
+ return { store, status: "empty" };
8279
+ }
8280
+ const limit = ttlMs(params.cache);
8281
+ const ageMs = cacheAgeMs(entry, params.nowMs);
8282
+ return {
8283
+ store,
8284
+ status: isFresh(entry, params.cache, params.nowMs) ? "fresh" : "expired",
8285
+ ageMs,
8286
+ ...limit === void 0 ? {} : { expiresInMs: limit - ageMs }
8215
8287
  };
8216
- const socket = prepareHeadroomSocket(fs12, ports.socketTrust, paths, ports.ownPid);
8217
- if (socket.refused !== void 0) {
8218
- return fail(socket.refused);
8288
+ }
8289
+ function formatCachedCredentialState(state) {
8290
+ if (state.status === "empty") {
8291
+ return `no cached entry in the ${state.store} store yet`;
8219
8292
  }
8220
- const { socketPath } = socket;
8221
- pruneStaleHeadroomSockets(fs12, paths, ports.isRunning);
8222
- if (!ensureInstalled()) {
8223
- return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
8293
+ if (state.status === "unreadable") {
8294
+ return `the ${state.store} store cannot be read here: ${state.reason}`;
8224
8295
  }
8225
- const version = ports.headroomVersion() ?? "unknown";
8226
- writeOwnState({ supervisorPid: ports.ownPid, version, installedSource });
8227
- ports.log(`agent-shim headroom supervisor ${String(ports.ownPid)}: managing headroom on allowlist [${allowlistOf(ports).join(", ")}]`);
8228
- const orphanPid = previousState?.headroomPid;
8229
- if (orphanPid !== void 0 && ports.isRunning(orphanPid)) {
8230
- ports.log(`agent-shim headroom supervisor: stopping daemon pid ${String(orphanPid)} left behind by the previous supervisor`);
8231
- ports.stopProcess(orphanPid);
8296
+ if (state.status === "expired" && (state.expiresInMs === void 0 || state.expiresInMs > 0)) {
8297
+ return "cached with a timestamp in the future, treated as expired";
8232
8298
  }
8233
- let headroomPid;
8234
- let runningHash;
8235
- let runningSettingsHash;
8236
- let consecutiveFailures = 0;
8237
- let idleSince;
8238
- let superseded = false;
8239
- let ticks = 0;
8240
- for (; ; ) {
8241
- if (options.tickLimit !== void 0 && ticks >= options.tickLimit) {
8242
- return HEADROOM_SUPERVISOR_STILL_RUNNING;
8243
- }
8244
- ticks += 1;
8245
- config = ports.readConfig();
8246
- const settingsHash = hashSettings(config.settings);
8247
- const allowlist = allowlistOf(ports);
8248
- const allowlistHash = hashAllowlist(allowlist);
8249
- pruneDeadSessions(fs12, paths.headroomSessionsDir, ports.isRunning);
8250
- const ownsNow = ownsState();
8251
- if (!ownsNow && !superseded) {
8252
- ports.log("agent-shim headroom supervisor: another supervisor now owns the state file; serving this daemon's own sessions until they end");
8253
- }
8254
- superseded = !ownsNow;
8255
- const crashed = headroomPid !== void 0 && !ports.isRunning(headroomPid);
8256
- if (headroomPid === void 0 || crashed) {
8257
- if (crashed) {
8258
- ports.log(`agent-shim headroom supervisor: headroom pid ${String(headroomPid)} died`);
8259
- headroomPid = void 0;
8260
- runningHash = void 0;
8261
- runningSettingsHash = void 0;
8262
- writeOwnState({
8263
- supervisorPid: ports.ownPid,
8264
- version,
8265
- installedSource
8266
- });
8267
- }
8268
- if (consecutiveFailures >= HEADROOM_START_RETRY_BUDGET) {
8269
- return fail(`headroom failed to become ready ${String(consecutiveFailures)} times in a row; giving up`);
8270
- }
8271
- if (installedSource !== config.source && !ensureInstalled()) {
8272
- return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
8273
- }
8274
- const pid = ports.spawnHeadroom(socketPath, allowlist, config.settings);
8275
- const ready = await waitUntilReady(ports, socketPath);
8276
- if (ready) {
8277
- consecutiveFailures = 0;
8278
- headroomPid = pid;
8279
- runningHash = allowlistHash;
8280
- runningSettingsHash = settingsHash;
8281
- const recorded = writeOwnState({
8282
- supervisorPid: ports.ownPid,
8283
- headroomPid: pid,
8284
- socketPath,
8285
- version,
8286
- allowlistHash,
8287
- settingsHash,
8288
- installedSource: config.source
8289
- });
8290
- if (recorded) {
8291
- fs12.removeRecursive(paths.headroomLockFile);
8292
- }
8293
- idleSince = void 0;
8294
- ports.log(`agent-shim headroom supervisor: headroom pid ${String(pid)} ready on unix socket ${socketPath}`);
8295
- } else {
8296
- ports.stopProcess(pid);
8297
- consecutiveFailures += 1;
8298
- ports.log(
8299
- `agent-shim headroom supervisor: headroom did not become ready on unix socket ${socketPath} (attempt ${String(consecutiveFailures)} of ${String(HEADROOM_START_RETRY_BUDGET)})`
8300
- );
8301
- await ports.sleep(backoffForAttempt(consecutiveFailures));
8302
- continue;
8303
- }
8304
- } else {
8305
- if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
8306
- if (sessionsForSupervisor(fs12, paths.headroomSessionsDir, ports.ownPid).length === 0) {
8307
- ports.log("agent-shim headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
8308
- ports.stopProcess(headroomPid);
8309
- headroomPid = void 0;
8310
- runningHash = void 0;
8311
- runningSettingsHash = void 0;
8312
- continue;
8313
- }
8314
- }
8315
- const sessions = sessionsForSupervisor(fs12, paths.headroomSessionsDir, ports.ownPid);
8316
- if (sessions.length === 0) {
8317
- idleSince ??= ports.now();
8318
- const idleMs = ports.now() - idleSince;
8319
- if (idleMs >= config.idleShutdownMinutes * MS_PER_MINUTE2) {
8320
- ports.log(
8321
- `agent-shim headroom supervisor: no sessions for ${String(config.idleShutdownMinutes)} minute(s); stopping headroom and exiting`
8322
- );
8323
- ports.stopProcess(headroomPid);
8324
- writeOwnState({ version });
8325
- return 0;
8326
- }
8327
- } else {
8328
- idleSince = void 0;
8329
- }
8330
- }
8331
- await ports.sleep(HEADROOM_POLL_MS);
8299
+ const age = `cached ${formatAge(state.ageMs)} ago`;
8300
+ if (state.expiresInMs === void 0) {
8301
+ return `${age}, no expiry`;
8332
8302
  }
8303
+ return state.status === "fresh" ? `${age}, expires in ${formatAge(state.expiresInMs)}` : `${age}, expired ${formatAge(-state.expiresInMs)} ago`;
8333
8304
  }
8334
- function currentHeadroomAllowlist(fs12, paths) {
8335
- const providers = readAllProviders(fs12, paths.providersDir).map((entry) => entry.provider);
8336
- return headroomAllowlist(headroomUpstreams(providers, frontDoorOrigin(readFrontDoorState(fs12, paths.frontdoorStateFile))));
8337
- }
8338
- function allowlistOf(ports) {
8339
- return currentHeadroomAllowlist(ports.fs, ports.paths);
8305
+
8306
+ // src/realCredentialCache.ts
8307
+ var KEYCHAIN_CACHE_SERVICE = "agent-shim-credential-cache";
8308
+ var CACHE_DIR_MODE = 448;
8309
+ var CACHE_FILE_MODE = 384;
8310
+ function cacheFileNameFor(owner) {
8311
+ return `${owner.replace(/[^A-Za-z0-9._-]/g, "_")}.json`;
8340
8312
  }
8341
- async function waitUntilReady(ports, socketPath) {
8342
- const deadline = ports.now() + HEADROOM_READY_TIMEOUT_MS;
8343
- for (; ; ) {
8344
- if (await ports.ready(socketPath)) {
8345
- return true;
8346
- }
8347
- if (ports.now() >= deadline) {
8348
- return false;
8349
- }
8350
- await ports.sleep(HEADROOM_POLL_MS);
8313
+ function parseEntry(raw) {
8314
+ let json;
8315
+ try {
8316
+ json = JSON.parse(raw);
8317
+ } catch {
8318
+ return void 0;
8351
8319
  }
8320
+ const parsed = CachedCredentialSchema.safeParse(json);
8321
+ return parsed.success ? parsed.data : void 0;
8352
8322
  }
8353
-
8354
- // src/realPorts.ts
8355
- function isEnoent3(error) {
8356
- return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
8357
- }
8358
- var realFsPort = {
8359
- readFileUtf8(filePath) {
8360
- try {
8361
- return import_node_fs6.default.readFileSync(filePath, "utf8");
8362
- } catch (error) {
8363
- if (isEnoent3(error)) {
8364
- return void 0;
8365
- }
8366
- throw error;
8367
- }
8368
- },
8369
- readConfigFile: cosmiconfigReader(),
8370
- readdir(dirPath) {
8371
- try {
8372
- return import_node_fs6.default.readdirSync(dirPath);
8373
- } catch (error) {
8374
- if (isEnoent3(error)) {
8375
- return [];
8376
- }
8377
- throw error;
8378
- }
8323
+ function requireKeychain() {
8324
+ if (process.platform !== "darwin") {
8325
+ throw new Error("the keychain credential store needs macOS; use --credential-cache-store file");
8379
8326
  }
8380
- };
8381
- function isErrorWithCode(error, code) {
8382
- return typeof error === "object" && error !== null && "code" in error && error.code === code;
8383
8327
  }
8384
- var OWNER_ONLY_DIR_MODE = 448;
8385
- var OWNER_ONLY_FILE_MODE = 384;
8386
- var PERMISSION_BITS2 = 511;
8387
- var realFarmFs = {
8388
- lstat(filePath) {
8389
- let stat;
8390
- try {
8391
- stat = import_node_fs6.default.lstatSync(filePath);
8392
- } catch (error) {
8393
- if (isEnoent3(error)) {
8394
- return void 0;
8395
- }
8396
- throw error;
8397
- }
8398
- const kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "dir" : "file";
8399
- return { kind, mtimeMs: stat.mtimeMs, sizeBytes: stat.size };
8400
- },
8401
- readdir(dirPath) {
8402
- try {
8403
- return import_node_fs6.default.readdirSync(dirPath);
8404
- } catch (error) {
8405
- if (isEnoent3(error) || isErrorWithCode(error, "ENOTDIR")) {
8406
- return [];
8407
- }
8408
- throw error;
8409
- }
8410
- },
8411
- mkdirp(dirPath) {
8412
- import_node_fs6.default.mkdirSync(dirPath, { recursive: true });
8413
- },
8414
- symlink(target, linkPath) {
8415
- import_node_fs6.default.symlinkSync(target, linkPath);
8416
- },
8417
- rename(from, to) {
8418
- import_node_fs6.default.renameSync(from, to);
8419
- },
8420
- removeRecursive(targetPath) {
8421
- import_node_fs6.default.rmSync(targetPath, { recursive: true, force: true });
8422
- },
8423
- copyRecursive(from, to) {
8424
- import_node_fs6.default.cpSync(from, to, { recursive: true, force: true, verbatimSymlinks: true });
8425
- },
8426
- readFileUtf8(filePath) {
8427
- try {
8428
- return import_node_fs6.default.readFileSync(filePath, "utf8");
8429
- } catch (error) {
8430
- if (isEnoent3(error)) {
8431
- return void 0;
8432
- }
8433
- throw error;
8434
- }
8435
- },
8436
- writeFileUtf8(filePath, contents) {
8437
- import_node_fs6.default.writeFileSync(filePath, contents, "utf8");
8438
- },
8439
- mkdirPrivate(dirPath) {
8440
- import_node_fs6.default.mkdirSync(dirPath, { recursive: true, mode: OWNER_ONLY_DIR_MODE });
8441
- import_node_fs6.default.chmodSync(dirPath, OWNER_ONLY_DIR_MODE);
8442
- },
8443
- writeFilePrivate(filePath, contents) {
8444
- const temp = `${filePath}.${String(process.pid)}.tmp`;
8445
- import_node_fs6.default.writeFileSync(temp, contents, { encoding: "utf8", mode: OWNER_ONLY_FILE_MODE });
8446
- import_node_fs6.default.chmodSync(temp, OWNER_ONLY_FILE_MODE);
8447
- import_node_fs6.default.renameSync(temp, filePath);
8448
- },
8449
- appendFilePrivate(filePath, contents) {
8450
- const descriptor = import_node_fs6.default.openSync(filePath, "a", OWNER_ONLY_FILE_MODE);
8451
- try {
8452
- if ((import_node_fs6.default.fstatSync(descriptor).mode & PERMISSION_BITS2) !== OWNER_ONLY_FILE_MODE) {
8453
- import_node_fs6.default.fchmodSync(descriptor, OWNER_ONLY_FILE_MODE);
8328
+ function security(args, stdin) {
8329
+ const result = (0, import_node_child_process2.spawnSync)("security", args, { encoding: "utf8", ...stdin === void 0 ? {} : { input: stdin }, stdio: ["pipe", "pipe", "ignore"] });
8330
+ return { status: result.status, stdout: result.stdout };
8331
+ }
8332
+ function createRealCredentialCache(root) {
8333
+ const dir = import_node_path21.default.join(root, "credential-cache");
8334
+ const filePath = (owner) => import_node_path21.default.join(dir, cacheFileNameFor(owner));
8335
+ return {
8336
+ read(store, owner) {
8337
+ if (store === "file") {
8338
+ try {
8339
+ return parseEntry(import_node_fs5.default.readFileSync(filePath(owner), "utf8"));
8340
+ } catch {
8341
+ return void 0;
8342
+ }
8454
8343
  }
8455
- const bytes = Buffer.from(contents, "utf8");
8456
- let written = 0;
8457
- while (written < bytes.length) {
8458
- written += import_node_fs6.default.writeSync(descriptor, bytes, written, bytes.length - written);
8344
+ requireKeychain();
8345
+ const result = security(["find-generic-password", "-s", KEYCHAIN_CACHE_SERVICE, "-a", owner, "-w"]);
8346
+ return result.status === 0 ? parseEntry(Buffer.from(result.stdout.trim(), "base64").toString("utf8")) : void 0;
8347
+ },
8348
+ write(store, owner, entry) {
8349
+ const serialised = JSON.stringify(entry);
8350
+ if (store === "file") {
8351
+ import_node_fs5.default.mkdirSync(dir, { recursive: true, mode: CACHE_DIR_MODE });
8352
+ import_node_fs5.default.chmodSync(dir, CACHE_DIR_MODE);
8353
+ const target = filePath(owner);
8354
+ const temp = `${target}.${String(process.pid)}.tmp`;
8355
+ import_node_fs5.default.writeFileSync(temp, serialised, { mode: CACHE_FILE_MODE });
8356
+ import_node_fs5.default.renameSync(temp, target);
8357
+ return;
8459
8358
  }
8460
- } finally {
8461
- import_node_fs6.default.closeSync(descriptor);
8462
- }
8463
- },
8464
- writeFileExclusive(filePath, contents) {
8465
- try {
8466
- import_node_fs6.default.writeFileSync(filePath, contents, { encoding: "utf8", flag: "wx" });
8467
- return true;
8468
- } catch (error) {
8469
- if (isErrorWithCode(error, "EEXIST")) {
8470
- return false;
8359
+ requireKeychain();
8360
+ const encoded = Buffer.from(serialised, "utf8").toString("base64");
8361
+ const result = security(["-i"], `add-generic-password -U -s ${KEYCHAIN_CACHE_SERVICE} -a ${JSON.stringify(owner)} -w ${encoded}
8362
+ `);
8363
+ if (result.status !== 0) {
8364
+ throw new Error(`could not write the keychain credential cache for ${owner}`);
8471
8365
  }
8472
- throw error;
8473
- }
8474
- },
8475
- hashFile(filePath) {
8476
- try {
8477
- return (0, import_node_crypto8.createHash)("sha256").update(import_node_fs6.default.readFileSync(filePath)).digest("hex");
8478
- } catch (error) {
8479
- if (isEnoent3(error) || isErrorWithCode(error, "EISDIR")) {
8480
- return void 0;
8366
+ },
8367
+ remove(store, owner) {
8368
+ if (store === "file") {
8369
+ import_node_fs5.default.rmSync(filePath(owner), { force: true });
8370
+ return;
8481
8371
  }
8482
- throw error;
8372
+ requireKeychain();
8373
+ security(["delete-generic-password", "-s", KEYCHAIN_CACHE_SERVICE, "-a", owner]);
8483
8374
  }
8375
+ };
8376
+ }
8377
+ function realCredentialCacheEnv(paths) {
8378
+ return { port: createRealCredentialCache(paths.root), platform: process.platform, now: () => Date.now() };
8379
+ }
8380
+
8381
+ // src/providersStore.ts
8382
+ var import_node_fs6 = __toESM(require("node:fs"), 1);
8383
+ var import_node_path22 = __toESM(require("node:path"), 1);
8384
+
8385
+ // src/credential.ts
8386
+ var CREDENTIAL_UNAVAILABLE_EXIT = 64;
8387
+ var CREDENTIAL_COMMAND_TIMEOUT_MS = 1e4;
8388
+ var CREDENTIAL_INTERACTIVE_TIMEOUT_MS = 12e4;
8389
+ var GROUP_OTHER_PERMISSION_BITS = 63;
8390
+ var PERMISSION_BITS2 = 511;
8391
+ var OCTAL_RADIX2 = 8;
8392
+ function commandArgv(source) {
8393
+ if ("command" in source) {
8394
+ return source.command;
8484
8395
  }
8485
- };
8486
- var INT32_BYTE_LENGTH = 4;
8487
- function realSleepSync(ms) {
8488
- Atomics.wait(new Int32Array(new SharedArrayBuffer(INT32_BYTE_LENGTH)), 0, 0, ms);
8396
+ if ("op" in source) {
8397
+ return ["op", "read", source.op];
8398
+ }
8399
+ const account = source.keychain.account;
8400
+ return ["security", "find-generic-password", "-s", source.keychain.service, ...account === void 0 ? [] : ["-a", account], "-w"];
8489
8401
  }
8490
- function realIsProcessAlive(pid) {
8491
- try {
8492
- process.kill(pid, 0);
8493
- return true;
8494
- } catch (error) {
8495
- return isErrorWithCode(error, "EPERM");
8402
+ function isInteractiveSource(source, env) {
8403
+ if ("command" in source || "keychain" in source) {
8404
+ return source.interactive ?? false;
8405
+ }
8406
+ if ("op" in source) {
8407
+ return source.interactive ?? (env.OP_SERVICE_ACCOUNT_TOKEN ?? "") === "";
8496
8408
  }
8409
+ return false;
8497
8410
  }
8498
- function realIsProcessZombie(pid) {
8499
- const result = (0, import_node_child_process2.spawnSync)("ps", ["-o", "stat=", "-p", String(pid)], { encoding: "utf8" });
8500
- return result.status === 0 && result.stdout.trim().startsWith("Z");
8411
+ function summariseSource(source) {
8412
+ if ("env" in source) {
8413
+ return { kind: "env", variable: source.env };
8414
+ }
8415
+ if ("file" in source) {
8416
+ return { kind: "file", path: source.file };
8417
+ }
8418
+ if ("command" in source) {
8419
+ return { kind: "command", program: source.command[0] };
8420
+ }
8421
+ if ("op" in source) {
8422
+ return { kind: "op", reference: source.op };
8423
+ }
8424
+ if ("keychain" in source) {
8425
+ return {
8426
+ kind: "keychain",
8427
+ service: source.keychain.service,
8428
+ ...source.keychain.account === void 0 ? {} : { account: source.keychain.account }
8429
+ };
8430
+ }
8431
+ return { kind: "literal" };
8501
8432
  }
8502
- function realIsProcessRunning(pid) {
8503
- return realIsProcessAlive(pid) && !realIsProcessZombie(pid);
8433
+ function summariseCredential(credential) {
8434
+ return {
8435
+ target: credential.target ?? "bearer",
8436
+ sources: credential.sources.map(summariseSource),
8437
+ ...credential.cache === void 0 ? {} : { cache: credential.cache }
8438
+ };
8504
8439
  }
8505
- var realRunPort = {
8506
- run(command, args) {
8507
- const result = (0, import_node_child_process2.spawnSync)(command, [...args], { encoding: "utf8" });
8508
- return { status: result.status, stdout: result.stdout, stderr: result.stderr };
8440
+ function formatSourceSummary(summary) {
8441
+ switch (summary.kind) {
8442
+ case "env":
8443
+ return `env ${summary.variable}`;
8444
+ case "file":
8445
+ return `file ${summary.path}`;
8446
+ case "command":
8447
+ return `command ${summary.program}`;
8448
+ case "op":
8449
+ return `op ${summary.reference}`;
8450
+ case "keychain":
8451
+ return `keychain ${summary.service}${summary.account === void 0 ? "" : ` (account ${summary.account})`}`;
8452
+ case "literal":
8453
+ return "literal (non-secret placeholder)";
8454
+ default:
8455
+ return summary;
8509
8456
  }
8510
- };
8511
- function expandHome2(filePath) {
8512
- return filePath.startsWith("~/") ? import_node_path22.default.join(import_node_os3.default.homedir(), filePath.slice(1)) : filePath;
8513
8457
  }
8514
- var MACOS_GUI_SESSION_MANAGER = "Aqua";
8515
- function hasDesktopSession() {
8516
- if (process.platform === "darwin") {
8517
- const result = (0, import_node_child_process2.spawnSync)("launchctl", ["managername"], { encoding: "utf8" });
8518
- return result.status === 0 && result.stdout.trim() === MACOS_GUI_SESSION_MANAGER;
8458
+ function describeSource(source) {
8459
+ return formatSourceSummary(summariseSource(source));
8460
+ }
8461
+ function formatCredentialSummary(summary) {
8462
+ const cache = summary.cache === void 0 ? "" : `, cached ${summary.cache.ttl === void 0 ? "with no expiry" : `for ${summary.cache.ttl}`}${summary.cache.store === void 0 ? "" : ` in the ${summary.cache.store} store`}`;
8463
+ return `${summary.target} from ${summary.sources.map(formatSourceSummary).join(", then ")}${cache}`;
8464
+ }
8465
+ function describeCredential(credential) {
8466
+ return formatCredentialSummary(summariseCredential(credential));
8467
+ }
8468
+ function trySource(source, params) {
8469
+ const none = (reason, warn = false) => ({ ok: false, reason, warn });
8470
+ if ("env" in source) {
8471
+ const value = params.env[source.env];
8472
+ return value === void 0 || value === "" ? none("is unset or empty") : { ok: true, token: value };
8519
8473
  }
8520
- if (process.platform === "win32") {
8521
- return false;
8474
+ if ("literal" in source) {
8475
+ return { ok: true, token: source.literal };
8522
8476
  }
8523
- return (process.env.DISPLAY ?? "") !== "" || (process.env.WAYLAND_DISPLAY ?? "") !== "";
8524
- }
8525
- var realCredentialPort = {
8526
- readSecretFile(filePath) {
8527
- const resolved = expandHome2(filePath);
8528
- let content;
8529
- let stat;
8530
- try {
8531
- stat = import_node_fs6.default.statSync(resolved);
8532
- content = import_node_fs6.default.readFileSync(resolved, "utf8");
8533
- } catch (error) {
8534
- if (isEnoent3(error)) {
8535
- return { found: false };
8536
- }
8537
- throw error;
8477
+ if ("file" in source) {
8478
+ const read = params.port.readSecretFile(source.file);
8479
+ if (!read.found) {
8480
+ return none("does not exist");
8538
8481
  }
8539
- return { found: true, content, mode: process.platform === "win32" ? void 0 : stat.mode };
8540
- },
8541
- runCommand(argv, options) {
8542
- const [command, ...args] = argv;
8543
- const result = (0, import_node_child_process2.spawnSync)(command, args, {
8544
- encoding: "utf8",
8545
- timeout: options.timeoutMs,
8546
- stdio: options.interactive ? ["inherit", "pipe", "inherit"] : ["ignore", "pipe", "pipe"]
8547
- });
8548
- if (result.error !== void 0) {
8549
- const timedOut = isErrorWithCode(result.error, "ETIMEDOUT");
8550
- return { status: result.status, stdout: "", stderr: timedOut ? "" : result.error.message, timedOut };
8482
+ if (read.mode !== void 0 && (read.mode & GROUP_OTHER_PERMISSION_BITS) !== 0) {
8483
+ return none(
8484
+ `is readable or writable by group or others (mode ${(read.mode & PERMISSION_BITS2).toString(OCTAL_RADIX2)}); run \`chmod 600 ${source.file}\``,
8485
+ true
8486
+ );
8551
8487
  }
8552
- return { status: result.status, stdout: result.stdout, stderr: options.interactive ? "" : result.stderr, timedOut: false };
8553
- },
8554
- personPresent: () => process.stdin.isTTY || hasDesktopSession()
8555
- };
8556
- function resolveGitBranch(run, cwd) {
8557
- const result = run.run("git", ["-C", cwd, "rev-parse", "--abbrev-ref", "HEAD"]);
8558
- if (result.status !== 0) {
8559
- return {};
8488
+ const token2 = read.content.trim();
8489
+ return token2 === "" ? none("is empty") : { ok: true, token: token2 };
8560
8490
  }
8561
- const name = result.stdout.trim();
8562
- if (name === "") {
8563
- return {};
8491
+ const argv = commandArgv(source);
8492
+ const interactive = isInteractiveSource(source, params.env);
8493
+ if (interactive && !params.port.personPresent()) {
8494
+ return none("needs a person to approve it, but there is no terminal or desktop session");
8564
8495
  }
8565
- if (name === "HEAD") {
8566
- return { branchDetached: true };
8496
+ const timeoutMs = source.timeoutMs ?? (interactive ? CREDENTIAL_INTERACTIVE_TIMEOUT_MS : CREDENTIAL_COMMAND_TIMEOUT_MS);
8497
+ const result = params.port.runCommand(argv, { timeoutMs, interactive });
8498
+ if (result.timedOut) {
8499
+ return none(`timed out after ${String(timeoutMs)}ms`);
8567
8500
  }
8568
- return { branch: name, branchDetached: false };
8569
- }
8570
- function resolveGitRoot(run, cwd) {
8571
- const result = run.run("git", ["-C", cwd, "rev-parse", "--show-toplevel"]);
8572
8501
  if (result.status !== 0) {
8573
- return void 0;
8502
+ const stderr = result.stderr.trim();
8503
+ const outcome = result.status === null ? "could not be run or was killed by a signal" : `exited with status ${String(result.status)}`;
8504
+ return none(`${outcome}${stderr === "" ? "" : `: ${stderr}`}`);
8574
8505
  }
8575
- const root = result.stdout.trim();
8576
- return root === "" ? void 0 : root;
8577
- }
8578
- function detachedDaemonSpawnOptions(logFd, env) {
8579
- return { detached: true, stdio: ["ignore", logFd, logFd], env };
8580
- }
8581
- function spawnDaemonThrough(executable) {
8582
- return (paths, subcommand, logPath) => spawnDetachedDaemon({ command: executable, args: [subcommand] }, paths, subcommand, logPath);
8506
+ const token = result.stdout.trim();
8507
+ return token === "" ? none("printed no token") : { ok: true, token };
8583
8508
  }
8584
- function spawnDaemonThroughScript(script) {
8585
- return (paths, subcommand, logPath) => spawnDetachedDaemon({ command: process.execPath, args: [script, subcommand] }, paths, subcommand, logPath);
8509
+ function warmCommandFor(subject) {
8510
+ const [kind, name] = subject.split(" ");
8511
+ return kind === "provider" ? `agent-shim credential warm --provider ${name ?? ""}` : `agent-shim credential warm ${name ?? ""}`;
8586
8512
  }
8587
- function spawnDetachedDaemon(invocation, paths, subcommand, logPath) {
8588
- import_node_fs6.default.mkdirSync(import_node_path22.default.dirname(logPath), { recursive: true });
8589
- const logFd = import_node_fs6.default.openSync(logPath, "a");
8590
- try {
8591
- const child = (0, import_node_child_process2.spawn)(invocation.command, invocation.args, detachedDaemonSpawnOptions(logFd, { ...process.env, AGENT_SHIM_HOME: paths.root }));
8592
- child.unref();
8593
- if (child.pid === void 0) {
8594
- throw new Error(`spawning ${subcommand} returned no pid`);
8513
+ function resolveCredential(params) {
8514
+ const target = params.credential.target ?? "bearer";
8515
+ const cacheBlock = params.credential.cache;
8516
+ const cache = cacheBlock === void 0 ? void 0 : params.port.cache;
8517
+ const store = cacheBlock === void 0 || cache === void 0 ? void 0 : effectiveStore(cacheBlock, cache.platform);
8518
+ if (cacheBlock !== void 0 && cache !== void 0 && store !== void 0 && params.refresh !== true) {
8519
+ const entry = cache.port.read(store, params.subject);
8520
+ if (entry !== void 0 && isFresh(entry, cacheBlock, cache.now())) {
8521
+ return { ok: true, credential: { target, token: entry.token, source: entry.source, warnings: [], cachedAt: entry.fetchedAt } };
8595
8522
  }
8596
- return child.pid;
8597
- } finally {
8598
- import_node_fs6.default.closeSync(logFd);
8599
8523
  }
8600
- }
8601
- function spawnHeadroomSupervisor(paths, spawnDaemon) {
8602
- return spawnDaemon(paths, "__headroom-supervisor", paths.headroomLogPath);
8603
- }
8604
- var SOCKET_PERMISSION_BITS = 511;
8605
- var realHeadroomSocketTrust = {
8606
- platform: process.platform,
8607
- currentUid: () => {
8608
- if (process.getuid === void 0) {
8609
- throw new Error(`no POSIX user id on ${process.platform}`);
8610
- }
8611
- return process.getuid();
8612
- },
8613
- lstat: (target) => {
8614
- let stat;
8615
- try {
8616
- stat = import_node_fs6.default.lstatSync(target);
8617
- } catch (error) {
8618
- if (isEnoent3(error)) {
8619
- return void 0;
8524
+ const attempts = [];
8525
+ const warnings = [];
8526
+ for (const source of params.credential.sources) {
8527
+ const outcome = trySource(source, params);
8528
+ if (outcome.ok) {
8529
+ if (cache !== void 0 && store !== void 0) {
8530
+ cache.port.write(store, params.subject, { token: outcome.token, fetchedAt: cache.now(), source });
8620
8531
  }
8621
- throw error;
8532
+ return { ok: true, credential: { target, token: outcome.token, source, warnings } };
8622
8533
  }
8623
- const kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "dir" : stat.isSocket() ? "socket" : "other";
8624
- return { kind, uid: stat.uid, mode: stat.mode & SOCKET_PERMISSION_BITS };
8534
+ const line = `${describeSource(source)} ${outcome.reason}`;
8535
+ attempts.push(line);
8536
+ if (outcome.warn) {
8537
+ warnings.push(`agent-shim: ${params.subject}: skipped ${line}`);
8538
+ }
8539
+ }
8540
+ const hint = cacheBlock === void 0 ? "" : `; fill the cache from a terminal with \`${warmCommandFor(params.subject)}\``;
8541
+ return { ok: false, message: `agent-shim: ${params.subject} has no usable credential: ${attempts.join("; ")}${hint}` };
8542
+ }
8543
+ function credentialVariables(target, token) {
8544
+ return {
8545
+ ANTHROPIC_AUTH_TOKEN: target === "bearer" ? token : void 0,
8546
+ ANTHROPIC_API_KEY: target === "apiKey" ? token : void 0,
8547
+ CLAUDE_CODE_OAUTH_TOKEN: target === "oauthToken" ? token : void 0
8548
+ };
8549
+ }
8550
+
8551
+ // src/providersStore.ts
8552
+ var ProviderNotFoundError = class extends CliError {
8553
+ constructor(name) {
8554
+ super(`No provider named "${name}". Run \`agent-shim provider add ${name}\` first.`);
8555
+ this.name = name;
8556
+ this.name = "ProviderNotFoundError";
8625
8557
  }
8558
+ name;
8626
8559
  };
8627
- function realHeadroomPort(paths, options) {
8560
+ var ProviderAlreadyExistsError = class extends CliError {
8561
+ constructor(providerName) {
8562
+ super(`A provider named "${providerName}" already exists.`);
8563
+ this.providerName = providerName;
8564
+ this.name = "ProviderAlreadyExistsError";
8565
+ }
8566
+ providerName;
8567
+ };
8568
+ var InvalidProviderNameError = class extends CliError {
8569
+ constructor(attemptedName) {
8570
+ super(
8571
+ `"${attemptedName}" is not a valid provider name. Provider names must start with a letter or number and may then contain letters, numbers, dots, hyphens, underscores, and at signs.`
8572
+ );
8573
+ this.attemptedName = attemptedName;
8574
+ this.name = "InvalidProviderNameError";
8575
+ }
8576
+ attemptedName;
8577
+ };
8578
+ var PROVIDER_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._@-]*$/;
8579
+ function providerJsonPath(paths, name) {
8580
+ return import_node_path22.default.join(paths.providersDir, `${name}.json`);
8581
+ }
8582
+ function providerExists(paths, name) {
8583
+ return import_node_fs6.default.existsSync(providerJsonPath(paths, name));
8584
+ }
8585
+ var LEGACY_PROVIDER_FIELDS = ["tokenEnv", "tokenCommand", "authScheme"];
8586
+ var LEGACY_ENV_CREDENTIAL_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
8587
+ var LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
8588
+ function isRecord4(value) {
8589
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8590
+ }
8591
+ function legacyProviderConversion(raw) {
8592
+ if (!isRecord4(raw) || "credential" in raw) {
8593
+ return void 0;
8594
+ }
8595
+ const env = isRecord4(raw.env) ? raw.env : void 0;
8596
+ const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
8597
+ const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
8598
+ if (fields.length === 0) {
8599
+ return void 0;
8600
+ }
8601
+ const sources = [];
8602
+ if (typeof raw.tokenEnv === "string") {
8603
+ sources.push({ env: raw.tokenEnv });
8604
+ }
8605
+ if (Array.isArray(raw.tokenCommand)) {
8606
+ sources.push({ command: raw.tokenCommand });
8607
+ }
8608
+ if (typeof env?.ANTHROPIC_AUTH_TOKEN === "string" && env.ANTHROPIC_AUTH_TOKEN !== "") {
8609
+ sources.push({ literal: LEGACY_LITERAL_PLACEHOLDER });
8610
+ }
8611
+ const remainingEnv = env === void 0 ? void 0 : Object.fromEntries(Object.entries(env).filter(([key]) => !LEGACY_ENV_CREDENTIAL_KEYS.includes(key)));
8612
+ const replacedKeys = [...LEGACY_PROVIDER_FIELDS, "env"];
8613
+ const rest = Object.fromEntries(Object.entries(raw).filter(([key]) => !replacedKeys.includes(key)));
8628
8614
  return {
8629
- ensure: ({ routesProvider }) => {
8630
- const up = ensureHeadroom({
8631
- paths,
8632
- launcherPid: process.pid,
8633
- ...routesProvider ? { requiredAllowlistHash: () => hashAllowlist(currentHeadroomAllowlist(realFarmFs, paths)) } : {},
8634
- ports: {
8635
- fs: realFarmFs,
8636
- // Zombie-aware on purpose: a supervisor that died while still a child of this launcher sits unreaped until the launcher itself exits, and a defunct supervisor answering signal 0 as alive would stretch every launch to the full start timeout.
8637
- isRunning: realIsProcessRunning,
8638
- now: () => Date.now(),
8639
- sleep: realSleepSync,
8640
- spawnSupervisor: (layout) => spawnHeadroomSupervisor(layout, options.spawnDaemon)
8641
- }
8642
- });
8643
- return {
8644
- socketPath: up.socketPath,
8645
- projectId: resolveGitRoot(realRunPort, options.cwd) ?? options.cwd
8646
- };
8647
- },
8648
- release: () => {
8649
- removeSession(realFarmFs, paths.headroomSessionsDir, process.pid);
8615
+ fields,
8616
+ replacement: {
8617
+ ...rest,
8618
+ credential: { sources, ...raw.authScheme === "apiKey" ? { target: "apiKey" } : {} },
8619
+ ...remainingEnv === void 0 || Object.keys(remainingEnv).length === 0 ? {} : { env: remainingEnv }
8650
8620
  }
8651
8621
  };
8652
8622
  }
8653
- var realProcPort = {
8654
- env: process.env,
8655
- argv: process.argv.slice(2),
8656
- exit: (code) => process.exit(code)
8623
+ var LegacyProviderFileError = class extends CliError {
8624
+ constructor(filePath, conversion) {
8625
+ super(
8626
+ `${filePath} uses ${conversion.fields.join(", ")}, which a credential block replaced. Rewrite it as:
8627
+ ` + JSON.stringify(conversion.replacement, null, 2)
8628
+ );
8629
+ this.filePath = filePath;
8630
+ this.conversion = conversion;
8631
+ this.name = "LegacyProviderFileError";
8632
+ }
8633
+ filePath;
8634
+ conversion;
8657
8635
  };
8658
- var EXECUTE_BITS_MASK = 73;
8659
- function listVersionsDir(dir) {
8660
- let entries;
8661
- try {
8662
- entries = import_node_fs6.default.readdirSync(dir, { withFileTypes: true });
8663
- } catch (error) {
8664
- if (isEnoent3(error)) {
8665
- return [];
8666
- }
8667
- throw error;
8636
+ function parseProviderFile(filePath, raw) {
8637
+ const conversion = legacyProviderConversion(raw);
8638
+ if (conversion !== void 0) {
8639
+ throw new LegacyProviderFileError(filePath, conversion);
8668
8640
  }
8669
- return entries.filter((entry) => entry.isFile()).map((entry) => {
8670
- const fullPath = import_node_path22.default.join(dir, entry.name);
8671
- const stat = import_node_fs6.default.statSync(fullPath);
8672
- return {
8673
- name: entry.name,
8674
- isFile: true,
8675
- isExecutable: (stat.mode & EXECUTE_BITS_MASK) !== 0,
8676
- sizeBytes: stat.size
8677
- };
8678
- });
8641
+ const parsed = ProviderSchema.safeParse(raw);
8642
+ if (!parsed.success) {
8643
+ throw new ConfigValidationError(filePath, parsed.error.issues);
8644
+ }
8645
+ return parsed.data;
8679
8646
  }
8680
- var WINDOWS_DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.PS1";
8681
- function windowsPathExtensions(pathext) {
8682
- return (pathext ?? WINDOWS_DEFAULT_PATHEXT).split(";").map((ext) => ext.trim()).filter((ext) => ext.length > 0);
8647
+ function readProvider(paths, name) {
8648
+ const filePath = providerJsonPath(paths, name);
8649
+ if (!import_node_fs6.default.existsSync(filePath)) {
8650
+ return void 0;
8651
+ }
8652
+ const raw = JSON.parse(import_node_fs6.default.readFileSync(filePath, "utf8"));
8653
+ return parseProviderFile(filePath, raw);
8683
8654
  }
8684
- function resolveExecutableCandidate(dir, name, env) {
8685
- const candidate = import_node_path22.default.join(dir, name);
8686
- if (env.platform === "win32") {
8687
- const extensions = windowsPathExtensions(env.pathext);
8688
- const alreadyHasRecognisedExtension = extensions.some((ext) => candidate.toLowerCase().endsWith(ext.toLowerCase()));
8689
- if (alreadyHasRecognisedExtension) {
8690
- return env.statFileMode(candidate) !== void 0 ? candidate : void 0;
8691
- }
8692
- for (const ext of extensions) {
8693
- const withExt = candidate + ext;
8694
- if (env.statFileMode(withExt) !== void 0) {
8695
- return withExt;
8655
+ function loadProvider(providersDir, name, port) {
8656
+ const filePath = import_node_path22.default.join(providersDir, `${name}.json`);
8657
+ const raw = port.readConfigFile(filePath);
8658
+ return raw === void 0 ? void 0 : parseProviderFile(filePath, raw);
8659
+ }
8660
+ function listProviders(paths) {
8661
+ const names = listProviderNames(
8662
+ (dir) => import_node_fs6.default.readdirSync(dir),
8663
+ paths.providersDir
8664
+ );
8665
+ const result = [];
8666
+ for (const name of names) {
8667
+ let provider;
8668
+ try {
8669
+ provider = readProvider(paths, name);
8670
+ } catch (error) {
8671
+ if (!(error instanceof ConfigValidationError || error instanceof LegacyProviderFileError)) {
8672
+ throw error;
8696
8673
  }
8674
+ console.error(`agent-shim: providers/${name}.json is invalid and was skipped: ${error.message}`);
8675
+ continue;
8676
+ }
8677
+ if (provider !== void 0) {
8678
+ result.push({ name, provider });
8697
8679
  }
8698
- return void 0;
8699
8680
  }
8700
- const mode = env.statFileMode(candidate);
8701
- return mode !== void 0 && (mode & EXECUTE_BITS_MASK) !== 0 ? candidate : void 0;
8681
+ return result;
8702
8682
  }
8703
- function realStatFileMode(candidate) {
8683
+ function listProviderNames(readdir, providersDir) {
8684
+ let entries;
8704
8685
  try {
8705
- const stat = import_node_fs6.default.statSync(candidate);
8706
- return stat.isFile() ? stat.mode : void 0;
8707
- } catch (error) {
8708
- if (isEnoent3(error)) {
8709
- return void 0;
8710
- }
8711
- throw error;
8686
+ entries = readdir(providersDir);
8687
+ } catch {
8688
+ return [];
8712
8689
  }
8690
+ return entries.filter((entry) => entry.endsWith(".json")).map((entry) => entry.slice(0, -".json".length)).sort();
8713
8691
  }
8714
- function findExecutableInDir(dir, name) {
8715
- return resolveExecutableCandidate(dir, name, {
8716
- platform: process.platform,
8717
- pathext: process.env.PATHEXT,
8718
- statFileMode: realStatFileMode
8719
- });
8692
+ function describeProviderEndpoint(provider) {
8693
+ return isCodexProvider(provider) ? "codex translation daemon" : provider.baseUrl;
8720
8694
  }
8721
- function searchPathForExecutable(pathDirs, name, findExecutable) {
8722
- for (const dir of pathDirs) {
8723
- const found = findExecutable(dir, name);
8724
- if (found !== void 0) {
8725
- return found;
8726
- }
8695
+ var ProviderKindMismatchError = class extends UsageError {
8696
+ constructor(message) {
8697
+ super(message);
8698
+ this.name = "ProviderKindMismatchError";
8727
8699
  }
8728
- return void 0;
8700
+ };
8701
+ function isEmptyCodexConfig(config) {
8702
+ return config === void 0 || config.defaultModel === void 0 && config.effort === void 0 && config.login === void 0 && (config.models === void 0 || Object.keys(config.models).length === 0);
8729
8703
  }
8730
- function resolveOwnExecutablePath(env) {
8731
- if (env.argv1 !== void 0 && !env.argv1.includes("/") && !env.argv1.includes("\\")) {
8732
- return searchPathForExecutable(env.pathDirs, env.argv1, env.findExecutableInDir) ?? env.execPath;
8704
+ function addProvider(paths, name, input) {
8705
+ if (providerExists(paths, name)) {
8706
+ throw new ProviderAlreadyExistsError(name);
8733
8707
  }
8734
- const candidate = env.argv1 === void 0 ? env.execPath : import_node_path22.default.resolve(env.cwd, env.argv1);
8735
- const candidateDir = import_node_path22.default.dirname(candidate);
8736
- const candidateDirOnPath = env.pathDirs.some((dir) => import_node_path22.default.resolve(dir) === candidateDir);
8737
- if (candidateDirOnPath) {
8738
- return candidate;
8708
+ if (!PROVIDER_NAME_RE.test(name)) {
8709
+ throw new InvalidProviderNameError(name);
8739
8710
  }
8740
- const viaPath = searchPathForExecutable(env.pathDirs, import_node_path22.default.basename(candidate), env.findExecutableInDir);
8741
- return viaPath ?? candidate;
8742
- }
8743
- function realOwnExecutablePath() {
8744
- return resolveOwnExecutablePath({
8745
- argv1: process.argv[1],
8746
- execPath: process.execPath,
8747
- cwd: process.cwd(),
8748
- pathDirs: (process.env.PATH ?? "").split(import_node_path22.default.delimiter).filter((dir) => dir !== ""),
8749
- findExecutableInDir
8750
- });
8711
+ const kind = input.kind ?? "http";
8712
+ if (kind === "codex" && input.baseUrl !== void 0) {
8713
+ throw new ProviderKindMismatchError("A codex provider has no base URL: the launcher points its sessions at the front-door daemon. Drop --base-url.");
8714
+ }
8715
+ if (kind === "http" && input.baseUrl === void 0) {
8716
+ throw new ProviderKindMismatchError("An http provider needs --base-url.");
8717
+ }
8718
+ if (kind === "http" && !isEmptyCodexConfig(input.codex)) {
8719
+ throw new ProviderKindMismatchError("Codex settings apply only to a codex provider (--kind codex).");
8720
+ }
8721
+ const shared = {
8722
+ displayName: input.displayName,
8723
+ credential: { sources: input.sources, ...input.target === void 0 ? {} : { target: input.target } },
8724
+ ...input.env === void 0 || Object.keys(input.env).length === 0 ? {} : { env: input.env }
8725
+ };
8726
+ const parsed = ProviderSchema.safeParse(
8727
+ kind === "codex" ? { kind, ...shared, ...isEmptyCodexConfig(input.codex) ? {} : { codex: input.codex } } : { ...shared, baseUrl: input.baseUrl }
8728
+ );
8729
+ if (!parsed.success) {
8730
+ throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
8731
+ }
8732
+ writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
8733
+ return parsed.data;
8751
8734
  }
8752
- function resolveContentSourcePath(env) {
8753
- return env.isSea ? env.execPath : env.argv1 ?? env.execPath;
8735
+ function updateProvider(paths, name, input) {
8736
+ const existing = readProvider(paths, name);
8737
+ if (existing === void 0) {
8738
+ throw new ProviderNotFoundError(name);
8739
+ }
8740
+ const unset = new Set(input.unsetEnv);
8741
+ const env = Object.fromEntries(Object.entries({ ...existing.env, ...input.env }).filter(([key]) => !unset.has(key)));
8742
+ const target = input.target ?? existing.credential.target;
8743
+ const cache = input.cache === void 0 ? existing.credential.cache : input.cache === false ? void 0 : input.cache;
8744
+ if (isCodexProvider(existing) && input.baseUrl !== void 0) {
8745
+ throw new ProviderKindMismatchError(`Provider "${name}" is a codex provider, which has no base URL.`);
8746
+ }
8747
+ if (!isCodexProvider(existing) && !isEmptyCodexConfig(input.codex)) {
8748
+ throw new ProviderKindMismatchError(`Provider "${name}" is an http provider; codex settings apply only to a codex provider.`);
8749
+ }
8750
+ const shared = {
8751
+ ...input.displayName === void 0 ? {} : { displayName: input.displayName },
8752
+ credential: {
8753
+ sources: input.sources ?? existing.credential.sources,
8754
+ ...target === void 0 ? {} : { target },
8755
+ ...cache === void 0 ? {} : { cache }
8756
+ },
8757
+ env: Object.keys(env).length === 0 ? void 0 : env
8758
+ };
8759
+ let candidate;
8760
+ if (isCodexProvider(existing)) {
8761
+ const codex = {
8762
+ ...existing.codex,
8763
+ ...input.codex,
8764
+ ...existing.codex?.models === void 0 && input.codex?.models === void 0 ? {} : { models: { ...existing.codex?.models, ...input.codex?.models } }
8765
+ };
8766
+ candidate = { ...existing, ...shared, codex: isEmptyCodexConfig(codex) ? void 0 : codex };
8767
+ } else {
8768
+ candidate = { ...existing, ...shared, ...input.baseUrl === void 0 ? {} : { baseUrl: input.baseUrl } };
8769
+ }
8770
+ const parsed = ProviderSchema.safeParse(candidate);
8771
+ if (!parsed.success) {
8772
+ throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
8773
+ }
8774
+ writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
8775
+ return parsed.data;
8754
8776
  }
8755
- function realContentSourcePath() {
8756
- return resolveContentSourcePath({ isSea: (0, import_node_sea.isSea)(), execPath: process.execPath, argv1: process.argv[1] });
8777
+ function removeProvider(paths, name) {
8778
+ if (!providerExists(paths, name)) {
8779
+ throw new ProviderNotFoundError(name);
8780
+ }
8781
+ import_node_fs6.default.rmSync(providerJsonPath(paths, name));
8757
8782
  }
8758
- function realResolveClaudeBinary(isOwnBinary) {
8759
- return (request4) => discoverClaudeBinary({
8760
- listVersionsDir,
8761
- pathDirs: (process.env.PATH ?? "").split(import_node_path22.default.delimiter).filter((dir) => dir !== ""),
8762
- findExecutableInDir,
8763
- isOwnBinary,
8764
- ...request4?.version === void 0 ? {} : { version: request4.version }
8765
- });
8783
+ function cascadeProviderName(cascade) {
8784
+ let name;
8785
+ for (const layer of assembleCascade(cascade).layers) {
8786
+ if (layer.launch?.provider !== void 0) {
8787
+ name = layer.launch.provider;
8788
+ }
8789
+ }
8790
+ return name;
8766
8791
  }
8767
- function realInstalledClaudeVersions() {
8768
- return installedVersions(listVersionsDir(defaultVersionsDir()));
8792
+ function resolveProvider(params) {
8793
+ const cliName = params.cliProvider !== void 0 && params.cliProvider !== "" ? params.cliProvider : void 0;
8794
+ const name = cliName ?? (params.cascade === void 0 ? void 0 : cascadeProviderName(params.cascade));
8795
+ if (name === void 0) {
8796
+ return void 0;
8797
+ }
8798
+ const definition = loadProvider(params.paths.providersDir, name, params.port);
8799
+ if (definition === void 0) {
8800
+ const known = listProviderNames(params.port.readdir, params.paths.providersDir);
8801
+ return {
8802
+ ok: false,
8803
+ status: 1,
8804
+ message: `agent-shim: no provider named "${name}". ` + (known.length > 0 ? `Known providers: ${known.join(", ")}.` : "No providers are defined yet; run `agent-shim provider add`.")
8805
+ };
8806
+ }
8807
+ if (params.credentials === void 0) {
8808
+ return { ok: false, status: 1, message: `agent-shim: provider ${name} needs its credential resolved, but this launcher has no credential port wired` };
8809
+ }
8810
+ const resolution = resolveCredential({ credential: definition.credential, env: params.env, port: params.credentials, subject: `provider ${name}` });
8811
+ if (!resolution.ok) {
8812
+ return { ok: false, status: CREDENTIAL_UNAVAILABLE_EXIT, message: resolution.message };
8813
+ }
8814
+ return { ok: true, provider: { name, definition, credential: resolution.credential }, warnings: resolution.credential.warnings };
8769
8815
  }
8770
8816
 
8771
8817
  // src/checkReport.ts
@@ -9204,42 +9250,14 @@ function loadCheckProvider(paths, name) {
9204
9250
  }
9205
9251
  function collectCheckReport(params) {
9206
9252
  const { paths, cwd } = params;
9207
- const home = import_node_os4.default.homedir();
9253
+ const home = import_node_os5.default.homedir();
9208
9254
  const claudeHome = resolveClaudeHome();
9209
9255
  const read = cosmiconfigReader();
9210
9256
  const classification = loadClassification(paths);
9211
9257
  const loaded = loadCascadeInput({ paths, home, cwd, read });
9212
- const selections = readDirectorySelections(loaded);
9213
9258
  const git = resolveGitBranch(realRunPort, cwd);
9214
- const decidedIdentity = decideIdentity({
9215
- env: params.env,
9216
- argv0Identity: params.identity,
9217
- directoryPinnedIdentity: selections.identity,
9218
- readActiveIdentityFile: () => {
9219
- const raw = realFsPort.readFileUtf8(paths.activeIdentityFile);
9220
- if (raw === void 0) {
9221
- return void 0;
9222
- }
9223
- const trimmed = raw.trim();
9224
- return trimmed === "" ? void 0 : trimmed;
9225
- }
9226
- });
9227
- let poolPick;
9228
- if (decidedIdentity.pool !== void 0) {
9229
- const pools = loaded.globalConfig?.pools ?? {};
9230
- if (pools[decidedIdentity.pool] === void 0) {
9231
- throw new PoolNotFoundError(decidedIdentity.pool);
9232
- }
9233
- poolPick = collectPoolPick({ paths, fs: realFsPort, usageFs: realFarmFs, poolName: decidedIdentity.pool, pools, directory: cwd, nowMs: Date.now() });
9234
- }
9235
- const identityDecision = poolPick?.pick === void 0 ? decidedIdentity : { ...decidedIdentity, name: poolPick.pick };
9236
- const loadedIdentity = identityDecision.name === void 0 ? void 0 : loadIdentity(paths.identitiesDir, identityDecision.name, realFsPort);
9237
- const configProfileDecision = decideConfigProfile({
9238
- env: params.env,
9239
- directoryRuleConfigProfile: selections.configProfile,
9240
- identityDefaultConfigProfile: loadedIdentity?.config.defaultConfigProfile,
9241
- globalDefaultConfigProfile: loaded.globalConfig?.defaultConfigProfile
9242
- });
9259
+ const { selection, identityConfig } = selectLaunch({ paths, cwd, env: params.env, ...params.identity === void 0 ? {} : { identity: params.identity } }, loaded);
9260
+ const { identity: identityDecision, poolPick, configProfile: configProfileDecision } = selection;
9243
9261
  const cascade = loadCascadeInput({
9244
9262
  paths,
9245
9263
  home,
@@ -9270,7 +9288,7 @@ function collectCheckReport(params) {
9270
9288
  ...poolPick === void 0 ? {} : { poolPick },
9271
9289
  ...configProfileDecision.name === void 0 ? {} : { configProfileName: configProfileDecision.name },
9272
9290
  configProfileSource: configProfileDecision.source,
9273
- ...loadedIdentity === void 0 ? {} : { identity: loadedIdentity.config },
9291
+ ...identityConfig === void 0 ? {} : { identity: identityConfig },
9274
9292
  settingsFiles,
9275
9293
  run: realRunPort,
9276
9294
  credentialCache: realCredentialCacheEnv(paths),
@@ -13144,14 +13162,14 @@ function resolvePoolLaunch(params) {
13144
13162
  }
13145
13163
 
13146
13164
  // src/launcher/spawn.ts
13147
- var import_node_os5 = __toESM(require("node:os"), 1);
13165
+ var import_node_os6 = __toESM(require("node:os"), 1);
13148
13166
  var SIGNAL_EXIT_CODE_OFFSET = 128;
13149
13167
  function exitCodeFor(result) {
13150
13168
  if (result.status !== null) {
13151
13169
  return result.status;
13152
13170
  }
13153
13171
  if (result.signal !== null) {
13154
- return SIGNAL_EXIT_CODE_OFFSET + import_node_os5.default.constants.signals[result.signal];
13172
+ return SIGNAL_EXIT_CODE_OFFSET + import_node_os6.default.constants.signals[result.signal];
13155
13173
  }
13156
13174
  return 1;
13157
13175
  }
@@ -13552,7 +13570,7 @@ function runLauncher(params) {
13552
13570
  }
13553
13571
 
13554
13572
  // src/launchWiring.ts
13555
- var import_node_os6 = __toESM(require("node:os"), 1);
13573
+ var import_node_os7 = __toESM(require("node:os"), 1);
13556
13574
  var import_node_crypto12 = require("node:crypto");
13557
13575
 
13558
13576
  // src/ownCli.ts
@@ -13717,7 +13735,7 @@ function realFrontDoorPort(paths, spawnDaemon) {
13717
13735
 
13718
13736
  // src/launchWiring.ts
13719
13737
  function buildFarmRuntime(paths, cwd) {
13720
- const home = import_node_os6.default.homedir();
13738
+ const home = import_node_os7.default.homedir();
13721
13739
  const read = cosmiconfigReader();
13722
13740
  const classification = loadClassification(paths);
13723
13741
  const loaded = loadCascadeInput({ paths, home, cwd, read });
@@ -14054,6 +14072,7 @@ function prepareClaudeLaunch(options) {
14054
14072
  resolveAgentShimHome,
14055
14073
  resolveClaudeHome,
14056
14074
  resolveDecisions,
14075
+ resolveLaunchSelection,
14057
14076
  resolveLayoutPaths,
14058
14077
  resolveSupervisorConfig,
14059
14078
  resyncFarm,