agent-shim 8.31.0 → 8.32.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
@@ -61796,7 +61796,7 @@ function registerIdentityCommand(program2, deps) {
61796
61796
  // package.json
61797
61797
  var package_default = {
61798
61798
  name: "agent-shim",
61799
- version: "8.31.0",
61799
+ version: "8.32.0",
61800
61800
  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.",
61801
61801
  license: "MIT",
61802
61802
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -62775,10 +62775,10 @@ function windowBlockedUntil(windows) {
62775
62775
  const resets = windows.flatMap((window) => window.status === "rejected" && window.resetsAtMs !== void 0 ? [window.resetsAtMs] : []);
62776
62776
  return resets.length === 0 ? void 0 : Math.max(...resets);
62777
62777
  }
62778
- function fiveHourBurn(records) {
62778
+ function windowBurn(records, which) {
62779
62779
  const points = [];
62780
62780
  for (const record2 of records) {
62781
- const window = record2.rateLimitHeaders === void 0 ? void 0 : parseUnifiedRateLimit(record2.rateLimitHeaders)?.fiveHour;
62781
+ const window = record2.rateLimitHeaders === void 0 ? void 0 : parseUnifiedRateLimit(record2.rateLimitHeaders)?.[which];
62782
62782
  if (window?.utilization !== void 0 && window.resetsAt !== void 0) {
62783
62783
  points.push({ resetsAt: window.resetsAt, point: { atMs: Date.parse(record2.at), utilization: window.utilization } });
62784
62784
  }
@@ -62872,11 +62872,16 @@ function assessIdentity(member, nowMs) {
62872
62872
  const score = remaining * plan.capacity / (untilResetMs / MS_PER_HOUR);
62873
62873
  const fiveRemaining = five?.utilization === void 0 ? void 0 : Math.max(0, 1 - five.utilization);
62874
62874
  const fiveUntil = five?.resetsAtMs === void 0 ? FIVE_HOUR_WINDOW_MS : Math.max(five.resetsAtMs - nowMs, MS_PER_SECOND3);
62875
- const burn = fiveHourBurn(member.records);
62875
+ const burn = windowBurn(member.records, "fiveHour");
62876
+ const sevenRemaining = seven?.utilization === void 0 ? void 0 : Math.max(0, 1 - seven.utilization);
62877
+ const sevenUntil = seven?.resetsAtMs === void 0 ? SEVEN_DAY_WINDOW_MS : Math.max(seven.resetsAtMs - nowMs, MS_PER_SECOND3);
62878
+ const sevenDayBurn = windowBurn(member.records, "sevenDay");
62876
62879
  return {
62877
62880
  candidate: { ...base, class: "scored", score, reasons: [...windowReasons.map((reason, index) => index === windowReasons.length - 1 ? `${reason}${observedAgo}` : reason), ...planReasons] },
62878
62881
  ...fiveRemaining === void 0 ? {} : { fiveHour: { remaining: fiveRemaining, untilResetMs: fiveUntil } },
62879
- ...burn === void 0 ? {} : { burn }
62882
+ ...burn === void 0 ? {} : { burn },
62883
+ ...sevenRemaining === void 0 ? {} : { sevenDay: { remaining: sevenRemaining, untilResetMs: sevenUntil } },
62884
+ ...sevenDayBurn === void 0 ? {} : { sevenDayBurn }
62880
62885
  };
62881
62886
  }
62882
62887
  function rankPool(input2) {
@@ -62887,14 +62892,27 @@ function rankPool(input2) {
62887
62892
  const candidates = assessments.map((assessment) => {
62888
62893
  const { candidate, fiveHour, burn } = assessment;
62889
62894
  const pace = burn ?? (sharedDemand === void 0 ? void 0 : sharedDemand / capacityOf(candidate.plan));
62890
- if (candidate.class !== "scored" || fiveHour === void 0 || pace === void 0) {
62895
+ const { sevenDay, sevenDayBurn } = assessment;
62896
+ if (candidate.class !== "scored") {
62891
62897
  return { ...candidate, feasible: true };
62892
62898
  }
62893
- const runsDryInMs = fiveHour.remaining / pace;
62894
- if (runsDryInMs >= fiveHour.untilResetMs) {
62899
+ const drySpots = [];
62900
+ if (fiveHour !== void 0 && pace !== void 0) {
62901
+ const runsDryInMs = fiveHour.remaining / pace;
62902
+ if (runsDryInMs < fiveHour.untilResetMs) {
62903
+ drySpots.push(`5h window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(fiveHour.untilResetMs)}`);
62904
+ }
62905
+ }
62906
+ if (sevenDay !== void 0 && sevenDayBurn !== void 0) {
62907
+ const runsDryInMs = sevenDay.remaining / sevenDayBurn;
62908
+ if (runsDryInMs < sevenDay.untilResetMs) {
62909
+ drySpots.push(`7d window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(sevenDay.untilResetMs)}`);
62910
+ }
62911
+ }
62912
+ if (drySpots.length === 0) {
62895
62913
  return { ...candidate, feasible: true };
62896
62914
  }
62897
- return { ...candidate, feasible: false, reasons: [`5h window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(fiveHour.untilResetMs)}`, ...candidate.reasons] };
62915
+ return { ...candidate, feasible: false, reasons: [...drySpots, ...candidate.reasons] };
62898
62916
  });
62899
62917
  const ordered = input2.preference === "listed" ? [...candidates] : [...candidates].sort(
62900
62918
  (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)
@@ -62902,7 +62920,8 @@ function rankPool(input2) {
62902
62920
  const sticky = input2.sticky;
62903
62921
  const stickyIndex = sticky === void 0 ? -1 : ordered.findIndex((candidate) => candidate.identity === sticky.identity);
62904
62922
  const stickyCandidate = ordered[stickyIndex];
62905
- if (sticky !== void 0 && stickyCandidate?.class === "scored" && stickyCandidate.feasible) {
62923
+ const keepSticky = stickyCandidate?.class === "scored" && stickyCandidate.feasible;
62924
+ if (sticky !== void 0 && keepSticky) {
62906
62925
  const ageMs = nowMs - Date.parse(sticky.at);
62907
62926
  const warm = ageMs <= PROMPT_CACHE_TTL_MS;
62908
62927
  if (input2.resuming || warm) {
@@ -62911,12 +62930,14 @@ function rankPool(input2) {
62911
62930
  ordered.unshift({ ...stickyCandidate, reasons: [why, ...stickyCandidate.reasons] });
62912
62931
  }
62913
62932
  }
62933
+ const movedOff = sticky !== void 0 && input2.resuming && !keepSticky ? { identity: sticky.identity, reason: stickyCandidate?.reasons[0] ?? "no longer among the pool's members" } : void 0;
62914
62934
  const pick2 = ordered.find((candidate) => candidate.class !== "ineligible");
62915
62935
  const returns = ordered.flatMap((candidate) => candidate.blockedUntilMs === void 0 ? [] : [{ identity: candidate.identity, atMs: candidate.blockedUntilMs }]);
62916
62936
  const earliestReturn = returns.length === 0 ? void 0 : returns.reduce((soonest, entry) => entry.atMs < soonest.atMs ? entry : soonest);
62917
62937
  return {
62918
62938
  candidates: ordered,
62919
- ...pick2 === void 0 ? { ...earliestReturn === void 0 ? {} : { earliestReturn } } : { pick: pick2 }
62939
+ ...pick2 === void 0 ? { ...earliestReturn === void 0 ? {} : { earliestReturn } } : { pick: pick2 },
62940
+ ...movedOff === void 0 || pick2 === void 0 ? {} : { movedOff }
62920
62941
  };
62921
62942
  }
62922
62943
 
@@ -63332,6 +63353,7 @@ function collectPoolPick(params) {
63332
63353
  candidates: ranking.candidates.map(candidateView),
63333
63354
  missing: missing.map(({ identity }) => identity),
63334
63355
  ...ranking.earliestReturn === void 0 ? {} : { earliestReturn: { identity: ranking.earliestReturn.identity, at: new Date(ranking.earliestReturn.atMs).toISOString() } },
63356
+ ...ranking.movedOff === void 0 ? {} : { movedOff: ranking.movedOff },
63335
63357
  ...sticky.problem === void 0 ? {} : { stickyProblem: sticky.problem }
63336
63358
  };
63337
63359
  }
@@ -69611,6 +69633,8 @@ var PoolPickReportSchema = external_exports.strictObject({
69611
69633
  missing: external_exports.array(external_exports.string()),
69612
69634
  /** When nothing can be picked, the soonest any member returns. */
69613
69635
  earliestReturn: external_exports.strictObject({ identity: external_exports.string(), at: external_exports.iso.datetime() }).optional(),
69636
+ /** The member a resumed conversation started on but moved off, and why, when the ranking could not keep it. */
69637
+ movedOff: external_exports.strictObject({ identity: external_exports.string(), reason: external_exports.string() }).optional(),
69614
69638
  /** Why the last-pick record could not be read, when that is the case. */
69615
69639
  stickyProblem: external_exports.string().optional()
69616
69640
  });
@@ -81187,10 +81211,11 @@ function resolvePoolLaunch(params) {
81187
81211
  if (graph.ranking.candidates.length === 0) {
81188
81212
  return { ok: false, message: `agent-shim: no member of pool "${poolName}" is an existing identity.` };
81189
81213
  }
81190
- const { pick: pick2, earliestReturn } = graph.ranking;
81214
+ const { pick: pick2, earliestReturn, movedOff } = graph.ranking;
81191
81215
  if (pick2 !== void 0) {
81192
81216
  recordStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd, pick2.identity, nowMs);
81193
- return { ok: true, identity: pick2.identity, explanation: `pool ${poolName}: ${pick2.reasons.slice(0, REASONS_LOGGED).join("; ")}` };
81217
+ const move = movedOff === void 0 ? "" : `moved off ${movedOff.identity} (${movedOff.reason}); `;
81218
+ return { ok: true, identity: pick2.identity, explanation: `pool ${poolName}: ${move}${pick2.reasons.slice(0, REASONS_LOGGED).join("; ")}` };
81194
81219
  }
81195
81220
  if (earliestReturn === void 0) {
81196
81221
  return { ok: false, message: `agent-shim: no member of pool "${poolName}" can be picked.` };
package/dist/index.cjs CHANGED
@@ -5355,7 +5355,7 @@ var import_undici = require("undici");
5355
5355
  // package.json
5356
5356
  var package_default = {
5357
5357
  name: "agent-shim",
5358
- version: "8.31.0",
5358
+ version: "8.32.0",
5359
5359
  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
5360
  license: "MIT",
5361
5361
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -6537,10 +6537,10 @@ function windowBlockedUntil(windows) {
6537
6537
  const resets = windows.flatMap((window) => window.status === "rejected" && window.resetsAtMs !== void 0 ? [window.resetsAtMs] : []);
6538
6538
  return resets.length === 0 ? void 0 : Math.max(...resets);
6539
6539
  }
6540
- function fiveHourBurn(records) {
6540
+ function windowBurn(records, which) {
6541
6541
  const points = [];
6542
6542
  for (const record of records) {
6543
- const window = record.rateLimitHeaders === void 0 ? void 0 : parseUnifiedRateLimit(record.rateLimitHeaders)?.fiveHour;
6543
+ const window = record.rateLimitHeaders === void 0 ? void 0 : parseUnifiedRateLimit(record.rateLimitHeaders)?.[which];
6544
6544
  if (window?.utilization !== void 0 && window.resetsAt !== void 0) {
6545
6545
  points.push({ resetsAt: window.resetsAt, point: { atMs: Date.parse(record.at), utilization: window.utilization } });
6546
6546
  }
@@ -6634,11 +6634,16 @@ function assessIdentity(member, nowMs) {
6634
6634
  const score = remaining * plan.capacity / (untilResetMs / MS_PER_HOUR);
6635
6635
  const fiveRemaining = five?.utilization === void 0 ? void 0 : Math.max(0, 1 - five.utilization);
6636
6636
  const fiveUntil = five?.resetsAtMs === void 0 ? FIVE_HOUR_WINDOW_MS : Math.max(five.resetsAtMs - nowMs, MS_PER_SECOND4);
6637
- const burn = fiveHourBurn(member.records);
6637
+ const burn = windowBurn(member.records, "fiveHour");
6638
+ const sevenRemaining = seven?.utilization === void 0 ? void 0 : Math.max(0, 1 - seven.utilization);
6639
+ const sevenUntil = seven?.resetsAtMs === void 0 ? SEVEN_DAY_WINDOW_MS : Math.max(seven.resetsAtMs - nowMs, MS_PER_SECOND4);
6640
+ const sevenDayBurn = windowBurn(member.records, "sevenDay");
6638
6641
  return {
6639
6642
  candidate: { ...base, class: "scored", score, reasons: [...windowReasons.map((reason, index) => index === windowReasons.length - 1 ? `${reason}${observedAgo}` : reason), ...planReasons] },
6640
6643
  ...fiveRemaining === void 0 ? {} : { fiveHour: { remaining: fiveRemaining, untilResetMs: fiveUntil } },
6641
- ...burn === void 0 ? {} : { burn }
6644
+ ...burn === void 0 ? {} : { burn },
6645
+ ...sevenRemaining === void 0 ? {} : { sevenDay: { remaining: sevenRemaining, untilResetMs: sevenUntil } },
6646
+ ...sevenDayBurn === void 0 ? {} : { sevenDayBurn }
6642
6647
  };
6643
6648
  }
6644
6649
  function rankPool(input) {
@@ -6649,14 +6654,27 @@ function rankPool(input) {
6649
6654
  const candidates = assessments.map((assessment) => {
6650
6655
  const { candidate, fiveHour, burn } = assessment;
6651
6656
  const pace = burn ?? (sharedDemand === void 0 ? void 0 : sharedDemand / capacityOf(candidate.plan));
6652
- if (candidate.class !== "scored" || fiveHour === void 0 || pace === void 0) {
6657
+ const { sevenDay, sevenDayBurn } = assessment;
6658
+ if (candidate.class !== "scored") {
6653
6659
  return { ...candidate, feasible: true };
6654
6660
  }
6655
- const runsDryInMs = fiveHour.remaining / pace;
6656
- if (runsDryInMs >= fiveHour.untilResetMs) {
6661
+ const drySpots = [];
6662
+ if (fiveHour !== void 0 && pace !== void 0) {
6663
+ const runsDryInMs = fiveHour.remaining / pace;
6664
+ if (runsDryInMs < fiveHour.untilResetMs) {
6665
+ drySpots.push(`5h window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(fiveHour.untilResetMs)}`);
6666
+ }
6667
+ }
6668
+ if (sevenDay !== void 0 && sevenDayBurn !== void 0) {
6669
+ const runsDryInMs = sevenDay.remaining / sevenDayBurn;
6670
+ if (runsDryInMs < sevenDay.untilResetMs) {
6671
+ drySpots.push(`7d window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(sevenDay.untilResetMs)}`);
6672
+ }
6673
+ }
6674
+ if (drySpots.length === 0) {
6657
6675
  return { ...candidate, feasible: true };
6658
6676
  }
6659
- return { ...candidate, feasible: false, reasons: [`5h window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(fiveHour.untilResetMs)}`, ...candidate.reasons] };
6677
+ return { ...candidate, feasible: false, reasons: [...drySpots, ...candidate.reasons] };
6660
6678
  });
6661
6679
  const ordered = input.preference === "listed" ? [...candidates] : [...candidates].sort(
6662
6680
  (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 +6682,8 @@ function rankPool(input) {
6664
6682
  const sticky = input.sticky;
6665
6683
  const stickyIndex = sticky === void 0 ? -1 : ordered.findIndex((candidate) => candidate.identity === sticky.identity);
6666
6684
  const stickyCandidate = ordered[stickyIndex];
6667
- if (sticky !== void 0 && stickyCandidate?.class === "scored" && stickyCandidate.feasible) {
6685
+ const keepSticky = stickyCandidate?.class === "scored" && stickyCandidate.feasible;
6686
+ if (sticky !== void 0 && keepSticky) {
6668
6687
  const ageMs = nowMs - Date.parse(sticky.at);
6669
6688
  const warm = ageMs <= PROMPT_CACHE_TTL_MS;
6670
6689
  if (input.resuming || warm) {
@@ -6673,12 +6692,14 @@ function rankPool(input) {
6673
6692
  ordered.unshift({ ...stickyCandidate, reasons: [why, ...stickyCandidate.reasons] });
6674
6693
  }
6675
6694
  }
6695
+ 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
6696
  const pick = ordered.find((candidate) => candidate.class !== "ineligible");
6677
6697
  const returns = ordered.flatMap((candidate) => candidate.blockedUntilMs === void 0 ? [] : [{ identity: candidate.identity, atMs: candidate.blockedUntilMs }]);
6678
6698
  const earliestReturn = returns.length === 0 ? void 0 : returns.reduce((soonest, entry) => entry.atMs < soonest.atMs ? entry : soonest);
6679
6699
  return {
6680
6700
  candidates: ordered,
6681
- ...pick === void 0 ? { ...earliestReturn === void 0 ? {} : { earliestReturn } } : { pick }
6701
+ ...pick === void 0 ? { ...earliestReturn === void 0 ? {} : { earliestReturn } } : { pick },
6702
+ ...movedOff === void 0 || pick === void 0 ? {} : { movedOff }
6682
6703
  };
6683
6704
  }
6684
6705
 
@@ -6936,6 +6957,7 @@ function collectPoolPick(params) {
6936
6957
  candidates: ranking.candidates.map(candidateView),
6937
6958
  missing: missing.map(({ identity }) => identity),
6938
6959
  ...ranking.earliestReturn === void 0 ? {} : { earliestReturn: { identity: ranking.earliestReturn.identity, at: new Date(ranking.earliestReturn.atMs).toISOString() } },
6960
+ ...ranking.movedOff === void 0 ? {} : { movedOff: ranking.movedOff },
6939
6961
  ...sticky.problem === void 0 ? {} : { stickyProblem: sticky.problem }
6940
6962
  };
6941
6963
  }
@@ -9207,6 +9229,8 @@ var PoolPickReportSchema = import_zod13.z.strictObject({
9207
9229
  missing: import_zod13.z.array(import_zod13.z.string()),
9208
9230
  /** When nothing can be picked, the soonest any member returns. */
9209
9231
  earliestReturn: import_zod13.z.strictObject({ identity: import_zod13.z.string(), at: import_zod13.z.iso.datetime() }).optional(),
9232
+ /** The member a resumed conversation started on but moved off, and why, when the ranking could not keep it. */
9233
+ movedOff: import_zod13.z.strictObject({ identity: import_zod13.z.string(), reason: import_zod13.z.string() }).optional(),
9210
9234
  /** Why the last-pick record could not be read, when that is the case. */
9211
9235
  stickyProblem: import_zod13.z.string().optional()
9212
9236
  });
@@ -12953,10 +12977,11 @@ function resolvePoolLaunch(params) {
12953
12977
  if (graph.ranking.candidates.length === 0) {
12954
12978
  return { ok: false, message: `agent-shim: no member of pool "${poolName}" is an existing identity.` };
12955
12979
  }
12956
- const { pick, earliestReturn } = graph.ranking;
12980
+ const { pick, earliestReturn, movedOff } = graph.ranking;
12957
12981
  if (pick !== void 0) {
12958
12982
  recordStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd, pick.identity, nowMs);
12959
- return { ok: true, identity: pick.identity, explanation: `pool ${poolName}: ${pick.reasons.slice(0, REASONS_LOGGED).join("; ")}` };
12983
+ const move = movedOff === void 0 ? "" : `moved off ${movedOff.identity} (${movedOff.reason}); `;
12984
+ return { ok: true, identity: pick.identity, explanation: `pool ${poolName}: ${move}${pick.reasons.slice(0, REASONS_LOGGED).join("; ")}` };
12960
12985
  }
12961
12986
  if (earliestReturn === void 0) {
12962
12987
  return { ok: false, message: `agent-shim: no member of pool "${poolName}" can be picked.` };
package/dist/index.mjs CHANGED
@@ -5043,7 +5043,7 @@ import { Agent as Agent3, fetch as undiciFetch } from "undici";
5043
5043
  // package.json
5044
5044
  var package_default = {
5045
5045
  name: "agent-shim",
5046
- version: "8.31.0",
5046
+ version: "8.32.0",
5047
5047
  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
5048
  license: "MIT",
5049
5049
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -6225,10 +6225,10 @@ function windowBlockedUntil(windows) {
6225
6225
  const resets = windows.flatMap((window) => window.status === "rejected" && window.resetsAtMs !== void 0 ? [window.resetsAtMs] : []);
6226
6226
  return resets.length === 0 ? void 0 : Math.max(...resets);
6227
6227
  }
6228
- function fiveHourBurn(records) {
6228
+ function windowBurn(records, which) {
6229
6229
  const points = [];
6230
6230
  for (const record of records) {
6231
- const window = record.rateLimitHeaders === void 0 ? void 0 : parseUnifiedRateLimit(record.rateLimitHeaders)?.fiveHour;
6231
+ const window = record.rateLimitHeaders === void 0 ? void 0 : parseUnifiedRateLimit(record.rateLimitHeaders)?.[which];
6232
6232
  if (window?.utilization !== void 0 && window.resetsAt !== void 0) {
6233
6233
  points.push({ resetsAt: window.resetsAt, point: { atMs: Date.parse(record.at), utilization: window.utilization } });
6234
6234
  }
@@ -6322,11 +6322,16 @@ function assessIdentity(member, nowMs) {
6322
6322
  const score = remaining * plan.capacity / (untilResetMs / MS_PER_HOUR);
6323
6323
  const fiveRemaining = five?.utilization === void 0 ? void 0 : Math.max(0, 1 - five.utilization);
6324
6324
  const fiveUntil = five?.resetsAtMs === void 0 ? FIVE_HOUR_WINDOW_MS : Math.max(five.resetsAtMs - nowMs, MS_PER_SECOND4);
6325
- const burn = fiveHourBurn(member.records);
6325
+ const burn = windowBurn(member.records, "fiveHour");
6326
+ const sevenRemaining = seven?.utilization === void 0 ? void 0 : Math.max(0, 1 - seven.utilization);
6327
+ const sevenUntil = seven?.resetsAtMs === void 0 ? SEVEN_DAY_WINDOW_MS : Math.max(seven.resetsAtMs - nowMs, MS_PER_SECOND4);
6328
+ const sevenDayBurn = windowBurn(member.records, "sevenDay");
6326
6329
  return {
6327
6330
  candidate: { ...base, class: "scored", score, reasons: [...windowReasons.map((reason, index) => index === windowReasons.length - 1 ? `${reason}${observedAgo}` : reason), ...planReasons] },
6328
6331
  ...fiveRemaining === void 0 ? {} : { fiveHour: { remaining: fiveRemaining, untilResetMs: fiveUntil } },
6329
- ...burn === void 0 ? {} : { burn }
6332
+ ...burn === void 0 ? {} : { burn },
6333
+ ...sevenRemaining === void 0 ? {} : { sevenDay: { remaining: sevenRemaining, untilResetMs: sevenUntil } },
6334
+ ...sevenDayBurn === void 0 ? {} : { sevenDayBurn }
6330
6335
  };
6331
6336
  }
6332
6337
  function rankPool(input) {
@@ -6337,14 +6342,27 @@ function rankPool(input) {
6337
6342
  const candidates = assessments.map((assessment) => {
6338
6343
  const { candidate, fiveHour, burn } = assessment;
6339
6344
  const pace = burn ?? (sharedDemand === void 0 ? void 0 : sharedDemand / capacityOf(candidate.plan));
6340
- if (candidate.class !== "scored" || fiveHour === void 0 || pace === void 0) {
6345
+ const { sevenDay, sevenDayBurn } = assessment;
6346
+ if (candidate.class !== "scored") {
6341
6347
  return { ...candidate, feasible: true };
6342
6348
  }
6343
- const runsDryInMs = fiveHour.remaining / pace;
6344
- if (runsDryInMs >= fiveHour.untilResetMs) {
6349
+ const drySpots = [];
6350
+ if (fiveHour !== void 0 && pace !== void 0) {
6351
+ const runsDryInMs = fiveHour.remaining / pace;
6352
+ if (runsDryInMs < fiveHour.untilResetMs) {
6353
+ drySpots.push(`5h window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(fiveHour.untilResetMs)}`);
6354
+ }
6355
+ }
6356
+ if (sevenDay !== void 0 && sevenDayBurn !== void 0) {
6357
+ const runsDryInMs = sevenDay.remaining / sevenDayBurn;
6358
+ if (runsDryInMs < sevenDay.untilResetMs) {
6359
+ drySpots.push(`7d window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(sevenDay.untilResetMs)}`);
6360
+ }
6361
+ }
6362
+ if (drySpots.length === 0) {
6345
6363
  return { ...candidate, feasible: true };
6346
6364
  }
6347
- return { ...candidate, feasible: false, reasons: [`5h window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(fiveHour.untilResetMs)}`, ...candidate.reasons] };
6365
+ return { ...candidate, feasible: false, reasons: [...drySpots, ...candidate.reasons] };
6348
6366
  });
6349
6367
  const ordered = input.preference === "listed" ? [...candidates] : [...candidates].sort(
6350
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)
@@ -6352,7 +6370,8 @@ function rankPool(input) {
6352
6370
  const sticky = input.sticky;
6353
6371
  const stickyIndex = sticky === void 0 ? -1 : ordered.findIndex((candidate) => candidate.identity === sticky.identity);
6354
6372
  const stickyCandidate = ordered[stickyIndex];
6355
- if (sticky !== void 0 && stickyCandidate?.class === "scored" && stickyCandidate.feasible) {
6373
+ const keepSticky = stickyCandidate?.class === "scored" && stickyCandidate.feasible;
6374
+ if (sticky !== void 0 && keepSticky) {
6356
6375
  const ageMs = nowMs - Date.parse(sticky.at);
6357
6376
  const warm = ageMs <= PROMPT_CACHE_TTL_MS;
6358
6377
  if (input.resuming || warm) {
@@ -6361,12 +6380,14 @@ function rankPool(input) {
6361
6380
  ordered.unshift({ ...stickyCandidate, reasons: [why, ...stickyCandidate.reasons] });
6362
6381
  }
6363
6382
  }
6383
+ 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
6384
  const pick = ordered.find((candidate) => candidate.class !== "ineligible");
6365
6385
  const returns = ordered.flatMap((candidate) => candidate.blockedUntilMs === void 0 ? [] : [{ identity: candidate.identity, atMs: candidate.blockedUntilMs }]);
6366
6386
  const earliestReturn = returns.length === 0 ? void 0 : returns.reduce((soonest, entry) => entry.atMs < soonest.atMs ? entry : soonest);
6367
6387
  return {
6368
6388
  candidates: ordered,
6369
- ...pick === void 0 ? { ...earliestReturn === void 0 ? {} : { earliestReturn } } : { pick }
6389
+ ...pick === void 0 ? { ...earliestReturn === void 0 ? {} : { earliestReturn } } : { pick },
6390
+ ...movedOff === void 0 || pick === void 0 ? {} : { movedOff }
6370
6391
  };
6371
6392
  }
6372
6393
 
@@ -6624,6 +6645,7 @@ function collectPoolPick(params) {
6624
6645
  candidates: ranking.candidates.map(candidateView),
6625
6646
  missing: missing.map(({ identity }) => identity),
6626
6647
  ...ranking.earliestReturn === void 0 ? {} : { earliestReturn: { identity: ranking.earliestReturn.identity, at: new Date(ranking.earliestReturn.atMs).toISOString() } },
6648
+ ...ranking.movedOff === void 0 ? {} : { movedOff: ranking.movedOff },
6627
6649
  ...sticky.problem === void 0 ? {} : { stickyProblem: sticky.problem }
6628
6650
  };
6629
6651
  }
@@ -8895,6 +8917,8 @@ var PoolPickReportSchema = z13.strictObject({
8895
8917
  missing: z13.array(z13.string()),
8896
8918
  /** When nothing can be picked, the soonest any member returns. */
8897
8919
  earliestReturn: z13.strictObject({ identity: z13.string(), at: z13.iso.datetime() }).optional(),
8920
+ /** The member a resumed conversation started on but moved off, and why, when the ranking could not keep it. */
8921
+ movedOff: z13.strictObject({ identity: z13.string(), reason: z13.string() }).optional(),
8898
8922
  /** Why the last-pick record could not be read, when that is the case. */
8899
8923
  stickyProblem: z13.string().optional()
8900
8924
  });
@@ -12641,10 +12665,11 @@ function resolvePoolLaunch(params) {
12641
12665
  if (graph.ranking.candidates.length === 0) {
12642
12666
  return { ok: false, message: `agent-shim: no member of pool "${poolName}" is an existing identity.` };
12643
12667
  }
12644
- const { pick, earliestReturn } = graph.ranking;
12668
+ const { pick, earliestReturn, movedOff } = graph.ranking;
12645
12669
  if (pick !== void 0) {
12646
12670
  recordStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd, pick.identity, nowMs);
12647
- return { ok: true, identity: pick.identity, explanation: `pool ${poolName}: ${pick.reasons.slice(0, REASONS_LOGGED).join("; ")}` };
12671
+ const move = movedOff === void 0 ? "" : `moved off ${movedOff.identity} (${movedOff.reason}); `;
12672
+ return { ok: true, identity: pick.identity, explanation: `pool ${poolName}: ${move}${pick.reasons.slice(0, REASONS_LOGGED).join("; ")}` };
12648
12673
  }
12649
12674
  if (earliestReturn === void 0) {
12650
12675
  return { ok: false, message: `agent-shim: no member of pool "${poolName}" can be picked.` };
@@ -2816,6 +2816,10 @@ declare const CheckReportJsonSchema: z.ZodObject<{
2816
2816
  identity: z.ZodString;
2817
2817
  at: z.ZodISODateTime;
2818
2818
  }, z.core.$strict>>;
2819
+ movedOff: z.ZodOptional<z.ZodObject<{
2820
+ identity: z.ZodString;
2821
+ reason: z.ZodString;
2822
+ }, z.core.$strict>>;
2819
2823
  stickyProblem: z.ZodOptional<z.ZodString>;
2820
2824
  }, z.core.$strict>>;
2821
2825
  configProfile: z.ZodObject<{
@@ -3293,6 +3297,10 @@ declare const PoolPickReportSchema: z.ZodObject<{
3293
3297
  identity: z.ZodString;
3294
3298
  at: z.ZodISODateTime;
3295
3299
  }, z.core.$strict>>;
3300
+ movedOff: z.ZodOptional<z.ZodObject<{
3301
+ identity: z.ZodString;
3302
+ reason: z.ZodString;
3303
+ }, z.core.$strict>>;
3296
3304
  stickyProblem: z.ZodOptional<z.ZodString>;
3297
3305
  }, z.core.$strict>;
3298
3306
  type PoolPickReport = z.infer<typeof PoolPickReportSchema>;
@@ -4240,6 +4248,10 @@ declare function createControlApiRouter(deps: ControlApiDeps): {
4240
4248
  identity: zod.ZodString;
4241
4249
  at: zod.ZodISODateTime;
4242
4250
  }, zod_v4_core.$strict>>;
4251
+ movedOff: zod.ZodOptional<zod.ZodObject<{
4252
+ identity: zod.ZodString;
4253
+ reason: zod.ZodString;
4254
+ }, zod_v4_core.$strict>>;
4243
4255
  stickyProblem: zod.ZodOptional<zod.ZodString>;
4244
4256
  }, zod_v4_core.$strict>>;
4245
4257
  configProfile: zod.ZodObject<{
@@ -4557,6 +4569,10 @@ declare function createControlApiRouter(deps: ControlApiDeps): {
4557
4569
  identity: zod.ZodString;
4558
4570
  at: zod.ZodISODateTime;
4559
4571
  }, zod_v4_core.$strict>>;
4572
+ movedOff: zod.ZodOptional<zod.ZodObject<{
4573
+ identity: zod.ZodString;
4574
+ reason: zod.ZodString;
4575
+ }, zod_v4_core.$strict>>;
4560
4576
  stickyProblem: zod.ZodOptional<zod.ZodString>;
4561
4577
  }, zod_v4_core.$strict>, Record<never, never>, Record<never, never>>;
4562
4578
  };
@@ -4813,6 +4829,10 @@ declare function createDoorApiRouter(deps: DoorApiDeps): {
4813
4829
  identity: zod.ZodString;
4814
4830
  at: zod.ZodISODateTime;
4815
4831
  }, zod_v4_core.$strict>>;
4832
+ movedOff: zod.ZodOptional<zod.ZodObject<{
4833
+ identity: zod.ZodString;
4834
+ reason: zod.ZodString;
4835
+ }, zod_v4_core.$strict>>;
4816
4836
  stickyProblem: zod.ZodOptional<zod.ZodString>;
4817
4837
  }, zod_v4_core.$strict>>;
4818
4838
  configProfile: zod.ZodObject<{
@@ -5130,6 +5150,10 @@ declare function createDoorApiRouter(deps: DoorApiDeps): {
5130
5150
  identity: zod.ZodString;
5131
5151
  at: zod.ZodISODateTime;
5132
5152
  }, zod_v4_core.$strict>>;
5153
+ movedOff: zod.ZodOptional<zod.ZodObject<{
5154
+ identity: zod.ZodString;
5155
+ reason: zod.ZodString;
5156
+ }, zod_v4_core.$strict>>;
5133
5157
  stickyProblem: zod.ZodOptional<zod.ZodString>;
5134
5158
  }, zod_v4_core.$strict>, Record<never, never>, Record<never, never>>;
5135
5159
  };
@@ -6065,7 +6089,7 @@ interface Candidate {
6065
6089
  readonly class: CandidateClass;
6066
6090
  /** Plan-size-weighted remaining quota per hour until its reset: higher is more wasted if left unused. Present for `scored` only. */
6067
6091
  readonly score?: number;
6068
- /** False when the five-hour window would run dry, at the observed pace, before it resets. A `scored` candidate that is not feasible ranks below every feasible one. */
6092
+ /** False when a quota window would run dry, at the observed pace, before it resets (the five-hour at its own or the rescaled shared pace, the seven-day at its own). A `scored` candidate that is not feasible ranks below every feasible one. */
6069
6093
  readonly feasible: boolean;
6070
6094
  /** When an `ineligible` candidate can be used again. */
6071
6095
  readonly blockedUntilMs?: number;
@@ -6083,11 +6107,16 @@ interface PoolRanking {
6083
6107
  readonly identity: string;
6084
6108
  readonly atMs: number;
6085
6109
  };
6110
+ /** The member a resumed or continued conversation started on but moved off, and why, when a launch that resumes could not keep it. One sentence for the launch decision line and `check`. */
6111
+ readonly movedOff?: {
6112
+ readonly identity: string;
6113
+ readonly reason: string;
6114
+ };
6086
6115
  }
6087
6116
  /**
6088
6117
  * Ranks `members` for a launch.
6089
6118
  *
6090
- * A member is `ineligible` while a window of its plan, or a refusal it last hit, still binds; it is `unknown` without recorded quota, `pay-per-use` when its usage bills by use, and otherwise `scored`. Scored members order by how much plan-size-weighted quota would expire unused per hour of runway, with any whose five-hour window would run dry at the observed pace (their own, or the person's pace on another member rescaled by plan size) placed behind those that would not. The member last picked for this directory then moves to the front if it is still usable and either the launch resumes a conversation or its prompt cache is still warm.
6119
+ * A member is `ineligible` while a window of its plan, or a refusal it last hit, still binds; it is `unknown` without recorded quota, `pay-per-use` when its usage bills by use, and otherwise `scored`. Scored members order by how much plan-size-weighted quota would expire unused per hour of runway, with any whose five-hour window would run dry at the observed pace (their own, or the person's pace on another member rescaled by plan size) or whose seven-day window would run dry at its own observed pace placed behind those that would not. The member last picked for this directory then moves to the front if it still serves on plan quota and is not projected to run dry, and either the launch resumes a conversation or its prompt cache is still warm; a resumed conversation that cannot keep its member re-ranks and continues on the pick, with the move named in `movedOff`.
6091
6120
  *
6092
6121
  * How members are ordered depends on `preference`. The default, "score", is the order above: class first, then feasibility, then score, with ties by name. "listed" keeps the order of `members`, which callers build in pool member order, so member order, not class, decides: an `unknown` (no usage recorded) or `pay-per-use` member can be picked before a later `scored` one, which is the list owner's stated preference, and a `scored` member that is not feasible keeps its place and its reason line rather than being demoted. In both modes the pick is the first non-`ineligible` candidate, `earliestReturn` still reports the soonest returning refused member, and the sticky-pick promotion applies unchanged.
6093
6122
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-shim",
3
- "version": "8.31.0",
3
+ "version": "8.32.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>",