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.mjs
CHANGED
|
@@ -307,9 +307,14 @@ var POOL_SELECTOR_PREFIX = "pool:";
|
|
|
307
307
|
var POOL_NAME_PATTERN = "[A-Za-z0-9][A-Za-z0-9._-]*";
|
|
308
308
|
var IDENTITY_NAME_PATTERN = "[A-Za-z0-9][A-Za-z0-9._@-]*";
|
|
309
309
|
var PoolNameSchema = z.string().min(1).regex(new RegExp(`^${POOL_NAME_PATTERN}$`));
|
|
310
|
-
var
|
|
310
|
+
var SELECTOR_RE = new RegExp(`^(${POOL_SELECTOR_PREFIX}${POOL_NAME_PATTERN}|${IDENTITY_NAME_PATTERN})$`);
|
|
311
|
+
var IdentitySelectorSchema = z.string().min(1).regex(SELECTOR_RE);
|
|
312
|
+
var PoolMemberSchema = z.string().min(1).regex(SELECTOR_RE);
|
|
313
|
+
var POOL_PREFERENCES = ["score", "listed"];
|
|
314
|
+
var PoolPreferenceSchema = z.enum(POOL_PREFERENCES);
|
|
311
315
|
var PoolSchema = z.strictObject({
|
|
312
|
-
identities: z.array(
|
|
316
|
+
identities: z.array(PoolMemberSchema).min(1).refine((members) => new Set(members).size === members.length, { message: "pool members must be unique" }),
|
|
317
|
+
preference: PoolPreferenceSchema.optional()
|
|
313
318
|
});
|
|
314
319
|
var DirectoryRuleSchema = ConfigProfileSchema.omit({ description: true }).extend({
|
|
315
320
|
path: z.string().min(1),
|
|
@@ -5352,54 +5357,6 @@ function resolveLayoutPaths() {
|
|
|
5352
5357
|
return buildLayoutPaths(resolveAgentShimHome());
|
|
5353
5358
|
}
|
|
5354
5359
|
|
|
5355
|
-
// src/usage/preflight.ts
|
|
5356
|
-
var MS_PER_SECOND2 = 1e3;
|
|
5357
|
-
var SECONDS_PER_MINUTE = 60;
|
|
5358
|
-
var MINUTES_PER_HOUR = 60;
|
|
5359
|
-
var HOURS_PER_DAY = 24;
|
|
5360
|
-
var WARNING_STATUSES = /* @__PURE__ */ new Set(["allowed_warning", "rejected"]);
|
|
5361
|
-
function formatAge(ms) {
|
|
5362
|
-
const minutes = Math.floor(ms / (MS_PER_SECOND2 * SECONDS_PER_MINUTE));
|
|
5363
|
-
if (minutes < MINUTES_PER_HOUR) {
|
|
5364
|
-
return `${String(Math.max(minutes, 0))}m`;
|
|
5365
|
-
}
|
|
5366
|
-
const hours = Math.floor(minutes / MINUTES_PER_HOUR);
|
|
5367
|
-
return hours < HOURS_PER_DAY ? `${String(hours)}h` : `${String(Math.floor(hours / HOURS_PER_DAY))}d`;
|
|
5368
|
-
}
|
|
5369
|
-
function effectiveWindow(window, nowMs) {
|
|
5370
|
-
const resetsAtMs = window.resetsAt === void 0 ? void 0 : Date.parse(window.resetsAt);
|
|
5371
|
-
if (resetsAtMs !== void 0 && resetsAtMs <= nowMs) {
|
|
5372
|
-
return { reset: true, utilization: 0 };
|
|
5373
|
-
}
|
|
5374
|
-
return {
|
|
5375
|
-
reset: false,
|
|
5376
|
-
...window.utilization === void 0 ? {} : { utilization: window.utilization },
|
|
5377
|
-
...resetsAtMs === void 0 ? {} : { resetsAtMs },
|
|
5378
|
-
...window.status === void 0 ? {} : { status: window.status }
|
|
5379
|
-
};
|
|
5380
|
-
}
|
|
5381
|
-
function quotaWarnings(snapshot, provider, nowMs) {
|
|
5382
|
-
const rateLimit = snapshot?.providers[provider]?.rateLimit;
|
|
5383
|
-
const unified = rateLimit?.unified;
|
|
5384
|
-
if (snapshot === void 0 || rateLimit === void 0 || unified === void 0) {
|
|
5385
|
-
return [];
|
|
5386
|
-
}
|
|
5387
|
-
const age = formatAge(nowMs - Date.parse(rateLimit.observedAt));
|
|
5388
|
-
const windows = [
|
|
5389
|
-
{ name: "five-hour", window: unified.fiveHour },
|
|
5390
|
-
{ name: "seven-day", window: unified.sevenDay }
|
|
5391
|
-
];
|
|
5392
|
-
return windows.flatMap(({ name, window }) => {
|
|
5393
|
-
const effective = window === void 0 ? void 0 : effectiveWindow(window, nowMs);
|
|
5394
|
-
if (window === void 0 || effective?.status === void 0 || !WARNING_STATUSES.has(effective.status)) {
|
|
5395
|
-
return [];
|
|
5396
|
-
}
|
|
5397
|
-
const reset = window.resetsAt === void 0 ? "" : `, resets ${window.resetsAt}`;
|
|
5398
|
-
const verdict = effective.status === "rejected" ? "exhausted" : "nearly used";
|
|
5399
|
-
return [`agent-shim: identity ${snapshot.identity}: the ${name} quota is ${verdict}${reset} (last seen ${age} ago)`];
|
|
5400
|
-
});
|
|
5401
|
-
}
|
|
5402
|
-
|
|
5403
5360
|
// src/usage/poolPick.ts
|
|
5404
5361
|
import path17 from "node:path";
|
|
5405
5362
|
import { z as z9 } from "zod";
|
|
@@ -5646,13 +5603,13 @@ function planOf(account) {
|
|
|
5646
5603
|
}
|
|
5647
5604
|
|
|
5648
5605
|
// src/usage/rateLimit.ts
|
|
5649
|
-
var
|
|
5606
|
+
var MS_PER_SECOND2 = 1e3;
|
|
5650
5607
|
function epochSecondsToIso(value) {
|
|
5651
5608
|
if (value === void 0) {
|
|
5652
5609
|
return void 0;
|
|
5653
5610
|
}
|
|
5654
5611
|
const seconds = Number(value);
|
|
5655
|
-
return Number.isFinite(seconds) && seconds > 0 ? new Date(seconds *
|
|
5612
|
+
return Number.isFinite(seconds) && seconds > 0 ? new Date(seconds * MS_PER_SECOND2).toISOString() : void 0;
|
|
5656
5613
|
}
|
|
5657
5614
|
function nonNegativeNumber(value) {
|
|
5658
5615
|
if (value === void 0 || value.trim() === "") {
|
|
@@ -5708,6 +5665,54 @@ var ErrorObjectSchema = z8.looseObject({
|
|
|
5708
5665
|
// src/usage/middleware.ts
|
|
5709
5666
|
var ANTHROPIC_PROVIDER = "anthropic";
|
|
5710
5667
|
|
|
5668
|
+
// src/usage/preflight.ts
|
|
5669
|
+
var MS_PER_SECOND3 = 1e3;
|
|
5670
|
+
var SECONDS_PER_MINUTE = 60;
|
|
5671
|
+
var MINUTES_PER_HOUR = 60;
|
|
5672
|
+
var HOURS_PER_DAY = 24;
|
|
5673
|
+
var WARNING_STATUSES = /* @__PURE__ */ new Set(["allowed_warning", "rejected"]);
|
|
5674
|
+
function formatAge(ms) {
|
|
5675
|
+
const minutes = Math.floor(ms / (MS_PER_SECOND3 * SECONDS_PER_MINUTE));
|
|
5676
|
+
if (minutes < MINUTES_PER_HOUR) {
|
|
5677
|
+
return `${String(Math.max(minutes, 0))}m`;
|
|
5678
|
+
}
|
|
5679
|
+
const hours = Math.floor(minutes / MINUTES_PER_HOUR);
|
|
5680
|
+
return hours < HOURS_PER_DAY ? `${String(hours)}h` : `${String(Math.floor(hours / HOURS_PER_DAY))}d`;
|
|
5681
|
+
}
|
|
5682
|
+
function effectiveWindow(window, nowMs) {
|
|
5683
|
+
const resetsAtMs = window.resetsAt === void 0 ? void 0 : Date.parse(window.resetsAt);
|
|
5684
|
+
if (resetsAtMs !== void 0 && resetsAtMs <= nowMs) {
|
|
5685
|
+
return { reset: true, utilization: 0 };
|
|
5686
|
+
}
|
|
5687
|
+
return {
|
|
5688
|
+
reset: false,
|
|
5689
|
+
...window.utilization === void 0 ? {} : { utilization: window.utilization },
|
|
5690
|
+
...resetsAtMs === void 0 ? {} : { resetsAtMs },
|
|
5691
|
+
...window.status === void 0 ? {} : { status: window.status }
|
|
5692
|
+
};
|
|
5693
|
+
}
|
|
5694
|
+
function quotaWarnings(snapshot, provider, nowMs) {
|
|
5695
|
+
const rateLimit = snapshot?.providers[provider]?.rateLimit;
|
|
5696
|
+
const unified = rateLimit?.unified;
|
|
5697
|
+
if (snapshot === void 0 || rateLimit === void 0 || unified === void 0) {
|
|
5698
|
+
return [];
|
|
5699
|
+
}
|
|
5700
|
+
const age = formatAge(nowMs - Date.parse(rateLimit.observedAt));
|
|
5701
|
+
const windows = [
|
|
5702
|
+
{ name: "five-hour", window: unified.fiveHour },
|
|
5703
|
+
{ name: "seven-day", window: unified.sevenDay }
|
|
5704
|
+
];
|
|
5705
|
+
return windows.flatMap(({ name, window }) => {
|
|
5706
|
+
const effective = window === void 0 ? void 0 : effectiveWindow(window, nowMs);
|
|
5707
|
+
if (window === void 0 || effective?.status === void 0 || !WARNING_STATUSES.has(effective.status)) {
|
|
5708
|
+
return [];
|
|
5709
|
+
}
|
|
5710
|
+
const reset = window.resetsAt === void 0 ? "" : `, resets ${window.resetsAt}`;
|
|
5711
|
+
const verdict = effective.status === "rejected" ? "exhausted" : "nearly used";
|
|
5712
|
+
return [`agent-shim: identity ${snapshot.identity}: the ${name} quota is ${verdict}${reset} (last seen ${age} ago)`];
|
|
5713
|
+
});
|
|
5714
|
+
}
|
|
5715
|
+
|
|
5711
5716
|
// src/usage/pick.ts
|
|
5712
5717
|
var MS_PER_SECOND4 = 1e3;
|
|
5713
5718
|
var MS_PER_HOUR = 36e5;
|
|
@@ -5769,7 +5774,30 @@ function describeWindow(label, window, effective, nowMs) {
|
|
|
5769
5774
|
function capacityOf(plan) {
|
|
5770
5775
|
return plan.kind === "subscription" ? plan.capacity : 1;
|
|
5771
5776
|
}
|
|
5777
|
+
function assessRefusedNested(member, nested, nowMs) {
|
|
5778
|
+
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 };
|
|
5779
|
+
return {
|
|
5780
|
+
candidate: {
|
|
5781
|
+
identity: member.identity,
|
|
5782
|
+
class: "ineligible",
|
|
5783
|
+
...refusal.blockedUntilMs === void 0 ? {} : { blockedUntilMs: refusal.blockedUntilMs },
|
|
5784
|
+
plan: planOf(member.account),
|
|
5785
|
+
reasons: [refusal.reason]
|
|
5786
|
+
}
|
|
5787
|
+
};
|
|
5788
|
+
}
|
|
5772
5789
|
function assess(member, nowMs) {
|
|
5790
|
+
const nested = member.nested;
|
|
5791
|
+
if (nested === void 0) {
|
|
5792
|
+
return assessIdentity(member, nowMs);
|
|
5793
|
+
}
|
|
5794
|
+
if (nested.kind === "refused") {
|
|
5795
|
+
return assessRefusedNested(member, nested, nowMs);
|
|
5796
|
+
}
|
|
5797
|
+
const assessment = assessIdentity(member, nowMs);
|
|
5798
|
+
return { ...assessment, candidate: { ...assessment.candidate, reasons: [`picked by pool "${nested.pool}": ${nested.reasons.join("; ")}`, ...assessment.candidate.reasons] } };
|
|
5799
|
+
}
|
|
5800
|
+
function assessIdentity(member, nowMs) {
|
|
5773
5801
|
const plan = planOf(member.account);
|
|
5774
5802
|
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`];
|
|
5775
5803
|
const base = { identity: member.identity, plan };
|
|
@@ -5836,7 +5864,7 @@ function rankPool(input) {
|
|
|
5836
5864
|
}
|
|
5837
5865
|
return { ...candidate, feasible: false, reasons: [`5h window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(fiveHour.untilResetMs)}`, ...candidate.reasons] };
|
|
5838
5866
|
});
|
|
5839
|
-
const ordered = [...candidates].sort(
|
|
5867
|
+
const ordered = input.preference === "listed" ? [...candidates] : [...candidates].sort(
|
|
5840
5868
|
(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)
|
|
5841
5869
|
);
|
|
5842
5870
|
const sticky = input.sticky;
|
|
@@ -5964,6 +5992,12 @@ var DAYS_IN_WEEK = 7;
|
|
|
5964
5992
|
var USAGE_RETENTION_MS = DAYS_IN_WEEK * DAY_MS2;
|
|
5965
5993
|
|
|
5966
5994
|
// src/usage/poolPick.ts
|
|
5995
|
+
var PoolGraphError = class extends CliError {
|
|
5996
|
+
constructor(message) {
|
|
5997
|
+
super(message);
|
|
5998
|
+
this.name = "PoolGraphError";
|
|
5999
|
+
}
|
|
6000
|
+
};
|
|
5967
6001
|
var SCHEMA_VERSION = 1;
|
|
5968
6002
|
var PickEntrySchema = z9.strictObject({ identity: z9.string().min(1), at: z9.iso.datetime() });
|
|
5969
6003
|
var PicksFileSchema = z9.strictObject({
|
|
@@ -5990,13 +6024,63 @@ function loadPoolMembers(fs12, paths, identities, nowMs) {
|
|
|
5990
6024
|
}
|
|
5991
6025
|
});
|
|
5992
6026
|
}
|
|
5993
|
-
function
|
|
5994
|
-
return
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
6027
|
+
function rankPoolGraph(input) {
|
|
6028
|
+
return rankNamedPool(input, input.poolName, [], input.sticky);
|
|
6029
|
+
}
|
|
6030
|
+
function rankNamedPool(input, poolName, stack, sticky) {
|
|
6031
|
+
const pool = input.pools[poolName];
|
|
6032
|
+
if (pool === void 0) {
|
|
6033
|
+
throw new PoolGraphError(`No pool named "${poolName}". Run \`agent-shim pool add ${poolName} --identity <name>...\` first.`);
|
|
6034
|
+
}
|
|
6035
|
+
const childStack = [...stack, poolName];
|
|
6036
|
+
const direct = pool.identities.filter((entry) => poolNameOf(entry) === void 0);
|
|
6037
|
+
const missing = direct.filter((entry) => !input.identityExists(entry)).map((identity) => ({ pool: poolName, identity }));
|
|
6038
|
+
const loaded = new Map(loadPoolMembers(input.fs, input.paths, direct.filter(input.identityExists), input.nowMs).map((member) => [member.identity, member]));
|
|
6039
|
+
const members = [];
|
|
6040
|
+
for (const entry of pool.identities) {
|
|
6041
|
+
const nestedName = poolNameOf(entry);
|
|
6042
|
+
if (nestedName === void 0) {
|
|
6043
|
+
const member = loaded.get(entry);
|
|
6044
|
+
if (member !== void 0) {
|
|
6045
|
+
members.push(member);
|
|
6046
|
+
}
|
|
6047
|
+
continue;
|
|
6048
|
+
}
|
|
6049
|
+
if (input.pools[nestedName] === void 0) {
|
|
6050
|
+
throw new PoolGraphError(`Pool "${poolName}" member "${entry}" names a pool that is not defined.`);
|
|
6051
|
+
}
|
|
6052
|
+
if (childStack.includes(nestedName)) {
|
|
6053
|
+
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.`);
|
|
6054
|
+
}
|
|
6055
|
+
const nested = nestedMember(input, nestedName, childStack, sticky);
|
|
6056
|
+
members.push(nested.member);
|
|
6057
|
+
missing.push(...nested.missing);
|
|
6058
|
+
}
|
|
6059
|
+
const ranking = rankPool({
|
|
6060
|
+
members,
|
|
6061
|
+
nowMs: input.nowMs,
|
|
6062
|
+
resuming: input.resuming,
|
|
6063
|
+
...sticky === void 0 ? {} : { sticky },
|
|
6064
|
+
...pool.preference === void 0 ? {} : { preference: pool.preference }
|
|
5999
6065
|
});
|
|
6066
|
+
return { ranking, missing };
|
|
6067
|
+
}
|
|
6068
|
+
function nestedMember(input, nestedName, childStack, sticky) {
|
|
6069
|
+
const nested = rankNamedPool(input, nestedName, childStack, sticky);
|
|
6070
|
+
const pick = nested.ranking.pick;
|
|
6071
|
+
if (pick !== void 0) {
|
|
6072
|
+
const [member] = loadPoolMembers(input.fs, input.paths, [pick.identity], input.nowMs);
|
|
6073
|
+
return { member: { ...member ?? { identity: pick.identity, records: [] }, nested: { kind: "pick", pool: nestedName, reasons: pick.reasons } }, missing: nested.missing };
|
|
6074
|
+
}
|
|
6075
|
+
const earliestReturn = nested.ranking.earliestReturn;
|
|
6076
|
+
return {
|
|
6077
|
+
member: {
|
|
6078
|
+
identity: earliestReturn?.identity ?? `pool:${nestedName}`,
|
|
6079
|
+
records: [],
|
|
6080
|
+
nested: { kind: "refused", pool: nestedName, ...earliestReturn === void 0 ? {} : { earliestReturn } }
|
|
6081
|
+
},
|
|
6082
|
+
missing: nested.missing
|
|
6083
|
+
};
|
|
6000
6084
|
}
|
|
6001
6085
|
function readPicksFile(fs12, file) {
|
|
6002
6086
|
const empty = { schemaVersion: SCHEMA_VERSION, picks: {} };
|
|
@@ -6027,59 +6111,6 @@ function recordStickyPick(fs12, picksFile, directory, identity, nowMs) {
|
|
|
6027
6111
|
`);
|
|
6028
6112
|
}
|
|
6029
6113
|
|
|
6030
|
-
// src/launcher/pool.ts
|
|
6031
|
-
var RESUME_FLAGS = /* @__PURE__ */ new Set(["--continue", "-c", "--resume", "-r"]);
|
|
6032
|
-
var TERMINATOR = "--";
|
|
6033
|
-
var REASONS_LOGGED = 3;
|
|
6034
|
-
function splitMembers(pool, paths, fs12) {
|
|
6035
|
-
const exists = (name) => loadIdentity(paths.identitiesDir, name, fs12) !== void 0;
|
|
6036
|
-
return { present: pool.identities.filter(exists), missing: pool.identities.filter((name) => !exists(name)) };
|
|
6037
|
-
}
|
|
6038
|
-
function isResuming(passthrough) {
|
|
6039
|
-
const end = passthrough.indexOf(TERMINATOR);
|
|
6040
|
-
return (end === -1 ? passthrough : passthrough.slice(0, end)).some((token) => RESUME_FLAGS.has(token));
|
|
6041
|
-
}
|
|
6042
|
-
function resolvePoolLaunch(params) {
|
|
6043
|
-
const { poolName, log } = params;
|
|
6044
|
-
const pool = params.pools?.[poolName];
|
|
6045
|
-
if (pool === void 0) {
|
|
6046
|
-
return { ok: false, message: `agent-shim: no pool named "${poolName}" (selected via ${params.selectedVia}). Run \`agent-shim pool add ${poolName} --identity <name>...\` first.` };
|
|
6047
|
-
}
|
|
6048
|
-
const { present: identities, missing } = splitMembers(pool, params.paths, params.fs);
|
|
6049
|
-
for (const name of missing) {
|
|
6050
|
-
log.warn(`agent-shim: pool "${poolName}" names identity "${name}", which does not exist; skipping it`);
|
|
6051
|
-
}
|
|
6052
|
-
if (identities.length === 0) {
|
|
6053
|
-
return { ok: false, message: `agent-shim: no member of pool "${poolName}" is an existing identity.` };
|
|
6054
|
-
}
|
|
6055
|
-
const resuming = isResuming(params.passthrough);
|
|
6056
|
-
const stickyRead = readStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd);
|
|
6057
|
-
if (stickyRead.problem !== void 0) {
|
|
6058
|
-
log.warn(`agent-shim: ${stickyRead.problem}; ignoring the last-pick record`);
|
|
6059
|
-
}
|
|
6060
|
-
for (; ; ) {
|
|
6061
|
-
const nowMs = params.now();
|
|
6062
|
-
const ranking = rankPoolFromStore({ fs: params.usageFs, paths: params.paths, identities, nowMs, resuming, ...stickyRead.sticky === void 0 ? {} : { sticky: stickyRead.sticky } });
|
|
6063
|
-
const { pick, earliestReturn } = ranking;
|
|
6064
|
-
if (pick !== void 0) {
|
|
6065
|
-
recordStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd, pick.identity, nowMs);
|
|
6066
|
-
return { ok: true, identity: pick.identity, explanation: `pool ${poolName}: ${pick.reasons.slice(0, REASONS_LOGGED).join("; ")}` };
|
|
6067
|
-
}
|
|
6068
|
-
if (earliestReturn === void 0) {
|
|
6069
|
-
return { ok: false, message: `agent-shim: no member of pool "${poolName}" can be picked.` };
|
|
6070
|
-
}
|
|
6071
|
-
const until = new Date(earliestReturn.atMs).toISOString();
|
|
6072
|
-
if (!params.wait) {
|
|
6073
|
-
return {
|
|
6074
|
-
ok: false,
|
|
6075
|
-
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.`
|
|
6076
|
-
};
|
|
6077
|
-
}
|
|
6078
|
-
log.info(`agent-shim: every member of pool "${poolName}" is refused; waiting until ${until} for ${earliestReturn.identity}`);
|
|
6079
|
-
params.sleep(Math.max(earliestReturn.atMs - nowMs, 0));
|
|
6080
|
-
}
|
|
6081
|
-
}
|
|
6082
|
-
|
|
6083
6114
|
// src/poolPickReport.ts
|
|
6084
6115
|
function candidateView(candidate) {
|
|
6085
6116
|
return {
|
|
@@ -6093,14 +6124,15 @@ function candidateView(candidate) {
|
|
|
6093
6124
|
};
|
|
6094
6125
|
}
|
|
6095
6126
|
function collectPoolPick(params) {
|
|
6096
|
-
const { present, missing } = splitMembers(params.pool, params.paths, params.fs);
|
|
6097
6127
|
const sticky = readStickyPick(params.usageFs, params.paths.usagePicksFile, params.directory);
|
|
6098
|
-
const ranking =
|
|
6128
|
+
const { ranking, missing } = rankPoolGraph({
|
|
6099
6129
|
fs: params.usageFs,
|
|
6100
6130
|
paths: params.paths,
|
|
6101
|
-
|
|
6131
|
+
pools: params.pools,
|
|
6132
|
+
poolName: params.poolName,
|
|
6102
6133
|
nowMs: params.nowMs,
|
|
6103
6134
|
resuming: false,
|
|
6135
|
+
identityExists: (name) => loadIdentity(params.paths.identitiesDir, name, params.fs) !== void 0,
|
|
6104
6136
|
...sticky.sticky === void 0 ? {} : { sticky: sticky.sticky }
|
|
6105
6137
|
});
|
|
6106
6138
|
return {
|
|
@@ -6108,7 +6140,7 @@ function collectPoolPick(params) {
|
|
|
6108
6140
|
directory: params.directory,
|
|
6109
6141
|
...ranking.pick === void 0 ? {} : { pick: ranking.pick.identity },
|
|
6110
6142
|
candidates: ranking.candidates.map(candidateView),
|
|
6111
|
-
missing:
|
|
6143
|
+
missing: missing.map(({ identity }) => identity),
|
|
6112
6144
|
...ranking.earliestReturn === void 0 ? {} : { earliestReturn: { identity: ranking.earliestReturn.identity, at: new Date(ranking.earliestReturn.atMs).toISOString() } },
|
|
6113
6145
|
...sticky.problem === void 0 ? {} : { stickyProblem: sticky.problem }
|
|
6114
6146
|
};
|
|
@@ -6278,7 +6310,41 @@ function requirePool(paths, name) {
|
|
|
6278
6310
|
function writePools(paths, pools) {
|
|
6279
6311
|
applyPatch(paths.globalConfigFile, GlobalConfigSchema, { pools }, { defaults: {} });
|
|
6280
6312
|
}
|
|
6281
|
-
function
|
|
6313
|
+
function poolCycleOf(pools, name) {
|
|
6314
|
+
const walk = (current, path32) => {
|
|
6315
|
+
for (const entry of pools[current]?.identities ?? []) {
|
|
6316
|
+
const nested = poolNameOf(entry);
|
|
6317
|
+
if (nested === void 0) {
|
|
6318
|
+
continue;
|
|
6319
|
+
}
|
|
6320
|
+
if (nested === name) {
|
|
6321
|
+
return [...path32, nested];
|
|
6322
|
+
}
|
|
6323
|
+
if (path32.includes(nested)) {
|
|
6324
|
+
continue;
|
|
6325
|
+
}
|
|
6326
|
+
const found = walk(nested, [...path32, nested]);
|
|
6327
|
+
if (found !== void 0) {
|
|
6328
|
+
return found;
|
|
6329
|
+
}
|
|
6330
|
+
}
|
|
6331
|
+
return void 0;
|
|
6332
|
+
};
|
|
6333
|
+
return walk(name, [name]);
|
|
6334
|
+
}
|
|
6335
|
+
function validateMembers(pools, name, members) {
|
|
6336
|
+
for (const entry of members) {
|
|
6337
|
+
const nested = poolNameOf(entry);
|
|
6338
|
+
if (nested !== void 0 && nested !== name && pools[nested] === void 0) {
|
|
6339
|
+
throw new UsageError(`Member "${entry}" of pool "${name}" names a pool that is not defined. Run \`agent-shim pool add ${nested} --identity <name>...\` first.`);
|
|
6340
|
+
}
|
|
6341
|
+
}
|
|
6342
|
+
const cycle = poolCycleOf({ ...pools, [name]: { identities: [...members] } }, name);
|
|
6343
|
+
if (cycle !== void 0) {
|
|
6344
|
+
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.`);
|
|
6345
|
+
}
|
|
6346
|
+
}
|
|
6347
|
+
function addPool(paths, name, identities, preference) {
|
|
6282
6348
|
if (!PoolNameSchema.safeParse(name).success) {
|
|
6283
6349
|
throw new InvalidPoolNameError(name);
|
|
6284
6350
|
}
|
|
@@ -6286,13 +6352,15 @@ function addPool(paths, name, identities) {
|
|
|
6286
6352
|
if (pools[name] !== void 0) {
|
|
6287
6353
|
throw new PoolAlreadyExistsError(name);
|
|
6288
6354
|
}
|
|
6289
|
-
|
|
6355
|
+
validateMembers(pools, name, identities);
|
|
6356
|
+
const pool = { identities: [...identities], ...preference === void 0 ? {} : { preference } };
|
|
6290
6357
|
writePools(paths, { ...pools, [name]: pool });
|
|
6291
6358
|
return pool;
|
|
6292
6359
|
}
|
|
6293
|
-
function setPool(paths, name, identities) {
|
|
6360
|
+
function setPool(paths, name, identities, preference) {
|
|
6294
6361
|
requirePool(paths, name);
|
|
6295
|
-
|
|
6362
|
+
validateMembers(readPools(paths), name, identities);
|
|
6363
|
+
const pool = { identities: [...identities], ...preference === void 0 ? {} : { preference } };
|
|
6296
6364
|
writePools(paths, { ...readPools(paths), [name]: pool });
|
|
6297
6365
|
return pool;
|
|
6298
6366
|
}
|
|
@@ -8203,11 +8271,11 @@ function collectCheckReport(params) {
|
|
|
8203
8271
|
});
|
|
8204
8272
|
let poolPick;
|
|
8205
8273
|
if (decidedIdentity.pool !== void 0) {
|
|
8206
|
-
const
|
|
8207
|
-
if (pool === void 0) {
|
|
8274
|
+
const pools = loaded.globalConfig?.pools ?? {};
|
|
8275
|
+
if (pools[decidedIdentity.pool] === void 0) {
|
|
8208
8276
|
throw new PoolNotFoundError(decidedIdentity.pool);
|
|
8209
8277
|
}
|
|
8210
|
-
poolPick = collectPoolPick({ paths, fs: realFsPort, usageFs: realFarmFs, poolName: decidedIdentity.pool,
|
|
8278
|
+
poolPick = collectPoolPick({ paths, fs: realFsPort, usageFs: realFarmFs, poolName: decidedIdentity.pool, pools, directory: cwd, nowMs: Date.now() });
|
|
8211
8279
|
}
|
|
8212
8280
|
const identityDecision = poolPick?.pick === void 0 ? decidedIdentity : { ...decidedIdentity, name: poolPick.pick };
|
|
8213
8281
|
const loadedIdentity = identityDecision.name === void 0 ? void 0 : loadIdentity(paths.identitiesDir, identityDecision.name, realFsPort);
|
|
@@ -8341,7 +8409,7 @@ var PoolPickReportSchema = z13.strictObject({
|
|
|
8341
8409
|
/** The member a launch would run as, absent when every member is refused. */
|
|
8342
8410
|
pick: z13.string().optional(),
|
|
8343
8411
|
candidates: z13.array(PoolPickCandidateViewSchema),
|
|
8344
|
-
/**
|
|
8412
|
+
/** Identity members anywhere in the pool's graph that do not exist; a launch skips them. */
|
|
8345
8413
|
missing: z13.array(z13.string()),
|
|
8346
8414
|
/** When nothing can be picked, the soonest any member returns. */
|
|
8347
8415
|
earliestReturn: z13.strictObject({ identity: z13.string(), at: z13.iso.datetime() }).optional(),
|
|
@@ -8971,12 +9039,33 @@ function runDoctor(params) {
|
|
|
8971
9039
|
const validatedGlobalConfig = params.globalConfig.raw === void 0 ? void 0 : validateJson(GlobalConfigSchema, params.globalConfig);
|
|
8972
9040
|
const pools = validatedGlobalConfig?.ok === true ? validatedGlobalConfig.data.pools ?? {} : {};
|
|
8973
9041
|
for (const [poolName, pool] of Object.entries(pools)) {
|
|
8974
|
-
const missing = pool.identities.filter((member) => !validIdentityNames.has(member));
|
|
8975
|
-
|
|
9042
|
+
const missing = pool.identities.filter((member) => poolNameOf(member) === void 0 && !validIdentityNames.has(member));
|
|
9043
|
+
const undefinedPools = pool.identities.flatMap((member) => {
|
|
9044
|
+
const nested = poolNameOf(member);
|
|
9045
|
+
return nested === void 0 || nested in pools ? [] : [nested];
|
|
9046
|
+
});
|
|
9047
|
+
if (missing.length === 0 && undefinedPools.length === 0) {
|
|
8976
9048
|
push("pool", "pass", `${poolName} is valid (${pool.identities.join(", ")}).`, poolName);
|
|
8977
9049
|
} else {
|
|
8978
|
-
|
|
9050
|
+
const problems = [
|
|
9051
|
+
...missing.map((member) => `identity "${member}", which does not exist`),
|
|
9052
|
+
...undefinedPools.map((nested) => `pool "${nested}", which is not defined`)
|
|
9053
|
+
];
|
|
9054
|
+
push("pool", "fail", `Pool "${poolName}" names ${problems.join(" and ")}.`, poolName);
|
|
9055
|
+
}
|
|
9056
|
+
}
|
|
9057
|
+
const reportedCycles = /* @__PURE__ */ new Set();
|
|
9058
|
+
for (const poolName of Object.keys(pools).sort()) {
|
|
9059
|
+
const chain = poolCycleOf(pools, poolName);
|
|
9060
|
+
if (chain === void 0) {
|
|
9061
|
+
continue;
|
|
9062
|
+
}
|
|
9063
|
+
const key = [...chain].slice(0, -1).sort().join(">");
|
|
9064
|
+
if (reportedCycles.has(key)) {
|
|
9065
|
+
continue;
|
|
8979
9066
|
}
|
|
9067
|
+
reportedCycles.add(key);
|
|
9068
|
+
push("pool", "fail", `Pool cycle: ${chain.join(" -> ")}. A pool cannot nest itself, directly or through another pool.`, chain[0]);
|
|
8980
9069
|
}
|
|
8981
9070
|
const selectionExists = (selector) => {
|
|
8982
9071
|
const poolName = poolNameOf(selector);
|
|
@@ -10779,7 +10868,7 @@ var LAUNCHER_FLAG_NAMES = [
|
|
|
10779
10868
|
...BOOLEAN_FLAGS.flatMap(({ flag }) => [flag, `--no-${flag.slice(2)}`])
|
|
10780
10869
|
];
|
|
10781
10870
|
var NATIVE_FLAG = "--native";
|
|
10782
|
-
var
|
|
10871
|
+
var TERMINATOR = "--";
|
|
10783
10872
|
function matchValuedFlag(token) {
|
|
10784
10873
|
for (const flag of VALUED_FLAGS) {
|
|
10785
10874
|
if (token === flag) {
|
|
@@ -10821,7 +10910,7 @@ function parseLauncherArgv(argv) {
|
|
|
10821
10910
|
if (token === void 0) {
|
|
10822
10911
|
continue;
|
|
10823
10912
|
}
|
|
10824
|
-
if (token ===
|
|
10913
|
+
if (token === TERMINATOR) {
|
|
10825
10914
|
rest.push(...remaining.slice(index));
|
|
10826
10915
|
break;
|
|
10827
10916
|
}
|
|
@@ -10848,7 +10937,7 @@ function parseLauncherArgv(argv) {
|
|
|
10848
10937
|
value = matched.inlineValue;
|
|
10849
10938
|
} else {
|
|
10850
10939
|
const next = remaining[index + 1];
|
|
10851
|
-
if (next === void 0 || next ===
|
|
10940
|
+
if (next === void 0 || next === TERMINATOR) {
|
|
10852
10941
|
rest.push(token);
|
|
10853
10942
|
continue;
|
|
10854
10943
|
}
|
|
@@ -11132,6 +11221,76 @@ function splitExtraFlags(value) {
|
|
|
11132
11221
|
return trimmed.split(/\s+/);
|
|
11133
11222
|
}
|
|
11134
11223
|
|
|
11224
|
+
// src/launcher/pool.ts
|
|
11225
|
+
var RESUME_FLAGS = /* @__PURE__ */ new Set(["--continue", "-c", "--resume", "-r"]);
|
|
11226
|
+
var TERMINATOR2 = "--";
|
|
11227
|
+
var REASONS_LOGGED = 3;
|
|
11228
|
+
function isResuming(passthrough) {
|
|
11229
|
+
const end = passthrough.indexOf(TERMINATOR2);
|
|
11230
|
+
return (end === -1 ? passthrough : passthrough.slice(0, end)).some((token) => RESUME_FLAGS.has(token));
|
|
11231
|
+
}
|
|
11232
|
+
function resolvePoolLaunch(params) {
|
|
11233
|
+
const { poolName, log } = params;
|
|
11234
|
+
const pool = params.pools?.[poolName];
|
|
11235
|
+
if (pool === void 0) {
|
|
11236
|
+
return { ok: false, message: `agent-shim: no pool named "${poolName}" (selected via ${params.selectedVia}). Run \`agent-shim pool add ${poolName} --identity <name>...\` first.` };
|
|
11237
|
+
}
|
|
11238
|
+
const resuming = isResuming(params.passthrough);
|
|
11239
|
+
const stickyRead = readStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd);
|
|
11240
|
+
if (stickyRead.problem !== void 0) {
|
|
11241
|
+
log.warn(`agent-shim: ${stickyRead.problem}; ignoring the last-pick record`);
|
|
11242
|
+
}
|
|
11243
|
+
const warned = /* @__PURE__ */ new Set();
|
|
11244
|
+
for (; ; ) {
|
|
11245
|
+
const nowMs = params.now();
|
|
11246
|
+
let graph;
|
|
11247
|
+
try {
|
|
11248
|
+
graph = rankPoolGraph({
|
|
11249
|
+
fs: params.usageFs,
|
|
11250
|
+
paths: params.paths,
|
|
11251
|
+
pools: params.pools ?? {},
|
|
11252
|
+
poolName,
|
|
11253
|
+
nowMs,
|
|
11254
|
+
resuming,
|
|
11255
|
+
identityExists: (name) => loadIdentity(params.paths.identitiesDir, name, params.fs) !== void 0,
|
|
11256
|
+
...stickyRead.sticky === void 0 ? {} : { sticky: stickyRead.sticky }
|
|
11257
|
+
});
|
|
11258
|
+
} catch (error) {
|
|
11259
|
+
if (error instanceof PoolGraphError) {
|
|
11260
|
+
return { ok: false, message: `agent-shim: ${error.message}` };
|
|
11261
|
+
}
|
|
11262
|
+
throw error;
|
|
11263
|
+
}
|
|
11264
|
+
for (const gone of graph.missing) {
|
|
11265
|
+
const key = `${gone.pool}:${gone.identity}`;
|
|
11266
|
+
if (!warned.has(key)) {
|
|
11267
|
+
warned.add(key);
|
|
11268
|
+
log.warn(`agent-shim: pool "${gone.pool}" names identity "${gone.identity}", which does not exist; skipping it`);
|
|
11269
|
+
}
|
|
11270
|
+
}
|
|
11271
|
+
if (graph.ranking.candidates.length === 0) {
|
|
11272
|
+
return { ok: false, message: `agent-shim: no member of pool "${poolName}" is an existing identity.` };
|
|
11273
|
+
}
|
|
11274
|
+
const { pick, earliestReturn } = graph.ranking;
|
|
11275
|
+
if (pick !== void 0) {
|
|
11276
|
+
recordStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd, pick.identity, nowMs);
|
|
11277
|
+
return { ok: true, identity: pick.identity, explanation: `pool ${poolName}: ${pick.reasons.slice(0, REASONS_LOGGED).join("; ")}` };
|
|
11278
|
+
}
|
|
11279
|
+
if (earliestReturn === void 0) {
|
|
11280
|
+
return { ok: false, message: `agent-shim: no member of pool "${poolName}" can be picked.` };
|
|
11281
|
+
}
|
|
11282
|
+
const until = new Date(earliestReturn.atMs).toISOString();
|
|
11283
|
+
if (!params.wait) {
|
|
11284
|
+
return {
|
|
11285
|
+
ok: false,
|
|
11286
|
+
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.`
|
|
11287
|
+
};
|
|
11288
|
+
}
|
|
11289
|
+
log.info(`agent-shim: every member of pool "${poolName}" is refused; waiting until ${until} for ${earliestReturn.identity}`);
|
|
11290
|
+
params.sleep(Math.max(earliestReturn.atMs - nowMs, 0));
|
|
11291
|
+
}
|
|
11292
|
+
}
|
|
11293
|
+
|
|
11135
11294
|
// src/launcher/spawn.ts
|
|
11136
11295
|
import os6 from "node:os";
|
|
11137
11296
|
var SIGNAL_EXIT_CODE_OFFSET = 128;
|