@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.
- package/dist/server.js +71 -1
- 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: () =>
|
|
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