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/cli.cjs +44 -32
- package/dist/index.cjs +1605 -1586
- package/dist/index.mjs +1605 -1587
- package/dist/types/index.d.ts +49 -2
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -5113,7 +5113,7 @@ import { Agent as Agent3, fetch as undiciFetch } from "undici";
|
|
|
5113
5113
|
// package.json
|
|
5114
5114
|
var package_default = {
|
|
5115
5115
|
name: "agent-shim",
|
|
5116
|
-
version: "8.
|
|
5116
|
+
version: "8.41.0",
|
|
5117
5117
|
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.",
|
|
5118
5118
|
license: "MIT",
|
|
5119
5119
|
author: "Joseph Mearman <joseph@mearman.co.uk>",
|
|
@@ -5547,7 +5547,10 @@ function frontDoorRcApiClient(port, ca, token) {
|
|
|
5547
5547
|
import { createORPCClient as createORPCClient2, ORPCError as ORPCError3 } from "@orpc/client";
|
|
5548
5548
|
|
|
5549
5549
|
// src/checkReport.ts
|
|
5550
|
-
import
|
|
5550
|
+
import os6 from "node:os";
|
|
5551
|
+
|
|
5552
|
+
// src/launchSelection.ts
|
|
5553
|
+
import os4 from "node:os";
|
|
5551
5554
|
|
|
5552
5555
|
// src/launcher/cascade.ts
|
|
5553
5556
|
import path11 from "node:path";
|
|
@@ -5719,319 +5722,127 @@ function loadIdentity(identitiesDir, name, fs12) {
|
|
|
5719
5722
|
return loadConfigFile(path12.join(identitiesDir, name, "identity.json"), IdentitySchema, fs12.readConfigFile);
|
|
5720
5723
|
}
|
|
5721
5724
|
|
|
5722
|
-
// src/paths.ts
|
|
5723
|
-
import fs3 from "node:fs";
|
|
5724
|
-
import os3 from "node:os";
|
|
5725
|
-
import path14 from "node:path";
|
|
5726
|
-
|
|
5727
|
-
// src/headroom/state.ts
|
|
5728
|
-
import { createHash as createHash3 } from "node:crypto";
|
|
5729
|
-
import path13 from "node:path";
|
|
5730
|
-
import { z as z5 } from "zod";
|
|
5731
|
-
var HEADROOM_ANTHROPIC_UPSTREAM = "https://api.anthropic.com";
|
|
5732
|
-
var HEADROOM_STATE_SCHEMA_VERSION = 2;
|
|
5733
|
-
var HeadroomStateSchema = z5.strictObject({
|
|
5734
|
-
/** The supervisor process owning the daemon. Alive means someone is keeping headroom running. */
|
|
5735
|
-
supervisorPid: z5.number().int().positive().optional(),
|
|
5736
|
-
/** The headroom proxy process itself. Absent while the supervisor is between restarts. */
|
|
5737
|
-
headroomPid: z5.number().int().positive().optional(),
|
|
5738
|
-
/**
|
|
5739
|
-
* 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.
|
|
5740
|
-
*/
|
|
5741
|
-
socketPath: z5.string().min(1).optional(),
|
|
5742
|
-
/** The `headroom --version` output of the running install. */
|
|
5743
|
-
version: z5.string().optional(),
|
|
5744
|
-
/** Hash of the allowlist the running proxy was started with, so a provider-file change is detected as drift. */
|
|
5745
|
-
allowlistHash: z5.string().optional(),
|
|
5746
|
-
/** Hash of the token-saving settings the running proxy was started with, so a settings change is detected as drift. */
|
|
5747
|
-
settingsHash: z5.string().optional(),
|
|
5748
|
-
/** The install spec the running install was last installed from, so a `source` config change is detected as drift. */
|
|
5749
|
-
installedSource: z5.string().optional(),
|
|
5750
|
-
/** The last fatal error, kept across shutdowns so `headroom status` can explain a daemon that is not running. */
|
|
5751
|
-
lastError: z5.string().optional()
|
|
5752
|
-
});
|
|
5753
|
-
var HeadroomSessionSchema = z5.strictObject({ pid: z5.number().int().positive(), startedAt: z5.number(), supervisorPid: z5.number().int().positive() });
|
|
5754
|
-
var HeadroomLockSchema = z5.strictObject({ pid: z5.number().int().positive(), at: z5.number() });
|
|
5755
|
-
function parseJson(raw, schema) {
|
|
5756
|
-
if (raw === void 0 || raw.trim() === "") {
|
|
5757
|
-
return void 0;
|
|
5758
|
-
}
|
|
5759
|
-
const parsed = schema.safeParse(JSON.parse(raw));
|
|
5760
|
-
return parsed.success ? parsed.data : void 0;
|
|
5761
|
-
}
|
|
5762
|
-
function readHeadroomState(fs12, stateFile) {
|
|
5763
|
-
try {
|
|
5764
|
-
return parseJson(fs12.readFileUtf8(stateFile), HeadroomStateSchema);
|
|
5765
|
-
} catch {
|
|
5766
|
-
return void 0;
|
|
5767
|
-
}
|
|
5768
|
-
}
|
|
5769
|
-
function writeHeadroomState(fs12, stateFile, state) {
|
|
5770
|
-
fs12.mkdirp(path13.dirname(stateFile));
|
|
5771
|
-
fs12.writeFilePrivate(stateFile, `${JSON.stringify(state, null, 2)}
|
|
5772
|
-
`);
|
|
5773
|
-
}
|
|
5774
|
-
function headroomAllowlist(providers) {
|
|
5775
|
-
const urls = /* @__PURE__ */ new Set([HEADROOM_ANTHROPIC_UPSTREAM]);
|
|
5776
|
-
for (const provider of providers) {
|
|
5777
|
-
urls.add(provider.baseUrl);
|
|
5778
|
-
}
|
|
5779
|
-
return [...urls].sort();
|
|
5780
|
-
}
|
|
5781
|
-
function headroomUpstreams(providers, frontDoorOrigin2) {
|
|
5782
|
-
const upstreams = providers.filter((provider) => !isCodexProvider(provider)).map((provider) => ({ baseUrl: provider.baseUrl }));
|
|
5783
|
-
if (providers.length > 0 && frontDoorOrigin2 !== void 0) {
|
|
5784
|
-
upstreams.push({ baseUrl: frontDoorOrigin2 });
|
|
5785
|
-
}
|
|
5786
|
-
return upstreams;
|
|
5787
|
-
}
|
|
5788
|
-
function hashAllowlist(allowlist) {
|
|
5789
|
-
return createHash3("sha256").update(allowlist.join("\n")).digest("hex");
|
|
5790
|
-
}
|
|
5791
|
-
function readAllProviders(fs12, providersDir) {
|
|
5792
|
-
const result = [];
|
|
5793
|
-
for (const name of fs12.readdir(providersDir)) {
|
|
5794
|
-
if (!name.endsWith(".json")) {
|
|
5795
|
-
continue;
|
|
5796
|
-
}
|
|
5797
|
-
try {
|
|
5798
|
-
const parsed = parseJson(fs12.readFileUtf8(path13.join(providersDir, name)), ProviderSchema);
|
|
5799
|
-
if (parsed !== void 0) {
|
|
5800
|
-
result.push({ name: name.slice(0, -".json".length), provider: parsed });
|
|
5801
|
-
}
|
|
5802
|
-
} catch {
|
|
5803
|
-
continue;
|
|
5804
|
-
}
|
|
5805
|
-
}
|
|
5806
|
-
return result;
|
|
5807
|
-
}
|
|
5808
|
-
function writeSession(fs12, sessionsDir, session) {
|
|
5809
|
-
fs12.mkdirp(sessionsDir);
|
|
5810
|
-
fs12.writeFileUtf8(path13.join(sessionsDir, `${String(session.pid)}.json`), `${JSON.stringify(session, null, 2)}
|
|
5811
|
-
`);
|
|
5812
|
-
}
|
|
5813
|
-
function removeSession(fs12, sessionsDir, pid) {
|
|
5814
|
-
fs12.removeRecursive(path13.join(sessionsDir, `${String(pid)}.json`));
|
|
5815
|
-
}
|
|
5816
|
-
function listSessions(fs12, sessionsDir) {
|
|
5817
|
-
const sessions = [];
|
|
5818
|
-
for (const name of fs12.readdir(sessionsDir)) {
|
|
5819
|
-
if (!name.endsWith(".json")) {
|
|
5820
|
-
continue;
|
|
5821
|
-
}
|
|
5822
|
-
try {
|
|
5823
|
-
const parsed = parseJson(fs12.readFileUtf8(path13.join(sessionsDir, name)), HeadroomSessionSchema);
|
|
5824
|
-
if (parsed !== void 0) {
|
|
5825
|
-
sessions.push(parsed);
|
|
5826
|
-
}
|
|
5827
|
-
} catch {
|
|
5828
|
-
continue;
|
|
5829
|
-
}
|
|
5830
|
-
}
|
|
5831
|
-
return sessions.sort((a, b) => a.pid - b.pid);
|
|
5832
|
-
}
|
|
5833
|
-
function sessionsForSupervisor(fs12, sessionsDir, supervisorPid) {
|
|
5834
|
-
return listSessions(fs12, sessionsDir).filter((session) => session.supervisorPid === supervisorPid);
|
|
5835
|
-
}
|
|
5836
|
-
function pruneDeadSessions(fs12, sessionsDir, isRunning) {
|
|
5837
|
-
const removed = [];
|
|
5838
|
-
for (const session of listSessions(fs12, sessionsDir)) {
|
|
5839
|
-
if (!isRunning(session.pid)) {
|
|
5840
|
-
removeSession(fs12, sessionsDir, session.pid);
|
|
5841
|
-
removed.push(session.pid);
|
|
5842
|
-
}
|
|
5843
|
-
}
|
|
5844
|
-
return removed;
|
|
5845
|
-
}
|
|
5846
|
-
function readStartLock(fs12, lockFile) {
|
|
5847
|
-
try {
|
|
5848
|
-
return parseJson(fs12.readFileUtf8(lockFile), HeadroomLockSchema);
|
|
5849
|
-
} catch {
|
|
5850
|
-
return void 0;
|
|
5851
|
-
}
|
|
5852
|
-
}
|
|
5853
|
-
|
|
5854
|
-
// src/paths.ts
|
|
5855
|
-
function resolveAgentShimHome(env = process.env, home = os3.homedir(), isDirectory = isExistingDirectory) {
|
|
5856
|
-
const fromEnv = env.AGENT_SHIM_HOME;
|
|
5857
|
-
if (fromEnv !== void 0 && fromEnv !== "") {
|
|
5858
|
-
return fromEnv;
|
|
5859
|
-
}
|
|
5860
|
-
const current = path14.join(home, HOME_DIRNAME);
|
|
5861
|
-
const legacy = path14.join(home, LEGACY_HOME_DIRNAME);
|
|
5862
|
-
return !isDirectory(current) && isDirectory(legacy) ? legacy : current;
|
|
5863
|
-
}
|
|
5864
|
-
function isExistingDirectory(candidate) {
|
|
5865
|
-
return fs3.statSync(candidate, { throwIfNoEntry: false })?.isDirectory() ?? false;
|
|
5866
|
-
}
|
|
5867
|
-
function resolveClaudeHome() {
|
|
5868
|
-
const fromEnv = process.env.AGENT_SHIM_CLAUDE_HOME;
|
|
5869
|
-
if (fromEnv !== void 0 && fromEnv !== "") {
|
|
5870
|
-
return fromEnv;
|
|
5871
|
-
}
|
|
5872
|
-
return path14.join(os3.homedir(), ".claude");
|
|
5873
|
-
}
|
|
5874
|
-
function buildLayoutPaths(root) {
|
|
5875
|
-
return {
|
|
5876
|
-
root,
|
|
5877
|
-
identitiesDir: path14.join(root, "identities"),
|
|
5878
|
-
configProfilesDir: path14.join(root, "config-profiles"),
|
|
5879
|
-
providersDir: path14.join(root, "providers"),
|
|
5880
|
-
directoryRulesFile: path14.join(root, "directory-rules.json"),
|
|
5881
|
-
activeIdentityFile: path14.join(root, "active-identity"),
|
|
5882
|
-
globalConfigFile: path14.join(root, "config.json"),
|
|
5883
|
-
categoriesLocalFile: path14.join(root, "categories.local.json"),
|
|
5884
|
-
claudeShimFile: path14.join(root, "claude-shim.json"),
|
|
5885
|
-
headroomDir: path14.join(root, "headroom"),
|
|
5886
|
-
headroomStateFile: path14.join(root, "headroom", `state.v${String(HEADROOM_STATE_SCHEMA_VERSION)}.json`),
|
|
5887
|
-
headroomSocketDir: path14.join(root, "headroom", "run"),
|
|
5888
|
-
headroomLockFile: path14.join(root, "headroom", "start.lock"),
|
|
5889
|
-
headroomSessionsDir: path14.join(root, "headroom", "sessions"),
|
|
5890
|
-
logsDir: path14.join(root, "logs"),
|
|
5891
|
-
headroomLogPath: path14.join(root, "logs", "headroom.log"),
|
|
5892
|
-
frontdoorDir: path14.join(root, "frontdoor"),
|
|
5893
|
-
frontdoorCaDir: path14.join(root, "frontdoor", "ca"),
|
|
5894
|
-
frontdoorCaCertFile: path14.join(root, "frontdoor", "ca", "ca.pem"),
|
|
5895
|
-
frontdoorCaKeyFile: path14.join(root, "frontdoor", "ca", "ca.key"),
|
|
5896
|
-
frontdoorCaBundlesDir: path14.join(root, "frontdoor", "ca", "bundles"),
|
|
5897
|
-
chatgptSignInFile: path14.join(root, "codex", "chatgpt-sign-in.json"),
|
|
5898
|
-
frontdoorStateFile: path14.join(root, "frontdoor", "state.json"),
|
|
5899
|
-
frontdoorLockFile: path14.join(root, "frontdoor", "start.lock"),
|
|
5900
|
-
frontdoorControlTokenFile: path14.join(root, "frontdoor", "control-token"),
|
|
5901
|
-
frontdoorSessionsDir: path14.join(root, "frontdoor", "sessions"),
|
|
5902
|
-
frontdoorRcCredentialsDir: path14.join(root, "frontdoor", "rc-credentials"),
|
|
5903
|
-
frontdoorLogPath: path14.join(root, "logs", "frontdoor.log"),
|
|
5904
|
-
usageDir: path14.join(root, "usage"),
|
|
5905
|
-
usageLogDir: path14.join(root, "usage", "log"),
|
|
5906
|
-
usageSnapshotsDir: path14.join(root, "usage", "snapshots"),
|
|
5907
|
-
usagePicksFile: path14.join(root, "usage", "picks.json")
|
|
5908
|
-
};
|
|
5909
|
-
}
|
|
5910
|
-
function resolveLayoutPaths() {
|
|
5911
|
-
return buildLayoutPaths(resolveAgentShimHome());
|
|
5912
|
-
}
|
|
5913
|
-
|
|
5914
5725
|
// src/usage/poolPick.ts
|
|
5915
|
-
import
|
|
5916
|
-
import { z as
|
|
5726
|
+
import path15 from "node:path";
|
|
5727
|
+
import { z as z8 } from "zod";
|
|
5917
5728
|
|
|
5918
5729
|
// src/usage/account.ts
|
|
5919
|
-
import
|
|
5730
|
+
import path13 from "node:path";
|
|
5920
5731
|
|
|
5921
5732
|
// src/usage/schema.ts
|
|
5922
|
-
import { z as
|
|
5733
|
+
import { z as z5 } from "zod";
|
|
5923
5734
|
var USAGE_SCHEMA_VERSION = 1;
|
|
5924
|
-
var VersionSchema =
|
|
5925
|
-
var InstantSchema =
|
|
5926
|
-
var TokenCountSchema =
|
|
5927
|
-
var TokenUsageSchema =
|
|
5735
|
+
var VersionSchema = z5.literal(USAGE_SCHEMA_VERSION);
|
|
5736
|
+
var InstantSchema = z5.iso.datetime();
|
|
5737
|
+
var TokenCountSchema = z5.number().int().nonnegative();
|
|
5738
|
+
var TokenUsageSchema = z5.strictObject({
|
|
5928
5739
|
inputTokens: TokenCountSchema.optional(),
|
|
5929
5740
|
outputTokens: TokenCountSchema.optional(),
|
|
5930
5741
|
cacheCreationInputTokens: TokenCountSchema.optional(),
|
|
5931
5742
|
cacheReadInputTokens: TokenCountSchema.optional()
|
|
5932
5743
|
});
|
|
5933
5744
|
var LIMIT_KINDS = ["rate-limited", "quota-exhausted"];
|
|
5934
|
-
var LimitClassificationSchema =
|
|
5935
|
-
kind:
|
|
5745
|
+
var LimitClassificationSchema = z5.strictObject({
|
|
5746
|
+
kind: z5.enum(LIMIT_KINDS),
|
|
5936
5747
|
/** Seconds to wait before retrying, from the response's `retry-after`. */
|
|
5937
|
-
retryAfterSeconds:
|
|
5748
|
+
retryAfterSeconds: z5.number().nonnegative().optional(),
|
|
5938
5749
|
/** When the limiting window resets, from the upstream's own reset header. */
|
|
5939
5750
|
resetAt: InstantSchema.optional(),
|
|
5940
5751
|
/** The window the upstream named as the binding one (`anthropic-ratelimit-unified-representative-claim`), such as `five_hour` or `seven_day`. */
|
|
5941
|
-
window:
|
|
5752
|
+
window: z5.string().optional(),
|
|
5942
5753
|
/** 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. */
|
|
5943
|
-
evidence:
|
|
5754
|
+
evidence: z5.array(z5.string())
|
|
5944
5755
|
});
|
|
5945
|
-
var UsageRecordSchema =
|
|
5756
|
+
var UsageRecordSchema = z5.strictObject({
|
|
5946
5757
|
schemaVersion: VersionSchema,
|
|
5947
5758
|
/** When the front door received the request. */
|
|
5948
5759
|
at: InstantSchema,
|
|
5949
5760
|
/** The launching identity, when the launch resolved one. */
|
|
5950
|
-
identity:
|
|
5761
|
+
identity: z5.string().optional(),
|
|
5951
5762
|
/** `anthropic` for an OAuth session's request to Claude Code's own API, otherwise the provider's name. */
|
|
5952
|
-
provider:
|
|
5763
|
+
provider: z5.string(),
|
|
5953
5764
|
/** The launch's session id, generated per launch. */
|
|
5954
|
-
sessionId:
|
|
5765
|
+
sessionId: z5.string().optional(),
|
|
5955
5766
|
/** The project the launch ran in (its git repository root), when the launch sent one: headroom launches do. */
|
|
5956
|
-
project:
|
|
5767
|
+
project: z5.string().optional(),
|
|
5957
5768
|
/** The front-door route that served the request. */
|
|
5958
|
-
route:
|
|
5959
|
-
method:
|
|
5769
|
+
route: z5.string(),
|
|
5770
|
+
method: z5.string(),
|
|
5960
5771
|
/** The API path as the upstream sees it (`/v1/messages`), without the provider prefix or a query string. */
|
|
5961
|
-
endpoint:
|
|
5772
|
+
endpoint: z5.string(),
|
|
5962
5773
|
/** The model the response named. */
|
|
5963
|
-
model:
|
|
5964
|
-
status:
|
|
5774
|
+
model: z5.string().optional(),
|
|
5775
|
+
status: z5.number().int(),
|
|
5965
5776
|
/** Milliseconds from the request's arrival to its response head. */
|
|
5966
|
-
latencyMs:
|
|
5777
|
+
latencyMs: z5.number().int().nonnegative(),
|
|
5967
5778
|
/** Milliseconds from the request's arrival to the end of its response body. */
|
|
5968
|
-
durationMs:
|
|
5779
|
+
durationMs: z5.number().int().nonnegative(),
|
|
5969
5780
|
/** Whether the client received the whole body. */
|
|
5970
|
-
outcome:
|
|
5781
|
+
outcome: z5.enum(["completed", "aborted"]),
|
|
5971
5782
|
usage: TokenUsageSchema.optional(),
|
|
5972
5783
|
/** The response's quota and rate-limit headers, by name (see `isRateLimitHeader`). */
|
|
5973
|
-
rateLimitHeaders:
|
|
5784
|
+
rateLimitHeaders: z5.record(z5.string(), z5.string()).optional(),
|
|
5974
5785
|
limit: LimitClassificationSchema.optional(),
|
|
5975
5786
|
/** The upstream's request id (`request-id`), for matching a record to the provider's own logs. */
|
|
5976
|
-
requestId:
|
|
5787
|
+
requestId: z5.string().optional()
|
|
5977
5788
|
});
|
|
5978
|
-
var QuotaWindowSchema =
|
|
5789
|
+
var QuotaWindowSchema = z5.strictObject({
|
|
5979
5790
|
/** The fraction of the window used, as the upstream reports it (0 to 1). */
|
|
5980
|
-
utilization:
|
|
5791
|
+
utilization: z5.number().nonnegative().optional(),
|
|
5981
5792
|
resetsAt: InstantSchema.optional(),
|
|
5982
5793
|
/** The window's own status (`allowed`, `allowed_warning`, `rejected`). */
|
|
5983
|
-
status:
|
|
5794
|
+
status: z5.string().optional()
|
|
5984
5795
|
});
|
|
5985
|
-
var UnifiedRateLimitSchema =
|
|
5796
|
+
var UnifiedRateLimitSchema = z5.strictObject({
|
|
5986
5797
|
/** The overall status: `allowed`, `allowed_warning` or `rejected`. */
|
|
5987
|
-
status:
|
|
5798
|
+
status: z5.string().optional(),
|
|
5988
5799
|
fiveHour: QuotaWindowSchema.optional(),
|
|
5989
5800
|
sevenDay: QuotaWindowSchema.optional(),
|
|
5990
5801
|
/** The window that currently binds. */
|
|
5991
|
-
representativeClaim:
|
|
5802
|
+
representativeClaim: z5.string().optional(),
|
|
5992
5803
|
/** When the binding window resets. */
|
|
5993
5804
|
resetAt: InstantSchema.optional(),
|
|
5994
5805
|
/** Whether usage beyond the plan (extra usage) is available: `allowed` or `rejected`. */
|
|
5995
|
-
overageStatus:
|
|
5806
|
+
overageStatus: z5.string().optional()
|
|
5996
5807
|
});
|
|
5997
|
-
var RateLimitStateSchema =
|
|
5808
|
+
var RateLimitStateSchema = z5.strictObject({
|
|
5998
5809
|
observedAt: InstantSchema,
|
|
5999
5810
|
/** Every quota and rate-limit header on that response, by name. */
|
|
6000
|
-
headers:
|
|
5811
|
+
headers: z5.record(z5.string(), z5.string()),
|
|
6001
5812
|
unified: UnifiedRateLimitSchema.optional()
|
|
6002
5813
|
});
|
|
6003
5814
|
var LimitEventSchema = LimitClassificationSchema.extend({
|
|
6004
5815
|
observedAt: InstantSchema,
|
|
6005
|
-
status:
|
|
5816
|
+
status: z5.number().int()
|
|
6006
5817
|
});
|
|
6007
|
-
var ProviderQuotaWindowSchema =
|
|
5818
|
+
var ProviderQuotaWindowSchema = z5.strictObject({
|
|
6008
5819
|
/** What the window measures, in the provider's terms (`tokens`, `tool-calls`). */
|
|
6009
|
-
measures:
|
|
5820
|
+
measures: z5.string(),
|
|
6010
5821
|
/** 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. */
|
|
6011
|
-
periodMs:
|
|
5822
|
+
periodMs: z5.number().int().positive().optional(),
|
|
6012
5823
|
/** The provider's own description of the period when `periodMs` could not be derived, such as `unit 4 x 1`. */
|
|
6013
|
-
period:
|
|
5824
|
+
period: z5.string().optional(),
|
|
6014
5825
|
/** The fraction of the window used (0 to 1). */
|
|
6015
|
-
utilization:
|
|
5826
|
+
utilization: z5.number().nonnegative(),
|
|
6016
5827
|
resetsAt: InstantSchema.optional(),
|
|
6017
5828
|
/** Absolute counts, when the provider reports them for this window. */
|
|
6018
|
-
limit:
|
|
6019
|
-
used:
|
|
6020
|
-
remaining:
|
|
5829
|
+
limit: z5.number().nonnegative().optional(),
|
|
5830
|
+
used: z5.number().nonnegative().optional(),
|
|
5831
|
+
remaining: z5.number().nonnegative().optional()
|
|
6021
5832
|
});
|
|
6022
|
-
var ProviderQuotaSchema =
|
|
5833
|
+
var ProviderQuotaSchema = z5.strictObject({
|
|
6023
5834
|
observedAt: InstantSchema,
|
|
6024
5835
|
/** Which usage endpoint produced it (`z.ai`). */
|
|
6025
|
-
source:
|
|
5836
|
+
source: z5.string(),
|
|
6026
5837
|
/** The subscription level the endpoint reports, when it does. */
|
|
6027
|
-
level:
|
|
6028
|
-
windows:
|
|
5838
|
+
level: z5.string().optional(),
|
|
5839
|
+
windows: z5.array(ProviderQuotaWindowSchema)
|
|
6029
5840
|
});
|
|
6030
|
-
var ProviderUsageStateSchema =
|
|
5841
|
+
var ProviderUsageStateSchema = z5.strictObject({
|
|
6031
5842
|
/** 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. */
|
|
6032
5843
|
lastRequestAt: InstantSchema.optional(),
|
|
6033
|
-
lastStatus:
|
|
6034
|
-
lastModel:
|
|
5844
|
+
lastStatus: z5.number().int().optional(),
|
|
5845
|
+
lastModel: z5.string().optional(),
|
|
6035
5846
|
/** The latest rate-limit state, from the last response that carried any rate-limit header. */
|
|
6036
5847
|
rateLimit: RateLimitStateSchema.optional(),
|
|
6037
5848
|
/** The latest classified refusal. It stays after later successes, so a reader compares its `resetAt` (or `observedAt` plus `retryAfterSeconds`) with the current time. */
|
|
@@ -6039,31 +5850,31 @@ var ProviderUsageStateSchema = z6.strictObject({
|
|
|
6039
5850
|
/** The latest quota the provider's usage endpoint reported, for providers whose quota is not visible in response headers. */
|
|
6040
5851
|
quota: ProviderQuotaSchema.optional()
|
|
6041
5852
|
});
|
|
6042
|
-
var AccountMetadataSchema =
|
|
6043
|
-
accountUuid:
|
|
6044
|
-
emailAddress:
|
|
6045
|
-
displayName:
|
|
6046
|
-
organizationUuid:
|
|
6047
|
-
organizationName:
|
|
6048
|
-
organizationType:
|
|
6049
|
-
organizationRole:
|
|
6050
|
-
workspaceRole:
|
|
6051
|
-
billingType:
|
|
6052
|
-
seatTier:
|
|
6053
|
-
organizationRateLimitTier:
|
|
6054
|
-
userRateLimitTier:
|
|
6055
|
-
hasExtraUsageEnabled:
|
|
6056
|
-
subscriptionCreatedAt:
|
|
6057
|
-
accountCreatedAt:
|
|
5853
|
+
var AccountMetadataSchema = z5.strictObject({
|
|
5854
|
+
accountUuid: z5.string().optional(),
|
|
5855
|
+
emailAddress: z5.string().optional(),
|
|
5856
|
+
displayName: z5.string().optional(),
|
|
5857
|
+
organizationUuid: z5.string().optional(),
|
|
5858
|
+
organizationName: z5.string().optional(),
|
|
5859
|
+
organizationType: z5.string().optional(),
|
|
5860
|
+
organizationRole: z5.string().optional(),
|
|
5861
|
+
workspaceRole: z5.string().optional(),
|
|
5862
|
+
billingType: z5.string().optional(),
|
|
5863
|
+
seatTier: z5.string().optional(),
|
|
5864
|
+
organizationRateLimitTier: z5.string().optional(),
|
|
5865
|
+
userRateLimitTier: z5.string().optional(),
|
|
5866
|
+
hasExtraUsageEnabled: z5.boolean().optional(),
|
|
5867
|
+
subscriptionCreatedAt: z5.string().optional(),
|
|
5868
|
+
accountCreatedAt: z5.string().optional()
|
|
6058
5869
|
});
|
|
6059
|
-
var UsageSnapshotSchema =
|
|
5870
|
+
var UsageSnapshotSchema = z5.strictObject({
|
|
6060
5871
|
schemaVersion: VersionSchema,
|
|
6061
|
-
identity:
|
|
5872
|
+
identity: z5.string(),
|
|
6062
5873
|
updatedAt: InstantSchema,
|
|
6063
5874
|
/** The identity's account metadata as of `updatedAt`. */
|
|
6064
5875
|
account: AccountMetadataSchema.optional(),
|
|
6065
5876
|
/** Latest state per provider, keyed by provider name (`anthropic` for OAuth sessions). */
|
|
6066
|
-
providers:
|
|
5877
|
+
providers: z5.record(z5.string(), ProviderUsageStateSchema)
|
|
6067
5878
|
});
|
|
6068
5879
|
|
|
6069
5880
|
// src/usage/account.ts
|
|
@@ -6083,7 +5894,7 @@ function isIdentityName(name) {
|
|
|
6083
5894
|
return IdentitySchema.shape.name.safeParse(name).success;
|
|
6084
5895
|
}
|
|
6085
5896
|
function claudeJsonPath(identitiesDir, identity) {
|
|
6086
|
-
return
|
|
5897
|
+
return path13.join(identitiesDir, identity, CLAUDE_JSON);
|
|
6087
5898
|
}
|
|
6088
5899
|
function readAccountMetadata(fs12, identitiesDir, identity) {
|
|
6089
5900
|
if (!isIdentityName(identity)) {
|
|
@@ -6122,20 +5933,20 @@ function readAccountMetadata(fs12, identitiesDir, identity) {
|
|
|
6122
5933
|
}
|
|
6123
5934
|
|
|
6124
5935
|
// src/usage/plan.ts
|
|
6125
|
-
import { z as
|
|
6126
|
-
var PlanClassSchema =
|
|
6127
|
-
|
|
6128
|
-
kind:
|
|
5936
|
+
import { z as z6 } from "zod";
|
|
5937
|
+
var PlanClassSchema = z6.union([
|
|
5938
|
+
z6.strictObject({
|
|
5939
|
+
kind: z6.literal("subscription"),
|
|
6129
5940
|
/** The plan's size as a multiple of the smallest subscription tier (Max 5x is 5, Max 20x is 20). */
|
|
6130
|
-
capacity:
|
|
5941
|
+
capacity: z6.number().positive(),
|
|
6131
5942
|
/** False when the tier string named no multiplier and no tier this project knows, so `capacity` is the neutral 1 rather than a reading. */
|
|
6132
|
-
recognised:
|
|
5943
|
+
recognised: z6.boolean(),
|
|
6133
5944
|
/** The tier string the classification rests on, when the account had one. */
|
|
6134
|
-
tier:
|
|
5945
|
+
tier: z6.string().optional()
|
|
6135
5946
|
}),
|
|
6136
|
-
|
|
6137
|
-
kind:
|
|
6138
|
-
tier:
|
|
5947
|
+
z6.strictObject({
|
|
5948
|
+
kind: z6.literal("pay-per-use"),
|
|
5949
|
+
tier: z6.string().optional()
|
|
6139
5950
|
})
|
|
6140
5951
|
]);
|
|
6141
5952
|
var TIER_MULTIPLIER = /(?:^|_)(\d+)x$/;
|
|
@@ -6204,17 +6015,17 @@ function parseUnifiedRateLimit(headers) {
|
|
|
6204
6015
|
}
|
|
6205
6016
|
|
|
6206
6017
|
// src/usage/scan.ts
|
|
6207
|
-
import { z as
|
|
6208
|
-
var AnthropicUsageSchema =
|
|
6209
|
-
input_tokens:
|
|
6210
|
-
output_tokens:
|
|
6211
|
-
cache_creation_input_tokens:
|
|
6212
|
-
cache_read_input_tokens:
|
|
6018
|
+
import { z as z7 } from "zod";
|
|
6019
|
+
var AnthropicUsageSchema = z7.looseObject({
|
|
6020
|
+
input_tokens: z7.number().int().nonnegative().nullish(),
|
|
6021
|
+
output_tokens: z7.number().int().nonnegative().nullish(),
|
|
6022
|
+
cache_creation_input_tokens: z7.number().int().nonnegative().nullish(),
|
|
6023
|
+
cache_read_input_tokens: z7.number().int().nonnegative().nullish()
|
|
6213
6024
|
});
|
|
6214
|
-
var ErrorObjectSchema =
|
|
6215
|
-
type:
|
|
6216
|
-
code:
|
|
6217
|
-
message:
|
|
6025
|
+
var ErrorObjectSchema = z7.looseObject({
|
|
6026
|
+
type: z7.string().optional(),
|
|
6027
|
+
code: z7.union([z7.string(), z7.number()]).optional(),
|
|
6028
|
+
message: z7.string().optional()
|
|
6218
6029
|
});
|
|
6219
6030
|
|
|
6220
6031
|
// src/usage/middleware.ts
|
|
@@ -6501,7 +6312,7 @@ function rankPool(input) {
|
|
|
6501
6312
|
}
|
|
6502
6313
|
|
|
6503
6314
|
// src/usage/read.ts
|
|
6504
|
-
import
|
|
6315
|
+
import path14 from "node:path";
|
|
6505
6316
|
var SEGMENT_RE = /^(\d{4}-\d{2}-\d{2})\.(\d+)\.jsonl$/;
|
|
6506
6317
|
var DAY_MS = 864e5;
|
|
6507
6318
|
function listLogSegments(fs12, logDir) {
|
|
@@ -6522,7 +6333,7 @@ function readUsageLog(fs12, logDir, options = {}) {
|
|
|
6522
6333
|
if (sinceMs !== void 0 && segment.endsAt <= sinceMs) {
|
|
6523
6334
|
continue;
|
|
6524
6335
|
}
|
|
6525
|
-
const contents = fs12.readFileUtf8(
|
|
6336
|
+
const contents = fs12.readFileUtf8(path14.join(logDir, segment.name));
|
|
6526
6337
|
if (contents === void 0) {
|
|
6527
6338
|
continue;
|
|
6528
6339
|
}
|
|
@@ -6564,7 +6375,7 @@ function snapshotPath(snapshotsDir, identity) {
|
|
|
6564
6375
|
if (!isIdentityName(identity)) {
|
|
6565
6376
|
throw new Error(`"${identity}" is not a valid identity name.`);
|
|
6566
6377
|
}
|
|
6567
|
-
return
|
|
6378
|
+
return path14.join(snapshotsDir, `${identity}.json`);
|
|
6568
6379
|
}
|
|
6569
6380
|
function readUsageSnapshot(fs12, snapshotsDir, identity) {
|
|
6570
6381
|
const file = snapshotPath(snapshotsDir, identity);
|
|
@@ -6611,10 +6422,10 @@ var PoolGraphError = class extends CliError {
|
|
|
6611
6422
|
}
|
|
6612
6423
|
};
|
|
6613
6424
|
var SCHEMA_VERSION = 1;
|
|
6614
|
-
var PickEntrySchema =
|
|
6615
|
-
var PicksFileSchema =
|
|
6616
|
-
schemaVersion:
|
|
6617
|
-
picks:
|
|
6425
|
+
var PickEntrySchema = z8.strictObject({ identity: z8.string().min(1), at: z8.iso.datetime() });
|
|
6426
|
+
var PicksFileSchema = z8.strictObject({
|
|
6427
|
+
schemaVersion: z8.literal(SCHEMA_VERSION),
|
|
6428
|
+
picks: z8.record(z8.string().min(1), PickEntrySchema)
|
|
6618
6429
|
});
|
|
6619
6430
|
function errorMessage(error) {
|
|
6620
6431
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -6742,7 +6553,7 @@ function recordStickyPick(fs12, picksFile, directory, identity, nowMs) {
|
|
|
6742
6553
|
const current = readPicksFile(fs12, picksFile).file.picks;
|
|
6743
6554
|
const kept = Object.entries(current).filter(([, entry]) => nowMs - Date.parse(entry.at) <= USAGE_RETENTION_MS);
|
|
6744
6555
|
const next = { schemaVersion: SCHEMA_VERSION, picks: { ...Object.fromEntries(kept), [directory]: { identity, at: new Date(nowMs).toISOString() } } };
|
|
6745
|
-
fs12.mkdirPrivate(
|
|
6556
|
+
fs12.mkdirPrivate(path15.dirname(picksFile));
|
|
6746
6557
|
fs12.writeFilePrivate(picksFile, `${JSON.stringify(next, null, 2)}
|
|
6747
6558
|
`);
|
|
6748
6559
|
}
|
|
@@ -6784,8 +6595,8 @@ function collectPoolPick(params) {
|
|
|
6784
6595
|
}
|
|
6785
6596
|
|
|
6786
6597
|
// src/configProfilesStore.ts
|
|
6787
|
-
import
|
|
6788
|
-
import
|
|
6598
|
+
import fs3 from "node:fs";
|
|
6599
|
+
import path16 from "node:path";
|
|
6789
6600
|
var ProfileNotFoundError = class extends CliError {
|
|
6790
6601
|
constructor(profileName) {
|
|
6791
6602
|
super(`No configuration profile named "${profileName}". Run \`agent-shim profile add ${profileName}\` first.`);
|
|
@@ -6811,10 +6622,10 @@ var InvalidCategoryNameError = class extends CliError {
|
|
|
6811
6622
|
categoryName;
|
|
6812
6623
|
};
|
|
6813
6624
|
function profileJsonPath(paths, name) {
|
|
6814
|
-
return
|
|
6625
|
+
return path16.join(paths.configProfilesDir, `${name}.json`);
|
|
6815
6626
|
}
|
|
6816
6627
|
function profileExists(paths, name) {
|
|
6817
|
-
return
|
|
6628
|
+
return fs3.existsSync(profileJsonPath(paths, name));
|
|
6818
6629
|
}
|
|
6819
6630
|
function requireProfileExists(paths, name) {
|
|
6820
6631
|
if (!profileExists(paths, name)) {
|
|
@@ -6840,10 +6651,10 @@ function createProfile(paths, name, extendsList, description) {
|
|
|
6840
6651
|
return parsed.data;
|
|
6841
6652
|
}
|
|
6842
6653
|
function listProfiles(paths) {
|
|
6843
|
-
if (!
|
|
6654
|
+
if (!fs3.existsSync(paths.configProfilesDir)) {
|
|
6844
6655
|
return [];
|
|
6845
6656
|
}
|
|
6846
|
-
const names =
|
|
6657
|
+
const names = fs3.readdirSync(paths.configProfilesDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length)).sort();
|
|
6847
6658
|
const result = [];
|
|
6848
6659
|
for (const name of names) {
|
|
6849
6660
|
const profile = readProfile(paths, name);
|
|
@@ -6906,7 +6717,7 @@ function setProfileMetadata(paths, name, patch) {
|
|
|
6906
6717
|
}
|
|
6907
6718
|
function removeProfile(paths, name) {
|
|
6908
6719
|
requireProfileExists(paths, name);
|
|
6909
|
-
|
|
6720
|
+
fs3.rmSync(profileJsonPath(paths, name));
|
|
6910
6721
|
}
|
|
6911
6722
|
|
|
6912
6723
|
// src/poolStore.ts
|
|
@@ -7008,1450 +6819,1684 @@ function removePool(paths, name) {
|
|
|
7008
6819
|
writePools(paths, remaining);
|
|
7009
6820
|
}
|
|
7010
6821
|
|
|
7011
|
-
// src/
|
|
7012
|
-
import { spawnSync } from "node:child_process";
|
|
7013
|
-
import
|
|
6822
|
+
// src/realPorts.ts
|
|
6823
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
6824
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
6825
|
+
import fs4 from "node:fs";
|
|
6826
|
+
import os3 from "node:os";
|
|
7014
6827
|
import path19 from "node:path";
|
|
6828
|
+
import { isSea } from "node:sea";
|
|
7015
6829
|
|
|
7016
|
-
// src/
|
|
7017
|
-
import {
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
|
|
6830
|
+
// src/headroom/state.ts
|
|
6831
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
6832
|
+
import path17 from "node:path";
|
|
6833
|
+
import { z as z9 } from "zod";
|
|
6834
|
+
var HEADROOM_ANTHROPIC_UPSTREAM = "https://api.anthropic.com";
|
|
6835
|
+
var HEADROOM_STATE_SCHEMA_VERSION = 2;
|
|
6836
|
+
var HeadroomStateSchema = z9.strictObject({
|
|
6837
|
+
/** The supervisor process owning the daemon. Alive means someone is keeping headroom running. */
|
|
6838
|
+
supervisorPid: z9.number().int().positive().optional(),
|
|
6839
|
+
/** The headroom proxy process itself. Absent while the supervisor is between restarts. */
|
|
6840
|
+
headroomPid: z9.number().int().positive().optional(),
|
|
6841
|
+
/**
|
|
6842
|
+
* 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.
|
|
6843
|
+
*/
|
|
6844
|
+
socketPath: z9.string().min(1).optional(),
|
|
6845
|
+
/** The `headroom --version` output of the running install. */
|
|
6846
|
+
version: z9.string().optional(),
|
|
6847
|
+
/** Hash of the allowlist the running proxy was started with, so a provider-file change is detected as drift. */
|
|
6848
|
+
allowlistHash: z9.string().optional(),
|
|
6849
|
+
/** Hash of the token-saving settings the running proxy was started with, so a settings change is detected as drift. */
|
|
6850
|
+
settingsHash: z9.string().optional(),
|
|
6851
|
+
/** The install spec the running install was last installed from, so a `source` config change is detected as drift. */
|
|
6852
|
+
installedSource: z9.string().optional(),
|
|
6853
|
+
/** The last fatal error, kept across shutdowns so `headroom status` can explain a daemon that is not running. */
|
|
6854
|
+
lastError: z9.string().optional()
|
|
7023
6855
|
});
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
if (cache.ttl === void 0) {
|
|
6856
|
+
var HeadroomSessionSchema = z9.strictObject({ pid: z9.number().int().positive(), startedAt: z9.number(), supervisorPid: z9.number().int().positive() });
|
|
6857
|
+
var HeadroomLockSchema = z9.strictObject({ pid: z9.number().int().positive(), at: z9.number() });
|
|
6858
|
+
function parseJson(raw, schema) {
|
|
6859
|
+
if (raw === void 0 || raw.trim() === "") {
|
|
7029
6860
|
return void 0;
|
|
7030
6861
|
}
|
|
7031
|
-
const
|
|
7032
|
-
|
|
7033
|
-
throw new Error(`credential cache ttl "${cache.ttl}" has no recognised unit`);
|
|
7034
|
-
}
|
|
7035
|
-
return Number(cache.ttl.slice(0, -1)) * multiplier;
|
|
6862
|
+
const parsed = schema.safeParse(JSON.parse(raw));
|
|
6863
|
+
return parsed.success ? parsed.data : void 0;
|
|
7036
6864
|
}
|
|
7037
|
-
function
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
6865
|
+
function readHeadroomState(fs12, stateFile) {
|
|
6866
|
+
try {
|
|
6867
|
+
return parseJson(fs12.readFileUtf8(stateFile), HeadroomStateSchema);
|
|
6868
|
+
} catch {
|
|
6869
|
+
return void 0;
|
|
7041
6870
|
}
|
|
7042
|
-
return limit === void 0 || now - entry.fetchedAt < limit;
|
|
7043
6871
|
}
|
|
7044
|
-
function
|
|
7045
|
-
|
|
6872
|
+
function writeHeadroomState(fs12, stateFile, state) {
|
|
6873
|
+
fs12.mkdirp(path17.dirname(stateFile));
|
|
6874
|
+
fs12.writeFilePrivate(stateFile, `${JSON.stringify(state, null, 2)}
|
|
6875
|
+
`);
|
|
7046
6876
|
}
|
|
7047
|
-
function
|
|
7048
|
-
const
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
entry = params.port.read(store, params.owner);
|
|
7052
|
-
} catch (error) {
|
|
7053
|
-
return { store, status: "unreadable", reason: error instanceof Error ? error.message : String(error) };
|
|
7054
|
-
}
|
|
7055
|
-
if (entry === void 0) {
|
|
7056
|
-
return { store, status: "empty" };
|
|
6877
|
+
function headroomAllowlist(providers) {
|
|
6878
|
+
const urls = /* @__PURE__ */ new Set([HEADROOM_ANTHROPIC_UPSTREAM]);
|
|
6879
|
+
for (const provider of providers) {
|
|
6880
|
+
urls.add(provider.baseUrl);
|
|
7057
6881
|
}
|
|
7058
|
-
|
|
7059
|
-
const ageMs = cacheAgeMs(entry, params.nowMs);
|
|
7060
|
-
return {
|
|
7061
|
-
store,
|
|
7062
|
-
status: isFresh(entry, params.cache, params.nowMs) ? "fresh" : "expired",
|
|
7063
|
-
ageMs,
|
|
7064
|
-
...limit === void 0 ? {} : { expiresInMs: limit - ageMs }
|
|
7065
|
-
};
|
|
6882
|
+
return [...urls].sort();
|
|
7066
6883
|
}
|
|
7067
|
-
function
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
if (state.status === "unreadable") {
|
|
7072
|
-
return `the ${state.store} store cannot be read here: ${state.reason}`;
|
|
7073
|
-
}
|
|
7074
|
-
if (state.status === "expired" && (state.expiresInMs === void 0 || state.expiresInMs > 0)) {
|
|
7075
|
-
return "cached with a timestamp in the future, treated as expired";
|
|
6884
|
+
function headroomUpstreams(providers, frontDoorOrigin2) {
|
|
6885
|
+
const upstreams = providers.filter((provider) => !isCodexProvider(provider)).map((provider) => ({ baseUrl: provider.baseUrl }));
|
|
6886
|
+
if (providers.length > 0 && frontDoorOrigin2 !== void 0) {
|
|
6887
|
+
upstreams.push({ baseUrl: frontDoorOrigin2 });
|
|
7076
6888
|
}
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
6889
|
+
return upstreams;
|
|
6890
|
+
}
|
|
6891
|
+
function hashAllowlist(allowlist) {
|
|
6892
|
+
return createHash3("sha256").update(allowlist.join("\n")).digest("hex");
|
|
6893
|
+
}
|
|
6894
|
+
function readAllProviders(fs12, providersDir) {
|
|
6895
|
+
const result = [];
|
|
6896
|
+
for (const name of fs12.readdir(providersDir)) {
|
|
6897
|
+
if (!name.endsWith(".json")) {
|
|
6898
|
+
continue;
|
|
6899
|
+
}
|
|
6900
|
+
try {
|
|
6901
|
+
const parsed = parseJson(fs12.readFileUtf8(path17.join(providersDir, name)), ProviderSchema);
|
|
6902
|
+
if (parsed !== void 0) {
|
|
6903
|
+
result.push({ name: name.slice(0, -".json".length), provider: parsed });
|
|
6904
|
+
}
|
|
6905
|
+
} catch {
|
|
6906
|
+
continue;
|
|
6907
|
+
}
|
|
7080
6908
|
}
|
|
7081
|
-
return
|
|
6909
|
+
return result;
|
|
7082
6910
|
}
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
var CACHE_FILE_MODE = 384;
|
|
7088
|
-
function cacheFileNameFor(owner) {
|
|
7089
|
-
return `${owner.replace(/[^A-Za-z0-9._-]/g, "_")}.json`;
|
|
6911
|
+
function writeSession(fs12, sessionsDir, session) {
|
|
6912
|
+
fs12.mkdirp(sessionsDir);
|
|
6913
|
+
fs12.writeFileUtf8(path17.join(sessionsDir, `${String(session.pid)}.json`), `${JSON.stringify(session, null, 2)}
|
|
6914
|
+
`);
|
|
7090
6915
|
}
|
|
7091
|
-
function
|
|
7092
|
-
|
|
6916
|
+
function removeSession(fs12, sessionsDir, pid) {
|
|
6917
|
+
fs12.removeRecursive(path17.join(sessionsDir, `${String(pid)}.json`));
|
|
6918
|
+
}
|
|
6919
|
+
function listSessions(fs12, sessionsDir) {
|
|
6920
|
+
const sessions = [];
|
|
6921
|
+
for (const name of fs12.readdir(sessionsDir)) {
|
|
6922
|
+
if (!name.endsWith(".json")) {
|
|
6923
|
+
continue;
|
|
6924
|
+
}
|
|
6925
|
+
try {
|
|
6926
|
+
const parsed = parseJson(fs12.readFileUtf8(path17.join(sessionsDir, name)), HeadroomSessionSchema);
|
|
6927
|
+
if (parsed !== void 0) {
|
|
6928
|
+
sessions.push(parsed);
|
|
6929
|
+
}
|
|
6930
|
+
} catch {
|
|
6931
|
+
continue;
|
|
6932
|
+
}
|
|
6933
|
+
}
|
|
6934
|
+
return sessions.sort((a, b) => a.pid - b.pid);
|
|
6935
|
+
}
|
|
6936
|
+
function sessionsForSupervisor(fs12, sessionsDir, supervisorPid) {
|
|
6937
|
+
return listSessions(fs12, sessionsDir).filter((session) => session.supervisorPid === supervisorPid);
|
|
6938
|
+
}
|
|
6939
|
+
function pruneDeadSessions(fs12, sessionsDir, isRunning) {
|
|
6940
|
+
const removed = [];
|
|
6941
|
+
for (const session of listSessions(fs12, sessionsDir)) {
|
|
6942
|
+
if (!isRunning(session.pid)) {
|
|
6943
|
+
removeSession(fs12, sessionsDir, session.pid);
|
|
6944
|
+
removed.push(session.pid);
|
|
6945
|
+
}
|
|
6946
|
+
}
|
|
6947
|
+
return removed;
|
|
6948
|
+
}
|
|
6949
|
+
function readStartLock(fs12, lockFile) {
|
|
7093
6950
|
try {
|
|
7094
|
-
|
|
6951
|
+
return parseJson(fs12.readFileUtf8(lockFile), HeadroomLockSchema);
|
|
7095
6952
|
} catch {
|
|
7096
6953
|
return void 0;
|
|
7097
6954
|
}
|
|
7098
|
-
const parsed = CachedCredentialSchema.safeParse(json);
|
|
7099
|
-
return parsed.success ? parsed.data : void 0;
|
|
7100
6955
|
}
|
|
7101
|
-
|
|
7102
|
-
|
|
7103
|
-
|
|
6956
|
+
|
|
6957
|
+
// src/headroom/ensure.ts
|
|
6958
|
+
var HEADROOM_START_TIMEOUT_MS = 12e4;
|
|
6959
|
+
var HEADROOM_LAUNCHER_POLL_MS = 250;
|
|
6960
|
+
var HeadroomStartError = class extends CliError {
|
|
6961
|
+
constructor(message) {
|
|
6962
|
+
super(message);
|
|
6963
|
+
this.name = "HeadroomStartError";
|
|
7104
6964
|
}
|
|
7105
|
-
}
|
|
7106
|
-
function
|
|
7107
|
-
const
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
if (
|
|
7116
|
-
|
|
7117
|
-
|
|
7118
|
-
|
|
7119
|
-
return
|
|
6965
|
+
};
|
|
6966
|
+
function ensureHeadroom(params) {
|
|
6967
|
+
const { paths, ports } = params;
|
|
6968
|
+
const deadline = ports.now() + HEADROOM_START_TIMEOUT_MS;
|
|
6969
|
+
let spawnedSupervisor = false;
|
|
6970
|
+
let awaitingAllowlist = false;
|
|
6971
|
+
for (; ; ) {
|
|
6972
|
+
const state = readHeadroomState(ports.fs, paths.headroomStateFile);
|
|
6973
|
+
if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
|
|
6974
|
+
const daemonUp = state.socketPath !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
|
|
6975
|
+
if (daemonUp && state.socketPath !== void 0) {
|
|
6976
|
+
const required = params.requiredAllowlistHash?.();
|
|
6977
|
+
if (required === void 0 || state.allowlistHash === required) {
|
|
6978
|
+
writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
|
|
6979
|
+
return { socketPath: state.socketPath };
|
|
7120
6980
|
}
|
|
6981
|
+
const holders = sessionsForSupervisor(ports.fs, paths.headroomSessionsDir, state.supervisorPid).filter((session) => ports.isRunning(session.pid));
|
|
6982
|
+
if (holders.length > 0) {
|
|
6983
|
+
throw new HeadroomStartError(
|
|
6984
|
+
`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.`
|
|
6985
|
+
);
|
|
6986
|
+
}
|
|
6987
|
+
awaitingAllowlist = true;
|
|
7121
6988
|
}
|
|
7122
|
-
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
|
|
7126
|
-
write(store, owner, entry) {
|
|
7127
|
-
const serialised = JSON.stringify(entry);
|
|
7128
|
-
if (store === "file") {
|
|
7129
|
-
fs5.mkdirSync(dir, { recursive: true, mode: CACHE_DIR_MODE });
|
|
7130
|
-
fs5.chmodSync(dir, CACHE_DIR_MODE);
|
|
7131
|
-
const target = filePath(owner);
|
|
7132
|
-
const temp = `${target}.${String(process.pid)}.tmp`;
|
|
7133
|
-
fs5.writeFileSync(temp, serialised, { mode: CACHE_FILE_MODE });
|
|
7134
|
-
fs5.renameSync(temp, target);
|
|
7135
|
-
return;
|
|
7136
|
-
}
|
|
7137
|
-
requireKeychain();
|
|
7138
|
-
const encoded = Buffer.from(serialised, "utf8").toString("base64");
|
|
7139
|
-
const result = security(["-i"], `add-generic-password -U -s ${KEYCHAIN_CACHE_SERVICE} -a ${JSON.stringify(owner)} -w ${encoded}
|
|
7140
|
-
`);
|
|
7141
|
-
if (result.status !== 0) {
|
|
7142
|
-
throw new Error(`could not write the keychain credential cache for ${owner}`);
|
|
6989
|
+
if (state.lastError !== void 0) {
|
|
6990
|
+
throw new HeadroomStartError(
|
|
6991
|
+
`the headroom daemon reported a fatal error and is not serving: ${state.lastError} (daemon log: ${paths.headroomLogPath})`
|
|
6992
|
+
);
|
|
7143
6993
|
}
|
|
7144
|
-
}
|
|
7145
|
-
|
|
7146
|
-
|
|
7147
|
-
|
|
7148
|
-
|
|
6994
|
+
} else if (!spawnedSupervisor) {
|
|
6995
|
+
ports.fs.mkdirp(paths.headroomDir);
|
|
6996
|
+
const created = ports.fs.writeFileExclusive(
|
|
6997
|
+
paths.headroomLockFile,
|
|
6998
|
+
`${JSON.stringify({ pid: params.launcherPid, at: ports.now() })}
|
|
6999
|
+
`
|
|
7000
|
+
);
|
|
7001
|
+
if (created) {
|
|
7002
|
+
ports.spawnSupervisor(paths);
|
|
7003
|
+
spawnedSupervisor = true;
|
|
7004
|
+
} else {
|
|
7005
|
+
const lock = readStartLock(ports.fs, paths.headroomLockFile);
|
|
7006
|
+
if (lock === void 0 || !ports.isRunning(lock.pid)) {
|
|
7007
|
+
ports.fs.removeRecursive(paths.headroomLockFile);
|
|
7008
|
+
continue;
|
|
7009
|
+
}
|
|
7149
7010
|
}
|
|
7150
|
-
requireKeychain();
|
|
7151
|
-
security(["delete-generic-password", "-s", KEYCHAIN_CACHE_SERVICE, "-a", owner]);
|
|
7152
7011
|
}
|
|
7012
|
+
if (ports.now() >= deadline) {
|
|
7013
|
+
const lastError = state?.lastError;
|
|
7014
|
+
throw new HeadroomStartError(
|
|
7015
|
+
`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}`
|
|
7016
|
+
);
|
|
7017
|
+
}
|
|
7018
|
+
ports.sleep(HEADROOM_LAUNCHER_POLL_MS);
|
|
7019
|
+
}
|
|
7020
|
+
}
|
|
7021
|
+
|
|
7022
|
+
// src/headroom/settings.ts
|
|
7023
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
7024
|
+
function settingsOf(config) {
|
|
7025
|
+
return {
|
|
7026
|
+
...config.mode === void 0 ? {} : { mode: config.mode },
|
|
7027
|
+
...config.targetRatio === void 0 ? {} : { targetRatio: config.targetRatio },
|
|
7028
|
+
...config.ccr === void 0 ? {} : { ccr: config.ccr },
|
|
7029
|
+
...config.rolloutChannel === void 0 ? {} : { rolloutChannel: config.rolloutChannel },
|
|
7030
|
+
...config.interceptToolResults === void 0 ? {} : { interceptToolResults: config.interceptToolResults },
|
|
7031
|
+
...config.readMaturation === void 0 ? {} : { readMaturation: config.readMaturation }
|
|
7153
7032
|
};
|
|
7154
7033
|
}
|
|
7155
|
-
function
|
|
7156
|
-
return
|
|
7034
|
+
function settingsArgs(settings) {
|
|
7035
|
+
return [
|
|
7036
|
+
...settings.mode === void 0 ? [] : ["--mode", settings.mode],
|
|
7037
|
+
...settings.targetRatio === void 0 ? [] : ["--target-ratio", String(settings.targetRatio)],
|
|
7038
|
+
...settings.ccr === "lossless" ? ["--lossless"] : [],
|
|
7039
|
+
...settings.ccr === "none" ? ["--no-ccr"] : [],
|
|
7040
|
+
...settings.interceptToolResults === true ? ["--intercept-tool-results"] : [],
|
|
7041
|
+
...settings.readMaturation === true ? ["--read-maturation"] : []
|
|
7042
|
+
];
|
|
7043
|
+
}
|
|
7044
|
+
function settingsEnv(settings) {
|
|
7045
|
+
return settings.rolloutChannel === void 0 ? {} : { HEADROOM_ROLLOUT_CHANNEL: settings.rolloutChannel };
|
|
7046
|
+
}
|
|
7047
|
+
function hashSettings(settings) {
|
|
7048
|
+
return createHash4("sha256").update(JSON.stringify([...settingsArgs(settings), JSON.stringify(settingsEnv(settings))])).digest("hex");
|
|
7157
7049
|
}
|
|
7158
7050
|
|
|
7159
|
-
// src/
|
|
7160
|
-
import
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
// src/credential.ts
|
|
7164
|
-
var CREDENTIAL_UNAVAILABLE_EXIT = 64;
|
|
7165
|
-
var CREDENTIAL_COMMAND_TIMEOUT_MS = 1e4;
|
|
7166
|
-
var CREDENTIAL_INTERACTIVE_TIMEOUT_MS = 12e4;
|
|
7167
|
-
var GROUP_OTHER_PERMISSION_BITS = 63;
|
|
7168
|
-
var PERMISSION_BITS = 511;
|
|
7051
|
+
// src/headroom/socket.ts
|
|
7052
|
+
import path18 from "node:path";
|
|
7053
|
+
var GROUP_OR_OTHER_BITS = 63;
|
|
7169
7054
|
var OCTAL_RADIX = 8;
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
}
|
|
7177
|
-
const account = source.keychain.account;
|
|
7178
|
-
return ["security", "find-generic-password", "-s", source.keychain.service, ...account === void 0 ? [] : ["-a", account], "-w"];
|
|
7055
|
+
var MODE_DIGITS = 4;
|
|
7056
|
+
var HEADROOM_SOCKET_SUFFIX = ".sock";
|
|
7057
|
+
var HEADROOM_SOCKET_NAME = /^(\d+)\.sock$/;
|
|
7058
|
+
var UNIX_SOCKET_PATH_MAX_BYTES = 103;
|
|
7059
|
+
var PLATFORMS_WITHOUT_UNIX_SOCKETS = ["win32"];
|
|
7060
|
+
function headroomSocketPath(paths, supervisorPid) {
|
|
7061
|
+
return path18.join(paths.headroomSocketDir, `${String(supervisorPid)}${HEADROOM_SOCKET_SUFFIX}`);
|
|
7179
7062
|
}
|
|
7180
|
-
function
|
|
7181
|
-
|
|
7182
|
-
return source.interactive ?? false;
|
|
7183
|
-
}
|
|
7184
|
-
if ("op" in source) {
|
|
7185
|
-
return source.interactive ?? (env.OP_SERVICE_ACCOUNT_TOKEN ?? "") === "";
|
|
7186
|
-
}
|
|
7187
|
-
return false;
|
|
7063
|
+
function platformRefusal(platform) {
|
|
7064
|
+
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;
|
|
7188
7065
|
}
|
|
7189
|
-
function
|
|
7190
|
-
if (
|
|
7191
|
-
return {
|
|
7066
|
+
function ownerOnlyRefusal(label, target, stat, expected, uid) {
|
|
7067
|
+
if (stat === void 0) {
|
|
7068
|
+
return `the headroom ${label} ${target} does not exist`;
|
|
7192
7069
|
}
|
|
7193
|
-
if ("
|
|
7194
|
-
return {
|
|
7070
|
+
if (stat.kind === "symlink") {
|
|
7071
|
+
return `the headroom ${label} ${target} is a symlink; it must be the real ${expected === "dir" ? "directory" : "socket"}`;
|
|
7195
7072
|
}
|
|
7196
|
-
if (
|
|
7197
|
-
return {
|
|
7073
|
+
if (stat.kind !== expected) {
|
|
7074
|
+
return `the headroom ${label} ${target} is not a ${expected === "dir" ? "directory" : "socket"}`;
|
|
7198
7075
|
}
|
|
7199
|
-
if (
|
|
7200
|
-
return {
|
|
7076
|
+
if (stat.uid !== uid) {
|
|
7077
|
+
return `the headroom ${label} ${target} is owned by uid ${String(stat.uid)}, not by this user (uid ${String(uid)})`;
|
|
7201
7078
|
}
|
|
7202
|
-
if (
|
|
7203
|
-
return {
|
|
7204
|
-
kind: "keychain",
|
|
7205
|
-
service: source.keychain.service,
|
|
7206
|
-
...source.keychain.account === void 0 ? {} : { account: source.keychain.account }
|
|
7207
|
-
};
|
|
7079
|
+
if ((stat.mode & GROUP_OR_OTHER_BITS) !== 0) {
|
|
7080
|
+
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`;
|
|
7208
7081
|
}
|
|
7209
|
-
return
|
|
7210
|
-
}
|
|
7211
|
-
function summariseCredential(credential) {
|
|
7212
|
-
return {
|
|
7213
|
-
target: credential.target ?? "bearer",
|
|
7214
|
-
sources: credential.sources.map(summariseSource),
|
|
7215
|
-
...credential.cache === void 0 ? {} : { cache: credential.cache }
|
|
7216
|
-
};
|
|
7082
|
+
return void 0;
|
|
7217
7083
|
}
|
|
7218
|
-
function
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
case "file":
|
|
7223
|
-
return `file ${summary.path}`;
|
|
7224
|
-
case "command":
|
|
7225
|
-
return `command ${summary.program}`;
|
|
7226
|
-
case "op":
|
|
7227
|
-
return `op ${summary.reference}`;
|
|
7228
|
-
case "keychain":
|
|
7229
|
-
return `keychain ${summary.service}${summary.account === void 0 ? "" : ` (account ${summary.account})`}`;
|
|
7230
|
-
case "literal":
|
|
7231
|
-
return "literal (non-secret placeholder)";
|
|
7232
|
-
default:
|
|
7233
|
-
return summary;
|
|
7084
|
+
function verifyHeadroomSocket(socketPath, ports) {
|
|
7085
|
+
const unsupported = platformRefusal(ports.platform);
|
|
7086
|
+
if (unsupported !== void 0) {
|
|
7087
|
+
return { refused: unsupported };
|
|
7234
7088
|
}
|
|
7089
|
+
const uid = ports.currentUid();
|
|
7090
|
+
const directory = path18.dirname(socketPath);
|
|
7091
|
+
const refusal = ownerOnlyRefusal("socket directory", directory, ports.lstat(directory), "dir", uid) ?? ownerOnlyRefusal("socket", socketPath, ports.lstat(socketPath), "socket", uid);
|
|
7092
|
+
return refusal === void 0 ? { socketPath } : { refused: refusal };
|
|
7235
7093
|
}
|
|
7236
|
-
function
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
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`}`;
|
|
7241
|
-
return `${summary.target} from ${summary.sources.map(formatSourceSummary).join(", then ")}${cache}`;
|
|
7242
|
-
}
|
|
7243
|
-
function describeCredential(credential) {
|
|
7244
|
-
return formatCredentialSummary(summariseCredential(credential));
|
|
7245
|
-
}
|
|
7246
|
-
function trySource(source, params) {
|
|
7247
|
-
const none = (reason, warn = false) => ({ ok: false, reason, warn });
|
|
7248
|
-
if ("env" in source) {
|
|
7249
|
-
const value = params.env[source.env];
|
|
7250
|
-
return value === void 0 || value === "" ? none("is unset or empty") : { ok: true, token: value };
|
|
7251
|
-
}
|
|
7252
|
-
if ("literal" in source) {
|
|
7253
|
-
return { ok: true, token: source.literal };
|
|
7254
|
-
}
|
|
7255
|
-
if ("file" in source) {
|
|
7256
|
-
const read = params.port.readSecretFile(source.file);
|
|
7257
|
-
if (!read.found) {
|
|
7258
|
-
return none("does not exist");
|
|
7259
|
-
}
|
|
7260
|
-
if (read.mode !== void 0 && (read.mode & GROUP_OTHER_PERMISSION_BITS) !== 0) {
|
|
7261
|
-
return none(
|
|
7262
|
-
`is readable or writable by group or others (mode ${(read.mode & PERMISSION_BITS).toString(OCTAL_RADIX)}); run \`chmod 600 ${source.file}\``,
|
|
7263
|
-
true
|
|
7264
|
-
);
|
|
7265
|
-
}
|
|
7266
|
-
const token2 = read.content.trim();
|
|
7267
|
-
return token2 === "" ? none("is empty") : { ok: true, token: token2 };
|
|
7268
|
-
}
|
|
7269
|
-
const argv = commandArgv(source);
|
|
7270
|
-
const interactive = isInteractiveSource(source, params.env);
|
|
7271
|
-
if (interactive && !params.port.personPresent()) {
|
|
7272
|
-
return none("needs a person to approve it, but there is no terminal or desktop session");
|
|
7094
|
+
function prepareHeadroomSocket(fs12, ports, paths, supervisorPid) {
|
|
7095
|
+
const unsupported = platformRefusal(ports.platform);
|
|
7096
|
+
if (unsupported !== void 0) {
|
|
7097
|
+
return { refused: unsupported };
|
|
7273
7098
|
}
|
|
7274
|
-
const
|
|
7275
|
-
const
|
|
7276
|
-
if (
|
|
7277
|
-
return
|
|
7099
|
+
const socketPath = headroomSocketPath(paths, supervisorPid);
|
|
7100
|
+
const length = Buffer.byteLength(socketPath, "utf8");
|
|
7101
|
+
if (length > UNIX_SOCKET_PATH_MAX_BYTES) {
|
|
7102
|
+
return {
|
|
7103
|
+
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`
|
|
7104
|
+
};
|
|
7278
7105
|
}
|
|
7279
|
-
if (
|
|
7280
|
-
|
|
7281
|
-
const outcome = result.status === null ? "could not be run or was killed by a signal" : `exited with status ${String(result.status)}`;
|
|
7282
|
-
return none(`${outcome}${stderr === "" ? "" : `: ${stderr}`}`);
|
|
7106
|
+
if (ports.lstat(paths.headroomSocketDir)?.kind === "symlink") {
|
|
7107
|
+
return { refused: `the headroom socket directory ${paths.headroomSocketDir} is a symlink; it must be the real directory` };
|
|
7283
7108
|
}
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
}
|
|
7287
|
-
function warmCommandFor(subject) {
|
|
7288
|
-
const [kind, name] = subject.split(" ");
|
|
7289
|
-
return kind === "provider" ? `agent-shim credential warm --provider ${name ?? ""}` : `agent-shim credential warm ${name ?? ""}`;
|
|
7109
|
+
fs12.mkdirPrivate(paths.headroomSocketDir);
|
|
7110
|
+
const refusal = ownerOnlyRefusal("socket directory", paths.headroomSocketDir, ports.lstat(paths.headroomSocketDir), "dir", ports.currentUid());
|
|
7111
|
+
return refusal === void 0 ? { socketPath } : { refused: refusal };
|
|
7290
7112
|
}
|
|
7291
|
-
function
|
|
7292
|
-
const
|
|
7293
|
-
const
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
const entry = cache.port.read(store, params.subject);
|
|
7298
|
-
if (entry !== void 0 && isFresh(entry, cacheBlock, cache.now())) {
|
|
7299
|
-
return { ok: true, credential: { target, token: entry.token, source: entry.source, warnings: [], cachedAt: entry.fetchedAt } };
|
|
7113
|
+
function pruneStaleHeadroomSockets(fs12, paths, isRunning) {
|
|
7114
|
+
const removed = [];
|
|
7115
|
+
for (const name of fs12.readdir(paths.headroomSocketDir)) {
|
|
7116
|
+
const pid = HEADROOM_SOCKET_NAME.exec(name)?.[1];
|
|
7117
|
+
if (pid === void 0 || isRunning(Number.parseInt(pid, 10))) {
|
|
7118
|
+
continue;
|
|
7300
7119
|
}
|
|
7120
|
+
fs12.removeRecursive(path18.join(paths.headroomSocketDir, name));
|
|
7121
|
+
removed.push(name);
|
|
7301
7122
|
}
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
-
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
if (outcome.warn) {
|
|
7315
|
-
warnings.push(`agent-shim: ${params.subject}: skipped ${line}`);
|
|
7316
|
-
}
|
|
7123
|
+
return removed;
|
|
7124
|
+
}
|
|
7125
|
+
|
|
7126
|
+
// src/headroom/source.ts
|
|
7127
|
+
import { z as z10 } from "zod";
|
|
7128
|
+
var GIT_SPEC_RE = /git\+[^@\s]+(?:@([^\s#]+))?/;
|
|
7129
|
+
var COMMIT_SHA_RE = /^[0-9a-f]{40}$/i;
|
|
7130
|
+
var DirectUrlSchema = z10.object({ vcs_info: z10.object({ commit_id: z10.string() }) });
|
|
7131
|
+
function isMovingGitSource(spec) {
|
|
7132
|
+
const match = GIT_SPEC_RE.exec(spec);
|
|
7133
|
+
if (match === null) {
|
|
7134
|
+
return false;
|
|
7317
7135
|
}
|
|
7318
|
-
const
|
|
7319
|
-
return
|
|
7136
|
+
const ref = match[1];
|
|
7137
|
+
return ref === void 0 || !COMMIT_SHA_RE.test(ref);
|
|
7320
7138
|
}
|
|
7321
|
-
function
|
|
7139
|
+
function pinnedGitCommit(spec) {
|
|
7140
|
+
const ref = GIT_SPEC_RE.exec(spec)?.[1];
|
|
7141
|
+
return ref !== void 0 && COMMIT_SHA_RE.test(ref) ? ref.toLowerCase() : void 0;
|
|
7142
|
+
}
|
|
7143
|
+
|
|
7144
|
+
// src/headroom/supervisor.ts
|
|
7145
|
+
function resolveSupervisorConfig(configured) {
|
|
7322
7146
|
return {
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7147
|
+
source: configured.source ?? HEADROOM_DEFAULT_SOURCE,
|
|
7148
|
+
idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES,
|
|
7149
|
+
settings: settingsOf(configured)
|
|
7326
7150
|
};
|
|
7327
7151
|
}
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
var
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
7152
|
+
var HEADROOM_BACKOFF_BASE_MS = 500;
|
|
7153
|
+
var HEADROOM_BACKOFF_CAP_MS = 3e4;
|
|
7154
|
+
var HEADROOM_START_RETRY_BUDGET = 5;
|
|
7155
|
+
var HEADROOM_READY_TIMEOUT_MS = 15e3;
|
|
7156
|
+
var HEADROOM_POLL_MS = 1e3;
|
|
7157
|
+
var MS_PER_MINUTE2 = 6e4;
|
|
7158
|
+
function backoffForAttempt(attempt) {
|
|
7159
|
+
return Math.min(HEADROOM_BACKOFF_CAP_MS, HEADROOM_BACKOFF_BASE_MS * 2 ** (attempt - 1));
|
|
7160
|
+
}
|
|
7161
|
+
function versionSatisfies(version, source) {
|
|
7162
|
+
if (version === void 0) {
|
|
7163
|
+
return false;
|
|
7335
7164
|
}
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
constructor(providerName) {
|
|
7340
|
-
super(`A provider named "${providerName}" already exists.`);
|
|
7341
|
-
this.providerName = providerName;
|
|
7342
|
-
this.name = "ProviderAlreadyExistsError";
|
|
7165
|
+
const specifier = /==|>=|<=|!=|>|<|~=/.exec(source);
|
|
7166
|
+
if (specifier === null) {
|
|
7167
|
+
return true;
|
|
7343
7168
|
}
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
super(
|
|
7349
|
-
`"${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.`
|
|
7350
|
-
);
|
|
7351
|
-
this.attemptedName = attemptedName;
|
|
7352
|
-
this.name = "InvalidProviderNameError";
|
|
7169
|
+
const installed = parseVersion(version);
|
|
7170
|
+
const wanted = parseVersion(source.slice(specifier.index + specifier[0].length));
|
|
7171
|
+
if (installed === void 0 || wanted === void 0) {
|
|
7172
|
+
return true;
|
|
7353
7173
|
}
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7174
|
+
const cmp = compareVersions2(installed, wanted);
|
|
7175
|
+
switch (specifier[0]) {
|
|
7176
|
+
case "==":
|
|
7177
|
+
return cmp === 0;
|
|
7178
|
+
case "!=":
|
|
7179
|
+
return cmp !== 0;
|
|
7180
|
+
case ">=":
|
|
7181
|
+
return cmp >= 0;
|
|
7182
|
+
case "<=":
|
|
7183
|
+
return cmp <= 0;
|
|
7184
|
+
case ">":
|
|
7185
|
+
return cmp > 0;
|
|
7186
|
+
case "<":
|
|
7187
|
+
return cmp < 0;
|
|
7188
|
+
case "~=":
|
|
7189
|
+
return compareVersions2(installed.slice(0, Math.max(0, wanted.length - 1)), wanted.slice(0, Math.max(0, wanted.length - 1))) === 0 && cmp >= 0;
|
|
7190
|
+
default:
|
|
7191
|
+
return true;
|
|
7372
7192
|
}
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
const
|
|
7376
|
-
if (
|
|
7193
|
+
}
|
|
7194
|
+
function parseVersion(input) {
|
|
7195
|
+
const match = /(\d+)(\.\d+)*/.exec(input);
|
|
7196
|
+
if (match === null) {
|
|
7377
7197
|
return void 0;
|
|
7378
7198
|
}
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7199
|
+
return match[0].split(".").map((part) => Number.parseInt(part, 10));
|
|
7200
|
+
}
|
|
7201
|
+
function compareVersions2(a, b) {
|
|
7202
|
+
for (let index = 0; index < Math.max(a.length, b.length); index += 1) {
|
|
7203
|
+
const left = a[index] ?? 0;
|
|
7204
|
+
const right = b[index] ?? 0;
|
|
7205
|
+
if (left !== right) {
|
|
7206
|
+
return left - right;
|
|
7207
|
+
}
|
|
7385
7208
|
}
|
|
7386
|
-
|
|
7387
|
-
|
|
7209
|
+
return 0;
|
|
7210
|
+
}
|
|
7211
|
+
var HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
|
|
7212
|
+
async function runSupervisor(initialConfig, ports, options = {}) {
|
|
7213
|
+
const { fs: fs12, paths } = ports;
|
|
7214
|
+
let config = initialConfig;
|
|
7215
|
+
const previousState = readHeadroomState(fs12, paths.headroomStateFile);
|
|
7216
|
+
if (previousState?.supervisorPid !== void 0 && previousState.supervisorPid !== ports.ownPid && ports.isRunning(previousState.supervisorPid)) {
|
|
7217
|
+
ports.log(`agent-shim headroom supervisor ${String(ports.ownPid)}: supervisor ${String(previousState.supervisorPid)} already owns the daemon; exiting`);
|
|
7218
|
+
return 0;
|
|
7388
7219
|
}
|
|
7389
|
-
|
|
7390
|
-
const
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
...remainingEnv === void 0 || Object.keys(remainingEnv).length === 0 ? {} : { env: remainingEnv }
|
|
7220
|
+
let installedSource = previousState?.installedSource;
|
|
7221
|
+
const ownsState = () => {
|
|
7222
|
+
const owner = readHeadroomState(fs12, paths.headroomStateFile)?.supervisorPid;
|
|
7223
|
+
return owner === void 0 || owner === ports.ownPid || !ports.isRunning(owner);
|
|
7224
|
+
};
|
|
7225
|
+
const writeOwnState = (state) => {
|
|
7226
|
+
if (!ownsState()) {
|
|
7227
|
+
return false;
|
|
7398
7228
|
}
|
|
7229
|
+
writeHeadroomState(fs12, paths.headroomStateFile, state);
|
|
7230
|
+
return true;
|
|
7399
7231
|
};
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7232
|
+
const fail = (message) => {
|
|
7233
|
+
if (writeOwnState({ lastError: message })) {
|
|
7234
|
+
fs12.removeRecursive(paths.headroomLockFile);
|
|
7235
|
+
}
|
|
7236
|
+
ports.log(`agent-shim headroom supervisor: ${message}`);
|
|
7237
|
+
return 1;
|
|
7238
|
+
};
|
|
7239
|
+
const ensureInstalled = () => {
|
|
7240
|
+
const version2 = ports.headroomVersion();
|
|
7241
|
+
const versionOk = version2 !== void 0 && versionSatisfies(version2, config.source);
|
|
7242
|
+
const pinned = pinnedGitCommit(config.source);
|
|
7243
|
+
const commitOk = pinned === void 0 || ports.installedCommit() === pinned;
|
|
7244
|
+
if (versionOk && commitOk && (installedSource === void 0 || installedSource === config.source)) {
|
|
7245
|
+
installedSource = config.source;
|
|
7246
|
+
return true;
|
|
7247
|
+
}
|
|
7248
|
+
ports.log(`agent-shim headroom supervisor: installing headroom from ${config.source}`);
|
|
7249
|
+
const result = ports.install(config.source);
|
|
7250
|
+
if (!result.ok) {
|
|
7251
|
+
return false;
|
|
7252
|
+
}
|
|
7253
|
+
installedSource = config.source;
|
|
7254
|
+
return true;
|
|
7255
|
+
};
|
|
7256
|
+
const socket = prepareHeadroomSocket(fs12, ports.socketTrust, paths, ports.ownPid);
|
|
7257
|
+
if (socket.refused !== void 0) {
|
|
7258
|
+
return fail(socket.refused);
|
|
7410
7259
|
}
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
const conversion = legacyProviderConversion(raw);
|
|
7416
|
-
if (conversion !== void 0) {
|
|
7417
|
-
throw new LegacyProviderFileError(filePath, conversion);
|
|
7260
|
+
const { socketPath } = socket;
|
|
7261
|
+
pruneStaleHeadroomSockets(fs12, paths, ports.isRunning);
|
|
7262
|
+
if (!ensureInstalled()) {
|
|
7263
|
+
return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
|
|
7418
7264
|
}
|
|
7419
|
-
const
|
|
7420
|
-
|
|
7421
|
-
|
|
7265
|
+
const version = ports.headroomVersion() ?? "unknown";
|
|
7266
|
+
writeOwnState({ supervisorPid: ports.ownPid, version, installedSource });
|
|
7267
|
+
ports.log(`agent-shim headroom supervisor ${String(ports.ownPid)}: managing headroom on allowlist [${allowlistOf(ports).join(", ")}]`);
|
|
7268
|
+
const orphanPid = previousState?.headroomPid;
|
|
7269
|
+
if (orphanPid !== void 0 && ports.isRunning(orphanPid)) {
|
|
7270
|
+
ports.log(`agent-shim headroom supervisor: stopping daemon pid ${String(orphanPid)} left behind by the previous supervisor`);
|
|
7271
|
+
ports.stopProcess(orphanPid);
|
|
7272
|
+
}
|
|
7273
|
+
let headroomPid;
|
|
7274
|
+
let runningHash;
|
|
7275
|
+
let runningSettingsHash;
|
|
7276
|
+
let consecutiveFailures = 0;
|
|
7277
|
+
let idleSince;
|
|
7278
|
+
let superseded = false;
|
|
7279
|
+
let ticks = 0;
|
|
7280
|
+
for (; ; ) {
|
|
7281
|
+
if (options.tickLimit !== void 0 && ticks >= options.tickLimit) {
|
|
7282
|
+
return HEADROOM_SUPERVISOR_STILL_RUNNING;
|
|
7283
|
+
}
|
|
7284
|
+
ticks += 1;
|
|
7285
|
+
config = ports.readConfig();
|
|
7286
|
+
const settingsHash = hashSettings(config.settings);
|
|
7287
|
+
const allowlist = allowlistOf(ports);
|
|
7288
|
+
const allowlistHash = hashAllowlist(allowlist);
|
|
7289
|
+
pruneDeadSessions(fs12, paths.headroomSessionsDir, ports.isRunning);
|
|
7290
|
+
const ownsNow = ownsState();
|
|
7291
|
+
if (!ownsNow && !superseded) {
|
|
7292
|
+
ports.log("agent-shim headroom supervisor: another supervisor now owns the state file; serving this daemon's own sessions until they end");
|
|
7293
|
+
}
|
|
7294
|
+
superseded = !ownsNow;
|
|
7295
|
+
const crashed = headroomPid !== void 0 && !ports.isRunning(headroomPid);
|
|
7296
|
+
if (headroomPid === void 0 || crashed) {
|
|
7297
|
+
if (crashed) {
|
|
7298
|
+
ports.log(`agent-shim headroom supervisor: headroom pid ${String(headroomPid)} died`);
|
|
7299
|
+
headroomPid = void 0;
|
|
7300
|
+
runningHash = void 0;
|
|
7301
|
+
runningSettingsHash = void 0;
|
|
7302
|
+
writeOwnState({
|
|
7303
|
+
supervisorPid: ports.ownPid,
|
|
7304
|
+
version,
|
|
7305
|
+
installedSource
|
|
7306
|
+
});
|
|
7307
|
+
}
|
|
7308
|
+
if (consecutiveFailures >= HEADROOM_START_RETRY_BUDGET) {
|
|
7309
|
+
return fail(`headroom failed to become ready ${String(consecutiveFailures)} times in a row; giving up`);
|
|
7310
|
+
}
|
|
7311
|
+
if (installedSource !== config.source && !ensureInstalled()) {
|
|
7312
|
+
return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
|
|
7313
|
+
}
|
|
7314
|
+
const pid = ports.spawnHeadroom(socketPath, allowlist, config.settings);
|
|
7315
|
+
const ready = await waitUntilReady(ports, socketPath);
|
|
7316
|
+
if (ready) {
|
|
7317
|
+
consecutiveFailures = 0;
|
|
7318
|
+
headroomPid = pid;
|
|
7319
|
+
runningHash = allowlistHash;
|
|
7320
|
+
runningSettingsHash = settingsHash;
|
|
7321
|
+
const recorded = writeOwnState({
|
|
7322
|
+
supervisorPid: ports.ownPid,
|
|
7323
|
+
headroomPid: pid,
|
|
7324
|
+
socketPath,
|
|
7325
|
+
version,
|
|
7326
|
+
allowlistHash,
|
|
7327
|
+
settingsHash,
|
|
7328
|
+
installedSource: config.source
|
|
7329
|
+
});
|
|
7330
|
+
if (recorded) {
|
|
7331
|
+
fs12.removeRecursive(paths.headroomLockFile);
|
|
7332
|
+
}
|
|
7333
|
+
idleSince = void 0;
|
|
7334
|
+
ports.log(`agent-shim headroom supervisor: headroom pid ${String(pid)} ready on unix socket ${socketPath}`);
|
|
7335
|
+
} else {
|
|
7336
|
+
ports.stopProcess(pid);
|
|
7337
|
+
consecutiveFailures += 1;
|
|
7338
|
+
ports.log(
|
|
7339
|
+
`agent-shim headroom supervisor: headroom did not become ready on unix socket ${socketPath} (attempt ${String(consecutiveFailures)} of ${String(HEADROOM_START_RETRY_BUDGET)})`
|
|
7340
|
+
);
|
|
7341
|
+
await ports.sleep(backoffForAttempt(consecutiveFailures));
|
|
7342
|
+
continue;
|
|
7343
|
+
}
|
|
7344
|
+
} else {
|
|
7345
|
+
if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
|
|
7346
|
+
if (sessionsForSupervisor(fs12, paths.headroomSessionsDir, ports.ownPid).length === 0) {
|
|
7347
|
+
ports.log("agent-shim headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
|
|
7348
|
+
ports.stopProcess(headroomPid);
|
|
7349
|
+
headroomPid = void 0;
|
|
7350
|
+
runningHash = void 0;
|
|
7351
|
+
runningSettingsHash = void 0;
|
|
7352
|
+
continue;
|
|
7353
|
+
}
|
|
7354
|
+
}
|
|
7355
|
+
const sessions = sessionsForSupervisor(fs12, paths.headroomSessionsDir, ports.ownPid);
|
|
7356
|
+
if (sessions.length === 0) {
|
|
7357
|
+
idleSince ??= ports.now();
|
|
7358
|
+
const idleMs = ports.now() - idleSince;
|
|
7359
|
+
if (idleMs >= config.idleShutdownMinutes * MS_PER_MINUTE2) {
|
|
7360
|
+
ports.log(
|
|
7361
|
+
`agent-shim headroom supervisor: no sessions for ${String(config.idleShutdownMinutes)} minute(s); stopping headroom and exiting`
|
|
7362
|
+
);
|
|
7363
|
+
ports.stopProcess(headroomPid);
|
|
7364
|
+
writeOwnState({ version });
|
|
7365
|
+
return 0;
|
|
7366
|
+
}
|
|
7367
|
+
} else {
|
|
7368
|
+
idleSince = void 0;
|
|
7369
|
+
}
|
|
7370
|
+
}
|
|
7371
|
+
await ports.sleep(HEADROOM_POLL_MS);
|
|
7422
7372
|
}
|
|
7423
|
-
return parsed.data;
|
|
7424
7373
|
}
|
|
7425
|
-
function
|
|
7426
|
-
const
|
|
7427
|
-
|
|
7428
|
-
|
|
7374
|
+
function currentHeadroomAllowlist(fs12, paths) {
|
|
7375
|
+
const providers = readAllProviders(fs12, paths.providersDir).map((entry) => entry.provider);
|
|
7376
|
+
return headroomAllowlist(headroomUpstreams(providers, frontDoorOrigin(readFrontDoorState(fs12, paths.frontdoorStateFile))));
|
|
7377
|
+
}
|
|
7378
|
+
function allowlistOf(ports) {
|
|
7379
|
+
return currentHeadroomAllowlist(ports.fs, ports.paths);
|
|
7380
|
+
}
|
|
7381
|
+
async function waitUntilReady(ports, socketPath) {
|
|
7382
|
+
const deadline = ports.now() + HEADROOM_READY_TIMEOUT_MS;
|
|
7383
|
+
for (; ; ) {
|
|
7384
|
+
if (await ports.ready(socketPath)) {
|
|
7385
|
+
return true;
|
|
7386
|
+
}
|
|
7387
|
+
if (ports.now() >= deadline) {
|
|
7388
|
+
return false;
|
|
7389
|
+
}
|
|
7390
|
+
await ports.sleep(HEADROOM_POLL_MS);
|
|
7429
7391
|
}
|
|
7430
|
-
const raw = JSON.parse(fs6.readFileSync(filePath, "utf8"));
|
|
7431
|
-
return parseProviderFile(filePath, raw);
|
|
7432
7392
|
}
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
return
|
|
7393
|
+
|
|
7394
|
+
// src/realPorts.ts
|
|
7395
|
+
function isEnoent3(error) {
|
|
7396
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
7437
7397
|
}
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
(dir) => fs6.readdirSync(dir),
|
|
7441
|
-
paths.providersDir
|
|
7442
|
-
);
|
|
7443
|
-
const result = [];
|
|
7444
|
-
for (const name of names) {
|
|
7445
|
-
let provider;
|
|
7398
|
+
var realFsPort = {
|
|
7399
|
+
readFileUtf8(filePath) {
|
|
7446
7400
|
try {
|
|
7447
|
-
|
|
7401
|
+
return fs4.readFileSync(filePath, "utf8");
|
|
7448
7402
|
} catch (error) {
|
|
7449
|
-
if (
|
|
7450
|
-
|
|
7403
|
+
if (isEnoent3(error)) {
|
|
7404
|
+
return void 0;
|
|
7451
7405
|
}
|
|
7452
|
-
|
|
7453
|
-
continue;
|
|
7406
|
+
throw error;
|
|
7454
7407
|
}
|
|
7455
|
-
|
|
7456
|
-
|
|
7408
|
+
},
|
|
7409
|
+
readConfigFile: cosmiconfigReader(),
|
|
7410
|
+
readdir(dirPath) {
|
|
7411
|
+
try {
|
|
7412
|
+
return fs4.readdirSync(dirPath);
|
|
7413
|
+
} catch (error) {
|
|
7414
|
+
if (isEnoent3(error)) {
|
|
7415
|
+
return [];
|
|
7416
|
+
}
|
|
7417
|
+
throw error;
|
|
7457
7418
|
}
|
|
7458
7419
|
}
|
|
7459
|
-
|
|
7420
|
+
};
|
|
7421
|
+
function isErrorWithCode(error, code) {
|
|
7422
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
7460
7423
|
}
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
|
|
7424
|
+
var OWNER_ONLY_DIR_MODE = 448;
|
|
7425
|
+
var OWNER_ONLY_FILE_MODE = 384;
|
|
7426
|
+
var PERMISSION_BITS = 511;
|
|
7427
|
+
var realFarmFs = {
|
|
7428
|
+
lstat(filePath) {
|
|
7429
|
+
let stat;
|
|
7430
|
+
try {
|
|
7431
|
+
stat = fs4.lstatSync(filePath);
|
|
7432
|
+
} catch (error) {
|
|
7433
|
+
if (isEnoent3(error)) {
|
|
7434
|
+
return void 0;
|
|
7435
|
+
}
|
|
7436
|
+
throw error;
|
|
7437
|
+
}
|
|
7438
|
+
const kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "dir" : "file";
|
|
7439
|
+
return { kind, mtimeMs: stat.mtimeMs, sizeBytes: stat.size };
|
|
7440
|
+
},
|
|
7441
|
+
readdir(dirPath) {
|
|
7442
|
+
try {
|
|
7443
|
+
return fs4.readdirSync(dirPath);
|
|
7444
|
+
} catch (error) {
|
|
7445
|
+
if (isEnoent3(error) || isErrorWithCode(error, "ENOTDIR")) {
|
|
7446
|
+
return [];
|
|
7447
|
+
}
|
|
7448
|
+
throw error;
|
|
7449
|
+
}
|
|
7450
|
+
},
|
|
7451
|
+
mkdirp(dirPath) {
|
|
7452
|
+
fs4.mkdirSync(dirPath, { recursive: true });
|
|
7453
|
+
},
|
|
7454
|
+
symlink(target, linkPath) {
|
|
7455
|
+
fs4.symlinkSync(target, linkPath);
|
|
7456
|
+
},
|
|
7457
|
+
rename(from, to) {
|
|
7458
|
+
fs4.renameSync(from, to);
|
|
7459
|
+
},
|
|
7460
|
+
removeRecursive(targetPath) {
|
|
7461
|
+
fs4.rmSync(targetPath, { recursive: true, force: true });
|
|
7462
|
+
},
|
|
7463
|
+
copyRecursive(from, to) {
|
|
7464
|
+
fs4.cpSync(from, to, { recursive: true, force: true, verbatimSymlinks: true });
|
|
7465
|
+
},
|
|
7466
|
+
readFileUtf8(filePath) {
|
|
7467
|
+
try {
|
|
7468
|
+
return fs4.readFileSync(filePath, "utf8");
|
|
7469
|
+
} catch (error) {
|
|
7470
|
+
if (isEnoent3(error)) {
|
|
7471
|
+
return void 0;
|
|
7472
|
+
}
|
|
7473
|
+
throw error;
|
|
7474
|
+
}
|
|
7475
|
+
},
|
|
7476
|
+
writeFileUtf8(filePath, contents) {
|
|
7477
|
+
fs4.writeFileSync(filePath, contents, "utf8");
|
|
7478
|
+
},
|
|
7479
|
+
mkdirPrivate(dirPath) {
|
|
7480
|
+
fs4.mkdirSync(dirPath, { recursive: true, mode: OWNER_ONLY_DIR_MODE });
|
|
7481
|
+
fs4.chmodSync(dirPath, OWNER_ONLY_DIR_MODE);
|
|
7482
|
+
},
|
|
7483
|
+
writeFilePrivate(filePath, contents) {
|
|
7484
|
+
const temp = `${filePath}.${String(process.pid)}.tmp`;
|
|
7485
|
+
fs4.writeFileSync(temp, contents, { encoding: "utf8", mode: OWNER_ONLY_FILE_MODE });
|
|
7486
|
+
fs4.chmodSync(temp, OWNER_ONLY_FILE_MODE);
|
|
7487
|
+
fs4.renameSync(temp, filePath);
|
|
7488
|
+
},
|
|
7489
|
+
appendFilePrivate(filePath, contents) {
|
|
7490
|
+
const descriptor = fs4.openSync(filePath, "a", OWNER_ONLY_FILE_MODE);
|
|
7491
|
+
try {
|
|
7492
|
+
if ((fs4.fstatSync(descriptor).mode & PERMISSION_BITS) !== OWNER_ONLY_FILE_MODE) {
|
|
7493
|
+
fs4.fchmodSync(descriptor, OWNER_ONLY_FILE_MODE);
|
|
7494
|
+
}
|
|
7495
|
+
const bytes = Buffer.from(contents, "utf8");
|
|
7496
|
+
let written = 0;
|
|
7497
|
+
while (written < bytes.length) {
|
|
7498
|
+
written += fs4.writeSync(descriptor, bytes, written, bytes.length - written);
|
|
7499
|
+
}
|
|
7500
|
+
} finally {
|
|
7501
|
+
fs4.closeSync(descriptor);
|
|
7502
|
+
}
|
|
7503
|
+
},
|
|
7504
|
+
writeFileExclusive(filePath, contents) {
|
|
7505
|
+
try {
|
|
7506
|
+
fs4.writeFileSync(filePath, contents, { encoding: "utf8", flag: "wx" });
|
|
7507
|
+
return true;
|
|
7508
|
+
} catch (error) {
|
|
7509
|
+
if (isErrorWithCode(error, "EEXIST")) {
|
|
7510
|
+
return false;
|
|
7511
|
+
}
|
|
7512
|
+
throw error;
|
|
7513
|
+
}
|
|
7514
|
+
},
|
|
7515
|
+
hashFile(filePath) {
|
|
7516
|
+
try {
|
|
7517
|
+
return createHash5("sha256").update(fs4.readFileSync(filePath)).digest("hex");
|
|
7518
|
+
} catch (error) {
|
|
7519
|
+
if (isEnoent3(error) || isErrorWithCode(error, "EISDIR")) {
|
|
7520
|
+
return void 0;
|
|
7521
|
+
}
|
|
7522
|
+
throw error;
|
|
7523
|
+
}
|
|
7467
7524
|
}
|
|
7468
|
-
|
|
7525
|
+
};
|
|
7526
|
+
var INT32_BYTE_LENGTH = 4;
|
|
7527
|
+
function realSleepSync(ms) {
|
|
7528
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(INT32_BYTE_LENGTH)), 0, 0, ms);
|
|
7469
7529
|
}
|
|
7470
|
-
function
|
|
7471
|
-
|
|
7530
|
+
function realIsProcessAlive(pid) {
|
|
7531
|
+
try {
|
|
7532
|
+
process.kill(pid, 0);
|
|
7533
|
+
return true;
|
|
7534
|
+
} catch (error) {
|
|
7535
|
+
return isErrorWithCode(error, "EPERM");
|
|
7536
|
+
}
|
|
7472
7537
|
}
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7538
|
+
function realIsProcessZombie(pid) {
|
|
7539
|
+
const result = spawnSync("ps", ["-o", "stat=", "-p", String(pid)], { encoding: "utf8" });
|
|
7540
|
+
return result.status === 0 && result.stdout.trim().startsWith("Z");
|
|
7541
|
+
}
|
|
7542
|
+
function realIsProcessRunning(pid) {
|
|
7543
|
+
return realIsProcessAlive(pid) && !realIsProcessZombie(pid);
|
|
7544
|
+
}
|
|
7545
|
+
var realRunPort = {
|
|
7546
|
+
run(command, args) {
|
|
7547
|
+
const result = spawnSync(command, [...args], { encoding: "utf8" });
|
|
7548
|
+
return { status: result.status, stdout: result.stdout, stderr: result.stderr };
|
|
7477
7549
|
}
|
|
7478
7550
|
};
|
|
7479
|
-
function
|
|
7480
|
-
return
|
|
7551
|
+
function expandHome2(filePath) {
|
|
7552
|
+
return filePath.startsWith("~/") ? path19.join(os3.homedir(), filePath.slice(1)) : filePath;
|
|
7481
7553
|
}
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
throw new InvalidProviderNameError(name);
|
|
7488
|
-
}
|
|
7489
|
-
const kind = input.kind ?? "http";
|
|
7490
|
-
if (kind === "codex" && input.baseUrl !== void 0) {
|
|
7491
|
-
throw new ProviderKindMismatchError("A codex provider has no base URL: the launcher points its sessions at the front-door daemon. Drop --base-url.");
|
|
7492
|
-
}
|
|
7493
|
-
if (kind === "http" && input.baseUrl === void 0) {
|
|
7494
|
-
throw new ProviderKindMismatchError("An http provider needs --base-url.");
|
|
7495
|
-
}
|
|
7496
|
-
if (kind === "http" && !isEmptyCodexConfig(input.codex)) {
|
|
7497
|
-
throw new ProviderKindMismatchError("Codex settings apply only to a codex provider (--kind codex).");
|
|
7554
|
+
var MACOS_GUI_SESSION_MANAGER = "Aqua";
|
|
7555
|
+
function hasDesktopSession() {
|
|
7556
|
+
if (process.platform === "darwin") {
|
|
7557
|
+
const result = spawnSync("launchctl", ["managername"], { encoding: "utf8" });
|
|
7558
|
+
return result.status === 0 && result.stdout.trim() === MACOS_GUI_SESSION_MANAGER;
|
|
7498
7559
|
}
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
credential: { sources: input.sources, ...input.target === void 0 ? {} : { target: input.target } },
|
|
7502
|
-
...input.env === void 0 || Object.keys(input.env).length === 0 ? {} : { env: input.env }
|
|
7503
|
-
};
|
|
7504
|
-
const parsed = ProviderSchema.safeParse(
|
|
7505
|
-
kind === "codex" ? { kind, ...shared, ...isEmptyCodexConfig(input.codex) ? {} : { codex: input.codex } } : { ...shared, baseUrl: input.baseUrl }
|
|
7506
|
-
);
|
|
7507
|
-
if (!parsed.success) {
|
|
7508
|
-
throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
|
|
7560
|
+
if (process.platform === "win32") {
|
|
7561
|
+
return false;
|
|
7509
7562
|
}
|
|
7510
|
-
|
|
7511
|
-
return parsed.data;
|
|
7563
|
+
return (process.env.DISPLAY ?? "") !== "" || (process.env.WAYLAND_DISPLAY ?? "") !== "";
|
|
7512
7564
|
}
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
|
|
7525
|
-
|
|
7526
|
-
|
|
7565
|
+
var realCredentialPort = {
|
|
7566
|
+
readSecretFile(filePath) {
|
|
7567
|
+
const resolved = expandHome2(filePath);
|
|
7568
|
+
let content;
|
|
7569
|
+
let stat;
|
|
7570
|
+
try {
|
|
7571
|
+
stat = fs4.statSync(resolved);
|
|
7572
|
+
content = fs4.readFileSync(resolved, "utf8");
|
|
7573
|
+
} catch (error) {
|
|
7574
|
+
if (isEnoent3(error)) {
|
|
7575
|
+
return { found: false };
|
|
7576
|
+
}
|
|
7577
|
+
throw error;
|
|
7578
|
+
}
|
|
7579
|
+
return { found: true, content, mode: process.platform === "win32" ? void 0 : stat.mode };
|
|
7580
|
+
},
|
|
7581
|
+
runCommand(argv, options) {
|
|
7582
|
+
const [command, ...args] = argv;
|
|
7583
|
+
const result = spawnSync(command, args, {
|
|
7584
|
+
encoding: "utf8",
|
|
7585
|
+
timeout: options.timeoutMs,
|
|
7586
|
+
stdio: options.interactive ? ["inherit", "pipe", "inherit"] : ["ignore", "pipe", "pipe"]
|
|
7587
|
+
});
|
|
7588
|
+
if (result.error !== void 0) {
|
|
7589
|
+
const timedOut = isErrorWithCode(result.error, "ETIMEDOUT");
|
|
7590
|
+
return { status: result.status, stdout: "", stderr: timedOut ? "" : result.error.message, timedOut };
|
|
7591
|
+
}
|
|
7592
|
+
return { status: result.status, stdout: result.stdout, stderr: options.interactive ? "" : result.stderr, timedOut: false };
|
|
7593
|
+
},
|
|
7594
|
+
personPresent: () => process.stdin.isTTY || hasDesktopSession()
|
|
7595
|
+
};
|
|
7596
|
+
function resolveGitBranch(run, cwd) {
|
|
7597
|
+
const result = run.run("git", ["-C", cwd, "rev-parse", "--abbrev-ref", "HEAD"]);
|
|
7598
|
+
if (result.status !== 0) {
|
|
7599
|
+
return {};
|
|
7527
7600
|
}
|
|
7528
|
-
const
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
sources: input.sources ?? existing.credential.sources,
|
|
7532
|
-
...target === void 0 ? {} : { target },
|
|
7533
|
-
...cache === void 0 ? {} : { cache }
|
|
7534
|
-
},
|
|
7535
|
-
env: Object.keys(env).length === 0 ? void 0 : env
|
|
7536
|
-
};
|
|
7537
|
-
let candidate;
|
|
7538
|
-
if (isCodexProvider(existing)) {
|
|
7539
|
-
const codex = {
|
|
7540
|
-
...existing.codex,
|
|
7541
|
-
...input.codex,
|
|
7542
|
-
...existing.codex?.models === void 0 && input.codex?.models === void 0 ? {} : { models: { ...existing.codex?.models, ...input.codex?.models } }
|
|
7543
|
-
};
|
|
7544
|
-
candidate = { ...existing, ...shared, codex: isEmptyCodexConfig(codex) ? void 0 : codex };
|
|
7545
|
-
} else {
|
|
7546
|
-
candidate = { ...existing, ...shared, ...input.baseUrl === void 0 ? {} : { baseUrl: input.baseUrl } };
|
|
7601
|
+
const name = result.stdout.trim();
|
|
7602
|
+
if (name === "") {
|
|
7603
|
+
return {};
|
|
7547
7604
|
}
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
|
|
7605
|
+
if (name === "HEAD") {
|
|
7606
|
+
return { branchDetached: true };
|
|
7551
7607
|
}
|
|
7552
|
-
|
|
7553
|
-
return parsed.data;
|
|
7608
|
+
return { branch: name, branchDetached: false };
|
|
7554
7609
|
}
|
|
7555
|
-
function
|
|
7556
|
-
|
|
7557
|
-
|
|
7610
|
+
function resolveGitRoot(run, cwd) {
|
|
7611
|
+
const result = run.run("git", ["-C", cwd, "rev-parse", "--show-toplevel"]);
|
|
7612
|
+
if (result.status !== 0) {
|
|
7613
|
+
return void 0;
|
|
7558
7614
|
}
|
|
7559
|
-
|
|
7615
|
+
const root = result.stdout.trim();
|
|
7616
|
+
return root === "" ? void 0 : root;
|
|
7560
7617
|
}
|
|
7561
|
-
function
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7618
|
+
function detachedDaemonSpawnOptions(logFd, env) {
|
|
7619
|
+
return { detached: true, stdio: ["ignore", logFd, logFd], env };
|
|
7620
|
+
}
|
|
7621
|
+
function spawnDaemonThrough(executable) {
|
|
7622
|
+
return (paths, subcommand, logPath) => spawnDetachedDaemon({ command: executable, args: [subcommand] }, paths, subcommand, logPath);
|
|
7623
|
+
}
|
|
7624
|
+
function spawnDaemonThroughScript(script) {
|
|
7625
|
+
return (paths, subcommand, logPath) => spawnDetachedDaemon({ command: process.execPath, args: [script, subcommand] }, paths, subcommand, logPath);
|
|
7626
|
+
}
|
|
7627
|
+
function spawnDetachedDaemon(invocation, paths, subcommand, logPath) {
|
|
7628
|
+
fs4.mkdirSync(path19.dirname(logPath), { recursive: true });
|
|
7629
|
+
const logFd = fs4.openSync(logPath, "a");
|
|
7630
|
+
try {
|
|
7631
|
+
const child = spawn(invocation.command, invocation.args, detachedDaemonSpawnOptions(logFd, { ...process.env, AGENT_SHIM_HOME: paths.root }));
|
|
7632
|
+
child.unref();
|
|
7633
|
+
if (child.pid === void 0) {
|
|
7634
|
+
throw new Error(`spawning ${subcommand} returned no pid`);
|
|
7566
7635
|
}
|
|
7636
|
+
return child.pid;
|
|
7637
|
+
} finally {
|
|
7638
|
+
fs4.closeSync(logFd);
|
|
7567
7639
|
}
|
|
7568
|
-
return name;
|
|
7569
7640
|
}
|
|
7570
|
-
function
|
|
7571
|
-
|
|
7572
|
-
const name = cliName ?? (params.cascade === void 0 ? void 0 : cascadeProviderName(params.cascade));
|
|
7573
|
-
if (name === void 0) {
|
|
7574
|
-
return void 0;
|
|
7575
|
-
}
|
|
7576
|
-
const definition = loadProvider(params.paths.providersDir, name, params.port);
|
|
7577
|
-
if (definition === void 0) {
|
|
7578
|
-
const known = listProviderNames(params.port.readdir, params.paths.providersDir);
|
|
7579
|
-
return {
|
|
7580
|
-
ok: false,
|
|
7581
|
-
status: 1,
|
|
7582
|
-
message: `agent-shim: no provider named "${name}". ` + (known.length > 0 ? `Known providers: ${known.join(", ")}.` : "No providers are defined yet; run `agent-shim provider add`.")
|
|
7583
|
-
};
|
|
7584
|
-
}
|
|
7585
|
-
if (params.credentials === void 0) {
|
|
7586
|
-
return { ok: false, status: 1, message: `agent-shim: provider ${name} needs its credential resolved, but this launcher has no credential port wired` };
|
|
7587
|
-
}
|
|
7588
|
-
const resolution = resolveCredential({ credential: definition.credential, env: params.env, port: params.credentials, subject: `provider ${name}` });
|
|
7589
|
-
if (!resolution.ok) {
|
|
7590
|
-
return { ok: false, status: CREDENTIAL_UNAVAILABLE_EXIT, message: resolution.message };
|
|
7591
|
-
}
|
|
7592
|
-
return { ok: true, provider: { name, definition, credential: resolution.credential }, warnings: resolution.credential.warnings };
|
|
7641
|
+
function spawnHeadroomSupervisor(paths, spawnDaemon) {
|
|
7642
|
+
return spawnDaemon(paths, "__headroom-supervisor", paths.headroomLogPath);
|
|
7593
7643
|
}
|
|
7594
|
-
|
|
7595
|
-
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
|
|
7600
|
-
import path22 from "node:path";
|
|
7601
|
-
import { isSea } from "node:sea";
|
|
7602
|
-
|
|
7603
|
-
// src/headroom/ensure.ts
|
|
7604
|
-
var HEADROOM_START_TIMEOUT_MS = 12e4;
|
|
7605
|
-
var HEADROOM_LAUNCHER_POLL_MS = 250;
|
|
7606
|
-
var HeadroomStartError = class extends CliError {
|
|
7607
|
-
constructor(message) {
|
|
7608
|
-
super(message);
|
|
7609
|
-
this.name = "HeadroomStartError";
|
|
7610
|
-
}
|
|
7611
|
-
};
|
|
7612
|
-
function ensureHeadroom(params) {
|
|
7613
|
-
const { paths, ports } = params;
|
|
7614
|
-
const deadline = ports.now() + HEADROOM_START_TIMEOUT_MS;
|
|
7615
|
-
let spawnedSupervisor = false;
|
|
7616
|
-
let awaitingAllowlist = false;
|
|
7617
|
-
for (; ; ) {
|
|
7618
|
-
const state = readHeadroomState(ports.fs, paths.headroomStateFile);
|
|
7619
|
-
if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
|
|
7620
|
-
const daemonUp = state.socketPath !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
|
|
7621
|
-
if (daemonUp && state.socketPath !== void 0) {
|
|
7622
|
-
const required = params.requiredAllowlistHash?.();
|
|
7623
|
-
if (required === void 0 || state.allowlistHash === required) {
|
|
7624
|
-
writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
|
|
7625
|
-
return { socketPath: state.socketPath };
|
|
7626
|
-
}
|
|
7627
|
-
const holders = sessionsForSupervisor(ports.fs, paths.headroomSessionsDir, state.supervisorPid).filter((session) => ports.isRunning(session.pid));
|
|
7628
|
-
if (holders.length > 0) {
|
|
7629
|
-
throw new HeadroomStartError(
|
|
7630
|
-
`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.`
|
|
7631
|
-
);
|
|
7632
|
-
}
|
|
7633
|
-
awaitingAllowlist = true;
|
|
7634
|
-
}
|
|
7635
|
-
if (state.lastError !== void 0) {
|
|
7636
|
-
throw new HeadroomStartError(
|
|
7637
|
-
`the headroom daemon reported a fatal error and is not serving: ${state.lastError} (daemon log: ${paths.headroomLogPath})`
|
|
7638
|
-
);
|
|
7639
|
-
}
|
|
7640
|
-
} else if (!spawnedSupervisor) {
|
|
7641
|
-
ports.fs.mkdirp(paths.headroomDir);
|
|
7642
|
-
const created = ports.fs.writeFileExclusive(
|
|
7643
|
-
paths.headroomLockFile,
|
|
7644
|
-
`${JSON.stringify({ pid: params.launcherPid, at: ports.now() })}
|
|
7645
|
-
`
|
|
7646
|
-
);
|
|
7647
|
-
if (created) {
|
|
7648
|
-
ports.spawnSupervisor(paths);
|
|
7649
|
-
spawnedSupervisor = true;
|
|
7650
|
-
} else {
|
|
7651
|
-
const lock = readStartLock(ports.fs, paths.headroomLockFile);
|
|
7652
|
-
if (lock === void 0 || !ports.isRunning(lock.pid)) {
|
|
7653
|
-
ports.fs.removeRecursive(paths.headroomLockFile);
|
|
7654
|
-
continue;
|
|
7655
|
-
}
|
|
7656
|
-
}
|
|
7644
|
+
var SOCKET_PERMISSION_BITS = 511;
|
|
7645
|
+
var realHeadroomSocketTrust = {
|
|
7646
|
+
platform: process.platform,
|
|
7647
|
+
currentUid: () => {
|
|
7648
|
+
if (process.getuid === void 0) {
|
|
7649
|
+
throw new Error(`no POSIX user id on ${process.platform}`);
|
|
7657
7650
|
}
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
|
|
7661
|
-
|
|
7662
|
-
|
|
7651
|
+
return process.getuid();
|
|
7652
|
+
},
|
|
7653
|
+
lstat: (target) => {
|
|
7654
|
+
let stat;
|
|
7655
|
+
try {
|
|
7656
|
+
stat = fs4.lstatSync(target);
|
|
7657
|
+
} catch (error) {
|
|
7658
|
+
if (isEnoent3(error)) {
|
|
7659
|
+
return void 0;
|
|
7660
|
+
}
|
|
7661
|
+
throw error;
|
|
7663
7662
|
}
|
|
7664
|
-
|
|
7663
|
+
const kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "dir" : stat.isSocket() ? "socket" : "other";
|
|
7664
|
+
return { kind, uid: stat.uid, mode: stat.mode & SOCKET_PERMISSION_BITS };
|
|
7665
7665
|
}
|
|
7666
|
-
}
|
|
7667
|
-
|
|
7668
|
-
// src/headroom/settings.ts
|
|
7669
|
-
import { createHash as createHash4 } from "node:crypto";
|
|
7670
|
-
function settingsOf(config) {
|
|
7666
|
+
};
|
|
7667
|
+
function realHeadroomPort(paths, options) {
|
|
7671
7668
|
return {
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7669
|
+
ensure: ({ routesProvider }) => {
|
|
7670
|
+
const up = ensureHeadroom({
|
|
7671
|
+
paths,
|
|
7672
|
+
launcherPid: process.pid,
|
|
7673
|
+
...routesProvider ? { requiredAllowlistHash: () => hashAllowlist(currentHeadroomAllowlist(realFarmFs, paths)) } : {},
|
|
7674
|
+
ports: {
|
|
7675
|
+
fs: realFarmFs,
|
|
7676
|
+
// 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.
|
|
7677
|
+
isRunning: realIsProcessRunning,
|
|
7678
|
+
now: () => Date.now(),
|
|
7679
|
+
sleep: realSleepSync,
|
|
7680
|
+
spawnSupervisor: (layout) => spawnHeadroomSupervisor(layout, options.spawnDaemon)
|
|
7681
|
+
}
|
|
7682
|
+
});
|
|
7683
|
+
return {
|
|
7684
|
+
socketPath: up.socketPath,
|
|
7685
|
+
projectId: resolveGitRoot(realRunPort, options.cwd) ?? options.cwd
|
|
7686
|
+
};
|
|
7687
|
+
},
|
|
7688
|
+
release: () => {
|
|
7689
|
+
removeSession(realFarmFs, paths.headroomSessionsDir, process.pid);
|
|
7690
|
+
}
|
|
7678
7691
|
};
|
|
7679
7692
|
}
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
7693
|
+
var realProcPort = {
|
|
7694
|
+
env: process.env,
|
|
7695
|
+
argv: process.argv.slice(2),
|
|
7696
|
+
exit: (code) => process.exit(code)
|
|
7697
|
+
};
|
|
7698
|
+
var EXECUTE_BITS_MASK = 73;
|
|
7699
|
+
function listVersionsDir(dir) {
|
|
7700
|
+
let entries;
|
|
7701
|
+
try {
|
|
7702
|
+
entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
7703
|
+
} catch (error) {
|
|
7704
|
+
if (isEnoent3(error)) {
|
|
7705
|
+
return [];
|
|
7706
|
+
}
|
|
7707
|
+
throw error;
|
|
7708
|
+
}
|
|
7709
|
+
return entries.filter((entry) => entry.isFile()).map((entry) => {
|
|
7710
|
+
const fullPath = path19.join(dir, entry.name);
|
|
7711
|
+
const stat = fs4.statSync(fullPath);
|
|
7712
|
+
return {
|
|
7713
|
+
name: entry.name,
|
|
7714
|
+
isFile: true,
|
|
7715
|
+
isExecutable: (stat.mode & EXECUTE_BITS_MASK) !== 0,
|
|
7716
|
+
sizeBytes: stat.size
|
|
7717
|
+
};
|
|
7718
|
+
});
|
|
7689
7719
|
}
|
|
7690
|
-
|
|
7691
|
-
|
|
7720
|
+
var WINDOWS_DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.PS1";
|
|
7721
|
+
function windowsPathExtensions(pathext) {
|
|
7722
|
+
return (pathext ?? WINDOWS_DEFAULT_PATHEXT).split(";").map((ext) => ext.trim()).filter((ext) => ext.length > 0);
|
|
7692
7723
|
}
|
|
7693
|
-
function
|
|
7694
|
-
|
|
7724
|
+
function resolveExecutableCandidate(dir, name, env) {
|
|
7725
|
+
const candidate = path19.join(dir, name);
|
|
7726
|
+
if (env.platform === "win32") {
|
|
7727
|
+
const extensions = windowsPathExtensions(env.pathext);
|
|
7728
|
+
const alreadyHasRecognisedExtension = extensions.some((ext) => candidate.toLowerCase().endsWith(ext.toLowerCase()));
|
|
7729
|
+
if (alreadyHasRecognisedExtension) {
|
|
7730
|
+
return env.statFileMode(candidate) !== void 0 ? candidate : void 0;
|
|
7731
|
+
}
|
|
7732
|
+
for (const ext of extensions) {
|
|
7733
|
+
const withExt = candidate + ext;
|
|
7734
|
+
if (env.statFileMode(withExt) !== void 0) {
|
|
7735
|
+
return withExt;
|
|
7736
|
+
}
|
|
7737
|
+
}
|
|
7738
|
+
return void 0;
|
|
7739
|
+
}
|
|
7740
|
+
const mode = env.statFileMode(candidate);
|
|
7741
|
+
return mode !== void 0 && (mode & EXECUTE_BITS_MASK) !== 0 ? candidate : void 0;
|
|
7695
7742
|
}
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
|
|
7704
|
-
|
|
7705
|
-
|
|
7706
|
-
function headroomSocketPath(paths, supervisorPid) {
|
|
7707
|
-
return path21.join(paths.headroomSocketDir, `${String(supervisorPid)}${HEADROOM_SOCKET_SUFFIX}`);
|
|
7743
|
+
function realStatFileMode(candidate) {
|
|
7744
|
+
try {
|
|
7745
|
+
const stat = fs4.statSync(candidate);
|
|
7746
|
+
return stat.isFile() ? stat.mode : void 0;
|
|
7747
|
+
} catch (error) {
|
|
7748
|
+
if (isEnoent3(error)) {
|
|
7749
|
+
return void 0;
|
|
7750
|
+
}
|
|
7751
|
+
throw error;
|
|
7752
|
+
}
|
|
7708
7753
|
}
|
|
7709
|
-
function
|
|
7710
|
-
return
|
|
7754
|
+
function findExecutableInDir(dir, name) {
|
|
7755
|
+
return resolveExecutableCandidate(dir, name, {
|
|
7756
|
+
platform: process.platform,
|
|
7757
|
+
pathext: process.env.PATHEXT,
|
|
7758
|
+
statFileMode: realStatFileMode
|
|
7759
|
+
});
|
|
7711
7760
|
}
|
|
7712
|
-
function
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
}
|
|
7719
|
-
if (stat.kind !== expected) {
|
|
7720
|
-
return `the headroom ${label} ${target} is not a ${expected === "dir" ? "directory" : "socket"}`;
|
|
7721
|
-
}
|
|
7722
|
-
if (stat.uid !== uid) {
|
|
7723
|
-
return `the headroom ${label} ${target} is owned by uid ${String(stat.uid)}, not by this user (uid ${String(uid)})`;
|
|
7724
|
-
}
|
|
7725
|
-
if ((stat.mode & GROUP_OR_OTHER_BITS) !== 0) {
|
|
7726
|
-
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`;
|
|
7761
|
+
function searchPathForExecutable(pathDirs, name, findExecutable) {
|
|
7762
|
+
for (const dir of pathDirs) {
|
|
7763
|
+
const found = findExecutable(dir, name);
|
|
7764
|
+
if (found !== void 0) {
|
|
7765
|
+
return found;
|
|
7766
|
+
}
|
|
7727
7767
|
}
|
|
7728
7768
|
return void 0;
|
|
7729
7769
|
}
|
|
7730
|
-
function
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
return { refused: unsupported };
|
|
7770
|
+
function resolveOwnExecutablePath(env) {
|
|
7771
|
+
if (env.argv1 !== void 0 && !env.argv1.includes("/") && !env.argv1.includes("\\")) {
|
|
7772
|
+
return searchPathForExecutable(env.pathDirs, env.argv1, env.findExecutableInDir) ?? env.execPath;
|
|
7734
7773
|
}
|
|
7735
|
-
const
|
|
7736
|
-
const
|
|
7737
|
-
const
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
function prepareHeadroomSocket(fs12, ports, paths, supervisorPid) {
|
|
7741
|
-
const unsupported = platformRefusal(ports.platform);
|
|
7742
|
-
if (unsupported !== void 0) {
|
|
7743
|
-
return { refused: unsupported };
|
|
7744
|
-
}
|
|
7745
|
-
const socketPath = headroomSocketPath(paths, supervisorPid);
|
|
7746
|
-
const length = Buffer.byteLength(socketPath, "utf8");
|
|
7747
|
-
if (length > UNIX_SOCKET_PATH_MAX_BYTES) {
|
|
7748
|
-
return {
|
|
7749
|
-
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`
|
|
7750
|
-
};
|
|
7751
|
-
}
|
|
7752
|
-
if (ports.lstat(paths.headroomSocketDir)?.kind === "symlink") {
|
|
7753
|
-
return { refused: `the headroom socket directory ${paths.headroomSocketDir} is a symlink; it must be the real directory` };
|
|
7774
|
+
const candidate = env.argv1 === void 0 ? env.execPath : path19.resolve(env.cwd, env.argv1);
|
|
7775
|
+
const candidateDir = path19.dirname(candidate);
|
|
7776
|
+
const candidateDirOnPath = env.pathDirs.some((dir) => path19.resolve(dir) === candidateDir);
|
|
7777
|
+
if (candidateDirOnPath) {
|
|
7778
|
+
return candidate;
|
|
7754
7779
|
}
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
return refusal === void 0 ? { socketPath } : { refused: refusal };
|
|
7780
|
+
const viaPath = searchPathForExecutable(env.pathDirs, path19.basename(candidate), env.findExecutableInDir);
|
|
7781
|
+
return viaPath ?? candidate;
|
|
7758
7782
|
}
|
|
7759
|
-
function
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
7783
|
+
function realOwnExecutablePath() {
|
|
7784
|
+
return resolveOwnExecutablePath({
|
|
7785
|
+
argv1: process.argv[1],
|
|
7786
|
+
execPath: process.execPath,
|
|
7787
|
+
cwd: process.cwd(),
|
|
7788
|
+
pathDirs: (process.env.PATH ?? "").split(path19.delimiter).filter((dir) => dir !== ""),
|
|
7789
|
+
findExecutableInDir
|
|
7790
|
+
});
|
|
7791
|
+
}
|
|
7792
|
+
function resolveContentSourcePath(env) {
|
|
7793
|
+
return env.isSea ? env.execPath : env.argv1 ?? env.execPath;
|
|
7794
|
+
}
|
|
7795
|
+
function realContentSourcePath() {
|
|
7796
|
+
return resolveContentSourcePath({ isSea: isSea(), execPath: process.execPath, argv1: process.argv[1] });
|
|
7797
|
+
}
|
|
7798
|
+
function realResolveClaudeBinary(isOwnBinary) {
|
|
7799
|
+
return (request4) => discoverClaudeBinary({
|
|
7800
|
+
listVersionsDir,
|
|
7801
|
+
pathDirs: (process.env.PATH ?? "").split(path19.delimiter).filter((dir) => dir !== ""),
|
|
7802
|
+
findExecutableInDir,
|
|
7803
|
+
isOwnBinary,
|
|
7804
|
+
...request4?.version === void 0 ? {} : { version: request4.version }
|
|
7805
|
+
});
|
|
7806
|
+
}
|
|
7807
|
+
function realInstalledClaudeVersions() {
|
|
7808
|
+
return installedVersions(listVersionsDir(defaultVersionsDir()));
|
|
7809
|
+
}
|
|
7810
|
+
|
|
7811
|
+
// src/launchSelection.ts
|
|
7812
|
+
function selectLaunch(params, loaded) {
|
|
7813
|
+
const { paths, cwd } = params;
|
|
7814
|
+
const selections = readDirectorySelections(loaded);
|
|
7815
|
+
const decidedIdentity = decideIdentity({
|
|
7816
|
+
env: params.env,
|
|
7817
|
+
argv0Identity: params.identity,
|
|
7818
|
+
directoryPinnedIdentity: selections.identity,
|
|
7819
|
+
readActiveIdentityFile: () => {
|
|
7820
|
+
const raw = realFsPort.readFileUtf8(paths.activeIdentityFile);
|
|
7821
|
+
if (raw === void 0) {
|
|
7822
|
+
return void 0;
|
|
7823
|
+
}
|
|
7824
|
+
const trimmed = raw.trim();
|
|
7825
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
7765
7826
|
}
|
|
7766
|
-
|
|
7767
|
-
|
|
7827
|
+
});
|
|
7828
|
+
let poolPick;
|
|
7829
|
+
if (decidedIdentity.pool !== void 0) {
|
|
7830
|
+
const pools = loaded.globalConfig?.pools ?? {};
|
|
7831
|
+
if (pools[decidedIdentity.pool] === void 0) {
|
|
7832
|
+
throw new PoolNotFoundError(decidedIdentity.pool);
|
|
7833
|
+
}
|
|
7834
|
+
poolPick = collectPoolPick({ paths, fs: realFsPort, usageFs: realFarmFs, poolName: decidedIdentity.pool, pools, directory: cwd, nowMs: Date.now() });
|
|
7768
7835
|
}
|
|
7769
|
-
|
|
7836
|
+
const identity = poolPick?.pick === void 0 ? decidedIdentity : { ...decidedIdentity, name: poolPick.pick };
|
|
7837
|
+
const loadedIdentity = identity.name === void 0 ? void 0 : loadIdentity(paths.identitiesDir, identity.name, realFsPort);
|
|
7838
|
+
const configProfile = decideConfigProfile({
|
|
7839
|
+
env: params.env,
|
|
7840
|
+
...params.configProfile === void 0 ? {} : { cliFlagConfigProfile: params.configProfile },
|
|
7841
|
+
directoryRuleConfigProfile: selections.configProfile,
|
|
7842
|
+
identityDefaultConfigProfile: loadedIdentity?.config.defaultConfigProfile,
|
|
7843
|
+
globalDefaultConfigProfile: loaded.globalConfig?.defaultConfigProfile
|
|
7844
|
+
});
|
|
7845
|
+
return {
|
|
7846
|
+
selection: { identity, ...poolPick === void 0 ? {} : { poolPick }, configProfile },
|
|
7847
|
+
...loadedIdentity === void 0 ? {} : { identityConfig: loadedIdentity.config }
|
|
7848
|
+
};
|
|
7849
|
+
}
|
|
7850
|
+
function resolveLaunchSelection(params) {
|
|
7851
|
+
const loaded = loadCascadeInput({ paths: params.paths, home: os4.homedir(), cwd: params.cwd, read: cosmiconfigReader() });
|
|
7852
|
+
return selectLaunch(params, loaded).selection;
|
|
7770
7853
|
}
|
|
7771
7854
|
|
|
7772
|
-
// src/
|
|
7773
|
-
import
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
return false;
|
|
7855
|
+
// src/paths.ts
|
|
7856
|
+
import fs5 from "node:fs";
|
|
7857
|
+
import os5 from "node:os";
|
|
7858
|
+
import path20 from "node:path";
|
|
7859
|
+
function resolveAgentShimHome(env = process.env, home = os5.homedir(), isDirectory = isExistingDirectory) {
|
|
7860
|
+
const fromEnv = env.AGENT_SHIM_HOME;
|
|
7861
|
+
if (fromEnv !== void 0 && fromEnv !== "") {
|
|
7862
|
+
return fromEnv;
|
|
7781
7863
|
}
|
|
7782
|
-
const
|
|
7783
|
-
|
|
7864
|
+
const current = path20.join(home, HOME_DIRNAME);
|
|
7865
|
+
const legacy = path20.join(home, LEGACY_HOME_DIRNAME);
|
|
7866
|
+
return !isDirectory(current) && isDirectory(legacy) ? legacy : current;
|
|
7784
7867
|
}
|
|
7785
|
-
function
|
|
7786
|
-
|
|
7787
|
-
return ref !== void 0 && COMMIT_SHA_RE.test(ref) ? ref.toLowerCase() : void 0;
|
|
7868
|
+
function isExistingDirectory(candidate) {
|
|
7869
|
+
return fs5.statSync(candidate, { throwIfNoEntry: false })?.isDirectory() ?? false;
|
|
7788
7870
|
}
|
|
7789
|
-
|
|
7790
|
-
|
|
7791
|
-
|
|
7871
|
+
function resolveClaudeHome() {
|
|
7872
|
+
const fromEnv = process.env.AGENT_SHIM_CLAUDE_HOME;
|
|
7873
|
+
if (fromEnv !== void 0 && fromEnv !== "") {
|
|
7874
|
+
return fromEnv;
|
|
7875
|
+
}
|
|
7876
|
+
return path20.join(os5.homedir(), ".claude");
|
|
7877
|
+
}
|
|
7878
|
+
function buildLayoutPaths(root) {
|
|
7792
7879
|
return {
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
7880
|
+
root,
|
|
7881
|
+
identitiesDir: path20.join(root, "identities"),
|
|
7882
|
+
configProfilesDir: path20.join(root, "config-profiles"),
|
|
7883
|
+
providersDir: path20.join(root, "providers"),
|
|
7884
|
+
directoryRulesFile: path20.join(root, "directory-rules.json"),
|
|
7885
|
+
activeIdentityFile: path20.join(root, "active-identity"),
|
|
7886
|
+
globalConfigFile: path20.join(root, "config.json"),
|
|
7887
|
+
categoriesLocalFile: path20.join(root, "categories.local.json"),
|
|
7888
|
+
claudeShimFile: path20.join(root, "claude-shim.json"),
|
|
7889
|
+
headroomDir: path20.join(root, "headroom"),
|
|
7890
|
+
headroomStateFile: path20.join(root, "headroom", `state.v${String(HEADROOM_STATE_SCHEMA_VERSION)}.json`),
|
|
7891
|
+
headroomSocketDir: path20.join(root, "headroom", "run"),
|
|
7892
|
+
headroomLockFile: path20.join(root, "headroom", "start.lock"),
|
|
7893
|
+
headroomSessionsDir: path20.join(root, "headroom", "sessions"),
|
|
7894
|
+
logsDir: path20.join(root, "logs"),
|
|
7895
|
+
headroomLogPath: path20.join(root, "logs", "headroom.log"),
|
|
7896
|
+
frontdoorDir: path20.join(root, "frontdoor"),
|
|
7897
|
+
frontdoorCaDir: path20.join(root, "frontdoor", "ca"),
|
|
7898
|
+
frontdoorCaCertFile: path20.join(root, "frontdoor", "ca", "ca.pem"),
|
|
7899
|
+
frontdoorCaKeyFile: path20.join(root, "frontdoor", "ca", "ca.key"),
|
|
7900
|
+
frontdoorCaBundlesDir: path20.join(root, "frontdoor", "ca", "bundles"),
|
|
7901
|
+
chatgptSignInFile: path20.join(root, "codex", "chatgpt-sign-in.json"),
|
|
7902
|
+
frontdoorStateFile: path20.join(root, "frontdoor", "state.json"),
|
|
7903
|
+
frontdoorLockFile: path20.join(root, "frontdoor", "start.lock"),
|
|
7904
|
+
frontdoorControlTokenFile: path20.join(root, "frontdoor", "control-token"),
|
|
7905
|
+
frontdoorSessionsDir: path20.join(root, "frontdoor", "sessions"),
|
|
7906
|
+
frontdoorRcCredentialsDir: path20.join(root, "frontdoor", "rc-credentials"),
|
|
7907
|
+
frontdoorLogPath: path20.join(root, "logs", "frontdoor.log"),
|
|
7908
|
+
usageDir: path20.join(root, "usage"),
|
|
7909
|
+
usageLogDir: path20.join(root, "usage", "log"),
|
|
7910
|
+
usageSnapshotsDir: path20.join(root, "usage", "snapshots"),
|
|
7911
|
+
usagePicksFile: path20.join(root, "usage", "picks.json")
|
|
7796
7912
|
};
|
|
7797
7913
|
}
|
|
7798
|
-
|
|
7799
|
-
|
|
7800
|
-
var HEADROOM_START_RETRY_BUDGET = 5;
|
|
7801
|
-
var HEADROOM_READY_TIMEOUT_MS = 15e3;
|
|
7802
|
-
var HEADROOM_POLL_MS = 1e3;
|
|
7803
|
-
var MS_PER_MINUTE2 = 6e4;
|
|
7804
|
-
function backoffForAttempt(attempt) {
|
|
7805
|
-
return Math.min(HEADROOM_BACKOFF_CAP_MS, HEADROOM_BACKOFF_BASE_MS * 2 ** (attempt - 1));
|
|
7914
|
+
function resolveLayoutPaths() {
|
|
7915
|
+
return buildLayoutPaths(resolveAgentShimHome());
|
|
7806
7916
|
}
|
|
7807
|
-
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
return cmp === 0;
|
|
7824
|
-
case "!=":
|
|
7825
|
-
return cmp !== 0;
|
|
7826
|
-
case ">=":
|
|
7827
|
-
return cmp >= 0;
|
|
7828
|
-
case "<=":
|
|
7829
|
-
return cmp <= 0;
|
|
7830
|
-
case ">":
|
|
7831
|
-
return cmp > 0;
|
|
7832
|
-
case "<":
|
|
7833
|
-
return cmp < 0;
|
|
7834
|
-
case "~=":
|
|
7835
|
-
return compareVersions2(installed.slice(0, Math.max(0, wanted.length - 1)), wanted.slice(0, Math.max(0, wanted.length - 1))) === 0 && cmp >= 0;
|
|
7836
|
-
default:
|
|
7837
|
-
return true;
|
|
7838
|
-
}
|
|
7917
|
+
|
|
7918
|
+
// src/realCredentialCache.ts
|
|
7919
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
7920
|
+
import fs6 from "node:fs";
|
|
7921
|
+
import path21 from "node:path";
|
|
7922
|
+
|
|
7923
|
+
// src/credentialCache.ts
|
|
7924
|
+
import { z as z11 } from "zod";
|
|
7925
|
+
var MS_PER_UNIT = { s: 1e3, m: 6e4, h: 36e5, d: 864e5 };
|
|
7926
|
+
var CachedCredentialSchema = z11.strictObject({
|
|
7927
|
+
token: z11.string().min(1),
|
|
7928
|
+
fetchedAt: z11.number().int().nonnegative(),
|
|
7929
|
+
source: CredentialSourceSchema
|
|
7930
|
+
});
|
|
7931
|
+
function effectiveStore(cache, platform) {
|
|
7932
|
+
return cache.store ?? (platform === "darwin" ? "keychain" : "file");
|
|
7839
7933
|
}
|
|
7840
|
-
function
|
|
7841
|
-
|
|
7842
|
-
if (match === null) {
|
|
7934
|
+
function ttlMs(cache) {
|
|
7935
|
+
if (cache.ttl === void 0) {
|
|
7843
7936
|
return void 0;
|
|
7844
7937
|
}
|
|
7845
|
-
|
|
7938
|
+
const multiplier = MS_PER_UNIT[cache.ttl.slice(-1)];
|
|
7939
|
+
if (multiplier === void 0) {
|
|
7940
|
+
throw new Error(`credential cache ttl "${cache.ttl}" has no recognised unit`);
|
|
7941
|
+
}
|
|
7942
|
+
return Number(cache.ttl.slice(0, -1)) * multiplier;
|
|
7846
7943
|
}
|
|
7847
|
-
function
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
if (left !== right) {
|
|
7852
|
-
return left - right;
|
|
7853
|
-
}
|
|
7944
|
+
function isFresh(entry, cache, now) {
|
|
7945
|
+
const limit = ttlMs(cache);
|
|
7946
|
+
if (entry.fetchedAt > now) {
|
|
7947
|
+
return false;
|
|
7854
7948
|
}
|
|
7855
|
-
return 0;
|
|
7949
|
+
return limit === void 0 || now - entry.fetchedAt < limit;
|
|
7856
7950
|
}
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
const
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7951
|
+
function cacheAgeMs(entry, now) {
|
|
7952
|
+
return Math.max(0, now - entry.fetchedAt);
|
|
7953
|
+
}
|
|
7954
|
+
function describeCachedCredential(params) {
|
|
7955
|
+
const store = effectiveStore(params.cache, params.platform);
|
|
7956
|
+
let entry;
|
|
7957
|
+
try {
|
|
7958
|
+
entry = params.port.read(store, params.owner);
|
|
7959
|
+
} catch (error) {
|
|
7960
|
+
return { store, status: "unreadable", reason: error instanceof Error ? error.message : String(error) };
|
|
7865
7961
|
}
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
return true;
|
|
7877
|
-
};
|
|
7878
|
-
const fail = (message) => {
|
|
7879
|
-
if (writeOwnState({ lastError: message })) {
|
|
7880
|
-
fs12.removeRecursive(paths.headroomLockFile);
|
|
7881
|
-
}
|
|
7882
|
-
ports.log(`agent-shim headroom supervisor: ${message}`);
|
|
7883
|
-
return 1;
|
|
7884
|
-
};
|
|
7885
|
-
const ensureInstalled = () => {
|
|
7886
|
-
const version2 = ports.headroomVersion();
|
|
7887
|
-
const versionOk = version2 !== void 0 && versionSatisfies(version2, config.source);
|
|
7888
|
-
const pinned = pinnedGitCommit(config.source);
|
|
7889
|
-
const commitOk = pinned === void 0 || ports.installedCommit() === pinned;
|
|
7890
|
-
if (versionOk && commitOk && (installedSource === void 0 || installedSource === config.source)) {
|
|
7891
|
-
installedSource = config.source;
|
|
7892
|
-
return true;
|
|
7893
|
-
}
|
|
7894
|
-
ports.log(`agent-shim headroom supervisor: installing headroom from ${config.source}`);
|
|
7895
|
-
const result = ports.install(config.source);
|
|
7896
|
-
if (!result.ok) {
|
|
7897
|
-
return false;
|
|
7898
|
-
}
|
|
7899
|
-
installedSource = config.source;
|
|
7900
|
-
return true;
|
|
7962
|
+
if (entry === void 0) {
|
|
7963
|
+
return { store, status: "empty" };
|
|
7964
|
+
}
|
|
7965
|
+
const limit = ttlMs(params.cache);
|
|
7966
|
+
const ageMs = cacheAgeMs(entry, params.nowMs);
|
|
7967
|
+
return {
|
|
7968
|
+
store,
|
|
7969
|
+
status: isFresh(entry, params.cache, params.nowMs) ? "fresh" : "expired",
|
|
7970
|
+
ageMs,
|
|
7971
|
+
...limit === void 0 ? {} : { expiresInMs: limit - ageMs }
|
|
7901
7972
|
};
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
7973
|
+
}
|
|
7974
|
+
function formatCachedCredentialState(state) {
|
|
7975
|
+
if (state.status === "empty") {
|
|
7976
|
+
return `no cached entry in the ${state.store} store yet`;
|
|
7905
7977
|
}
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
if (!ensureInstalled()) {
|
|
7909
|
-
return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
|
|
7978
|
+
if (state.status === "unreadable") {
|
|
7979
|
+
return `the ${state.store} store cannot be read here: ${state.reason}`;
|
|
7910
7980
|
}
|
|
7911
|
-
|
|
7912
|
-
|
|
7913
|
-
ports.log(`agent-shim headroom supervisor ${String(ports.ownPid)}: managing headroom on allowlist [${allowlistOf(ports).join(", ")}]`);
|
|
7914
|
-
const orphanPid = previousState?.headroomPid;
|
|
7915
|
-
if (orphanPid !== void 0 && ports.isRunning(orphanPid)) {
|
|
7916
|
-
ports.log(`agent-shim headroom supervisor: stopping daemon pid ${String(orphanPid)} left behind by the previous supervisor`);
|
|
7917
|
-
ports.stopProcess(orphanPid);
|
|
7981
|
+
if (state.status === "expired" && (state.expiresInMs === void 0 || state.expiresInMs > 0)) {
|
|
7982
|
+
return "cached with a timestamp in the future, treated as expired";
|
|
7918
7983
|
}
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
let consecutiveFailures = 0;
|
|
7923
|
-
let idleSince;
|
|
7924
|
-
let superseded = false;
|
|
7925
|
-
let ticks = 0;
|
|
7926
|
-
for (; ; ) {
|
|
7927
|
-
if (options.tickLimit !== void 0 && ticks >= options.tickLimit) {
|
|
7928
|
-
return HEADROOM_SUPERVISOR_STILL_RUNNING;
|
|
7929
|
-
}
|
|
7930
|
-
ticks += 1;
|
|
7931
|
-
config = ports.readConfig();
|
|
7932
|
-
const settingsHash = hashSettings(config.settings);
|
|
7933
|
-
const allowlist = allowlistOf(ports);
|
|
7934
|
-
const allowlistHash = hashAllowlist(allowlist);
|
|
7935
|
-
pruneDeadSessions(fs12, paths.headroomSessionsDir, ports.isRunning);
|
|
7936
|
-
const ownsNow = ownsState();
|
|
7937
|
-
if (!ownsNow && !superseded) {
|
|
7938
|
-
ports.log("agent-shim headroom supervisor: another supervisor now owns the state file; serving this daemon's own sessions until they end");
|
|
7939
|
-
}
|
|
7940
|
-
superseded = !ownsNow;
|
|
7941
|
-
const crashed = headroomPid !== void 0 && !ports.isRunning(headroomPid);
|
|
7942
|
-
if (headroomPid === void 0 || crashed) {
|
|
7943
|
-
if (crashed) {
|
|
7944
|
-
ports.log(`agent-shim headroom supervisor: headroom pid ${String(headroomPid)} died`);
|
|
7945
|
-
headroomPid = void 0;
|
|
7946
|
-
runningHash = void 0;
|
|
7947
|
-
runningSettingsHash = void 0;
|
|
7948
|
-
writeOwnState({
|
|
7949
|
-
supervisorPid: ports.ownPid,
|
|
7950
|
-
version,
|
|
7951
|
-
installedSource
|
|
7952
|
-
});
|
|
7953
|
-
}
|
|
7954
|
-
if (consecutiveFailures >= HEADROOM_START_RETRY_BUDGET) {
|
|
7955
|
-
return fail(`headroom failed to become ready ${String(consecutiveFailures)} times in a row; giving up`);
|
|
7956
|
-
}
|
|
7957
|
-
if (installedSource !== config.source && !ensureInstalled()) {
|
|
7958
|
-
return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
|
|
7959
|
-
}
|
|
7960
|
-
const pid = ports.spawnHeadroom(socketPath, allowlist, config.settings);
|
|
7961
|
-
const ready = await waitUntilReady(ports, socketPath);
|
|
7962
|
-
if (ready) {
|
|
7963
|
-
consecutiveFailures = 0;
|
|
7964
|
-
headroomPid = pid;
|
|
7965
|
-
runningHash = allowlistHash;
|
|
7966
|
-
runningSettingsHash = settingsHash;
|
|
7967
|
-
const recorded = writeOwnState({
|
|
7968
|
-
supervisorPid: ports.ownPid,
|
|
7969
|
-
headroomPid: pid,
|
|
7970
|
-
socketPath,
|
|
7971
|
-
version,
|
|
7972
|
-
allowlistHash,
|
|
7973
|
-
settingsHash,
|
|
7974
|
-
installedSource: config.source
|
|
7975
|
-
});
|
|
7976
|
-
if (recorded) {
|
|
7977
|
-
fs12.removeRecursive(paths.headroomLockFile);
|
|
7978
|
-
}
|
|
7979
|
-
idleSince = void 0;
|
|
7980
|
-
ports.log(`agent-shim headroom supervisor: headroom pid ${String(pid)} ready on unix socket ${socketPath}`);
|
|
7981
|
-
} else {
|
|
7982
|
-
ports.stopProcess(pid);
|
|
7983
|
-
consecutiveFailures += 1;
|
|
7984
|
-
ports.log(
|
|
7985
|
-
`agent-shim headroom supervisor: headroom did not become ready on unix socket ${socketPath} (attempt ${String(consecutiveFailures)} of ${String(HEADROOM_START_RETRY_BUDGET)})`
|
|
7986
|
-
);
|
|
7987
|
-
await ports.sleep(backoffForAttempt(consecutiveFailures));
|
|
7988
|
-
continue;
|
|
7989
|
-
}
|
|
7990
|
-
} else {
|
|
7991
|
-
if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
|
|
7992
|
-
if (sessionsForSupervisor(fs12, paths.headroomSessionsDir, ports.ownPid).length === 0) {
|
|
7993
|
-
ports.log("agent-shim headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
|
|
7994
|
-
ports.stopProcess(headroomPid);
|
|
7995
|
-
headroomPid = void 0;
|
|
7996
|
-
runningHash = void 0;
|
|
7997
|
-
runningSettingsHash = void 0;
|
|
7998
|
-
continue;
|
|
7999
|
-
}
|
|
8000
|
-
}
|
|
8001
|
-
const sessions = sessionsForSupervisor(fs12, paths.headroomSessionsDir, ports.ownPid);
|
|
8002
|
-
if (sessions.length === 0) {
|
|
8003
|
-
idleSince ??= ports.now();
|
|
8004
|
-
const idleMs = ports.now() - idleSince;
|
|
8005
|
-
if (idleMs >= config.idleShutdownMinutes * MS_PER_MINUTE2) {
|
|
8006
|
-
ports.log(
|
|
8007
|
-
`agent-shim headroom supervisor: no sessions for ${String(config.idleShutdownMinutes)} minute(s); stopping headroom and exiting`
|
|
8008
|
-
);
|
|
8009
|
-
ports.stopProcess(headroomPid);
|
|
8010
|
-
writeOwnState({ version });
|
|
8011
|
-
return 0;
|
|
8012
|
-
}
|
|
8013
|
-
} else {
|
|
8014
|
-
idleSince = void 0;
|
|
8015
|
-
}
|
|
8016
|
-
}
|
|
8017
|
-
await ports.sleep(HEADROOM_POLL_MS);
|
|
7984
|
+
const age = `cached ${formatAge(state.ageMs)} ago`;
|
|
7985
|
+
if (state.expiresInMs === void 0) {
|
|
7986
|
+
return `${age}, no expiry`;
|
|
8018
7987
|
}
|
|
7988
|
+
return state.status === "fresh" ? `${age}, expires in ${formatAge(state.expiresInMs)}` : `${age}, expired ${formatAge(-state.expiresInMs)} ago`;
|
|
8019
7989
|
}
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
|
|
7990
|
+
|
|
7991
|
+
// src/realCredentialCache.ts
|
|
7992
|
+
var KEYCHAIN_CACHE_SERVICE = "agent-shim-credential-cache";
|
|
7993
|
+
var CACHE_DIR_MODE = 448;
|
|
7994
|
+
var CACHE_FILE_MODE = 384;
|
|
7995
|
+
function cacheFileNameFor(owner) {
|
|
7996
|
+
return `${owner.replace(/[^A-Za-z0-9._-]/g, "_")}.json`;
|
|
8026
7997
|
}
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
if (ports.now() >= deadline) {
|
|
8034
|
-
return false;
|
|
8035
|
-
}
|
|
8036
|
-
await ports.sleep(HEADROOM_POLL_MS);
|
|
7998
|
+
function parseEntry(raw) {
|
|
7999
|
+
let json;
|
|
8000
|
+
try {
|
|
8001
|
+
json = JSON.parse(raw);
|
|
8002
|
+
} catch {
|
|
8003
|
+
return void 0;
|
|
8037
8004
|
}
|
|
8005
|
+
const parsed = CachedCredentialSchema.safeParse(json);
|
|
8006
|
+
return parsed.success ? parsed.data : void 0;
|
|
8038
8007
|
}
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
8043
|
-
}
|
|
8044
|
-
var realFsPort = {
|
|
8045
|
-
readFileUtf8(filePath) {
|
|
8046
|
-
try {
|
|
8047
|
-
return fs7.readFileSync(filePath, "utf8");
|
|
8048
|
-
} catch (error) {
|
|
8049
|
-
if (isEnoent3(error)) {
|
|
8050
|
-
return void 0;
|
|
8051
|
-
}
|
|
8052
|
-
throw error;
|
|
8053
|
-
}
|
|
8054
|
-
},
|
|
8055
|
-
readConfigFile: cosmiconfigReader(),
|
|
8056
|
-
readdir(dirPath) {
|
|
8057
|
-
try {
|
|
8058
|
-
return fs7.readdirSync(dirPath);
|
|
8059
|
-
} catch (error) {
|
|
8060
|
-
if (isEnoent3(error)) {
|
|
8061
|
-
return [];
|
|
8062
|
-
}
|
|
8063
|
-
throw error;
|
|
8064
|
-
}
|
|
8008
|
+
function requireKeychain() {
|
|
8009
|
+
if (process.platform !== "darwin") {
|
|
8010
|
+
throw new Error("the keychain credential store needs macOS; use --credential-cache-store file");
|
|
8065
8011
|
}
|
|
8066
|
-
};
|
|
8067
|
-
function isErrorWithCode(error, code) {
|
|
8068
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
8069
8012
|
}
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
|
|
8079
|
-
if (
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
return { kind, mtimeMs: stat.mtimeMs, sizeBytes: stat.size };
|
|
8086
|
-
},
|
|
8087
|
-
readdir(dirPath) {
|
|
8088
|
-
try {
|
|
8089
|
-
return fs7.readdirSync(dirPath);
|
|
8090
|
-
} catch (error) {
|
|
8091
|
-
if (isEnoent3(error) || isErrorWithCode(error, "ENOTDIR")) {
|
|
8092
|
-
return [];
|
|
8093
|
-
}
|
|
8094
|
-
throw error;
|
|
8095
|
-
}
|
|
8096
|
-
},
|
|
8097
|
-
mkdirp(dirPath) {
|
|
8098
|
-
fs7.mkdirSync(dirPath, { recursive: true });
|
|
8099
|
-
},
|
|
8100
|
-
symlink(target, linkPath) {
|
|
8101
|
-
fs7.symlinkSync(target, linkPath);
|
|
8102
|
-
},
|
|
8103
|
-
rename(from, to) {
|
|
8104
|
-
fs7.renameSync(from, to);
|
|
8105
|
-
},
|
|
8106
|
-
removeRecursive(targetPath) {
|
|
8107
|
-
fs7.rmSync(targetPath, { recursive: true, force: true });
|
|
8108
|
-
},
|
|
8109
|
-
copyRecursive(from, to) {
|
|
8110
|
-
fs7.cpSync(from, to, { recursive: true, force: true, verbatimSymlinks: true });
|
|
8111
|
-
},
|
|
8112
|
-
readFileUtf8(filePath) {
|
|
8113
|
-
try {
|
|
8114
|
-
return fs7.readFileSync(filePath, "utf8");
|
|
8115
|
-
} catch (error) {
|
|
8116
|
-
if (isEnoent3(error)) {
|
|
8117
|
-
return void 0;
|
|
8118
|
-
}
|
|
8119
|
-
throw error;
|
|
8120
|
-
}
|
|
8121
|
-
},
|
|
8122
|
-
writeFileUtf8(filePath, contents) {
|
|
8123
|
-
fs7.writeFileSync(filePath, contents, "utf8");
|
|
8124
|
-
},
|
|
8125
|
-
mkdirPrivate(dirPath) {
|
|
8126
|
-
fs7.mkdirSync(dirPath, { recursive: true, mode: OWNER_ONLY_DIR_MODE });
|
|
8127
|
-
fs7.chmodSync(dirPath, OWNER_ONLY_DIR_MODE);
|
|
8128
|
-
},
|
|
8129
|
-
writeFilePrivate(filePath, contents) {
|
|
8130
|
-
const temp = `${filePath}.${String(process.pid)}.tmp`;
|
|
8131
|
-
fs7.writeFileSync(temp, contents, { encoding: "utf8", mode: OWNER_ONLY_FILE_MODE });
|
|
8132
|
-
fs7.chmodSync(temp, OWNER_ONLY_FILE_MODE);
|
|
8133
|
-
fs7.renameSync(temp, filePath);
|
|
8134
|
-
},
|
|
8135
|
-
appendFilePrivate(filePath, contents) {
|
|
8136
|
-
const descriptor = fs7.openSync(filePath, "a", OWNER_ONLY_FILE_MODE);
|
|
8137
|
-
try {
|
|
8138
|
-
if ((fs7.fstatSync(descriptor).mode & PERMISSION_BITS2) !== OWNER_ONLY_FILE_MODE) {
|
|
8139
|
-
fs7.fchmodSync(descriptor, OWNER_ONLY_FILE_MODE);
|
|
8013
|
+
function security(args, stdin) {
|
|
8014
|
+
const result = spawnSync2("security", args, { encoding: "utf8", ...stdin === void 0 ? {} : { input: stdin }, stdio: ["pipe", "pipe", "ignore"] });
|
|
8015
|
+
return { status: result.status, stdout: result.stdout };
|
|
8016
|
+
}
|
|
8017
|
+
function createRealCredentialCache(root) {
|
|
8018
|
+
const dir = path21.join(root, "credential-cache");
|
|
8019
|
+
const filePath = (owner) => path21.join(dir, cacheFileNameFor(owner));
|
|
8020
|
+
return {
|
|
8021
|
+
read(store, owner) {
|
|
8022
|
+
if (store === "file") {
|
|
8023
|
+
try {
|
|
8024
|
+
return parseEntry(fs6.readFileSync(filePath(owner), "utf8"));
|
|
8025
|
+
} catch {
|
|
8026
|
+
return void 0;
|
|
8027
|
+
}
|
|
8140
8028
|
}
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
8029
|
+
requireKeychain();
|
|
8030
|
+
const result = security(["find-generic-password", "-s", KEYCHAIN_CACHE_SERVICE, "-a", owner, "-w"]);
|
|
8031
|
+
return result.status === 0 ? parseEntry(Buffer.from(result.stdout.trim(), "base64").toString("utf8")) : void 0;
|
|
8032
|
+
},
|
|
8033
|
+
write(store, owner, entry) {
|
|
8034
|
+
const serialised = JSON.stringify(entry);
|
|
8035
|
+
if (store === "file") {
|
|
8036
|
+
fs6.mkdirSync(dir, { recursive: true, mode: CACHE_DIR_MODE });
|
|
8037
|
+
fs6.chmodSync(dir, CACHE_DIR_MODE);
|
|
8038
|
+
const target = filePath(owner);
|
|
8039
|
+
const temp = `${target}.${String(process.pid)}.tmp`;
|
|
8040
|
+
fs6.writeFileSync(temp, serialised, { mode: CACHE_FILE_MODE });
|
|
8041
|
+
fs6.renameSync(temp, target);
|
|
8042
|
+
return;
|
|
8145
8043
|
}
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
fs7.writeFileSync(filePath, contents, { encoding: "utf8", flag: "wx" });
|
|
8153
|
-
return true;
|
|
8154
|
-
} catch (error) {
|
|
8155
|
-
if (isErrorWithCode(error, "EEXIST")) {
|
|
8156
|
-
return false;
|
|
8044
|
+
requireKeychain();
|
|
8045
|
+
const encoded = Buffer.from(serialised, "utf8").toString("base64");
|
|
8046
|
+
const result = security(["-i"], `add-generic-password -U -s ${KEYCHAIN_CACHE_SERVICE} -a ${JSON.stringify(owner)} -w ${encoded}
|
|
8047
|
+
`);
|
|
8048
|
+
if (result.status !== 0) {
|
|
8049
|
+
throw new Error(`could not write the keychain credential cache for ${owner}`);
|
|
8157
8050
|
}
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
return createHash5("sha256").update(fs7.readFileSync(filePath)).digest("hex");
|
|
8164
|
-
} catch (error) {
|
|
8165
|
-
if (isEnoent3(error) || isErrorWithCode(error, "EISDIR")) {
|
|
8166
|
-
return void 0;
|
|
8051
|
+
},
|
|
8052
|
+
remove(store, owner) {
|
|
8053
|
+
if (store === "file") {
|
|
8054
|
+
fs6.rmSync(filePath(owner), { force: true });
|
|
8055
|
+
return;
|
|
8167
8056
|
}
|
|
8168
|
-
|
|
8057
|
+
requireKeychain();
|
|
8058
|
+
security(["delete-generic-password", "-s", KEYCHAIN_CACHE_SERVICE, "-a", owner]);
|
|
8169
8059
|
}
|
|
8060
|
+
};
|
|
8061
|
+
}
|
|
8062
|
+
function realCredentialCacheEnv(paths) {
|
|
8063
|
+
return { port: createRealCredentialCache(paths.root), platform: process.platform, now: () => Date.now() };
|
|
8064
|
+
}
|
|
8065
|
+
|
|
8066
|
+
// src/providersStore.ts
|
|
8067
|
+
import fs7 from "node:fs";
|
|
8068
|
+
import path22 from "node:path";
|
|
8069
|
+
|
|
8070
|
+
// src/credential.ts
|
|
8071
|
+
var CREDENTIAL_UNAVAILABLE_EXIT = 64;
|
|
8072
|
+
var CREDENTIAL_COMMAND_TIMEOUT_MS = 1e4;
|
|
8073
|
+
var CREDENTIAL_INTERACTIVE_TIMEOUT_MS = 12e4;
|
|
8074
|
+
var GROUP_OTHER_PERMISSION_BITS = 63;
|
|
8075
|
+
var PERMISSION_BITS2 = 511;
|
|
8076
|
+
var OCTAL_RADIX2 = 8;
|
|
8077
|
+
function commandArgv(source) {
|
|
8078
|
+
if ("command" in source) {
|
|
8079
|
+
return source.command;
|
|
8170
8080
|
}
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
8081
|
+
if ("op" in source) {
|
|
8082
|
+
return ["op", "read", source.op];
|
|
8083
|
+
}
|
|
8084
|
+
const account = source.keychain.account;
|
|
8085
|
+
return ["security", "find-generic-password", "-s", source.keychain.service, ...account === void 0 ? [] : ["-a", account], "-w"];
|
|
8175
8086
|
}
|
|
8176
|
-
function
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
return
|
|
8087
|
+
function isInteractiveSource(source, env) {
|
|
8088
|
+
if ("command" in source || "keychain" in source) {
|
|
8089
|
+
return source.interactive ?? false;
|
|
8090
|
+
}
|
|
8091
|
+
if ("op" in source) {
|
|
8092
|
+
return source.interactive ?? (env.OP_SERVICE_ACCOUNT_TOKEN ?? "") === "";
|
|
8182
8093
|
}
|
|
8094
|
+
return false;
|
|
8183
8095
|
}
|
|
8184
|
-
function
|
|
8185
|
-
|
|
8186
|
-
|
|
8096
|
+
function summariseSource(source) {
|
|
8097
|
+
if ("env" in source) {
|
|
8098
|
+
return { kind: "env", variable: source.env };
|
|
8099
|
+
}
|
|
8100
|
+
if ("file" in source) {
|
|
8101
|
+
return { kind: "file", path: source.file };
|
|
8102
|
+
}
|
|
8103
|
+
if ("command" in source) {
|
|
8104
|
+
return { kind: "command", program: source.command[0] };
|
|
8105
|
+
}
|
|
8106
|
+
if ("op" in source) {
|
|
8107
|
+
return { kind: "op", reference: source.op };
|
|
8108
|
+
}
|
|
8109
|
+
if ("keychain" in source) {
|
|
8110
|
+
return {
|
|
8111
|
+
kind: "keychain",
|
|
8112
|
+
service: source.keychain.service,
|
|
8113
|
+
...source.keychain.account === void 0 ? {} : { account: source.keychain.account }
|
|
8114
|
+
};
|
|
8115
|
+
}
|
|
8116
|
+
return { kind: "literal" };
|
|
8187
8117
|
}
|
|
8188
|
-
function
|
|
8189
|
-
return
|
|
8118
|
+
function summariseCredential(credential) {
|
|
8119
|
+
return {
|
|
8120
|
+
target: credential.target ?? "bearer",
|
|
8121
|
+
sources: credential.sources.map(summariseSource),
|
|
8122
|
+
...credential.cache === void 0 ? {} : { cache: credential.cache }
|
|
8123
|
+
};
|
|
8190
8124
|
}
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
8125
|
+
function formatSourceSummary(summary) {
|
|
8126
|
+
switch (summary.kind) {
|
|
8127
|
+
case "env":
|
|
8128
|
+
return `env ${summary.variable}`;
|
|
8129
|
+
case "file":
|
|
8130
|
+
return `file ${summary.path}`;
|
|
8131
|
+
case "command":
|
|
8132
|
+
return `command ${summary.program}`;
|
|
8133
|
+
case "op":
|
|
8134
|
+
return `op ${summary.reference}`;
|
|
8135
|
+
case "keychain":
|
|
8136
|
+
return `keychain ${summary.service}${summary.account === void 0 ? "" : ` (account ${summary.account})`}`;
|
|
8137
|
+
case "literal":
|
|
8138
|
+
return "literal (non-secret placeholder)";
|
|
8139
|
+
default:
|
|
8140
|
+
return summary;
|
|
8195
8141
|
}
|
|
8196
|
-
};
|
|
8197
|
-
function expandHome2(filePath) {
|
|
8198
|
-
return filePath.startsWith("~/") ? path22.join(os4.homedir(), filePath.slice(1)) : filePath;
|
|
8199
8142
|
}
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8143
|
+
function describeSource(source) {
|
|
8144
|
+
return formatSourceSummary(summariseSource(source));
|
|
8145
|
+
}
|
|
8146
|
+
function formatCredentialSummary(summary) {
|
|
8147
|
+
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`}`;
|
|
8148
|
+
return `${summary.target} from ${summary.sources.map(formatSourceSummary).join(", then ")}${cache}`;
|
|
8149
|
+
}
|
|
8150
|
+
function describeCredential(credential) {
|
|
8151
|
+
return formatCredentialSummary(summariseCredential(credential));
|
|
8152
|
+
}
|
|
8153
|
+
function trySource(source, params) {
|
|
8154
|
+
const none = (reason, warn = false) => ({ ok: false, reason, warn });
|
|
8155
|
+
if ("env" in source) {
|
|
8156
|
+
const value = params.env[source.env];
|
|
8157
|
+
return value === void 0 || value === "" ? none("is unset or empty") : { ok: true, token: value };
|
|
8205
8158
|
}
|
|
8206
|
-
if (
|
|
8207
|
-
return
|
|
8159
|
+
if ("literal" in source) {
|
|
8160
|
+
return { ok: true, token: source.literal };
|
|
8208
8161
|
}
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
const resolved = expandHome2(filePath);
|
|
8214
|
-
let content;
|
|
8215
|
-
let stat;
|
|
8216
|
-
try {
|
|
8217
|
-
stat = fs7.statSync(resolved);
|
|
8218
|
-
content = fs7.readFileSync(resolved, "utf8");
|
|
8219
|
-
} catch (error) {
|
|
8220
|
-
if (isEnoent3(error)) {
|
|
8221
|
-
return { found: false };
|
|
8222
|
-
}
|
|
8223
|
-
throw error;
|
|
8162
|
+
if ("file" in source) {
|
|
8163
|
+
const read = params.port.readSecretFile(source.file);
|
|
8164
|
+
if (!read.found) {
|
|
8165
|
+
return none("does not exist");
|
|
8224
8166
|
}
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
encoding: "utf8",
|
|
8231
|
-
timeout: options.timeoutMs,
|
|
8232
|
-
stdio: options.interactive ? ["inherit", "pipe", "inherit"] : ["ignore", "pipe", "pipe"]
|
|
8233
|
-
});
|
|
8234
|
-
if (result.error !== void 0) {
|
|
8235
|
-
const timedOut = isErrorWithCode(result.error, "ETIMEDOUT");
|
|
8236
|
-
return { status: result.status, stdout: "", stderr: timedOut ? "" : result.error.message, timedOut };
|
|
8167
|
+
if (read.mode !== void 0 && (read.mode & GROUP_OTHER_PERMISSION_BITS) !== 0) {
|
|
8168
|
+
return none(
|
|
8169
|
+
`is readable or writable by group or others (mode ${(read.mode & PERMISSION_BITS2).toString(OCTAL_RADIX2)}); run \`chmod 600 ${source.file}\``,
|
|
8170
|
+
true
|
|
8171
|
+
);
|
|
8237
8172
|
}
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
personPresent: () => process.stdin.isTTY || hasDesktopSession()
|
|
8241
|
-
};
|
|
8242
|
-
function resolveGitBranch(run, cwd) {
|
|
8243
|
-
const result = run.run("git", ["-C", cwd, "rev-parse", "--abbrev-ref", "HEAD"]);
|
|
8244
|
-
if (result.status !== 0) {
|
|
8245
|
-
return {};
|
|
8173
|
+
const token2 = read.content.trim();
|
|
8174
|
+
return token2 === "" ? none("is empty") : { ok: true, token: token2 };
|
|
8246
8175
|
}
|
|
8247
|
-
const
|
|
8248
|
-
|
|
8249
|
-
|
|
8176
|
+
const argv = commandArgv(source);
|
|
8177
|
+
const interactive = isInteractiveSource(source, params.env);
|
|
8178
|
+
if (interactive && !params.port.personPresent()) {
|
|
8179
|
+
return none("needs a person to approve it, but there is no terminal or desktop session");
|
|
8250
8180
|
}
|
|
8251
|
-
|
|
8252
|
-
|
|
8181
|
+
const timeoutMs = source.timeoutMs ?? (interactive ? CREDENTIAL_INTERACTIVE_TIMEOUT_MS : CREDENTIAL_COMMAND_TIMEOUT_MS);
|
|
8182
|
+
const result = params.port.runCommand(argv, { timeoutMs, interactive });
|
|
8183
|
+
if (result.timedOut) {
|
|
8184
|
+
return none(`timed out after ${String(timeoutMs)}ms`);
|
|
8253
8185
|
}
|
|
8254
|
-
return { branch: name, branchDetached: false };
|
|
8255
|
-
}
|
|
8256
|
-
function resolveGitRoot(run, cwd) {
|
|
8257
|
-
const result = run.run("git", ["-C", cwd, "rev-parse", "--show-toplevel"]);
|
|
8258
8186
|
if (result.status !== 0) {
|
|
8259
|
-
|
|
8187
|
+
const stderr = result.stderr.trim();
|
|
8188
|
+
const outcome = result.status === null ? "could not be run or was killed by a signal" : `exited with status ${String(result.status)}`;
|
|
8189
|
+
return none(`${outcome}${stderr === "" ? "" : `: ${stderr}`}`);
|
|
8260
8190
|
}
|
|
8261
|
-
const
|
|
8262
|
-
return
|
|
8263
|
-
}
|
|
8264
|
-
function detachedDaemonSpawnOptions(logFd, env) {
|
|
8265
|
-
return { detached: true, stdio: ["ignore", logFd, logFd], env };
|
|
8266
|
-
}
|
|
8267
|
-
function spawnDaemonThrough(executable) {
|
|
8268
|
-
return (paths, subcommand, logPath) => spawnDetachedDaemon({ command: executable, args: [subcommand] }, paths, subcommand, logPath);
|
|
8191
|
+
const token = result.stdout.trim();
|
|
8192
|
+
return token === "" ? none("printed no token") : { ok: true, token };
|
|
8269
8193
|
}
|
|
8270
|
-
function
|
|
8271
|
-
|
|
8194
|
+
function warmCommandFor(subject) {
|
|
8195
|
+
const [kind, name] = subject.split(" ");
|
|
8196
|
+
return kind === "provider" ? `agent-shim credential warm --provider ${name ?? ""}` : `agent-shim credential warm ${name ?? ""}`;
|
|
8272
8197
|
}
|
|
8273
|
-
function
|
|
8274
|
-
|
|
8275
|
-
const
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8198
|
+
function resolveCredential(params) {
|
|
8199
|
+
const target = params.credential.target ?? "bearer";
|
|
8200
|
+
const cacheBlock = params.credential.cache;
|
|
8201
|
+
const cache = cacheBlock === void 0 ? void 0 : params.port.cache;
|
|
8202
|
+
const store = cacheBlock === void 0 || cache === void 0 ? void 0 : effectiveStore(cacheBlock, cache.platform);
|
|
8203
|
+
if (cacheBlock !== void 0 && cache !== void 0 && store !== void 0 && params.refresh !== true) {
|
|
8204
|
+
const entry = cache.port.read(store, params.subject);
|
|
8205
|
+
if (entry !== void 0 && isFresh(entry, cacheBlock, cache.now())) {
|
|
8206
|
+
return { ok: true, credential: { target, token: entry.token, source: entry.source, warnings: [], cachedAt: entry.fetchedAt } };
|
|
8281
8207
|
}
|
|
8282
|
-
return child.pid;
|
|
8283
|
-
} finally {
|
|
8284
|
-
fs7.closeSync(logFd);
|
|
8285
8208
|
}
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
currentUid: () => {
|
|
8294
|
-
if (process.getuid === void 0) {
|
|
8295
|
-
throw new Error(`no POSIX user id on ${process.platform}`);
|
|
8296
|
-
}
|
|
8297
|
-
return process.getuid();
|
|
8298
|
-
},
|
|
8299
|
-
lstat: (target) => {
|
|
8300
|
-
let stat;
|
|
8301
|
-
try {
|
|
8302
|
-
stat = fs7.lstatSync(target);
|
|
8303
|
-
} catch (error) {
|
|
8304
|
-
if (isEnoent3(error)) {
|
|
8305
|
-
return void 0;
|
|
8209
|
+
const attempts = [];
|
|
8210
|
+
const warnings = [];
|
|
8211
|
+
for (const source of params.credential.sources) {
|
|
8212
|
+
const outcome = trySource(source, params);
|
|
8213
|
+
if (outcome.ok) {
|
|
8214
|
+
if (cache !== void 0 && store !== void 0) {
|
|
8215
|
+
cache.port.write(store, params.subject, { token: outcome.token, fetchedAt: cache.now(), source });
|
|
8306
8216
|
}
|
|
8307
|
-
|
|
8217
|
+
return { ok: true, credential: { target, token: outcome.token, source, warnings } };
|
|
8308
8218
|
}
|
|
8309
|
-
const
|
|
8310
|
-
|
|
8219
|
+
const line = `${describeSource(source)} ${outcome.reason}`;
|
|
8220
|
+
attempts.push(line);
|
|
8221
|
+
if (outcome.warn) {
|
|
8222
|
+
warnings.push(`agent-shim: ${params.subject}: skipped ${line}`);
|
|
8223
|
+
}
|
|
8224
|
+
}
|
|
8225
|
+
const hint = cacheBlock === void 0 ? "" : `; fill the cache from a terminal with \`${warmCommandFor(params.subject)}\``;
|
|
8226
|
+
return { ok: false, message: `agent-shim: ${params.subject} has no usable credential: ${attempts.join("; ")}${hint}` };
|
|
8227
|
+
}
|
|
8228
|
+
function credentialVariables(target, token) {
|
|
8229
|
+
return {
|
|
8230
|
+
ANTHROPIC_AUTH_TOKEN: target === "bearer" ? token : void 0,
|
|
8231
|
+
ANTHROPIC_API_KEY: target === "apiKey" ? token : void 0,
|
|
8232
|
+
CLAUDE_CODE_OAUTH_TOKEN: target === "oauthToken" ? token : void 0
|
|
8233
|
+
};
|
|
8234
|
+
}
|
|
8235
|
+
|
|
8236
|
+
// src/providersStore.ts
|
|
8237
|
+
var ProviderNotFoundError = class extends CliError {
|
|
8238
|
+
constructor(name) {
|
|
8239
|
+
super(`No provider named "${name}". Run \`agent-shim provider add ${name}\` first.`);
|
|
8240
|
+
this.name = name;
|
|
8241
|
+
this.name = "ProviderNotFoundError";
|
|
8311
8242
|
}
|
|
8243
|
+
name;
|
|
8312
8244
|
};
|
|
8313
|
-
|
|
8245
|
+
var ProviderAlreadyExistsError = class extends CliError {
|
|
8246
|
+
constructor(providerName) {
|
|
8247
|
+
super(`A provider named "${providerName}" already exists.`);
|
|
8248
|
+
this.providerName = providerName;
|
|
8249
|
+
this.name = "ProviderAlreadyExistsError";
|
|
8250
|
+
}
|
|
8251
|
+
providerName;
|
|
8252
|
+
};
|
|
8253
|
+
var InvalidProviderNameError = class extends CliError {
|
|
8254
|
+
constructor(attemptedName) {
|
|
8255
|
+
super(
|
|
8256
|
+
`"${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.`
|
|
8257
|
+
);
|
|
8258
|
+
this.attemptedName = attemptedName;
|
|
8259
|
+
this.name = "InvalidProviderNameError";
|
|
8260
|
+
}
|
|
8261
|
+
attemptedName;
|
|
8262
|
+
};
|
|
8263
|
+
var PROVIDER_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._@-]*$/;
|
|
8264
|
+
function providerJsonPath(paths, name) {
|
|
8265
|
+
return path22.join(paths.providersDir, `${name}.json`);
|
|
8266
|
+
}
|
|
8267
|
+
function providerExists(paths, name) {
|
|
8268
|
+
return fs7.existsSync(providerJsonPath(paths, name));
|
|
8269
|
+
}
|
|
8270
|
+
var LEGACY_PROVIDER_FIELDS = ["tokenEnv", "tokenCommand", "authScheme"];
|
|
8271
|
+
var LEGACY_ENV_CREDENTIAL_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
|
|
8272
|
+
var LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
|
|
8273
|
+
function isRecord4(value) {
|
|
8274
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8275
|
+
}
|
|
8276
|
+
function legacyProviderConversion(raw) {
|
|
8277
|
+
if (!isRecord4(raw) || "credential" in raw) {
|
|
8278
|
+
return void 0;
|
|
8279
|
+
}
|
|
8280
|
+
const env = isRecord4(raw.env) ? raw.env : void 0;
|
|
8281
|
+
const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
|
|
8282
|
+
const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
|
|
8283
|
+
if (fields.length === 0) {
|
|
8284
|
+
return void 0;
|
|
8285
|
+
}
|
|
8286
|
+
const sources = [];
|
|
8287
|
+
if (typeof raw.tokenEnv === "string") {
|
|
8288
|
+
sources.push({ env: raw.tokenEnv });
|
|
8289
|
+
}
|
|
8290
|
+
if (Array.isArray(raw.tokenCommand)) {
|
|
8291
|
+
sources.push({ command: raw.tokenCommand });
|
|
8292
|
+
}
|
|
8293
|
+
if (typeof env?.ANTHROPIC_AUTH_TOKEN === "string" && env.ANTHROPIC_AUTH_TOKEN !== "") {
|
|
8294
|
+
sources.push({ literal: LEGACY_LITERAL_PLACEHOLDER });
|
|
8295
|
+
}
|
|
8296
|
+
const remainingEnv = env === void 0 ? void 0 : Object.fromEntries(Object.entries(env).filter(([key]) => !LEGACY_ENV_CREDENTIAL_KEYS.includes(key)));
|
|
8297
|
+
const replacedKeys = [...LEGACY_PROVIDER_FIELDS, "env"];
|
|
8298
|
+
const rest = Object.fromEntries(Object.entries(raw).filter(([key]) => !replacedKeys.includes(key)));
|
|
8314
8299
|
return {
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
ports: {
|
|
8321
|
-
fs: realFarmFs,
|
|
8322
|
-
// 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.
|
|
8323
|
-
isRunning: realIsProcessRunning,
|
|
8324
|
-
now: () => Date.now(),
|
|
8325
|
-
sleep: realSleepSync,
|
|
8326
|
-
spawnSupervisor: (layout) => spawnHeadroomSupervisor(layout, options.spawnDaemon)
|
|
8327
|
-
}
|
|
8328
|
-
});
|
|
8329
|
-
return {
|
|
8330
|
-
socketPath: up.socketPath,
|
|
8331
|
-
projectId: resolveGitRoot(realRunPort, options.cwd) ?? options.cwd
|
|
8332
|
-
};
|
|
8333
|
-
},
|
|
8334
|
-
release: () => {
|
|
8335
|
-
removeSession(realFarmFs, paths.headroomSessionsDir, process.pid);
|
|
8300
|
+
fields,
|
|
8301
|
+
replacement: {
|
|
8302
|
+
...rest,
|
|
8303
|
+
credential: { sources, ...raw.authScheme === "apiKey" ? { target: "apiKey" } : {} },
|
|
8304
|
+
...remainingEnv === void 0 || Object.keys(remainingEnv).length === 0 ? {} : { env: remainingEnv }
|
|
8336
8305
|
}
|
|
8337
8306
|
};
|
|
8338
8307
|
}
|
|
8339
|
-
var
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
8308
|
+
var LegacyProviderFileError = class extends CliError {
|
|
8309
|
+
constructor(filePath, conversion) {
|
|
8310
|
+
super(
|
|
8311
|
+
`${filePath} uses ${conversion.fields.join(", ")}, which a credential block replaced. Rewrite it as:
|
|
8312
|
+
` + JSON.stringify(conversion.replacement, null, 2)
|
|
8313
|
+
);
|
|
8314
|
+
this.filePath = filePath;
|
|
8315
|
+
this.conversion = conversion;
|
|
8316
|
+
this.name = "LegacyProviderFileError";
|
|
8317
|
+
}
|
|
8318
|
+
filePath;
|
|
8319
|
+
conversion;
|
|
8343
8320
|
};
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
entries = fs7.readdirSync(dir, { withFileTypes: true });
|
|
8349
|
-
} catch (error) {
|
|
8350
|
-
if (isEnoent3(error)) {
|
|
8351
|
-
return [];
|
|
8352
|
-
}
|
|
8353
|
-
throw error;
|
|
8321
|
+
function parseProviderFile(filePath, raw) {
|
|
8322
|
+
const conversion = legacyProviderConversion(raw);
|
|
8323
|
+
if (conversion !== void 0) {
|
|
8324
|
+
throw new LegacyProviderFileError(filePath, conversion);
|
|
8354
8325
|
}
|
|
8355
|
-
|
|
8356
|
-
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
|
|
8360
|
-
isFile: true,
|
|
8361
|
-
isExecutable: (stat.mode & EXECUTE_BITS_MASK) !== 0,
|
|
8362
|
-
sizeBytes: stat.size
|
|
8363
|
-
};
|
|
8364
|
-
});
|
|
8326
|
+
const parsed = ProviderSchema.safeParse(raw);
|
|
8327
|
+
if (!parsed.success) {
|
|
8328
|
+
throw new ConfigValidationError(filePath, parsed.error.issues);
|
|
8329
|
+
}
|
|
8330
|
+
return parsed.data;
|
|
8365
8331
|
}
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8332
|
+
function readProvider(paths, name) {
|
|
8333
|
+
const filePath = providerJsonPath(paths, name);
|
|
8334
|
+
if (!fs7.existsSync(filePath)) {
|
|
8335
|
+
return void 0;
|
|
8336
|
+
}
|
|
8337
|
+
const raw = JSON.parse(fs7.readFileSync(filePath, "utf8"));
|
|
8338
|
+
return parseProviderFile(filePath, raw);
|
|
8369
8339
|
}
|
|
8370
|
-
function
|
|
8371
|
-
const
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
|
|
8376
|
-
|
|
8377
|
-
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
|
|
8340
|
+
function loadProvider(providersDir, name, port) {
|
|
8341
|
+
const filePath = path22.join(providersDir, `${name}.json`);
|
|
8342
|
+
const raw = port.readConfigFile(filePath);
|
|
8343
|
+
return raw === void 0 ? void 0 : parseProviderFile(filePath, raw);
|
|
8344
|
+
}
|
|
8345
|
+
function listProviders(paths) {
|
|
8346
|
+
const names = listProviderNames(
|
|
8347
|
+
(dir) => fs7.readdirSync(dir),
|
|
8348
|
+
paths.providersDir
|
|
8349
|
+
);
|
|
8350
|
+
const result = [];
|
|
8351
|
+
for (const name of names) {
|
|
8352
|
+
let provider;
|
|
8353
|
+
try {
|
|
8354
|
+
provider = readProvider(paths, name);
|
|
8355
|
+
} catch (error) {
|
|
8356
|
+
if (!(error instanceof ConfigValidationError || error instanceof LegacyProviderFileError)) {
|
|
8357
|
+
throw error;
|
|
8382
8358
|
}
|
|
8359
|
+
console.error(`agent-shim: providers/${name}.json is invalid and was skipped: ${error.message}`);
|
|
8360
|
+
continue;
|
|
8361
|
+
}
|
|
8362
|
+
if (provider !== void 0) {
|
|
8363
|
+
result.push({ name, provider });
|
|
8383
8364
|
}
|
|
8384
|
-
return void 0;
|
|
8385
8365
|
}
|
|
8386
|
-
|
|
8387
|
-
return mode !== void 0 && (mode & EXECUTE_BITS_MASK) !== 0 ? candidate : void 0;
|
|
8366
|
+
return result;
|
|
8388
8367
|
}
|
|
8389
|
-
function
|
|
8368
|
+
function listProviderNames(readdir, providersDir) {
|
|
8369
|
+
let entries;
|
|
8390
8370
|
try {
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
|
|
8394
|
-
if (isEnoent3(error)) {
|
|
8395
|
-
return void 0;
|
|
8396
|
-
}
|
|
8397
|
-
throw error;
|
|
8371
|
+
entries = readdir(providersDir);
|
|
8372
|
+
} catch {
|
|
8373
|
+
return [];
|
|
8398
8374
|
}
|
|
8375
|
+
return entries.filter((entry) => entry.endsWith(".json")).map((entry) => entry.slice(0, -".json".length)).sort();
|
|
8399
8376
|
}
|
|
8400
|
-
function
|
|
8401
|
-
return
|
|
8402
|
-
platform: process.platform,
|
|
8403
|
-
pathext: process.env.PATHEXT,
|
|
8404
|
-
statFileMode: realStatFileMode
|
|
8405
|
-
});
|
|
8377
|
+
function describeProviderEndpoint(provider) {
|
|
8378
|
+
return isCodexProvider(provider) ? "codex translation daemon" : provider.baseUrl;
|
|
8406
8379
|
}
|
|
8407
|
-
|
|
8408
|
-
|
|
8409
|
-
|
|
8410
|
-
|
|
8411
|
-
return found;
|
|
8412
|
-
}
|
|
8380
|
+
var ProviderKindMismatchError = class extends UsageError {
|
|
8381
|
+
constructor(message) {
|
|
8382
|
+
super(message);
|
|
8383
|
+
this.name = "ProviderKindMismatchError";
|
|
8413
8384
|
}
|
|
8414
|
-
|
|
8385
|
+
};
|
|
8386
|
+
function isEmptyCodexConfig(config) {
|
|
8387
|
+
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);
|
|
8415
8388
|
}
|
|
8416
|
-
function
|
|
8417
|
-
if (
|
|
8418
|
-
|
|
8389
|
+
function addProvider(paths, name, input) {
|
|
8390
|
+
if (providerExists(paths, name)) {
|
|
8391
|
+
throw new ProviderAlreadyExistsError(name);
|
|
8419
8392
|
}
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
const candidateDirOnPath = env.pathDirs.some((dir) => path22.resolve(dir) === candidateDir);
|
|
8423
|
-
if (candidateDirOnPath) {
|
|
8424
|
-
return candidate;
|
|
8393
|
+
if (!PROVIDER_NAME_RE.test(name)) {
|
|
8394
|
+
throw new InvalidProviderNameError(name);
|
|
8425
8395
|
}
|
|
8426
|
-
const
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
|
|
8435
|
-
|
|
8436
|
-
|
|
8396
|
+
const kind = input.kind ?? "http";
|
|
8397
|
+
if (kind === "codex" && input.baseUrl !== void 0) {
|
|
8398
|
+
throw new ProviderKindMismatchError("A codex provider has no base URL: the launcher points its sessions at the front-door daemon. Drop --base-url.");
|
|
8399
|
+
}
|
|
8400
|
+
if (kind === "http" && input.baseUrl === void 0) {
|
|
8401
|
+
throw new ProviderKindMismatchError("An http provider needs --base-url.");
|
|
8402
|
+
}
|
|
8403
|
+
if (kind === "http" && !isEmptyCodexConfig(input.codex)) {
|
|
8404
|
+
throw new ProviderKindMismatchError("Codex settings apply only to a codex provider (--kind codex).");
|
|
8405
|
+
}
|
|
8406
|
+
const shared = {
|
|
8407
|
+
displayName: input.displayName,
|
|
8408
|
+
credential: { sources: input.sources, ...input.target === void 0 ? {} : { target: input.target } },
|
|
8409
|
+
...input.env === void 0 || Object.keys(input.env).length === 0 ? {} : { env: input.env }
|
|
8410
|
+
};
|
|
8411
|
+
const parsed = ProviderSchema.safeParse(
|
|
8412
|
+
kind === "codex" ? { kind, ...shared, ...isEmptyCodexConfig(input.codex) ? {} : { codex: input.codex } } : { ...shared, baseUrl: input.baseUrl }
|
|
8413
|
+
);
|
|
8414
|
+
if (!parsed.success) {
|
|
8415
|
+
throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
|
|
8416
|
+
}
|
|
8417
|
+
writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
|
|
8418
|
+
return parsed.data;
|
|
8437
8419
|
}
|
|
8438
|
-
function
|
|
8439
|
-
|
|
8420
|
+
function updateProvider(paths, name, input) {
|
|
8421
|
+
const existing = readProvider(paths, name);
|
|
8422
|
+
if (existing === void 0) {
|
|
8423
|
+
throw new ProviderNotFoundError(name);
|
|
8424
|
+
}
|
|
8425
|
+
const unset = new Set(input.unsetEnv);
|
|
8426
|
+
const env = Object.fromEntries(Object.entries({ ...existing.env, ...input.env }).filter(([key]) => !unset.has(key)));
|
|
8427
|
+
const target = input.target ?? existing.credential.target;
|
|
8428
|
+
const cache = input.cache === void 0 ? existing.credential.cache : input.cache === false ? void 0 : input.cache;
|
|
8429
|
+
if (isCodexProvider(existing) && input.baseUrl !== void 0) {
|
|
8430
|
+
throw new ProviderKindMismatchError(`Provider "${name}" is a codex provider, which has no base URL.`);
|
|
8431
|
+
}
|
|
8432
|
+
if (!isCodexProvider(existing) && !isEmptyCodexConfig(input.codex)) {
|
|
8433
|
+
throw new ProviderKindMismatchError(`Provider "${name}" is an http provider; codex settings apply only to a codex provider.`);
|
|
8434
|
+
}
|
|
8435
|
+
const shared = {
|
|
8436
|
+
...input.displayName === void 0 ? {} : { displayName: input.displayName },
|
|
8437
|
+
credential: {
|
|
8438
|
+
sources: input.sources ?? existing.credential.sources,
|
|
8439
|
+
...target === void 0 ? {} : { target },
|
|
8440
|
+
...cache === void 0 ? {} : { cache }
|
|
8441
|
+
},
|
|
8442
|
+
env: Object.keys(env).length === 0 ? void 0 : env
|
|
8443
|
+
};
|
|
8444
|
+
let candidate;
|
|
8445
|
+
if (isCodexProvider(existing)) {
|
|
8446
|
+
const codex = {
|
|
8447
|
+
...existing.codex,
|
|
8448
|
+
...input.codex,
|
|
8449
|
+
...existing.codex?.models === void 0 && input.codex?.models === void 0 ? {} : { models: { ...existing.codex?.models, ...input.codex?.models } }
|
|
8450
|
+
};
|
|
8451
|
+
candidate = { ...existing, ...shared, codex: isEmptyCodexConfig(codex) ? void 0 : codex };
|
|
8452
|
+
} else {
|
|
8453
|
+
candidate = { ...existing, ...shared, ...input.baseUrl === void 0 ? {} : { baseUrl: input.baseUrl } };
|
|
8454
|
+
}
|
|
8455
|
+
const parsed = ProviderSchema.safeParse(candidate);
|
|
8456
|
+
if (!parsed.success) {
|
|
8457
|
+
throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
|
|
8458
|
+
}
|
|
8459
|
+
writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
|
|
8460
|
+
return parsed.data;
|
|
8440
8461
|
}
|
|
8441
|
-
function
|
|
8442
|
-
|
|
8462
|
+
function removeProvider(paths, name) {
|
|
8463
|
+
if (!providerExists(paths, name)) {
|
|
8464
|
+
throw new ProviderNotFoundError(name);
|
|
8465
|
+
}
|
|
8466
|
+
fs7.rmSync(providerJsonPath(paths, name));
|
|
8443
8467
|
}
|
|
8444
|
-
function
|
|
8445
|
-
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
|
|
8468
|
+
function cascadeProviderName(cascade) {
|
|
8469
|
+
let name;
|
|
8470
|
+
for (const layer of assembleCascade(cascade).layers) {
|
|
8471
|
+
if (layer.launch?.provider !== void 0) {
|
|
8472
|
+
name = layer.launch.provider;
|
|
8473
|
+
}
|
|
8474
|
+
}
|
|
8475
|
+
return name;
|
|
8452
8476
|
}
|
|
8453
|
-
function
|
|
8454
|
-
|
|
8477
|
+
function resolveProvider(params) {
|
|
8478
|
+
const cliName = params.cliProvider !== void 0 && params.cliProvider !== "" ? params.cliProvider : void 0;
|
|
8479
|
+
const name = cliName ?? (params.cascade === void 0 ? void 0 : cascadeProviderName(params.cascade));
|
|
8480
|
+
if (name === void 0) {
|
|
8481
|
+
return void 0;
|
|
8482
|
+
}
|
|
8483
|
+
const definition = loadProvider(params.paths.providersDir, name, params.port);
|
|
8484
|
+
if (definition === void 0) {
|
|
8485
|
+
const known = listProviderNames(params.port.readdir, params.paths.providersDir);
|
|
8486
|
+
return {
|
|
8487
|
+
ok: false,
|
|
8488
|
+
status: 1,
|
|
8489
|
+
message: `agent-shim: no provider named "${name}". ` + (known.length > 0 ? `Known providers: ${known.join(", ")}.` : "No providers are defined yet; run `agent-shim provider add`.")
|
|
8490
|
+
};
|
|
8491
|
+
}
|
|
8492
|
+
if (params.credentials === void 0) {
|
|
8493
|
+
return { ok: false, status: 1, message: `agent-shim: provider ${name} needs its credential resolved, but this launcher has no credential port wired` };
|
|
8494
|
+
}
|
|
8495
|
+
const resolution = resolveCredential({ credential: definition.credential, env: params.env, port: params.credentials, subject: `provider ${name}` });
|
|
8496
|
+
if (!resolution.ok) {
|
|
8497
|
+
return { ok: false, status: CREDENTIAL_UNAVAILABLE_EXIT, message: resolution.message };
|
|
8498
|
+
}
|
|
8499
|
+
return { ok: true, provider: { name, definition, credential: resolution.credential }, warnings: resolution.credential.warnings };
|
|
8455
8500
|
}
|
|
8456
8501
|
|
|
8457
8502
|
// src/checkReport.ts
|
|
@@ -8890,42 +8935,14 @@ function loadCheckProvider(paths, name) {
|
|
|
8890
8935
|
}
|
|
8891
8936
|
function collectCheckReport(params) {
|
|
8892
8937
|
const { paths, cwd } = params;
|
|
8893
|
-
const home =
|
|
8938
|
+
const home = os6.homedir();
|
|
8894
8939
|
const claudeHome = resolveClaudeHome();
|
|
8895
8940
|
const read = cosmiconfigReader();
|
|
8896
8941
|
const classification = loadClassification(paths);
|
|
8897
8942
|
const loaded = loadCascadeInput({ paths, home, cwd, read });
|
|
8898
|
-
const selections = readDirectorySelections(loaded);
|
|
8899
8943
|
const git = resolveGitBranch(realRunPort, cwd);
|
|
8900
|
-
const
|
|
8901
|
-
|
|
8902
|
-
argv0Identity: params.identity,
|
|
8903
|
-
directoryPinnedIdentity: selections.identity,
|
|
8904
|
-
readActiveIdentityFile: () => {
|
|
8905
|
-
const raw = realFsPort.readFileUtf8(paths.activeIdentityFile);
|
|
8906
|
-
if (raw === void 0) {
|
|
8907
|
-
return void 0;
|
|
8908
|
-
}
|
|
8909
|
-
const trimmed = raw.trim();
|
|
8910
|
-
return trimmed === "" ? void 0 : trimmed;
|
|
8911
|
-
}
|
|
8912
|
-
});
|
|
8913
|
-
let poolPick;
|
|
8914
|
-
if (decidedIdentity.pool !== void 0) {
|
|
8915
|
-
const pools = loaded.globalConfig?.pools ?? {};
|
|
8916
|
-
if (pools[decidedIdentity.pool] === void 0) {
|
|
8917
|
-
throw new PoolNotFoundError(decidedIdentity.pool);
|
|
8918
|
-
}
|
|
8919
|
-
poolPick = collectPoolPick({ paths, fs: realFsPort, usageFs: realFarmFs, poolName: decidedIdentity.pool, pools, directory: cwd, nowMs: Date.now() });
|
|
8920
|
-
}
|
|
8921
|
-
const identityDecision = poolPick?.pick === void 0 ? decidedIdentity : { ...decidedIdentity, name: poolPick.pick };
|
|
8922
|
-
const loadedIdentity = identityDecision.name === void 0 ? void 0 : loadIdentity(paths.identitiesDir, identityDecision.name, realFsPort);
|
|
8923
|
-
const configProfileDecision = decideConfigProfile({
|
|
8924
|
-
env: params.env,
|
|
8925
|
-
directoryRuleConfigProfile: selections.configProfile,
|
|
8926
|
-
identityDefaultConfigProfile: loadedIdentity?.config.defaultConfigProfile,
|
|
8927
|
-
globalDefaultConfigProfile: loaded.globalConfig?.defaultConfigProfile
|
|
8928
|
-
});
|
|
8944
|
+
const { selection, identityConfig } = selectLaunch({ paths, cwd, env: params.env, ...params.identity === void 0 ? {} : { identity: params.identity } }, loaded);
|
|
8945
|
+
const { identity: identityDecision, poolPick, configProfile: configProfileDecision } = selection;
|
|
8929
8946
|
const cascade = loadCascadeInput({
|
|
8930
8947
|
paths,
|
|
8931
8948
|
home,
|
|
@@ -8956,7 +8973,7 @@ function collectCheckReport(params) {
|
|
|
8956
8973
|
...poolPick === void 0 ? {} : { poolPick },
|
|
8957
8974
|
...configProfileDecision.name === void 0 ? {} : { configProfileName: configProfileDecision.name },
|
|
8958
8975
|
configProfileSource: configProfileDecision.source,
|
|
8959
|
-
...
|
|
8976
|
+
...identityConfig === void 0 ? {} : { identity: identityConfig },
|
|
8960
8977
|
settingsFiles,
|
|
8961
8978
|
run: realRunPort,
|
|
8962
8979
|
credentialCache: realCredentialCacheEnv(paths),
|
|
@@ -12830,14 +12847,14 @@ function resolvePoolLaunch(params) {
|
|
|
12830
12847
|
}
|
|
12831
12848
|
|
|
12832
12849
|
// src/launcher/spawn.ts
|
|
12833
|
-
import
|
|
12850
|
+
import os7 from "node:os";
|
|
12834
12851
|
var SIGNAL_EXIT_CODE_OFFSET = 128;
|
|
12835
12852
|
function exitCodeFor(result) {
|
|
12836
12853
|
if (result.status !== null) {
|
|
12837
12854
|
return result.status;
|
|
12838
12855
|
}
|
|
12839
12856
|
if (result.signal !== null) {
|
|
12840
|
-
return SIGNAL_EXIT_CODE_OFFSET +
|
|
12857
|
+
return SIGNAL_EXIT_CODE_OFFSET + os7.constants.signals[result.signal];
|
|
12841
12858
|
}
|
|
12842
12859
|
return 1;
|
|
12843
12860
|
}
|
|
@@ -13238,7 +13255,7 @@ function runLauncher(params) {
|
|
|
13238
13255
|
}
|
|
13239
13256
|
|
|
13240
13257
|
// src/launchWiring.ts
|
|
13241
|
-
import
|
|
13258
|
+
import os8 from "node:os";
|
|
13242
13259
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
13243
13260
|
|
|
13244
13261
|
// src/ownCli.ts
|
|
@@ -13403,7 +13420,7 @@ function realFrontDoorPort(paths, spawnDaemon) {
|
|
|
13403
13420
|
|
|
13404
13421
|
// src/launchWiring.ts
|
|
13405
13422
|
function buildFarmRuntime(paths, cwd) {
|
|
13406
|
-
const home =
|
|
13423
|
+
const home = os8.homedir();
|
|
13407
13424
|
const read = cosmiconfigReader();
|
|
13408
13425
|
const classification = loadClassification(paths);
|
|
13409
13426
|
const loaded = loadCascadeInput({ paths, home, cwd, read });
|
|
@@ -13739,6 +13756,7 @@ export {
|
|
|
13739
13756
|
resolveAgentShimHome,
|
|
13740
13757
|
resolveClaudeHome,
|
|
13741
13758
|
resolveDecisions,
|
|
13759
|
+
resolveLaunchSelection,
|
|
13742
13760
|
resolveLayoutPaths,
|
|
13743
13761
|
resolveSupervisorConfig,
|
|
13744
13762
|
resyncFarm,
|