agent-shim 8.18.0 → 8.20.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/README.md +3 -3
- package/dist/cli.cjs +267 -96
- package/dist/index.cjs +289 -130
- package/dist/index.mjs +289 -130
- package/dist/types/index.d.ts +57 -16
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -571,9 +571,14 @@ var POOL_SELECTOR_PREFIX = "pool:";
|
|
|
571
571
|
var POOL_NAME_PATTERN = "[A-Za-z0-9][A-Za-z0-9._-]*";
|
|
572
572
|
var IDENTITY_NAME_PATTERN = "[A-Za-z0-9][A-Za-z0-9._@-]*";
|
|
573
573
|
var PoolNameSchema = import_zod.z.string().min(1).regex(new RegExp(`^${POOL_NAME_PATTERN}$`));
|
|
574
|
-
var
|
|
574
|
+
var SELECTOR_RE = new RegExp(`^(${POOL_SELECTOR_PREFIX}${POOL_NAME_PATTERN}|${IDENTITY_NAME_PATTERN})$`);
|
|
575
|
+
var IdentitySelectorSchema = import_zod.z.string().min(1).regex(SELECTOR_RE);
|
|
576
|
+
var PoolMemberSchema = import_zod.z.string().min(1).regex(SELECTOR_RE);
|
|
577
|
+
var POOL_PREFERENCES = ["score", "listed"];
|
|
578
|
+
var PoolPreferenceSchema = import_zod.z.enum(POOL_PREFERENCES);
|
|
575
579
|
var PoolSchema = import_zod.z.strictObject({
|
|
576
|
-
identities: import_zod.z.array(
|
|
580
|
+
identities: import_zod.z.array(PoolMemberSchema).min(1).refine((members) => new Set(members).size === members.length, { message: "pool members must be unique" }),
|
|
581
|
+
preference: PoolPreferenceSchema.optional()
|
|
577
582
|
});
|
|
578
583
|
var DirectoryRuleSchema = ConfigProfileSchema.omit({ description: true }).extend({
|
|
579
584
|
path: import_zod.z.string().min(1),
|
|
@@ -5616,54 +5621,6 @@ function resolveLayoutPaths() {
|
|
|
5616
5621
|
return buildLayoutPaths(resolveAgentShimHome());
|
|
5617
5622
|
}
|
|
5618
5623
|
|
|
5619
|
-
// src/usage/preflight.ts
|
|
5620
|
-
var MS_PER_SECOND2 = 1e3;
|
|
5621
|
-
var SECONDS_PER_MINUTE = 60;
|
|
5622
|
-
var MINUTES_PER_HOUR = 60;
|
|
5623
|
-
var HOURS_PER_DAY = 24;
|
|
5624
|
-
var WARNING_STATUSES = /* @__PURE__ */ new Set(["allowed_warning", "rejected"]);
|
|
5625
|
-
function formatAge(ms) {
|
|
5626
|
-
const minutes = Math.floor(ms / (MS_PER_SECOND2 * SECONDS_PER_MINUTE));
|
|
5627
|
-
if (minutes < MINUTES_PER_HOUR) {
|
|
5628
|
-
return `${String(Math.max(minutes, 0))}m`;
|
|
5629
|
-
}
|
|
5630
|
-
const hours = Math.floor(minutes / MINUTES_PER_HOUR);
|
|
5631
|
-
return hours < HOURS_PER_DAY ? `${String(hours)}h` : `${String(Math.floor(hours / HOURS_PER_DAY))}d`;
|
|
5632
|
-
}
|
|
5633
|
-
function effectiveWindow(window, nowMs) {
|
|
5634
|
-
const resetsAtMs = window.resetsAt === void 0 ? void 0 : Date.parse(window.resetsAt);
|
|
5635
|
-
if (resetsAtMs !== void 0 && resetsAtMs <= nowMs) {
|
|
5636
|
-
return { reset: true, utilization: 0 };
|
|
5637
|
-
}
|
|
5638
|
-
return {
|
|
5639
|
-
reset: false,
|
|
5640
|
-
...window.utilization === void 0 ? {} : { utilization: window.utilization },
|
|
5641
|
-
...resetsAtMs === void 0 ? {} : { resetsAtMs },
|
|
5642
|
-
...window.status === void 0 ? {} : { status: window.status }
|
|
5643
|
-
};
|
|
5644
|
-
}
|
|
5645
|
-
function quotaWarnings(snapshot, provider, nowMs) {
|
|
5646
|
-
const rateLimit = snapshot?.providers[provider]?.rateLimit;
|
|
5647
|
-
const unified = rateLimit?.unified;
|
|
5648
|
-
if (snapshot === void 0 || rateLimit === void 0 || unified === void 0) {
|
|
5649
|
-
return [];
|
|
5650
|
-
}
|
|
5651
|
-
const age = formatAge(nowMs - Date.parse(rateLimit.observedAt));
|
|
5652
|
-
const windows = [
|
|
5653
|
-
{ name: "five-hour", window: unified.fiveHour },
|
|
5654
|
-
{ name: "seven-day", window: unified.sevenDay }
|
|
5655
|
-
];
|
|
5656
|
-
return windows.flatMap(({ name, window }) => {
|
|
5657
|
-
const effective = window === void 0 ? void 0 : effectiveWindow(window, nowMs);
|
|
5658
|
-
if (window === void 0 || effective?.status === void 0 || !WARNING_STATUSES.has(effective.status)) {
|
|
5659
|
-
return [];
|
|
5660
|
-
}
|
|
5661
|
-
const reset = window.resetsAt === void 0 ? "" : `, resets ${window.resetsAt}`;
|
|
5662
|
-
const verdict = effective.status === "rejected" ? "exhausted" : "nearly used";
|
|
5663
|
-
return [`agent-shim: identity ${snapshot.identity}: the ${name} quota is ${verdict}${reset} (last seen ${age} ago)`];
|
|
5664
|
-
});
|
|
5665
|
-
}
|
|
5666
|
-
|
|
5667
5624
|
// src/usage/poolPick.ts
|
|
5668
5625
|
var import_node_path17 = __toESM(require("node:path"), 1);
|
|
5669
5626
|
var import_zod9 = require("zod");
|
|
@@ -5910,13 +5867,13 @@ function planOf(account) {
|
|
|
5910
5867
|
}
|
|
5911
5868
|
|
|
5912
5869
|
// src/usage/rateLimit.ts
|
|
5913
|
-
var
|
|
5870
|
+
var MS_PER_SECOND2 = 1e3;
|
|
5914
5871
|
function epochSecondsToIso(value) {
|
|
5915
5872
|
if (value === void 0) {
|
|
5916
5873
|
return void 0;
|
|
5917
5874
|
}
|
|
5918
5875
|
const seconds = Number(value);
|
|
5919
|
-
return Number.isFinite(seconds) && seconds > 0 ? new Date(seconds *
|
|
5876
|
+
return Number.isFinite(seconds) && seconds > 0 ? new Date(seconds * MS_PER_SECOND2).toISOString() : void 0;
|
|
5920
5877
|
}
|
|
5921
5878
|
function nonNegativeNumber(value) {
|
|
5922
5879
|
if (value === void 0 || value.trim() === "") {
|
|
@@ -5972,6 +5929,54 @@ var ErrorObjectSchema = import_zod8.z.looseObject({
|
|
|
5972
5929
|
// src/usage/middleware.ts
|
|
5973
5930
|
var ANTHROPIC_PROVIDER = "anthropic";
|
|
5974
5931
|
|
|
5932
|
+
// src/usage/preflight.ts
|
|
5933
|
+
var MS_PER_SECOND3 = 1e3;
|
|
5934
|
+
var SECONDS_PER_MINUTE = 60;
|
|
5935
|
+
var MINUTES_PER_HOUR = 60;
|
|
5936
|
+
var HOURS_PER_DAY = 24;
|
|
5937
|
+
var WARNING_STATUSES = /* @__PURE__ */ new Set(["allowed_warning", "rejected"]);
|
|
5938
|
+
function formatAge(ms) {
|
|
5939
|
+
const minutes = Math.floor(ms / (MS_PER_SECOND3 * SECONDS_PER_MINUTE));
|
|
5940
|
+
if (minutes < MINUTES_PER_HOUR) {
|
|
5941
|
+
return `${String(Math.max(minutes, 0))}m`;
|
|
5942
|
+
}
|
|
5943
|
+
const hours = Math.floor(minutes / MINUTES_PER_HOUR);
|
|
5944
|
+
return hours < HOURS_PER_DAY ? `${String(hours)}h` : `${String(Math.floor(hours / HOURS_PER_DAY))}d`;
|
|
5945
|
+
}
|
|
5946
|
+
function effectiveWindow(window, nowMs) {
|
|
5947
|
+
const resetsAtMs = window.resetsAt === void 0 ? void 0 : Date.parse(window.resetsAt);
|
|
5948
|
+
if (resetsAtMs !== void 0 && resetsAtMs <= nowMs) {
|
|
5949
|
+
return { reset: true, utilization: 0 };
|
|
5950
|
+
}
|
|
5951
|
+
return {
|
|
5952
|
+
reset: false,
|
|
5953
|
+
...window.utilization === void 0 ? {} : { utilization: window.utilization },
|
|
5954
|
+
...resetsAtMs === void 0 ? {} : { resetsAtMs },
|
|
5955
|
+
...window.status === void 0 ? {} : { status: window.status }
|
|
5956
|
+
};
|
|
5957
|
+
}
|
|
5958
|
+
function quotaWarnings(snapshot, provider, nowMs) {
|
|
5959
|
+
const rateLimit = snapshot?.providers[provider]?.rateLimit;
|
|
5960
|
+
const unified = rateLimit?.unified;
|
|
5961
|
+
if (snapshot === void 0 || rateLimit === void 0 || unified === void 0) {
|
|
5962
|
+
return [];
|
|
5963
|
+
}
|
|
5964
|
+
const age = formatAge(nowMs - Date.parse(rateLimit.observedAt));
|
|
5965
|
+
const windows = [
|
|
5966
|
+
{ name: "five-hour", window: unified.fiveHour },
|
|
5967
|
+
{ name: "seven-day", window: unified.sevenDay }
|
|
5968
|
+
];
|
|
5969
|
+
return windows.flatMap(({ name, window }) => {
|
|
5970
|
+
const effective = window === void 0 ? void 0 : effectiveWindow(window, nowMs);
|
|
5971
|
+
if (window === void 0 || effective?.status === void 0 || !WARNING_STATUSES.has(effective.status)) {
|
|
5972
|
+
return [];
|
|
5973
|
+
}
|
|
5974
|
+
const reset = window.resetsAt === void 0 ? "" : `, resets ${window.resetsAt}`;
|
|
5975
|
+
const verdict = effective.status === "rejected" ? "exhausted" : "nearly used";
|
|
5976
|
+
return [`agent-shim: identity ${snapshot.identity}: the ${name} quota is ${verdict}${reset} (last seen ${age} ago)`];
|
|
5977
|
+
});
|
|
5978
|
+
}
|
|
5979
|
+
|
|
5975
5980
|
// src/usage/pick.ts
|
|
5976
5981
|
var MS_PER_SECOND4 = 1e3;
|
|
5977
5982
|
var MS_PER_HOUR = 36e5;
|
|
@@ -6033,7 +6038,30 @@ function describeWindow(label, window, effective, nowMs) {
|
|
|
6033
6038
|
function capacityOf(plan) {
|
|
6034
6039
|
return plan.kind === "subscription" ? plan.capacity : 1;
|
|
6035
6040
|
}
|
|
6041
|
+
function assessRefusedNested(member, nested, nowMs) {
|
|
6042
|
+
const refusal = nested.earliestReturn === void 0 ? { reason: `no member of pool "${nested.pool}" can be picked` } : { reason: `every member of pool "${nested.pool}" is refused; ${nested.earliestReturn.identity} returns at ${new Date(nested.earliestReturn.atMs).toISOString()} (in ${formatAge(nested.earliestReturn.atMs - nowMs)})`, blockedUntilMs: nested.earliestReturn.atMs };
|
|
6043
|
+
return {
|
|
6044
|
+
candidate: {
|
|
6045
|
+
identity: member.identity,
|
|
6046
|
+
class: "ineligible",
|
|
6047
|
+
...refusal.blockedUntilMs === void 0 ? {} : { blockedUntilMs: refusal.blockedUntilMs },
|
|
6048
|
+
plan: planOf(member.account),
|
|
6049
|
+
reasons: [refusal.reason]
|
|
6050
|
+
}
|
|
6051
|
+
};
|
|
6052
|
+
}
|
|
6036
6053
|
function assess(member, nowMs) {
|
|
6054
|
+
const nested = member.nested;
|
|
6055
|
+
if (nested === void 0) {
|
|
6056
|
+
return assessIdentity(member, nowMs);
|
|
6057
|
+
}
|
|
6058
|
+
if (nested.kind === "refused") {
|
|
6059
|
+
return assessRefusedNested(member, nested, nowMs);
|
|
6060
|
+
}
|
|
6061
|
+
const assessment = assessIdentity(member, nowMs);
|
|
6062
|
+
return { ...assessment, candidate: { ...assessment.candidate, reasons: [`picked by pool "${nested.pool}": ${nested.reasons.join("; ")}`, ...assessment.candidate.reasons] } };
|
|
6063
|
+
}
|
|
6064
|
+
function assessIdentity(member, nowMs) {
|
|
6037
6065
|
const plan = planOf(member.account);
|
|
6038
6066
|
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`];
|
|
6039
6067
|
const base = { identity: member.identity, plan };
|
|
@@ -6100,7 +6128,7 @@ function rankPool(input) {
|
|
|
6100
6128
|
}
|
|
6101
6129
|
return { ...candidate, feasible: false, reasons: [`5h window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(fiveHour.untilResetMs)}`, ...candidate.reasons] };
|
|
6102
6130
|
});
|
|
6103
|
-
const ordered = [...candidates].sort(
|
|
6131
|
+
const ordered = input.preference === "listed" ? [...candidates] : [...candidates].sort(
|
|
6104
6132
|
(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)
|
|
6105
6133
|
);
|
|
6106
6134
|
const sticky = input.sticky;
|
|
@@ -6228,6 +6256,12 @@ var DAYS_IN_WEEK = 7;
|
|
|
6228
6256
|
var USAGE_RETENTION_MS = DAYS_IN_WEEK * DAY_MS2;
|
|
6229
6257
|
|
|
6230
6258
|
// src/usage/poolPick.ts
|
|
6259
|
+
var PoolGraphError = class extends CliError {
|
|
6260
|
+
constructor(message) {
|
|
6261
|
+
super(message);
|
|
6262
|
+
this.name = "PoolGraphError";
|
|
6263
|
+
}
|
|
6264
|
+
};
|
|
6231
6265
|
var SCHEMA_VERSION = 1;
|
|
6232
6266
|
var PickEntrySchema = import_zod9.z.strictObject({ identity: import_zod9.z.string().min(1), at: import_zod9.z.iso.datetime() });
|
|
6233
6267
|
var PicksFileSchema = import_zod9.z.strictObject({
|
|
@@ -6254,13 +6288,63 @@ function loadPoolMembers(fs12, paths, identities, nowMs) {
|
|
|
6254
6288
|
}
|
|
6255
6289
|
});
|
|
6256
6290
|
}
|
|
6257
|
-
function
|
|
6258
|
-
return
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6291
|
+
function rankPoolGraph(input) {
|
|
6292
|
+
return rankNamedPool(input, input.poolName, [], input.sticky);
|
|
6293
|
+
}
|
|
6294
|
+
function rankNamedPool(input, poolName, stack, sticky) {
|
|
6295
|
+
const pool = input.pools[poolName];
|
|
6296
|
+
if (pool === void 0) {
|
|
6297
|
+
throw new PoolGraphError(`No pool named "${poolName}". Run \`agent-shim pool add ${poolName} --identity <name>...\` first.`);
|
|
6298
|
+
}
|
|
6299
|
+
const childStack = [...stack, poolName];
|
|
6300
|
+
const direct = pool.identities.filter((entry) => poolNameOf(entry) === void 0);
|
|
6301
|
+
const missing = direct.filter((entry) => !input.identityExists(entry)).map((identity) => ({ pool: poolName, identity }));
|
|
6302
|
+
const loaded = new Map(loadPoolMembers(input.fs, input.paths, direct.filter(input.identityExists), input.nowMs).map((member) => [member.identity, member]));
|
|
6303
|
+
const members = [];
|
|
6304
|
+
for (const entry of pool.identities) {
|
|
6305
|
+
const nestedName = poolNameOf(entry);
|
|
6306
|
+
if (nestedName === void 0) {
|
|
6307
|
+
const member = loaded.get(entry);
|
|
6308
|
+
if (member !== void 0) {
|
|
6309
|
+
members.push(member);
|
|
6310
|
+
}
|
|
6311
|
+
continue;
|
|
6312
|
+
}
|
|
6313
|
+
if (input.pools[nestedName] === void 0) {
|
|
6314
|
+
throw new PoolGraphError(`Pool "${poolName}" member "${entry}" names a pool that is not defined.`);
|
|
6315
|
+
}
|
|
6316
|
+
if (childStack.includes(nestedName)) {
|
|
6317
|
+
throw new PoolGraphError(`Pool "${poolName}" member "${entry}" closes a cycle: ${[...childStack.slice(childStack.indexOf(nestedName)), nestedName].join(" -> ")}. A pool cannot nest itself, directly or through another pool.`);
|
|
6318
|
+
}
|
|
6319
|
+
const nested = nestedMember(input, nestedName, childStack, sticky);
|
|
6320
|
+
members.push(nested.member);
|
|
6321
|
+
missing.push(...nested.missing);
|
|
6322
|
+
}
|
|
6323
|
+
const ranking = rankPool({
|
|
6324
|
+
members,
|
|
6325
|
+
nowMs: input.nowMs,
|
|
6326
|
+
resuming: input.resuming,
|
|
6327
|
+
...sticky === void 0 ? {} : { sticky },
|
|
6328
|
+
...pool.preference === void 0 ? {} : { preference: pool.preference }
|
|
6263
6329
|
});
|
|
6330
|
+
return { ranking, missing };
|
|
6331
|
+
}
|
|
6332
|
+
function nestedMember(input, nestedName, childStack, sticky) {
|
|
6333
|
+
const nested = rankNamedPool(input, nestedName, childStack, sticky);
|
|
6334
|
+
const pick = nested.ranking.pick;
|
|
6335
|
+
if (pick !== void 0) {
|
|
6336
|
+
const [member] = loadPoolMembers(input.fs, input.paths, [pick.identity], input.nowMs);
|
|
6337
|
+
return { member: { ...member ?? { identity: pick.identity, records: [] }, nested: { kind: "pick", pool: nestedName, reasons: pick.reasons } }, missing: nested.missing };
|
|
6338
|
+
}
|
|
6339
|
+
const earliestReturn = nested.ranking.earliestReturn;
|
|
6340
|
+
return {
|
|
6341
|
+
member: {
|
|
6342
|
+
identity: earliestReturn?.identity ?? `pool:${nestedName}`,
|
|
6343
|
+
records: [],
|
|
6344
|
+
nested: { kind: "refused", pool: nestedName, ...earliestReturn === void 0 ? {} : { earliestReturn } }
|
|
6345
|
+
},
|
|
6346
|
+
missing: nested.missing
|
|
6347
|
+
};
|
|
6264
6348
|
}
|
|
6265
6349
|
function readPicksFile(fs12, file) {
|
|
6266
6350
|
const empty = { schemaVersion: SCHEMA_VERSION, picks: {} };
|
|
@@ -6291,59 +6375,6 @@ function recordStickyPick(fs12, picksFile, directory, identity, nowMs) {
|
|
|
6291
6375
|
`);
|
|
6292
6376
|
}
|
|
6293
6377
|
|
|
6294
|
-
// src/launcher/pool.ts
|
|
6295
|
-
var RESUME_FLAGS = /* @__PURE__ */ new Set(["--continue", "-c", "--resume", "-r"]);
|
|
6296
|
-
var TERMINATOR = "--";
|
|
6297
|
-
var REASONS_LOGGED = 3;
|
|
6298
|
-
function splitMembers(pool, paths, fs12) {
|
|
6299
|
-
const exists = (name) => loadIdentity(paths.identitiesDir, name, fs12) !== void 0;
|
|
6300
|
-
return { present: pool.identities.filter(exists), missing: pool.identities.filter((name) => !exists(name)) };
|
|
6301
|
-
}
|
|
6302
|
-
function isResuming(passthrough) {
|
|
6303
|
-
const end = passthrough.indexOf(TERMINATOR);
|
|
6304
|
-
return (end === -1 ? passthrough : passthrough.slice(0, end)).some((token) => RESUME_FLAGS.has(token));
|
|
6305
|
-
}
|
|
6306
|
-
function resolvePoolLaunch(params) {
|
|
6307
|
-
const { poolName, log } = params;
|
|
6308
|
-
const pool = params.pools?.[poolName];
|
|
6309
|
-
if (pool === void 0) {
|
|
6310
|
-
return { ok: false, message: `agent-shim: no pool named "${poolName}" (selected via ${params.selectedVia}). Run \`agent-shim pool add ${poolName} --identity <name>...\` first.` };
|
|
6311
|
-
}
|
|
6312
|
-
const { present: identities, missing } = splitMembers(pool, params.paths, params.fs);
|
|
6313
|
-
for (const name of missing) {
|
|
6314
|
-
log.warn(`agent-shim: pool "${poolName}" names identity "${name}", which does not exist; skipping it`);
|
|
6315
|
-
}
|
|
6316
|
-
if (identities.length === 0) {
|
|
6317
|
-
return { ok: false, message: `agent-shim: no member of pool "${poolName}" is an existing identity.` };
|
|
6318
|
-
}
|
|
6319
|
-
const resuming = isResuming(params.passthrough);
|
|
6320
|
-
const stickyRead = readStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd);
|
|
6321
|
-
if (stickyRead.problem !== void 0) {
|
|
6322
|
-
log.warn(`agent-shim: ${stickyRead.problem}; ignoring the last-pick record`);
|
|
6323
|
-
}
|
|
6324
|
-
for (; ; ) {
|
|
6325
|
-
const nowMs = params.now();
|
|
6326
|
-
const ranking = rankPoolFromStore({ fs: params.usageFs, paths: params.paths, identities, nowMs, resuming, ...stickyRead.sticky === void 0 ? {} : { sticky: stickyRead.sticky } });
|
|
6327
|
-
const { pick, earliestReturn } = ranking;
|
|
6328
|
-
if (pick !== void 0) {
|
|
6329
|
-
recordStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd, pick.identity, nowMs);
|
|
6330
|
-
return { ok: true, identity: pick.identity, explanation: `pool ${poolName}: ${pick.reasons.slice(0, REASONS_LOGGED).join("; ")}` };
|
|
6331
|
-
}
|
|
6332
|
-
if (earliestReturn === void 0) {
|
|
6333
|
-
return { ok: false, message: `agent-shim: no member of pool "${poolName}" can be picked.` };
|
|
6334
|
-
}
|
|
6335
|
-
const until = new Date(earliestReturn.atMs).toISOString();
|
|
6336
|
-
if (!params.wait) {
|
|
6337
|
-
return {
|
|
6338
|
-
ok: false,
|
|
6339
|
-
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.`
|
|
6340
|
-
};
|
|
6341
|
-
}
|
|
6342
|
-
log.info(`agent-shim: every member of pool "${poolName}" is refused; waiting until ${until} for ${earliestReturn.identity}`);
|
|
6343
|
-
params.sleep(Math.max(earliestReturn.atMs - nowMs, 0));
|
|
6344
|
-
}
|
|
6345
|
-
}
|
|
6346
|
-
|
|
6347
6378
|
// src/poolPickReport.ts
|
|
6348
6379
|
function candidateView(candidate) {
|
|
6349
6380
|
return {
|
|
@@ -6357,14 +6388,15 @@ function candidateView(candidate) {
|
|
|
6357
6388
|
};
|
|
6358
6389
|
}
|
|
6359
6390
|
function collectPoolPick(params) {
|
|
6360
|
-
const { present, missing } = splitMembers(params.pool, params.paths, params.fs);
|
|
6361
6391
|
const sticky = readStickyPick(params.usageFs, params.paths.usagePicksFile, params.directory);
|
|
6362
|
-
const ranking =
|
|
6392
|
+
const { ranking, missing } = rankPoolGraph({
|
|
6363
6393
|
fs: params.usageFs,
|
|
6364
6394
|
paths: params.paths,
|
|
6365
|
-
|
|
6395
|
+
pools: params.pools,
|
|
6396
|
+
poolName: params.poolName,
|
|
6366
6397
|
nowMs: params.nowMs,
|
|
6367
6398
|
resuming: false,
|
|
6399
|
+
identityExists: (name) => loadIdentity(params.paths.identitiesDir, name, params.fs) !== void 0,
|
|
6368
6400
|
...sticky.sticky === void 0 ? {} : { sticky: sticky.sticky }
|
|
6369
6401
|
});
|
|
6370
6402
|
return {
|
|
@@ -6372,7 +6404,7 @@ function collectPoolPick(params) {
|
|
|
6372
6404
|
directory: params.directory,
|
|
6373
6405
|
...ranking.pick === void 0 ? {} : { pick: ranking.pick.identity },
|
|
6374
6406
|
candidates: ranking.candidates.map(candidateView),
|
|
6375
|
-
missing:
|
|
6407
|
+
missing: missing.map(({ identity }) => identity),
|
|
6376
6408
|
...ranking.earliestReturn === void 0 ? {} : { earliestReturn: { identity: ranking.earliestReturn.identity, at: new Date(ranking.earliestReturn.atMs).toISOString() } },
|
|
6377
6409
|
...sticky.problem === void 0 ? {} : { stickyProblem: sticky.problem }
|
|
6378
6410
|
};
|
|
@@ -6542,7 +6574,41 @@ function requirePool(paths, name) {
|
|
|
6542
6574
|
function writePools(paths, pools) {
|
|
6543
6575
|
applyPatch(paths.globalConfigFile, GlobalConfigSchema, { pools }, { defaults: {} });
|
|
6544
6576
|
}
|
|
6545
|
-
function
|
|
6577
|
+
function poolCycleOf(pools, name) {
|
|
6578
|
+
const walk = (current, path32) => {
|
|
6579
|
+
for (const entry of pools[current]?.identities ?? []) {
|
|
6580
|
+
const nested = poolNameOf(entry);
|
|
6581
|
+
if (nested === void 0) {
|
|
6582
|
+
continue;
|
|
6583
|
+
}
|
|
6584
|
+
if (nested === name) {
|
|
6585
|
+
return [...path32, nested];
|
|
6586
|
+
}
|
|
6587
|
+
if (path32.includes(nested)) {
|
|
6588
|
+
continue;
|
|
6589
|
+
}
|
|
6590
|
+
const found = walk(nested, [...path32, nested]);
|
|
6591
|
+
if (found !== void 0) {
|
|
6592
|
+
return found;
|
|
6593
|
+
}
|
|
6594
|
+
}
|
|
6595
|
+
return void 0;
|
|
6596
|
+
};
|
|
6597
|
+
return walk(name, [name]);
|
|
6598
|
+
}
|
|
6599
|
+
function validateMembers(pools, name, members) {
|
|
6600
|
+
for (const entry of members) {
|
|
6601
|
+
const nested = poolNameOf(entry);
|
|
6602
|
+
if (nested !== void 0 && nested !== name && pools[nested] === void 0) {
|
|
6603
|
+
throw new UsageError(`Member "${entry}" of pool "${name}" names a pool that is not defined. Run \`agent-shim pool add ${nested} --identity <name>...\` first.`);
|
|
6604
|
+
}
|
|
6605
|
+
}
|
|
6606
|
+
const cycle = poolCycleOf({ ...pools, [name]: { identities: [...members] } }, name);
|
|
6607
|
+
if (cycle !== void 0) {
|
|
6608
|
+
throw new UsageError(`Member "pool:${cycle[1] ?? name}" would make pool "${name}" reach itself: ${cycle.join(" -> ")}. A pool cannot nest itself, directly or through another pool.`);
|
|
6609
|
+
}
|
|
6610
|
+
}
|
|
6611
|
+
function addPool(paths, name, identities, preference) {
|
|
6546
6612
|
if (!PoolNameSchema.safeParse(name).success) {
|
|
6547
6613
|
throw new InvalidPoolNameError(name);
|
|
6548
6614
|
}
|
|
@@ -6550,13 +6616,15 @@ function addPool(paths, name, identities) {
|
|
|
6550
6616
|
if (pools[name] !== void 0) {
|
|
6551
6617
|
throw new PoolAlreadyExistsError(name);
|
|
6552
6618
|
}
|
|
6553
|
-
|
|
6619
|
+
validateMembers(pools, name, identities);
|
|
6620
|
+
const pool = { identities: [...identities], ...preference === void 0 ? {} : { preference } };
|
|
6554
6621
|
writePools(paths, { ...pools, [name]: pool });
|
|
6555
6622
|
return pool;
|
|
6556
6623
|
}
|
|
6557
|
-
function setPool(paths, name, identities) {
|
|
6624
|
+
function setPool(paths, name, identities, preference) {
|
|
6558
6625
|
requirePool(paths, name);
|
|
6559
|
-
|
|
6626
|
+
validateMembers(readPools(paths), name, identities);
|
|
6627
|
+
const pool = { identities: [...identities], ...preference === void 0 ? {} : { preference } };
|
|
6560
6628
|
writePools(paths, { ...readPools(paths), [name]: pool });
|
|
6561
6629
|
return pool;
|
|
6562
6630
|
}
|
|
@@ -8467,11 +8535,11 @@ function collectCheckReport(params) {
|
|
|
8467
8535
|
});
|
|
8468
8536
|
let poolPick;
|
|
8469
8537
|
if (decidedIdentity.pool !== void 0) {
|
|
8470
|
-
const
|
|
8471
|
-
if (pool === void 0) {
|
|
8538
|
+
const pools = loaded.globalConfig?.pools ?? {};
|
|
8539
|
+
if (pools[decidedIdentity.pool] === void 0) {
|
|
8472
8540
|
throw new PoolNotFoundError(decidedIdentity.pool);
|
|
8473
8541
|
}
|
|
8474
|
-
poolPick = collectPoolPick({ paths, fs: realFsPort, usageFs: realFarmFs, poolName: decidedIdentity.pool,
|
|
8542
|
+
poolPick = collectPoolPick({ paths, fs: realFsPort, usageFs: realFarmFs, poolName: decidedIdentity.pool, pools, directory: cwd, nowMs: Date.now() });
|
|
8475
8543
|
}
|
|
8476
8544
|
const identityDecision = poolPick?.pick === void 0 ? decidedIdentity : { ...decidedIdentity, name: poolPick.pick };
|
|
8477
8545
|
const loadedIdentity = identityDecision.name === void 0 ? void 0 : loadIdentity(paths.identitiesDir, identityDecision.name, realFsPort);
|
|
@@ -8605,7 +8673,7 @@ var PoolPickReportSchema = import_zod13.z.strictObject({
|
|
|
8605
8673
|
/** The member a launch would run as, absent when every member is refused. */
|
|
8606
8674
|
pick: import_zod13.z.string().optional(),
|
|
8607
8675
|
candidates: import_zod13.z.array(PoolPickCandidateViewSchema),
|
|
8608
|
-
/**
|
|
8676
|
+
/** Identity members anywhere in the pool's graph that do not exist; a launch skips them. */
|
|
8609
8677
|
missing: import_zod13.z.array(import_zod13.z.string()),
|
|
8610
8678
|
/** When nothing can be picked, the soonest any member returns. */
|
|
8611
8679
|
earliestReturn: import_zod13.z.strictObject({ identity: import_zod13.z.string(), at: import_zod13.z.iso.datetime() }).optional(),
|
|
@@ -9235,12 +9303,33 @@ function runDoctor(params) {
|
|
|
9235
9303
|
const validatedGlobalConfig = params.globalConfig.raw === void 0 ? void 0 : validateJson(GlobalConfigSchema, params.globalConfig);
|
|
9236
9304
|
const pools = validatedGlobalConfig?.ok === true ? validatedGlobalConfig.data.pools ?? {} : {};
|
|
9237
9305
|
for (const [poolName, pool] of Object.entries(pools)) {
|
|
9238
|
-
const missing = pool.identities.filter((member) => !validIdentityNames.has(member));
|
|
9239
|
-
|
|
9306
|
+
const missing = pool.identities.filter((member) => poolNameOf(member) === void 0 && !validIdentityNames.has(member));
|
|
9307
|
+
const undefinedPools = pool.identities.flatMap((member) => {
|
|
9308
|
+
const nested = poolNameOf(member);
|
|
9309
|
+
return nested === void 0 || nested in pools ? [] : [nested];
|
|
9310
|
+
});
|
|
9311
|
+
if (missing.length === 0 && undefinedPools.length === 0) {
|
|
9240
9312
|
push("pool", "pass", `${poolName} is valid (${pool.identities.join(", ")}).`, poolName);
|
|
9241
9313
|
} else {
|
|
9242
|
-
|
|
9314
|
+
const problems = [
|
|
9315
|
+
...missing.map((member) => `identity "${member}", which does not exist`),
|
|
9316
|
+
...undefinedPools.map((nested) => `pool "${nested}", which is not defined`)
|
|
9317
|
+
];
|
|
9318
|
+
push("pool", "fail", `Pool "${poolName}" names ${problems.join(" and ")}.`, poolName);
|
|
9319
|
+
}
|
|
9320
|
+
}
|
|
9321
|
+
const reportedCycles = /* @__PURE__ */ new Set();
|
|
9322
|
+
for (const poolName of Object.keys(pools).sort()) {
|
|
9323
|
+
const chain = poolCycleOf(pools, poolName);
|
|
9324
|
+
if (chain === void 0) {
|
|
9325
|
+
continue;
|
|
9326
|
+
}
|
|
9327
|
+
const key = [...chain].slice(0, -1).sort().join(">");
|
|
9328
|
+
if (reportedCycles.has(key)) {
|
|
9329
|
+
continue;
|
|
9243
9330
|
}
|
|
9331
|
+
reportedCycles.add(key);
|
|
9332
|
+
push("pool", "fail", `Pool cycle: ${chain.join(" -> ")}. A pool cannot nest itself, directly or through another pool.`, chain[0]);
|
|
9244
9333
|
}
|
|
9245
9334
|
const selectionExists = (selector) => {
|
|
9246
9335
|
const poolName = poolNameOf(selector);
|
|
@@ -11043,7 +11132,7 @@ var LAUNCHER_FLAG_NAMES = [
|
|
|
11043
11132
|
...BOOLEAN_FLAGS.flatMap(({ flag }) => [flag, `--no-${flag.slice(2)}`])
|
|
11044
11133
|
];
|
|
11045
11134
|
var NATIVE_FLAG = "--native";
|
|
11046
|
-
var
|
|
11135
|
+
var TERMINATOR = "--";
|
|
11047
11136
|
function matchValuedFlag(token) {
|
|
11048
11137
|
for (const flag of VALUED_FLAGS) {
|
|
11049
11138
|
if (token === flag) {
|
|
@@ -11085,7 +11174,7 @@ function parseLauncherArgv(argv) {
|
|
|
11085
11174
|
if (token === void 0) {
|
|
11086
11175
|
continue;
|
|
11087
11176
|
}
|
|
11088
|
-
if (token ===
|
|
11177
|
+
if (token === TERMINATOR) {
|
|
11089
11178
|
rest.push(...remaining.slice(index));
|
|
11090
11179
|
break;
|
|
11091
11180
|
}
|
|
@@ -11112,7 +11201,7 @@ function parseLauncherArgv(argv) {
|
|
|
11112
11201
|
value = matched.inlineValue;
|
|
11113
11202
|
} else {
|
|
11114
11203
|
const next = remaining[index + 1];
|
|
11115
|
-
if (next === void 0 || next ===
|
|
11204
|
+
if (next === void 0 || next === TERMINATOR) {
|
|
11116
11205
|
rest.push(token);
|
|
11117
11206
|
continue;
|
|
11118
11207
|
}
|
|
@@ -11396,6 +11485,76 @@ function splitExtraFlags(value) {
|
|
|
11396
11485
|
return trimmed.split(/\s+/);
|
|
11397
11486
|
}
|
|
11398
11487
|
|
|
11488
|
+
// src/launcher/pool.ts
|
|
11489
|
+
var RESUME_FLAGS = /* @__PURE__ */ new Set(["--continue", "-c", "--resume", "-r"]);
|
|
11490
|
+
var TERMINATOR2 = "--";
|
|
11491
|
+
var REASONS_LOGGED = 3;
|
|
11492
|
+
function isResuming(passthrough) {
|
|
11493
|
+
const end = passthrough.indexOf(TERMINATOR2);
|
|
11494
|
+
return (end === -1 ? passthrough : passthrough.slice(0, end)).some((token) => RESUME_FLAGS.has(token));
|
|
11495
|
+
}
|
|
11496
|
+
function resolvePoolLaunch(params) {
|
|
11497
|
+
const { poolName, log } = params;
|
|
11498
|
+
const pool = params.pools?.[poolName];
|
|
11499
|
+
if (pool === void 0) {
|
|
11500
|
+
return { ok: false, message: `agent-shim: no pool named "${poolName}" (selected via ${params.selectedVia}). Run \`agent-shim pool add ${poolName} --identity <name>...\` first.` };
|
|
11501
|
+
}
|
|
11502
|
+
const resuming = isResuming(params.passthrough);
|
|
11503
|
+
const stickyRead = readStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd);
|
|
11504
|
+
if (stickyRead.problem !== void 0) {
|
|
11505
|
+
log.warn(`agent-shim: ${stickyRead.problem}; ignoring the last-pick record`);
|
|
11506
|
+
}
|
|
11507
|
+
const warned = /* @__PURE__ */ new Set();
|
|
11508
|
+
for (; ; ) {
|
|
11509
|
+
const nowMs = params.now();
|
|
11510
|
+
let graph;
|
|
11511
|
+
try {
|
|
11512
|
+
graph = rankPoolGraph({
|
|
11513
|
+
fs: params.usageFs,
|
|
11514
|
+
paths: params.paths,
|
|
11515
|
+
pools: params.pools ?? {},
|
|
11516
|
+
poolName,
|
|
11517
|
+
nowMs,
|
|
11518
|
+
resuming,
|
|
11519
|
+
identityExists: (name) => loadIdentity(params.paths.identitiesDir, name, params.fs) !== void 0,
|
|
11520
|
+
...stickyRead.sticky === void 0 ? {} : { sticky: stickyRead.sticky }
|
|
11521
|
+
});
|
|
11522
|
+
} catch (error) {
|
|
11523
|
+
if (error instanceof PoolGraphError) {
|
|
11524
|
+
return { ok: false, message: `agent-shim: ${error.message}` };
|
|
11525
|
+
}
|
|
11526
|
+
throw error;
|
|
11527
|
+
}
|
|
11528
|
+
for (const gone of graph.missing) {
|
|
11529
|
+
const key = `${gone.pool}:${gone.identity}`;
|
|
11530
|
+
if (!warned.has(key)) {
|
|
11531
|
+
warned.add(key);
|
|
11532
|
+
log.warn(`agent-shim: pool "${gone.pool}" names identity "${gone.identity}", which does not exist; skipping it`);
|
|
11533
|
+
}
|
|
11534
|
+
}
|
|
11535
|
+
if (graph.ranking.candidates.length === 0) {
|
|
11536
|
+
return { ok: false, message: `agent-shim: no member of pool "${poolName}" is an existing identity.` };
|
|
11537
|
+
}
|
|
11538
|
+
const { pick, earliestReturn } = graph.ranking;
|
|
11539
|
+
if (pick !== void 0) {
|
|
11540
|
+
recordStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd, pick.identity, nowMs);
|
|
11541
|
+
return { ok: true, identity: pick.identity, explanation: `pool ${poolName}: ${pick.reasons.slice(0, REASONS_LOGGED).join("; ")}` };
|
|
11542
|
+
}
|
|
11543
|
+
if (earliestReturn === void 0) {
|
|
11544
|
+
return { ok: false, message: `agent-shim: no member of pool "${poolName}" can be picked.` };
|
|
11545
|
+
}
|
|
11546
|
+
const until = new Date(earliestReturn.atMs).toISOString();
|
|
11547
|
+
if (!params.wait) {
|
|
11548
|
+
return {
|
|
11549
|
+
ok: false,
|
|
11550
|
+
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.`
|
|
11551
|
+
};
|
|
11552
|
+
}
|
|
11553
|
+
log.info(`agent-shim: every member of pool "${poolName}" is refused; waiting until ${until} for ${earliestReturn.identity}`);
|
|
11554
|
+
params.sleep(Math.max(earliestReturn.atMs - nowMs, 0));
|
|
11555
|
+
}
|
|
11556
|
+
}
|
|
11557
|
+
|
|
11399
11558
|
// src/launcher/spawn.ts
|
|
11400
11559
|
var import_node_os5 = __toESM(require("node:os"), 1);
|
|
11401
11560
|
var SIGNAL_EXIT_CODE_OFFSET = 128;
|