@bivy/bivy 0.3.0-staging.41 → 0.3.0-staging.43

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 +71 -1
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -3420,7 +3420,14 @@ function startRelayIfConfigured() {
3420
3420
  terminals.dropClient(RELAY_CLIENT_ID);
3421
3421
  relay = new RelayConnector(config, (msg) => void handleRelayMessage(msg), {
3422
3422
  pairing: pairingStore,
3423
- onWorkAvailable: () => controlPlanePoller?.poke(),
3423
+ onWorkAvailable: () => {
3424
+ controlPlanePoller?.poke();
3425
+ // A relay wake also means "something changed for this account" — kick a
3426
+ // (debounced) model-auth sync so a peer node answers any pending vault-key
3427
+ // request from a freshly-launched ephemeral runner without waiting for its
3428
+ // 30s poll. Cheap and idempotent; coalesced to at most one sync per burst.
3429
+ triggerModelAuthSyncSoon();
3430
+ },
3424
3431
  });
3425
3432
  relay.start();
3426
3433
  if (config.controlPlaneUrl && config.enrollmentToken) {
@@ -3520,6 +3527,62 @@ async function modelAuthFetch(pathname, init = {}) {
3520
3527
  headers.set("content-type", "application/json");
3521
3528
  return fetch(`${sessionAdvertiseTarget.controlPlaneUrl.replace(/\/$/, "")}${pathname}`, { ...init, headers });
3522
3529
  }
3530
+ // Debounced model-auth sync trigger. A relay wake (`work.available`) fires this
3531
+ // so peers answer a new node's vault-key request promptly (event-driven) instead
3532
+ // of on the steady 30s poll. Coalesces a burst of wakes into one sync.
3533
+ let modelAuthSyncSoonTimer;
3534
+ function triggerModelAuthSyncSoon() {
3535
+ if (modelAuthSyncSoonTimer)
3536
+ return;
3537
+ modelAuthSyncSoonTimer = setTimeout(() => {
3538
+ modelAuthSyncSoonTimer = undefined;
3539
+ void syncModelAuthFromControlPlane();
3540
+ }, 250);
3541
+ modelAuthSyncSoonTimer.unref?.();
3542
+ }
3543
+ // Cold-start fast-retry. A freshly-launched node (typically a short-lived
3544
+ // ephemeral runner) that holds vault ciphertext but no wrapped key yet must wait
3545
+ // for a peer node to answer its key request. The 30s steady poll is too slow for
3546
+ // a machine that may only live a minute, so once we've requested the key we
3547
+ // re-sync on a brief bounded cadence until the wrapped key arrives (a peer
3548
+ // answered) or we give up and let the steady poll continue. Peer-only by design:
3549
+ // the key is always answered by another node over the E2E wrap — nothing ever
3550
+ // transits the device or control plane in the clear.
3551
+ const MODEL_AUTH_COLDSTART_INTERVAL_MS = 2_000;
3552
+ const MODEL_AUTH_COLDSTART_MAX_ATTEMPTS = 30; // ~60s bounded
3553
+ let modelAuthColdStartActive = false;
3554
+ let modelAuthColdStartAttempts = 0;
3555
+ let modelAuthColdStartTimer;
3556
+ function stopModelAuthColdStart() {
3557
+ modelAuthColdStartActive = false;
3558
+ modelAuthColdStartAttempts = 0;
3559
+ if (modelAuthColdStartTimer) {
3560
+ clearTimeout(modelAuthColdStartTimer);
3561
+ modelAuthColdStartTimer = undefined;
3562
+ }
3563
+ }
3564
+ function ensureModelAuthColdStart() {
3565
+ if (modelAuthColdStartActive)
3566
+ return; // already retrying
3567
+ modelAuthColdStartActive = true;
3568
+ modelAuthColdStartAttempts = 0;
3569
+ const tick = () => {
3570
+ modelAuthColdStartTimer = undefined;
3571
+ if (!modelAuthColdStartActive)
3572
+ return;
3573
+ // A concurrent sync may have already landed the key — stop as soon as we have it.
3574
+ if (readLocalModelAuthVaultKey() || modelAuthColdStartAttempts >= MODEL_AUTH_COLDSTART_MAX_ATTEMPTS) {
3575
+ stopModelAuthColdStart();
3576
+ return;
3577
+ }
3578
+ modelAuthColdStartAttempts++;
3579
+ void syncModelAuthFromControlPlane();
3580
+ modelAuthColdStartTimer = setTimeout(tick, MODEL_AUTH_COLDSTART_INTERVAL_MS);
3581
+ modelAuthColdStartTimer.unref?.();
3582
+ };
3583
+ modelAuthColdStartTimer = setTimeout(tick, MODEL_AUTH_COLDSTART_INTERVAL_MS);
3584
+ modelAuthColdStartTimer.unref?.();
3585
+ }
3523
3586
  async function syncModelAuthFromControlPlane() {
3524
3587
  if (!sessionAdvertiseTarget)
3525
3588
  return;
@@ -3543,10 +3606,17 @@ async function syncModelAuthFromControlPlane() {
3543
3606
  await writePiModelsProjection();
3544
3607
  await broadcastLocalModels();
3545
3608
  lastPushedModelAuthCiphertext = data.vault.ciphertext;
3609
+ // Got the key and imported the vault (incl. any subscription-OAuth logins) —
3610
+ // the cold-start race is over.
3611
+ stopModelAuthColdStart();
3546
3612
  broadcast({ type: "providers.list", providers: await listProvidersUnified() });
3547
3613
  }
3548
3614
  else if (data.vault?.ciphertext && !vaultKeyB64) {
3549
3615
  await modelAuthFetch("/node/model-auth-key/request", { method: "POST", body: JSON.stringify({ publicKey: pairingStore.nodePublicKeyB64() }) });
3616
+ // No peer has wrapped our key yet. Fast-retry (bounded) so a short-lived
3617
+ // ephemeral runner picks up the key within seconds of a peer answering,
3618
+ // rather than waiting for its next 30s poll.
3619
+ ensureModelAuthColdStart();
3550
3620
  }
3551
3621
  else if (Object.keys(await exportProviderAuth(credsDir)).length > 0 ||
3552
3622
  Object.keys(exportLocalModels(localModelsDir)).length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.3.0-staging.41",
3
+ "version": "0.3.0-staging.43",
4
4
  "type": "module",
5
5
  "license": "FSL-1.1-ALv2",
6
6
  "description": "Run coding agents on machines you own. Source-available, self-hostable agent workspace.",