@bivy/bivy 0.12.0-staging.404 → 0.12.0-staging.406

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.
Files changed (2) hide show
  1. package/dist/server.js +43 -9
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -2878,6 +2878,17 @@ const RELAY_COMMANDS = {
2878
2878
  });
2879
2879
  return;
2880
2880
  }
2881
+ const remoteSessionRequestId = requestId ?? randomUUID();
2882
+ const sessionAdmission = await admitRelaySessionCreate(remoteSessionRequestId);
2883
+ if (!sessionAdmission.allowed) {
2884
+ relay?.sendEvent({
2885
+ type: "session.error",
2886
+ code: sessionAdmission.code || "remote_session_limit",
2887
+ error: sessionAdmission.error,
2888
+ requestId,
2889
+ });
2890
+ return;
2891
+ }
2881
2892
  let record;
2882
2893
  try {
2883
2894
  // Deduped by requestId so a client's post-reconnect retry adopts the
@@ -3131,6 +3142,24 @@ async function modelAuthFetch(pathname, init = {}) {
3131
3142
  headers.set("content-type", "application/json");
3132
3143
  return fetch(`${sessionAdvertiseTarget.controlPlaneUrl.replace(/\/$/, "")}${pathname}`, { ...init, headers });
3133
3144
  }
3145
+ async function admitRelaySessionCreate(idempotencyKey) {
3146
+ // Self-hosted/direct deployments without an account extension stay unrestricted:
3147
+ // the control plane answers allowed when no deployment extension is configured.
3148
+ const res = await modelAuthFetch("/node/policy/check", {
3149
+ method: "POST",
3150
+ body: JSON.stringify({ operation: "session.create", idempotencyKey }),
3151
+ });
3152
+ if (!res)
3153
+ return { allowed: true };
3154
+ const decision = await res.json().catch(() => ({}));
3155
+ if (res.ok && decision.allowed !== false)
3156
+ return { allowed: true };
3157
+ return {
3158
+ allowed: false,
3159
+ code: decision.code,
3160
+ error: decision.reason || decision.error || "This account has reached its remote session allowance.",
3161
+ };
3162
+ }
3134
3163
  // Debounced model-auth sync trigger. A relay wake (`work.available`) fires this
3135
3164
  // so peers answer a new node's vault-key request promptly (event-driven) instead
3136
3165
  // of on the steady 30s poll. Coalesces a burst of wakes into one sync.
@@ -4596,18 +4625,23 @@ function invalidateGitHubApps() {
4596
4625
  // token nor an app key. When BIVY_HOSTED_MINT is set they mint a fresh,
4597
4626
  // short-lived token from the control plane per git op — cached until ~5 min
4598
4627
  // before expiry so a burst of git ops is a single round trip. This is the final
4599
- // fallback rung after local apps and BIVY_GITHUB_TOKEN.
4600
- let hostedMintCache;
4601
- async function hostedMintToken() {
4628
+ // fallback rung after local apps and BIVY_GITHUB_TOKEN. Passing the repo lets
4629
+ // the control plane pick the matching central-app installation and scope the
4630
+ // token down to that repo, so the cache is keyed per repo.
4631
+ const hostedMintCache = new Map();
4632
+ async function hostedMintToken(repo) {
4602
4633
  if (!process.env.BIVY_HOSTED_MINT || !sessionAdvertiseTarget)
4603
4634
  return undefined;
4635
+ const cacheKey = repo ?? "";
4604
4636
  const now = Date.now();
4605
- if (hostedMintCache && hostedMintCache.expiresAt - now > 5 * 60 * 1000)
4606
- return hostedMintCache.token;
4637
+ const cached = hostedMintCache.get(cacheKey);
4638
+ if (cached && cached.expiresAt - now > 5 * 60 * 1000)
4639
+ return cached.token;
4607
4640
  try {
4608
4641
  const res = await fetch(`${sessionAdvertiseTarget.controlPlaneUrl.replace(/\/$/, "")}/node/hosted-git-credential`, {
4609
4642
  method: "POST",
4610
- headers: { authorization: `Bearer ${sessionAdvertiseTarget.enrollmentToken}` },
4643
+ headers: { authorization: `Bearer ${sessionAdvertiseTarget.enrollmentToken}`, "content-type": "application/json" },
4644
+ body: JSON.stringify(repo ? { repo } : {}),
4611
4645
  });
4612
4646
  if (!res.ok)
4613
4647
  return undefined;
@@ -4615,7 +4649,7 @@ async function hostedMintToken() {
4615
4649
  if (!data.token)
4616
4650
  return undefined;
4617
4651
  const parsed = data.expiresAt ? Date.parse(data.expiresAt) : NaN;
4618
- hostedMintCache = { token: data.token, expiresAt: Number.isFinite(parsed) ? parsed : now + 55 * 60 * 1000 };
4652
+ hostedMintCache.set(cacheKey, { token: data.token, expiresAt: Number.isFinite(parsed) ? parsed : now + 55 * 60 * 1000 });
4619
4653
  return data.token;
4620
4654
  }
4621
4655
  catch {
@@ -4639,7 +4673,7 @@ async function resolveTokenForWorkItem(item) {
4639
4673
  }
4640
4674
  }
4641
4675
  }
4642
- return (await resolveGitHubToken()) ?? (await hostedMintToken());
4676
+ return (await resolveGitHubToken()) ?? (await hostedMintToken(item.repo));
4643
4677
  }
4644
4678
  /**
4645
4679
  * The token for interactive repo operations (clone/fetch/push/PR) on `owner/repo`.
@@ -4687,7 +4721,7 @@ async function resolveTokenForRepo(owner, repo) {
4687
4721
  }
4688
4722
  }
4689
4723
  }
4690
- return (await resolveGitHubToken()) ?? (await hostedMintToken());
4724
+ return (await resolveGitHubToken()) ?? (await hostedMintToken(`${owner}/${repo}`));
4691
4725
  }
4692
4726
  /** The session source a Linear-issue pickup advertises, keyed by the issue's
4693
4727
  * provider-native id so the control plane can correlate a re-dispatch to it
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.12.0-staging.404",
3
+ "version": "0.12.0-staging.406",
4
4
  "type": "module",
5
5
  "license": "AGPL-3.0-only",
6
6
  "description": "Run coding agents on machines you own. Open-source, self-hostable agent workspace.",