agent-shim 8.32.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/index.mjs CHANGED
@@ -56,6 +56,7 @@ var categories_default_default = {
56
56
  // src/config/schema.ts
57
57
  import path2 from "node:path";
58
58
  import { z } from "zod";
59
+ import { PredicateNodeSchema } from "trilean";
59
60
 
60
61
  // src/versionDiscovery.ts
61
62
  import os from "node:os";
@@ -186,13 +187,14 @@ var CategoryMapSchema = z.strictObject({
186
187
  }).transform((input) => expandAllCategoryKey(input));
187
188
  var DURATION_RE = /^(?:0|[1-9][0-9]*)(?:ms|s|m|h|d|w)$/;
188
189
  var DurationSchema = z.string().regex(DURATION_RE);
189
- var WhenSchema = z.strictObject({
190
+ var WhenConditionObjectSchema = z.strictObject({
190
191
  newerThan: DurationSchema.optional(),
191
192
  olderThan: DurationSchema.optional(),
192
193
  maxSizeBytes: z.int().positive().optional(),
193
194
  branch: z.string().min(1).optional(),
194
195
  env: z.record(z.string().min(1), z.string()).optional()
195
196
  });
197
+ var WhenSchema = z.union([WhenConditionObjectSchema, PredicateNodeSchema]);
196
198
  var EntryValueSchema = z.union([
197
199
  z.boolean(),
198
200
  z.strictObject({ value: z.boolean(), when: WhenSchema })
@@ -309,11 +311,14 @@ var IDENTITY_NAME_PATTERN = "[A-Za-z0-9][A-Za-z0-9._@-]*";
309
311
  var PoolNameSchema = z.string().min(1).regex(new RegExp(`^${POOL_NAME_PATTERN}$`));
310
312
  var SELECTOR_RE = new RegExp(`^(${POOL_SELECTOR_PREFIX}${POOL_NAME_PATTERN}|${IDENTITY_NAME_PATTERN})$`);
311
313
  var IdentitySelectorSchema = z.string().min(1).regex(SELECTOR_RE);
312
- 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
+ }
313
318
  var POOL_PREFERENCES = ["score", "listed"];
314
319
  var PoolPreferenceSchema = z.enum(POOL_PREFERENCES);
315
320
  var PoolSchema = z.strictObject({
316
- 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" }),
317
322
  preference: PoolPreferenceSchema.optional()
318
323
  });
319
324
  var DirectoryRuleSchema = ConfigProfileSchema.omit({ description: true }).extend({
@@ -657,6 +662,7 @@ function classifyEntries(names, options) {
657
662
 
658
663
  // src/resolve/conditions.ts
659
664
  import picomatch2 from "picomatch";
665
+ import { createSyncEvaluator } from "trilean";
660
666
  var MILLISECONDS_PER_UNIT = Object.freeze({
661
667
  ms: 1,
662
668
  s: 1e3,
@@ -687,52 +693,87 @@ function matchBranch(pattern, branch, detached = false) {
687
693
  }
688
694
  return picomatch2(pattern, { dot: true, nocase: false })(branch);
689
695
  }
696
+ var evaluator = createSyncEvaluator({});
697
+ function resolversOf(context) {
698
+ const pool = context.pool;
699
+ const values = {
700
+ now: context.nowMs,
701
+ ...context.fact?.latestMtimeMs === void 0 ? {} : { "entry.latestMtimeMs": context.fact.latestMtimeMs },
702
+ ...context.fact?.totalSizeBytes === void 0 ? {} : { "entry.totalSizeBytes": context.fact.totalSizeBytes },
703
+ ...context.branch === void 0 ? {} : { "repo.branch": context.branch },
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 }
710
+ };
711
+ return {
712
+ resolveValue: (key) => {
713
+ const wrap = (value2) => ({ found: true, value: typeof value2 === "number" ? { kind: "number", value: value2 } : typeof value2 === "string" ? { kind: "text", value: value2 } : { kind: "boolean", value: value2 } });
714
+ if (typeof key !== "string") {
715
+ return { found: false };
716
+ }
717
+ if (key.startsWith("env.")) {
718
+ const value2 = context.env[key.slice("env.".length)];
719
+ return value2 === void 0 ? { found: false } : wrap(value2);
720
+ }
721
+ const value = values[key];
722
+ return value === void 0 ? { found: false } : wrap(value);
723
+ },
724
+ // This embedding provides the reference namespace only: a tree that asks for a lookup or a collection finds neither, which the evaluator carries as indeterminate rather than letting the tree pretend an answer.
725
+ resolveLookup: () => ({ found: false }),
726
+ resolveCollection: () => []
727
+ };
728
+ }
729
+ function fieldPredicate(field, when, context) {
730
+ switch (field) {
731
+ case "newerThan": {
732
+ const window = parseDuration(when.newerThan ?? "");
733
+ return { kind: "compare", op: "gte", left: { kind: "reference", key: "entry.latestMtimeMs" }, right: { kind: "numberLiteral", value: context.nowMs - window } };
734
+ }
735
+ case "olderThan": {
736
+ const window = parseDuration(when.olderThan ?? "");
737
+ return { kind: "compare", op: "lt", left: { kind: "reference", key: "entry.latestMtimeMs" }, right: { kind: "numberLiteral", value: context.nowMs - window } };
738
+ }
739
+ case "maxSizeBytes":
740
+ return { kind: "compare", op: "lte", left: { kind: "reference", key: "entry.totalSizeBytes" }, right: { kind: "numberLiteral", value: when.maxSizeBytes ?? 0 } };
741
+ case "branch":
742
+ return { kind: "allOf", operands: [{ kind: "textCompare", op: "portableMatches", left: { kind: "reference", key: "repo.branch" }, right: { kind: "textLiteral", value: when.branch ?? "" } }, { kind: "compare", op: "eq", left: { kind: "reference", key: "repo.detached" }, right: { kind: "booleanLiteral", value: false } }] };
743
+ case "env": {
744
+ const entries = Object.entries(when.env ?? {});
745
+ return { kind: "allOf", operands: entries.map(([name, expected]) => ({ kind: "textCompare", op: "equals", left: { kind: "reference", key: `env.${name}` }, right: { kind: "textLiteral", value: expected } })) };
746
+ }
747
+ }
748
+ return { kind: "allOf", operands: [] };
749
+ }
690
750
  function evaluateWhen(when, context) {
691
751
  if (when === void 0) {
692
- return { passed: true, checked: [], failed: [] };
752
+ return { status: "definite", passed: true, checked: [], failed: [] };
753
+ }
754
+ if ("kind" in when) {
755
+ const result = evaluator.evaluatePredicate(when, void 0, resolversOf(context));
756
+ return result.status === "indeterminate" ? { status: "indeterminate", reason: result.reason.message, checked: ["predicate"] } : { status: "definite", passed: result.value, checked: ["predicate"], failed: result.value ? [] : ["predicate"] };
693
757
  }
694
758
  const checked = [];
695
759
  const failed = [];
696
- if (when.newerThan !== void 0) {
697
- checked.push("newerThan");
698
- const window = parseDuration(when.newerThan);
699
- const latest = context.fact?.latestMtimeMs;
700
- if (latest === void 0 || context.nowMs - latest > window) {
701
- failed.push("newerThan");
702
- }
703
- }
704
- if (when.olderThan !== void 0) {
705
- checked.push("olderThan");
706
- const window = parseDuration(when.olderThan);
707
- const latest = context.fact?.latestMtimeMs;
708
- if (latest === void 0 || context.nowMs - latest <= window) {
709
- failed.push("olderThan");
710
- }
711
- }
712
- if (when.maxSizeBytes !== void 0) {
713
- checked.push("maxSizeBytes");
714
- const total = context.fact?.totalSizeBytes;
715
- if (total === void 0 || total > when.maxSizeBytes) {
716
- failed.push("maxSizeBytes");
760
+ for (const field of ["newerThan", "olderThan", "maxSizeBytes", "branch", "env"]) {
761
+ if (when[field] === void 0) {
762
+ continue;
717
763
  }
718
- }
719
- if (when.branch !== void 0) {
720
- checked.push("branch");
721
- if (!matchBranch(when.branch, context.branch, context.branchDetached ?? false)) {
722
- failed.push("branch");
764
+ checked.push(field);
765
+ const result = evaluator.evaluatePredicate(fieldPredicate(field, when, context), void 0, resolversOf(context));
766
+ if (result.status === "indeterminate") {
767
+ return { status: "indeterminate", reason: result.reason.message, checked };
723
768
  }
724
- }
725
- if (when.env !== void 0) {
726
- checked.push("env");
727
- const mismatch = Object.entries(when.env).some(([name, expected]) => context.env[name] !== expected);
728
- if (mismatch) {
729
- failed.push("env");
769
+ if (!result.value) {
770
+ failed.push(field);
730
771
  }
731
772
  }
732
- return { passed: failed.length === 0, checked, failed };
773
+ return { status: "definite", passed: failed.length === 0, checked, failed };
733
774
  }
734
775
  function isVacuousWhen(when) {
735
- return when !== void 0 && Object.keys(when).length === 0;
776
+ return when !== void 0 && !("kind" in when) && Object.keys(when).length === 0;
736
777
  }
737
778
 
738
779
  // src/resolve/match.ts
@@ -1103,10 +1144,10 @@ function selectRule(relPath, flattened, context) {
1103
1144
  ...context.branchDetached === void 0 ? {} : { branchDetached: context.branchDetached },
1104
1145
  env: context.env
1105
1146
  });
1106
- if (evaluation.passed) {
1147
+ if (evaluation.status === "definite" && evaluation.passed) {
1107
1148
  return { rule, eliminated };
1108
1149
  }
1109
- eliminated.push({ rule, failed: evaluation.failed });
1150
+ eliminated.push({ rule, failed: evaluation.status === "definite" ? evaluation.failed : [`${evaluation.checked.join(", ")} could not be decided: ${evaluation.reason}`] });
1110
1151
  }
1111
1152
  return { eliminated };
1112
1153
  }
@@ -1340,7 +1381,10 @@ function planFarm(params) {
1340
1381
  var ENTER_EVERYTHING = () => true;
1341
1382
  function readsSubtreeAggregates(rule) {
1342
1383
  const when = rule.when;
1343
- return when !== void 0 && (when.newerThan !== void 0 || when.olderThan !== void 0 || when.maxSizeBytes !== void 0);
1384
+ if (when === void 0) {
1385
+ return false;
1386
+ }
1387
+ return "kind" in when || when.newerThan !== void 0 || when.olderThan !== void 0 || when.maxSizeBytes !== void 0;
1344
1388
  }
1345
1389
  function hasUnboundedDepth(pattern) {
1346
1390
  return pattern.includes("**") || /[{(]/.test(pattern);
@@ -5043,7 +5087,7 @@ import { Agent as Agent3, fetch as undiciFetch } from "undici";
5043
5087
  // package.json
5044
5088
  var package_default = {
5045
5089
  name: "agent-shim",
5046
- version: "8.32.0",
5090
+ version: "8.34.0",
5047
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.",
5048
5092
  license: "MIT",
5049
5093
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -5099,6 +5143,7 @@ var package_default = {
5099
5143
  commander: "^15.0.0",
5100
5144
  cosmiconfig: "^10.0.1",
5101
5145
  picomatch: "^4.0.7",
5146
+ trilean: "1.7.1",
5102
5147
  undici: "7.29.1",
5103
5148
  zod: "^4.6.1"
5104
5149
  },
@@ -6334,12 +6379,48 @@ function assessIdentity(member, nowMs) {
6334
6379
  ...sevenDayBurn === void 0 ? {} : { sevenDayBurn }
6335
6380
  };
6336
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
+ }
6337
6403
  function rankPool(input) {
6338
6404
  const { members, nowMs } = input;
6339
- const assessments = members.map((member) => assess(member, nowMs));
6405
+ const assessments = members.map((member) => ({ member, ...assess(member, nowMs) }));
6340
6406
  const demands = assessments.flatMap(({ candidate, burn }) => burn === void 0 ? [] : [burn * capacityOf(candidate.plan)]);
6341
6407
  const sharedDemand = demands.length === 0 ? void 0 : Math.max(...demands);
6342
- 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) => {
6343
6424
  const { candidate, fiveHour, burn } = assessment;
6344
6425
  const pace = burn ?? (sharedDemand === void 0 ? void 0 : sharedDemand / capacityOf(candidate.plan));
6345
6426
  const { sevenDay, sevenDayBurn } = assessment;
@@ -6365,7 +6446,7 @@ function rankPool(input) {
6365
6446
  return { ...candidate, feasible: false, reasons: [...drySpots, ...candidate.reasons] };
6366
6447
  });
6367
6448
  const ordered = input.preference === "listed" ? [...candidates] : [...candidates].sort(
6368
- (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)
6369
6450
  );
6370
6451
  const sticky = input.sticky;
6371
6452
  const stickyIndex = sticky === void 0 ? -1 : ordered.findIndex((candidate) => candidate.identity === sticky.identity);
@@ -6536,27 +6617,27 @@ function rankNamedPool(input, poolName, stack, sticky) {
6536
6617
  throw new PoolGraphError(`No pool named "${poolName}". Run \`agent-shim pool add ${poolName} --identity <name>...\` first.`);
6537
6618
  }
6538
6619
  const childStack = [...stack, poolName];
6539
- const direct = pool.identities.filter((entry) => poolNameOf(entry) === void 0);
6540
- const missing = direct.filter((entry) => !input.identityExists(entry)).map((identity) => ({ pool: poolName, identity }));
6541
- 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]));
6542
6623
  const members = [];
6543
6624
  for (const entry of pool.identities) {
6544
- const nestedName = poolNameOf(entry);
6625
+ const nestedName = poolNameOf(poolMemberIdentity(entry));
6545
6626
  if (nestedName === void 0) {
6546
- const member = loaded.get(entry);
6627
+ const member = loaded.get(poolMemberIdentity(entry));
6547
6628
  if (member !== void 0) {
6548
- members.push(member);
6629
+ members.push(typeof entry === "string" ? member : { ...member, policy: entry.when });
6549
6630
  }
6550
6631
  continue;
6551
6632
  }
6552
6633
  if (input.pools[nestedName] === void 0) {
6553
- 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.`);
6554
6635
  }
6555
6636
  if (childStack.includes(nestedName)) {
6556
- 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.`);
6557
6638
  }
6558
6639
  const nested = nestedMember(input, nestedName, childStack, sticky);
6559
- members.push(nested.member);
6640
+ members.push(typeof entry === "string" ? nested.member : { ...nested.member, policy: entry.when });
6560
6641
  missing.push(...nested.missing);
6561
6642
  }
6562
6643
  const ranking = rankPool({
@@ -6817,7 +6898,7 @@ function writePools(paths, pools) {
6817
6898
  function poolCycleOf(pools, name) {
6818
6899
  const walk = (current, path32) => {
6819
6900
  for (const entry of pools[current]?.identities ?? []) {
6820
- const nested = poolNameOf(entry);
6901
+ const nested = poolNameOf(poolMemberIdentity(entry));
6821
6902
  if (nested === void 0) {
6822
6903
  continue;
6823
6904
  }
@@ -6838,9 +6919,10 @@ function poolCycleOf(pools, name) {
6838
6919
  }
6839
6920
  function validateMembers(pools, name, members) {
6840
6921
  for (const entry of members) {
6841
- const nested = poolNameOf(entry);
6922
+ const identity = poolMemberIdentity(entry);
6923
+ const nested = poolNameOf(identity);
6842
6924
  if (nested !== void 0 && nested !== name && pools[nested] === void 0) {
6843
- 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.`);
6844
6926
  }
6845
6927
  }
6846
6928
  const cycle = poolCycleOf({ ...pools, [name]: { identities: [...members] } }, name);
@@ -9545,16 +9627,16 @@ function runDoctor(params) {
9545
9627
  const validatedGlobalConfig = params.globalConfig.raw === void 0 ? void 0 : validateJson(GlobalConfigSchema, params.globalConfig);
9546
9628
  const pools = validatedGlobalConfig?.ok === true ? validatedGlobalConfig.data.pools ?? {} : {};
9547
9629
  for (const [poolName, pool] of Object.entries(pools)) {
9548
- 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)));
9549
9631
  const undefinedPools = pool.identities.flatMap((member) => {
9550
- const nested = poolNameOf(member);
9632
+ const nested = poolNameOf(poolMemberIdentity(member));
9551
9633
  return nested === void 0 || nested in pools ? [] : [nested];
9552
9634
  });
9553
9635
  if (missing.length === 0 && undefinedPools.length === 0) {
9554
- push("pool", "pass", `${poolName} is valid (${pool.identities.join(", ")}).`, poolName);
9636
+ push("pool", "pass", `${poolName} is valid (${pool.identities.map(poolMemberIdentity).join(", ")}).`, poolName);
9555
9637
  } else {
9556
9638
  const problems = [
9557
- ...missing.map((member) => `identity "${member}", which does not exist`),
9639
+ ...missing.map((member) => `identity "${poolMemberIdentity(member)}", which does not exist`),
9558
9640
  ...undefinedPools.map((nested) => `pool "${nested}", which is not defined`)
9559
9641
  ];
9560
9642
  push("pool", "fail", `Pool "${poolName}" names ${problems.join(" and ")}.`, poolName);