agent-shim 8.7.0 → 8.8.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 +30 -21
- package/dist/index.cjs +1814 -1758
- package/dist/index.mjs +1805 -1758
- package/dist/types/index.d.ts +238 -60
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -3323,25 +3323,19 @@ function realConnectEffects() {
|
|
|
3323
3323
|
}
|
|
3324
3324
|
};
|
|
3325
3325
|
},
|
|
3326
|
-
connectTlsUpstream: async (host, port, clientAlpn) =>
|
|
3327
|
-
const
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
}
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
candidate.once("error", (error) => {
|
|
3336
|
-
if ("code" in error && error.code === "EADDRINUSE") {
|
|
3337
|
-
settle(dial(upstreamLocalPort()));
|
|
3338
|
-
return;
|
|
3339
|
-
}
|
|
3340
|
-
reject(error);
|
|
3326
|
+
connectTlsUpstream: async (host, port, clientAlpn) => {
|
|
3327
|
+
const dialled = await reservedDial({ host, port });
|
|
3328
|
+
if (dialled.error !== null) {
|
|
3329
|
+
throw dialled.error;
|
|
3330
|
+
}
|
|
3331
|
+
return await new Promise((resolve, reject) => {
|
|
3332
|
+
const secure = tls.connect({ socket: dialled.socket, servername: host, ...clientAlpn === void 0 ? {} : { ALPNProtocols: [clientAlpn] } });
|
|
3333
|
+
secure.once("secureConnect", () => {
|
|
3334
|
+
resolve(secure);
|
|
3341
3335
|
});
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
}
|
|
3336
|
+
secure.once("error", reject);
|
|
3337
|
+
});
|
|
3338
|
+
},
|
|
3345
3339
|
connectTcp: async (host, port) => await new Promise((resolve, reject) => {
|
|
3346
3340
|
const socket = net2.connect({ host, port });
|
|
3347
3341
|
socket.once("connect", () => {
|
|
@@ -4903,547 +4897,380 @@ function listUsageSnapshots(fs12, snapshotsDir) {
|
|
|
4903
4897
|
}).sort((left, right) => left.identity.localeCompare(right.identity));
|
|
4904
4898
|
}
|
|
4905
4899
|
|
|
4906
|
-
// src/
|
|
4907
|
-
var
|
|
4908
|
-
var
|
|
4909
|
-
var
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
const { paths, ports } = params;
|
|
4917
|
-
const deadline = ports.now() + HEADROOM_START_TIMEOUT_MS;
|
|
4918
|
-
let spawnedSupervisor = false;
|
|
4919
|
-
let awaitingAllowlist = false;
|
|
4920
|
-
for (; ; ) {
|
|
4921
|
-
const state = readHeadroomState(ports.fs, paths.headroomStateFile);
|
|
4922
|
-
if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
|
|
4923
|
-
const daemonUp = state.socketPath !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
|
|
4924
|
-
if (daemonUp && state.socketPath !== void 0) {
|
|
4925
|
-
const required = params.requiredAllowlistHash?.();
|
|
4926
|
-
if (required === void 0 || state.allowlistHash === required) {
|
|
4927
|
-
writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
|
|
4928
|
-
return { socketPath: state.socketPath };
|
|
4929
|
-
}
|
|
4930
|
-
const holders = sessionsForSupervisor(ports.fs, paths.headroomSessionsDir, state.supervisorPid).filter((session) => ports.isRunning(session.pid));
|
|
4931
|
-
if (holders.length > 0) {
|
|
4932
|
-
throw new HeadroomStartError(
|
|
4933
|
-
`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.`
|
|
4934
|
-
);
|
|
4935
|
-
}
|
|
4936
|
-
awaitingAllowlist = true;
|
|
4937
|
-
}
|
|
4938
|
-
if (state.lastError !== void 0) {
|
|
4939
|
-
throw new HeadroomStartError(
|
|
4940
|
-
`the headroom daemon reported a fatal error and is not serving: ${state.lastError} (daemon log: ${paths.headroomLogPath})`
|
|
4941
|
-
);
|
|
4942
|
-
}
|
|
4943
|
-
} else if (!spawnedSupervisor) {
|
|
4944
|
-
ports.fs.mkdirp(paths.headroomDir);
|
|
4945
|
-
const created = ports.fs.writeFileExclusive(
|
|
4946
|
-
paths.headroomLockFile,
|
|
4947
|
-
`${JSON.stringify({ pid: params.launcherPid, at: ports.now() })}
|
|
4948
|
-
`
|
|
4949
|
-
);
|
|
4950
|
-
if (created) {
|
|
4951
|
-
ports.spawnSupervisor(paths);
|
|
4952
|
-
spawnedSupervisor = true;
|
|
4953
|
-
} else {
|
|
4954
|
-
const lock = readStartLock(ports.fs, paths.headroomLockFile);
|
|
4955
|
-
if (lock === void 0 || !ports.isRunning(lock.pid)) {
|
|
4956
|
-
ports.fs.removeRecursive(paths.headroomLockFile);
|
|
4957
|
-
continue;
|
|
4958
|
-
}
|
|
4959
|
-
}
|
|
4960
|
-
}
|
|
4961
|
-
if (ports.now() >= deadline) {
|
|
4962
|
-
const lastError = state?.lastError;
|
|
4963
|
-
throw new HeadroomStartError(
|
|
4964
|
-
`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}`
|
|
4965
|
-
);
|
|
4966
|
-
}
|
|
4967
|
-
ports.sleep(HEADROOM_LAUNCHER_POLL_MS);
|
|
4900
|
+
// src/usage/preflight.ts
|
|
4901
|
+
var MS_PER_SECOND2 = 1e3;
|
|
4902
|
+
var SECONDS_PER_MINUTE = 60;
|
|
4903
|
+
var MINUTES_PER_HOUR = 60;
|
|
4904
|
+
var HOURS_PER_DAY = 24;
|
|
4905
|
+
var WARNING_STATUSES = /* @__PURE__ */ new Set(["allowed_warning", "rejected"]);
|
|
4906
|
+
function formatAge(ms) {
|
|
4907
|
+
const minutes = Math.floor(ms / (MS_PER_SECOND2 * SECONDS_PER_MINUTE));
|
|
4908
|
+
if (minutes < MINUTES_PER_HOUR) {
|
|
4909
|
+
return `${String(Math.max(minutes, 0))}m`;
|
|
4968
4910
|
}
|
|
4911
|
+
const hours = Math.floor(minutes / MINUTES_PER_HOUR);
|
|
4912
|
+
return hours < HOURS_PER_DAY ? `${String(hours)}h` : `${String(Math.floor(hours / HOURS_PER_DAY))}d`;
|
|
4969
4913
|
}
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4914
|
+
function effectiveWindow(window, nowMs) {
|
|
4915
|
+
const resetsAtMs = window.resetsAt === void 0 ? void 0 : Date.parse(window.resetsAt);
|
|
4916
|
+
if (resetsAtMs !== void 0 && resetsAtMs <= nowMs) {
|
|
4917
|
+
return { reset: true, utilization: 0 };
|
|
4918
|
+
}
|
|
4974
4919
|
return {
|
|
4975
|
-
|
|
4976
|
-
...
|
|
4977
|
-
...
|
|
4978
|
-
...
|
|
4979
|
-
...config.interceptToolResults === void 0 ? {} : { interceptToolResults: config.interceptToolResults },
|
|
4980
|
-
...config.readMaturation === void 0 ? {} : { readMaturation: config.readMaturation }
|
|
4920
|
+
reset: false,
|
|
4921
|
+
...window.utilization === void 0 ? {} : { utilization: window.utilization },
|
|
4922
|
+
...resetsAtMs === void 0 ? {} : { resetsAtMs },
|
|
4923
|
+
...window.status === void 0 ? {} : { status: window.status }
|
|
4981
4924
|
};
|
|
4982
4925
|
}
|
|
4983
|
-
function
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4926
|
+
function quotaWarnings(snapshot, provider, nowMs) {
|
|
4927
|
+
const rateLimit = snapshot?.providers[provider]?.rateLimit;
|
|
4928
|
+
const unified = rateLimit?.unified;
|
|
4929
|
+
if (snapshot === void 0 || rateLimit === void 0 || unified === void 0) {
|
|
4930
|
+
return [];
|
|
4931
|
+
}
|
|
4932
|
+
const age = formatAge(nowMs - Date.parse(rateLimit.observedAt));
|
|
4933
|
+
const windows = [
|
|
4934
|
+
{ name: "five-hour", window: unified.fiveHour },
|
|
4935
|
+
{ name: "seven-day", window: unified.sevenDay }
|
|
4991
4936
|
];
|
|
4992
|
-
}
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4937
|
+
return windows.flatMap(({ name, window }) => {
|
|
4938
|
+
const effective = window === void 0 ? void 0 : effectiveWindow(window, nowMs);
|
|
4939
|
+
if (window === void 0 || effective?.status === void 0 || !WARNING_STATUSES.has(effective.status)) {
|
|
4940
|
+
return [];
|
|
4941
|
+
}
|
|
4942
|
+
const reset = window.resetsAt === void 0 ? "" : `, resets ${window.resetsAt}`;
|
|
4943
|
+
const verdict = effective.status === "rejected" ? "exhausted" : "nearly used";
|
|
4944
|
+
return [`agent-shim: identity ${snapshot.identity}: the ${name} quota is ${verdict}${reset} (last seen ${age} ago)`];
|
|
4945
|
+
});
|
|
4998
4946
|
}
|
|
4999
4947
|
|
|
5000
|
-
// src/
|
|
5001
|
-
import
|
|
5002
|
-
var
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
}
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
4948
|
+
// src/usage/plan.ts
|
|
4949
|
+
import { z as z6 } from "zod";
|
|
4950
|
+
var PlanClassSchema = z6.union([
|
|
4951
|
+
z6.strictObject({
|
|
4952
|
+
kind: z6.literal("subscription"),
|
|
4953
|
+
/** The plan's size as a multiple of the smallest subscription tier (Max 5x is 5, Max 20x is 20). */
|
|
4954
|
+
capacity: z6.number().positive(),
|
|
4955
|
+
/** False when the tier string named no multiplier and no tier this project knows, so `capacity` is the neutral 1 rather than a reading. */
|
|
4956
|
+
recognised: z6.boolean(),
|
|
4957
|
+
/** The tier string the classification rests on, when the account had one. */
|
|
4958
|
+
tier: z6.string().optional()
|
|
4959
|
+
}),
|
|
4960
|
+
z6.strictObject({
|
|
4961
|
+
kind: z6.literal("pay-per-use"),
|
|
4962
|
+
tier: z6.string().optional()
|
|
4963
|
+
})
|
|
4964
|
+
]);
|
|
4965
|
+
var TIER_MULTIPLIER = /(?:^|_)(\d+)x$/;
|
|
4966
|
+
var ZERO_TIER = /(?:^|_)zero$/;
|
|
4967
|
+
var BASE_TIER = /(?:^|_)(?:pro|free)$/;
|
|
4968
|
+
function planOf(account) {
|
|
4969
|
+
const tier = account?.organizationRateLimitTier ?? account?.userRateLimitTier;
|
|
4970
|
+
const tierField = tier === void 0 ? {} : { tier };
|
|
4971
|
+
if (tier === void 0) {
|
|
4972
|
+
return { kind: "subscription", capacity: 1, recognised: false };
|
|
5024
4973
|
}
|
|
5025
|
-
if (
|
|
5026
|
-
return
|
|
4974
|
+
if (ZERO_TIER.test(tier)) {
|
|
4975
|
+
return { kind: "pay-per-use", ...tierField };
|
|
5027
4976
|
}
|
|
5028
|
-
|
|
5029
|
-
|
|
4977
|
+
const multiplier = TIER_MULTIPLIER.exec(tier)?.[1];
|
|
4978
|
+
if (multiplier !== void 0) {
|
|
4979
|
+
return { kind: "subscription", capacity: Number(multiplier), recognised: true, ...tierField };
|
|
5030
4980
|
}
|
|
5031
|
-
return
|
|
4981
|
+
return { kind: "subscription", capacity: 1, recognised: BASE_TIER.test(tier), ...tierField };
|
|
5032
4982
|
}
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
const length = Buffer.byteLength(socketPath, "utf8");
|
|
5040
|
-
if (length > UNIX_SOCKET_PATH_MAX_BYTES) {
|
|
5041
|
-
return {
|
|
5042
|
-
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`
|
|
5043
|
-
};
|
|
5044
|
-
}
|
|
5045
|
-
if (ports.lstat(paths.headroomSocketDir)?.kind === "symlink") {
|
|
5046
|
-
return { refused: `the headroom socket directory ${paths.headroomSocketDir} is a symlink; it must be the real directory` };
|
|
4983
|
+
|
|
4984
|
+
// src/usage/rateLimit.ts
|
|
4985
|
+
var MS_PER_SECOND3 = 1e3;
|
|
4986
|
+
function epochSecondsToIso(value) {
|
|
4987
|
+
if (value === void 0) {
|
|
4988
|
+
return void 0;
|
|
5047
4989
|
}
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
return refusal === void 0 ? { socketPath } : { refused: refusal };
|
|
4990
|
+
const seconds = Number(value);
|
|
4991
|
+
return Number.isFinite(seconds) && seconds > 0 ? new Date(seconds * MS_PER_SECOND3).toISOString() : void 0;
|
|
5051
4992
|
}
|
|
5052
|
-
function
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
const pid = HEADROOM_SOCKET_NAME.exec(name)?.[1];
|
|
5056
|
-
if (pid === void 0 || isRunning(Number.parseInt(pid, 10))) {
|
|
5057
|
-
continue;
|
|
5058
|
-
}
|
|
5059
|
-
fs12.removeRecursive(path11.join(paths.headroomSocketDir, name));
|
|
5060
|
-
removed.push(name);
|
|
4993
|
+
function nonNegativeNumber(value) {
|
|
4994
|
+
if (value === void 0 || value.trim() === "") {
|
|
4995
|
+
return void 0;
|
|
5061
4996
|
}
|
|
5062
|
-
|
|
4997
|
+
const parsed = Number(value);
|
|
4998
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
5063
4999
|
}
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
const match = GIT_SPEC_RE.exec(spec);
|
|
5072
|
-
if (match === null) {
|
|
5073
|
-
return false;
|
|
5000
|
+
var UNIFIED_PREFIX = "anthropic-ratelimit-unified-";
|
|
5001
|
+
function unifiedWindow(headers, window) {
|
|
5002
|
+
const utilization = nonNegativeNumber(headers[`${UNIFIED_PREFIX}${window}-utilization`]);
|
|
5003
|
+
const resetsAt = epochSecondsToIso(headers[`${UNIFIED_PREFIX}${window}-reset`]);
|
|
5004
|
+
const status = headers[`${UNIFIED_PREFIX}${window}-status`];
|
|
5005
|
+
if (utilization === void 0 && resetsAt === void 0 && status === void 0) {
|
|
5006
|
+
return void 0;
|
|
5074
5007
|
}
|
|
5075
|
-
|
|
5076
|
-
return ref === void 0 || !COMMIT_SHA_RE.test(ref);
|
|
5077
|
-
}
|
|
5078
|
-
function pinnedGitCommit(spec) {
|
|
5079
|
-
const ref = GIT_SPEC_RE.exec(spec)?.[1];
|
|
5080
|
-
return ref !== void 0 && COMMIT_SHA_RE.test(ref) ? ref.toLowerCase() : void 0;
|
|
5008
|
+
return { ...utilization === void 0 ? {} : { utilization }, ...resetsAt === void 0 ? {} : { resetsAt }, ...status === void 0 ? {} : { status } };
|
|
5081
5009
|
}
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5010
|
+
function parseUnifiedRateLimit(headers) {
|
|
5011
|
+
if (!Object.keys(headers).some((name) => name.startsWith(UNIFIED_PREFIX))) {
|
|
5012
|
+
return void 0;
|
|
5013
|
+
}
|
|
5014
|
+
const status = headers[`${UNIFIED_PREFIX}status`];
|
|
5015
|
+
const fiveHour = unifiedWindow(headers, "5h");
|
|
5016
|
+
const sevenDay = unifiedWindow(headers, "7d");
|
|
5017
|
+
const representativeClaim = headers[`${UNIFIED_PREFIX}representative-claim`];
|
|
5018
|
+
const resetAt = epochSecondsToIso(headers[`${UNIFIED_PREFIX}reset`]);
|
|
5019
|
+
const overageStatus = headers[`${UNIFIED_PREFIX}overage-status`];
|
|
5085
5020
|
return {
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5021
|
+
...status === void 0 ? {} : { status },
|
|
5022
|
+
...fiveHour === void 0 ? {} : { fiveHour },
|
|
5023
|
+
...sevenDay === void 0 ? {} : { sevenDay },
|
|
5024
|
+
...representativeClaim === void 0 ? {} : { representativeClaim },
|
|
5025
|
+
...resetAt === void 0 ? {} : { resetAt },
|
|
5026
|
+
...overageStatus === void 0 ? {} : { overageStatus }
|
|
5089
5027
|
};
|
|
5090
5028
|
}
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
var
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5029
|
+
|
|
5030
|
+
// src/usage/scan.ts
|
|
5031
|
+
import { z as z7 } from "zod";
|
|
5032
|
+
var AnthropicUsageSchema = z7.looseObject({
|
|
5033
|
+
input_tokens: z7.number().int().nonnegative().nullish(),
|
|
5034
|
+
output_tokens: z7.number().int().nonnegative().nullish(),
|
|
5035
|
+
cache_creation_input_tokens: z7.number().int().nonnegative().nullish(),
|
|
5036
|
+
cache_read_input_tokens: z7.number().int().nonnegative().nullish()
|
|
5037
|
+
});
|
|
5038
|
+
var ErrorObjectSchema = z7.looseObject({
|
|
5039
|
+
type: z7.string().optional(),
|
|
5040
|
+
code: z7.union([z7.string(), z7.number()]).optional(),
|
|
5041
|
+
message: z7.string().optional()
|
|
5042
|
+
});
|
|
5043
|
+
|
|
5044
|
+
// src/usage/middleware.ts
|
|
5045
|
+
var ANTHROPIC_PROVIDER = "anthropic";
|
|
5046
|
+
|
|
5047
|
+
// src/usage/pick.ts
|
|
5048
|
+
var MS_PER_SECOND4 = 1e3;
|
|
5049
|
+
var MS_PER_HOUR = 36e5;
|
|
5050
|
+
var HOURS_PER_DAY2 = 24;
|
|
5051
|
+
var FIVE_HOURS = 5;
|
|
5052
|
+
var PROMPT_CACHE_TTL_HOURS = 1;
|
|
5053
|
+
var PERCENT = 100;
|
|
5054
|
+
var SEVEN_DAYS = 7;
|
|
5055
|
+
var FIVE_HOUR_WINDOW_MS = FIVE_HOURS * MS_PER_HOUR;
|
|
5056
|
+
var SEVEN_DAY_WINDOW_MS = SEVEN_DAYS * HOURS_PER_DAY2 * MS_PER_HOUR;
|
|
5057
|
+
var PROMPT_CACHE_TTL_MS = PROMPT_CACHE_TTL_HOURS * MS_PER_HOUR;
|
|
5058
|
+
var CLASS_ORDER = { scored: 0, unknown: 1, "pay-per-use": 2, ineligible: 3 };
|
|
5059
|
+
function unifiedOf(state) {
|
|
5060
|
+
return state?.rateLimit?.unified;
|
|
5099
5061
|
}
|
|
5100
|
-
function
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
const specifier = /==|>=|<=|!=|>|<|~=/.exec(source);
|
|
5105
|
-
if (specifier === null) {
|
|
5106
|
-
return true;
|
|
5107
|
-
}
|
|
5108
|
-
const installed = parseVersion(version);
|
|
5109
|
-
const wanted = parseVersion(source.slice(specifier.index + specifier[0].length));
|
|
5110
|
-
if (installed === void 0 || wanted === void 0) {
|
|
5111
|
-
return true;
|
|
5062
|
+
function limitBlockedUntil(state, nowMs) {
|
|
5063
|
+
const limit = state?.lastLimit;
|
|
5064
|
+
if (state === void 0 || limit === void 0) {
|
|
5065
|
+
return void 0;
|
|
5112
5066
|
}
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
case "==":
|
|
5116
|
-
return cmp === 0;
|
|
5117
|
-
case "!=":
|
|
5118
|
-
return cmp !== 0;
|
|
5119
|
-
case ">=":
|
|
5120
|
-
return cmp >= 0;
|
|
5121
|
-
case "<=":
|
|
5122
|
-
return cmp <= 0;
|
|
5123
|
-
case ">":
|
|
5124
|
-
return cmp > 0;
|
|
5125
|
-
case "<":
|
|
5126
|
-
return cmp < 0;
|
|
5127
|
-
case "~=":
|
|
5128
|
-
return compareVersions(installed.slice(0, Math.max(0, wanted.length - 1)), wanted.slice(0, Math.max(0, wanted.length - 1))) === 0 && cmp >= 0;
|
|
5129
|
-
default:
|
|
5130
|
-
return true;
|
|
5067
|
+
if (Date.parse(state.lastRequestAt) > Date.parse(limit.observedAt)) {
|
|
5068
|
+
return void 0;
|
|
5131
5069
|
}
|
|
5070
|
+
const until = limit.resetAt === void 0 ? limit.retryAfterSeconds === void 0 ? void 0 : Date.parse(limit.observedAt) + limit.retryAfterSeconds * MS_PER_SECOND4 : Date.parse(limit.resetAt);
|
|
5071
|
+
return until !== void 0 && until > nowMs ? until : void 0;
|
|
5132
5072
|
}
|
|
5133
|
-
function
|
|
5134
|
-
const
|
|
5135
|
-
|
|
5073
|
+
function windowBlockedUntil(windows) {
|
|
5074
|
+
const resets = windows.flatMap((window) => window.status === "rejected" && window.resetsAtMs !== void 0 ? [window.resetsAtMs] : []);
|
|
5075
|
+
return resets.length === 0 ? void 0 : Math.max(...resets);
|
|
5076
|
+
}
|
|
5077
|
+
function fiveHourBurn(records) {
|
|
5078
|
+
const points = [];
|
|
5079
|
+
for (const record of records) {
|
|
5080
|
+
const window = record.rateLimitHeaders === void 0 ? void 0 : parseUnifiedRateLimit(record.rateLimitHeaders)?.fiveHour;
|
|
5081
|
+
if (window?.utilization !== void 0 && window.resetsAt !== void 0) {
|
|
5082
|
+
points.push({ resetsAt: window.resetsAt, point: { atMs: Date.parse(record.at), utilization: window.utilization } });
|
|
5083
|
+
}
|
|
5084
|
+
}
|
|
5085
|
+
const latest = points.at(-1)?.resetsAt;
|
|
5086
|
+
const current = points.filter((entry) => entry.resetsAt === latest).map((entry) => entry.point);
|
|
5087
|
+
const first = current.at(0);
|
|
5088
|
+
const last = current.at(-1);
|
|
5089
|
+
if (first === void 0 || last === void 0 || last.atMs <= first.atMs || last.utilization <= first.utilization) {
|
|
5136
5090
|
return void 0;
|
|
5137
5091
|
}
|
|
5138
|
-
return
|
|
5092
|
+
return (last.utilization - first.utilization) / (last.atMs - first.atMs);
|
|
5139
5093
|
}
|
|
5140
|
-
function
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
}
|
|
5094
|
+
function percent(fraction) {
|
|
5095
|
+
return `${String(Math.round(fraction * PERCENT))}%`;
|
|
5096
|
+
}
|
|
5097
|
+
function describeWindow(label, window, effective, nowMs) {
|
|
5098
|
+
if (effective.reset) {
|
|
5099
|
+
return `${label} window has reset`;
|
|
5147
5100
|
}
|
|
5148
|
-
|
|
5101
|
+
const used = effective.utilization === void 0 ? "usage unknown" : `${percent(effective.utilization)} used`;
|
|
5102
|
+
const resets = effective.resetsAtMs === void 0 ? "" : `, resets in ${formatAge(effective.resetsAtMs - nowMs)}`;
|
|
5103
|
+
return `${label} ${used}${resets}${window.status === "allowed_warning" ? " (nearly used)" : ""}`;
|
|
5149
5104
|
}
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
const
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5105
|
+
function capacityOf(plan) {
|
|
5106
|
+
return plan.kind === "subscription" ? plan.capacity : 1;
|
|
5107
|
+
}
|
|
5108
|
+
function assess(member, nowMs) {
|
|
5109
|
+
const plan = planOf(member.account);
|
|
5110
|
+
const planReasons = plan.kind === "pay-per-use" ? ["billed by use (no plan allowance)"] : plan.recognised ? [`${String(plan.capacity)}x plan`] : [`plan tier ${plan.tier === void 0 ? "not known" : `"${plan.tier}" not recognised`}, counted as 1x`];
|
|
5111
|
+
const base = { identity: member.identity, plan };
|
|
5112
|
+
if (member.readError !== void 0) {
|
|
5113
|
+
return { candidate: { ...base, class: "unknown", reasons: [`usage state unreadable: ${member.readError}`, ...planReasons] } };
|
|
5158
5114
|
}
|
|
5159
|
-
|
|
5160
|
-
const
|
|
5161
|
-
|
|
5162
|
-
return
|
|
5163
|
-
}
|
|
5164
|
-
const
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
const versionOk = version2 !== void 0 && versionSatisfies(version2, config.source);
|
|
5181
|
-
const pinned = pinnedGitCommit(config.source);
|
|
5182
|
-
const commitOk = pinned === void 0 || ports.installedCommit() === pinned;
|
|
5183
|
-
if (versionOk && commitOk && (installedSource === void 0 || installedSource === config.source)) {
|
|
5184
|
-
installedSource = config.source;
|
|
5185
|
-
return true;
|
|
5186
|
-
}
|
|
5187
|
-
ports.log(`agent-shim headroom supervisor: installing headroom from ${config.source}`);
|
|
5188
|
-
const result = ports.install(config.source);
|
|
5189
|
-
if (!result.ok) {
|
|
5190
|
-
return false;
|
|
5115
|
+
const state = member.snapshot?.providers[ANTHROPIC_PROVIDER];
|
|
5116
|
+
const unified = unifiedOf(state);
|
|
5117
|
+
if (member.snapshot === void 0 || state === void 0 || unified === void 0) {
|
|
5118
|
+
return { candidate: { ...base, class: "unknown", reasons: ["no usage recorded yet, so its quota is unknown (turn on trackUsage for it)", ...planReasons] } };
|
|
5119
|
+
}
|
|
5120
|
+
const observedAgo = state.rateLimit === void 0 ? "" : ` (last seen ${formatAge(nowMs - Date.parse(state.rateLimit.observedAt))} ago)`;
|
|
5121
|
+
const fiveHourWindow = unified.fiveHour;
|
|
5122
|
+
const sevenDayWindow = unified.sevenDay;
|
|
5123
|
+
const five = fiveHourWindow === void 0 ? void 0 : effectiveWindow(fiveHourWindow, nowMs);
|
|
5124
|
+
const seven = sevenDayWindow === void 0 ? void 0 : effectiveWindow(sevenDayWindow, nowMs);
|
|
5125
|
+
const windowReasons = [
|
|
5126
|
+
...fiveHourWindow === void 0 || five === void 0 ? [] : [describeWindow("5h", fiveHourWindow, five, nowMs)],
|
|
5127
|
+
...sevenDayWindow === void 0 || seven === void 0 ? [] : [describeWindow("7d", sevenDayWindow, seven, nowMs)]
|
|
5128
|
+
];
|
|
5129
|
+
const blockedUntil = Math.max(
|
|
5130
|
+
windowBlockedUntil([five, seven].flatMap((window) => window === void 0 ? [] : [window])) ?? 0,
|
|
5131
|
+
limitBlockedUntil(state, nowMs) ?? 0
|
|
5132
|
+
);
|
|
5133
|
+
if (blockedUntil > nowMs) {
|
|
5134
|
+
if (unified.overageStatus === "allowed") {
|
|
5135
|
+
return { candidate: { ...base, class: "pay-per-use", reasons: [`plan exhausted until ${new Date(blockedUntil).toISOString()}, continues as extra usage`, ...windowReasons, ...planReasons] } };
|
|
5191
5136
|
}
|
|
5192
|
-
|
|
5193
|
-
return true;
|
|
5194
|
-
};
|
|
5195
|
-
const socket = prepareHeadroomSocket(fs12, ports.socketTrust, paths, ports.ownPid);
|
|
5196
|
-
if (socket.refused !== void 0) {
|
|
5197
|
-
return fail(socket.refused);
|
|
5137
|
+
return { candidate: { ...base, class: "ineligible", blockedUntilMs: blockedUntil, reasons: [`refused until ${new Date(blockedUntil).toISOString()} (in ${formatAge(blockedUntil - nowMs)})`, ...windowReasons, ...planReasons] } };
|
|
5198
5138
|
}
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
if (!ensureInstalled()) {
|
|
5202
|
-
return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
|
|
5139
|
+
if (plan.kind === "pay-per-use" || fiveHourWindow === void 0 && sevenDayWindow === void 0) {
|
|
5140
|
+
return { candidate: { ...base, class: "pay-per-use", reasons: [...plan.kind === "pay-per-use" ? planReasons : ["no plan windows reported, so usage bills as extra usage"]] } };
|
|
5203
5141
|
}
|
|
5204
|
-
const
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
const orphanPid = previousState?.headroomPid;
|
|
5208
|
-
if (orphanPid !== void 0 && ports.isRunning(orphanPid)) {
|
|
5209
|
-
ports.log(`agent-shim headroom supervisor: stopping daemon pid ${String(orphanPid)} left behind by the previous supervisor`);
|
|
5210
|
-
ports.stopProcess(orphanPid);
|
|
5142
|
+
const basis = seven?.utilization !== void 0 ? { window: seven, windowMs: SEVEN_DAY_WINDOW_MS } : five?.utilization !== void 0 ? { window: five, windowMs: FIVE_HOUR_WINDOW_MS } : void 0;
|
|
5143
|
+
if (basis?.window.utilization === void 0) {
|
|
5144
|
+
return { candidate: { ...base, class: "unknown", reasons: ["quota windows reported no utilisation", ...windowReasons, ...planReasons] } };
|
|
5211
5145
|
}
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5146
|
+
const remaining = Math.max(0, 1 - basis.window.utilization);
|
|
5147
|
+
const untilResetMs = basis.window.resetsAtMs === void 0 ? basis.windowMs : Math.max(basis.window.resetsAtMs - nowMs, MS_PER_SECOND4);
|
|
5148
|
+
const score = remaining * plan.capacity / (untilResetMs / MS_PER_HOUR);
|
|
5149
|
+
const fiveRemaining = five?.utilization === void 0 ? void 0 : Math.max(0, 1 - five.utilization);
|
|
5150
|
+
const fiveUntil = five?.resetsAtMs === void 0 ? FIVE_HOUR_WINDOW_MS : Math.max(five.resetsAtMs - nowMs, MS_PER_SECOND4);
|
|
5151
|
+
const burn = fiveHourBurn(member.records);
|
|
5152
|
+
return {
|
|
5153
|
+
candidate: { ...base, class: "scored", score, reasons: [...windowReasons.map((reason, index) => index === windowReasons.length - 1 ? `${reason}${observedAgo}` : reason), ...planReasons] },
|
|
5154
|
+
...fiveRemaining === void 0 ? {} : { fiveHour: { remaining: fiveRemaining, untilResetMs: fiveUntil } },
|
|
5155
|
+
...burn === void 0 ? {} : { burn }
|
|
5156
|
+
};
|
|
5157
|
+
}
|
|
5158
|
+
function rankPool(input) {
|
|
5159
|
+
const { members, nowMs } = input;
|
|
5160
|
+
const assessments = members.map((member) => assess(member, nowMs));
|
|
5161
|
+
const demands = assessments.flatMap(({ candidate, burn }) => burn === void 0 ? [] : [burn * capacityOf(candidate.plan)]);
|
|
5162
|
+
const sharedDemand = demands.length === 0 ? void 0 : Math.max(...demands);
|
|
5163
|
+
const candidates = assessments.map((assessment) => {
|
|
5164
|
+
const { candidate, fiveHour, burn } = assessment;
|
|
5165
|
+
const pace = burn ?? (sharedDemand === void 0 ? void 0 : sharedDemand / capacityOf(candidate.plan));
|
|
5166
|
+
if (candidate.class !== "scored" || fiveHour === void 0 || pace === void 0) {
|
|
5167
|
+
return { ...candidate, feasible: true };
|
|
5222
5168
|
}
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
const allowlist = allowlistOf(ports);
|
|
5227
|
-
const allowlistHash = hashAllowlist(allowlist);
|
|
5228
|
-
pruneDeadSessions(fs12, paths.headroomSessionsDir, ports.isRunning);
|
|
5229
|
-
const ownsNow = ownsState();
|
|
5230
|
-
if (!ownsNow && !superseded) {
|
|
5231
|
-
ports.log("agent-shim headroom supervisor: another supervisor now owns the state file; serving this daemon's own sessions until they end");
|
|
5169
|
+
const runsDryInMs = fiveHour.remaining / pace;
|
|
5170
|
+
if (runsDryInMs >= fiveHour.untilResetMs) {
|
|
5171
|
+
return { ...candidate, feasible: true };
|
|
5232
5172
|
}
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
return fail(`headroom failed to become ready ${String(consecutiveFailures)} times in a row; giving up`);
|
|
5249
|
-
}
|
|
5250
|
-
if (installedSource !== config.source && !ensureInstalled()) {
|
|
5251
|
-
return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
|
|
5252
|
-
}
|
|
5253
|
-
const pid = ports.spawnHeadroom(socketPath, allowlist, config.settings);
|
|
5254
|
-
const ready = await waitUntilReady(ports, socketPath);
|
|
5255
|
-
if (ready) {
|
|
5256
|
-
consecutiveFailures = 0;
|
|
5257
|
-
headroomPid = pid;
|
|
5258
|
-
runningHash = allowlistHash;
|
|
5259
|
-
runningSettingsHash = settingsHash;
|
|
5260
|
-
const recorded = writeOwnState({
|
|
5261
|
-
supervisorPid: ports.ownPid,
|
|
5262
|
-
headroomPid: pid,
|
|
5263
|
-
socketPath,
|
|
5264
|
-
version,
|
|
5265
|
-
allowlistHash,
|
|
5266
|
-
settingsHash,
|
|
5267
|
-
installedSource: config.source
|
|
5268
|
-
});
|
|
5269
|
-
if (recorded) {
|
|
5270
|
-
fs12.removeRecursive(paths.headroomLockFile);
|
|
5271
|
-
}
|
|
5272
|
-
idleSince = void 0;
|
|
5273
|
-
ports.log(`agent-shim headroom supervisor: headroom pid ${String(pid)} ready on unix socket ${socketPath}`);
|
|
5274
|
-
} else {
|
|
5275
|
-
ports.stopProcess(pid);
|
|
5276
|
-
consecutiveFailures += 1;
|
|
5277
|
-
ports.log(
|
|
5278
|
-
`agent-shim headroom supervisor: headroom did not become ready on unix socket ${socketPath} (attempt ${String(consecutiveFailures)} of ${String(HEADROOM_START_RETRY_BUDGET)})`
|
|
5279
|
-
);
|
|
5280
|
-
await ports.sleep(backoffForAttempt(consecutiveFailures));
|
|
5281
|
-
continue;
|
|
5282
|
-
}
|
|
5283
|
-
} else {
|
|
5284
|
-
if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
|
|
5285
|
-
if (sessionsForSupervisor(fs12, paths.headroomSessionsDir, ports.ownPid).length === 0) {
|
|
5286
|
-
ports.log("agent-shim headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
|
|
5287
|
-
ports.stopProcess(headroomPid);
|
|
5288
|
-
headroomPid = void 0;
|
|
5289
|
-
runningHash = void 0;
|
|
5290
|
-
runningSettingsHash = void 0;
|
|
5291
|
-
continue;
|
|
5292
|
-
}
|
|
5293
|
-
}
|
|
5294
|
-
const sessions = sessionsForSupervisor(fs12, paths.headroomSessionsDir, ports.ownPid);
|
|
5295
|
-
if (sessions.length === 0) {
|
|
5296
|
-
idleSince ??= ports.now();
|
|
5297
|
-
const idleMs = ports.now() - idleSince;
|
|
5298
|
-
if (idleMs >= config.idleShutdownMinutes * MS_PER_MINUTE2) {
|
|
5299
|
-
ports.log(
|
|
5300
|
-
`agent-shim headroom supervisor: no sessions for ${String(config.idleShutdownMinutes)} minute(s); stopping headroom and exiting`
|
|
5301
|
-
);
|
|
5302
|
-
ports.stopProcess(headroomPid);
|
|
5303
|
-
writeOwnState({ version });
|
|
5304
|
-
return 0;
|
|
5305
|
-
}
|
|
5306
|
-
} else {
|
|
5307
|
-
idleSince = void 0;
|
|
5308
|
-
}
|
|
5309
|
-
}
|
|
5310
|
-
await ports.sleep(HEADROOM_POLL_MS);
|
|
5311
|
-
}
|
|
5312
|
-
}
|
|
5313
|
-
function currentHeadroomAllowlist(fs12, paths) {
|
|
5314
|
-
const providers = readAllProviders(fs12, paths.providersDir).map((entry) => entry.provider);
|
|
5315
|
-
return headroomAllowlist(headroomUpstreams(providers, frontDoorOrigin(readFrontDoorState(fs12, paths.frontdoorStateFile))));
|
|
5316
|
-
}
|
|
5317
|
-
function allowlistOf(ports) {
|
|
5318
|
-
return currentHeadroomAllowlist(ports.fs, ports.paths);
|
|
5319
|
-
}
|
|
5320
|
-
async function waitUntilReady(ports, socketPath) {
|
|
5321
|
-
const deadline = ports.now() + HEADROOM_READY_TIMEOUT_MS;
|
|
5322
|
-
for (; ; ) {
|
|
5323
|
-
if (await ports.ready(socketPath)) {
|
|
5324
|
-
return true;
|
|
5325
|
-
}
|
|
5326
|
-
if (ports.now() >= deadline) {
|
|
5327
|
-
return false;
|
|
5328
|
-
}
|
|
5329
|
-
await ports.sleep(HEADROOM_POLL_MS);
|
|
5330
|
-
}
|
|
5331
|
-
}
|
|
5332
|
-
|
|
5333
|
-
// src/launcher/guard.ts
|
|
5334
|
-
var AMBIENT_CREDENTIAL_VARS = [
|
|
5335
|
-
"ANTHROPIC_API_KEY",
|
|
5336
|
-
"ANTHROPIC_AUTH_TOKEN",
|
|
5337
|
-
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
5338
|
-
"CLAUDE_CODE_USE_BEDROCK",
|
|
5339
|
-
"CLAUDE_CODE_USE_VERTEX",
|
|
5340
|
-
"CLAUDE_CODE_USE_FOUNDRY"
|
|
5341
|
-
];
|
|
5342
|
-
function detectAmbientCredential(env, injected) {
|
|
5343
|
-
for (const variable of AMBIENT_CREDENTIAL_VARS) {
|
|
5344
|
-
const value = env[variable];
|
|
5345
|
-
if (value === void 0 || value === "") {
|
|
5346
|
-
continue;
|
|
5347
|
-
}
|
|
5348
|
-
if (injected?.variable === variable && value === injected.token) {
|
|
5349
|
-
continue;
|
|
5173
|
+
return { ...candidate, feasible: false, reasons: [`5h window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(fiveHour.untilResetMs)}`, ...candidate.reasons] };
|
|
5174
|
+
});
|
|
5175
|
+
const ordered = [...candidates].sort(
|
|
5176
|
+
(left, right) => CLASS_ORDER[left.class] - CLASS_ORDER[right.class] || Number(right.feasible) - Number(left.feasible) || (right.score ?? 0) - (left.score ?? 0) || left.identity.localeCompare(right.identity)
|
|
5177
|
+
);
|
|
5178
|
+
const sticky = input.sticky;
|
|
5179
|
+
const stickyIndex = sticky === void 0 ? -1 : ordered.findIndex((candidate) => candidate.identity === sticky.identity);
|
|
5180
|
+
const stickyCandidate = ordered[stickyIndex];
|
|
5181
|
+
if (sticky !== void 0 && stickyCandidate?.class === "scored" && stickyCandidate.feasible) {
|
|
5182
|
+
const ageMs = nowMs - Date.parse(sticky.at);
|
|
5183
|
+
const warm = ageMs <= PROMPT_CACHE_TTL_MS;
|
|
5184
|
+
if (input.resuming || warm) {
|
|
5185
|
+
const why = warm ? `used here ${formatAge(ageMs)} ago, prompt cache still warm` : "resuming a conversation started on this account";
|
|
5186
|
+
ordered.splice(stickyIndex, 1);
|
|
5187
|
+
ordered.unshift({ ...stickyCandidate, reasons: [why, ...stickyCandidate.reasons] });
|
|
5350
5188
|
}
|
|
5351
|
-
return { variable };
|
|
5352
|
-
}
|
|
5353
|
-
return void 0;
|
|
5354
|
-
}
|
|
5355
|
-
function formatAmbientCredentialGuardMessage(variable, identityName) {
|
|
5356
|
-
const identitySetCommand = identityName === void 0 ? "agent-shim identity set <name> --allow-ambient-credential" : `agent-shim identity set ${identityName} --allow-ambient-credential`;
|
|
5357
|
-
return [
|
|
5358
|
-
`error: ${variable} is set in the environment. This identity's isolated`,
|
|
5359
|
-
"credential would be bypassed \u2014 every identity authenticates as this same key",
|
|
5360
|
-
"while it's set. Unset it, or if this is deliberate, opt in per-launch with",
|
|
5361
|
-
"AGENT_SHIM_ALLOW_AMBIENT_CREDENTIAL=1, or persistently for this identity with",
|
|
5362
|
-
`\`${identitySetCommand}\`.`
|
|
5363
|
-
].join("\n");
|
|
5364
|
-
}
|
|
5365
|
-
function evaluateAmbientCredentialGuard(params) {
|
|
5366
|
-
const detected = detectAmbientCredential(params.env, params.injectedCredential);
|
|
5367
|
-
if (detected === void 0) {
|
|
5368
|
-
return { ok: true };
|
|
5369
|
-
}
|
|
5370
|
-
if (params.allowAmbientCredentialOverride || params.allowAmbientCredential || params.providerSelected === true) {
|
|
5371
|
-
return { ok: true };
|
|
5372
5189
|
}
|
|
5190
|
+
const pick = ordered.find((candidate) => candidate.class !== "ineligible");
|
|
5191
|
+
const returns = ordered.flatMap((candidate) => candidate.blockedUntilMs === void 0 ? [] : [{ identity: candidate.identity, atMs: candidate.blockedUntilMs }]);
|
|
5192
|
+
const earliestReturn = returns.length === 0 ? void 0 : returns.reduce((soonest, entry) => entry.atMs < soonest.atMs ? entry : soonest);
|
|
5373
5193
|
return {
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
message: formatAmbientCredentialGuardMessage(detected.variable, params.identityName)
|
|
5194
|
+
candidates: ordered,
|
|
5195
|
+
...pick === void 0 ? { ...earliestReturn === void 0 ? {} : { earliestReturn } } : { pick }
|
|
5377
5196
|
};
|
|
5378
5197
|
}
|
|
5379
5198
|
|
|
5380
|
-
// src/
|
|
5381
|
-
import
|
|
5382
|
-
import
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5199
|
+
// src/usage/poolPick.ts
|
|
5200
|
+
import path11 from "node:path";
|
|
5201
|
+
import { z as z8 } from "zod";
|
|
5202
|
+
|
|
5203
|
+
// src/usage/store.ts
|
|
5204
|
+
var DAY_MS2 = 864e5;
|
|
5205
|
+
var DAYS_IN_WEEK = 7;
|
|
5206
|
+
var USAGE_RETENTION_MS = DAYS_IN_WEEK * DAY_MS2;
|
|
5207
|
+
|
|
5208
|
+
// src/usage/poolPick.ts
|
|
5209
|
+
var SCHEMA_VERSION = 1;
|
|
5210
|
+
var PickEntrySchema = z8.strictObject({ identity: z8.string().min(1), at: z8.iso.datetime() });
|
|
5211
|
+
var PicksFileSchema = z8.strictObject({
|
|
5212
|
+
schemaVersion: z8.literal(SCHEMA_VERSION),
|
|
5213
|
+
picks: z8.record(z8.string().min(1), PickEntrySchema)
|
|
5214
|
+
});
|
|
5215
|
+
function errorMessage(error) {
|
|
5216
|
+
return error instanceof Error ? error.message : String(error);
|
|
5392
5217
|
}
|
|
5393
|
-
function
|
|
5394
|
-
|
|
5218
|
+
function loadPoolMembers(fs12, paths, identities, nowMs) {
|
|
5219
|
+
const windowStartMs = nowMs - FIVE_HOUR_WINDOW_MS;
|
|
5220
|
+
const log = readUsageLog(fs12, paths.usageLogDir, { sinceMs: windowStartMs });
|
|
5221
|
+
return identities.map((identity) => {
|
|
5222
|
+
const records = log.records.filter((record) => record.identity === identity && record.provider === ANTHROPIC_PROVIDER && Date.parse(record.at) >= windowStartMs);
|
|
5223
|
+
try {
|
|
5224
|
+
const snapshot = readUsageSnapshot(fs12, paths.usageSnapshotsDir, identity);
|
|
5225
|
+
const account = readAccountMetadata(fs12, paths.identitiesDir, identity) ?? snapshot?.account;
|
|
5226
|
+
return { identity, records, ...snapshot === void 0 ? {} : { snapshot }, ...account === void 0 ? {} : { account } };
|
|
5227
|
+
} catch (error) {
|
|
5228
|
+
if (error instanceof UsageSnapshotError || error instanceof AccountMetadataError) {
|
|
5229
|
+
return { identity, records, readError: errorMessage(error) };
|
|
5230
|
+
}
|
|
5231
|
+
throw error;
|
|
5232
|
+
}
|
|
5233
|
+
});
|
|
5395
5234
|
}
|
|
5396
|
-
function
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5235
|
+
function rankPoolFromStore(params) {
|
|
5236
|
+
return rankPool({
|
|
5237
|
+
members: loadPoolMembers(params.fs, params.paths, params.identities, params.nowMs),
|
|
5238
|
+
nowMs: params.nowMs,
|
|
5239
|
+
resuming: params.resuming,
|
|
5240
|
+
...params.sticky === void 0 ? {} : { sticky: params.sticky }
|
|
5241
|
+
});
|
|
5242
|
+
}
|
|
5243
|
+
function readPicksFile(fs12, file) {
|
|
5244
|
+
const empty = { schemaVersion: SCHEMA_VERSION, picks: {} };
|
|
5245
|
+
const raw = fs12.readFileUtf8(file);
|
|
5246
|
+
if (raw === void 0) {
|
|
5247
|
+
return { file: empty };
|
|
5248
|
+
}
|
|
5249
|
+
let parsed;
|
|
5250
|
+
try {
|
|
5251
|
+
parsed = JSON.parse(raw);
|
|
5252
|
+
} catch {
|
|
5253
|
+
return { file: empty, problem: `${file} is not valid JSON` };
|
|
5400
5254
|
}
|
|
5401
|
-
|
|
5255
|
+
const result = PicksFileSchema.safeParse(parsed);
|
|
5256
|
+
return result.success ? { file: result.data } : { file: empty, problem: `${file} is not a picks file this agent-shim can read` };
|
|
5402
5257
|
}
|
|
5403
|
-
function
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
configProfilesDir: path12.join(root, "config-profiles"),
|
|
5408
|
-
providersDir: path12.join(root, "providers"),
|
|
5409
|
-
directoryRulesFile: path12.join(root, "directory-rules.json"),
|
|
5410
|
-
activeIdentityFile: path12.join(root, "active-identity"),
|
|
5411
|
-
globalConfigFile: path12.join(root, "config.json"),
|
|
5412
|
-
categoriesLocalFile: path12.join(root, "categories.local.json"),
|
|
5413
|
-
claudeShimFile: path12.join(root, "claude-shim.json"),
|
|
5414
|
-
headroomDir: path12.join(root, "headroom"),
|
|
5415
|
-
headroomStateFile: path12.join(root, "headroom", `state.v${String(HEADROOM_STATE_SCHEMA_VERSION)}.json`),
|
|
5416
|
-
headroomSocketDir: path12.join(root, "headroom", "run"),
|
|
5417
|
-
headroomLockFile: path12.join(root, "headroom", "start.lock"),
|
|
5418
|
-
headroomSessionsDir: path12.join(root, "headroom", "sessions"),
|
|
5419
|
-
logsDir: path12.join(root, "logs"),
|
|
5420
|
-
headroomLogPath: path12.join(root, "logs", "headroom.log"),
|
|
5421
|
-
frontdoorDir: path12.join(root, "frontdoor"),
|
|
5422
|
-
frontdoorCaDir: path12.join(root, "frontdoor", "ca"),
|
|
5423
|
-
frontdoorCaCertFile: path12.join(root, "frontdoor", "ca", "ca.pem"),
|
|
5424
|
-
frontdoorCaKeyFile: path12.join(root, "frontdoor", "ca", "ca.key"),
|
|
5425
|
-
frontdoorCaBundlesDir: path12.join(root, "frontdoor", "ca", "bundles"),
|
|
5426
|
-
frontdoorStateFile: path12.join(root, "frontdoor", "state.json"),
|
|
5427
|
-
frontdoorLockFile: path12.join(root, "frontdoor", "start.lock"),
|
|
5428
|
-
frontdoorControlTokenFile: path12.join(root, "frontdoor", "control-token"),
|
|
5429
|
-
frontdoorSessionsDir: path12.join(root, "frontdoor", "sessions"),
|
|
5430
|
-
frontdoorLogPath: path12.join(root, "logs", "frontdoor.log"),
|
|
5431
|
-
usageDir: path12.join(root, "usage"),
|
|
5432
|
-
usageLogDir: path12.join(root, "usage", "log"),
|
|
5433
|
-
usageSnapshotsDir: path12.join(root, "usage", "snapshots"),
|
|
5434
|
-
usagePicksFile: path12.join(root, "usage", "picks.json")
|
|
5435
|
-
};
|
|
5258
|
+
function readStickyPick(fs12, picksFile, directory) {
|
|
5259
|
+
const read = readPicksFile(fs12, picksFile);
|
|
5260
|
+
const entry = read.file.picks[directory];
|
|
5261
|
+
return { ...entry === void 0 ? {} : { sticky: entry }, ...read.problem === void 0 ? {} : { problem: read.problem } };
|
|
5436
5262
|
}
|
|
5437
|
-
function
|
|
5438
|
-
|
|
5263
|
+
function recordStickyPick(fs12, picksFile, directory, identity, nowMs) {
|
|
5264
|
+
const current = readPicksFile(fs12, picksFile).file.picks;
|
|
5265
|
+
const kept = Object.entries(current).filter(([, entry]) => nowMs - Date.parse(entry.at) <= USAGE_RETENTION_MS);
|
|
5266
|
+
const next = { schemaVersion: SCHEMA_VERSION, picks: { ...Object.fromEntries(kept), [directory]: { identity, at: new Date(nowMs).toISOString() } } };
|
|
5267
|
+
fs12.mkdirPrivate(path11.dirname(picksFile));
|
|
5268
|
+
fs12.writeFilePrivate(picksFile, `${JSON.stringify(next, null, 2)}
|
|
5269
|
+
`);
|
|
5439
5270
|
}
|
|
5440
5271
|
|
|
5441
|
-
// src/identityStore.ts
|
|
5442
|
-
import fs5 from "node:fs";
|
|
5443
|
-
import path15 from "node:path";
|
|
5444
|
-
|
|
5445
5272
|
// src/launcher/identity.ts
|
|
5446
|
-
import
|
|
5273
|
+
import path12 from "node:path";
|
|
5447
5274
|
function poolNameOf(selector) {
|
|
5448
5275
|
return selector.startsWith(POOL_SELECTOR_PREFIX) ? selector.slice(POOL_SELECTOR_PREFIX.length) : void 0;
|
|
5449
5276
|
}
|
|
@@ -5492,1471 +5319,1682 @@ function decideConfigProfile(params) {
|
|
|
5492
5319
|
return { source: "none" };
|
|
5493
5320
|
}
|
|
5494
5321
|
function loadIdentity(identitiesDir, name, fs12) {
|
|
5495
|
-
return loadConfigFile(
|
|
5322
|
+
return loadConfigFile(path12.join(identitiesDir, name, "identity.json"), IdentitySchema, fs12.readConfigFile);
|
|
5496
5323
|
}
|
|
5497
5324
|
|
|
5498
|
-
// src/
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
var
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
this.name = "ProfileNotFoundError";
|
|
5506
|
-
}
|
|
5507
|
-
profileName;
|
|
5508
|
-
};
|
|
5509
|
-
var ProfileAlreadyExistsError = class extends CliError {
|
|
5510
|
-
constructor(profileName) {
|
|
5511
|
-
super(`A configuration profile named "${profileName}" already exists.`);
|
|
5512
|
-
this.profileName = profileName;
|
|
5513
|
-
this.name = "ProfileAlreadyExistsError";
|
|
5514
|
-
}
|
|
5515
|
-
profileName;
|
|
5516
|
-
};
|
|
5517
|
-
var InvalidCategoryNameError = class extends CliError {
|
|
5518
|
-
constructor(categoryName) {
|
|
5519
|
-
super(`"${categoryName}" is not a category a configuration profile may toggle (runtime, history, knowledge, settings).`);
|
|
5520
|
-
this.categoryName = categoryName;
|
|
5521
|
-
this.name = "InvalidCategoryNameError";
|
|
5522
|
-
}
|
|
5523
|
-
categoryName;
|
|
5524
|
-
};
|
|
5525
|
-
function profileJsonPath(paths, name) {
|
|
5526
|
-
return path14.join(paths.configProfilesDir, `${name}.json`);
|
|
5325
|
+
// src/launcher/pool.ts
|
|
5326
|
+
var RESUME_FLAGS = /* @__PURE__ */ new Set(["--continue", "-c", "--resume", "-r"]);
|
|
5327
|
+
var TERMINATOR = "--";
|
|
5328
|
+
var REASONS_LOGGED = 3;
|
|
5329
|
+
function splitMembers(pool, paths, fs12) {
|
|
5330
|
+
const exists = (name) => loadIdentity(paths.identitiesDir, name, fs12) !== void 0;
|
|
5331
|
+
return { present: pool.identities.filter(exists), missing: pool.identities.filter((name) => !exists(name)) };
|
|
5527
5332
|
}
|
|
5528
|
-
function
|
|
5529
|
-
|
|
5333
|
+
function isResuming(passthrough) {
|
|
5334
|
+
const end = passthrough.indexOf(TERMINATOR);
|
|
5335
|
+
return (end === -1 ? passthrough : passthrough.slice(0, end)).some((token) => RESUME_FLAGS.has(token));
|
|
5530
5336
|
}
|
|
5531
|
-
function
|
|
5532
|
-
|
|
5533
|
-
|
|
5337
|
+
function resolvePoolLaunch(params) {
|
|
5338
|
+
const { poolName, log } = params;
|
|
5339
|
+
const pool = params.pools?.[poolName];
|
|
5340
|
+
if (pool === void 0) {
|
|
5341
|
+
return { ok: false, message: `agent-shim: no pool named "${poolName}" (selected via ${params.selectedVia}). Run \`agent-shim pool add ${poolName} --identity <name>...\` first.` };
|
|
5534
5342
|
}
|
|
5535
|
-
}
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
}
|
|
5539
|
-
function createProfile(paths, name, extendsList, description) {
|
|
5540
|
-
if (profileExists(paths, name)) {
|
|
5541
|
-
throw new ProfileAlreadyExistsError(name);
|
|
5343
|
+
const { present: identities, missing } = splitMembers(pool, params.paths, params.fs);
|
|
5344
|
+
for (const name of missing) {
|
|
5345
|
+
log.warn(`agent-shim: pool "${poolName}" names identity "${name}", which does not exist; skipping it`);
|
|
5542
5346
|
}
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
...description === void 0 ? {} : { description },
|
|
5546
|
-
...extendsList !== void 0 && extendsList.length > 0 ? { extends: [...extendsList] } : {}
|
|
5547
|
-
});
|
|
5548
|
-
if (!parsed.success) {
|
|
5549
|
-
throw new ConfigValidationError(filePath, parsed.error.issues);
|
|
5347
|
+
if (identities.length === 0) {
|
|
5348
|
+
return { ok: false, message: `agent-shim: no member of pool "${poolName}" is an existing identity.` };
|
|
5550
5349
|
}
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
if (!fs4.existsSync(paths.configProfilesDir)) {
|
|
5556
|
-
return [];
|
|
5350
|
+
const resuming = isResuming(params.passthrough);
|
|
5351
|
+
const stickyRead = readStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd);
|
|
5352
|
+
if (stickyRead.problem !== void 0) {
|
|
5353
|
+
log.warn(`agent-shim: ${stickyRead.problem}; ignoring the last-pick record`);
|
|
5557
5354
|
}
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
const
|
|
5562
|
-
if (
|
|
5563
|
-
|
|
5355
|
+
for (; ; ) {
|
|
5356
|
+
const nowMs = params.now();
|
|
5357
|
+
const ranking = rankPoolFromStore({ fs: params.usageFs, paths: params.paths, identities, nowMs, resuming, ...stickyRead.sticky === void 0 ? {} : { sticky: stickyRead.sticky } });
|
|
5358
|
+
const { pick, earliestReturn } = ranking;
|
|
5359
|
+
if (pick !== void 0) {
|
|
5360
|
+
recordStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd, pick.identity, nowMs);
|
|
5361
|
+
return { ok: true, identity: pick.identity, explanation: `pool ${poolName}: ${pick.reasons.slice(0, REASONS_LOGGED).join("; ")}` };
|
|
5564
5362
|
}
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
}
|
|
5568
|
-
function globalConfigPath(paths) {
|
|
5569
|
-
return paths.globalConfigFile;
|
|
5570
|
-
}
|
|
5571
|
-
function readGlobalConfig(paths) {
|
|
5572
|
-
return readJson(globalConfigPath(paths), GlobalConfigSchema);
|
|
5573
|
-
}
|
|
5574
|
-
function setGlobalDefaultProfile(paths, name) {
|
|
5575
|
-
return applyPatch(
|
|
5576
|
-
globalConfigPath(paths),
|
|
5577
|
-
GlobalConfigSchema,
|
|
5578
|
-
{ defaultConfigProfile: name },
|
|
5579
|
-
{ defaults: {} }
|
|
5580
|
-
);
|
|
5581
|
-
}
|
|
5582
|
-
function validateCategoryNames(patch) {
|
|
5583
|
-
for (const key of Object.keys(patch)) {
|
|
5584
|
-
if (!isOverridableCategory(key)) {
|
|
5585
|
-
throw new InvalidCategoryNameError(key);
|
|
5363
|
+
if (earliestReturn === void 0) {
|
|
5364
|
+
return { ok: false, message: `agent-shim: no member of pool "${poolName}" can be picked.` };
|
|
5586
5365
|
}
|
|
5366
|
+
const until = new Date(earliestReturn.atMs).toISOString();
|
|
5367
|
+
if (!params.wait) {
|
|
5368
|
+
return {
|
|
5369
|
+
ok: false,
|
|
5370
|
+
message: `agent-shim: every member of pool "${poolName}" is refused right now; the earliest, ${earliestReturn.identity}, returns at ${until} (in ${formatAge(earliestReturn.atMs - nowMs)}). Pass --wait to sleep until then.`
|
|
5371
|
+
};
|
|
5372
|
+
}
|
|
5373
|
+
log.info(`agent-shim: every member of pool "${poolName}" is refused; waiting until ${until} for ${earliestReturn.identity}`);
|
|
5374
|
+
params.sleep(Math.max(earliestReturn.atMs - nowMs, 0));
|
|
5587
5375
|
}
|
|
5588
5376
|
}
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
return applyPatch(profileJsonPath(paths, name), ConfigProfileSchema, { entries: mergedEntries });
|
|
5602
|
-
}
|
|
5603
|
-
function setProfileLaunchFlags(paths, name, patch) {
|
|
5604
|
-
requireProfileExists(paths, name);
|
|
5605
|
-
const existing = readProfile(paths, name) ?? {};
|
|
5606
|
-
const mergedLaunch = { ...existing.launch, ...patch };
|
|
5607
|
-
return applyPatch(profileJsonPath(paths, name), ConfigProfileSchema, {
|
|
5608
|
-
// Read as `unknown` so the check sees the `undefined` a cleared key holds, which LaunchFlags' optional properties hide from the value type.
|
|
5609
|
-
launch: Object.values(mergedLaunch).every((value) => value === void 0) ? void 0 : mergedLaunch
|
|
5610
|
-
});
|
|
5377
|
+
|
|
5378
|
+
// src/poolPickReport.ts
|
|
5379
|
+
function candidateView(candidate) {
|
|
5380
|
+
return {
|
|
5381
|
+
identity: candidate.identity,
|
|
5382
|
+
class: candidate.class,
|
|
5383
|
+
...candidate.score === void 0 ? {} : { score: candidate.score },
|
|
5384
|
+
feasible: candidate.feasible,
|
|
5385
|
+
...candidate.blockedUntilMs === void 0 ? {} : { blockedUntil: new Date(candidate.blockedUntilMs).toISOString() },
|
|
5386
|
+
plan: candidate.plan,
|
|
5387
|
+
reasons: [...candidate.reasons]
|
|
5388
|
+
};
|
|
5611
5389
|
}
|
|
5612
|
-
function
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5390
|
+
function collectPoolPick(params) {
|
|
5391
|
+
const { present, missing } = splitMembers(params.pool, params.paths, params.fs);
|
|
5392
|
+
const sticky = readStickyPick(params.usageFs, params.paths.usagePicksFile, params.directory);
|
|
5393
|
+
const ranking = rankPoolFromStore({
|
|
5394
|
+
fs: params.usageFs,
|
|
5395
|
+
paths: params.paths,
|
|
5396
|
+
identities: present,
|
|
5397
|
+
nowMs: params.nowMs,
|
|
5398
|
+
resuming: false,
|
|
5399
|
+
...sticky.sticky === void 0 ? {} : { sticky: sticky.sticky }
|
|
5617
5400
|
});
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5401
|
+
return {
|
|
5402
|
+
pool: params.poolName,
|
|
5403
|
+
directory: params.directory,
|
|
5404
|
+
...ranking.pick === void 0 ? {} : { pick: ranking.pick.identity },
|
|
5405
|
+
candidates: ranking.candidates.map(candidateView),
|
|
5406
|
+
missing: [...missing],
|
|
5407
|
+
...ranking.earliestReturn === void 0 ? {} : { earliestReturn: { identity: ranking.earliestReturn.identity, at: new Date(ranking.earliestReturn.atMs).toISOString() } },
|
|
5408
|
+
...sticky.problem === void 0 ? {} : { stickyProblem: sticky.problem }
|
|
5409
|
+
};
|
|
5622
5410
|
}
|
|
5623
5411
|
|
|
5624
|
-
// src/
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
const pool = readPools(paths)[name];
|
|
5654
|
-
if (pool === void 0) {
|
|
5655
|
-
throw new PoolNotFoundError(name);
|
|
5656
|
-
}
|
|
5657
|
-
return pool;
|
|
5658
|
-
}
|
|
5659
|
-
function writePools(paths, pools) {
|
|
5660
|
-
applyPatch(paths.globalConfigFile, GlobalConfigSchema, { pools }, { defaults: {} });
|
|
5661
|
-
}
|
|
5662
|
-
function addPool(paths, name, identities) {
|
|
5663
|
-
if (!PoolNameSchema.safeParse(name).success) {
|
|
5664
|
-
throw new InvalidPoolNameError(name);
|
|
5665
|
-
}
|
|
5666
|
-
const pools = readPools(paths);
|
|
5667
|
-
if (pools[name] !== void 0) {
|
|
5668
|
-
throw new PoolAlreadyExistsError(name);
|
|
5669
|
-
}
|
|
5670
|
-
const pool = { identities: [...identities] };
|
|
5671
|
-
writePools(paths, { ...pools, [name]: pool });
|
|
5672
|
-
return pool;
|
|
5673
|
-
}
|
|
5674
|
-
function setPool(paths, name, identities) {
|
|
5675
|
-
requirePool(paths, name);
|
|
5676
|
-
const pool = { identities: [...identities] };
|
|
5677
|
-
writePools(paths, { ...readPools(paths), [name]: pool });
|
|
5678
|
-
return pool;
|
|
5679
|
-
}
|
|
5680
|
-
function removePool(paths, name) {
|
|
5681
|
-
requirePool(paths, name);
|
|
5682
|
-
const remaining = Object.fromEntries(Object.entries(readPools(paths)).filter(([existing]) => existing !== name));
|
|
5683
|
-
writePools(paths, remaining);
|
|
5684
|
-
}
|
|
5412
|
+
// src/usage/pickReportSchema.ts
|
|
5413
|
+
import { z as z9 } from "zod";
|
|
5414
|
+
var PoolPickCandidateViewSchema = z9.strictObject({
|
|
5415
|
+
identity: z9.string(),
|
|
5416
|
+
/** In pick order: `scored` has usable quota data, `unknown` has none, `pay-per-use` bills by use, `ineligible` is refused right now. */
|
|
5417
|
+
class: z9.enum(["scored", "unknown", "pay-per-use", "ineligible"]),
|
|
5418
|
+
/** Plan-size-weighted remaining quota per hour until its reset; present for `scored` only. */
|
|
5419
|
+
score: z9.number().optional(),
|
|
5420
|
+
/** False when the five-hour window would run dry, at the observed pace, before it resets. */
|
|
5421
|
+
feasible: z9.boolean(),
|
|
5422
|
+
/** When an `ineligible` member can be used again, as an ISO instant. */
|
|
5423
|
+
blockedUntil: z9.iso.datetime().optional(),
|
|
5424
|
+
plan: PlanClassSchema,
|
|
5425
|
+
/** The facts behind the class and score, in the order they matter. */
|
|
5426
|
+
reasons: z9.array(z9.string())
|
|
5427
|
+
});
|
|
5428
|
+
var PoolPickReportSchema = z9.strictObject({
|
|
5429
|
+
pool: z9.string(),
|
|
5430
|
+
directory: z9.string(),
|
|
5431
|
+
/** The member a launch would run as, absent when every member is refused. */
|
|
5432
|
+
pick: z9.string().optional(),
|
|
5433
|
+
candidates: z9.array(PoolPickCandidateViewSchema),
|
|
5434
|
+
/** Members that name an identity that does not exist; a launch skips them. */
|
|
5435
|
+
missing: z9.array(z9.string()),
|
|
5436
|
+
/** When nothing can be picked, the soonest any member returns. */
|
|
5437
|
+
earliestReturn: z9.strictObject({ identity: z9.string(), at: z9.iso.datetime() }).optional(),
|
|
5438
|
+
/** Why the last-pick record could not be read, when that is the case. */
|
|
5439
|
+
stickyProblem: z9.string().optional()
|
|
5440
|
+
});
|
|
5685
5441
|
|
|
5686
|
-
// src/
|
|
5687
|
-
var
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5442
|
+
// src/headroom/ensure.ts
|
|
5443
|
+
var HEADROOM_START_TIMEOUT_MS = 12e4;
|
|
5444
|
+
var HEADROOM_LAUNCHER_POLL_MS = 250;
|
|
5445
|
+
var HeadroomStartError = class extends CliError {
|
|
5446
|
+
constructor(message) {
|
|
5447
|
+
super(message);
|
|
5448
|
+
this.name = "HeadroomStartError";
|
|
5692
5449
|
}
|
|
5693
|
-
name;
|
|
5694
5450
|
};
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5451
|
+
function ensureHeadroom(params) {
|
|
5452
|
+
const { paths, ports } = params;
|
|
5453
|
+
const deadline = ports.now() + HEADROOM_START_TIMEOUT_MS;
|
|
5454
|
+
let spawnedSupervisor = false;
|
|
5455
|
+
let awaitingAllowlist = false;
|
|
5456
|
+
for (; ; ) {
|
|
5457
|
+
const state = readHeadroomState(ports.fs, paths.headroomStateFile);
|
|
5458
|
+
if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
|
|
5459
|
+
const daemonUp = state.socketPath !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
|
|
5460
|
+
if (daemonUp && state.socketPath !== void 0) {
|
|
5461
|
+
const required = params.requiredAllowlistHash?.();
|
|
5462
|
+
if (required === void 0 || state.allowlistHash === required) {
|
|
5463
|
+
writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
|
|
5464
|
+
return { socketPath: state.socketPath };
|
|
5465
|
+
}
|
|
5466
|
+
const holders = sessionsForSupervisor(ports.fs, paths.headroomSessionsDir, state.supervisorPid).filter((session) => ports.isRunning(session.pid));
|
|
5467
|
+
if (holders.length > 0) {
|
|
5468
|
+
throw new HeadroomStartError(
|
|
5469
|
+
`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.`
|
|
5470
|
+
);
|
|
5471
|
+
}
|
|
5472
|
+
awaitingAllowlist = true;
|
|
5473
|
+
}
|
|
5474
|
+
if (state.lastError !== void 0) {
|
|
5475
|
+
throw new HeadroomStartError(
|
|
5476
|
+
`the headroom daemon reported a fatal error and is not serving: ${state.lastError} (daemon log: ${paths.headroomLogPath})`
|
|
5477
|
+
);
|
|
5478
|
+
}
|
|
5479
|
+
} else if (!spawnedSupervisor) {
|
|
5480
|
+
ports.fs.mkdirp(paths.headroomDir);
|
|
5481
|
+
const created = ports.fs.writeFileExclusive(
|
|
5482
|
+
paths.headroomLockFile,
|
|
5483
|
+
`${JSON.stringify({ pid: params.launcherPid, at: ports.now() })}
|
|
5484
|
+
`
|
|
5485
|
+
);
|
|
5486
|
+
if (created) {
|
|
5487
|
+
ports.spawnSupervisor(paths);
|
|
5488
|
+
spawnedSupervisor = true;
|
|
5489
|
+
} else {
|
|
5490
|
+
const lock = readStartLock(ports.fs, paths.headroomLockFile);
|
|
5491
|
+
if (lock === void 0 || !ports.isRunning(lock.pid)) {
|
|
5492
|
+
ports.fs.removeRecursive(paths.headroomLockFile);
|
|
5493
|
+
continue;
|
|
5494
|
+
}
|
|
5495
|
+
}
|
|
5496
|
+
}
|
|
5497
|
+
if (ports.now() >= deadline) {
|
|
5498
|
+
const lastError = state?.lastError;
|
|
5499
|
+
throw new HeadroomStartError(
|
|
5500
|
+
`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}`
|
|
5501
|
+
);
|
|
5502
|
+
}
|
|
5503
|
+
ports.sleep(HEADROOM_LAUNCHER_POLL_MS);
|
|
5710
5504
|
}
|
|
5711
|
-
attemptedName;
|
|
5712
|
-
};
|
|
5713
|
-
function identityJsonPath(paths, name) {
|
|
5714
|
-
return path15.join(paths.identitiesDir, name, "identity.json");
|
|
5715
5505
|
}
|
|
5716
|
-
|
|
5717
|
-
|
|
5506
|
+
|
|
5507
|
+
// src/headroom/settings.ts
|
|
5508
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
5509
|
+
function settingsOf(config) {
|
|
5510
|
+
return {
|
|
5511
|
+
...config.mode === void 0 ? {} : { mode: config.mode },
|
|
5512
|
+
...config.targetRatio === void 0 ? {} : { targetRatio: config.targetRatio },
|
|
5513
|
+
...config.ccr === void 0 ? {} : { ccr: config.ccr },
|
|
5514
|
+
...config.rolloutChannel === void 0 ? {} : { rolloutChannel: config.rolloutChannel },
|
|
5515
|
+
...config.interceptToolResults === void 0 ? {} : { interceptToolResults: config.interceptToolResults },
|
|
5516
|
+
...config.readMaturation === void 0 ? {} : { readMaturation: config.readMaturation }
|
|
5517
|
+
};
|
|
5718
5518
|
}
|
|
5719
|
-
function
|
|
5720
|
-
return
|
|
5519
|
+
function settingsArgs(settings) {
|
|
5520
|
+
return [
|
|
5521
|
+
...settings.mode === void 0 ? [] : ["--mode", settings.mode],
|
|
5522
|
+
...settings.targetRatio === void 0 ? [] : ["--target-ratio", String(settings.targetRatio)],
|
|
5523
|
+
...settings.ccr === "lossless" ? ["--lossless"] : [],
|
|
5524
|
+
...settings.ccr === "none" ? ["--no-ccr"] : [],
|
|
5525
|
+
...settings.interceptToolResults === true ? ["--intercept-tool-results"] : [],
|
|
5526
|
+
...settings.readMaturation === true ? ["--read-maturation"] : []
|
|
5527
|
+
];
|
|
5721
5528
|
}
|
|
5722
|
-
function
|
|
5723
|
-
|
|
5724
|
-
throw new IdentityAlreadyExistsError(name);
|
|
5725
|
-
}
|
|
5726
|
-
const parsed = IdentitySchema.safeParse({ name, allowAmbientCredential: false });
|
|
5727
|
-
if (!parsed.success) {
|
|
5728
|
-
throw new InvalidIdentityNameError(name);
|
|
5729
|
-
}
|
|
5730
|
-
writeJsonAtomic(identityJsonPath(paths, name), parsed.data);
|
|
5731
|
-
return parsed.data;
|
|
5529
|
+
function settingsEnv(settings) {
|
|
5530
|
+
return settings.rolloutChannel === void 0 ? {} : { HEADROOM_ROLLOUT_CHANNEL: settings.rolloutChannel };
|
|
5732
5531
|
}
|
|
5733
|
-
function
|
|
5734
|
-
|
|
5735
|
-
if (pool !== void 0) {
|
|
5736
|
-
requirePool(paths, pool);
|
|
5737
|
-
} else if (!identityExists(paths, name)) {
|
|
5738
|
-
throw new IdentityNotFoundError(name);
|
|
5739
|
-
}
|
|
5740
|
-
writeTextAtomic(paths.activeIdentityFile, `${name}
|
|
5741
|
-
`);
|
|
5532
|
+
function hashSettings(settings) {
|
|
5533
|
+
return createHash4("sha256").update(JSON.stringify([...settingsArgs(settings), JSON.stringify(settingsEnv(settings))])).digest("hex");
|
|
5742
5534
|
}
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5535
|
+
|
|
5536
|
+
// src/headroom/socket.ts
|
|
5537
|
+
import path13 from "node:path";
|
|
5538
|
+
var GROUP_OR_OTHER_BITS = 63;
|
|
5539
|
+
var OCTAL_RADIX = 8;
|
|
5540
|
+
var MODE_DIGITS = 4;
|
|
5541
|
+
var HEADROOM_SOCKET_SUFFIX = ".sock";
|
|
5542
|
+
var HEADROOM_SOCKET_NAME = /^(\d+)\.sock$/;
|
|
5543
|
+
var UNIX_SOCKET_PATH_MAX_BYTES = 103;
|
|
5544
|
+
var PLATFORMS_WITHOUT_UNIX_SOCKETS = ["win32"];
|
|
5545
|
+
function headroomSocketPath(paths, supervisorPid) {
|
|
5546
|
+
return path13.join(paths.headroomSocketDir, `${String(supervisorPid)}${HEADROOM_SOCKET_SUFFIX}`);
|
|
5749
5547
|
}
|
|
5750
|
-
function
|
|
5751
|
-
return
|
|
5548
|
+
function platformRefusal(platform) {
|
|
5549
|
+
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;
|
|
5752
5550
|
}
|
|
5753
|
-
function
|
|
5754
|
-
|
|
5755
|
-
return
|
|
5756
|
-
} catch (error) {
|
|
5757
|
-
if (error instanceof ConfigValidationError || error instanceof SyntaxError) {
|
|
5758
|
-
return { problem: error.message.replace(/\s*\n\s*/g, " ") };
|
|
5759
|
-
}
|
|
5760
|
-
throw error;
|
|
5551
|
+
function ownerOnlyRefusal(label, target, stat, expected, uid) {
|
|
5552
|
+
if (stat === void 0) {
|
|
5553
|
+
return `the headroom ${label} ${target} does not exist`;
|
|
5761
5554
|
}
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
if (!fs5.existsSync(paths.identitiesDir)) {
|
|
5765
|
-
return [];
|
|
5555
|
+
if (stat.kind === "symlink") {
|
|
5556
|
+
return `the headroom ${label} ${target} is a symlink; it must be the real ${expected === "dir" ? "directory" : "socket"}`;
|
|
5766
5557
|
}
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
const result = [];
|
|
5770
|
-
for (const name of names) {
|
|
5771
|
-
const read = readIdentityForListing(paths, name);
|
|
5772
|
-
if (read === void 0) {
|
|
5773
|
-
continue;
|
|
5774
|
-
}
|
|
5775
|
-
const isActive = name === active;
|
|
5776
|
-
result.push("problem" in read ? { name, isActive, problem: read.problem } : { name, identity: read, isActive });
|
|
5558
|
+
if (stat.kind !== expected) {
|
|
5559
|
+
return `the headroom ${label} ${target} is not a ${expected === "dir" ? "directory" : "socket"}`;
|
|
5777
5560
|
}
|
|
5778
|
-
|
|
5779
|
-
}
|
|
5780
|
-
function setDefaultConfigProfile(paths, identityName, profileName) {
|
|
5781
|
-
if (!identityExists(paths, identityName)) {
|
|
5782
|
-
throw new IdentityNotFoundError(identityName);
|
|
5561
|
+
if (stat.uid !== uid) {
|
|
5562
|
+
return `the headroom ${label} ${target} is owned by uid ${String(stat.uid)}, not by this user (uid ${String(uid)})`;
|
|
5783
5563
|
}
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
});
|
|
5787
|
-
}
|
|
5788
|
-
function setAllowAmbientCredential(paths, identityName, allow) {
|
|
5789
|
-
if (!identityExists(paths, identityName)) {
|
|
5790
|
-
throw new IdentityNotFoundError(identityName);
|
|
5564
|
+
if ((stat.mode & GROUP_OR_OTHER_BITS) !== 0) {
|
|
5565
|
+
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`;
|
|
5791
5566
|
}
|
|
5792
|
-
return
|
|
5793
|
-
allowAmbientCredential: allow
|
|
5794
|
-
});
|
|
5567
|
+
return void 0;
|
|
5795
5568
|
}
|
|
5796
|
-
function
|
|
5797
|
-
const
|
|
5798
|
-
if (
|
|
5799
|
-
|
|
5569
|
+
function prepareHeadroomSocket(fs12, ports, paths, supervisorPid) {
|
|
5570
|
+
const unsupported = platformRefusal(ports.platform);
|
|
5571
|
+
if (unsupported !== void 0) {
|
|
5572
|
+
return { refused: unsupported };
|
|
5800
5573
|
}
|
|
5801
|
-
|
|
5802
|
-
|
|
5574
|
+
const socketPath = headroomSocketPath(paths, supervisorPid);
|
|
5575
|
+
const length = Buffer.byteLength(socketPath, "utf8");
|
|
5576
|
+
if (length > UNIX_SOCKET_PATH_MAX_BYTES) {
|
|
5577
|
+
return {
|
|
5578
|
+
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`
|
|
5579
|
+
};
|
|
5803
5580
|
}
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
throw new UsageError(`Identity "${identityName}" has no credential yet: pass --credential to give it one before choosing its target.`);
|
|
5581
|
+
if (ports.lstat(paths.headroomSocketDir)?.kind === "symlink") {
|
|
5582
|
+
return { refused: `the headroom socket directory ${paths.headroomSocketDir} is a symlink; it must be the real directory` };
|
|
5807
5583
|
}
|
|
5808
|
-
|
|
5809
|
-
const
|
|
5810
|
-
return
|
|
5811
|
-
credential: { sources: [...sources], ...target === void 0 ? {} : { target }, ...cache === void 0 ? {} : { cache } }
|
|
5812
|
-
});
|
|
5813
|
-
}
|
|
5814
|
-
function belongsToIdentity(entry, name) {
|
|
5815
|
-
return entry === name || entry === path15.basename(identityLockPath("", name)) || entry.startsWith(`.${name}.scratch.`) || entry.startsWith(`.${name}.previous.`);
|
|
5584
|
+
fs12.mkdirPrivate(paths.headroomSocketDir);
|
|
5585
|
+
const refusal = ownerOnlyRefusal("socket directory", paths.headroomSocketDir, ports.lstat(paths.headroomSocketDir), "dir", ports.currentUid());
|
|
5586
|
+
return refusal === void 0 ? { socketPath } : { refused: refusal };
|
|
5816
5587
|
}
|
|
5817
|
-
function
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
fs5.rmSync(path15.join(paths.identitiesDir, entry), { recursive: true, force: true });
|
|
5588
|
+
function pruneStaleHeadroomSockets(fs12, paths, isRunning) {
|
|
5589
|
+
const removed = [];
|
|
5590
|
+
for (const name of fs12.readdir(paths.headroomSocketDir)) {
|
|
5591
|
+
const pid = HEADROOM_SOCKET_NAME.exec(name)?.[1];
|
|
5592
|
+
if (pid === void 0 || isRunning(Number.parseInt(pid, 10))) {
|
|
5593
|
+
continue;
|
|
5824
5594
|
}
|
|
5595
|
+
fs12.removeRecursive(path13.join(paths.headroomSocketDir, name));
|
|
5596
|
+
removed.push(name);
|
|
5825
5597
|
}
|
|
5826
|
-
|
|
5827
|
-
fs5.rmSync(paths.activeIdentityFile);
|
|
5828
|
-
}
|
|
5598
|
+
return removed;
|
|
5829
5599
|
}
|
|
5830
5600
|
|
|
5831
|
-
// src/
|
|
5832
|
-
import
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5601
|
+
// src/headroom/source.ts
|
|
5602
|
+
import { z as z10 } from "zod";
|
|
5603
|
+
var GIT_SPEC_RE = /git\+[^@\s]+(?:@([^\s#]+))?/;
|
|
5604
|
+
var COMMIT_SHA_RE = /^[0-9a-f]{40}$/i;
|
|
5605
|
+
var DirectUrlSchema = z10.object({ vcs_info: z10.object({ commit_id: z10.string() }) });
|
|
5606
|
+
function isMovingGitSource(spec) {
|
|
5607
|
+
const match = GIT_SPEC_RE.exec(spec);
|
|
5608
|
+
if (match === null) {
|
|
5609
|
+
return false;
|
|
5610
|
+
}
|
|
5611
|
+
const ref = match[1];
|
|
5612
|
+
return ref === void 0 || !COMMIT_SHA_RE.test(ref);
|
|
5613
|
+
}
|
|
5614
|
+
function pinnedGitCommit(spec) {
|
|
5615
|
+
const ref = GIT_SPEC_RE.exec(spec)?.[1];
|
|
5616
|
+
return ref !== void 0 && COMMIT_SHA_RE.test(ref) ? ref.toLowerCase() : void 0;
|
|
5617
|
+
}
|
|
5837
5618
|
|
|
5838
|
-
// src/
|
|
5839
|
-
|
|
5840
|
-
var SECONDS_PER_MINUTE = 60;
|
|
5841
|
-
var MINUTES_PER_HOUR = 60;
|
|
5842
|
-
var HOURS_PER_DAY = 24;
|
|
5843
|
-
var WARNING_STATUSES = /* @__PURE__ */ new Set(["allowed_warning", "rejected"]);
|
|
5844
|
-
function formatAge(ms) {
|
|
5845
|
-
const minutes = Math.floor(ms / (MS_PER_SECOND2 * SECONDS_PER_MINUTE));
|
|
5846
|
-
if (minutes < MINUTES_PER_HOUR) {
|
|
5847
|
-
return `${String(Math.max(minutes, 0))}m`;
|
|
5848
|
-
}
|
|
5849
|
-
const hours = Math.floor(minutes / MINUTES_PER_HOUR);
|
|
5850
|
-
return hours < HOURS_PER_DAY ? `${String(hours)}h` : `${String(Math.floor(hours / HOURS_PER_DAY))}d`;
|
|
5851
|
-
}
|
|
5852
|
-
function effectiveWindow(window, nowMs) {
|
|
5853
|
-
const resetsAtMs = window.resetsAt === void 0 ? void 0 : Date.parse(window.resetsAt);
|
|
5854
|
-
if (resetsAtMs !== void 0 && resetsAtMs <= nowMs) {
|
|
5855
|
-
return { reset: true, utilization: 0 };
|
|
5856
|
-
}
|
|
5619
|
+
// src/headroom/supervisor.ts
|
|
5620
|
+
function resolveSupervisorConfig(configured) {
|
|
5857
5621
|
return {
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
...window.status === void 0 ? {} : { status: window.status }
|
|
5622
|
+
source: configured.source ?? HEADROOM_DEFAULT_SOURCE,
|
|
5623
|
+
idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES,
|
|
5624
|
+
settings: settingsOf(configured)
|
|
5862
5625
|
};
|
|
5863
5626
|
}
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
{ name: "five-hour", window: unified.fiveHour },
|
|
5873
|
-
{ name: "seven-day", window: unified.sevenDay }
|
|
5874
|
-
];
|
|
5875
|
-
return windows.flatMap(({ name, window }) => {
|
|
5876
|
-
const effective = window === void 0 ? void 0 : effectiveWindow(window, nowMs);
|
|
5877
|
-
if (window === void 0 || effective?.status === void 0 || !WARNING_STATUSES.has(effective.status)) {
|
|
5878
|
-
return [];
|
|
5879
|
-
}
|
|
5880
|
-
const reset = window.resetsAt === void 0 ? "" : `, resets ${window.resetsAt}`;
|
|
5881
|
-
const verdict = effective.status === "rejected" ? "exhausted" : "nearly used";
|
|
5882
|
-
return [`agent-shim: identity ${snapshot.identity}: the ${name} quota is ${verdict}${reset} (last seen ${age} ago)`];
|
|
5883
|
-
});
|
|
5627
|
+
var HEADROOM_BACKOFF_BASE_MS = 500;
|
|
5628
|
+
var HEADROOM_BACKOFF_CAP_MS = 3e4;
|
|
5629
|
+
var HEADROOM_START_RETRY_BUDGET = 5;
|
|
5630
|
+
var HEADROOM_READY_TIMEOUT_MS = 15e3;
|
|
5631
|
+
var HEADROOM_POLL_MS = 1e3;
|
|
5632
|
+
var MS_PER_MINUTE2 = 6e4;
|
|
5633
|
+
function backoffForAttempt(attempt) {
|
|
5634
|
+
return Math.min(HEADROOM_BACKOFF_CAP_MS, HEADROOM_BACKOFF_BASE_MS * 2 ** (attempt - 1));
|
|
5884
5635
|
}
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
}
|
|
5893
|
-
|
|
5894
|
-
|
|
5636
|
+
function versionSatisfies(version, source) {
|
|
5637
|
+
if (version === void 0) {
|
|
5638
|
+
return false;
|
|
5639
|
+
}
|
|
5640
|
+
const specifier = /==|>=|<=|!=|>|<|~=/.exec(source);
|
|
5641
|
+
if (specifier === null) {
|
|
5642
|
+
return true;
|
|
5643
|
+
}
|
|
5644
|
+
const installed = parseVersion(version);
|
|
5645
|
+
const wanted = parseVersion(source.slice(specifier.index + specifier[0].length));
|
|
5646
|
+
if (installed === void 0 || wanted === void 0) {
|
|
5647
|
+
return true;
|
|
5648
|
+
}
|
|
5649
|
+
const cmp = compareVersions(installed, wanted);
|
|
5650
|
+
switch (specifier[0]) {
|
|
5651
|
+
case "==":
|
|
5652
|
+
return cmp === 0;
|
|
5653
|
+
case "!=":
|
|
5654
|
+
return cmp !== 0;
|
|
5655
|
+
case ">=":
|
|
5656
|
+
return cmp >= 0;
|
|
5657
|
+
case "<=":
|
|
5658
|
+
return cmp <= 0;
|
|
5659
|
+
case ">":
|
|
5660
|
+
return cmp > 0;
|
|
5661
|
+
case "<":
|
|
5662
|
+
return cmp < 0;
|
|
5663
|
+
case "~=":
|
|
5664
|
+
return compareVersions(installed.slice(0, Math.max(0, wanted.length - 1)), wanted.slice(0, Math.max(0, wanted.length - 1))) === 0 && cmp >= 0;
|
|
5665
|
+
default:
|
|
5666
|
+
return true;
|
|
5667
|
+
}
|
|
5895
5668
|
}
|
|
5896
|
-
function
|
|
5897
|
-
|
|
5669
|
+
function parseVersion(input) {
|
|
5670
|
+
const match = /(\d+)(\.\d+)*/.exec(input);
|
|
5671
|
+
if (match === null) {
|
|
5898
5672
|
return void 0;
|
|
5899
5673
|
}
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5674
|
+
return match[0].split(".").map((part) => Number.parseInt(part, 10));
|
|
5675
|
+
}
|
|
5676
|
+
function compareVersions(a, b) {
|
|
5677
|
+
for (let index = 0; index < Math.max(a.length, b.length); index += 1) {
|
|
5678
|
+
const left = a[index] ?? 0;
|
|
5679
|
+
const right = b[index] ?? 0;
|
|
5680
|
+
if (left !== right) {
|
|
5681
|
+
return left - right;
|
|
5682
|
+
}
|
|
5903
5683
|
}
|
|
5904
|
-
return
|
|
5684
|
+
return 0;
|
|
5905
5685
|
}
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5686
|
+
var HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
|
|
5687
|
+
async function runSupervisor(initialConfig, ports, options = {}) {
|
|
5688
|
+
const { fs: fs12, paths } = ports;
|
|
5689
|
+
let config = initialConfig;
|
|
5690
|
+
const previousState = readHeadroomState(fs12, paths.headroomStateFile);
|
|
5691
|
+
if (previousState?.supervisorPid !== void 0 && previousState.supervisorPid !== ports.ownPid && ports.isRunning(previousState.supervisorPid)) {
|
|
5692
|
+
ports.log(`agent-shim headroom supervisor ${String(ports.ownPid)}: supervisor ${String(previousState.supervisorPid)} already owns the daemon; exiting`);
|
|
5693
|
+
return 0;
|
|
5694
|
+
}
|
|
5695
|
+
let installedSource = previousState?.installedSource;
|
|
5696
|
+
const ownsState = () => {
|
|
5697
|
+
const owner = readHeadroomState(fs12, paths.headroomStateFile)?.supervisorPid;
|
|
5698
|
+
return owner === void 0 || owner === ports.ownPid || !ports.isRunning(owner);
|
|
5699
|
+
};
|
|
5700
|
+
const writeOwnState = (state) => {
|
|
5701
|
+
if (!ownsState()) {
|
|
5702
|
+
return false;
|
|
5703
|
+
}
|
|
5704
|
+
writeHeadroomState(fs12, paths.headroomStateFile, state);
|
|
5705
|
+
return true;
|
|
5706
|
+
};
|
|
5707
|
+
const fail = (message) => {
|
|
5708
|
+
if (writeOwnState({ lastError: message })) {
|
|
5709
|
+
fs12.removeRecursive(paths.headroomLockFile);
|
|
5710
|
+
}
|
|
5711
|
+
ports.log(`agent-shim headroom supervisor: ${message}`);
|
|
5712
|
+
return 1;
|
|
5713
|
+
};
|
|
5714
|
+
const ensureInstalled = () => {
|
|
5715
|
+
const version2 = ports.headroomVersion();
|
|
5716
|
+
const versionOk = version2 !== void 0 && versionSatisfies(version2, config.source);
|
|
5717
|
+
const pinned = pinnedGitCommit(config.source);
|
|
5718
|
+
const commitOk = pinned === void 0 || ports.installedCommit() === pinned;
|
|
5719
|
+
if (versionOk && commitOk && (installedSource === void 0 || installedSource === config.source)) {
|
|
5720
|
+
installedSource = config.source;
|
|
5721
|
+
return true;
|
|
5722
|
+
}
|
|
5723
|
+
ports.log(`agent-shim headroom supervisor: installing headroom from ${config.source}`);
|
|
5724
|
+
const result = ports.install(config.source);
|
|
5725
|
+
if (!result.ok) {
|
|
5726
|
+
return false;
|
|
5727
|
+
}
|
|
5728
|
+
installedSource = config.source;
|
|
5729
|
+
return true;
|
|
5730
|
+
};
|
|
5731
|
+
const socket = prepareHeadroomSocket(fs12, ports.socketTrust, paths, ports.ownPid);
|
|
5732
|
+
if (socket.refused !== void 0) {
|
|
5733
|
+
return fail(socket.refused);
|
|
5734
|
+
}
|
|
5735
|
+
const { socketPath } = socket;
|
|
5736
|
+
pruneStaleHeadroomSockets(fs12, paths, ports.isRunning);
|
|
5737
|
+
if (!ensureInstalled()) {
|
|
5738
|
+
return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
|
|
5739
|
+
}
|
|
5740
|
+
const version = ports.headroomVersion() ?? "unknown";
|
|
5741
|
+
writeOwnState({ supervisorPid: ports.ownPid, version, installedSource });
|
|
5742
|
+
ports.log(`agent-shim headroom supervisor ${String(ports.ownPid)}: managing headroom on allowlist [${allowlistOf(ports).join(", ")}]`);
|
|
5743
|
+
const orphanPid = previousState?.headroomPid;
|
|
5744
|
+
if (orphanPid !== void 0 && ports.isRunning(orphanPid)) {
|
|
5745
|
+
ports.log(`agent-shim headroom supervisor: stopping daemon pid ${String(orphanPid)} left behind by the previous supervisor`);
|
|
5746
|
+
ports.stopProcess(orphanPid);
|
|
5747
|
+
}
|
|
5748
|
+
let headroomPid;
|
|
5749
|
+
let runningHash;
|
|
5750
|
+
let runningSettingsHash;
|
|
5751
|
+
let consecutiveFailures = 0;
|
|
5752
|
+
let idleSince;
|
|
5753
|
+
let superseded = false;
|
|
5754
|
+
let ticks = 0;
|
|
5755
|
+
for (; ; ) {
|
|
5756
|
+
if (options.tickLimit !== void 0 && ticks >= options.tickLimit) {
|
|
5757
|
+
return HEADROOM_SUPERVISOR_STILL_RUNNING;
|
|
5758
|
+
}
|
|
5759
|
+
ticks += 1;
|
|
5760
|
+
config = ports.readConfig();
|
|
5761
|
+
const settingsHash = hashSettings(config.settings);
|
|
5762
|
+
const allowlist = allowlistOf(ports);
|
|
5763
|
+
const allowlistHash = hashAllowlist(allowlist);
|
|
5764
|
+
pruneDeadSessions(fs12, paths.headroomSessionsDir, ports.isRunning);
|
|
5765
|
+
const ownsNow = ownsState();
|
|
5766
|
+
if (!ownsNow && !superseded) {
|
|
5767
|
+
ports.log("agent-shim headroom supervisor: another supervisor now owns the state file; serving this daemon's own sessions until they end");
|
|
5768
|
+
}
|
|
5769
|
+
superseded = !ownsNow;
|
|
5770
|
+
const crashed = headroomPid !== void 0 && !ports.isRunning(headroomPid);
|
|
5771
|
+
if (headroomPid === void 0 || crashed) {
|
|
5772
|
+
if (crashed) {
|
|
5773
|
+
ports.log(`agent-shim headroom supervisor: headroom pid ${String(headroomPid)} died`);
|
|
5774
|
+
headroomPid = void 0;
|
|
5775
|
+
runningHash = void 0;
|
|
5776
|
+
runningSettingsHash = void 0;
|
|
5777
|
+
writeOwnState({
|
|
5778
|
+
supervisorPid: ports.ownPid,
|
|
5779
|
+
version,
|
|
5780
|
+
installedSource
|
|
5781
|
+
});
|
|
5782
|
+
}
|
|
5783
|
+
if (consecutiveFailures >= HEADROOM_START_RETRY_BUDGET) {
|
|
5784
|
+
return fail(`headroom failed to become ready ${String(consecutiveFailures)} times in a row; giving up`);
|
|
5785
|
+
}
|
|
5786
|
+
if (installedSource !== config.source && !ensureInstalled()) {
|
|
5787
|
+
return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
|
|
5788
|
+
}
|
|
5789
|
+
const pid = ports.spawnHeadroom(socketPath, allowlist, config.settings);
|
|
5790
|
+
const ready = await waitUntilReady(ports, socketPath);
|
|
5791
|
+
if (ready) {
|
|
5792
|
+
consecutiveFailures = 0;
|
|
5793
|
+
headroomPid = pid;
|
|
5794
|
+
runningHash = allowlistHash;
|
|
5795
|
+
runningSettingsHash = settingsHash;
|
|
5796
|
+
const recorded = writeOwnState({
|
|
5797
|
+
supervisorPid: ports.ownPid,
|
|
5798
|
+
headroomPid: pid,
|
|
5799
|
+
socketPath,
|
|
5800
|
+
version,
|
|
5801
|
+
allowlistHash,
|
|
5802
|
+
settingsHash,
|
|
5803
|
+
installedSource: config.source
|
|
5804
|
+
});
|
|
5805
|
+
if (recorded) {
|
|
5806
|
+
fs12.removeRecursive(paths.headroomLockFile);
|
|
5807
|
+
}
|
|
5808
|
+
idleSince = void 0;
|
|
5809
|
+
ports.log(`agent-shim headroom supervisor: headroom pid ${String(pid)} ready on unix socket ${socketPath}`);
|
|
5810
|
+
} else {
|
|
5811
|
+
ports.stopProcess(pid);
|
|
5812
|
+
consecutiveFailures += 1;
|
|
5813
|
+
ports.log(
|
|
5814
|
+
`agent-shim headroom supervisor: headroom did not become ready on unix socket ${socketPath} (attempt ${String(consecutiveFailures)} of ${String(HEADROOM_START_RETRY_BUDGET)})`
|
|
5815
|
+
);
|
|
5816
|
+
await ports.sleep(backoffForAttempt(consecutiveFailures));
|
|
5817
|
+
continue;
|
|
5818
|
+
}
|
|
5819
|
+
} else {
|
|
5820
|
+
if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
|
|
5821
|
+
if (sessionsForSupervisor(fs12, paths.headroomSessionsDir, ports.ownPid).length === 0) {
|
|
5822
|
+
ports.log("agent-shim headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
|
|
5823
|
+
ports.stopProcess(headroomPid);
|
|
5824
|
+
headroomPid = void 0;
|
|
5825
|
+
runningHash = void 0;
|
|
5826
|
+
runningSettingsHash = void 0;
|
|
5827
|
+
continue;
|
|
5828
|
+
}
|
|
5829
|
+
}
|
|
5830
|
+
const sessions = sessionsForSupervisor(fs12, paths.headroomSessionsDir, ports.ownPid);
|
|
5831
|
+
if (sessions.length === 0) {
|
|
5832
|
+
idleSince ??= ports.now();
|
|
5833
|
+
const idleMs = ports.now() - idleSince;
|
|
5834
|
+
if (idleMs >= config.idleShutdownMinutes * MS_PER_MINUTE2) {
|
|
5835
|
+
ports.log(
|
|
5836
|
+
`agent-shim headroom supervisor: no sessions for ${String(config.idleShutdownMinutes)} minute(s); stopping headroom and exiting`
|
|
5837
|
+
);
|
|
5838
|
+
ports.stopProcess(headroomPid);
|
|
5839
|
+
writeOwnState({ version });
|
|
5840
|
+
return 0;
|
|
5841
|
+
}
|
|
5842
|
+
} else {
|
|
5843
|
+
idleSince = void 0;
|
|
5844
|
+
}
|
|
5845
|
+
}
|
|
5846
|
+
await ports.sleep(HEADROOM_POLL_MS);
|
|
5910
5847
|
}
|
|
5911
|
-
return limit === void 0 || now - entry.fetchedAt < limit;
|
|
5912
5848
|
}
|
|
5913
|
-
function
|
|
5914
|
-
|
|
5849
|
+
function currentHeadroomAllowlist(fs12, paths) {
|
|
5850
|
+
const providers = readAllProviders(fs12, paths.providersDir).map((entry) => entry.provider);
|
|
5851
|
+
return headroomAllowlist(headroomUpstreams(providers, frontDoorOrigin(readFrontDoorState(fs12, paths.frontdoorStateFile))));
|
|
5915
5852
|
}
|
|
5916
|
-
function
|
|
5917
|
-
|
|
5918
|
-
let entry;
|
|
5919
|
-
try {
|
|
5920
|
-
entry = params.port.read(store, params.owner);
|
|
5921
|
-
} catch (error) {
|
|
5922
|
-
return { store, status: "unreadable", reason: error instanceof Error ? error.message : String(error) };
|
|
5923
|
-
}
|
|
5924
|
-
if (entry === void 0) {
|
|
5925
|
-
return { store, status: "empty" };
|
|
5926
|
-
}
|
|
5927
|
-
const limit = ttlMs(params.cache);
|
|
5928
|
-
const ageMs = cacheAgeMs(entry, params.nowMs);
|
|
5929
|
-
return {
|
|
5930
|
-
store,
|
|
5931
|
-
status: isFresh(entry, params.cache, params.nowMs) ? "fresh" : "expired",
|
|
5932
|
-
ageMs,
|
|
5933
|
-
...limit === void 0 ? {} : { expiresInMs: limit - ageMs }
|
|
5934
|
-
};
|
|
5853
|
+
function allowlistOf(ports) {
|
|
5854
|
+
return currentHeadroomAllowlist(ports.fs, ports.paths);
|
|
5935
5855
|
}
|
|
5936
|
-
function
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
const age = `cached ${formatAge(state.ageMs)} ago`;
|
|
5947
|
-
if (state.expiresInMs === void 0) {
|
|
5948
|
-
return `${age}, no expiry`;
|
|
5856
|
+
async function waitUntilReady(ports, socketPath) {
|
|
5857
|
+
const deadline = ports.now() + HEADROOM_READY_TIMEOUT_MS;
|
|
5858
|
+
for (; ; ) {
|
|
5859
|
+
if (await ports.ready(socketPath)) {
|
|
5860
|
+
return true;
|
|
5861
|
+
}
|
|
5862
|
+
if (ports.now() >= deadline) {
|
|
5863
|
+
return false;
|
|
5864
|
+
}
|
|
5865
|
+
await ports.sleep(HEADROOM_POLL_MS);
|
|
5949
5866
|
}
|
|
5950
|
-
return state.status === "fresh" ? `${age}, expires in ${formatAge(state.expiresInMs)}` : `${age}, expired ${formatAge(-state.expiresInMs)} ago`;
|
|
5951
5867
|
}
|
|
5952
5868
|
|
|
5953
|
-
// src/
|
|
5954
|
-
var
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5869
|
+
// src/launcher/guard.ts
|
|
5870
|
+
var AMBIENT_CREDENTIAL_VARS = [
|
|
5871
|
+
"ANTHROPIC_API_KEY",
|
|
5872
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
5873
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
5874
|
+
"CLAUDE_CODE_USE_BEDROCK",
|
|
5875
|
+
"CLAUDE_CODE_USE_VERTEX",
|
|
5876
|
+
"CLAUDE_CODE_USE_FOUNDRY"
|
|
5877
|
+
];
|
|
5878
|
+
function detectAmbientCredential(env, injected) {
|
|
5879
|
+
for (const variable of AMBIENT_CREDENTIAL_VARS) {
|
|
5880
|
+
const value = env[variable];
|
|
5881
|
+
if (value === void 0 || value === "") {
|
|
5882
|
+
continue;
|
|
5883
|
+
}
|
|
5884
|
+
if (injected?.variable === variable && value === injected.token) {
|
|
5885
|
+
continue;
|
|
5886
|
+
}
|
|
5887
|
+
return { variable };
|
|
5966
5888
|
}
|
|
5967
|
-
|
|
5968
|
-
return ["security", "find-generic-password", "-s", source.keychain.service, ...account === void 0 ? [] : ["-a", account], "-w"];
|
|
5889
|
+
return void 0;
|
|
5969
5890
|
}
|
|
5970
|
-
function
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5891
|
+
function formatAmbientCredentialGuardMessage(variable, identityName) {
|
|
5892
|
+
const identitySetCommand = identityName === void 0 ? "agent-shim identity set <name> --allow-ambient-credential" : `agent-shim identity set ${identityName} --allow-ambient-credential`;
|
|
5893
|
+
return [
|
|
5894
|
+
`error: ${variable} is set in the environment. This identity's isolated`,
|
|
5895
|
+
"credential would be bypassed \u2014 every identity authenticates as this same key",
|
|
5896
|
+
"while it's set. Unset it, or if this is deliberate, opt in per-launch with",
|
|
5897
|
+
"AGENT_SHIM_ALLOW_AMBIENT_CREDENTIAL=1, or persistently for this identity with",
|
|
5898
|
+
`\`${identitySetCommand}\`.`
|
|
5899
|
+
].join("\n");
|
|
5978
5900
|
}
|
|
5979
|
-
function
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
if ("file" in source) {
|
|
5984
|
-
return { kind: "file", path: source.file };
|
|
5985
|
-
}
|
|
5986
|
-
if ("command" in source) {
|
|
5987
|
-
return { kind: "command", program: source.command[0] };
|
|
5988
|
-
}
|
|
5989
|
-
if ("op" in source) {
|
|
5990
|
-
return { kind: "op", reference: source.op };
|
|
5901
|
+
function evaluateAmbientCredentialGuard(params) {
|
|
5902
|
+
const detected = detectAmbientCredential(params.env, params.injectedCredential);
|
|
5903
|
+
if (detected === void 0) {
|
|
5904
|
+
return { ok: true };
|
|
5991
5905
|
}
|
|
5992
|
-
if (
|
|
5993
|
-
return {
|
|
5994
|
-
kind: "keychain",
|
|
5995
|
-
service: source.keychain.service,
|
|
5996
|
-
...source.keychain.account === void 0 ? {} : { account: source.keychain.account }
|
|
5997
|
-
};
|
|
5906
|
+
if (params.allowAmbientCredentialOverride || params.allowAmbientCredential || params.providerSelected === true) {
|
|
5907
|
+
return { ok: true };
|
|
5998
5908
|
}
|
|
5999
|
-
return { kind: "literal" };
|
|
6000
|
-
}
|
|
6001
|
-
function summariseCredential(credential) {
|
|
6002
5909
|
return {
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
5910
|
+
ok: false,
|
|
5911
|
+
variable: detected.variable,
|
|
5912
|
+
message: formatAmbientCredentialGuardMessage(detected.variable, params.identityName)
|
|
6006
5913
|
};
|
|
6007
5914
|
}
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
return `op ${summary.reference}`;
|
|
6018
|
-
case "keychain":
|
|
6019
|
-
return `keychain ${summary.service}${summary.account === void 0 ? "" : ` (account ${summary.account})`}`;
|
|
6020
|
-
case "literal":
|
|
6021
|
-
return "literal (non-secret placeholder)";
|
|
6022
|
-
default:
|
|
6023
|
-
return summary;
|
|
5915
|
+
|
|
5916
|
+
// src/paths.ts
|
|
5917
|
+
import fs3 from "node:fs";
|
|
5918
|
+
import os from "node:os";
|
|
5919
|
+
import path14 from "node:path";
|
|
5920
|
+
function resolveAgentShimHome(env = process.env, home = os.homedir(), isDirectory = isExistingDirectory) {
|
|
5921
|
+
const fromEnv = env.AGENT_SHIM_HOME;
|
|
5922
|
+
if (fromEnv !== void 0 && fromEnv !== "") {
|
|
5923
|
+
return fromEnv;
|
|
6024
5924
|
}
|
|
5925
|
+
const current = path14.join(home, HOME_DIRNAME);
|
|
5926
|
+
const legacy = path14.join(home, LEGACY_HOME_DIRNAME);
|
|
5927
|
+
return !isDirectory(current) && isDirectory(legacy) ? legacy : current;
|
|
6025
5928
|
}
|
|
6026
|
-
function
|
|
6027
|
-
return
|
|
5929
|
+
function isExistingDirectory(candidate) {
|
|
5930
|
+
return fs3.statSync(candidate, { throwIfNoEntry: false })?.isDirectory() ?? false;
|
|
6028
5931
|
}
|
|
6029
|
-
function
|
|
6030
|
-
const
|
|
6031
|
-
|
|
5932
|
+
function resolveClaudeHome() {
|
|
5933
|
+
const fromEnv = process.env.AGENT_SHIM_CLAUDE_HOME;
|
|
5934
|
+
if (fromEnv !== void 0 && fromEnv !== "") {
|
|
5935
|
+
return fromEnv;
|
|
5936
|
+
}
|
|
5937
|
+
return path14.join(os.homedir(), ".claude");
|
|
6032
5938
|
}
|
|
6033
|
-
function
|
|
6034
|
-
return
|
|
5939
|
+
function buildLayoutPaths(root) {
|
|
5940
|
+
return {
|
|
5941
|
+
root,
|
|
5942
|
+
identitiesDir: path14.join(root, "identities"),
|
|
5943
|
+
configProfilesDir: path14.join(root, "config-profiles"),
|
|
5944
|
+
providersDir: path14.join(root, "providers"),
|
|
5945
|
+
directoryRulesFile: path14.join(root, "directory-rules.json"),
|
|
5946
|
+
activeIdentityFile: path14.join(root, "active-identity"),
|
|
5947
|
+
globalConfigFile: path14.join(root, "config.json"),
|
|
5948
|
+
categoriesLocalFile: path14.join(root, "categories.local.json"),
|
|
5949
|
+
claudeShimFile: path14.join(root, "claude-shim.json"),
|
|
5950
|
+
headroomDir: path14.join(root, "headroom"),
|
|
5951
|
+
headroomStateFile: path14.join(root, "headroom", `state.v${String(HEADROOM_STATE_SCHEMA_VERSION)}.json`),
|
|
5952
|
+
headroomSocketDir: path14.join(root, "headroom", "run"),
|
|
5953
|
+
headroomLockFile: path14.join(root, "headroom", "start.lock"),
|
|
5954
|
+
headroomSessionsDir: path14.join(root, "headroom", "sessions"),
|
|
5955
|
+
logsDir: path14.join(root, "logs"),
|
|
5956
|
+
headroomLogPath: path14.join(root, "logs", "headroom.log"),
|
|
5957
|
+
frontdoorDir: path14.join(root, "frontdoor"),
|
|
5958
|
+
frontdoorCaDir: path14.join(root, "frontdoor", "ca"),
|
|
5959
|
+
frontdoorCaCertFile: path14.join(root, "frontdoor", "ca", "ca.pem"),
|
|
5960
|
+
frontdoorCaKeyFile: path14.join(root, "frontdoor", "ca", "ca.key"),
|
|
5961
|
+
frontdoorCaBundlesDir: path14.join(root, "frontdoor", "ca", "bundles"),
|
|
5962
|
+
frontdoorStateFile: path14.join(root, "frontdoor", "state.json"),
|
|
5963
|
+
frontdoorLockFile: path14.join(root, "frontdoor", "start.lock"),
|
|
5964
|
+
frontdoorControlTokenFile: path14.join(root, "frontdoor", "control-token"),
|
|
5965
|
+
frontdoorSessionsDir: path14.join(root, "frontdoor", "sessions"),
|
|
5966
|
+
frontdoorLogPath: path14.join(root, "logs", "frontdoor.log"),
|
|
5967
|
+
usageDir: path14.join(root, "usage"),
|
|
5968
|
+
usageLogDir: path14.join(root, "usage", "log"),
|
|
5969
|
+
usageSnapshotsDir: path14.join(root, "usage", "snapshots"),
|
|
5970
|
+
usagePicksFile: path14.join(root, "usage", "picks.json")
|
|
5971
|
+
};
|
|
6035
5972
|
}
|
|
6036
|
-
function
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
}
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
`is readable or writable by group or others (mode ${(read.mode & PERMISSION_BITS).toString(OCTAL_RADIX2)}); run \`chmod 600 ${source.file}\``,
|
|
6053
|
-
true
|
|
6054
|
-
);
|
|
6055
|
-
}
|
|
6056
|
-
const token2 = read.content.trim();
|
|
6057
|
-
return token2 === "" ? none("is empty") : { ok: true, token: token2 };
|
|
5973
|
+
function resolveLayoutPaths() {
|
|
5974
|
+
return buildLayoutPaths(resolveAgentShimHome());
|
|
5975
|
+
}
|
|
5976
|
+
|
|
5977
|
+
// src/identityStore.ts
|
|
5978
|
+
import fs5 from "node:fs";
|
|
5979
|
+
import path16 from "node:path";
|
|
5980
|
+
|
|
5981
|
+
// src/configProfilesStore.ts
|
|
5982
|
+
import fs4 from "node:fs";
|
|
5983
|
+
import path15 from "node:path";
|
|
5984
|
+
var ProfileNotFoundError = class extends CliError {
|
|
5985
|
+
constructor(profileName) {
|
|
5986
|
+
super(`No configuration profile named "${profileName}". Run \`agent-shim profile add ${profileName}\` first.`);
|
|
5987
|
+
this.profileName = profileName;
|
|
5988
|
+
this.name = "ProfileNotFoundError";
|
|
6058
5989
|
}
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
5990
|
+
profileName;
|
|
5991
|
+
};
|
|
5992
|
+
var ProfileAlreadyExistsError = class extends CliError {
|
|
5993
|
+
constructor(profileName) {
|
|
5994
|
+
super(`A configuration profile named "${profileName}" already exists.`);
|
|
5995
|
+
this.profileName = profileName;
|
|
5996
|
+
this.name = "ProfileAlreadyExistsError";
|
|
6063
5997
|
}
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
5998
|
+
profileName;
|
|
5999
|
+
};
|
|
6000
|
+
var InvalidCategoryNameError = class extends CliError {
|
|
6001
|
+
constructor(categoryName) {
|
|
6002
|
+
super(`"${categoryName}" is not a category a configuration profile may toggle (runtime, history, knowledge, settings).`);
|
|
6003
|
+
this.categoryName = categoryName;
|
|
6004
|
+
this.name = "InvalidCategoryNameError";
|
|
6068
6005
|
}
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6006
|
+
categoryName;
|
|
6007
|
+
};
|
|
6008
|
+
function profileJsonPath(paths, name) {
|
|
6009
|
+
return path15.join(paths.configProfilesDir, `${name}.json`);
|
|
6010
|
+
}
|
|
6011
|
+
function profileExists(paths, name) {
|
|
6012
|
+
return fs4.existsSync(profileJsonPath(paths, name));
|
|
6013
|
+
}
|
|
6014
|
+
function requireProfileExists(paths, name) {
|
|
6015
|
+
if (!profileExists(paths, name)) {
|
|
6016
|
+
throw new ProfileNotFoundError(name);
|
|
6073
6017
|
}
|
|
6074
|
-
const token = result.stdout.trim();
|
|
6075
|
-
return token === "" ? none("printed no token") : { ok: true, token };
|
|
6076
6018
|
}
|
|
6077
|
-
function
|
|
6078
|
-
|
|
6079
|
-
return kind === "provider" ? `agent-shim credential warm --provider ${name ?? ""}` : `agent-shim credential warm ${name ?? ""}`;
|
|
6019
|
+
function readProfile(paths, name) {
|
|
6020
|
+
return readJson(profileJsonPath(paths, name), ConfigProfileSchema);
|
|
6080
6021
|
}
|
|
6081
|
-
function
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
const cache = cacheBlock === void 0 ? void 0 : params.port.cache;
|
|
6085
|
-
const store = cacheBlock === void 0 || cache === void 0 ? void 0 : effectiveStore(cacheBlock, cache.platform);
|
|
6086
|
-
if (cacheBlock !== void 0 && cache !== void 0 && store !== void 0 && params.refresh !== true) {
|
|
6087
|
-
const entry = cache.port.read(store, params.subject);
|
|
6088
|
-
if (entry !== void 0 && isFresh(entry, cacheBlock, cache.now())) {
|
|
6089
|
-
return { ok: true, credential: { target, token: entry.token, source: entry.source, warnings: [], cachedAt: entry.fetchedAt } };
|
|
6090
|
-
}
|
|
6022
|
+
function createProfile(paths, name, extendsList, description) {
|
|
6023
|
+
if (profileExists(paths, name)) {
|
|
6024
|
+
throw new ProfileAlreadyExistsError(name);
|
|
6091
6025
|
}
|
|
6092
|
-
const
|
|
6093
|
-
const
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6026
|
+
const filePath = profileJsonPath(paths, name);
|
|
6027
|
+
const parsed = ConfigProfileSchema.safeParse({
|
|
6028
|
+
...description === void 0 ? {} : { description },
|
|
6029
|
+
...extendsList !== void 0 && extendsList.length > 0 ? { extends: [...extendsList] } : {}
|
|
6030
|
+
});
|
|
6031
|
+
if (!parsed.success) {
|
|
6032
|
+
throw new ConfigValidationError(filePath, parsed.error.issues);
|
|
6033
|
+
}
|
|
6034
|
+
writeJsonAtomic(filePath, parsed.data);
|
|
6035
|
+
return parsed.data;
|
|
6036
|
+
}
|
|
6037
|
+
function listProfiles(paths) {
|
|
6038
|
+
if (!fs4.existsSync(paths.configProfilesDir)) {
|
|
6039
|
+
return [];
|
|
6040
|
+
}
|
|
6041
|
+
const names = fs4.readdirSync(paths.configProfilesDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length)).sort();
|
|
6042
|
+
const result = [];
|
|
6043
|
+
for (const name of names) {
|
|
6044
|
+
const profile = readProfile(paths, name);
|
|
6045
|
+
if (profile !== void 0) {
|
|
6046
|
+
result.push({ name, profile });
|
|
6101
6047
|
}
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6048
|
+
}
|
|
6049
|
+
return result;
|
|
6050
|
+
}
|
|
6051
|
+
function globalConfigPath(paths) {
|
|
6052
|
+
return paths.globalConfigFile;
|
|
6053
|
+
}
|
|
6054
|
+
function readGlobalConfig(paths) {
|
|
6055
|
+
return readJson(globalConfigPath(paths), GlobalConfigSchema);
|
|
6056
|
+
}
|
|
6057
|
+
function setGlobalDefaultProfile(paths, name) {
|
|
6058
|
+
return applyPatch(
|
|
6059
|
+
globalConfigPath(paths),
|
|
6060
|
+
GlobalConfigSchema,
|
|
6061
|
+
{ defaultConfigProfile: name },
|
|
6062
|
+
{ defaults: {} }
|
|
6063
|
+
);
|
|
6064
|
+
}
|
|
6065
|
+
function validateCategoryNames(patch) {
|
|
6066
|
+
for (const key of Object.keys(patch)) {
|
|
6067
|
+
if (!isOverridableCategory(key)) {
|
|
6068
|
+
throw new InvalidCategoryNameError(key);
|
|
6106
6069
|
}
|
|
6107
6070
|
}
|
|
6108
|
-
const hint = cacheBlock === void 0 ? "" : `; fill the cache from a terminal with \`${warmCommandFor(params.subject)}\``;
|
|
6109
|
-
return { ok: false, message: `agent-shim: ${params.subject} has no usable credential: ${attempts.join("; ")}${hint}` };
|
|
6110
6071
|
}
|
|
6111
|
-
function
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
};
|
|
6072
|
+
function setProfileCategories(paths, name, patch) {
|
|
6073
|
+
requireProfileExists(paths, name);
|
|
6074
|
+
const expandedPatch = expandAllCategoryKey(patch);
|
|
6075
|
+
validateCategoryNames(expandedPatch);
|
|
6076
|
+
const existing = readProfile(paths, name) ?? {};
|
|
6077
|
+
const mergedCategories = { ...existing.categories, ...expandedPatch };
|
|
6078
|
+
return applyPatch(profileJsonPath(paths, name), ConfigProfileSchema, { categories: mergedCategories });
|
|
6079
|
+
}
|
|
6080
|
+
function setProfileEntries(paths, name, patch) {
|
|
6081
|
+
requireProfileExists(paths, name);
|
|
6082
|
+
const existing = readProfile(paths, name) ?? {};
|
|
6083
|
+
const mergedEntries = { ...existing.entries, ...patch };
|
|
6084
|
+
return applyPatch(profileJsonPath(paths, name), ConfigProfileSchema, { entries: mergedEntries });
|
|
6085
|
+
}
|
|
6086
|
+
function setProfileLaunchFlags(paths, name, patch) {
|
|
6087
|
+
requireProfileExists(paths, name);
|
|
6088
|
+
const existing = readProfile(paths, name) ?? {};
|
|
6089
|
+
const mergedLaunch = { ...existing.launch, ...patch };
|
|
6090
|
+
return applyPatch(profileJsonPath(paths, name), ConfigProfileSchema, {
|
|
6091
|
+
// Read as `unknown` so the check sees the `undefined` a cleared key holds, which LaunchFlags' optional properties hide from the value type.
|
|
6092
|
+
launch: Object.values(mergedLaunch).every((value) => value === void 0) ? void 0 : mergedLaunch
|
|
6093
|
+
});
|
|
6094
|
+
}
|
|
6095
|
+
function setProfileMetadata(paths, name, patch) {
|
|
6096
|
+
requireProfileExists(paths, name);
|
|
6097
|
+
return applyPatch(profileJsonPath(paths, name), ConfigProfileSchema, {
|
|
6098
|
+
...patch.extends === void 0 ? {} : { extends: patch.extends.length === 0 ? void 0 : [...patch.extends] },
|
|
6099
|
+
...patch.description === void 0 ? {} : { description: patch.description === false ? void 0 : patch.description }
|
|
6100
|
+
});
|
|
6101
|
+
}
|
|
6102
|
+
function removeProfile(paths, name) {
|
|
6103
|
+
requireProfileExists(paths, name);
|
|
6104
|
+
fs4.rmSync(profileJsonPath(paths, name));
|
|
6117
6105
|
}
|
|
6118
6106
|
|
|
6119
|
-
// src/
|
|
6120
|
-
var
|
|
6121
|
-
constructor(
|
|
6122
|
-
super(`No
|
|
6123
|
-
this.
|
|
6124
|
-
this.name = "
|
|
6107
|
+
// src/poolStore.ts
|
|
6108
|
+
var PoolNotFoundError = class extends CliError {
|
|
6109
|
+
constructor(poolName) {
|
|
6110
|
+
super(`No pool named "${poolName}". Run \`agent-shim pool add ${poolName} --identity <name>...\` first.`);
|
|
6111
|
+
this.poolName = poolName;
|
|
6112
|
+
this.name = "PoolNotFoundError";
|
|
6125
6113
|
}
|
|
6126
|
-
|
|
6114
|
+
poolName;
|
|
6127
6115
|
};
|
|
6128
|
-
var
|
|
6129
|
-
constructor(
|
|
6130
|
-
super(`A
|
|
6131
|
-
this.
|
|
6132
|
-
this.name = "
|
|
6116
|
+
var PoolAlreadyExistsError = class extends CliError {
|
|
6117
|
+
constructor(poolName) {
|
|
6118
|
+
super(`A pool named "${poolName}" already exists. Use \`agent-shim pool set ${poolName}\` to change its members.`);
|
|
6119
|
+
this.poolName = poolName;
|
|
6120
|
+
this.name = "PoolAlreadyExistsError";
|
|
6133
6121
|
}
|
|
6134
|
-
|
|
6122
|
+
poolName;
|
|
6135
6123
|
};
|
|
6136
|
-
var
|
|
6124
|
+
var InvalidPoolNameError = class extends CliError {
|
|
6137
6125
|
constructor(attemptedName) {
|
|
6138
|
-
super(
|
|
6139
|
-
`"${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.`
|
|
6140
|
-
);
|
|
6126
|
+
super(`"${attemptedName}" is not a valid pool name: it must start with a letter or number and may then contain letters, numbers, dots, hyphens and underscores.`);
|
|
6141
6127
|
this.attemptedName = attemptedName;
|
|
6142
|
-
this.name = "
|
|
6128
|
+
this.name = "InvalidPoolNameError";
|
|
6143
6129
|
}
|
|
6144
6130
|
attemptedName;
|
|
6145
6131
|
};
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
return path16.join(paths.providersDir, `${name}.json`);
|
|
6132
|
+
function readPools(paths) {
|
|
6133
|
+
return readGlobalConfig(paths)?.pools ?? {};
|
|
6149
6134
|
}
|
|
6150
|
-
function
|
|
6151
|
-
|
|
6135
|
+
function requirePool(paths, name) {
|
|
6136
|
+
const pool = readPools(paths)[name];
|
|
6137
|
+
if (pool === void 0) {
|
|
6138
|
+
throw new PoolNotFoundError(name);
|
|
6139
|
+
}
|
|
6140
|
+
return pool;
|
|
6152
6141
|
}
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
var LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
|
|
6156
|
-
function isRecord3(value) {
|
|
6157
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6142
|
+
function writePools(paths, pools) {
|
|
6143
|
+
applyPatch(paths.globalConfigFile, GlobalConfigSchema, { pools }, { defaults: {} });
|
|
6158
6144
|
}
|
|
6159
|
-
function
|
|
6160
|
-
if (!
|
|
6161
|
-
|
|
6162
|
-
}
|
|
6163
|
-
const env = isRecord3(raw.env) ? raw.env : void 0;
|
|
6164
|
-
const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
|
|
6165
|
-
const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
|
|
6166
|
-
if (fields.length === 0) {
|
|
6167
|
-
return void 0;
|
|
6145
|
+
function addPool(paths, name, identities) {
|
|
6146
|
+
if (!PoolNameSchema.safeParse(name).success) {
|
|
6147
|
+
throw new InvalidPoolNameError(name);
|
|
6168
6148
|
}
|
|
6169
|
-
const
|
|
6170
|
-
if (
|
|
6171
|
-
|
|
6149
|
+
const pools = readPools(paths);
|
|
6150
|
+
if (pools[name] !== void 0) {
|
|
6151
|
+
throw new PoolAlreadyExistsError(name);
|
|
6172
6152
|
}
|
|
6173
|
-
|
|
6174
|
-
|
|
6153
|
+
const pool = { identities: [...identities] };
|
|
6154
|
+
writePools(paths, { ...pools, [name]: pool });
|
|
6155
|
+
return pool;
|
|
6156
|
+
}
|
|
6157
|
+
function setPool(paths, name, identities) {
|
|
6158
|
+
requirePool(paths, name);
|
|
6159
|
+
const pool = { identities: [...identities] };
|
|
6160
|
+
writePools(paths, { ...readPools(paths), [name]: pool });
|
|
6161
|
+
return pool;
|
|
6162
|
+
}
|
|
6163
|
+
function removePool(paths, name) {
|
|
6164
|
+
requirePool(paths, name);
|
|
6165
|
+
const remaining = Object.fromEntries(Object.entries(readPools(paths)).filter(([existing]) => existing !== name));
|
|
6166
|
+
writePools(paths, remaining);
|
|
6167
|
+
}
|
|
6168
|
+
|
|
6169
|
+
// src/identityStore.ts
|
|
6170
|
+
var IdentityNotFoundError = class extends CliError {
|
|
6171
|
+
constructor(name) {
|
|
6172
|
+
super(`No identity named "${name}". Run \`agent-shim identity add ${name}\` first.`);
|
|
6173
|
+
this.name = name;
|
|
6174
|
+
this.name = "IdentityNotFoundError";
|
|
6175
6175
|
}
|
|
6176
|
-
|
|
6177
|
-
|
|
6176
|
+
name;
|
|
6177
|
+
};
|
|
6178
|
+
var IdentityAlreadyExistsError = class extends CliError {
|
|
6179
|
+
constructor(identityName) {
|
|
6180
|
+
super(`An identity named "${identityName}" already exists.`);
|
|
6181
|
+
this.identityName = identityName;
|
|
6182
|
+
this.name = "IdentityAlreadyExistsError";
|
|
6178
6183
|
}
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
fields,
|
|
6184
|
-
replacement: {
|
|
6185
|
-
...rest,
|
|
6186
|
-
credential: { sources, ...raw.authScheme === "apiKey" ? { target: "apiKey" } : {} },
|
|
6187
|
-
...remainingEnv === void 0 || Object.keys(remainingEnv).length === 0 ? {} : { env: remainingEnv }
|
|
6188
|
-
}
|
|
6189
|
-
};
|
|
6190
|
-
}
|
|
6191
|
-
var LegacyProviderFileError = class extends CliError {
|
|
6192
|
-
constructor(filePath, conversion) {
|
|
6184
|
+
identityName;
|
|
6185
|
+
};
|
|
6186
|
+
var InvalidIdentityNameError = class extends CliError {
|
|
6187
|
+
constructor(attemptedName) {
|
|
6193
6188
|
super(
|
|
6194
|
-
|
|
6195
|
-
` + JSON.stringify(conversion.replacement, null, 2)
|
|
6189
|
+
`"${attemptedName}" is not a valid identity name \u2014 identity names must start with a letter or number and may then contain letters, numbers, dots, hyphens, underscores, and at signs.`
|
|
6196
6190
|
);
|
|
6197
|
-
this.
|
|
6198
|
-
this.
|
|
6199
|
-
this.name = "LegacyProviderFileError";
|
|
6200
|
-
}
|
|
6201
|
-
filePath;
|
|
6202
|
-
conversion;
|
|
6203
|
-
};
|
|
6204
|
-
function parseProviderFile(filePath, raw) {
|
|
6205
|
-
const conversion = legacyProviderConversion(raw);
|
|
6206
|
-
if (conversion !== void 0) {
|
|
6207
|
-
throw new LegacyProviderFileError(filePath, conversion);
|
|
6191
|
+
this.attemptedName = attemptedName;
|
|
6192
|
+
this.name = "InvalidIdentityNameError";
|
|
6208
6193
|
}
|
|
6209
|
-
|
|
6194
|
+
attemptedName;
|
|
6195
|
+
};
|
|
6196
|
+
function identityJsonPath(paths, name) {
|
|
6197
|
+
return path16.join(paths.identitiesDir, name, "identity.json");
|
|
6198
|
+
}
|
|
6199
|
+
function identityExists(paths, name) {
|
|
6200
|
+
return fs5.existsSync(identityJsonPath(paths, name));
|
|
6201
|
+
}
|
|
6202
|
+
function readIdentity(paths, name) {
|
|
6203
|
+
return readJson(identityJsonPath(paths, name), IdentitySchema);
|
|
6204
|
+
}
|
|
6205
|
+
function addIdentity(paths, name) {
|
|
6206
|
+
if (identityExists(paths, name)) {
|
|
6207
|
+
throw new IdentityAlreadyExistsError(name);
|
|
6208
|
+
}
|
|
6209
|
+
const parsed = IdentitySchema.safeParse({ name, allowAmbientCredential: false });
|
|
6210
6210
|
if (!parsed.success) {
|
|
6211
|
-
throw new
|
|
6211
|
+
throw new InvalidIdentityNameError(name);
|
|
6212
6212
|
}
|
|
6213
|
+
writeJsonAtomic(identityJsonPath(paths, name), parsed.data);
|
|
6213
6214
|
return parsed.data;
|
|
6214
6215
|
}
|
|
6215
|
-
function
|
|
6216
|
-
const
|
|
6217
|
-
if (
|
|
6216
|
+
function useIdentity(paths, name) {
|
|
6217
|
+
const pool = poolNameOf(name);
|
|
6218
|
+
if (pool !== void 0) {
|
|
6219
|
+
requirePool(paths, pool);
|
|
6220
|
+
} else if (!identityExists(paths, name)) {
|
|
6221
|
+
throw new IdentityNotFoundError(name);
|
|
6222
|
+
}
|
|
6223
|
+
writeTextAtomic(paths.activeIdentityFile, `${name}
|
|
6224
|
+
`);
|
|
6225
|
+
}
|
|
6226
|
+
function readActiveIdentity(paths) {
|
|
6227
|
+
if (!fs5.existsSync(paths.activeIdentityFile)) {
|
|
6218
6228
|
return void 0;
|
|
6219
6229
|
}
|
|
6220
|
-
const raw =
|
|
6221
|
-
return
|
|
6230
|
+
const raw = fs5.readFileSync(paths.activeIdentityFile, "utf8").trim();
|
|
6231
|
+
return raw === "" ? void 0 : raw;
|
|
6222
6232
|
}
|
|
6223
|
-
function
|
|
6224
|
-
|
|
6225
|
-
const raw = port.readConfigFile(filePath);
|
|
6226
|
-
return raw === void 0 ? void 0 : parseProviderFile(filePath, raw);
|
|
6233
|
+
function isIdentityDirectoryName(name) {
|
|
6234
|
+
return !name.startsWith(".");
|
|
6227
6235
|
}
|
|
6228
|
-
function
|
|
6229
|
-
|
|
6230
|
-
(
|
|
6231
|
-
|
|
6232
|
-
|
|
6236
|
+
function readIdentityForListing(paths, name) {
|
|
6237
|
+
try {
|
|
6238
|
+
return readIdentity(paths, name);
|
|
6239
|
+
} catch (error) {
|
|
6240
|
+
if (error instanceof ConfigValidationError || error instanceof SyntaxError) {
|
|
6241
|
+
return { problem: error.message.replace(/\s*\n\s*/g, " ") };
|
|
6242
|
+
}
|
|
6243
|
+
throw error;
|
|
6244
|
+
}
|
|
6245
|
+
}
|
|
6246
|
+
function listIdentities(paths) {
|
|
6247
|
+
if (!fs5.existsSync(paths.identitiesDir)) {
|
|
6248
|
+
return [];
|
|
6249
|
+
}
|
|
6250
|
+
const active = readActiveIdentity(paths);
|
|
6251
|
+
const names = fs5.readdirSync(paths.identitiesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && isIdentityDirectoryName(entry.name)).map((entry) => entry.name).sort();
|
|
6233
6252
|
const result = [];
|
|
6234
6253
|
for (const name of names) {
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
provider = readProvider(paths, name);
|
|
6238
|
-
} catch (error) {
|
|
6239
|
-
if (!(error instanceof ConfigValidationError || error instanceof LegacyProviderFileError)) {
|
|
6240
|
-
throw error;
|
|
6241
|
-
}
|
|
6242
|
-
console.error(`agent-shim: providers/${name}.json is invalid and was skipped: ${error.message}`);
|
|
6254
|
+
const read = readIdentityForListing(paths, name);
|
|
6255
|
+
if (read === void 0) {
|
|
6243
6256
|
continue;
|
|
6244
6257
|
}
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
}
|
|
6258
|
+
const isActive = name === active;
|
|
6259
|
+
result.push("problem" in read ? { name, isActive, problem: read.problem } : { name, identity: read, isActive });
|
|
6248
6260
|
}
|
|
6249
6261
|
return result;
|
|
6250
6262
|
}
|
|
6251
|
-
function
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
entries = readdir(providersDir);
|
|
6255
|
-
} catch {
|
|
6256
|
-
return [];
|
|
6263
|
+
function setDefaultConfigProfile(paths, identityName, profileName) {
|
|
6264
|
+
if (!identityExists(paths, identityName)) {
|
|
6265
|
+
throw new IdentityNotFoundError(identityName);
|
|
6257
6266
|
}
|
|
6258
|
-
return
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
return isCodexProvider(provider) ? "codex translation daemon" : provider.baseUrl;
|
|
6267
|
+
return applyPatch(identityJsonPath(paths, identityName), IdentitySchema, {
|
|
6268
|
+
defaultConfigProfile: profileName
|
|
6269
|
+
});
|
|
6262
6270
|
}
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
this.name = "ProviderKindMismatchError";
|
|
6271
|
+
function setAllowAmbientCredential(paths, identityName, allow) {
|
|
6272
|
+
if (!identityExists(paths, identityName)) {
|
|
6273
|
+
throw new IdentityNotFoundError(identityName);
|
|
6267
6274
|
}
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6275
|
+
return applyPatch(identityJsonPath(paths, identityName), IdentitySchema, {
|
|
6276
|
+
allowAmbientCredential: allow
|
|
6277
|
+
});
|
|
6271
6278
|
}
|
|
6272
|
-
function
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
if (!PROVIDER_NAME_RE.test(name)) {
|
|
6277
|
-
throw new InvalidProviderNameError(name);
|
|
6278
|
-
}
|
|
6279
|
-
const kind = input.kind ?? "http";
|
|
6280
|
-
if (kind === "codex" && input.baseUrl !== void 0) {
|
|
6281
|
-
throw new ProviderKindMismatchError("A codex provider has no base URL: the launcher points its sessions at the front-door daemon. Drop --base-url.");
|
|
6282
|
-
}
|
|
6283
|
-
if (kind === "http" && input.baseUrl === void 0) {
|
|
6284
|
-
throw new ProviderKindMismatchError("An http provider needs --base-url.");
|
|
6279
|
+
function setIdentityCredential(paths, identityName, change) {
|
|
6280
|
+
const existing = readIdentity(paths, identityName);
|
|
6281
|
+
if (existing === void 0) {
|
|
6282
|
+
throw new IdentityNotFoundError(identityName);
|
|
6285
6283
|
}
|
|
6286
|
-
if (
|
|
6287
|
-
|
|
6284
|
+
if (change === false) {
|
|
6285
|
+
return applyPatch(identityJsonPath(paths, identityName), IdentitySchema, { credential: void 0 });
|
|
6288
6286
|
}
|
|
6289
|
-
const
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
...input.env === void 0 || Object.keys(input.env).length === 0 ? {} : { env: input.env }
|
|
6293
|
-
};
|
|
6294
|
-
const parsed = ProviderSchema.safeParse(
|
|
6295
|
-
kind === "codex" ? { kind, ...shared, ...isEmptyCodexConfig(input.codex) ? {} : { codex: input.codex } } : { ...shared, baseUrl: input.baseUrl }
|
|
6296
|
-
);
|
|
6297
|
-
if (!parsed.success) {
|
|
6298
|
-
throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
|
|
6287
|
+
const sources = change.sources ?? existing.credential?.sources;
|
|
6288
|
+
if (sources === void 0) {
|
|
6289
|
+
throw new UsageError(`Identity "${identityName}" has no credential yet: pass --credential to give it one before choosing its target.`);
|
|
6299
6290
|
}
|
|
6300
|
-
|
|
6301
|
-
|
|
6291
|
+
const target = change.target ?? existing.credential?.target;
|
|
6292
|
+
const cache = change.cache === void 0 ? existing.credential?.cache : change.cache === false ? void 0 : change.cache;
|
|
6293
|
+
return applyPatch(identityJsonPath(paths, identityName), IdentitySchema, {
|
|
6294
|
+
credential: { sources: [...sources], ...target === void 0 ? {} : { target }, ...cache === void 0 ? {} : { cache } }
|
|
6295
|
+
});
|
|
6302
6296
|
}
|
|
6303
|
-
function
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6297
|
+
function belongsToIdentity(entry, name) {
|
|
6298
|
+
return entry === name || entry === path16.basename(identityLockPath("", name)) || entry.startsWith(`.${name}.scratch.`) || entry.startsWith(`.${name}.previous.`);
|
|
6299
|
+
}
|
|
6300
|
+
function removeIdentity(paths, name) {
|
|
6301
|
+
if (!identityExists(paths, name)) {
|
|
6302
|
+
throw new IdentityNotFoundError(name);
|
|
6307
6303
|
}
|
|
6308
|
-
const
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
if (isCodexProvider(existing) && input.baseUrl !== void 0) {
|
|
6313
|
-
throw new ProviderKindMismatchError(`Provider "${name}" is a codex provider, which has no base URL.`);
|
|
6304
|
+
for (const entry of fs5.readdirSync(paths.identitiesDir)) {
|
|
6305
|
+
if (belongsToIdentity(entry, name)) {
|
|
6306
|
+
fs5.rmSync(path16.join(paths.identitiesDir, entry), { recursive: true, force: true });
|
|
6307
|
+
}
|
|
6314
6308
|
}
|
|
6315
|
-
if (
|
|
6316
|
-
|
|
6309
|
+
if (readActiveIdentity(paths) === name) {
|
|
6310
|
+
fs5.rmSync(paths.activeIdentityFile);
|
|
6317
6311
|
}
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6312
|
+
}
|
|
6313
|
+
|
|
6314
|
+
// src/providersStore.ts
|
|
6315
|
+
import fs6 from "node:fs";
|
|
6316
|
+
import path17 from "node:path";
|
|
6317
|
+
|
|
6318
|
+
// src/credentialCache.ts
|
|
6319
|
+
import { z as z11 } from "zod";
|
|
6320
|
+
var MS_PER_UNIT = { s: 1e3, m: 6e4, h: 36e5, d: 864e5 };
|
|
6321
|
+
var CachedCredentialSchema = z11.strictObject({
|
|
6322
|
+
token: z11.string().min(1),
|
|
6323
|
+
fetchedAt: z11.number().int().nonnegative(),
|
|
6324
|
+
source: CredentialSourceSchema
|
|
6325
|
+
});
|
|
6326
|
+
function effectiveStore(cache, platform) {
|
|
6327
|
+
return cache.store ?? (platform === "darwin" ? "keychain" : "file");
|
|
6328
|
+
}
|
|
6329
|
+
function ttlMs(cache) {
|
|
6330
|
+
if (cache.ttl === void 0) {
|
|
6331
|
+
return void 0;
|
|
6337
6332
|
}
|
|
6338
|
-
const
|
|
6339
|
-
if (
|
|
6340
|
-
throw new
|
|
6333
|
+
const multiplier = MS_PER_UNIT[cache.ttl.slice(-1)];
|
|
6334
|
+
if (multiplier === void 0) {
|
|
6335
|
+
throw new Error(`credential cache ttl "${cache.ttl}" has no recognised unit`);
|
|
6341
6336
|
}
|
|
6342
|
-
|
|
6343
|
-
return parsed.data;
|
|
6337
|
+
return Number(cache.ttl.slice(0, -1)) * multiplier;
|
|
6344
6338
|
}
|
|
6345
|
-
function
|
|
6346
|
-
|
|
6347
|
-
|
|
6339
|
+
function isFresh(entry, cache, now) {
|
|
6340
|
+
const limit = ttlMs(cache);
|
|
6341
|
+
if (entry.fetchedAt > now) {
|
|
6342
|
+
return false;
|
|
6348
6343
|
}
|
|
6349
|
-
|
|
6344
|
+
return limit === void 0 || now - entry.fetchedAt < limit;
|
|
6350
6345
|
}
|
|
6351
|
-
function
|
|
6352
|
-
|
|
6353
|
-
for (const layer of assembleCascade(cascade).layers) {
|
|
6354
|
-
if (layer.launch?.provider !== void 0) {
|
|
6355
|
-
name = layer.launch.provider;
|
|
6356
|
-
}
|
|
6357
|
-
}
|
|
6358
|
-
return name;
|
|
6346
|
+
function cacheAgeMs(entry, now) {
|
|
6347
|
+
return Math.max(0, now - entry.fetchedAt);
|
|
6359
6348
|
}
|
|
6360
|
-
function
|
|
6361
|
-
const
|
|
6362
|
-
|
|
6363
|
-
|
|
6364
|
-
|
|
6349
|
+
function describeCachedCredential(params) {
|
|
6350
|
+
const store = effectiveStore(params.cache, params.platform);
|
|
6351
|
+
let entry;
|
|
6352
|
+
try {
|
|
6353
|
+
entry = params.port.read(store, params.owner);
|
|
6354
|
+
} catch (error) {
|
|
6355
|
+
return { store, status: "unreadable", reason: error instanceof Error ? error.message : String(error) };
|
|
6356
|
+
}
|
|
6357
|
+
if (entry === void 0) {
|
|
6358
|
+
return { store, status: "empty" };
|
|
6359
|
+
}
|
|
6360
|
+
const limit = ttlMs(params.cache);
|
|
6361
|
+
const ageMs = cacheAgeMs(entry, params.nowMs);
|
|
6362
|
+
return {
|
|
6363
|
+
store,
|
|
6364
|
+
status: isFresh(entry, params.cache, params.nowMs) ? "fresh" : "expired",
|
|
6365
|
+
ageMs,
|
|
6366
|
+
...limit === void 0 ? {} : { expiresInMs: limit - ageMs }
|
|
6367
|
+
};
|
|
6368
|
+
}
|
|
6369
|
+
function formatCachedCredentialState(state) {
|
|
6370
|
+
if (state.status === "empty") {
|
|
6371
|
+
return `no cached entry in the ${state.store} store yet`;
|
|
6365
6372
|
}
|
|
6366
|
-
|
|
6367
|
-
|
|
6368
|
-
const known = listProviderNames(params.port.readdir, params.paths.providersDir);
|
|
6369
|
-
return {
|
|
6370
|
-
ok: false,
|
|
6371
|
-
status: 1,
|
|
6372
|
-
message: `agent-shim: no provider named "${name}". ` + (known.length > 0 ? `Known providers: ${known.join(", ")}.` : "No providers are defined yet; run `agent-shim provider add`.")
|
|
6373
|
-
};
|
|
6373
|
+
if (state.status === "unreadable") {
|
|
6374
|
+
return `the ${state.store} store cannot be read here: ${state.reason}`;
|
|
6374
6375
|
}
|
|
6375
|
-
if (
|
|
6376
|
-
return
|
|
6376
|
+
if (state.status === "expired" && (state.expiresInMs === void 0 || state.expiresInMs > 0)) {
|
|
6377
|
+
return "cached with a timestamp in the future, treated as expired";
|
|
6377
6378
|
}
|
|
6378
|
-
const
|
|
6379
|
-
if (
|
|
6380
|
-
return {
|
|
6379
|
+
const age = `cached ${formatAge(state.ageMs)} ago`;
|
|
6380
|
+
if (state.expiresInMs === void 0) {
|
|
6381
|
+
return `${age}, no expiry`;
|
|
6381
6382
|
}
|
|
6382
|
-
return
|
|
6383
|
+
return state.status === "fresh" ? `${age}, expires in ${formatAge(state.expiresInMs)}` : `${age}, expired ${formatAge(-state.expiresInMs)} ago`;
|
|
6383
6384
|
}
|
|
6384
6385
|
|
|
6385
|
-
// src/
|
|
6386
|
-
var
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
constructor() {
|
|
6396
|
-
super("A directory rule must set at least one of --config-profile or --identity.");
|
|
6397
|
-
this.name = "DirectoryRuleMissingTargetError";
|
|
6386
|
+
// src/credential.ts
|
|
6387
|
+
var CREDENTIAL_UNAVAILABLE_EXIT = 64;
|
|
6388
|
+
var CREDENTIAL_COMMAND_TIMEOUT_MS = 1e4;
|
|
6389
|
+
var CREDENTIAL_INTERACTIVE_TIMEOUT_MS = 12e4;
|
|
6390
|
+
var GROUP_OTHER_PERMISSION_BITS = 63;
|
|
6391
|
+
var PERMISSION_BITS = 511;
|
|
6392
|
+
var OCTAL_RADIX2 = 8;
|
|
6393
|
+
function commandArgv(source) {
|
|
6394
|
+
if ("command" in source) {
|
|
6395
|
+
return source.command;
|
|
6398
6396
|
}
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
constructor(rulePath) {
|
|
6402
|
-
super(`A directory rule for path "${rulePath}" already exists. Use \`agent-shim rule set\` to change it.`);
|
|
6403
|
-
this.rulePath = rulePath;
|
|
6404
|
-
this.name = "DirectoryRuleAlreadyExistsError";
|
|
6397
|
+
if ("op" in source) {
|
|
6398
|
+
return ["op", "read", source.op];
|
|
6405
6399
|
}
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
function readDirectoryRules(paths) {
|
|
6409
|
-
return readJson(paths.directoryRulesFile, DirectoryRulesSchema) ?? { rules: [] };
|
|
6400
|
+
const account = source.keychain.account;
|
|
6401
|
+
return ["security", "find-generic-password", "-s", source.keychain.service, ...account === void 0 ? [] : ["-a", account], "-w"];
|
|
6410
6402
|
}
|
|
6411
|
-
function
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
throw new ConfigValidationError(paths.directoryRulesFile, parsed.error.issues);
|
|
6403
|
+
function isInteractiveSource(source, env) {
|
|
6404
|
+
if ("command" in source || "keychain" in source) {
|
|
6405
|
+
return source.interactive ?? false;
|
|
6415
6406
|
}
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
return
|
|
6407
|
+
if ("op" in source) {
|
|
6408
|
+
return source.interactive ?? (env.OP_SERVICE_ACCOUNT_TOKEN ?? "") === "";
|
|
6409
|
+
}
|
|
6410
|
+
return false;
|
|
6420
6411
|
}
|
|
6421
|
-
function
|
|
6422
|
-
if (
|
|
6423
|
-
|
|
6412
|
+
function summariseSource(source) {
|
|
6413
|
+
if ("env" in source) {
|
|
6414
|
+
return { kind: "env", variable: source.env };
|
|
6424
6415
|
}
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
throw new DirectoryRuleAlreadyExistsError(rulePath);
|
|
6416
|
+
if ("file" in source) {
|
|
6417
|
+
return { kind: "file", path: source.file };
|
|
6428
6418
|
}
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
return created;
|
|
6432
|
-
}
|
|
6433
|
-
function ruleHasEffect(rule) {
|
|
6434
|
-
return rule.configProfile !== void 0 || rule.identity !== void 0 || rule.categories !== void 0 || rule.entries !== void 0 || rule.launch !== void 0;
|
|
6435
|
-
}
|
|
6436
|
-
function updateDirectoryRule(paths, rulePath, options) {
|
|
6437
|
-
const current = readDirectoryRules(paths);
|
|
6438
|
-
const index = current.rules.findIndex((rule) => rule.path === rulePath);
|
|
6439
|
-
const existing = index === -1 ? void 0 : current.rules[index];
|
|
6440
|
-
if (existing === void 0) {
|
|
6441
|
-
throw new DirectoryRuleNotFoundError(rulePath);
|
|
6419
|
+
if ("command" in source) {
|
|
6420
|
+
return { kind: "command", program: source.command[0] };
|
|
6442
6421
|
}
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
const nextIdentity = options.identity === void 0 ? identity : options.identity === false ? void 0 : options.identity;
|
|
6446
|
-
const updated = {
|
|
6447
|
-
...rest,
|
|
6448
|
-
...nextConfigProfile === void 0 ? {} : { configProfile: nextConfigProfile },
|
|
6449
|
-
...nextIdentity === void 0 ? {} : { identity: nextIdentity }
|
|
6450
|
-
};
|
|
6451
|
-
if (!ruleHasEffect(updated)) {
|
|
6452
|
-
throw new DirectoryRuleMissingTargetError();
|
|
6422
|
+
if ("op" in source) {
|
|
6423
|
+
return { kind: "op", reference: source.op };
|
|
6453
6424
|
}
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6425
|
+
if ("keychain" in source) {
|
|
6426
|
+
return {
|
|
6427
|
+
kind: "keychain",
|
|
6428
|
+
service: source.keychain.service,
|
|
6429
|
+
...source.keychain.account === void 0 ? {} : { account: source.keychain.account }
|
|
6430
|
+
};
|
|
6431
|
+
}
|
|
6432
|
+
return { kind: "literal" };
|
|
6458
6433
|
}
|
|
6459
|
-
function
|
|
6434
|
+
function summariseCredential(credential) {
|
|
6460
6435
|
return {
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
...
|
|
6436
|
+
target: credential.target ?? "bearer",
|
|
6437
|
+
sources: credential.sources.map(summariseSource),
|
|
6438
|
+
...credential.cache === void 0 ? {} : { cache: credential.cache }
|
|
6464
6439
|
};
|
|
6465
6440
|
}
|
|
6466
|
-
function
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6441
|
+
function formatSourceSummary(summary) {
|
|
6442
|
+
switch (summary.kind) {
|
|
6443
|
+
case "env":
|
|
6444
|
+
return `env ${summary.variable}`;
|
|
6445
|
+
case "file":
|
|
6446
|
+
return `file ${summary.path}`;
|
|
6447
|
+
case "command":
|
|
6448
|
+
return `command ${summary.program}`;
|
|
6449
|
+
case "op":
|
|
6450
|
+
return `op ${summary.reference}`;
|
|
6451
|
+
case "keychain":
|
|
6452
|
+
return `keychain ${summary.service}${summary.account === void 0 ? "" : ` (account ${summary.account})`}`;
|
|
6453
|
+
case "literal":
|
|
6454
|
+
return "literal (non-secret placeholder)";
|
|
6455
|
+
default:
|
|
6456
|
+
return summary;
|
|
6471
6457
|
}
|
|
6472
|
-
writeDirectoryRules(paths, { ...current, rules: nextRules });
|
|
6473
6458
|
}
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
import os4 from "node:os";
|
|
6477
|
-
|
|
6478
|
-
// src/launcher/cascade.ts
|
|
6479
|
-
import path18 from "node:path";
|
|
6480
|
-
|
|
6481
|
-
// src/pathNorm.ts
|
|
6482
|
-
import path17 from "node:path";
|
|
6483
|
-
function expandTilde(value, home) {
|
|
6484
|
-
if (value === "~") {
|
|
6485
|
-
return home;
|
|
6486
|
-
}
|
|
6487
|
-
if (value.startsWith("~/")) {
|
|
6488
|
-
return path17.join(home, value.slice(2));
|
|
6489
|
-
}
|
|
6490
|
-
return value;
|
|
6459
|
+
function describeSource(source) {
|
|
6460
|
+
return formatSourceSummary(summariseSource(source));
|
|
6491
6461
|
}
|
|
6492
|
-
function
|
|
6493
|
-
|
|
6462
|
+
function formatCredentialSummary(summary) {
|
|
6463
|
+
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`}`;
|
|
6464
|
+
return `${summary.target} from ${summary.sources.map(formatSourceSummary).join(", then ")}${cache}`;
|
|
6494
6465
|
}
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6466
|
+
function describeCredential(credential) {
|
|
6467
|
+
return formatCredentialSummary(summariseCredential(credential));
|
|
6468
|
+
}
|
|
6469
|
+
function trySource(source, params) {
|
|
6470
|
+
const none = (reason, warn = false) => ({ ok: false, reason, warn });
|
|
6471
|
+
if ("env" in source) {
|
|
6472
|
+
const value = params.env[source.env];
|
|
6473
|
+
return value === void 0 || value === "" ? none("is unset or empty") : { ok: true, token: value };
|
|
6474
|
+
}
|
|
6475
|
+
if ("literal" in source) {
|
|
6476
|
+
return { ok: true, token: source.literal };
|
|
6477
|
+
}
|
|
6478
|
+
if ("file" in source) {
|
|
6479
|
+
const read = params.port.readSecretFile(source.file);
|
|
6480
|
+
if (!read.found) {
|
|
6481
|
+
return none("does not exist");
|
|
6507
6482
|
}
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
...limit === void 0 ? {} : { limit: expandTilde(limit, params.home) },
|
|
6514
|
-
...params.isReadable === void 0 ? {} : { isReadable: params.isReadable }
|
|
6515
|
-
});
|
|
6516
|
-
const levels = [];
|
|
6517
|
-
for (const dir of dirs) {
|
|
6518
|
-
const portable = loadConfigFile(path18.join(dir, PORTABLE_CONFIG_FILENAME), PortableConfigSchema, params.read) ?? loadConfigFile(path18.join(dir, LEGACY_PORTABLE_CONFIG_FILENAME), PortableConfigSchema, params.read);
|
|
6519
|
-
const portableLocal = loadConfigFile(path18.join(dir, PORTABLE_LOCAL_CONFIG_FILENAME), PortableConfigSchema, params.read) ?? loadConfigFile(path18.join(dir, LEGACY_PORTABLE_LOCAL_CONFIG_FILENAME), PortableConfigSchema, params.read);
|
|
6520
|
-
const rules = (directoryRules?.config.rules ?? []).map((rule, index) => ({ rule, index })).filter(({ rule }) => normaliseRulePath(rule.path, params.home) === dir).map(({ rule, index }) => {
|
|
6521
|
-
const entryOrder = directoryRules?.ruleEntryOrders[index];
|
|
6522
|
-
return {
|
|
6523
|
-
rule,
|
|
6524
|
-
...entryOrder === void 0 ? {} : { entryOrder },
|
|
6525
|
-
filepath: params.paths.directoryRulesFile
|
|
6526
|
-
};
|
|
6527
|
-
});
|
|
6528
|
-
if (portable === void 0 && portableLocal === void 0 && rules.length === 0) {
|
|
6529
|
-
continue;
|
|
6483
|
+
if (read.mode !== void 0 && (read.mode & GROUP_OTHER_PERMISSION_BITS) !== 0) {
|
|
6484
|
+
return none(
|
|
6485
|
+
`is readable or writable by group or others (mode ${(read.mode & PERMISSION_BITS).toString(OCTAL_RADIX2)}); run \`chmod 600 ${source.file}\``,
|
|
6486
|
+
true
|
|
6487
|
+
);
|
|
6530
6488
|
}
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6489
|
+
const token2 = read.content.trim();
|
|
6490
|
+
return token2 === "" ? none("is empty") : { ok: true, token: token2 };
|
|
6491
|
+
}
|
|
6492
|
+
const argv = commandArgv(source);
|
|
6493
|
+
const interactive = isInteractiveSource(source, params.env);
|
|
6494
|
+
if (interactive && !params.port.personPresent()) {
|
|
6495
|
+
return none("needs a person to approve it, but there is no terminal or desktop session");
|
|
6496
|
+
}
|
|
6497
|
+
const timeoutMs = source.timeoutMs ?? (interactive ? CREDENTIAL_INTERACTIVE_TIMEOUT_MS : CREDENTIAL_COMMAND_TIMEOUT_MS);
|
|
6498
|
+
const result = params.port.runCommand(argv, { timeoutMs, interactive });
|
|
6499
|
+
if (result.timedOut) {
|
|
6500
|
+
return none(`timed out after ${String(timeoutMs)}ms`);
|
|
6501
|
+
}
|
|
6502
|
+
if (result.status !== 0) {
|
|
6503
|
+
const stderr = result.stderr.trim();
|
|
6504
|
+
const outcome = result.status === null ? "could not be run or was killed by a signal" : `exited with status ${String(result.status)}`;
|
|
6505
|
+
return none(`${outcome}${stderr === "" ? "" : `: ${stderr}`}`);
|
|
6537
6506
|
}
|
|
6538
|
-
const
|
|
6539
|
-
|
|
6540
|
-
...globalConfig === void 0 ? {} : { globalConfig: { config: globalConfig.config, entryOrder: globalConfig.entryOrder, filepath: globalConfig.filepath } },
|
|
6541
|
-
...params.baseConfigProfile === void 0 ? {} : { baseConfigProfile: params.baseConfigProfile },
|
|
6542
|
-
loadProfile,
|
|
6543
|
-
levels,
|
|
6544
|
-
...params.cliOverride === void 0 ? {} : { cliOverride: params.cliOverride }
|
|
6545
|
-
};
|
|
6546
|
-
return { input, ...globalConfig === void 0 ? {} : { globalConfig: globalConfig.config } };
|
|
6507
|
+
const token = result.stdout.trim();
|
|
6508
|
+
return token === "" ? none("printed no token") : { ok: true, token };
|
|
6547
6509
|
}
|
|
6548
|
-
function
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6510
|
+
function warmCommandFor(subject) {
|
|
6511
|
+
const [kind, name] = subject.split(" ");
|
|
6512
|
+
return kind === "provider" ? `agent-shim credential warm --provider ${name ?? ""}` : `agent-shim credential warm ${name ?? ""}`;
|
|
6513
|
+
}
|
|
6514
|
+
function resolveCredential(params) {
|
|
6515
|
+
const target = params.credential.target ?? "bearer";
|
|
6516
|
+
const cacheBlock = params.credential.cache;
|
|
6517
|
+
const cache = cacheBlock === void 0 ? void 0 : params.port.cache;
|
|
6518
|
+
const store = cacheBlock === void 0 || cache === void 0 ? void 0 : effectiveStore(cacheBlock, cache.platform);
|
|
6519
|
+
if (cacheBlock !== void 0 && cache !== void 0 && store !== void 0 && params.refresh !== true) {
|
|
6520
|
+
const entry = cache.port.read(store, params.subject);
|
|
6521
|
+
if (entry !== void 0 && isFresh(entry, cacheBlock, cache.now())) {
|
|
6522
|
+
return { ok: true, credential: { target, token: entry.token, source: entry.source, warnings: [], cachedAt: entry.fetchedAt } };
|
|
6523
|
+
}
|
|
6524
|
+
}
|
|
6525
|
+
const attempts = [];
|
|
6526
|
+
const warnings = [];
|
|
6527
|
+
for (const source of params.credential.sources) {
|
|
6528
|
+
const outcome = trySource(source, params);
|
|
6529
|
+
if (outcome.ok) {
|
|
6530
|
+
if (cache !== void 0 && store !== void 0) {
|
|
6531
|
+
cache.port.write(store, params.subject, { token: outcome.token, fetchedAt: cache.now(), source });
|
|
6561
6532
|
}
|
|
6533
|
+
return { ok: true, credential: { target, token: outcome.token, source, warnings } };
|
|
6534
|
+
}
|
|
6535
|
+
const line = `${describeSource(source)} ${outcome.reason}`;
|
|
6536
|
+
attempts.push(line);
|
|
6537
|
+
if (outcome.warn) {
|
|
6538
|
+
warnings.push(`agent-shim: ${params.subject}: skipped ${line}`);
|
|
6562
6539
|
}
|
|
6563
6540
|
}
|
|
6564
|
-
|
|
6541
|
+
const hint = cacheBlock === void 0 ? "" : `; fill the cache from a terminal with \`${warmCommandFor(params.subject)}\``;
|
|
6542
|
+
return { ok: false, message: `agent-shim: ${params.subject} has no usable credential: ${attempts.join("; ")}${hint}` };
|
|
6543
|
+
}
|
|
6544
|
+
function credentialVariables(target, token) {
|
|
6545
|
+
return {
|
|
6546
|
+
ANTHROPIC_AUTH_TOKEN: target === "bearer" ? token : void 0,
|
|
6547
|
+
ANTHROPIC_API_KEY: target === "apiKey" ? token : void 0,
|
|
6548
|
+
CLAUDE_CODE_OAUTH_TOKEN: target === "oauthToken" ? token : void 0
|
|
6549
|
+
};
|
|
6565
6550
|
}
|
|
6566
6551
|
|
|
6567
|
-
// src/
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
var ZERO_TIER = /(?:^|_)zero$/;
|
|
6574
|
-
var BASE_TIER = /(?:^|_)(?:pro|free)$/;
|
|
6575
|
-
function planOf(account) {
|
|
6576
|
-
const tier = account?.organizationRateLimitTier ?? account?.userRateLimitTier;
|
|
6577
|
-
const tierField = tier === void 0 ? {} : { tier };
|
|
6578
|
-
if (tier === void 0) {
|
|
6579
|
-
return { kind: "subscription", capacity: 1, recognised: false };
|
|
6552
|
+
// src/providersStore.ts
|
|
6553
|
+
var ProviderNotFoundError = class extends CliError {
|
|
6554
|
+
constructor(name) {
|
|
6555
|
+
super(`No provider named "${name}". Run \`agent-shim provider add ${name}\` first.`);
|
|
6556
|
+
this.name = name;
|
|
6557
|
+
this.name = "ProviderNotFoundError";
|
|
6580
6558
|
}
|
|
6581
|
-
|
|
6582
|
-
|
|
6559
|
+
name;
|
|
6560
|
+
};
|
|
6561
|
+
var ProviderAlreadyExistsError = class extends CliError {
|
|
6562
|
+
constructor(providerName) {
|
|
6563
|
+
super(`A provider named "${providerName}" already exists.`);
|
|
6564
|
+
this.providerName = providerName;
|
|
6565
|
+
this.name = "ProviderAlreadyExistsError";
|
|
6583
6566
|
}
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6567
|
+
providerName;
|
|
6568
|
+
};
|
|
6569
|
+
var InvalidProviderNameError = class extends CliError {
|
|
6570
|
+
constructor(attemptedName) {
|
|
6571
|
+
super(
|
|
6572
|
+
`"${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.`
|
|
6573
|
+
);
|
|
6574
|
+
this.attemptedName = attemptedName;
|
|
6575
|
+
this.name = "InvalidProviderNameError";
|
|
6587
6576
|
}
|
|
6588
|
-
|
|
6577
|
+
attemptedName;
|
|
6578
|
+
};
|
|
6579
|
+
var PROVIDER_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._@-]*$/;
|
|
6580
|
+
function providerJsonPath(paths, name) {
|
|
6581
|
+
return path17.join(paths.providersDir, `${name}.json`);
|
|
6589
6582
|
}
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
var MS_PER_SECOND3 = 1e3;
|
|
6593
|
-
function epochSecondsToIso(value) {
|
|
6594
|
-
if (value === void 0) {
|
|
6595
|
-
return void 0;
|
|
6596
|
-
}
|
|
6597
|
-
const seconds = Number(value);
|
|
6598
|
-
return Number.isFinite(seconds) && seconds > 0 ? new Date(seconds * MS_PER_SECOND3).toISOString() : void 0;
|
|
6583
|
+
function providerExists(paths, name) {
|
|
6584
|
+
return fs6.existsSync(providerJsonPath(paths, name));
|
|
6599
6585
|
}
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
6586
|
+
var LEGACY_PROVIDER_FIELDS = ["tokenEnv", "tokenCommand", "authScheme"];
|
|
6587
|
+
var LEGACY_ENV_CREDENTIAL_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
|
|
6588
|
+
var LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
|
|
6589
|
+
function isRecord3(value) {
|
|
6590
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6606
6591
|
}
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
const utilization = nonNegativeNumber(headers[`${UNIFIED_PREFIX}${window}-utilization`]);
|
|
6610
|
-
const resetsAt = epochSecondsToIso(headers[`${UNIFIED_PREFIX}${window}-reset`]);
|
|
6611
|
-
const status = headers[`${UNIFIED_PREFIX}${window}-status`];
|
|
6612
|
-
if (utilization === void 0 && resetsAt === void 0 && status === void 0) {
|
|
6592
|
+
function legacyProviderConversion(raw) {
|
|
6593
|
+
if (!isRecord3(raw) || "credential" in raw) {
|
|
6613
6594
|
return void 0;
|
|
6614
6595
|
}
|
|
6615
|
-
|
|
6616
|
-
|
|
6617
|
-
|
|
6618
|
-
if (
|
|
6596
|
+
const env = isRecord3(raw.env) ? raw.env : void 0;
|
|
6597
|
+
const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
|
|
6598
|
+
const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
|
|
6599
|
+
if (fields.length === 0) {
|
|
6619
6600
|
return void 0;
|
|
6620
6601
|
}
|
|
6621
|
-
const
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6602
|
+
const sources = [];
|
|
6603
|
+
if (typeof raw.tokenEnv === "string") {
|
|
6604
|
+
sources.push({ env: raw.tokenEnv });
|
|
6605
|
+
}
|
|
6606
|
+
if (Array.isArray(raw.tokenCommand)) {
|
|
6607
|
+
sources.push({ command: raw.tokenCommand });
|
|
6608
|
+
}
|
|
6609
|
+
if (typeof env?.ANTHROPIC_AUTH_TOKEN === "string" && env.ANTHROPIC_AUTH_TOKEN !== "") {
|
|
6610
|
+
sources.push({ literal: LEGACY_LITERAL_PLACEHOLDER });
|
|
6611
|
+
}
|
|
6612
|
+
const remainingEnv = env === void 0 ? void 0 : Object.fromEntries(Object.entries(env).filter(([key]) => !LEGACY_ENV_CREDENTIAL_KEYS.includes(key)));
|
|
6613
|
+
const replacedKeys = [...LEGACY_PROVIDER_FIELDS, "env"];
|
|
6614
|
+
const rest = Object.fromEntries(Object.entries(raw).filter(([key]) => !replacedKeys.includes(key)));
|
|
6627
6615
|
return {
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6616
|
+
fields,
|
|
6617
|
+
replacement: {
|
|
6618
|
+
...rest,
|
|
6619
|
+
credential: { sources, ...raw.authScheme === "apiKey" ? { target: "apiKey" } : {} },
|
|
6620
|
+
...remainingEnv === void 0 || Object.keys(remainingEnv).length === 0 ? {} : { env: remainingEnv }
|
|
6621
|
+
}
|
|
6634
6622
|
};
|
|
6635
6623
|
}
|
|
6636
|
-
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
var ErrorObjectSchema = z8.looseObject({
|
|
6646
|
-
type: z8.string().optional(),
|
|
6647
|
-
code: z8.union([z8.string(), z8.number()]).optional(),
|
|
6648
|
-
message: z8.string().optional()
|
|
6649
|
-
});
|
|
6650
|
-
|
|
6651
|
-
// src/usage/middleware.ts
|
|
6652
|
-
var ANTHROPIC_PROVIDER = "anthropic";
|
|
6653
|
-
|
|
6654
|
-
// src/usage/pick.ts
|
|
6655
|
-
var MS_PER_SECOND4 = 1e3;
|
|
6656
|
-
var MS_PER_HOUR = 36e5;
|
|
6657
|
-
var HOURS_PER_DAY2 = 24;
|
|
6658
|
-
var FIVE_HOURS = 5;
|
|
6659
|
-
var PROMPT_CACHE_TTL_HOURS = 1;
|
|
6660
|
-
var PERCENT = 100;
|
|
6661
|
-
var SEVEN_DAYS = 7;
|
|
6662
|
-
var FIVE_HOUR_WINDOW_MS = FIVE_HOURS * MS_PER_HOUR;
|
|
6663
|
-
var SEVEN_DAY_WINDOW_MS = SEVEN_DAYS * HOURS_PER_DAY2 * MS_PER_HOUR;
|
|
6664
|
-
var PROMPT_CACHE_TTL_MS = PROMPT_CACHE_TTL_HOURS * MS_PER_HOUR;
|
|
6665
|
-
var CLASS_ORDER = { scored: 0, unknown: 1, "pay-per-use": 2, ineligible: 3 };
|
|
6666
|
-
function unifiedOf(state) {
|
|
6667
|
-
return state?.rateLimit?.unified;
|
|
6668
|
-
}
|
|
6669
|
-
function limitBlockedUntil(state, nowMs) {
|
|
6670
|
-
const limit = state?.lastLimit;
|
|
6671
|
-
if (state === void 0 || limit === void 0) {
|
|
6672
|
-
return void 0;
|
|
6624
|
+
var LegacyProviderFileError = class extends CliError {
|
|
6625
|
+
constructor(filePath, conversion) {
|
|
6626
|
+
super(
|
|
6627
|
+
`${filePath} uses ${conversion.fields.join(", ")}, which a credential block replaced. Rewrite it as:
|
|
6628
|
+
` + JSON.stringify(conversion.replacement, null, 2)
|
|
6629
|
+
);
|
|
6630
|
+
this.filePath = filePath;
|
|
6631
|
+
this.conversion = conversion;
|
|
6632
|
+
this.name = "LegacyProviderFileError";
|
|
6673
6633
|
}
|
|
6674
|
-
|
|
6634
|
+
filePath;
|
|
6635
|
+
conversion;
|
|
6636
|
+
};
|
|
6637
|
+
function parseProviderFile(filePath, raw) {
|
|
6638
|
+
const conversion = legacyProviderConversion(raw);
|
|
6639
|
+
if (conversion !== void 0) {
|
|
6640
|
+
throw new LegacyProviderFileError(filePath, conversion);
|
|
6641
|
+
}
|
|
6642
|
+
const parsed = ProviderSchema.safeParse(raw);
|
|
6643
|
+
if (!parsed.success) {
|
|
6644
|
+
throw new ConfigValidationError(filePath, parsed.error.issues);
|
|
6645
|
+
}
|
|
6646
|
+
return parsed.data;
|
|
6647
|
+
}
|
|
6648
|
+
function readProvider(paths, name) {
|
|
6649
|
+
const filePath = providerJsonPath(paths, name);
|
|
6650
|
+
if (!fs6.existsSync(filePath)) {
|
|
6675
6651
|
return void 0;
|
|
6676
6652
|
}
|
|
6677
|
-
const
|
|
6678
|
-
return
|
|
6653
|
+
const raw = JSON.parse(fs6.readFileSync(filePath, "utf8"));
|
|
6654
|
+
return parseProviderFile(filePath, raw);
|
|
6679
6655
|
}
|
|
6680
|
-
function
|
|
6681
|
-
const
|
|
6682
|
-
|
|
6656
|
+
function loadProvider(providersDir, name, port) {
|
|
6657
|
+
const filePath = path17.join(providersDir, `${name}.json`);
|
|
6658
|
+
const raw = port.readConfigFile(filePath);
|
|
6659
|
+
return raw === void 0 ? void 0 : parseProviderFile(filePath, raw);
|
|
6683
6660
|
}
|
|
6684
|
-
function
|
|
6685
|
-
const
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
|
|
6661
|
+
function listProviders(paths) {
|
|
6662
|
+
const names = listProviderNames(
|
|
6663
|
+
(dir) => fs6.readdirSync(dir),
|
|
6664
|
+
paths.providersDir
|
|
6665
|
+
);
|
|
6666
|
+
const result = [];
|
|
6667
|
+
for (const name of names) {
|
|
6668
|
+
let provider;
|
|
6669
|
+
try {
|
|
6670
|
+
provider = readProvider(paths, name);
|
|
6671
|
+
} catch (error) {
|
|
6672
|
+
if (!(error instanceof ConfigValidationError || error instanceof LegacyProviderFileError)) {
|
|
6673
|
+
throw error;
|
|
6674
|
+
}
|
|
6675
|
+
console.error(`agent-shim: providers/${name}.json is invalid and was skipped: ${error.message}`);
|
|
6676
|
+
continue;
|
|
6677
|
+
}
|
|
6678
|
+
if (provider !== void 0) {
|
|
6679
|
+
result.push({ name, provider });
|
|
6690
6680
|
}
|
|
6691
6681
|
}
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6682
|
+
return result;
|
|
6683
|
+
}
|
|
6684
|
+
function listProviderNames(readdir, providersDir) {
|
|
6685
|
+
let entries;
|
|
6686
|
+
try {
|
|
6687
|
+
entries = readdir(providersDir);
|
|
6688
|
+
} catch {
|
|
6689
|
+
return [];
|
|
6698
6690
|
}
|
|
6699
|
-
return (
|
|
6691
|
+
return entries.filter((entry) => entry.endsWith(".json")).map((entry) => entry.slice(0, -".json".length)).sort();
|
|
6700
6692
|
}
|
|
6701
|
-
function
|
|
6702
|
-
return
|
|
6693
|
+
function describeProviderEndpoint(provider) {
|
|
6694
|
+
return isCodexProvider(provider) ? "codex translation daemon" : provider.baseUrl;
|
|
6703
6695
|
}
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6696
|
+
var ProviderKindMismatchError = class extends UsageError {
|
|
6697
|
+
constructor(message) {
|
|
6698
|
+
super(message);
|
|
6699
|
+
this.name = "ProviderKindMismatchError";
|
|
6707
6700
|
}
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
return
|
|
6711
|
-
}
|
|
6712
|
-
function capacityOf(plan) {
|
|
6713
|
-
return plan.kind === "subscription" ? plan.capacity : 1;
|
|
6701
|
+
};
|
|
6702
|
+
function isEmptyCodexConfig(config) {
|
|
6703
|
+
return config === void 0 || config.defaultModel === void 0 && config.effort === void 0 && (config.models === void 0 || Object.keys(config.models).length === 0);
|
|
6714
6704
|
}
|
|
6715
|
-
function
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
const base = { identity: member.identity, plan };
|
|
6719
|
-
if (member.readError !== void 0) {
|
|
6720
|
-
return { candidate: { ...base, class: "unknown", reasons: [`usage state unreadable: ${member.readError}`, ...planReasons] } };
|
|
6705
|
+
function addProvider(paths, name, input) {
|
|
6706
|
+
if (providerExists(paths, name)) {
|
|
6707
|
+
throw new ProviderAlreadyExistsError(name);
|
|
6721
6708
|
}
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
if (member.snapshot === void 0 || state === void 0 || unified === void 0) {
|
|
6725
|
-
return { candidate: { ...base, class: "unknown", reasons: ["no usage recorded yet, so its quota is unknown (turn on trackUsage for it)", ...planReasons] } };
|
|
6709
|
+
if (!PROVIDER_NAME_RE.test(name)) {
|
|
6710
|
+
throw new InvalidProviderNameError(name);
|
|
6726
6711
|
}
|
|
6727
|
-
const
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
const five = fiveHourWindow === void 0 ? void 0 : effectiveWindow(fiveHourWindow, nowMs);
|
|
6731
|
-
const seven = sevenDayWindow === void 0 ? void 0 : effectiveWindow(sevenDayWindow, nowMs);
|
|
6732
|
-
const windowReasons = [
|
|
6733
|
-
...fiveHourWindow === void 0 || five === void 0 ? [] : [describeWindow("5h", fiveHourWindow, five, nowMs)],
|
|
6734
|
-
...sevenDayWindow === void 0 || seven === void 0 ? [] : [describeWindow("7d", sevenDayWindow, seven, nowMs)]
|
|
6735
|
-
];
|
|
6736
|
-
const blockedUntil = Math.max(
|
|
6737
|
-
windowBlockedUntil([five, seven].flatMap((window) => window === void 0 ? [] : [window])) ?? 0,
|
|
6738
|
-
limitBlockedUntil(state, nowMs) ?? 0
|
|
6739
|
-
);
|
|
6740
|
-
if (blockedUntil > nowMs) {
|
|
6741
|
-
if (unified.overageStatus === "allowed") {
|
|
6742
|
-
return { candidate: { ...base, class: "pay-per-use", reasons: [`plan exhausted until ${new Date(blockedUntil).toISOString()}, continues as extra usage`, ...windowReasons, ...planReasons] } };
|
|
6743
|
-
}
|
|
6744
|
-
return { candidate: { ...base, class: "ineligible", blockedUntilMs: blockedUntil, reasons: [`refused until ${new Date(blockedUntil).toISOString()} (in ${formatAge(blockedUntil - nowMs)})`, ...windowReasons, ...planReasons] } };
|
|
6712
|
+
const kind = input.kind ?? "http";
|
|
6713
|
+
if (kind === "codex" && input.baseUrl !== void 0) {
|
|
6714
|
+
throw new ProviderKindMismatchError("A codex provider has no base URL: the launcher points its sessions at the front-door daemon. Drop --base-url.");
|
|
6745
6715
|
}
|
|
6746
|
-
if (
|
|
6747
|
-
|
|
6716
|
+
if (kind === "http" && input.baseUrl === void 0) {
|
|
6717
|
+
throw new ProviderKindMismatchError("An http provider needs --base-url.");
|
|
6748
6718
|
}
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
return { candidate: { ...base, class: "unknown", reasons: ["quota windows reported no utilisation", ...windowReasons, ...planReasons] } };
|
|
6719
|
+
if (kind === "http" && !isEmptyCodexConfig(input.codex)) {
|
|
6720
|
+
throw new ProviderKindMismatchError("Codex settings apply only to a codex provider (--kind codex).");
|
|
6752
6721
|
}
|
|
6753
|
-
const
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
const fiveUntil = five?.resetsAtMs === void 0 ? FIVE_HOUR_WINDOW_MS : Math.max(five.resetsAtMs - nowMs, MS_PER_SECOND4);
|
|
6758
|
-
const burn = fiveHourBurn(member.records);
|
|
6759
|
-
return {
|
|
6760
|
-
candidate: { ...base, class: "scored", score, reasons: [...windowReasons.map((reason, index) => index === windowReasons.length - 1 ? `${reason}${observedAgo}` : reason), ...planReasons] },
|
|
6761
|
-
...fiveRemaining === void 0 ? {} : { fiveHour: { remaining: fiveRemaining, untilResetMs: fiveUntil } },
|
|
6762
|
-
...burn === void 0 ? {} : { burn }
|
|
6722
|
+
const shared = {
|
|
6723
|
+
displayName: input.displayName,
|
|
6724
|
+
credential: { sources: input.sources, ...input.target === void 0 ? {} : { target: input.target } },
|
|
6725
|
+
...input.env === void 0 || Object.keys(input.env).length === 0 ? {} : { env: input.env }
|
|
6763
6726
|
};
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
const { members, nowMs } = input;
|
|
6767
|
-
const assessments = members.map((member) => assess(member, nowMs));
|
|
6768
|
-
const demands = assessments.flatMap(({ candidate, burn }) => burn === void 0 ? [] : [burn * capacityOf(candidate.plan)]);
|
|
6769
|
-
const sharedDemand = demands.length === 0 ? void 0 : Math.max(...demands);
|
|
6770
|
-
const candidates = assessments.map((assessment) => {
|
|
6771
|
-
const { candidate, fiveHour, burn } = assessment;
|
|
6772
|
-
const pace = burn ?? (sharedDemand === void 0 ? void 0 : sharedDemand / capacityOf(candidate.plan));
|
|
6773
|
-
if (candidate.class !== "scored" || fiveHour === void 0 || pace === void 0) {
|
|
6774
|
-
return { ...candidate, feasible: true };
|
|
6775
|
-
}
|
|
6776
|
-
const runsDryInMs = fiveHour.remaining / pace;
|
|
6777
|
-
if (runsDryInMs >= fiveHour.untilResetMs) {
|
|
6778
|
-
return { ...candidate, feasible: true };
|
|
6779
|
-
}
|
|
6780
|
-
return { ...candidate, feasible: false, reasons: [`5h window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(fiveHour.untilResetMs)}`, ...candidate.reasons] };
|
|
6781
|
-
});
|
|
6782
|
-
const ordered = [...candidates].sort(
|
|
6783
|
-
(left, right) => CLASS_ORDER[left.class] - CLASS_ORDER[right.class] || Number(right.feasible) - Number(left.feasible) || (right.score ?? 0) - (left.score ?? 0) || left.identity.localeCompare(right.identity)
|
|
6727
|
+
const parsed = ProviderSchema.safeParse(
|
|
6728
|
+
kind === "codex" ? { kind, ...shared, ...isEmptyCodexConfig(input.codex) ? {} : { codex: input.codex } } : { ...shared, baseUrl: input.baseUrl }
|
|
6784
6729
|
);
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
const stickyCandidate = ordered[stickyIndex];
|
|
6788
|
-
if (sticky !== void 0 && stickyCandidate?.class === "scored" && stickyCandidate.feasible) {
|
|
6789
|
-
const ageMs = nowMs - Date.parse(sticky.at);
|
|
6790
|
-
const warm = ageMs <= PROMPT_CACHE_TTL_MS;
|
|
6791
|
-
if (input.resuming || warm) {
|
|
6792
|
-
const why = warm ? `used here ${formatAge(ageMs)} ago, prompt cache still warm` : "resuming a conversation started on this account";
|
|
6793
|
-
ordered.splice(stickyIndex, 1);
|
|
6794
|
-
ordered.unshift({ ...stickyCandidate, reasons: [why, ...stickyCandidate.reasons] });
|
|
6795
|
-
}
|
|
6730
|
+
if (!parsed.success) {
|
|
6731
|
+
throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
|
|
6796
6732
|
}
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
|
|
6733
|
+
writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
|
|
6734
|
+
return parsed.data;
|
|
6735
|
+
}
|
|
6736
|
+
function updateProvider(paths, name, input) {
|
|
6737
|
+
const existing = readProvider(paths, name);
|
|
6738
|
+
if (existing === void 0) {
|
|
6739
|
+
throw new ProviderNotFoundError(name);
|
|
6740
|
+
}
|
|
6741
|
+
const unset = new Set(input.unsetEnv);
|
|
6742
|
+
const env = Object.fromEntries(Object.entries({ ...existing.env, ...input.env }).filter(([key]) => !unset.has(key)));
|
|
6743
|
+
const target = input.target ?? existing.credential.target;
|
|
6744
|
+
const cache = input.cache === void 0 ? existing.credential.cache : input.cache === false ? void 0 : input.cache;
|
|
6745
|
+
if (isCodexProvider(existing) && input.baseUrl !== void 0) {
|
|
6746
|
+
throw new ProviderKindMismatchError(`Provider "${name}" is a codex provider, which has no base URL.`);
|
|
6747
|
+
}
|
|
6748
|
+
if (!isCodexProvider(existing) && !isEmptyCodexConfig(input.codex)) {
|
|
6749
|
+
throw new ProviderKindMismatchError(`Provider "${name}" is an http provider; codex settings apply only to a codex provider.`);
|
|
6750
|
+
}
|
|
6751
|
+
const shared = {
|
|
6752
|
+
...input.displayName === void 0 ? {} : { displayName: input.displayName },
|
|
6753
|
+
credential: {
|
|
6754
|
+
sources: input.sources ?? existing.credential.sources,
|
|
6755
|
+
...target === void 0 ? {} : { target },
|
|
6756
|
+
...cache === void 0 ? {} : { cache }
|
|
6757
|
+
},
|
|
6758
|
+
env: Object.keys(env).length === 0 ? void 0 : env
|
|
6803
6759
|
};
|
|
6760
|
+
let candidate;
|
|
6761
|
+
if (isCodexProvider(existing)) {
|
|
6762
|
+
const codex = {
|
|
6763
|
+
...existing.codex,
|
|
6764
|
+
...input.codex,
|
|
6765
|
+
...existing.codex?.models === void 0 && input.codex?.models === void 0 ? {} : { models: { ...existing.codex?.models, ...input.codex?.models } }
|
|
6766
|
+
};
|
|
6767
|
+
candidate = { ...existing, ...shared, codex: isEmptyCodexConfig(codex) ? void 0 : codex };
|
|
6768
|
+
} else {
|
|
6769
|
+
candidate = { ...existing, ...shared, ...input.baseUrl === void 0 ? {} : { baseUrl: input.baseUrl } };
|
|
6770
|
+
}
|
|
6771
|
+
const parsed = ProviderSchema.safeParse(candidate);
|
|
6772
|
+
if (!parsed.success) {
|
|
6773
|
+
throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
|
|
6774
|
+
}
|
|
6775
|
+
writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
|
|
6776
|
+
return parsed.data;
|
|
6804
6777
|
}
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
// src/usage/poolPick.ts
|
|
6812
|
-
var SCHEMA_VERSION = 1;
|
|
6813
|
-
var PickEntrySchema = z9.strictObject({ identity: z9.string().min(1), at: z9.iso.datetime() });
|
|
6814
|
-
var PicksFileSchema = z9.strictObject({
|
|
6815
|
-
schemaVersion: z9.literal(SCHEMA_VERSION),
|
|
6816
|
-
picks: z9.record(z9.string().min(1), PickEntrySchema)
|
|
6817
|
-
});
|
|
6818
|
-
function errorMessage(error) {
|
|
6819
|
-
return error instanceof Error ? error.message : String(error);
|
|
6778
|
+
function removeProvider(paths, name) {
|
|
6779
|
+
if (!providerExists(paths, name)) {
|
|
6780
|
+
throw new ProviderNotFoundError(name);
|
|
6781
|
+
}
|
|
6782
|
+
fs6.rmSync(providerJsonPath(paths, name));
|
|
6820
6783
|
}
|
|
6821
|
-
function
|
|
6822
|
-
|
|
6823
|
-
const
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
try {
|
|
6827
|
-
const snapshot = readUsageSnapshot(fs12, paths.usageSnapshotsDir, identity);
|
|
6828
|
-
const account = readAccountMetadata(fs12, paths.identitiesDir, identity) ?? snapshot?.account;
|
|
6829
|
-
return { identity, records, ...snapshot === void 0 ? {} : { snapshot }, ...account === void 0 ? {} : { account } };
|
|
6830
|
-
} catch (error) {
|
|
6831
|
-
if (error instanceof UsageSnapshotError || error instanceof AccountMetadataError) {
|
|
6832
|
-
return { identity, records, readError: errorMessage(error) };
|
|
6833
|
-
}
|
|
6834
|
-
throw error;
|
|
6784
|
+
function cascadeProviderName(cascade) {
|
|
6785
|
+
let name;
|
|
6786
|
+
for (const layer of assembleCascade(cascade).layers) {
|
|
6787
|
+
if (layer.launch?.provider !== void 0) {
|
|
6788
|
+
name = layer.launch.provider;
|
|
6835
6789
|
}
|
|
6836
|
-
}
|
|
6790
|
+
}
|
|
6791
|
+
return name;
|
|
6837
6792
|
}
|
|
6838
|
-
function
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6793
|
+
function resolveProvider(params) {
|
|
6794
|
+
const cliName = params.cliProvider !== void 0 && params.cliProvider !== "" ? params.cliProvider : void 0;
|
|
6795
|
+
const name = cliName ?? (params.cascade === void 0 ? void 0 : cascadeProviderName(params.cascade));
|
|
6796
|
+
if (name === void 0) {
|
|
6797
|
+
return void 0;
|
|
6798
|
+
}
|
|
6799
|
+
const definition = loadProvider(params.paths.providersDir, name, params.port);
|
|
6800
|
+
if (definition === void 0) {
|
|
6801
|
+
const known = listProviderNames(params.port.readdir, params.paths.providersDir);
|
|
6802
|
+
return {
|
|
6803
|
+
ok: false,
|
|
6804
|
+
status: 1,
|
|
6805
|
+
message: `agent-shim: no provider named "${name}". ` + (known.length > 0 ? `Known providers: ${known.join(", ")}.` : "No providers are defined yet; run `agent-shim provider add`.")
|
|
6806
|
+
};
|
|
6807
|
+
}
|
|
6808
|
+
if (params.credentials === void 0) {
|
|
6809
|
+
return { ok: false, status: 1, message: `agent-shim: provider ${name} needs its credential resolved, but this launcher has no credential port wired` };
|
|
6810
|
+
}
|
|
6811
|
+
const resolution = resolveCredential({ credential: definition.credential, env: params.env, port: params.credentials, subject: `provider ${name}` });
|
|
6812
|
+
if (!resolution.ok) {
|
|
6813
|
+
return { ok: false, status: CREDENTIAL_UNAVAILABLE_EXIT, message: resolution.message };
|
|
6814
|
+
}
|
|
6815
|
+
return { ok: true, provider: { name, definition, credential: resolution.credential }, warnings: resolution.credential.warnings };
|
|
6845
6816
|
}
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6817
|
+
|
|
6818
|
+
// src/directoryRulesStore.ts
|
|
6819
|
+
var DirectoryRuleNotFoundError = class extends CliError {
|
|
6820
|
+
constructor(rulePath) {
|
|
6821
|
+
super(`No directory rule found for path "${rulePath}".`);
|
|
6822
|
+
this.rulePath = rulePath;
|
|
6823
|
+
this.name = "DirectoryRuleNotFoundError";
|
|
6851
6824
|
}
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
|
|
6825
|
+
rulePath;
|
|
6826
|
+
};
|
|
6827
|
+
var DirectoryRuleMissingTargetError = class extends CliError {
|
|
6828
|
+
constructor() {
|
|
6829
|
+
super("A directory rule must set at least one of --config-profile or --identity.");
|
|
6830
|
+
this.name = "DirectoryRuleMissingTargetError";
|
|
6857
6831
|
}
|
|
6858
|
-
|
|
6859
|
-
|
|
6832
|
+
};
|
|
6833
|
+
var DirectoryRuleAlreadyExistsError = class extends CliError {
|
|
6834
|
+
constructor(rulePath) {
|
|
6835
|
+
super(`A directory rule for path "${rulePath}" already exists. Use \`agent-shim rule set\` to change it.`);
|
|
6836
|
+
this.rulePath = rulePath;
|
|
6837
|
+
this.name = "DirectoryRuleAlreadyExistsError";
|
|
6838
|
+
}
|
|
6839
|
+
rulePath;
|
|
6840
|
+
};
|
|
6841
|
+
function readDirectoryRules(paths) {
|
|
6842
|
+
return readJson(paths.directoryRulesFile, DirectoryRulesSchema) ?? { rules: [] };
|
|
6860
6843
|
}
|
|
6861
|
-
function
|
|
6862
|
-
const
|
|
6863
|
-
|
|
6864
|
-
|
|
6844
|
+
function writeDirectoryRules(paths, rules) {
|
|
6845
|
+
const parsed = DirectoryRulesSchema.safeParse(rules);
|
|
6846
|
+
if (!parsed.success) {
|
|
6847
|
+
throw new ConfigValidationError(paths.directoryRulesFile, parsed.error.issues);
|
|
6848
|
+
}
|
|
6849
|
+
writeJsonAtomic(paths.directoryRulesFile, parsed.data);
|
|
6865
6850
|
}
|
|
6866
|
-
function
|
|
6867
|
-
|
|
6868
|
-
const kept = Object.entries(current).filter(([, entry]) => nowMs - Date.parse(entry.at) <= USAGE_RETENTION_MS);
|
|
6869
|
-
const next = { schemaVersion: SCHEMA_VERSION, picks: { ...Object.fromEntries(kept), [directory]: { identity, at: new Date(nowMs).toISOString() } } };
|
|
6870
|
-
fs12.mkdirPrivate(path19.dirname(picksFile));
|
|
6871
|
-
fs12.writeFilePrivate(picksFile, `${JSON.stringify(next, null, 2)}
|
|
6872
|
-
`);
|
|
6851
|
+
function listDirectoryRules(paths) {
|
|
6852
|
+
return readDirectoryRules(paths).rules;
|
|
6873
6853
|
}
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6854
|
+
function addDirectoryRule(paths, rulePath, options) {
|
|
6855
|
+
if (options.configProfile === void 0 && options.identity === void 0) {
|
|
6856
|
+
throw new DirectoryRuleMissingTargetError();
|
|
6857
|
+
}
|
|
6858
|
+
const current = readDirectoryRules(paths);
|
|
6859
|
+
if (current.rules.some((rule) => rule.path === rulePath)) {
|
|
6860
|
+
throw new DirectoryRuleAlreadyExistsError(rulePath);
|
|
6861
|
+
}
|
|
6862
|
+
const created = buildNewRule(rulePath, options);
|
|
6863
|
+
writeDirectoryRules(paths, { ...current, rules: [...current.rules, created] });
|
|
6864
|
+
return created;
|
|
6882
6865
|
}
|
|
6883
|
-
function
|
|
6884
|
-
|
|
6885
|
-
return (end === -1 ? passthrough : passthrough.slice(0, end)).some((token) => RESUME_FLAGS.has(token));
|
|
6866
|
+
function ruleHasEffect(rule) {
|
|
6867
|
+
return rule.configProfile !== void 0 || rule.identity !== void 0 || rule.categories !== void 0 || rule.entries !== void 0 || rule.launch !== void 0;
|
|
6886
6868
|
}
|
|
6887
|
-
function
|
|
6888
|
-
const
|
|
6889
|
-
const
|
|
6890
|
-
|
|
6891
|
-
|
|
6869
|
+
function updateDirectoryRule(paths, rulePath, options) {
|
|
6870
|
+
const current = readDirectoryRules(paths);
|
|
6871
|
+
const index = current.rules.findIndex((rule) => rule.path === rulePath);
|
|
6872
|
+
const existing = index === -1 ? void 0 : current.rules[index];
|
|
6873
|
+
if (existing === void 0) {
|
|
6874
|
+
throw new DirectoryRuleNotFoundError(rulePath);
|
|
6892
6875
|
}
|
|
6893
|
-
const {
|
|
6894
|
-
|
|
6895
|
-
|
|
6876
|
+
const { configProfile, identity, ...rest } = existing;
|
|
6877
|
+
const nextConfigProfile = options.configProfile === void 0 ? configProfile : options.configProfile === false ? void 0 : options.configProfile;
|
|
6878
|
+
const nextIdentity = options.identity === void 0 ? identity : options.identity === false ? void 0 : options.identity;
|
|
6879
|
+
const updated = {
|
|
6880
|
+
...rest,
|
|
6881
|
+
...nextConfigProfile === void 0 ? {} : { configProfile: nextConfigProfile },
|
|
6882
|
+
...nextIdentity === void 0 ? {} : { identity: nextIdentity }
|
|
6883
|
+
};
|
|
6884
|
+
if (!ruleHasEffect(updated)) {
|
|
6885
|
+
throw new DirectoryRuleMissingTargetError();
|
|
6896
6886
|
}
|
|
6897
|
-
|
|
6898
|
-
|
|
6887
|
+
const nextRules = [...current.rules];
|
|
6888
|
+
nextRules[index] = updated;
|
|
6889
|
+
writeDirectoryRules(paths, { ...current, rules: nextRules });
|
|
6890
|
+
return updated;
|
|
6891
|
+
}
|
|
6892
|
+
function buildNewRule(rulePath, options) {
|
|
6893
|
+
return {
|
|
6894
|
+
path: rulePath,
|
|
6895
|
+
...options.configProfile !== void 0 ? { configProfile: options.configProfile } : {},
|
|
6896
|
+
...options.identity !== void 0 ? { identity: options.identity } : {}
|
|
6897
|
+
};
|
|
6898
|
+
}
|
|
6899
|
+
function removeDirectoryRule(paths, rulePath) {
|
|
6900
|
+
const current = readDirectoryRules(paths);
|
|
6901
|
+
const nextRules = current.rules.filter((rule) => rule.path !== rulePath);
|
|
6902
|
+
if (nextRules.length === current.rules.length) {
|
|
6903
|
+
throw new DirectoryRuleNotFoundError(rulePath);
|
|
6899
6904
|
}
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
6905
|
+
writeDirectoryRules(paths, { ...current, rules: nextRules });
|
|
6906
|
+
}
|
|
6907
|
+
|
|
6908
|
+
// src/checkReport.ts
|
|
6909
|
+
import os4 from "node:os";
|
|
6910
|
+
|
|
6911
|
+
// src/launcher/cascade.ts
|
|
6912
|
+
import path19 from "node:path";
|
|
6913
|
+
|
|
6914
|
+
// src/pathNorm.ts
|
|
6915
|
+
import path18 from "node:path";
|
|
6916
|
+
function expandTilde(value, home) {
|
|
6917
|
+
if (value === "~") {
|
|
6918
|
+
return home;
|
|
6904
6919
|
}
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6920
|
+
if (value.startsWith("~/")) {
|
|
6921
|
+
return path18.join(home, value.slice(2));
|
|
6922
|
+
}
|
|
6923
|
+
return value;
|
|
6924
|
+
}
|
|
6925
|
+
function normaliseRulePath(rulePath, home) {
|
|
6926
|
+
return path18.resolve(expandTilde(rulePath, home));
|
|
6927
|
+
}
|
|
6928
|
+
|
|
6929
|
+
// src/launcher/cascade.ts
|
|
6930
|
+
var PORTABLE_CONFIG_FILENAME = ".agent-shim.json";
|
|
6931
|
+
var PORTABLE_LOCAL_CONFIG_FILENAME = ".agent-shim.local.json";
|
|
6932
|
+
function loadCascadeInput(params) {
|
|
6933
|
+
const globalConfig = loadConfigFile(params.paths.globalConfigFile, GlobalConfigSchema, params.read);
|
|
6934
|
+
const directoryRules = loadConfigFile(params.paths.directoryRulesFile, DirectoryRulesSchema, params.read);
|
|
6935
|
+
const loadProfile = (name) => {
|
|
6936
|
+
const filepath = path19.join(params.paths.configProfilesDir, `${name}.json`);
|
|
6937
|
+
const loaded = loadConfigFile(filepath, ConfigProfileSchema, params.read);
|
|
6938
|
+
if (loaded === void 0) {
|
|
6939
|
+
return void 0;
|
|
6915
6940
|
}
|
|
6916
|
-
|
|
6917
|
-
|
|
6941
|
+
return { name, profile: loaded.config, entryOrder: loaded.entryOrder, filepath: loaded.filepath };
|
|
6942
|
+
};
|
|
6943
|
+
const limit = globalConfig?.config.walkUpLimit;
|
|
6944
|
+
const dirs = walkDirectoryAncestors(params.cwd, {
|
|
6945
|
+
home: params.home,
|
|
6946
|
+
...limit === void 0 ? {} : { limit: expandTilde(limit, params.home) },
|
|
6947
|
+
...params.isReadable === void 0 ? {} : { isReadable: params.isReadable }
|
|
6948
|
+
});
|
|
6949
|
+
const levels = [];
|
|
6950
|
+
for (const dir of dirs) {
|
|
6951
|
+
const portable = loadConfigFile(path19.join(dir, PORTABLE_CONFIG_FILENAME), PortableConfigSchema, params.read) ?? loadConfigFile(path19.join(dir, LEGACY_PORTABLE_CONFIG_FILENAME), PortableConfigSchema, params.read);
|
|
6952
|
+
const portableLocal = loadConfigFile(path19.join(dir, PORTABLE_LOCAL_CONFIG_FILENAME), PortableConfigSchema, params.read) ?? loadConfigFile(path19.join(dir, LEGACY_PORTABLE_LOCAL_CONFIG_FILENAME), PortableConfigSchema, params.read);
|
|
6953
|
+
const rules = (directoryRules?.config.rules ?? []).map((rule, index) => ({ rule, index })).filter(({ rule }) => normaliseRulePath(rule.path, params.home) === dir).map(({ rule, index }) => {
|
|
6954
|
+
const entryOrder = directoryRules?.ruleEntryOrders[index];
|
|
6918
6955
|
return {
|
|
6919
|
-
|
|
6920
|
-
|
|
6956
|
+
rule,
|
|
6957
|
+
...entryOrder === void 0 ? {} : { entryOrder },
|
|
6958
|
+
filepath: params.paths.directoryRulesFile
|
|
6921
6959
|
};
|
|
6960
|
+
});
|
|
6961
|
+
if (portable === void 0 && portableLocal === void 0 && rules.length === 0) {
|
|
6962
|
+
continue;
|
|
6922
6963
|
}
|
|
6923
|
-
|
|
6924
|
-
|
|
6964
|
+
levels.push({
|
|
6965
|
+
dir,
|
|
6966
|
+
...portable === void 0 ? {} : { portable: { config: portable.config, entryOrder: portable.entryOrder, filepath: portable.filepath } },
|
|
6967
|
+
...rules.length === 0 ? {} : { rules },
|
|
6968
|
+
...portableLocal === void 0 ? {} : { portableLocal: { config: portableLocal.config, entryOrder: portableLocal.entryOrder, filepath: portableLocal.filepath } }
|
|
6969
|
+
});
|
|
6925
6970
|
}
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
...candidate.score === void 0 ? {} : { score: candidate.score },
|
|
6934
|
-
feasible: candidate.feasible,
|
|
6935
|
-
...candidate.blockedUntilMs === void 0 ? {} : { blockedUntil: new Date(candidate.blockedUntilMs).toISOString() },
|
|
6936
|
-
plan: candidate.plan,
|
|
6937
|
-
reasons: candidate.reasons
|
|
6971
|
+
const input = {
|
|
6972
|
+
home: params.home,
|
|
6973
|
+
...globalConfig === void 0 ? {} : { globalConfig: { config: globalConfig.config, entryOrder: globalConfig.entryOrder, filepath: globalConfig.filepath } },
|
|
6974
|
+
...params.baseConfigProfile === void 0 ? {} : { baseConfigProfile: params.baseConfigProfile },
|
|
6975
|
+
loadProfile,
|
|
6976
|
+
levels,
|
|
6977
|
+
...params.cliOverride === void 0 ? {} : { cliOverride: params.cliOverride }
|
|
6938
6978
|
};
|
|
6979
|
+
return { input, ...globalConfig === void 0 ? {} : { globalConfig: globalConfig.config } };
|
|
6939
6980
|
}
|
|
6940
|
-
function
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
const
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
...ranking.earliestReturn === void 0 ? {} : { earliestReturn: { identity: ranking.earliestReturn.identity, at: new Date(ranking.earliestReturn.atMs).toISOString() } },
|
|
6958
|
-
...sticky.problem === void 0 ? {} : { stickyProblem: sticky.problem }
|
|
6959
|
-
};
|
|
6981
|
+
function readDirectorySelections(loaded) {
|
|
6982
|
+
let identity;
|
|
6983
|
+
let configProfile;
|
|
6984
|
+
for (const level of loaded.input.levels ?? []) {
|
|
6985
|
+
for (const source of [level.portable?.config, ...(level.rules ?? []).map((entry) => entry.rule), level.portableLocal?.config]) {
|
|
6986
|
+
if (source === void 0) {
|
|
6987
|
+
continue;
|
|
6988
|
+
}
|
|
6989
|
+
if (source.identity !== void 0) {
|
|
6990
|
+
identity = source.identity;
|
|
6991
|
+
}
|
|
6992
|
+
if (source.configProfile !== void 0) {
|
|
6993
|
+
configProfile = source.configProfile;
|
|
6994
|
+
}
|
|
6995
|
+
}
|
|
6996
|
+
}
|
|
6997
|
+
return { ...identity === void 0 ? {} : { identity }, ...configProfile === void 0 ? {} : { configProfile } };
|
|
6960
6998
|
}
|
|
6961
6999
|
|
|
6962
7000
|
// src/realCredentialCache.ts
|
|
@@ -7519,7 +7557,7 @@ function realResolveClaudeBinary(isOwnBinary) {
|
|
|
7519
7557
|
|
|
7520
7558
|
// src/checkReport.ts
|
|
7521
7559
|
import path23 from "node:path";
|
|
7522
|
-
import { z as
|
|
7560
|
+
import { z as z12 } from "zod";
|
|
7523
7561
|
|
|
7524
7562
|
// src/cli/bool.ts
|
|
7525
7563
|
function parseBoolWord(input) {
|
|
@@ -7634,9 +7672,9 @@ function lookupKeychainService(run, farmRoot) {
|
|
|
7634
7672
|
note: serviceName === void 0 ? `A Keychain entry was found for account "${farmRoot}" but its service name could not be parsed from security's output.` : `Keychain service name for this identity: "${serviceName}".`
|
|
7635
7673
|
};
|
|
7636
7674
|
}
|
|
7637
|
-
var SettingsSecretsShapeSchema =
|
|
7638
|
-
env:
|
|
7639
|
-
hooks:
|
|
7675
|
+
var SettingsSecretsShapeSchema = z12.looseObject({
|
|
7676
|
+
env: z12.record(z12.string(), z12.unknown()).optional(),
|
|
7677
|
+
hooks: z12.record(z12.string(), z12.array(z12.unknown())).optional()
|
|
7640
7678
|
});
|
|
7641
7679
|
function hookCommandCountOf(group) {
|
|
7642
7680
|
if (typeof group !== "object" || group === null) {
|
|
@@ -7930,11 +7968,11 @@ function collectCheckReport(params) {
|
|
|
7930
7968
|
// src/claudeShim.ts
|
|
7931
7969
|
import fs9 from "node:fs";
|
|
7932
7970
|
import path24 from "node:path";
|
|
7933
|
-
import { z as
|
|
7934
|
-
var ClaudeShimStateSchema =
|
|
7935
|
-
targetPath:
|
|
7936
|
-
method:
|
|
7937
|
-
installedAtMs:
|
|
7971
|
+
import { z as z13 } from "zod";
|
|
7972
|
+
var ClaudeShimStateSchema = z13.strictObject({
|
|
7973
|
+
targetPath: z13.string().min(1),
|
|
7974
|
+
method: z13.enum(["hardlink", "copy"]),
|
|
7975
|
+
installedAtMs: z13.number()
|
|
7938
7976
|
});
|
|
7939
7977
|
function commandFilename(ownExecutablePath, commandName) {
|
|
7940
7978
|
return ownExecutablePath.toLowerCase().endsWith(".exe") ? `${commandName}.exe` : commandName;
|
|
@@ -9389,8 +9427,11 @@ export {
|
|
|
9389
9427
|
InvalidProviderNameError,
|
|
9390
9428
|
LaunchRefusedError,
|
|
9391
9429
|
LegacyProviderFileError,
|
|
9430
|
+
PROMPT_CACHE_TTL_MS,
|
|
9431
|
+
PlanClassSchema,
|
|
9392
9432
|
PoolNameSchema,
|
|
9393
9433
|
PoolNotFoundError,
|
|
9434
|
+
PoolPickReportSchema,
|
|
9394
9435
|
PoolSchema,
|
|
9395
9436
|
PortableConfigSchema,
|
|
9396
9437
|
ProfileAlreadyExistsError,
|
|
@@ -9427,16 +9468,19 @@ export {
|
|
|
9427
9468
|
checkReportToJson,
|
|
9428
9469
|
collectCheckReport,
|
|
9429
9470
|
collectDoctorReport,
|
|
9471
|
+
collectPoolPick,
|
|
9430
9472
|
createLeafCache,
|
|
9431
9473
|
createProfile,
|
|
9432
9474
|
createRcControlHandler,
|
|
9433
9475
|
createRcSessionTracker,
|
|
9434
9476
|
describeProviderEndpoint,
|
|
9435
9477
|
detectAmbientCredential,
|
|
9478
|
+
effectiveWindow,
|
|
9436
9479
|
ensureCa,
|
|
9437
9480
|
ensureFrontDoor,
|
|
9438
9481
|
ensureHeadroom,
|
|
9439
9482
|
evaluateAmbientCredentialGuard,
|
|
9483
|
+
evaluateWhen,
|
|
9440
9484
|
formatAmbientCredentialGuardMessage,
|
|
9441
9485
|
formatCheckReport,
|
|
9442
9486
|
formatDoctorReport,
|
|
@@ -9454,13 +9498,16 @@ export {
|
|
|
9454
9498
|
listProfiles,
|
|
9455
9499
|
listProviders,
|
|
9456
9500
|
listUsageSnapshots,
|
|
9501
|
+
matchBranch,
|
|
9457
9502
|
mintLeaf,
|
|
9458
9503
|
observingRoutedRoute,
|
|
9459
9504
|
parseConnectTarget,
|
|
9505
|
+
planOf,
|
|
9460
9506
|
prepareClaudeLaunch,
|
|
9461
9507
|
prepareLaunch,
|
|
9462
9508
|
profileExists,
|
|
9463
9509
|
providerExists,
|
|
9510
|
+
rankPool,
|
|
9464
9511
|
rcEventWriteResultFromAnswer,
|
|
9465
9512
|
rcObserverAsPassthrough,
|
|
9466
9513
|
readActiveIdentity,
|