agent-shim 8.33.0 → 8.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -57135,11 +57135,14 @@ var IDENTITY_NAME_PATTERN = "[A-Za-z0-9][A-Za-z0-9._@-]*";
57135
57135
  var PoolNameSchema = external_exports.string().min(1).regex(new RegExp(`^${POOL_NAME_PATTERN}$`));
57136
57136
  var SELECTOR_RE = new RegExp(`^(${POOL_SELECTOR_PREFIX}${POOL_NAME_PATTERN}|${IDENTITY_NAME_PATTERN})$`);
57137
57137
  var IdentitySelectorSchema = external_exports.string().min(1).regex(SELECTOR_RE);
57138
- var PoolMemberSchema = external_exports.string().min(1).regex(SELECTOR_RE);
57138
+ var PoolMemberSchema = external_exports.union([external_exports.string().min(1).regex(SELECTOR_RE), external_exports.strictObject({ identity: external_exports.string().min(1).regex(SELECTOR_RE), when: WhenSchema })]);
57139
+ function poolMemberIdentity(member) {
57140
+ return typeof member === "string" ? member : member.identity;
57141
+ }
57139
57142
  var POOL_PREFERENCES = ["score", "listed"];
57140
57143
  var PoolPreferenceSchema = external_exports.enum(POOL_PREFERENCES);
57141
57144
  var PoolSchema = external_exports.strictObject({
57142
- identities: external_exports.array(PoolMemberSchema).min(1).refine((members2) => new Set(members2).size === members2.length, { message: "pool members must be unique" }),
57145
+ identities: external_exports.array(PoolMemberSchema).min(1).refine((members2) => new Set(members2.map(poolMemberIdentity)).size === members2.length, { message: "pool members must be unique" }),
57143
57146
  preference: PoolPreferenceSchema.optional()
57144
57147
  });
57145
57148
  var DirectoryRuleSchema = ConfigProfileSchema.omit({ description: true }).extend({
@@ -60153,7 +60156,7 @@ function writePools(paths, pools) {
60153
60156
  function poolCycleOf(pools, name) {
60154
60157
  const walk = (current, path46) => {
60155
60158
  for (const entry of pools[current]?.identities ?? []) {
60156
- const nested = poolNameOf(entry);
60159
+ const nested = poolNameOf(poolMemberIdentity(entry));
60157
60160
  if (nested === void 0) {
60158
60161
  continue;
60159
60162
  }
@@ -60174,9 +60177,10 @@ function poolCycleOf(pools, name) {
60174
60177
  }
60175
60178
  function validateMembers(pools, name, members2) {
60176
60179
  for (const entry of members2) {
60177
- const nested = poolNameOf(entry);
60180
+ const identity = poolMemberIdentity(entry);
60181
+ const nested = poolNameOf(identity);
60178
60182
  if (nested !== void 0 && nested !== name && pools[nested] === void 0) {
60179
- throw new UsageError(`Member "${entry}" of pool "${name}" names a pool that is not defined. Run \`agent-shim pool add ${nested} --identity <name>...\` first.`);
60183
+ throw new UsageError(`Member "${identity}" of pool "${name}" names a pool that is not defined. Run \`agent-shim pool add ${nested} --identity <name>...\` first.`);
60180
60184
  }
60181
60185
  }
60182
60186
  const cycle = poolCycleOf({ ...pools, [name]: { identities: [...members2] } }, name);
@@ -60915,12 +60919,18 @@ function parseDuration(value2) {
60915
60919
  }
60916
60920
  var evaluator = createSyncEvaluator({});
60917
60921
  function resolversOf(context) {
60922
+ const pool = context.pool;
60918
60923
  const values = {
60919
60924
  now: context.nowMs,
60920
60925
  ...context.fact?.latestMtimeMs === void 0 ? {} : { "entry.latestMtimeMs": context.fact.latestMtimeMs },
60921
60926
  ...context.fact?.totalSizeBytes === void 0 ? {} : { "entry.totalSizeBytes": context.fact.totalSizeBytes },
60922
60927
  ...context.branch === void 0 ? {} : { "repo.branch": context.branch },
60923
- "repo.detached": context.branchDetached ?? false
60928
+ "repo.detached": context.branchDetached ?? false,
60929
+ ...pool?.fiveHour === void 0 ? {} : { "quota.fiveHour.remaining": pool.fiveHour.remaining, "quota.fiveHour.utilization": pool.fiveHour.utilization, "quota.fiveHour.hoursUntilReset": pool.fiveHour.hoursUntilReset },
60930
+ ...pool?.sevenDay === void 0 ? {} : { "quota.sevenDay.remaining": pool.sevenDay.remaining, "quota.sevenDay.utilization": pool.sevenDay.utilization, "quota.sevenDay.hoursUntilReset": pool.sevenDay.hoursUntilReset },
60931
+ ...pool?.burnPerHour === void 0 ? {} : { "quota.burnPerHour": pool.burnPerHour },
60932
+ ...pool?.resuming === void 0 ? {} : { "session.resuming": pool.resuming },
60933
+ ...pool?.lastPickHoursAgo === void 0 ? {} : { "session.lastPickHoursAgo": pool.lastPickHoursAgo }
60924
60934
  };
60925
60935
  return {
60926
60936
  resolveValue: (key) => {
@@ -63655,7 +63665,7 @@ function registerIdentityCommand(program2, deps) {
63655
63665
  // package.json
63656
63666
  var package_default = {
63657
63667
  name: "agent-shim",
63658
- version: "8.33.0",
63668
+ version: "8.34.0",
63659
63669
  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.",
63660
63670
  license: "MIT",
63661
63671
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -64744,12 +64754,48 @@ function assessIdentity(member, nowMs) {
64744
64754
  ...sevenDayBurn === void 0 ? {} : { sevenDayBurn }
64745
64755
  };
64746
64756
  }
64757
+ function poolFactsOf(member, nowMs, resuming, sticky) {
64758
+ const unified = member.snapshot?.providers[ANTHROPIC_PROVIDER]?.rateLimit?.unified;
64759
+ const window = (which) => {
64760
+ const raw = unified?.[which];
64761
+ if (raw?.utilization === void 0) {
64762
+ return void 0;
64763
+ }
64764
+ if (raw.resetsAt === void 0) {
64765
+ return void 0;
64766
+ }
64767
+ return { remaining: Math.max(0, 1 - raw.utilization), utilization: raw.utilization, hoursUntilReset: Math.max(0, (Date.parse(raw.resetsAt) - nowMs) / MS_PER_HOUR) };
64768
+ };
64769
+ const burn = windowBurn(member.records, "fiveHour");
64770
+ return {
64771
+ ...window("fiveHour") === void 0 ? {} : { fiveHour: window("fiveHour") },
64772
+ ...window("sevenDay") === void 0 ? {} : { sevenDay: window("sevenDay") },
64773
+ ...burn === void 0 ? {} : { burnPerHour: burn * MS_PER_HOUR },
64774
+ resuming,
64775
+ ...sticky?.identity === member.identity ? { lastPickHoursAgo: Math.max(0, (nowMs - Date.parse(sticky.at)) / MS_PER_HOUR) } : {}
64776
+ };
64777
+ }
64747
64778
  function rankPool(input2) {
64748
64779
  const { members: members2, nowMs } = input2;
64749
- const assessments = members2.map((member) => assess(member, nowMs));
64780
+ const assessments = members2.map((member) => ({ member, ...assess(member, nowMs) }));
64750
64781
  const demands = assessments.flatMap(({ candidate, burn }) => burn === void 0 ? [] : [burn * capacityOf(candidate.plan)]);
64751
64782
  const sharedDemand = demands.length === 0 ? void 0 : Math.max(...demands);
64752
- const candidates = assessments.map((assessment) => {
64783
+ const guarded = assessments.map((assessment) => {
64784
+ const { candidate } = assessment;
64785
+ if (candidate.class === "ineligible" || assessment.member.policy === void 0) {
64786
+ return assessment;
64787
+ }
64788
+ const context = { nowMs, env: {}, pool: poolFactsOf(assessment.member, nowMs, input2.resuming, input2.sticky) };
64789
+ const verdict = evaluateWhen(assessment.member.policy, context);
64790
+ if (verdict.status === "indeterminate") {
64791
+ return { ...assessment, candidate: { ...candidate, policyDemoted: true, reasons: [`policy undecided (${verdict.checked.join(", ") === "predicate" ? "predicate" : verdict.checked.join(", ")}): ${verdict.reason}`, ...candidate.reasons] } };
64792
+ }
64793
+ if (verdict.passed) {
64794
+ return assessment;
64795
+ }
64796
+ return { ...assessment, candidate: { ...candidate, class: "ineligible", reasons: [`skipped by policy (${verdict.checked.join(", ")}: did not hold)`, ...candidate.reasons] } };
64797
+ });
64798
+ const candidates = guarded.map((assessment) => {
64753
64799
  const { candidate, fiveHour, burn } = assessment;
64754
64800
  const pace = burn ?? (sharedDemand === void 0 ? void 0 : sharedDemand / capacityOf(candidate.plan));
64755
64801
  const { sevenDay, sevenDayBurn } = assessment;
@@ -64775,7 +64821,7 @@ function rankPool(input2) {
64775
64821
  return { ...candidate, feasible: false, reasons: [...drySpots, ...candidate.reasons] };
64776
64822
  });
64777
64823
  const ordered = input2.preference === "listed" ? [...candidates] : [...candidates].sort(
64778
- (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)
64824
+ (left, right) => CLASS_ORDER[left.class] - CLASS_ORDER[right.class] || Number(left.policyDemoted ?? false) - Number(right.policyDemoted ?? false) || Number(right.feasible) - Number(left.feasible) || (right.score ?? 0) - (left.score ?? 0) || left.identity.localeCompare(right.identity)
64779
64825
  );
64780
64826
  const sticky = input2.sticky;
64781
64827
  const stickyIndex = sticky === void 0 ? -1 : ordered.findIndex((candidate) => candidate.identity === sticky.identity);
@@ -65104,27 +65150,27 @@ function rankNamedPool(input2, poolName, stack, sticky) {
65104
65150
  throw new PoolGraphError(`No pool named "${poolName}". Run \`agent-shim pool add ${poolName} --identity <name>...\` first.`);
65105
65151
  }
65106
65152
  const childStack = [...stack, poolName];
65107
- const direct = pool.identities.filter((entry) => poolNameOf(entry) === void 0);
65108
- const missing = direct.filter((entry) => !input2.identityExists(entry)).map((identity) => ({ pool: poolName, identity }));
65109
- const loaded = new Map(loadPoolMembers(input2.fs, input2.paths, direct.filter(input2.identityExists), input2.nowMs).map((member) => [member.identity, member]));
65153
+ const direct = pool.identities.filter((entry) => poolNameOf(poolMemberIdentity(entry)) === void 0);
65154
+ const missing = direct.filter((entry) => !input2.identityExists(poolMemberIdentity(entry))).map((entry) => ({ pool: poolName, identity: poolMemberIdentity(entry) }));
65155
+ const loaded = new Map(loadPoolMembers(input2.fs, input2.paths, direct.filter((entry) => input2.identityExists(poolMemberIdentity(entry))).map(poolMemberIdentity), input2.nowMs).map((member) => [member.identity, member]));
65110
65156
  const members2 = [];
65111
65157
  for (const entry of pool.identities) {
65112
- const nestedName = poolNameOf(entry);
65158
+ const nestedName = poolNameOf(poolMemberIdentity(entry));
65113
65159
  if (nestedName === void 0) {
65114
- const member = loaded.get(entry);
65160
+ const member = loaded.get(poolMemberIdentity(entry));
65115
65161
  if (member !== void 0) {
65116
- members2.push(member);
65162
+ members2.push(typeof entry === "string" ? member : { ...member, policy: entry.when });
65117
65163
  }
65118
65164
  continue;
65119
65165
  }
65120
65166
  if (input2.pools[nestedName] === void 0) {
65121
- throw new PoolGraphError(`Pool "${poolName}" member "${entry}" names a pool that is not defined.`);
65167
+ throw new PoolGraphError(`Pool "${poolName}" member "${poolMemberIdentity(entry)}" names a pool that is not defined.`);
65122
65168
  }
65123
65169
  if (childStack.includes(nestedName)) {
65124
- 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.`);
65170
+ throw new PoolGraphError(`Pool "${poolName}" member "${poolMemberIdentity(entry)}" closes a cycle: ${[...childStack.slice(childStack.indexOf(nestedName)), nestedName].join(" -> ")}. A pool cannot nest itself, directly or through another pool.`);
65125
65171
  }
65126
65172
  const nested = nestedMember(input2, nestedName, childStack, sticky);
65127
- members2.push(nested.member);
65173
+ members2.push(typeof entry === "string" ? nested.member : { ...nested.member, policy: entry.when });
65128
65174
  missing.push(...nested.missing);
65129
65175
  }
65130
65176
  const ranking = rankPool({
@@ -70139,16 +70185,16 @@ function runDoctor(params) {
70139
70185
  const validatedGlobalConfig = params.globalConfig.raw === void 0 ? void 0 : validateJson(GlobalConfigSchema, params.globalConfig);
70140
70186
  const pools = validatedGlobalConfig?.ok === true ? validatedGlobalConfig.data.pools ?? {} : {};
70141
70187
  for (const [poolName, pool] of Object.entries(pools)) {
70142
- const missing = pool.identities.filter((member) => poolNameOf(member) === void 0 && !validIdentityNames.has(member));
70188
+ const missing = pool.identities.filter((member) => poolNameOf(poolMemberIdentity(member)) === void 0 && !validIdentityNames.has(poolMemberIdentity(member)));
70143
70189
  const undefinedPools = pool.identities.flatMap((member) => {
70144
- const nested = poolNameOf(member);
70190
+ const nested = poolNameOf(poolMemberIdentity(member));
70145
70191
  return nested === void 0 || nested in pools ? [] : [nested];
70146
70192
  });
70147
70193
  if (missing.length === 0 && undefinedPools.length === 0) {
70148
- push("pool", "pass", `${poolName} is valid (${pool.identities.join(", ")}).`, poolName);
70194
+ push("pool", "pass", `${poolName} is valid (${pool.identities.map(poolMemberIdentity).join(", ")}).`, poolName);
70149
70195
  } else {
70150
70196
  const problems = [
70151
- ...missing.map((member) => `identity "${member}", which does not exist`),
70197
+ ...missing.map((member) => `identity "${poolMemberIdentity(member)}", which does not exist`),
70152
70198
  ...undefinedPools.map((nested) => `pool "${nested}", which is not defined`)
70153
70199
  ];
70154
70200
  push("pool", "fail", `Pool "${poolName}" names ${problems.join(" and ")}.`, poolName);
@@ -81780,12 +81826,12 @@ function registerPoolCommand(program2, deps) {
81780
81826
  }
81781
81827
  const members2 = options.identity === void 0 ? existing.identities : membersOf(options);
81782
81828
  if (options.identity !== void 0) {
81783
- requireIdentities(paths, members2);
81829
+ requireIdentities(paths, membersOf(options));
81784
81830
  }
81785
81831
  const preference = options.preference === void 0 ? existing.preference : options.preference === false ? void 0 : options.preference;
81786
81832
  const updated = setPool(paths, name, members2, preference);
81787
81833
  reportMutation(options.json, { action: "updated", kind: "pool", name, value: updated }, () => {
81788
- console.log(`Pool "${name}" now has ${members2.join(", ")}.`);
81834
+ console.log(`Pool "${name}" now has ${members2.map(poolMemberIdentity).join(", ")}.`);
81789
81835
  });
81790
81836
  }),
81791
81837
  ["agent-shim pool set subs --identity work --identity personal --identity spare", "agent-shim pool set subs --preference listed"]
@@ -81805,7 +81851,7 @@ function registerPoolCommand(program2, deps) {
81805
81851
  }
81806
81852
  for (const name of names) {
81807
81853
  const entry = pools[name];
81808
- console.log(`${active === `${POOL_SELECTOR_PREFIX}${name}` ? "*" : " "} ${name}: ${(entry?.identities ?? []).join(", ")}${entry?.preference === void 0 ? "" : " (preference: listed)"}`);
81854
+ console.log(`${active === `${POOL_SELECTOR_PREFIX}${name}` ? "*" : " "} ${name}: ${(entry?.identities ?? []).map(poolMemberIdentity).join(", ")}${entry?.preference === void 0 ? "" : " (preference: listed)"}`);
81809
81855
  }
81810
81856
  }),
81811
81857
  ["agent-shim pool list", "agent-shim pool list --json"]
@@ -81818,7 +81864,7 @@ function registerPoolCommand(program2, deps) {
81818
81864
  return;
81819
81865
  }
81820
81866
  console.log(`Pool: ${name}`);
81821
- console.log(`Members: ${entry.identities.join(", ")}`);
81867
+ console.log(`Members: ${entry.identities.map(poolMemberIdentity).join(", ")}`);
81822
81868
  if (entry.preference !== void 0) {
81823
81869
  console.log(`Preference: ${entry.preference}`);
81824
81870
  }
package/dist/index.cjs CHANGED
@@ -623,11 +623,14 @@ var IDENTITY_NAME_PATTERN = "[A-Za-z0-9][A-Za-z0-9._@-]*";
623
623
  var PoolNameSchema = import_zod.z.string().min(1).regex(new RegExp(`^${POOL_NAME_PATTERN}$`));
624
624
  var SELECTOR_RE = new RegExp(`^(${POOL_SELECTOR_PREFIX}${POOL_NAME_PATTERN}|${IDENTITY_NAME_PATTERN})$`);
625
625
  var IdentitySelectorSchema = import_zod.z.string().min(1).regex(SELECTOR_RE);
626
- var PoolMemberSchema = import_zod.z.string().min(1).regex(SELECTOR_RE);
626
+ var PoolMemberSchema = import_zod.z.union([import_zod.z.string().min(1).regex(SELECTOR_RE), import_zod.z.strictObject({ identity: import_zod.z.string().min(1).regex(SELECTOR_RE), when: WhenSchema })]);
627
+ function poolMemberIdentity(member) {
628
+ return typeof member === "string" ? member : member.identity;
629
+ }
627
630
  var POOL_PREFERENCES = ["score", "listed"];
628
631
  var PoolPreferenceSchema = import_zod.z.enum(POOL_PREFERENCES);
629
632
  var PoolSchema = import_zod.z.strictObject({
630
- identities: import_zod.z.array(PoolMemberSchema).min(1).refine((members) => new Set(members).size === members.length, { message: "pool members must be unique" }),
633
+ identities: import_zod.z.array(PoolMemberSchema).min(1).refine((members) => new Set(members.map(poolMemberIdentity)).size === members.length, { message: "pool members must be unique" }),
631
634
  preference: PoolPreferenceSchema.optional()
632
635
  });
633
636
  var DirectoryRuleSchema = ConfigProfileSchema.omit({ description: true }).extend({
@@ -1004,12 +1007,18 @@ function matchBranch(pattern, branch, detached = false) {
1004
1007
  }
1005
1008
  var evaluator = (0, import_trilean2.createSyncEvaluator)({});
1006
1009
  function resolversOf(context) {
1010
+ const pool = context.pool;
1007
1011
  const values = {
1008
1012
  now: context.nowMs,
1009
1013
  ...context.fact?.latestMtimeMs === void 0 ? {} : { "entry.latestMtimeMs": context.fact.latestMtimeMs },
1010
1014
  ...context.fact?.totalSizeBytes === void 0 ? {} : { "entry.totalSizeBytes": context.fact.totalSizeBytes },
1011
1015
  ...context.branch === void 0 ? {} : { "repo.branch": context.branch },
1012
- "repo.detached": context.branchDetached ?? false
1016
+ "repo.detached": context.branchDetached ?? false,
1017
+ ...pool?.fiveHour === void 0 ? {} : { "quota.fiveHour.remaining": pool.fiveHour.remaining, "quota.fiveHour.utilization": pool.fiveHour.utilization, "quota.fiveHour.hoursUntilReset": pool.fiveHour.hoursUntilReset },
1018
+ ...pool?.sevenDay === void 0 ? {} : { "quota.sevenDay.remaining": pool.sevenDay.remaining, "quota.sevenDay.utilization": pool.sevenDay.utilization, "quota.sevenDay.hoursUntilReset": pool.sevenDay.hoursUntilReset },
1019
+ ...pool?.burnPerHour === void 0 ? {} : { "quota.burnPerHour": pool.burnPerHour },
1020
+ ...pool?.resuming === void 0 ? {} : { "session.resuming": pool.resuming },
1021
+ ...pool?.lastPickHoursAgo === void 0 ? {} : { "session.lastPickHoursAgo": pool.lastPickHoursAgo }
1013
1022
  };
1014
1023
  return {
1015
1024
  resolveValue: (key) => {
@@ -5390,7 +5399,7 @@ var import_undici = require("undici");
5390
5399
  // package.json
5391
5400
  var package_default = {
5392
5401
  name: "agent-shim",
5393
- version: "8.33.0",
5402
+ version: "8.34.0",
5394
5403
  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.",
5395
5404
  license: "MIT",
5396
5405
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -6682,12 +6691,48 @@ function assessIdentity(member, nowMs) {
6682
6691
  ...sevenDayBurn === void 0 ? {} : { sevenDayBurn }
6683
6692
  };
6684
6693
  }
6694
+ function poolFactsOf(member, nowMs, resuming, sticky) {
6695
+ const unified = member.snapshot?.providers[ANTHROPIC_PROVIDER]?.rateLimit?.unified;
6696
+ const window = (which) => {
6697
+ const raw = unified?.[which];
6698
+ if (raw?.utilization === void 0) {
6699
+ return void 0;
6700
+ }
6701
+ if (raw.resetsAt === void 0) {
6702
+ return void 0;
6703
+ }
6704
+ return { remaining: Math.max(0, 1 - raw.utilization), utilization: raw.utilization, hoursUntilReset: Math.max(0, (Date.parse(raw.resetsAt) - nowMs) / MS_PER_HOUR) };
6705
+ };
6706
+ const burn = windowBurn(member.records, "fiveHour");
6707
+ return {
6708
+ ...window("fiveHour") === void 0 ? {} : { fiveHour: window("fiveHour") },
6709
+ ...window("sevenDay") === void 0 ? {} : { sevenDay: window("sevenDay") },
6710
+ ...burn === void 0 ? {} : { burnPerHour: burn * MS_PER_HOUR },
6711
+ resuming,
6712
+ ...sticky?.identity === member.identity ? { lastPickHoursAgo: Math.max(0, (nowMs - Date.parse(sticky.at)) / MS_PER_HOUR) } : {}
6713
+ };
6714
+ }
6685
6715
  function rankPool(input) {
6686
6716
  const { members, nowMs } = input;
6687
- const assessments = members.map((member) => assess(member, nowMs));
6717
+ const assessments = members.map((member) => ({ member, ...assess(member, nowMs) }));
6688
6718
  const demands = assessments.flatMap(({ candidate, burn }) => burn === void 0 ? [] : [burn * capacityOf(candidate.plan)]);
6689
6719
  const sharedDemand = demands.length === 0 ? void 0 : Math.max(...demands);
6690
- const candidates = assessments.map((assessment) => {
6720
+ const guarded = assessments.map((assessment) => {
6721
+ const { candidate } = assessment;
6722
+ if (candidate.class === "ineligible" || assessment.member.policy === void 0) {
6723
+ return assessment;
6724
+ }
6725
+ const context = { nowMs, env: {}, pool: poolFactsOf(assessment.member, nowMs, input.resuming, input.sticky) };
6726
+ const verdict = evaluateWhen(assessment.member.policy, context);
6727
+ if (verdict.status === "indeterminate") {
6728
+ return { ...assessment, candidate: { ...candidate, policyDemoted: true, reasons: [`policy undecided (${verdict.checked.join(", ") === "predicate" ? "predicate" : verdict.checked.join(", ")}): ${verdict.reason}`, ...candidate.reasons] } };
6729
+ }
6730
+ if (verdict.passed) {
6731
+ return assessment;
6732
+ }
6733
+ return { ...assessment, candidate: { ...candidate, class: "ineligible", reasons: [`skipped by policy (${verdict.checked.join(", ")}: did not hold)`, ...candidate.reasons] } };
6734
+ });
6735
+ const candidates = guarded.map((assessment) => {
6691
6736
  const { candidate, fiveHour, burn } = assessment;
6692
6737
  const pace = burn ?? (sharedDemand === void 0 ? void 0 : sharedDemand / capacityOf(candidate.plan));
6693
6738
  const { sevenDay, sevenDayBurn } = assessment;
@@ -6713,7 +6758,7 @@ function rankPool(input) {
6713
6758
  return { ...candidate, feasible: false, reasons: [...drySpots, ...candidate.reasons] };
6714
6759
  });
6715
6760
  const ordered = input.preference === "listed" ? [...candidates] : [...candidates].sort(
6716
- (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)
6761
+ (left, right) => CLASS_ORDER[left.class] - CLASS_ORDER[right.class] || Number(left.policyDemoted ?? false) - Number(right.policyDemoted ?? false) || Number(right.feasible) - Number(left.feasible) || (right.score ?? 0) - (left.score ?? 0) || left.identity.localeCompare(right.identity)
6717
6762
  );
6718
6763
  const sticky = input.sticky;
6719
6764
  const stickyIndex = sticky === void 0 ? -1 : ordered.findIndex((candidate) => candidate.identity === sticky.identity);
@@ -6884,27 +6929,27 @@ function rankNamedPool(input, poolName, stack, sticky) {
6884
6929
  throw new PoolGraphError(`No pool named "${poolName}". Run \`agent-shim pool add ${poolName} --identity <name>...\` first.`);
6885
6930
  }
6886
6931
  const childStack = [...stack, poolName];
6887
- const direct = pool.identities.filter((entry) => poolNameOf(entry) === void 0);
6888
- const missing = direct.filter((entry) => !input.identityExists(entry)).map((identity) => ({ pool: poolName, identity }));
6889
- const loaded = new Map(loadPoolMembers(input.fs, input.paths, direct.filter(input.identityExists), input.nowMs).map((member) => [member.identity, member]));
6932
+ const direct = pool.identities.filter((entry) => poolNameOf(poolMemberIdentity(entry)) === void 0);
6933
+ const missing = direct.filter((entry) => !input.identityExists(poolMemberIdentity(entry))).map((entry) => ({ pool: poolName, identity: poolMemberIdentity(entry) }));
6934
+ const loaded = new Map(loadPoolMembers(input.fs, input.paths, direct.filter((entry) => input.identityExists(poolMemberIdentity(entry))).map(poolMemberIdentity), input.nowMs).map((member) => [member.identity, member]));
6890
6935
  const members = [];
6891
6936
  for (const entry of pool.identities) {
6892
- const nestedName = poolNameOf(entry);
6937
+ const nestedName = poolNameOf(poolMemberIdentity(entry));
6893
6938
  if (nestedName === void 0) {
6894
- const member = loaded.get(entry);
6939
+ const member = loaded.get(poolMemberIdentity(entry));
6895
6940
  if (member !== void 0) {
6896
- members.push(member);
6941
+ members.push(typeof entry === "string" ? member : { ...member, policy: entry.when });
6897
6942
  }
6898
6943
  continue;
6899
6944
  }
6900
6945
  if (input.pools[nestedName] === void 0) {
6901
- throw new PoolGraphError(`Pool "${poolName}" member "${entry}" names a pool that is not defined.`);
6946
+ throw new PoolGraphError(`Pool "${poolName}" member "${poolMemberIdentity(entry)}" names a pool that is not defined.`);
6902
6947
  }
6903
6948
  if (childStack.includes(nestedName)) {
6904
- 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.`);
6949
+ throw new PoolGraphError(`Pool "${poolName}" member "${poolMemberIdentity(entry)}" closes a cycle: ${[...childStack.slice(childStack.indexOf(nestedName)), nestedName].join(" -> ")}. A pool cannot nest itself, directly or through another pool.`);
6905
6950
  }
6906
6951
  const nested = nestedMember(input, nestedName, childStack, sticky);
6907
- members.push(nested.member);
6952
+ members.push(typeof entry === "string" ? nested.member : { ...nested.member, policy: entry.when });
6908
6953
  missing.push(...nested.missing);
6909
6954
  }
6910
6955
  const ranking = rankPool({
@@ -7165,7 +7210,7 @@ function writePools(paths, pools) {
7165
7210
  function poolCycleOf(pools, name) {
7166
7211
  const walk = (current, path32) => {
7167
7212
  for (const entry of pools[current]?.identities ?? []) {
7168
- const nested = poolNameOf(entry);
7213
+ const nested = poolNameOf(poolMemberIdentity(entry));
7169
7214
  if (nested === void 0) {
7170
7215
  continue;
7171
7216
  }
@@ -7186,9 +7231,10 @@ function poolCycleOf(pools, name) {
7186
7231
  }
7187
7232
  function validateMembers(pools, name, members) {
7188
7233
  for (const entry of members) {
7189
- const nested = poolNameOf(entry);
7234
+ const identity = poolMemberIdentity(entry);
7235
+ const nested = poolNameOf(identity);
7190
7236
  if (nested !== void 0 && nested !== name && pools[nested] === void 0) {
7191
- throw new UsageError(`Member "${entry}" of pool "${name}" names a pool that is not defined. Run \`agent-shim pool add ${nested} --identity <name>...\` first.`);
7237
+ throw new UsageError(`Member "${identity}" of pool "${name}" names a pool that is not defined. Run \`agent-shim pool add ${nested} --identity <name>...\` first.`);
7192
7238
  }
7193
7239
  }
7194
7240
  const cycle = poolCycleOf({ ...pools, [name]: { identities: [...members] } }, name);
@@ -9893,16 +9939,16 @@ function runDoctor(params) {
9893
9939
  const validatedGlobalConfig = params.globalConfig.raw === void 0 ? void 0 : validateJson(GlobalConfigSchema, params.globalConfig);
9894
9940
  const pools = validatedGlobalConfig?.ok === true ? validatedGlobalConfig.data.pools ?? {} : {};
9895
9941
  for (const [poolName, pool] of Object.entries(pools)) {
9896
- const missing = pool.identities.filter((member) => poolNameOf(member) === void 0 && !validIdentityNames.has(member));
9942
+ const missing = pool.identities.filter((member) => poolNameOf(poolMemberIdentity(member)) === void 0 && !validIdentityNames.has(poolMemberIdentity(member)));
9897
9943
  const undefinedPools = pool.identities.flatMap((member) => {
9898
- const nested = poolNameOf(member);
9944
+ const nested = poolNameOf(poolMemberIdentity(member));
9899
9945
  return nested === void 0 || nested in pools ? [] : [nested];
9900
9946
  });
9901
9947
  if (missing.length === 0 && undefinedPools.length === 0) {
9902
- push("pool", "pass", `${poolName} is valid (${pool.identities.join(", ")}).`, poolName);
9948
+ push("pool", "pass", `${poolName} is valid (${pool.identities.map(poolMemberIdentity).join(", ")}).`, poolName);
9903
9949
  } else {
9904
9950
  const problems = [
9905
- ...missing.map((member) => `identity "${member}", which does not exist`),
9951
+ ...missing.map((member) => `identity "${poolMemberIdentity(member)}", which does not exist`),
9906
9952
  ...undefinedPools.map((nested) => `pool "${nested}", which is not defined`)
9907
9953
  ];
9908
9954
  push("pool", "fail", `Pool "${poolName}" names ${problems.join(" and ")}.`, poolName);
package/dist/index.mjs CHANGED
@@ -311,11 +311,14 @@ var IDENTITY_NAME_PATTERN = "[A-Za-z0-9][A-Za-z0-9._@-]*";
311
311
  var PoolNameSchema = z.string().min(1).regex(new RegExp(`^${POOL_NAME_PATTERN}$`));
312
312
  var SELECTOR_RE = new RegExp(`^(${POOL_SELECTOR_PREFIX}${POOL_NAME_PATTERN}|${IDENTITY_NAME_PATTERN})$`);
313
313
  var IdentitySelectorSchema = z.string().min(1).regex(SELECTOR_RE);
314
- var PoolMemberSchema = z.string().min(1).regex(SELECTOR_RE);
314
+ var PoolMemberSchema = z.union([z.string().min(1).regex(SELECTOR_RE), z.strictObject({ identity: z.string().min(1).regex(SELECTOR_RE), when: WhenSchema })]);
315
+ function poolMemberIdentity(member) {
316
+ return typeof member === "string" ? member : member.identity;
317
+ }
315
318
  var POOL_PREFERENCES = ["score", "listed"];
316
319
  var PoolPreferenceSchema = z.enum(POOL_PREFERENCES);
317
320
  var PoolSchema = z.strictObject({
318
- identities: z.array(PoolMemberSchema).min(1).refine((members) => new Set(members).size === members.length, { message: "pool members must be unique" }),
321
+ identities: z.array(PoolMemberSchema).min(1).refine((members) => new Set(members.map(poolMemberIdentity)).size === members.length, { message: "pool members must be unique" }),
319
322
  preference: PoolPreferenceSchema.optional()
320
323
  });
321
324
  var DirectoryRuleSchema = ConfigProfileSchema.omit({ description: true }).extend({
@@ -692,12 +695,18 @@ function matchBranch(pattern, branch, detached = false) {
692
695
  }
693
696
  var evaluator = createSyncEvaluator({});
694
697
  function resolversOf(context) {
698
+ const pool = context.pool;
695
699
  const values = {
696
700
  now: context.nowMs,
697
701
  ...context.fact?.latestMtimeMs === void 0 ? {} : { "entry.latestMtimeMs": context.fact.latestMtimeMs },
698
702
  ...context.fact?.totalSizeBytes === void 0 ? {} : { "entry.totalSizeBytes": context.fact.totalSizeBytes },
699
703
  ...context.branch === void 0 ? {} : { "repo.branch": context.branch },
700
- "repo.detached": context.branchDetached ?? false
704
+ "repo.detached": context.branchDetached ?? false,
705
+ ...pool?.fiveHour === void 0 ? {} : { "quota.fiveHour.remaining": pool.fiveHour.remaining, "quota.fiveHour.utilization": pool.fiveHour.utilization, "quota.fiveHour.hoursUntilReset": pool.fiveHour.hoursUntilReset },
706
+ ...pool?.sevenDay === void 0 ? {} : { "quota.sevenDay.remaining": pool.sevenDay.remaining, "quota.sevenDay.utilization": pool.sevenDay.utilization, "quota.sevenDay.hoursUntilReset": pool.sevenDay.hoursUntilReset },
707
+ ...pool?.burnPerHour === void 0 ? {} : { "quota.burnPerHour": pool.burnPerHour },
708
+ ...pool?.resuming === void 0 ? {} : { "session.resuming": pool.resuming },
709
+ ...pool?.lastPickHoursAgo === void 0 ? {} : { "session.lastPickHoursAgo": pool.lastPickHoursAgo }
701
710
  };
702
711
  return {
703
712
  resolveValue: (key) => {
@@ -5078,7 +5087,7 @@ import { Agent as Agent3, fetch as undiciFetch } from "undici";
5078
5087
  // package.json
5079
5088
  var package_default = {
5080
5089
  name: "agent-shim",
5081
- version: "8.33.0",
5090
+ version: "8.34.0",
5082
5091
  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.",
5083
5092
  license: "MIT",
5084
5093
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -6370,12 +6379,48 @@ function assessIdentity(member, nowMs) {
6370
6379
  ...sevenDayBurn === void 0 ? {} : { sevenDayBurn }
6371
6380
  };
6372
6381
  }
6382
+ function poolFactsOf(member, nowMs, resuming, sticky) {
6383
+ const unified = member.snapshot?.providers[ANTHROPIC_PROVIDER]?.rateLimit?.unified;
6384
+ const window = (which) => {
6385
+ const raw = unified?.[which];
6386
+ if (raw?.utilization === void 0) {
6387
+ return void 0;
6388
+ }
6389
+ if (raw.resetsAt === void 0) {
6390
+ return void 0;
6391
+ }
6392
+ return { remaining: Math.max(0, 1 - raw.utilization), utilization: raw.utilization, hoursUntilReset: Math.max(0, (Date.parse(raw.resetsAt) - nowMs) / MS_PER_HOUR) };
6393
+ };
6394
+ const burn = windowBurn(member.records, "fiveHour");
6395
+ return {
6396
+ ...window("fiveHour") === void 0 ? {} : { fiveHour: window("fiveHour") },
6397
+ ...window("sevenDay") === void 0 ? {} : { sevenDay: window("sevenDay") },
6398
+ ...burn === void 0 ? {} : { burnPerHour: burn * MS_PER_HOUR },
6399
+ resuming,
6400
+ ...sticky?.identity === member.identity ? { lastPickHoursAgo: Math.max(0, (nowMs - Date.parse(sticky.at)) / MS_PER_HOUR) } : {}
6401
+ };
6402
+ }
6373
6403
  function rankPool(input) {
6374
6404
  const { members, nowMs } = input;
6375
- const assessments = members.map((member) => assess(member, nowMs));
6405
+ const assessments = members.map((member) => ({ member, ...assess(member, nowMs) }));
6376
6406
  const demands = assessments.flatMap(({ candidate, burn }) => burn === void 0 ? [] : [burn * capacityOf(candidate.plan)]);
6377
6407
  const sharedDemand = demands.length === 0 ? void 0 : Math.max(...demands);
6378
- const candidates = assessments.map((assessment) => {
6408
+ const guarded = assessments.map((assessment) => {
6409
+ const { candidate } = assessment;
6410
+ if (candidate.class === "ineligible" || assessment.member.policy === void 0) {
6411
+ return assessment;
6412
+ }
6413
+ const context = { nowMs, env: {}, pool: poolFactsOf(assessment.member, nowMs, input.resuming, input.sticky) };
6414
+ const verdict = evaluateWhen(assessment.member.policy, context);
6415
+ if (verdict.status === "indeterminate") {
6416
+ return { ...assessment, candidate: { ...candidate, policyDemoted: true, reasons: [`policy undecided (${verdict.checked.join(", ") === "predicate" ? "predicate" : verdict.checked.join(", ")}): ${verdict.reason}`, ...candidate.reasons] } };
6417
+ }
6418
+ if (verdict.passed) {
6419
+ return assessment;
6420
+ }
6421
+ return { ...assessment, candidate: { ...candidate, class: "ineligible", reasons: [`skipped by policy (${verdict.checked.join(", ")}: did not hold)`, ...candidate.reasons] } };
6422
+ });
6423
+ const candidates = guarded.map((assessment) => {
6379
6424
  const { candidate, fiveHour, burn } = assessment;
6380
6425
  const pace = burn ?? (sharedDemand === void 0 ? void 0 : sharedDemand / capacityOf(candidate.plan));
6381
6426
  const { sevenDay, sevenDayBurn } = assessment;
@@ -6401,7 +6446,7 @@ function rankPool(input) {
6401
6446
  return { ...candidate, feasible: false, reasons: [...drySpots, ...candidate.reasons] };
6402
6447
  });
6403
6448
  const ordered = input.preference === "listed" ? [...candidates] : [...candidates].sort(
6404
- (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)
6449
+ (left, right) => CLASS_ORDER[left.class] - CLASS_ORDER[right.class] || Number(left.policyDemoted ?? false) - Number(right.policyDemoted ?? false) || Number(right.feasible) - Number(left.feasible) || (right.score ?? 0) - (left.score ?? 0) || left.identity.localeCompare(right.identity)
6405
6450
  );
6406
6451
  const sticky = input.sticky;
6407
6452
  const stickyIndex = sticky === void 0 ? -1 : ordered.findIndex((candidate) => candidate.identity === sticky.identity);
@@ -6572,27 +6617,27 @@ function rankNamedPool(input, poolName, stack, sticky) {
6572
6617
  throw new PoolGraphError(`No pool named "${poolName}". Run \`agent-shim pool add ${poolName} --identity <name>...\` first.`);
6573
6618
  }
6574
6619
  const childStack = [...stack, poolName];
6575
- const direct = pool.identities.filter((entry) => poolNameOf(entry) === void 0);
6576
- const missing = direct.filter((entry) => !input.identityExists(entry)).map((identity) => ({ pool: poolName, identity }));
6577
- const loaded = new Map(loadPoolMembers(input.fs, input.paths, direct.filter(input.identityExists), input.nowMs).map((member) => [member.identity, member]));
6620
+ const direct = pool.identities.filter((entry) => poolNameOf(poolMemberIdentity(entry)) === void 0);
6621
+ const missing = direct.filter((entry) => !input.identityExists(poolMemberIdentity(entry))).map((entry) => ({ pool: poolName, identity: poolMemberIdentity(entry) }));
6622
+ const loaded = new Map(loadPoolMembers(input.fs, input.paths, direct.filter((entry) => input.identityExists(poolMemberIdentity(entry))).map(poolMemberIdentity), input.nowMs).map((member) => [member.identity, member]));
6578
6623
  const members = [];
6579
6624
  for (const entry of pool.identities) {
6580
- const nestedName = poolNameOf(entry);
6625
+ const nestedName = poolNameOf(poolMemberIdentity(entry));
6581
6626
  if (nestedName === void 0) {
6582
- const member = loaded.get(entry);
6627
+ const member = loaded.get(poolMemberIdentity(entry));
6583
6628
  if (member !== void 0) {
6584
- members.push(member);
6629
+ members.push(typeof entry === "string" ? member : { ...member, policy: entry.when });
6585
6630
  }
6586
6631
  continue;
6587
6632
  }
6588
6633
  if (input.pools[nestedName] === void 0) {
6589
- throw new PoolGraphError(`Pool "${poolName}" member "${entry}" names a pool that is not defined.`);
6634
+ throw new PoolGraphError(`Pool "${poolName}" member "${poolMemberIdentity(entry)}" names a pool that is not defined.`);
6590
6635
  }
6591
6636
  if (childStack.includes(nestedName)) {
6592
- 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.`);
6637
+ throw new PoolGraphError(`Pool "${poolName}" member "${poolMemberIdentity(entry)}" closes a cycle: ${[...childStack.slice(childStack.indexOf(nestedName)), nestedName].join(" -> ")}. A pool cannot nest itself, directly or through another pool.`);
6593
6638
  }
6594
6639
  const nested = nestedMember(input, nestedName, childStack, sticky);
6595
- members.push(nested.member);
6640
+ members.push(typeof entry === "string" ? nested.member : { ...nested.member, policy: entry.when });
6596
6641
  missing.push(...nested.missing);
6597
6642
  }
6598
6643
  const ranking = rankPool({
@@ -6853,7 +6898,7 @@ function writePools(paths, pools) {
6853
6898
  function poolCycleOf(pools, name) {
6854
6899
  const walk = (current, path32) => {
6855
6900
  for (const entry of pools[current]?.identities ?? []) {
6856
- const nested = poolNameOf(entry);
6901
+ const nested = poolNameOf(poolMemberIdentity(entry));
6857
6902
  if (nested === void 0) {
6858
6903
  continue;
6859
6904
  }
@@ -6874,9 +6919,10 @@ function poolCycleOf(pools, name) {
6874
6919
  }
6875
6920
  function validateMembers(pools, name, members) {
6876
6921
  for (const entry of members) {
6877
- const nested = poolNameOf(entry);
6922
+ const identity = poolMemberIdentity(entry);
6923
+ const nested = poolNameOf(identity);
6878
6924
  if (nested !== void 0 && nested !== name && pools[nested] === void 0) {
6879
- throw new UsageError(`Member "${entry}" of pool "${name}" names a pool that is not defined. Run \`agent-shim pool add ${nested} --identity <name>...\` first.`);
6925
+ throw new UsageError(`Member "${identity}" of pool "${name}" names a pool that is not defined. Run \`agent-shim pool add ${nested} --identity <name>...\` first.`);
6880
6926
  }
6881
6927
  }
6882
6928
  const cycle = poolCycleOf({ ...pools, [name]: { identities: [...members] } }, name);
@@ -9581,16 +9627,16 @@ function runDoctor(params) {
9581
9627
  const validatedGlobalConfig = params.globalConfig.raw === void 0 ? void 0 : validateJson(GlobalConfigSchema, params.globalConfig);
9582
9628
  const pools = validatedGlobalConfig?.ok === true ? validatedGlobalConfig.data.pools ?? {} : {};
9583
9629
  for (const [poolName, pool] of Object.entries(pools)) {
9584
- const missing = pool.identities.filter((member) => poolNameOf(member) === void 0 && !validIdentityNames.has(member));
9630
+ const missing = pool.identities.filter((member) => poolNameOf(poolMemberIdentity(member)) === void 0 && !validIdentityNames.has(poolMemberIdentity(member)));
9585
9631
  const undefinedPools = pool.identities.flatMap((member) => {
9586
- const nested = poolNameOf(member);
9632
+ const nested = poolNameOf(poolMemberIdentity(member));
9587
9633
  return nested === void 0 || nested in pools ? [] : [nested];
9588
9634
  });
9589
9635
  if (missing.length === 0 && undefinedPools.length === 0) {
9590
- push("pool", "pass", `${poolName} is valid (${pool.identities.join(", ")}).`, poolName);
9636
+ push("pool", "pass", `${poolName} is valid (${pool.identities.map(poolMemberIdentity).join(", ")}).`, poolName);
9591
9637
  } else {
9592
9638
  const problems = [
9593
- ...missing.map((member) => `identity "${member}", which does not exist`),
9639
+ ...missing.map((member) => `identity "${poolMemberIdentity(member)}", which does not exist`),
9594
9640
  ...undefinedPools.map((nested) => `pool "${nested}", which is not defined`)
9595
9641
  ];
9596
9642
  push("pool", "fail", `Pool "${poolName}" names ${problems.join(" and ")}.`, poolName);
@@ -630,7 +630,81 @@ type PoolPreference = z.infer<typeof PoolPreferenceSchema>;
630
630
  * `preference` decides how the member is chosen, and an absent field means "score". "score" ranks members by plan-size-weighted remaining quota per hour until reset, so the quota closest to expiring is spent first (use-it-or-lose-it). "listed" ranks members strictly in the order the pool lists them, skipping only a member that is currently refused, so a pool can pin one identity first and fall back to the others only while that one is refused.
631
631
  */
632
632
  declare const PoolSchema: z.ZodObject<{
633
- identities: z.ZodArray<z.ZodString>;
633
+ identities: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
634
+ identity: z.ZodString;
635
+ when: z.ZodUnion<readonly [z.ZodObject<{
636
+ newerThan: z.ZodOptional<z.ZodString>;
637
+ olderThan: z.ZodOptional<z.ZodString>;
638
+ maxSizeBytes: z.ZodOptional<z.ZodInt>;
639
+ branch: z.ZodOptional<z.ZodString>;
640
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
641
+ }, z.core.$strict>, z.ZodDiscriminatedUnion<[z.ZodObject<{
642
+ kind: z.ZodLiteral<"not">;
643
+ operand: typeof PredicateNodeSchema;
644
+ }, z.core.$strip>, z.ZodObject<{
645
+ kind: z.ZodLiteral<"and">;
646
+ left: typeof PredicateNodeSchema;
647
+ right: typeof PredicateNodeSchema;
648
+ }, z.core.$strip>, z.ZodObject<{
649
+ kind: z.ZodLiteral<"or">;
650
+ left: typeof PredicateNodeSchema;
651
+ right: typeof PredicateNodeSchema;
652
+ }, z.core.$strip>, z.ZodObject<{
653
+ kind: z.ZodLiteral<"allOf">;
654
+ operands: z.ZodArray<typeof PredicateNodeSchema>;
655
+ }, z.core.$strip>, z.ZodObject<{
656
+ kind: z.ZodLiteral<"anyOf">;
657
+ operands: z.ZodArray<typeof PredicateNodeSchema>;
658
+ }, z.core.$strip>, z.ZodObject<{
659
+ kind: z.ZodLiteral<"compare">;
660
+ op: z.ZodEnum<{
661
+ gt: "gt";
662
+ gte: "gte";
663
+ lt: "lt";
664
+ lte: "lte";
665
+ eq: "eq";
666
+ neq: "neq";
667
+ }>;
668
+ left: typeof trilean.ExpressionNodeSchema;
669
+ right: typeof trilean.ExpressionNodeSchema;
670
+ }, z.core.$strip>, z.ZodObject<{
671
+ kind: z.ZodLiteral<"textCompare">;
672
+ op: z.ZodEnum<{
673
+ equals: "equals";
674
+ notEquals: "notEquals";
675
+ matches: "matches";
676
+ notMatches: "notMatches";
677
+ portableMatches: "portableMatches";
678
+ portableNotMatches: "portableNotMatches";
679
+ }>;
680
+ left: typeof trilean.ExpressionNodeSchema;
681
+ right: typeof trilean.ExpressionNodeSchema;
682
+ }, z.core.$strip>, z.ZodObject<{
683
+ kind: z.ZodLiteral<"memberOf">;
684
+ op: z.ZodEnum<{
685
+ in: "in";
686
+ notIn: "notIn";
687
+ }>;
688
+ operand: typeof trilean.ExpressionNodeSchema;
689
+ candidates: z.ZodArray<typeof trilean.ExpressionNodeSchema>;
690
+ }, z.core.$strip>, z.ZodObject<{
691
+ kind: z.ZodLiteral<"exists">;
692
+ operand: typeof trilean.ExpressionNodeSchema;
693
+ }, z.core.$strip>, z.ZodObject<{
694
+ kind: z.ZodLiteral<"some">;
695
+ collection: z.ZodType<trilean.JsonValue, unknown, z.core.$ZodTypeInternals<trilean.JsonValue, unknown>>;
696
+ item: typeof PredicateNodeSchema;
697
+ filter: z.ZodOptional<typeof PredicateNodeSchema>;
698
+ }, z.core.$strip>, z.ZodObject<{
699
+ kind: z.ZodLiteral<"every">;
700
+ collection: z.ZodType<trilean.JsonValue, unknown, z.core.$ZodTypeInternals<trilean.JsonValue, unknown>>;
701
+ item: typeof PredicateNodeSchema;
702
+ filter: z.ZodOptional<typeof PredicateNodeSchema>;
703
+ }, z.core.$strip>, z.ZodObject<{
704
+ kind: z.ZodLiteral<"treeReference">;
705
+ key: z.ZodType<trilean.JsonValue, unknown, z.core.$ZodTypeInternals<trilean.JsonValue, unknown>>;
706
+ }, z.core.$strip>], "kind">]>;
707
+ }, z.core.$strict>]>>;
634
708
  preference: z.ZodOptional<z.ZodEnum<{
635
709
  score: "score";
636
710
  listed: "listed";
@@ -1332,7 +1406,81 @@ declare const GlobalConfigSchema: z.ZodObject<{
1332
1406
  }>>;
1333
1407
  }, z.core.$strict>>;
1334
1408
  pools: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
1335
- identities: z.ZodArray<z.ZodString>;
1409
+ identities: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
1410
+ identity: z.ZodString;
1411
+ when: z.ZodUnion<readonly [z.ZodObject<{
1412
+ newerThan: z.ZodOptional<z.ZodString>;
1413
+ olderThan: z.ZodOptional<z.ZodString>;
1414
+ maxSizeBytes: z.ZodOptional<z.ZodInt>;
1415
+ branch: z.ZodOptional<z.ZodString>;
1416
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1417
+ }, z.core.$strict>, z.ZodDiscriminatedUnion<[z.ZodObject<{
1418
+ kind: z.ZodLiteral<"not">;
1419
+ operand: typeof PredicateNodeSchema;
1420
+ }, z.core.$strip>, z.ZodObject<{
1421
+ kind: z.ZodLiteral<"and">;
1422
+ left: typeof PredicateNodeSchema;
1423
+ right: typeof PredicateNodeSchema;
1424
+ }, z.core.$strip>, z.ZodObject<{
1425
+ kind: z.ZodLiteral<"or">;
1426
+ left: typeof PredicateNodeSchema;
1427
+ right: typeof PredicateNodeSchema;
1428
+ }, z.core.$strip>, z.ZodObject<{
1429
+ kind: z.ZodLiteral<"allOf">;
1430
+ operands: z.ZodArray<typeof PredicateNodeSchema>;
1431
+ }, z.core.$strip>, z.ZodObject<{
1432
+ kind: z.ZodLiteral<"anyOf">;
1433
+ operands: z.ZodArray<typeof PredicateNodeSchema>;
1434
+ }, z.core.$strip>, z.ZodObject<{
1435
+ kind: z.ZodLiteral<"compare">;
1436
+ op: z.ZodEnum<{
1437
+ gt: "gt";
1438
+ gte: "gte";
1439
+ lt: "lt";
1440
+ lte: "lte";
1441
+ eq: "eq";
1442
+ neq: "neq";
1443
+ }>;
1444
+ left: typeof trilean.ExpressionNodeSchema;
1445
+ right: typeof trilean.ExpressionNodeSchema;
1446
+ }, z.core.$strip>, z.ZodObject<{
1447
+ kind: z.ZodLiteral<"textCompare">;
1448
+ op: z.ZodEnum<{
1449
+ equals: "equals";
1450
+ notEquals: "notEquals";
1451
+ matches: "matches";
1452
+ notMatches: "notMatches";
1453
+ portableMatches: "portableMatches";
1454
+ portableNotMatches: "portableNotMatches";
1455
+ }>;
1456
+ left: typeof trilean.ExpressionNodeSchema;
1457
+ right: typeof trilean.ExpressionNodeSchema;
1458
+ }, z.core.$strip>, z.ZodObject<{
1459
+ kind: z.ZodLiteral<"memberOf">;
1460
+ op: z.ZodEnum<{
1461
+ in: "in";
1462
+ notIn: "notIn";
1463
+ }>;
1464
+ operand: typeof trilean.ExpressionNodeSchema;
1465
+ candidates: z.ZodArray<typeof trilean.ExpressionNodeSchema>;
1466
+ }, z.core.$strip>, z.ZodObject<{
1467
+ kind: z.ZodLiteral<"exists">;
1468
+ operand: typeof trilean.ExpressionNodeSchema;
1469
+ }, z.core.$strip>, z.ZodObject<{
1470
+ kind: z.ZodLiteral<"some">;
1471
+ collection: z.ZodType<trilean.JsonValue, unknown, z.core.$ZodTypeInternals<trilean.JsonValue, unknown>>;
1472
+ item: typeof PredicateNodeSchema;
1473
+ filter: z.ZodOptional<typeof PredicateNodeSchema>;
1474
+ }, z.core.$strip>, z.ZodObject<{
1475
+ kind: z.ZodLiteral<"every">;
1476
+ collection: z.ZodType<trilean.JsonValue, unknown, z.core.$ZodTypeInternals<trilean.JsonValue, unknown>>;
1477
+ item: typeof PredicateNodeSchema;
1478
+ filter: z.ZodOptional<typeof PredicateNodeSchema>;
1479
+ }, z.core.$strip>, z.ZodObject<{
1480
+ kind: z.ZodLiteral<"treeReference">;
1481
+ key: z.ZodType<trilean.JsonValue, unknown, z.core.$ZodTypeInternals<trilean.JsonValue, unknown>>;
1482
+ }, z.core.$strip>], "kind">]>;
1483
+ }, z.core.$strict>]>>;
1336
1484
  preference: z.ZodOptional<z.ZodEnum<{
1337
1485
  score: "score";
1338
1486
  listed: "listed";
@@ -5230,12 +5378,12 @@ declare function createControlApiRouter(deps: ControlApiDeps): {
5230
5378
  identity: "identity";
5231
5379
  provider: "provider";
5232
5380
  pool: "pool";
5233
- keychain: "keychain";
5234
5381
  "ambient-credential": "ambient-credential";
5235
5382
  "binary-discovery": "binary-discovery";
5236
5383
  "claude-shim": "claude-shim";
5237
5384
  "path-resolution": "path-resolution";
5238
5385
  "legacy-name": "legacy-name";
5386
+ keychain: "keychain";
5239
5387
  "directory-rules": "directory-rules";
5240
5388
  "categories-local": "categories-local";
5241
5389
  "active-identity": "active-identity";
@@ -5811,12 +5959,12 @@ declare function createDoorApiRouter(deps: DoorApiDeps): {
5811
5959
  identity: "identity";
5812
5960
  provider: "provider";
5813
5961
  pool: "pool";
5814
- keychain: "keychain";
5815
5962
  "ambient-credential": "ambient-credential";
5816
5963
  "binary-discovery": "binary-discovery";
5817
5964
  "claude-shim": "claude-shim";
5818
5965
  "path-resolution": "path-resolution";
5819
5966
  "legacy-name": "legacy-name";
5967
+ keychain: "keychain";
5820
5968
  "directory-rules": "directory-rules";
5821
5969
  "categories-local": "categories-local";
5822
5970
  "active-identity": "active-identity";
@@ -6301,12 +6449,12 @@ declare const DoctorFindingSchema: z.ZodObject<{
6301
6449
  identity: "identity";
6302
6450
  provider: "provider";
6303
6451
  pool: "pool";
6304
- keychain: "keychain";
6305
6452
  "ambient-credential": "ambient-credential";
6306
6453
  "binary-discovery": "binary-discovery";
6307
6454
  "claude-shim": "claude-shim";
6308
6455
  "path-resolution": "path-resolution";
6309
6456
  "legacy-name": "legacy-name";
6457
+ keychain: "keychain";
6310
6458
  "directory-rules": "directory-rules";
6311
6459
  "categories-local": "categories-local";
6312
6460
  "active-identity": "active-identity";
@@ -6330,12 +6478,12 @@ declare const DoctorRunOutputSchema: z.ZodObject<{
6330
6478
  identity: "identity";
6331
6479
  provider: "provider";
6332
6480
  pool: "pool";
6333
- keychain: "keychain";
6334
6481
  "ambient-credential": "ambient-credential";
6335
6482
  "binary-discovery": "binary-discovery";
6336
6483
  "claude-shim": "claude-shim";
6337
6484
  "path-resolution": "path-resolution";
6338
6485
  "legacy-name": "legacy-name";
6486
+ keychain: "keychain";
6339
6487
  "directory-rules": "directory-rules";
6340
6488
  "categories-local": "categories-local";
6341
6489
  "active-identity": "active-identity";
@@ -6784,6 +6932,8 @@ interface PoolMember {
6784
6932
  readonly records: readonly UsageRecord[];
6785
6933
  /** Present when this member stands for a nested `pool:<name>` entry rather than a direct identity. */
6786
6934
  readonly nested?: NestedContribution;
6935
+ /** The member entry's policy condition, from the object form of a pool member: definite false skips the member as ineligible by policy, indeterminate demotes it to last within its class, naming the missing fact either way. */
6936
+ readonly policy?: WhenCondition;
6787
6937
  }
6788
6938
  /** The member last picked for this directory, kept so a conversation stays on the account whose prompt cache is warm. */
6789
6939
  interface StickyPick {
@@ -6811,6 +6961,8 @@ interface Candidate {
6811
6961
  /** When an `ineligible` candidate can be used again. */
6812
6962
  readonly blockedUntilMs?: number;
6813
6963
  readonly plan: PlanClass;
6964
+ /** True when the member's policy condition could not be decided and it is ranked last within its class, naming the missing fact in its reasons. */
6965
+ readonly policyDemoted?: boolean;
6814
6966
  /** Human-readable facts behind the class and score, in the order they matter. */
6815
6967
  readonly reasons: readonly string[];
6816
6968
  }
@@ -6852,6 +7004,15 @@ declare function collectPoolPick(params: Readonly<{
6852
7004
  nowMs: number;
6853
7005
  }>): PoolPickReport;
6854
7006
 
7007
+ /** One quota window's pool-selection facts, in the units a policy reads: fractions of the window, and hours until it resets. */
7008
+ interface PoolWindowFacts {
7009
+ /** The fraction of the window still unused, between zero and one. */
7010
+ readonly remaining: number;
7011
+ /** The fraction of the window already used, between zero and one. */
7012
+ readonly utilization: number;
7013
+ /** How long until the window resets, in hours. */
7014
+ readonly hoursUntilReset: number;
7015
+ }
6855
7016
  /** Everything a `when` condition can be evaluated against, injected rather than read. */
6856
7017
  interface ConditionContext {
6857
7018
  readonly nowMs: number;
@@ -6862,6 +7023,16 @@ interface ConditionContext {
6862
7023
  /** True when the repository is in detached-HEAD state, in which case no `branch` condition can match. */
6863
7024
  readonly branchDetached?: boolean;
6864
7025
  readonly env: Readonly<Record<string, string | undefined>>;
7026
+ /**
7027
+ * The pool-selection facts a member entry's policy condition reads. Present only when the condition guards a pool member, so the same reference namespace serves the cascade and the pool: `quota.fiveHour.remaining`, `quota.fiveHour.utilization`, `quota.fiveHour.hoursUntilReset`, the same three under `quota.sevenDay`, `quota.burnPerHour` (the five-hour window's observed utilisation pace, in fractions per hour), `session.resuming` (this launch resumes or continues a conversation) and `session.lastPickHoursAgo` (how long ago this directory last picked this member). Conversation context is deliberately not provided: nothing the door holds knows it, so a condition referencing it is indeterminate, naming the missing fact, rather than guessed.
7028
+ */
7029
+ readonly pool?: {
7030
+ readonly fiveHour?: PoolWindowFacts;
7031
+ readonly sevenDay?: PoolWindowFacts;
7032
+ readonly burnPerHour?: number;
7033
+ readonly resuming?: boolean;
7034
+ readonly lastPickHoursAgo?: number;
7035
+ };
6865
7036
  }
6866
7037
  /**
6867
7038
  * The result of evaluating one `when`: definite, with which condition fields were present and which of those did not hold, or indeterminate, when a datum the condition needed is missing. Indeterminate is not false: a missing fact has not failed the condition, it has left it undecided, and the consumer decides what an undecided entry does (the resolver keeps today's behaviour of not passing it, so nothing changes for existing configurations; the pool ranking of #258 instead keeps such a member but ranks it last, naming the missing fact).
@@ -7215,6 +7386,9 @@ declare function updateProvider(paths: LayoutPaths, name: string, input: UpdateP
7215
7386
  /** Deletes a provider definition. Throws `ProviderNotFoundError` when it does not exist. */
7216
7387
  declare function removeProvider(paths: LayoutPaths, name: string): void;
7217
7388
 
7389
+ /** One pool member entry, whichever form it takes: the plain selector string, or the object form carrying its policy condition. */
7390
+ type PoolMemberEntry = Pool["identities"][number];
7391
+
7218
7392
  /** Raised when a command names a pool that is not defined in the global config. */
7219
7393
  declare class PoolNotFoundError extends CliError {
7220
7394
  readonly poolName: string;
@@ -7225,9 +7399,9 @@ declare function readPools(paths: LayoutPaths): Readonly<Record<string, Pool>>;
7225
7399
  /** The pool named `name`; throws `PoolNotFoundError` when none is defined. */
7226
7400
  declare function requirePool(paths: LayoutPaths, name: string): Pool;
7227
7401
  /** Defines a new pool. Throws `InvalidPoolNameError` for a name `PoolNameSchema` rejects, `PoolAlreadyExistsError` for one already defined, and `UsageError` for a nested member that names an undefined pool or a cycle. */
7228
- declare function addPool(paths: LayoutPaths, name: string, identities: readonly string[], preference?: PoolPreference): Pool;
7402
+ declare function addPool(paths: LayoutPaths, name: string, identities: readonly PoolMemberEntry[], preference?: PoolPreference): Pool;
7229
7403
  /** Replaces an existing pool's members and preference. Throws `PoolNotFoundError` when it is not defined, and `UsageError` for a nested member that names an undefined pool or a cycle. */
7230
- declare function setPool(paths: LayoutPaths, name: string, identities: readonly string[], preference?: PoolPreference): Pool;
7404
+ declare function setPool(paths: LayoutPaths, name: string, identities: readonly PoolMemberEntry[], preference?: PoolPreference): Pool;
7231
7405
  /** Removes a pool. Throws `PoolNotFoundError` when it is not defined. Anything that selected it (an active-identity file, a directory rule) is left for `doctor` to report, as with a removed identity. */
7232
7406
  declare function removePool(paths: LayoutPaths, name: string): void;
7233
7407
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-shim",
3
- "version": "8.33.0",
3
+ "version": "8.34.0",
4
4
  "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.",
5
5
  "license": "MIT",
6
6
  "author": "Joseph Mearman <joseph@mearman.co.uk>",