agent-shim 8.31.0 → 8.33.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 +2020 -135
- package/dist/index.cjs +113 -52
- package/dist/index.mjs +113 -52
- package/dist/types/index.d.ts +788 -35
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -368,6 +368,7 @@ var categories_default_default = {
|
|
|
368
368
|
// src/config/schema.ts
|
|
369
369
|
var import_node_path2 = __toESM(require("node:path"), 1);
|
|
370
370
|
var import_zod = require("zod");
|
|
371
|
+
var import_trilean = require("trilean");
|
|
371
372
|
|
|
372
373
|
// src/versionDiscovery.ts
|
|
373
374
|
var import_node_os = __toESM(require("node:os"), 1);
|
|
@@ -498,13 +499,14 @@ var CategoryMapSchema = import_zod.z.strictObject({
|
|
|
498
499
|
}).transform((input) => expandAllCategoryKey(input));
|
|
499
500
|
var DURATION_RE = /^(?:0|[1-9][0-9]*)(?:ms|s|m|h|d|w)$/;
|
|
500
501
|
var DurationSchema = import_zod.z.string().regex(DURATION_RE);
|
|
501
|
-
var
|
|
502
|
+
var WhenConditionObjectSchema = import_zod.z.strictObject({
|
|
502
503
|
newerThan: DurationSchema.optional(),
|
|
503
504
|
olderThan: DurationSchema.optional(),
|
|
504
505
|
maxSizeBytes: import_zod.z.int().positive().optional(),
|
|
505
506
|
branch: import_zod.z.string().min(1).optional(),
|
|
506
507
|
env: import_zod.z.record(import_zod.z.string().min(1), import_zod.z.string()).optional()
|
|
507
508
|
});
|
|
509
|
+
var WhenSchema = import_zod.z.union([WhenConditionObjectSchema, import_trilean.PredicateNodeSchema]);
|
|
508
510
|
var EntryValueSchema = import_zod.z.union([
|
|
509
511
|
import_zod.z.boolean(),
|
|
510
512
|
import_zod.z.strictObject({ value: import_zod.z.boolean(), when: WhenSchema })
|
|
@@ -969,6 +971,7 @@ function classifyEntries(names, options) {
|
|
|
969
971
|
|
|
970
972
|
// src/resolve/conditions.ts
|
|
971
973
|
var import_picomatch2 = __toESM(require("picomatch"), 1);
|
|
974
|
+
var import_trilean2 = require("trilean");
|
|
972
975
|
var MILLISECONDS_PER_UNIT = Object.freeze({
|
|
973
976
|
ms: 1,
|
|
974
977
|
s: 1e3,
|
|
@@ -999,52 +1002,81 @@ function matchBranch(pattern, branch, detached = false) {
|
|
|
999
1002
|
}
|
|
1000
1003
|
return (0, import_picomatch2.default)(pattern, { dot: true, nocase: false })(branch);
|
|
1001
1004
|
}
|
|
1005
|
+
var evaluator = (0, import_trilean2.createSyncEvaluator)({});
|
|
1006
|
+
function resolversOf(context) {
|
|
1007
|
+
const values = {
|
|
1008
|
+
now: context.nowMs,
|
|
1009
|
+
...context.fact?.latestMtimeMs === void 0 ? {} : { "entry.latestMtimeMs": context.fact.latestMtimeMs },
|
|
1010
|
+
...context.fact?.totalSizeBytes === void 0 ? {} : { "entry.totalSizeBytes": context.fact.totalSizeBytes },
|
|
1011
|
+
...context.branch === void 0 ? {} : { "repo.branch": context.branch },
|
|
1012
|
+
"repo.detached": context.branchDetached ?? false
|
|
1013
|
+
};
|
|
1014
|
+
return {
|
|
1015
|
+
resolveValue: (key) => {
|
|
1016
|
+
const wrap = (value2) => ({ found: true, value: typeof value2 === "number" ? { kind: "number", value: value2 } : typeof value2 === "string" ? { kind: "text", value: value2 } : { kind: "boolean", value: value2 } });
|
|
1017
|
+
if (typeof key !== "string") {
|
|
1018
|
+
return { found: false };
|
|
1019
|
+
}
|
|
1020
|
+
if (key.startsWith("env.")) {
|
|
1021
|
+
const value2 = context.env[key.slice("env.".length)];
|
|
1022
|
+
return value2 === void 0 ? { found: false } : wrap(value2);
|
|
1023
|
+
}
|
|
1024
|
+
const value = values[key];
|
|
1025
|
+
return value === void 0 ? { found: false } : wrap(value);
|
|
1026
|
+
},
|
|
1027
|
+
// 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.
|
|
1028
|
+
resolveLookup: () => ({ found: false }),
|
|
1029
|
+
resolveCollection: () => []
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
function fieldPredicate(field, when, context) {
|
|
1033
|
+
switch (field) {
|
|
1034
|
+
case "newerThan": {
|
|
1035
|
+
const window = parseDuration(when.newerThan ?? "");
|
|
1036
|
+
return { kind: "compare", op: "gte", left: { kind: "reference", key: "entry.latestMtimeMs" }, right: { kind: "numberLiteral", value: context.nowMs - window } };
|
|
1037
|
+
}
|
|
1038
|
+
case "olderThan": {
|
|
1039
|
+
const window = parseDuration(when.olderThan ?? "");
|
|
1040
|
+
return { kind: "compare", op: "lt", left: { kind: "reference", key: "entry.latestMtimeMs" }, right: { kind: "numberLiteral", value: context.nowMs - window } };
|
|
1041
|
+
}
|
|
1042
|
+
case "maxSizeBytes":
|
|
1043
|
+
return { kind: "compare", op: "lte", left: { kind: "reference", key: "entry.totalSizeBytes" }, right: { kind: "numberLiteral", value: when.maxSizeBytes ?? 0 } };
|
|
1044
|
+
case "branch":
|
|
1045
|
+
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 } }] };
|
|
1046
|
+
case "env": {
|
|
1047
|
+
const entries = Object.entries(when.env ?? {});
|
|
1048
|
+
return { kind: "allOf", operands: entries.map(([name, expected]) => ({ kind: "textCompare", op: "equals", left: { kind: "reference", key: `env.${name}` }, right: { kind: "textLiteral", value: expected } })) };
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
return { kind: "allOf", operands: [] };
|
|
1052
|
+
}
|
|
1002
1053
|
function evaluateWhen(when, context) {
|
|
1003
1054
|
if (when === void 0) {
|
|
1004
|
-
return { passed: true, checked: [], failed: [] };
|
|
1055
|
+
return { status: "definite", passed: true, checked: [], failed: [] };
|
|
1056
|
+
}
|
|
1057
|
+
if ("kind" in when) {
|
|
1058
|
+
const result = evaluator.evaluatePredicate(when, void 0, resolversOf(context));
|
|
1059
|
+
return result.status === "indeterminate" ? { status: "indeterminate", reason: result.reason.message, checked: ["predicate"] } : { status: "definite", passed: result.value, checked: ["predicate"], failed: result.value ? [] : ["predicate"] };
|
|
1005
1060
|
}
|
|
1006
1061
|
const checked = [];
|
|
1007
1062
|
const failed = [];
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
const latest = context.fact?.latestMtimeMs;
|
|
1012
|
-
if (latest === void 0 || context.nowMs - latest > window) {
|
|
1013
|
-
failed.push("newerThan");
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
if (when.olderThan !== void 0) {
|
|
1017
|
-
checked.push("olderThan");
|
|
1018
|
-
const window = parseDuration(when.olderThan);
|
|
1019
|
-
const latest = context.fact?.latestMtimeMs;
|
|
1020
|
-
if (latest === void 0 || context.nowMs - latest <= window) {
|
|
1021
|
-
failed.push("olderThan");
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
if (when.maxSizeBytes !== void 0) {
|
|
1025
|
-
checked.push("maxSizeBytes");
|
|
1026
|
-
const total = context.fact?.totalSizeBytes;
|
|
1027
|
-
if (total === void 0 || total > when.maxSizeBytes) {
|
|
1028
|
-
failed.push("maxSizeBytes");
|
|
1063
|
+
for (const field of ["newerThan", "olderThan", "maxSizeBytes", "branch", "env"]) {
|
|
1064
|
+
if (when[field] === void 0) {
|
|
1065
|
+
continue;
|
|
1029
1066
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
failed.push("branch");
|
|
1067
|
+
checked.push(field);
|
|
1068
|
+
const result = evaluator.evaluatePredicate(fieldPredicate(field, when, context), void 0, resolversOf(context));
|
|
1069
|
+
if (result.status === "indeterminate") {
|
|
1070
|
+
return { status: "indeterminate", reason: result.reason.message, checked };
|
|
1035
1071
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
checked.push("env");
|
|
1039
|
-
const mismatch = Object.entries(when.env).some(([name, expected]) => context.env[name] !== expected);
|
|
1040
|
-
if (mismatch) {
|
|
1041
|
-
failed.push("env");
|
|
1072
|
+
if (!result.value) {
|
|
1073
|
+
failed.push(field);
|
|
1042
1074
|
}
|
|
1043
1075
|
}
|
|
1044
|
-
return { passed: failed.length === 0, checked, failed };
|
|
1076
|
+
return { status: "definite", passed: failed.length === 0, checked, failed };
|
|
1045
1077
|
}
|
|
1046
1078
|
function isVacuousWhen(when) {
|
|
1047
|
-
return when !== void 0 && Object.keys(when).length === 0;
|
|
1079
|
+
return when !== void 0 && !("kind" in when) && Object.keys(when).length === 0;
|
|
1048
1080
|
}
|
|
1049
1081
|
|
|
1050
1082
|
// src/resolve/match.ts
|
|
@@ -1415,10 +1447,10 @@ function selectRule(relPath, flattened, context) {
|
|
|
1415
1447
|
...context.branchDetached === void 0 ? {} : { branchDetached: context.branchDetached },
|
|
1416
1448
|
env: context.env
|
|
1417
1449
|
});
|
|
1418
|
-
if (evaluation.passed) {
|
|
1450
|
+
if (evaluation.status === "definite" && evaluation.passed) {
|
|
1419
1451
|
return { rule, eliminated };
|
|
1420
1452
|
}
|
|
1421
|
-
eliminated.push({ rule, failed: evaluation.failed });
|
|
1453
|
+
eliminated.push({ rule, failed: evaluation.status === "definite" ? evaluation.failed : [`${evaluation.checked.join(", ")} could not be decided: ${evaluation.reason}`] });
|
|
1422
1454
|
}
|
|
1423
1455
|
return { eliminated };
|
|
1424
1456
|
}
|
|
@@ -1652,7 +1684,10 @@ function planFarm(params) {
|
|
|
1652
1684
|
var ENTER_EVERYTHING = () => true;
|
|
1653
1685
|
function readsSubtreeAggregates(rule) {
|
|
1654
1686
|
const when = rule.when;
|
|
1655
|
-
|
|
1687
|
+
if (when === void 0) {
|
|
1688
|
+
return false;
|
|
1689
|
+
}
|
|
1690
|
+
return "kind" in when || when.newerThan !== void 0 || when.olderThan !== void 0 || when.maxSizeBytes !== void 0;
|
|
1656
1691
|
}
|
|
1657
1692
|
function hasUnboundedDepth(pattern) {
|
|
1658
1693
|
return pattern.includes("**") || /[{(]/.test(pattern);
|
|
@@ -5355,7 +5390,7 @@ var import_undici = require("undici");
|
|
|
5355
5390
|
// package.json
|
|
5356
5391
|
var package_default = {
|
|
5357
5392
|
name: "agent-shim",
|
|
5358
|
-
version: "8.
|
|
5393
|
+
version: "8.33.0",
|
|
5359
5394
|
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.",
|
|
5360
5395
|
license: "MIT",
|
|
5361
5396
|
author: "Joseph Mearman <joseph@mearman.co.uk>",
|
|
@@ -5411,6 +5446,7 @@ var package_default = {
|
|
|
5411
5446
|
commander: "^15.0.0",
|
|
5412
5447
|
cosmiconfig: "^10.0.1",
|
|
5413
5448
|
picomatch: "^4.0.7",
|
|
5449
|
+
trilean: "1.7.1",
|
|
5414
5450
|
undici: "7.29.1",
|
|
5415
5451
|
zod: "^4.6.1"
|
|
5416
5452
|
},
|
|
@@ -6537,10 +6573,10 @@ function windowBlockedUntil(windows) {
|
|
|
6537
6573
|
const resets = windows.flatMap((window) => window.status === "rejected" && window.resetsAtMs !== void 0 ? [window.resetsAtMs] : []);
|
|
6538
6574
|
return resets.length === 0 ? void 0 : Math.max(...resets);
|
|
6539
6575
|
}
|
|
6540
|
-
function
|
|
6576
|
+
function windowBurn(records, which) {
|
|
6541
6577
|
const points = [];
|
|
6542
6578
|
for (const record of records) {
|
|
6543
|
-
const window = record.rateLimitHeaders === void 0 ? void 0 : parseUnifiedRateLimit(record.rateLimitHeaders)?.
|
|
6579
|
+
const window = record.rateLimitHeaders === void 0 ? void 0 : parseUnifiedRateLimit(record.rateLimitHeaders)?.[which];
|
|
6544
6580
|
if (window?.utilization !== void 0 && window.resetsAt !== void 0) {
|
|
6545
6581
|
points.push({ resetsAt: window.resetsAt, point: { atMs: Date.parse(record.at), utilization: window.utilization } });
|
|
6546
6582
|
}
|
|
@@ -6634,11 +6670,16 @@ function assessIdentity(member, nowMs) {
|
|
|
6634
6670
|
const score = remaining * plan.capacity / (untilResetMs / MS_PER_HOUR);
|
|
6635
6671
|
const fiveRemaining = five?.utilization === void 0 ? void 0 : Math.max(0, 1 - five.utilization);
|
|
6636
6672
|
const fiveUntil = five?.resetsAtMs === void 0 ? FIVE_HOUR_WINDOW_MS : Math.max(five.resetsAtMs - nowMs, MS_PER_SECOND4);
|
|
6637
|
-
const burn =
|
|
6673
|
+
const burn = windowBurn(member.records, "fiveHour");
|
|
6674
|
+
const sevenRemaining = seven?.utilization === void 0 ? void 0 : Math.max(0, 1 - seven.utilization);
|
|
6675
|
+
const sevenUntil = seven?.resetsAtMs === void 0 ? SEVEN_DAY_WINDOW_MS : Math.max(seven.resetsAtMs - nowMs, MS_PER_SECOND4);
|
|
6676
|
+
const sevenDayBurn = windowBurn(member.records, "sevenDay");
|
|
6638
6677
|
return {
|
|
6639
6678
|
candidate: { ...base, class: "scored", score, reasons: [...windowReasons.map((reason, index) => index === windowReasons.length - 1 ? `${reason}${observedAgo}` : reason), ...planReasons] },
|
|
6640
6679
|
...fiveRemaining === void 0 ? {} : { fiveHour: { remaining: fiveRemaining, untilResetMs: fiveUntil } },
|
|
6641
|
-
...burn === void 0 ? {} : { burn }
|
|
6680
|
+
...burn === void 0 ? {} : { burn },
|
|
6681
|
+
...sevenRemaining === void 0 ? {} : { sevenDay: { remaining: sevenRemaining, untilResetMs: sevenUntil } },
|
|
6682
|
+
...sevenDayBurn === void 0 ? {} : { sevenDayBurn }
|
|
6642
6683
|
};
|
|
6643
6684
|
}
|
|
6644
6685
|
function rankPool(input) {
|
|
@@ -6649,14 +6690,27 @@ function rankPool(input) {
|
|
|
6649
6690
|
const candidates = assessments.map((assessment) => {
|
|
6650
6691
|
const { candidate, fiveHour, burn } = assessment;
|
|
6651
6692
|
const pace = burn ?? (sharedDemand === void 0 ? void 0 : sharedDemand / capacityOf(candidate.plan));
|
|
6652
|
-
|
|
6693
|
+
const { sevenDay, sevenDayBurn } = assessment;
|
|
6694
|
+
if (candidate.class !== "scored") {
|
|
6653
6695
|
return { ...candidate, feasible: true };
|
|
6654
6696
|
}
|
|
6655
|
-
const
|
|
6656
|
-
if (
|
|
6697
|
+
const drySpots = [];
|
|
6698
|
+
if (fiveHour !== void 0 && pace !== void 0) {
|
|
6699
|
+
const runsDryInMs = fiveHour.remaining / pace;
|
|
6700
|
+
if (runsDryInMs < fiveHour.untilResetMs) {
|
|
6701
|
+
drySpots.push(`5h window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(fiveHour.untilResetMs)}`);
|
|
6702
|
+
}
|
|
6703
|
+
}
|
|
6704
|
+
if (sevenDay !== void 0 && sevenDayBurn !== void 0) {
|
|
6705
|
+
const runsDryInMs = sevenDay.remaining / sevenDayBurn;
|
|
6706
|
+
if (runsDryInMs < sevenDay.untilResetMs) {
|
|
6707
|
+
drySpots.push(`7d window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(sevenDay.untilResetMs)}`);
|
|
6708
|
+
}
|
|
6709
|
+
}
|
|
6710
|
+
if (drySpots.length === 0) {
|
|
6657
6711
|
return { ...candidate, feasible: true };
|
|
6658
6712
|
}
|
|
6659
|
-
return { ...candidate, feasible: false, reasons: [
|
|
6713
|
+
return { ...candidate, feasible: false, reasons: [...drySpots, ...candidate.reasons] };
|
|
6660
6714
|
});
|
|
6661
6715
|
const ordered = input.preference === "listed" ? [...candidates] : [...candidates].sort(
|
|
6662
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)
|
|
@@ -6664,7 +6718,8 @@ function rankPool(input) {
|
|
|
6664
6718
|
const sticky = input.sticky;
|
|
6665
6719
|
const stickyIndex = sticky === void 0 ? -1 : ordered.findIndex((candidate) => candidate.identity === sticky.identity);
|
|
6666
6720
|
const stickyCandidate = ordered[stickyIndex];
|
|
6667
|
-
|
|
6721
|
+
const keepSticky = stickyCandidate?.class === "scored" && stickyCandidate.feasible;
|
|
6722
|
+
if (sticky !== void 0 && keepSticky) {
|
|
6668
6723
|
const ageMs = nowMs - Date.parse(sticky.at);
|
|
6669
6724
|
const warm = ageMs <= PROMPT_CACHE_TTL_MS;
|
|
6670
6725
|
if (input.resuming || warm) {
|
|
@@ -6673,12 +6728,14 @@ function rankPool(input) {
|
|
|
6673
6728
|
ordered.unshift({ ...stickyCandidate, reasons: [why, ...stickyCandidate.reasons] });
|
|
6674
6729
|
}
|
|
6675
6730
|
}
|
|
6731
|
+
const movedOff = sticky !== void 0 && input.resuming && !keepSticky ? { identity: sticky.identity, reason: stickyCandidate?.reasons[0] ?? "no longer among the pool's members" } : void 0;
|
|
6676
6732
|
const pick = ordered.find((candidate) => candidate.class !== "ineligible");
|
|
6677
6733
|
const returns = ordered.flatMap((candidate) => candidate.blockedUntilMs === void 0 ? [] : [{ identity: candidate.identity, atMs: candidate.blockedUntilMs }]);
|
|
6678
6734
|
const earliestReturn = returns.length === 0 ? void 0 : returns.reduce((soonest, entry) => entry.atMs < soonest.atMs ? entry : soonest);
|
|
6679
6735
|
return {
|
|
6680
6736
|
candidates: ordered,
|
|
6681
|
-
...pick === void 0 ? { ...earliestReturn === void 0 ? {} : { earliestReturn } } : { pick }
|
|
6737
|
+
...pick === void 0 ? { ...earliestReturn === void 0 ? {} : { earliestReturn } } : { pick },
|
|
6738
|
+
...movedOff === void 0 || pick === void 0 ? {} : { movedOff }
|
|
6682
6739
|
};
|
|
6683
6740
|
}
|
|
6684
6741
|
|
|
@@ -6936,6 +6993,7 @@ function collectPoolPick(params) {
|
|
|
6936
6993
|
candidates: ranking.candidates.map(candidateView),
|
|
6937
6994
|
missing: missing.map(({ identity }) => identity),
|
|
6938
6995
|
...ranking.earliestReturn === void 0 ? {} : { earliestReturn: { identity: ranking.earliestReturn.identity, at: new Date(ranking.earliestReturn.atMs).toISOString() } },
|
|
6996
|
+
...ranking.movedOff === void 0 ? {} : { movedOff: ranking.movedOff },
|
|
6939
6997
|
...sticky.problem === void 0 ? {} : { stickyProblem: sticky.problem }
|
|
6940
6998
|
};
|
|
6941
6999
|
}
|
|
@@ -9207,6 +9265,8 @@ var PoolPickReportSchema = import_zod13.z.strictObject({
|
|
|
9207
9265
|
missing: import_zod13.z.array(import_zod13.z.string()),
|
|
9208
9266
|
/** When nothing can be picked, the soonest any member returns. */
|
|
9209
9267
|
earliestReturn: import_zod13.z.strictObject({ identity: import_zod13.z.string(), at: import_zod13.z.iso.datetime() }).optional(),
|
|
9268
|
+
/** The member a resumed conversation started on but moved off, and why, when the ranking could not keep it. */
|
|
9269
|
+
movedOff: import_zod13.z.strictObject({ identity: import_zod13.z.string(), reason: import_zod13.z.string() }).optional(),
|
|
9210
9270
|
/** Why the last-pick record could not be read, when that is the case. */
|
|
9211
9271
|
stickyProblem: import_zod13.z.string().optional()
|
|
9212
9272
|
});
|
|
@@ -12953,10 +13013,11 @@ function resolvePoolLaunch(params) {
|
|
|
12953
13013
|
if (graph.ranking.candidates.length === 0) {
|
|
12954
13014
|
return { ok: false, message: `agent-shim: no member of pool "${poolName}" is an existing identity.` };
|
|
12955
13015
|
}
|
|
12956
|
-
const { pick, earliestReturn } = graph.ranking;
|
|
13016
|
+
const { pick, earliestReturn, movedOff } = graph.ranking;
|
|
12957
13017
|
if (pick !== void 0) {
|
|
12958
13018
|
recordStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd, pick.identity, nowMs);
|
|
12959
|
-
|
|
13019
|
+
const move = movedOff === void 0 ? "" : `moved off ${movedOff.identity} (${movedOff.reason}); `;
|
|
13020
|
+
return { ok: true, identity: pick.identity, explanation: `pool ${poolName}: ${move}${pick.reasons.slice(0, REASONS_LOGGED).join("; ")}` };
|
|
12960
13021
|
}
|
|
12961
13022
|
if (earliestReturn === void 0) {
|
|
12962
13023
|
return { ok: false, message: `agent-shim: no member of pool "${poolName}" can be picked.` };
|
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
|
|
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 })
|
|
@@ -657,6 +659,7 @@ function classifyEntries(names, options) {
|
|
|
657
659
|
|
|
658
660
|
// src/resolve/conditions.ts
|
|
659
661
|
import picomatch2 from "picomatch";
|
|
662
|
+
import { createSyncEvaluator } from "trilean";
|
|
660
663
|
var MILLISECONDS_PER_UNIT = Object.freeze({
|
|
661
664
|
ms: 1,
|
|
662
665
|
s: 1e3,
|
|
@@ -687,52 +690,81 @@ function matchBranch(pattern, branch, detached = false) {
|
|
|
687
690
|
}
|
|
688
691
|
return picomatch2(pattern, { dot: true, nocase: false })(branch);
|
|
689
692
|
}
|
|
693
|
+
var evaluator = createSyncEvaluator({});
|
|
694
|
+
function resolversOf(context) {
|
|
695
|
+
const values = {
|
|
696
|
+
now: context.nowMs,
|
|
697
|
+
...context.fact?.latestMtimeMs === void 0 ? {} : { "entry.latestMtimeMs": context.fact.latestMtimeMs },
|
|
698
|
+
...context.fact?.totalSizeBytes === void 0 ? {} : { "entry.totalSizeBytes": context.fact.totalSizeBytes },
|
|
699
|
+
...context.branch === void 0 ? {} : { "repo.branch": context.branch },
|
|
700
|
+
"repo.detached": context.branchDetached ?? false
|
|
701
|
+
};
|
|
702
|
+
return {
|
|
703
|
+
resolveValue: (key) => {
|
|
704
|
+
const wrap = (value2) => ({ found: true, value: typeof value2 === "number" ? { kind: "number", value: value2 } : typeof value2 === "string" ? { kind: "text", value: value2 } : { kind: "boolean", value: value2 } });
|
|
705
|
+
if (typeof key !== "string") {
|
|
706
|
+
return { found: false };
|
|
707
|
+
}
|
|
708
|
+
if (key.startsWith("env.")) {
|
|
709
|
+
const value2 = context.env[key.slice("env.".length)];
|
|
710
|
+
return value2 === void 0 ? { found: false } : wrap(value2);
|
|
711
|
+
}
|
|
712
|
+
const value = values[key];
|
|
713
|
+
return value === void 0 ? { found: false } : wrap(value);
|
|
714
|
+
},
|
|
715
|
+
// 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.
|
|
716
|
+
resolveLookup: () => ({ found: false }),
|
|
717
|
+
resolveCollection: () => []
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
function fieldPredicate(field, when, context) {
|
|
721
|
+
switch (field) {
|
|
722
|
+
case "newerThan": {
|
|
723
|
+
const window = parseDuration(when.newerThan ?? "");
|
|
724
|
+
return { kind: "compare", op: "gte", left: { kind: "reference", key: "entry.latestMtimeMs" }, right: { kind: "numberLiteral", value: context.nowMs - window } };
|
|
725
|
+
}
|
|
726
|
+
case "olderThan": {
|
|
727
|
+
const window = parseDuration(when.olderThan ?? "");
|
|
728
|
+
return { kind: "compare", op: "lt", left: { kind: "reference", key: "entry.latestMtimeMs" }, right: { kind: "numberLiteral", value: context.nowMs - window } };
|
|
729
|
+
}
|
|
730
|
+
case "maxSizeBytes":
|
|
731
|
+
return { kind: "compare", op: "lte", left: { kind: "reference", key: "entry.totalSizeBytes" }, right: { kind: "numberLiteral", value: when.maxSizeBytes ?? 0 } };
|
|
732
|
+
case "branch":
|
|
733
|
+
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 } }] };
|
|
734
|
+
case "env": {
|
|
735
|
+
const entries = Object.entries(when.env ?? {});
|
|
736
|
+
return { kind: "allOf", operands: entries.map(([name, expected]) => ({ kind: "textCompare", op: "equals", left: { kind: "reference", key: `env.${name}` }, right: { kind: "textLiteral", value: expected } })) };
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
return { kind: "allOf", operands: [] };
|
|
740
|
+
}
|
|
690
741
|
function evaluateWhen(when, context) {
|
|
691
742
|
if (when === void 0) {
|
|
692
|
-
return { passed: true, checked: [], failed: [] };
|
|
743
|
+
return { status: "definite", passed: true, checked: [], failed: [] };
|
|
744
|
+
}
|
|
745
|
+
if ("kind" in when) {
|
|
746
|
+
const result = evaluator.evaluatePredicate(when, void 0, resolversOf(context));
|
|
747
|
+
return result.status === "indeterminate" ? { status: "indeterminate", reason: result.reason.message, checked: ["predicate"] } : { status: "definite", passed: result.value, checked: ["predicate"], failed: result.value ? [] : ["predicate"] };
|
|
693
748
|
}
|
|
694
749
|
const checked = [];
|
|
695
750
|
const failed = [];
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
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");
|
|
751
|
+
for (const field of ["newerThan", "olderThan", "maxSizeBytes", "branch", "env"]) {
|
|
752
|
+
if (when[field] === void 0) {
|
|
753
|
+
continue;
|
|
717
754
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
failed.push("branch");
|
|
755
|
+
checked.push(field);
|
|
756
|
+
const result = evaluator.evaluatePredicate(fieldPredicate(field, when, context), void 0, resolversOf(context));
|
|
757
|
+
if (result.status === "indeterminate") {
|
|
758
|
+
return { status: "indeterminate", reason: result.reason.message, checked };
|
|
723
759
|
}
|
|
724
|
-
|
|
725
|
-
|
|
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");
|
|
760
|
+
if (!result.value) {
|
|
761
|
+
failed.push(field);
|
|
730
762
|
}
|
|
731
763
|
}
|
|
732
|
-
return { passed: failed.length === 0, checked, failed };
|
|
764
|
+
return { status: "definite", passed: failed.length === 0, checked, failed };
|
|
733
765
|
}
|
|
734
766
|
function isVacuousWhen(when) {
|
|
735
|
-
return when !== void 0 && Object.keys(when).length === 0;
|
|
767
|
+
return when !== void 0 && !("kind" in when) && Object.keys(when).length === 0;
|
|
736
768
|
}
|
|
737
769
|
|
|
738
770
|
// src/resolve/match.ts
|
|
@@ -1103,10 +1135,10 @@ function selectRule(relPath, flattened, context) {
|
|
|
1103
1135
|
...context.branchDetached === void 0 ? {} : { branchDetached: context.branchDetached },
|
|
1104
1136
|
env: context.env
|
|
1105
1137
|
});
|
|
1106
|
-
if (evaluation.passed) {
|
|
1138
|
+
if (evaluation.status === "definite" && evaluation.passed) {
|
|
1107
1139
|
return { rule, eliminated };
|
|
1108
1140
|
}
|
|
1109
|
-
eliminated.push({ rule, failed: evaluation.failed });
|
|
1141
|
+
eliminated.push({ rule, failed: evaluation.status === "definite" ? evaluation.failed : [`${evaluation.checked.join(", ")} could not be decided: ${evaluation.reason}`] });
|
|
1110
1142
|
}
|
|
1111
1143
|
return { eliminated };
|
|
1112
1144
|
}
|
|
@@ -1340,7 +1372,10 @@ function planFarm(params) {
|
|
|
1340
1372
|
var ENTER_EVERYTHING = () => true;
|
|
1341
1373
|
function readsSubtreeAggregates(rule) {
|
|
1342
1374
|
const when = rule.when;
|
|
1343
|
-
|
|
1375
|
+
if (when === void 0) {
|
|
1376
|
+
return false;
|
|
1377
|
+
}
|
|
1378
|
+
return "kind" in when || when.newerThan !== void 0 || when.olderThan !== void 0 || when.maxSizeBytes !== void 0;
|
|
1344
1379
|
}
|
|
1345
1380
|
function hasUnboundedDepth(pattern) {
|
|
1346
1381
|
return pattern.includes("**") || /[{(]/.test(pattern);
|
|
@@ -5043,7 +5078,7 @@ import { Agent as Agent3, fetch as undiciFetch } from "undici";
|
|
|
5043
5078
|
// package.json
|
|
5044
5079
|
var package_default = {
|
|
5045
5080
|
name: "agent-shim",
|
|
5046
|
-
version: "8.
|
|
5081
|
+
version: "8.33.0",
|
|
5047
5082
|
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
5083
|
license: "MIT",
|
|
5049
5084
|
author: "Joseph Mearman <joseph@mearman.co.uk>",
|
|
@@ -5099,6 +5134,7 @@ var package_default = {
|
|
|
5099
5134
|
commander: "^15.0.0",
|
|
5100
5135
|
cosmiconfig: "^10.0.1",
|
|
5101
5136
|
picomatch: "^4.0.7",
|
|
5137
|
+
trilean: "1.7.1",
|
|
5102
5138
|
undici: "7.29.1",
|
|
5103
5139
|
zod: "^4.6.1"
|
|
5104
5140
|
},
|
|
@@ -6225,10 +6261,10 @@ function windowBlockedUntil(windows) {
|
|
|
6225
6261
|
const resets = windows.flatMap((window) => window.status === "rejected" && window.resetsAtMs !== void 0 ? [window.resetsAtMs] : []);
|
|
6226
6262
|
return resets.length === 0 ? void 0 : Math.max(...resets);
|
|
6227
6263
|
}
|
|
6228
|
-
function
|
|
6264
|
+
function windowBurn(records, which) {
|
|
6229
6265
|
const points = [];
|
|
6230
6266
|
for (const record of records) {
|
|
6231
|
-
const window = record.rateLimitHeaders === void 0 ? void 0 : parseUnifiedRateLimit(record.rateLimitHeaders)?.
|
|
6267
|
+
const window = record.rateLimitHeaders === void 0 ? void 0 : parseUnifiedRateLimit(record.rateLimitHeaders)?.[which];
|
|
6232
6268
|
if (window?.utilization !== void 0 && window.resetsAt !== void 0) {
|
|
6233
6269
|
points.push({ resetsAt: window.resetsAt, point: { atMs: Date.parse(record.at), utilization: window.utilization } });
|
|
6234
6270
|
}
|
|
@@ -6322,11 +6358,16 @@ function assessIdentity(member, nowMs) {
|
|
|
6322
6358
|
const score = remaining * plan.capacity / (untilResetMs / MS_PER_HOUR);
|
|
6323
6359
|
const fiveRemaining = five?.utilization === void 0 ? void 0 : Math.max(0, 1 - five.utilization);
|
|
6324
6360
|
const fiveUntil = five?.resetsAtMs === void 0 ? FIVE_HOUR_WINDOW_MS : Math.max(five.resetsAtMs - nowMs, MS_PER_SECOND4);
|
|
6325
|
-
const burn =
|
|
6361
|
+
const burn = windowBurn(member.records, "fiveHour");
|
|
6362
|
+
const sevenRemaining = seven?.utilization === void 0 ? void 0 : Math.max(0, 1 - seven.utilization);
|
|
6363
|
+
const sevenUntil = seven?.resetsAtMs === void 0 ? SEVEN_DAY_WINDOW_MS : Math.max(seven.resetsAtMs - nowMs, MS_PER_SECOND4);
|
|
6364
|
+
const sevenDayBurn = windowBurn(member.records, "sevenDay");
|
|
6326
6365
|
return {
|
|
6327
6366
|
candidate: { ...base, class: "scored", score, reasons: [...windowReasons.map((reason, index) => index === windowReasons.length - 1 ? `${reason}${observedAgo}` : reason), ...planReasons] },
|
|
6328
6367
|
...fiveRemaining === void 0 ? {} : { fiveHour: { remaining: fiveRemaining, untilResetMs: fiveUntil } },
|
|
6329
|
-
...burn === void 0 ? {} : { burn }
|
|
6368
|
+
...burn === void 0 ? {} : { burn },
|
|
6369
|
+
...sevenRemaining === void 0 ? {} : { sevenDay: { remaining: sevenRemaining, untilResetMs: sevenUntil } },
|
|
6370
|
+
...sevenDayBurn === void 0 ? {} : { sevenDayBurn }
|
|
6330
6371
|
};
|
|
6331
6372
|
}
|
|
6332
6373
|
function rankPool(input) {
|
|
@@ -6337,14 +6378,27 @@ function rankPool(input) {
|
|
|
6337
6378
|
const candidates = assessments.map((assessment) => {
|
|
6338
6379
|
const { candidate, fiveHour, burn } = assessment;
|
|
6339
6380
|
const pace = burn ?? (sharedDemand === void 0 ? void 0 : sharedDemand / capacityOf(candidate.plan));
|
|
6340
|
-
|
|
6381
|
+
const { sevenDay, sevenDayBurn } = assessment;
|
|
6382
|
+
if (candidate.class !== "scored") {
|
|
6341
6383
|
return { ...candidate, feasible: true };
|
|
6342
6384
|
}
|
|
6343
|
-
const
|
|
6344
|
-
if (
|
|
6385
|
+
const drySpots = [];
|
|
6386
|
+
if (fiveHour !== void 0 && pace !== void 0) {
|
|
6387
|
+
const runsDryInMs = fiveHour.remaining / pace;
|
|
6388
|
+
if (runsDryInMs < fiveHour.untilResetMs) {
|
|
6389
|
+
drySpots.push(`5h window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(fiveHour.untilResetMs)}`);
|
|
6390
|
+
}
|
|
6391
|
+
}
|
|
6392
|
+
if (sevenDay !== void 0 && sevenDayBurn !== void 0) {
|
|
6393
|
+
const runsDryInMs = sevenDay.remaining / sevenDayBurn;
|
|
6394
|
+
if (runsDryInMs < sevenDay.untilResetMs) {
|
|
6395
|
+
drySpots.push(`7d window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(sevenDay.untilResetMs)}`);
|
|
6396
|
+
}
|
|
6397
|
+
}
|
|
6398
|
+
if (drySpots.length === 0) {
|
|
6345
6399
|
return { ...candidate, feasible: true };
|
|
6346
6400
|
}
|
|
6347
|
-
return { ...candidate, feasible: false, reasons: [
|
|
6401
|
+
return { ...candidate, feasible: false, reasons: [...drySpots, ...candidate.reasons] };
|
|
6348
6402
|
});
|
|
6349
6403
|
const ordered = input.preference === "listed" ? [...candidates] : [...candidates].sort(
|
|
6350
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)
|
|
@@ -6352,7 +6406,8 @@ function rankPool(input) {
|
|
|
6352
6406
|
const sticky = input.sticky;
|
|
6353
6407
|
const stickyIndex = sticky === void 0 ? -1 : ordered.findIndex((candidate) => candidate.identity === sticky.identity);
|
|
6354
6408
|
const stickyCandidate = ordered[stickyIndex];
|
|
6355
|
-
|
|
6409
|
+
const keepSticky = stickyCandidate?.class === "scored" && stickyCandidate.feasible;
|
|
6410
|
+
if (sticky !== void 0 && keepSticky) {
|
|
6356
6411
|
const ageMs = nowMs - Date.parse(sticky.at);
|
|
6357
6412
|
const warm = ageMs <= PROMPT_CACHE_TTL_MS;
|
|
6358
6413
|
if (input.resuming || warm) {
|
|
@@ -6361,12 +6416,14 @@ function rankPool(input) {
|
|
|
6361
6416
|
ordered.unshift({ ...stickyCandidate, reasons: [why, ...stickyCandidate.reasons] });
|
|
6362
6417
|
}
|
|
6363
6418
|
}
|
|
6419
|
+
const movedOff = sticky !== void 0 && input.resuming && !keepSticky ? { identity: sticky.identity, reason: stickyCandidate?.reasons[0] ?? "no longer among the pool's members" } : void 0;
|
|
6364
6420
|
const pick = ordered.find((candidate) => candidate.class !== "ineligible");
|
|
6365
6421
|
const returns = ordered.flatMap((candidate) => candidate.blockedUntilMs === void 0 ? [] : [{ identity: candidate.identity, atMs: candidate.blockedUntilMs }]);
|
|
6366
6422
|
const earliestReturn = returns.length === 0 ? void 0 : returns.reduce((soonest, entry) => entry.atMs < soonest.atMs ? entry : soonest);
|
|
6367
6423
|
return {
|
|
6368
6424
|
candidates: ordered,
|
|
6369
|
-
...pick === void 0 ? { ...earliestReturn === void 0 ? {} : { earliestReturn } } : { pick }
|
|
6425
|
+
...pick === void 0 ? { ...earliestReturn === void 0 ? {} : { earliestReturn } } : { pick },
|
|
6426
|
+
...movedOff === void 0 || pick === void 0 ? {} : { movedOff }
|
|
6370
6427
|
};
|
|
6371
6428
|
}
|
|
6372
6429
|
|
|
@@ -6624,6 +6681,7 @@ function collectPoolPick(params) {
|
|
|
6624
6681
|
candidates: ranking.candidates.map(candidateView),
|
|
6625
6682
|
missing: missing.map(({ identity }) => identity),
|
|
6626
6683
|
...ranking.earliestReturn === void 0 ? {} : { earliestReturn: { identity: ranking.earliestReturn.identity, at: new Date(ranking.earliestReturn.atMs).toISOString() } },
|
|
6684
|
+
...ranking.movedOff === void 0 ? {} : { movedOff: ranking.movedOff },
|
|
6627
6685
|
...sticky.problem === void 0 ? {} : { stickyProblem: sticky.problem }
|
|
6628
6686
|
};
|
|
6629
6687
|
}
|
|
@@ -8895,6 +8953,8 @@ var PoolPickReportSchema = z13.strictObject({
|
|
|
8895
8953
|
missing: z13.array(z13.string()),
|
|
8896
8954
|
/** When nothing can be picked, the soonest any member returns. */
|
|
8897
8955
|
earliestReturn: z13.strictObject({ identity: z13.string(), at: z13.iso.datetime() }).optional(),
|
|
8956
|
+
/** The member a resumed conversation started on but moved off, and why, when the ranking could not keep it. */
|
|
8957
|
+
movedOff: z13.strictObject({ identity: z13.string(), reason: z13.string() }).optional(),
|
|
8898
8958
|
/** Why the last-pick record could not be read, when that is the case. */
|
|
8899
8959
|
stickyProblem: z13.string().optional()
|
|
8900
8960
|
});
|
|
@@ -12641,10 +12701,11 @@ function resolvePoolLaunch(params) {
|
|
|
12641
12701
|
if (graph.ranking.candidates.length === 0) {
|
|
12642
12702
|
return { ok: false, message: `agent-shim: no member of pool "${poolName}" is an existing identity.` };
|
|
12643
12703
|
}
|
|
12644
|
-
const { pick, earliestReturn } = graph.ranking;
|
|
12704
|
+
const { pick, earliestReturn, movedOff } = graph.ranking;
|
|
12645
12705
|
if (pick !== void 0) {
|
|
12646
12706
|
recordStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd, pick.identity, nowMs);
|
|
12647
|
-
|
|
12707
|
+
const move = movedOff === void 0 ? "" : `moved off ${movedOff.identity} (${movedOff.reason}); `;
|
|
12708
|
+
return { ok: true, identity: pick.identity, explanation: `pool ${poolName}: ${move}${pick.reasons.slice(0, REASONS_LOGGED).join("; ")}` };
|
|
12648
12709
|
}
|
|
12649
12710
|
if (earliestReturn === void 0) {
|
|
12650
12711
|
return { ok: false, message: `agent-shim: no member of pool "${poolName}" can be picked.` };
|