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/README.md
CHANGED
|
@@ -57,7 +57,7 @@ That's it — with no further configuration, everything in `~/.claude` that isn'
|
|
|
57
57
|
- **An identity** is a login: the thing that owns credentials and daemon state, selected with `agent-shim run @<name>` or, once `agent-shim shim enable` has run, `claude @<name>`.
|
|
58
58
|
- **A configuration profile** is a reusable, named bundle of sharing rules, independent of any identity, resolved per working directory via directory rules or a committed `.agent-shim.json`.
|
|
59
59
|
|
|
60
|
-
A third, optional thing sits on top of identities: **a pool** is a named list of them. Select `pool:<name>` where an identity name goes (`claude @pool:subs`) and agent-shim picks the member whose unused quota is closest to expiring, skipping any that are refused right now, using the usage it already records. See [docs/configuration-model.md](docs/configuration-model.md#pools-picking-an-account-at-launch).
|
|
60
|
+
A third, optional thing sits on top of identities: **a pool** is a named list of them. Select `pool:<name>` where an identity name goes (`claude @pool:subs`) and agent-shim picks the member whose unused quota is closest to expiring, skipping any that are refused right now, using the usage it already records. A pool can instead declare `--preference listed`, which tries the members in the order the pool lists them, skipping only one that is refused right now. A member entry may itself name another pool (`pool:<name>`), which is ranked with its own policy and contributes its pick at that position, so "this identity first, then the fleet" nests the fleet rather than copying its member list; cycles are refused. See [docs/configuration-model.md](docs/configuration-model.md#pools-picking-an-account-at-launch).
|
|
61
61
|
|
|
62
62
|
Every top-level entry in `~/.claude` is classified into one of five categories, shipped as a default map (`config/categories.default.json`):
|
|
63
63
|
|
|
@@ -103,8 +103,8 @@ agent-shim profile show <name> [--json]
|
|
|
103
103
|
agent-shim profile remove <name> [--yes] [--json]
|
|
104
104
|
agent-shim profile use <name> # the global default configuration profile [--json]
|
|
105
105
|
|
|
106
|
-
agent-shim pool add <name> --identity <
|
|
107
|
-
agent-shim pool set <name> --identity <
|
|
106
|
+
agent-shim pool add <name> --identity <member>... [--preference <score|listed>] [--json]
|
|
107
|
+
agent-shim pool set <name> [--identity <member>...] [--preference <score|listed> | --no-preference] [--json]
|
|
108
108
|
agent-shim pool list [--json]
|
|
109
109
|
agent-shim pool show <name> [--json]
|
|
110
110
|
agent-shim pool remove <name> [--yes] [--json]
|
package/dist/cli.cjs
CHANGED
|
@@ -55299,9 +55299,14 @@ var POOL_SELECTOR_PREFIX = "pool:";
|
|
|
55299
55299
|
var POOL_NAME_PATTERN = "[A-Za-z0-9][A-Za-z0-9._-]*";
|
|
55300
55300
|
var IDENTITY_NAME_PATTERN = "[A-Za-z0-9][A-Za-z0-9._@-]*";
|
|
55301
55301
|
var PoolNameSchema = external_exports.string().min(1).regex(new RegExp(`^${POOL_NAME_PATTERN}$`));
|
|
55302
|
-
var
|
|
55302
|
+
var SELECTOR_RE = new RegExp(`^(${POOL_SELECTOR_PREFIX}${POOL_NAME_PATTERN}|${IDENTITY_NAME_PATTERN})$`);
|
|
55303
|
+
var IdentitySelectorSchema = external_exports.string().min(1).regex(SELECTOR_RE);
|
|
55304
|
+
var PoolMemberSchema = external_exports.string().min(1).regex(SELECTOR_RE);
|
|
55305
|
+
var POOL_PREFERENCES = ["score", "listed"];
|
|
55306
|
+
var PoolPreferenceSchema = external_exports.enum(POOL_PREFERENCES);
|
|
55303
55307
|
var PoolSchema = external_exports.strictObject({
|
|
55304
|
-
identities: external_exports.array(
|
|
55308
|
+
identities: external_exports.array(PoolMemberSchema).min(1).refine((members2) => new Set(members2).size === members2.length, { message: "pool members must be unique" }),
|
|
55309
|
+
preference: PoolPreferenceSchema.optional()
|
|
55305
55310
|
});
|
|
55306
55311
|
var DirectoryRuleSchema = ConfigProfileSchema.omit({ description: true }).extend({
|
|
55307
55312
|
path: external_exports.string().min(1),
|
|
@@ -58305,7 +58310,41 @@ function requirePool(paths, name) {
|
|
|
58305
58310
|
function writePools(paths, pools) {
|
|
58306
58311
|
applyPatch(paths.globalConfigFile, GlobalConfigSchema, { pools }, { defaults: {} });
|
|
58307
58312
|
}
|
|
58308
|
-
function
|
|
58313
|
+
function poolCycleOf(pools, name) {
|
|
58314
|
+
const walk = (current, path44) => {
|
|
58315
|
+
for (const entry of pools[current]?.identities ?? []) {
|
|
58316
|
+
const nested = poolNameOf(entry);
|
|
58317
|
+
if (nested === void 0) {
|
|
58318
|
+
continue;
|
|
58319
|
+
}
|
|
58320
|
+
if (nested === name) {
|
|
58321
|
+
return [...path44, nested];
|
|
58322
|
+
}
|
|
58323
|
+
if (path44.includes(nested)) {
|
|
58324
|
+
continue;
|
|
58325
|
+
}
|
|
58326
|
+
const found = walk(nested, [...path44, nested]);
|
|
58327
|
+
if (found !== void 0) {
|
|
58328
|
+
return found;
|
|
58329
|
+
}
|
|
58330
|
+
}
|
|
58331
|
+
return void 0;
|
|
58332
|
+
};
|
|
58333
|
+
return walk(name, [name]);
|
|
58334
|
+
}
|
|
58335
|
+
function validateMembers(pools, name, members2) {
|
|
58336
|
+
for (const entry of members2) {
|
|
58337
|
+
const nested = poolNameOf(entry);
|
|
58338
|
+
if (nested !== void 0 && nested !== name && pools[nested] === void 0) {
|
|
58339
|
+
throw new UsageError(`Member "${entry}" of pool "${name}" names a pool that is not defined. Run \`agent-shim pool add ${nested} --identity <name>...\` first.`);
|
|
58340
|
+
}
|
|
58341
|
+
}
|
|
58342
|
+
const cycle = poolCycleOf({ ...pools, [name]: { identities: [...members2] } }, name);
|
|
58343
|
+
if (cycle !== void 0) {
|
|
58344
|
+
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.`);
|
|
58345
|
+
}
|
|
58346
|
+
}
|
|
58347
|
+
function addPool(paths, name, identities, preference) {
|
|
58309
58348
|
if (!PoolNameSchema.safeParse(name).success) {
|
|
58310
58349
|
throw new InvalidPoolNameError(name);
|
|
58311
58350
|
}
|
|
@@ -58313,13 +58352,15 @@ function addPool(paths, name, identities) {
|
|
|
58313
58352
|
if (pools[name] !== void 0) {
|
|
58314
58353
|
throw new PoolAlreadyExistsError(name);
|
|
58315
58354
|
}
|
|
58316
|
-
|
|
58355
|
+
validateMembers(pools, name, identities);
|
|
58356
|
+
const pool = { identities: [...identities], ...preference === void 0 ? {} : { preference } };
|
|
58317
58357
|
writePools(paths, { ...pools, [name]: pool });
|
|
58318
58358
|
return pool;
|
|
58319
58359
|
}
|
|
58320
|
-
function setPool(paths, name, identities) {
|
|
58360
|
+
function setPool(paths, name, identities, preference) {
|
|
58321
58361
|
requirePool(paths, name);
|
|
58322
|
-
|
|
58362
|
+
validateMembers(readPools(paths), name, identities);
|
|
58363
|
+
const pool = { identities: [...identities], ...preference === void 0 ? {} : { preference } };
|
|
58323
58364
|
writePools(paths, { ...readPools(paths), [name]: pool });
|
|
58324
58365
|
return pool;
|
|
58325
58366
|
}
|
|
@@ -61749,7 +61790,7 @@ function registerIdentityCommand(program2, deps) {
|
|
|
61749
61790
|
// package.json
|
|
61750
61791
|
var package_default = {
|
|
61751
61792
|
name: "agent-shim",
|
|
61752
|
-
version: "8.
|
|
61793
|
+
version: "8.20.0",
|
|
61753
61794
|
description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
|
|
61754
61795
|
license: "MIT",
|
|
61755
61796
|
author: "Joseph Mearman <joseph@mearman.co.uk>",
|
|
@@ -61828,7 +61869,7 @@ var package_default = {
|
|
|
61828
61869
|
rollup: "4.64.0",
|
|
61829
61870
|
"rollup-plugin-dts": "6.5.1",
|
|
61830
61871
|
"semantic-release": "^25.0.9",
|
|
61831
|
-
turbo: "^2.
|
|
61872
|
+
turbo: "^2.11.5",
|
|
61832
61873
|
typescript: "^6.0.3",
|
|
61833
61874
|
"typescript-eslint": "^8.70.0",
|
|
61834
61875
|
vitest: "^4.1.11"
|
|
@@ -62756,7 +62797,30 @@ function describeWindow(label, window, effective, nowMs) {
|
|
|
62756
62797
|
function capacityOf(plan) {
|
|
62757
62798
|
return plan.kind === "subscription" ? plan.capacity : 1;
|
|
62758
62799
|
}
|
|
62800
|
+
function assessRefusedNested(member, nested, nowMs) {
|
|
62801
|
+
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 };
|
|
62802
|
+
return {
|
|
62803
|
+
candidate: {
|
|
62804
|
+
identity: member.identity,
|
|
62805
|
+
class: "ineligible",
|
|
62806
|
+
...refusal.blockedUntilMs === void 0 ? {} : { blockedUntilMs: refusal.blockedUntilMs },
|
|
62807
|
+
plan: planOf(member.account),
|
|
62808
|
+
reasons: [refusal.reason]
|
|
62809
|
+
}
|
|
62810
|
+
};
|
|
62811
|
+
}
|
|
62759
62812
|
function assess(member, nowMs) {
|
|
62813
|
+
const nested = member.nested;
|
|
62814
|
+
if (nested === void 0) {
|
|
62815
|
+
return assessIdentity(member, nowMs);
|
|
62816
|
+
}
|
|
62817
|
+
if (nested.kind === "refused") {
|
|
62818
|
+
return assessRefusedNested(member, nested, nowMs);
|
|
62819
|
+
}
|
|
62820
|
+
const assessment = assessIdentity(member, nowMs);
|
|
62821
|
+
return { ...assessment, candidate: { ...assessment.candidate, reasons: [`picked by pool "${nested.pool}": ${nested.reasons.join("; ")}`, ...assessment.candidate.reasons] } };
|
|
62822
|
+
}
|
|
62823
|
+
function assessIdentity(member, nowMs) {
|
|
62760
62824
|
const plan = planOf(member.account);
|
|
62761
62825
|
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`];
|
|
62762
62826
|
const base = { identity: member.identity, plan };
|
|
@@ -62823,7 +62887,7 @@ function rankPool(input2) {
|
|
|
62823
62887
|
}
|
|
62824
62888
|
return { ...candidate, feasible: false, reasons: [`5h window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(fiveHour.untilResetMs)}`, ...candidate.reasons] };
|
|
62825
62889
|
});
|
|
62826
|
-
const ordered = [...candidates].sort(
|
|
62890
|
+
const ordered = input2.preference === "listed" ? [...candidates] : [...candidates].sort(
|
|
62827
62891
|
(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)
|
|
62828
62892
|
);
|
|
62829
62893
|
const sticky = input2.sticky;
|
|
@@ -63108,6 +63172,12 @@ function createUsageStore(deps) {
|
|
|
63108
63172
|
}
|
|
63109
63173
|
|
|
63110
63174
|
// src/usage/poolPick.ts
|
|
63175
|
+
var PoolGraphError = class extends CliError {
|
|
63176
|
+
constructor(message) {
|
|
63177
|
+
super(message);
|
|
63178
|
+
this.name = "PoolGraphError";
|
|
63179
|
+
}
|
|
63180
|
+
};
|
|
63111
63181
|
var SCHEMA_VERSION = 1;
|
|
63112
63182
|
var PickEntrySchema = external_exports.strictObject({ identity: external_exports.string().min(1), at: external_exports.iso.datetime() });
|
|
63113
63183
|
var PicksFileSchema = external_exports.strictObject({
|
|
@@ -63134,13 +63204,63 @@ function loadPoolMembers(fs19, paths, identities, nowMs) {
|
|
|
63134
63204
|
}
|
|
63135
63205
|
});
|
|
63136
63206
|
}
|
|
63137
|
-
function
|
|
63138
|
-
return
|
|
63139
|
-
|
|
63140
|
-
|
|
63141
|
-
|
|
63142
|
-
|
|
63207
|
+
function rankPoolGraph(input2) {
|
|
63208
|
+
return rankNamedPool(input2, input2.poolName, [], input2.sticky);
|
|
63209
|
+
}
|
|
63210
|
+
function rankNamedPool(input2, poolName, stack, sticky) {
|
|
63211
|
+
const pool = input2.pools[poolName];
|
|
63212
|
+
if (pool === void 0) {
|
|
63213
|
+
throw new PoolGraphError(`No pool named "${poolName}". Run \`agent-shim pool add ${poolName} --identity <name>...\` first.`);
|
|
63214
|
+
}
|
|
63215
|
+
const childStack = [...stack, poolName];
|
|
63216
|
+
const direct = pool.identities.filter((entry) => poolNameOf(entry) === void 0);
|
|
63217
|
+
const missing = direct.filter((entry) => !input2.identityExists(entry)).map((identity) => ({ pool: poolName, identity }));
|
|
63218
|
+
const loaded = new Map(loadPoolMembers(input2.fs, input2.paths, direct.filter(input2.identityExists), input2.nowMs).map((member) => [member.identity, member]));
|
|
63219
|
+
const members2 = [];
|
|
63220
|
+
for (const entry of pool.identities) {
|
|
63221
|
+
const nestedName = poolNameOf(entry);
|
|
63222
|
+
if (nestedName === void 0) {
|
|
63223
|
+
const member = loaded.get(entry);
|
|
63224
|
+
if (member !== void 0) {
|
|
63225
|
+
members2.push(member);
|
|
63226
|
+
}
|
|
63227
|
+
continue;
|
|
63228
|
+
}
|
|
63229
|
+
if (input2.pools[nestedName] === void 0) {
|
|
63230
|
+
throw new PoolGraphError(`Pool "${poolName}" member "${entry}" names a pool that is not defined.`);
|
|
63231
|
+
}
|
|
63232
|
+
if (childStack.includes(nestedName)) {
|
|
63233
|
+
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.`);
|
|
63234
|
+
}
|
|
63235
|
+
const nested = nestedMember(input2, nestedName, childStack, sticky);
|
|
63236
|
+
members2.push(nested.member);
|
|
63237
|
+
missing.push(...nested.missing);
|
|
63238
|
+
}
|
|
63239
|
+
const ranking = rankPool({
|
|
63240
|
+
members: members2,
|
|
63241
|
+
nowMs: input2.nowMs,
|
|
63242
|
+
resuming: input2.resuming,
|
|
63243
|
+
...sticky === void 0 ? {} : { sticky },
|
|
63244
|
+
...pool.preference === void 0 ? {} : { preference: pool.preference }
|
|
63143
63245
|
});
|
|
63246
|
+
return { ranking, missing };
|
|
63247
|
+
}
|
|
63248
|
+
function nestedMember(input2, nestedName, childStack, sticky) {
|
|
63249
|
+
const nested = rankNamedPool(input2, nestedName, childStack, sticky);
|
|
63250
|
+
const pick2 = nested.ranking.pick;
|
|
63251
|
+
if (pick2 !== void 0) {
|
|
63252
|
+
const [member] = loadPoolMembers(input2.fs, input2.paths, [pick2.identity], input2.nowMs);
|
|
63253
|
+
return { member: { ...member ?? { identity: pick2.identity, records: [] }, nested: { kind: "pick", pool: nestedName, reasons: pick2.reasons } }, missing: nested.missing };
|
|
63254
|
+
}
|
|
63255
|
+
const earliestReturn = nested.ranking.earliestReturn;
|
|
63256
|
+
return {
|
|
63257
|
+
member: {
|
|
63258
|
+
identity: earliestReturn?.identity ?? `pool:${nestedName}`,
|
|
63259
|
+
records: [],
|
|
63260
|
+
nested: { kind: "refused", pool: nestedName, ...earliestReturn === void 0 ? {} : { earliestReturn } }
|
|
63261
|
+
},
|
|
63262
|
+
missing: nested.missing
|
|
63263
|
+
};
|
|
63144
63264
|
}
|
|
63145
63265
|
function readPicksFile(fs19, file2) {
|
|
63146
63266
|
const empty = { schemaVersion: SCHEMA_VERSION, picks: {} };
|
|
@@ -63171,59 +63291,6 @@ function recordStickyPick(fs19, picksFile, directory, identity, nowMs) {
|
|
|
63171
63291
|
`);
|
|
63172
63292
|
}
|
|
63173
63293
|
|
|
63174
|
-
// src/launcher/pool.ts
|
|
63175
|
-
var RESUME_FLAGS = /* @__PURE__ */ new Set(["--continue", "-c", "--resume", "-r"]);
|
|
63176
|
-
var TERMINATOR = "--";
|
|
63177
|
-
var REASONS_LOGGED = 3;
|
|
63178
|
-
function splitMembers(pool, paths, fs19) {
|
|
63179
|
-
const exists = (name) => loadIdentity(paths.identitiesDir, name, fs19) !== void 0;
|
|
63180
|
-
return { present: pool.identities.filter(exists), missing: pool.identities.filter((name) => !exists(name)) };
|
|
63181
|
-
}
|
|
63182
|
-
function isResuming(passthrough) {
|
|
63183
|
-
const end = passthrough.indexOf(TERMINATOR);
|
|
63184
|
-
return (end === -1 ? passthrough : passthrough.slice(0, end)).some((token) => RESUME_FLAGS.has(token));
|
|
63185
|
-
}
|
|
63186
|
-
function resolvePoolLaunch(params) {
|
|
63187
|
-
const { poolName, log } = params;
|
|
63188
|
-
const pool = params.pools?.[poolName];
|
|
63189
|
-
if (pool === void 0) {
|
|
63190
|
-
return { ok: false, message: `agent-shim: no pool named "${poolName}" (selected via ${params.selectedVia}). Run \`agent-shim pool add ${poolName} --identity <name>...\` first.` };
|
|
63191
|
-
}
|
|
63192
|
-
const { present: identities, missing } = splitMembers(pool, params.paths, params.fs);
|
|
63193
|
-
for (const name of missing) {
|
|
63194
|
-
log.warn(`agent-shim: pool "${poolName}" names identity "${name}", which does not exist; skipping it`);
|
|
63195
|
-
}
|
|
63196
|
-
if (identities.length === 0) {
|
|
63197
|
-
return { ok: false, message: `agent-shim: no member of pool "${poolName}" is an existing identity.` };
|
|
63198
|
-
}
|
|
63199
|
-
const resuming = isResuming(params.passthrough);
|
|
63200
|
-
const stickyRead = readStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd);
|
|
63201
|
-
if (stickyRead.problem !== void 0) {
|
|
63202
|
-
log.warn(`agent-shim: ${stickyRead.problem}; ignoring the last-pick record`);
|
|
63203
|
-
}
|
|
63204
|
-
for (; ; ) {
|
|
63205
|
-
const nowMs = params.now();
|
|
63206
|
-
const ranking = rankPoolFromStore({ fs: params.usageFs, paths: params.paths, identities, nowMs, resuming, ...stickyRead.sticky === void 0 ? {} : { sticky: stickyRead.sticky } });
|
|
63207
|
-
const { pick: pick2, earliestReturn } = ranking;
|
|
63208
|
-
if (pick2 !== void 0) {
|
|
63209
|
-
recordStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd, pick2.identity, nowMs);
|
|
63210
|
-
return { ok: true, identity: pick2.identity, explanation: `pool ${poolName}: ${pick2.reasons.slice(0, REASONS_LOGGED).join("; ")}` };
|
|
63211
|
-
}
|
|
63212
|
-
if (earliestReturn === void 0) {
|
|
63213
|
-
return { ok: false, message: `agent-shim: no member of pool "${poolName}" can be picked.` };
|
|
63214
|
-
}
|
|
63215
|
-
const until = new Date(earliestReturn.atMs).toISOString();
|
|
63216
|
-
if (!params.wait) {
|
|
63217
|
-
return {
|
|
63218
|
-
ok: false,
|
|
63219
|
-
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.`
|
|
63220
|
-
};
|
|
63221
|
-
}
|
|
63222
|
-
log.info(`agent-shim: every member of pool "${poolName}" is refused; waiting until ${until} for ${earliestReturn.identity}`);
|
|
63223
|
-
params.sleep(Math.max(earliestReturn.atMs - nowMs, 0));
|
|
63224
|
-
}
|
|
63225
|
-
}
|
|
63226
|
-
|
|
63227
63294
|
// src/poolPickReport.ts
|
|
63228
63295
|
function candidateView(candidate) {
|
|
63229
63296
|
return {
|
|
@@ -63237,14 +63304,15 @@ function candidateView(candidate) {
|
|
|
63237
63304
|
};
|
|
63238
63305
|
}
|
|
63239
63306
|
function collectPoolPick(params) {
|
|
63240
|
-
const { present, missing } = splitMembers(params.pool, params.paths, params.fs);
|
|
63241
63307
|
const sticky = readStickyPick(params.usageFs, params.paths.usagePicksFile, params.directory);
|
|
63242
|
-
const ranking =
|
|
63308
|
+
const { ranking, missing } = rankPoolGraph({
|
|
63243
63309
|
fs: params.usageFs,
|
|
63244
63310
|
paths: params.paths,
|
|
63245
|
-
|
|
63311
|
+
pools: params.pools,
|
|
63312
|
+
poolName: params.poolName,
|
|
63246
63313
|
nowMs: params.nowMs,
|
|
63247
63314
|
resuming: false,
|
|
63315
|
+
identityExists: (name) => loadIdentity(params.paths.identitiesDir, name, params.fs) !== void 0,
|
|
63248
63316
|
...sticky.sticky === void 0 ? {} : { sticky: sticky.sticky }
|
|
63249
63317
|
});
|
|
63250
63318
|
return {
|
|
@@ -63252,7 +63320,7 @@ function collectPoolPick(params) {
|
|
|
63252
63320
|
directory: params.directory,
|
|
63253
63321
|
...ranking.pick === void 0 ? {} : { pick: ranking.pick.identity },
|
|
63254
63322
|
candidates: ranking.candidates.map(candidateView),
|
|
63255
|
-
missing:
|
|
63323
|
+
missing: missing.map(({ identity }) => identity),
|
|
63256
63324
|
...ranking.earliestReturn === void 0 ? {} : { earliestReturn: { identity: ranking.earliestReturn.identity, at: new Date(ranking.earliestReturn.atMs).toISOString() } },
|
|
63257
63325
|
...sticky.problem === void 0 ? {} : { stickyProblem: sticky.problem }
|
|
63258
63326
|
};
|
|
@@ -64047,11 +64115,11 @@ function collectCheckReport(params) {
|
|
|
64047
64115
|
});
|
|
64048
64116
|
let poolPick;
|
|
64049
64117
|
if (decidedIdentity.pool !== void 0) {
|
|
64050
|
-
const
|
|
64051
|
-
if (pool === void 0) {
|
|
64118
|
+
const pools = loaded.globalConfig?.pools ?? {};
|
|
64119
|
+
if (pools[decidedIdentity.pool] === void 0) {
|
|
64052
64120
|
throw new PoolNotFoundError(decidedIdentity.pool);
|
|
64053
64121
|
}
|
|
64054
|
-
poolPick = collectPoolPick({ paths, fs: realFsPort, usageFs: realFarmFs, poolName: decidedIdentity.pool,
|
|
64122
|
+
poolPick = collectPoolPick({ paths, fs: realFsPort, usageFs: realFarmFs, poolName: decidedIdentity.pool, pools, directory: cwd, nowMs: Date.now() });
|
|
64055
64123
|
}
|
|
64056
64124
|
const identityDecision = poolPick?.pick === void 0 ? decidedIdentity : { ...decidedIdentity, name: poolPick.pick };
|
|
64057
64125
|
const loadedIdentity = identityDecision.name === void 0 ? void 0 : loadIdentity(paths.identitiesDir, identityDecision.name, realFsPort);
|
|
@@ -67940,13 +68008,34 @@ function runDoctor(params) {
|
|
|
67940
68008
|
const validatedGlobalConfig = params.globalConfig.raw === void 0 ? void 0 : validateJson(GlobalConfigSchema, params.globalConfig);
|
|
67941
68009
|
const pools = validatedGlobalConfig?.ok === true ? validatedGlobalConfig.data.pools ?? {} : {};
|
|
67942
68010
|
for (const [poolName, pool] of Object.entries(pools)) {
|
|
67943
|
-
const missing = pool.identities.filter((member) => !validIdentityNames.has(member));
|
|
67944
|
-
|
|
68011
|
+
const missing = pool.identities.filter((member) => poolNameOf(member) === void 0 && !validIdentityNames.has(member));
|
|
68012
|
+
const undefinedPools = pool.identities.flatMap((member) => {
|
|
68013
|
+
const nested = poolNameOf(member);
|
|
68014
|
+
return nested === void 0 || nested in pools ? [] : [nested];
|
|
68015
|
+
});
|
|
68016
|
+
if (missing.length === 0 && undefinedPools.length === 0) {
|
|
67945
68017
|
push("pool", "pass", `${poolName} is valid (${pool.identities.join(", ")}).`, poolName);
|
|
67946
68018
|
} else {
|
|
67947
|
-
|
|
68019
|
+
const problems = [
|
|
68020
|
+
...missing.map((member) => `identity "${member}", which does not exist`),
|
|
68021
|
+
...undefinedPools.map((nested) => `pool "${nested}", which is not defined`)
|
|
68022
|
+
];
|
|
68023
|
+
push("pool", "fail", `Pool "${poolName}" names ${problems.join(" and ")}.`, poolName);
|
|
67948
68024
|
}
|
|
67949
68025
|
}
|
|
68026
|
+
const reportedCycles = /* @__PURE__ */ new Set();
|
|
68027
|
+
for (const poolName of Object.keys(pools).sort()) {
|
|
68028
|
+
const chain = poolCycleOf(pools, poolName);
|
|
68029
|
+
if (chain === void 0) {
|
|
68030
|
+
continue;
|
|
68031
|
+
}
|
|
68032
|
+
const key = [...chain].slice(0, -1).sort().join(">");
|
|
68033
|
+
if (reportedCycles.has(key)) {
|
|
68034
|
+
continue;
|
|
68035
|
+
}
|
|
68036
|
+
reportedCycles.add(key);
|
|
68037
|
+
push("pool", "fail", `Pool cycle: ${chain.join(" -> ")}. A pool cannot nest itself, directly or through another pool.`, chain[0]);
|
|
68038
|
+
}
|
|
67950
68039
|
const selectionExists = (selector) => {
|
|
67951
68040
|
const poolName = poolNameOf(selector);
|
|
67952
68041
|
return poolName === void 0 ? validIdentityNames.has(selector) : poolName in pools;
|
|
@@ -69238,7 +69327,7 @@ var PoolPickReportSchema = external_exports.strictObject({
|
|
|
69238
69327
|
/** The member a launch would run as, absent when every member is refused. */
|
|
69239
69328
|
pick: external_exports.string().optional(),
|
|
69240
69329
|
candidates: external_exports.array(PoolPickCandidateViewSchema),
|
|
69241
|
-
/**
|
|
69330
|
+
/** Identity members anywhere in the pool's graph that do not exist; a launch skips them. */
|
|
69242
69331
|
missing: external_exports.array(external_exports.string()),
|
|
69243
69332
|
/** When nothing can be picked, the soonest any member returns. */
|
|
69244
69333
|
earliestReturn: external_exports.strictObject({ identity: external_exports.string(), at: external_exports.iso.datetime() }).optional(),
|
|
@@ -75782,7 +75871,7 @@ var LAUNCHER_FLAG_NAMES = [
|
|
|
75782
75871
|
...BOOLEAN_FLAGS.flatMap(({ flag }) => [flag, `--no-${flag.slice(2)}`])
|
|
75783
75872
|
];
|
|
75784
75873
|
var NATIVE_FLAG = "--native";
|
|
75785
|
-
var
|
|
75874
|
+
var TERMINATOR = "--";
|
|
75786
75875
|
function matchValuedFlag(token) {
|
|
75787
75876
|
for (const flag of VALUED_FLAGS) {
|
|
75788
75877
|
if (token === flag) {
|
|
@@ -75824,7 +75913,7 @@ function parseLauncherArgv(argv) {
|
|
|
75824
75913
|
if (token === void 0) {
|
|
75825
75914
|
continue;
|
|
75826
75915
|
}
|
|
75827
|
-
if (token ===
|
|
75916
|
+
if (token === TERMINATOR) {
|
|
75828
75917
|
rest.push(...remaining.slice(index));
|
|
75829
75918
|
break;
|
|
75830
75919
|
}
|
|
@@ -75851,7 +75940,7 @@ function parseLauncherArgv(argv) {
|
|
|
75851
75940
|
value2 = matched.inlineValue;
|
|
75852
75941
|
} else {
|
|
75853
75942
|
const next = remaining[index + 1];
|
|
75854
|
-
if (next === void 0 || next ===
|
|
75943
|
+
if (next === void 0 || next === TERMINATOR) {
|
|
75855
75944
|
rest.push(token);
|
|
75856
75945
|
continue;
|
|
75857
75946
|
}
|
|
@@ -76708,7 +76797,7 @@ function formatPoolPick(report, nowMs) {
|
|
|
76708
76797
|
function requireIdentities(paths, names) {
|
|
76709
76798
|
const existing = new Set(listIdentities(paths).map((entry) => entry.name));
|
|
76710
76799
|
for (const name of names) {
|
|
76711
|
-
if (!existing.has(name)) {
|
|
76800
|
+
if (poolNameOf(name) === void 0 && !existing.has(name)) {
|
|
76712
76801
|
throw new IdentityNotFoundError(name);
|
|
76713
76802
|
}
|
|
76714
76803
|
}
|
|
@@ -76719,40 +76808,48 @@ function membersOf(options) {
|
|
|
76719
76808
|
}
|
|
76720
76809
|
return options.identity;
|
|
76721
76810
|
}
|
|
76811
|
+
var PREFERENCE_DESCRIPTION = "How a launch picks a member: score (the default) picks the member whose remaining quota expires soonest; listed tries the members in the order the pool lists them, skipping any that is refused right now.";
|
|
76722
76812
|
function registerPoolCommand(program2, deps) {
|
|
76723
76813
|
const { paths } = deps;
|
|
76724
76814
|
const pool = withExamples(
|
|
76725
|
-
program2.command("pool").description("Manage pools: named sets of identities that a launch picks one member of, by remaining quota, with `claude @pool:<name>`."),
|
|
76815
|
+
program2.command("pool").description("Manage pools: named sets of identities that a launch picks one member of, by remaining quota or the pool's listed order, with `claude @pool:<name>`."),
|
|
76726
76816
|
["agent-shim pool add subs --identity work --identity personal", "agent-shim pool pick subs"]
|
|
76727
76817
|
);
|
|
76728
76818
|
withExamples(
|
|
76729
|
-
pool.command("add <name>").description("Define a pool. Fails if one with this name already exists.").option("--identity <name>", "A member identity (repeatable).", collectRepeated).option("--json", "Print the result as JSON.").action((name, options) => {
|
|
76819
|
+
pool.command("add <name>").description("Define a pool. Fails if one with this name already exists.").option("--identity <name>", "A member: an identity name, or pool:<name> to nest another pool with its own policy (repeatable).", collectRepeated).addOption(new Option("--preference <mode>", PREFERENCE_DESCRIPTION).choices(POOL_PREFERENCES)).option("--json", "Print the result as JSON.").action((name, options) => {
|
|
76730
76820
|
const members2 = membersOf(options);
|
|
76731
76821
|
requireIdentities(paths, members2);
|
|
76732
|
-
const created = addPool(paths, name, members2);
|
|
76822
|
+
const created = addPool(paths, name, members2, options.preference);
|
|
76733
76823
|
reportMutation(options.json, { action: "created", kind: "pool", name, value: created }, () => {
|
|
76734
76824
|
console.log(`Created pool "${name}" with ${members2.join(", ")}.`);
|
|
76735
76825
|
});
|
|
76736
76826
|
}),
|
|
76737
|
-
["agent-shim pool add subs --identity work --identity personal"]
|
|
76827
|
+
["agent-shim pool add subs --identity work --identity personal", "agent-shim pool add client --identity client-main --identity spare --preference listed"]
|
|
76738
76828
|
);
|
|
76739
76829
|
withExamples(
|
|
76740
|
-
pool.command("set <name>").description("Replace a pool's members.").option("--identity <name>", "A member identity (repeatable); the full new list.", collectRepeated).option("--json", "Print the result as JSON.").action((name, options) => {
|
|
76741
|
-
const
|
|
76742
|
-
|
|
76743
|
-
|
|
76830
|
+
pool.command("set <name>").description("Replace a pool's members, its preference, or both.").option("--identity <name>", "A member: an identity name, or pool:<name> to nest another pool with its own policy (repeatable); the full new list. Kept as-is when this option is absent.", collectRepeated).addOption(new Option("--preference <mode>", PREFERENCE_DESCRIPTION).choices(POOL_PREFERENCES)).option("--no-preference", "Clear the preference back to the default, score.").option("--json", "Print the result as JSON.").action((name, options) => {
|
|
76831
|
+
const existing = requirePool(paths, name);
|
|
76832
|
+
if (options.identity === void 0 && options.preference === void 0) {
|
|
76833
|
+
throw new UsageError("Nothing to change: pass --identity, --preference or --no-preference.");
|
|
76834
|
+
}
|
|
76835
|
+
const members2 = options.identity === void 0 ? existing.identities : membersOf(options);
|
|
76836
|
+
if (options.identity !== void 0) {
|
|
76837
|
+
requireIdentities(paths, members2);
|
|
76838
|
+
}
|
|
76839
|
+
const preference = options.preference === void 0 ? existing.preference : options.preference === false ? void 0 : options.preference;
|
|
76840
|
+
const updated = setPool(paths, name, members2, preference);
|
|
76744
76841
|
reportMutation(options.json, { action: "updated", kind: "pool", name, value: updated }, () => {
|
|
76745
76842
|
console.log(`Pool "${name}" now has ${members2.join(", ")}.`);
|
|
76746
76843
|
});
|
|
76747
76844
|
}),
|
|
76748
|
-
["agent-shim pool set subs --identity work --identity personal --identity spare"]
|
|
76845
|
+
["agent-shim pool set subs --identity work --identity personal --identity spare", "agent-shim pool set subs --preference listed"]
|
|
76749
76846
|
);
|
|
76750
76847
|
withExamples(
|
|
76751
76848
|
pool.command("list").description("List every pool and its members, marking the active selection with *.").option("--json", "Print the pools as JSON.").action((options) => {
|
|
76752
76849
|
const pools = readPools(paths);
|
|
76753
76850
|
const active = readActiveIdentity(paths);
|
|
76754
76851
|
if (options.json === true) {
|
|
76755
|
-
printJson(Object.entries(pools).map(([name, entry]) => ({ name, identities: entry.identities, active: active === `${POOL_SELECTOR_PREFIX}${name}` })));
|
|
76852
|
+
printJson(Object.entries(pools).map(([name, entry]) => ({ name, identities: entry.identities, ...entry.preference === void 0 ? {} : { preference: entry.preference }, active: active === `${POOL_SELECTOR_PREFIX}${name}` })));
|
|
76756
76853
|
return;
|
|
76757
76854
|
}
|
|
76758
76855
|
const names = Object.keys(pools).sort();
|
|
@@ -76761,7 +76858,8 @@ function registerPoolCommand(program2, deps) {
|
|
|
76761
76858
|
return;
|
|
76762
76859
|
}
|
|
76763
76860
|
for (const name of names) {
|
|
76764
|
-
|
|
76861
|
+
const entry = pools[name];
|
|
76862
|
+
console.log(`${active === `${POOL_SELECTOR_PREFIX}${name}` ? "*" : " "} ${name}: ${(entry?.identities ?? []).join(", ")}${entry?.preference === void 0 ? "" : " (preference: listed)"}`);
|
|
76765
76863
|
}
|
|
76766
76864
|
}),
|
|
76767
76865
|
["agent-shim pool list", "agent-shim pool list --json"]
|
|
@@ -76770,11 +76868,14 @@ function registerPoolCommand(program2, deps) {
|
|
|
76770
76868
|
pool.command("show <name>").description("Show one pool's members.").option("--json", "Print the pool as JSON.").action((name, options) => {
|
|
76771
76869
|
const entry = requirePool(paths, name);
|
|
76772
76870
|
if (options.json === true) {
|
|
76773
|
-
printJson({ name, identities: entry.identities });
|
|
76871
|
+
printJson({ name, identities: entry.identities, ...entry.preference === void 0 ? {} : { preference: entry.preference } });
|
|
76774
76872
|
return;
|
|
76775
76873
|
}
|
|
76776
76874
|
console.log(`Pool: ${name}`);
|
|
76777
76875
|
console.log(`Members: ${entry.identities.join(", ")}`);
|
|
76876
|
+
if (entry.preference !== void 0) {
|
|
76877
|
+
console.log(`Preference: ${entry.preference}`);
|
|
76878
|
+
}
|
|
76778
76879
|
}),
|
|
76779
76880
|
["agent-shim pool show subs"]
|
|
76780
76881
|
);
|
|
@@ -76801,7 +76902,7 @@ function registerPoolCommand(program2, deps) {
|
|
|
76801
76902
|
withExamples(
|
|
76802
76903
|
pool.command("pick <name>").description("Show how a launch from this directory would rank the pool's members right now, and which it would run as. Read-only: it records nothing.").option("--json", "Print the ranking as JSON.").action((name, options) => {
|
|
76803
76904
|
const nowMs = Date.now();
|
|
76804
|
-
const report = collectPoolPick({ paths, fs: realFsPort, usageFs: realFarmFs, poolName: name,
|
|
76905
|
+
const report = collectPoolPick({ paths, fs: realFsPort, usageFs: realFarmFs, poolName: name, pools: readPools(paths), directory: process.cwd(), nowMs });
|
|
76805
76906
|
if (options.json === true) {
|
|
76806
76907
|
printJson(report);
|
|
76807
76908
|
return;
|
|
@@ -77568,6 +77669,76 @@ function splitExtraFlags(value2) {
|
|
|
77568
77669
|
return trimmed.split(/\s+/);
|
|
77569
77670
|
}
|
|
77570
77671
|
|
|
77672
|
+
// src/launcher/pool.ts
|
|
77673
|
+
var RESUME_FLAGS = /* @__PURE__ */ new Set(["--continue", "-c", "--resume", "-r"]);
|
|
77674
|
+
var TERMINATOR2 = "--";
|
|
77675
|
+
var REASONS_LOGGED = 3;
|
|
77676
|
+
function isResuming(passthrough) {
|
|
77677
|
+
const end = passthrough.indexOf(TERMINATOR2);
|
|
77678
|
+
return (end === -1 ? passthrough : passthrough.slice(0, end)).some((token) => RESUME_FLAGS.has(token));
|
|
77679
|
+
}
|
|
77680
|
+
function resolvePoolLaunch(params) {
|
|
77681
|
+
const { poolName, log } = params;
|
|
77682
|
+
const pool = params.pools?.[poolName];
|
|
77683
|
+
if (pool === void 0) {
|
|
77684
|
+
return { ok: false, message: `agent-shim: no pool named "${poolName}" (selected via ${params.selectedVia}). Run \`agent-shim pool add ${poolName} --identity <name>...\` first.` };
|
|
77685
|
+
}
|
|
77686
|
+
const resuming = isResuming(params.passthrough);
|
|
77687
|
+
const stickyRead = readStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd);
|
|
77688
|
+
if (stickyRead.problem !== void 0) {
|
|
77689
|
+
log.warn(`agent-shim: ${stickyRead.problem}; ignoring the last-pick record`);
|
|
77690
|
+
}
|
|
77691
|
+
const warned = /* @__PURE__ */ new Set();
|
|
77692
|
+
for (; ; ) {
|
|
77693
|
+
const nowMs = params.now();
|
|
77694
|
+
let graph;
|
|
77695
|
+
try {
|
|
77696
|
+
graph = rankPoolGraph({
|
|
77697
|
+
fs: params.usageFs,
|
|
77698
|
+
paths: params.paths,
|
|
77699
|
+
pools: params.pools ?? {},
|
|
77700
|
+
poolName,
|
|
77701
|
+
nowMs,
|
|
77702
|
+
resuming,
|
|
77703
|
+
identityExists: (name) => loadIdentity(params.paths.identitiesDir, name, params.fs) !== void 0,
|
|
77704
|
+
...stickyRead.sticky === void 0 ? {} : { sticky: stickyRead.sticky }
|
|
77705
|
+
});
|
|
77706
|
+
} catch (error62) {
|
|
77707
|
+
if (error62 instanceof PoolGraphError) {
|
|
77708
|
+
return { ok: false, message: `agent-shim: ${error62.message}` };
|
|
77709
|
+
}
|
|
77710
|
+
throw error62;
|
|
77711
|
+
}
|
|
77712
|
+
for (const gone of graph.missing) {
|
|
77713
|
+
const key = `${gone.pool}:${gone.identity}`;
|
|
77714
|
+
if (!warned.has(key)) {
|
|
77715
|
+
warned.add(key);
|
|
77716
|
+
log.warn(`agent-shim: pool "${gone.pool}" names identity "${gone.identity}", which does not exist; skipping it`);
|
|
77717
|
+
}
|
|
77718
|
+
}
|
|
77719
|
+
if (graph.ranking.candidates.length === 0) {
|
|
77720
|
+
return { ok: false, message: `agent-shim: no member of pool "${poolName}" is an existing identity.` };
|
|
77721
|
+
}
|
|
77722
|
+
const { pick: pick2, earliestReturn } = graph.ranking;
|
|
77723
|
+
if (pick2 !== void 0) {
|
|
77724
|
+
recordStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd, pick2.identity, nowMs);
|
|
77725
|
+
return { ok: true, identity: pick2.identity, explanation: `pool ${poolName}: ${pick2.reasons.slice(0, REASONS_LOGGED).join("; ")}` };
|
|
77726
|
+
}
|
|
77727
|
+
if (earliestReturn === void 0) {
|
|
77728
|
+
return { ok: false, message: `agent-shim: no member of pool "${poolName}" can be picked.` };
|
|
77729
|
+
}
|
|
77730
|
+
const until = new Date(earliestReturn.atMs).toISOString();
|
|
77731
|
+
if (!params.wait) {
|
|
77732
|
+
return {
|
|
77733
|
+
ok: false,
|
|
77734
|
+
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.`
|
|
77735
|
+
};
|
|
77736
|
+
}
|
|
77737
|
+
log.info(`agent-shim: every member of pool "${poolName}" is refused; waiting until ${until} for ${earliestReturn.identity}`);
|
|
77738
|
+
params.sleep(Math.max(earliestReturn.atMs - nowMs, 0));
|
|
77739
|
+
}
|
|
77740
|
+
}
|
|
77741
|
+
|
|
77571
77742
|
// src/launcher/spawn.ts
|
|
77572
77743
|
var import_node_os7 = __toESM(require("node:os"), 1);
|
|
77573
77744
|
var SIGNAL_EXIT_CODE_OFFSET = 128;
|