@bivy/bivy 0.6.0-staging.82 → 0.6.0-staging.83
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 +72 -21
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -535,7 +535,9 @@ if (sessionRunPolicy) {
|
|
|
535
535
|
console.log(`[policy] in-session model reroute enabled: ${process.env.BIVY_SESSION_MODEL_FALLBACK}`);
|
|
536
536
|
}
|
|
537
537
|
let lastUpdateCheckAt = 0;
|
|
538
|
-
|
|
538
|
+
// The most recent "this node is behind" finding, so a client that connects after
|
|
539
|
+
// the check already ran still gets the banner (replayed on connect below).
|
|
540
|
+
let pendingBivyUpdate = null;
|
|
539
541
|
function runtimeSummary(rt) {
|
|
540
542
|
return runtimeHost.summary(rt);
|
|
541
543
|
}
|
|
@@ -625,11 +627,11 @@ function readJsonFile(file) {
|
|
|
625
627
|
return undefined;
|
|
626
628
|
}
|
|
627
629
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
630
|
+
// Poll npm for a newer release (throttled to every 6h). On finding one, remember
|
|
631
|
+
// it and push a dedicated `node.update` event so every connected app can show a
|
|
632
|
+
// banner with a one-tap "Update this node" button (see runBivyUpdate). Safe to
|
|
633
|
+
// call from anywhere — never throws, never interrupts a session.
|
|
634
|
+
async function checkBivyUpdate() {
|
|
633
635
|
const now = Date.now();
|
|
634
636
|
if (now - lastUpdateCheckAt < 6 * 60 * 60 * 1000)
|
|
635
637
|
return;
|
|
@@ -641,25 +643,48 @@ async function maybeNotifyBivyUpdate(record) {
|
|
|
641
643
|
const res = await fetch(updateRegistryUrl, { signal: AbortSignal.timeout(5000) });
|
|
642
644
|
if (!res.ok)
|
|
643
645
|
return;
|
|
644
|
-
const
|
|
645
|
-
if (!
|
|
646
|
+
const latest = (await res.json()).version;
|
|
647
|
+
if (!latest || !isNewerVersion(latest, current))
|
|
646
648
|
return;
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
updateNoticeSentFor = latestVersion;
|
|
650
|
-
const label = ` ${latestVersion}`;
|
|
651
|
-
broadcast({
|
|
652
|
-
type: "session.notice",
|
|
653
|
-
sessionId: record.id,
|
|
654
|
-
level: "info",
|
|
655
|
-
message: `A newer Bivy version${label} is available. Run \`bivy update\` in your terminal to update.`,
|
|
656
|
-
action: "bivy update",
|
|
657
|
-
});
|
|
649
|
+
pendingBivyUpdate = { current, latest };
|
|
650
|
+
broadcast({ type: "node.update", current, latest });
|
|
658
651
|
}
|
|
659
652
|
catch {
|
|
660
653
|
// Best-effort update checks should never interrupt a session.
|
|
661
654
|
}
|
|
662
655
|
}
|
|
656
|
+
async function maybeNotifyBivyUpdate() {
|
|
657
|
+
// The daemon creates an initial session during startup before any UI is
|
|
658
|
+
// connected. Don't spend a check until someone can see the banner.
|
|
659
|
+
if (clients.size === 0 && !relay)
|
|
660
|
+
return;
|
|
661
|
+
await checkBivyUpdate();
|
|
662
|
+
}
|
|
663
|
+
// Run `bivy update` on this node, the same command a user would type. The CLI
|
|
664
|
+
// re-spawns itself detached, waits for any in-flight turn, updates, and restarts
|
|
665
|
+
// the service (logging to update.log), so we just fire-and-forget it here. The
|
|
666
|
+
// bin ships next to this server bundle in both the git checkout (src/server.ts)
|
|
667
|
+
// and the published package (dist/server.js), so repoRoot/bin/bivy.mjs resolves
|
|
668
|
+
// in both. Returns a friendly error instead of throwing when it can't be found
|
|
669
|
+
// (e.g. an unusual layout), so the banner can fall back to the manual command.
|
|
670
|
+
function runBivyUpdate() {
|
|
671
|
+
const script = path.join(repoRoot, "bin", "bivy.mjs");
|
|
672
|
+
if (!fs.existsSync(script)) {
|
|
673
|
+
return { ok: false, error: "Could not locate the bivy CLI on this node — run `bivy update` in a terminal." };
|
|
674
|
+
}
|
|
675
|
+
try {
|
|
676
|
+
const child = spawn(process.execPath, [script, "update"], {
|
|
677
|
+
detached: true,
|
|
678
|
+
stdio: "ignore",
|
|
679
|
+
env: process.env,
|
|
680
|
+
});
|
|
681
|
+
child.unref();
|
|
682
|
+
return { ok: true };
|
|
683
|
+
}
|
|
684
|
+
catch (error) {
|
|
685
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
686
|
+
}
|
|
687
|
+
}
|
|
663
688
|
function runtimeInstallSpec(requested) {
|
|
664
689
|
let id = String(requested ?? "").trim().toLowerCase();
|
|
665
690
|
// Normalize a few historical aliases to their canonical runtime id.
|
|
@@ -2597,6 +2622,14 @@ const RELAY_COMMANDS = {
|
|
|
2597
2622
|
ping(msg, ctx) {
|
|
2598
2623
|
ctx.reply({ type: "pong", requestId: typeof msg.requestId === "string" ? msg.requestId : undefined });
|
|
2599
2624
|
},
|
|
2625
|
+
// Kick off `bivy update` on this node from the app's version-mismatch banner
|
|
2626
|
+
// (see runBivyUpdate). The node restarts itself when the update lands, so the
|
|
2627
|
+
// client just sees the socket reconnect on the new build; a failure to even
|
|
2628
|
+
// start reports back so the banner can show the manual command.
|
|
2629
|
+
"node.update"(_msg, ctx) {
|
|
2630
|
+
const result = runBivyUpdate();
|
|
2631
|
+
ctx.reply({ type: "node.update.result", ok: result.ok, error: result.error });
|
|
2632
|
+
},
|
|
2600
2633
|
// Fetch a stored attachment's bytes by content hash. The relay client (a phone
|
|
2601
2634
|
// not on the LAN) can't reach the GET /api/attachment endpoint, so it fetches
|
|
2602
2635
|
// over the encrypted tunnel instead; the relay framing chunks the base64 payload
|
|
@@ -7640,7 +7673,7 @@ async function createSession(workspace = defaultWorkspace, sessionFile, opts = {
|
|
|
7640
7673
|
if (makeActive)
|
|
7641
7674
|
active = existing;
|
|
7642
7675
|
broadcast({ type: "session.created", sessionId: existing.id, name: existing.session.getName(), workspace: existing.workspace, sessionFile: existing.sessionFile, source: existing.source, branch: existing.worktree?.branch, prUrl: existing.prUrl, runtimeId: existing.runtimeId, agentName: getRuntime(existing.runtimeId).displayName, bivySession: bivySessionEnvelope(existing), capabilities: capabilitiesWithCommands(existing.runtimeId, existing.session) });
|
|
7643
|
-
void maybeNotifyBivyUpdate(
|
|
7676
|
+
void maybeNotifyBivyUpdate();
|
|
7644
7677
|
return existing;
|
|
7645
7678
|
}
|
|
7646
7679
|
// Pick the agent for this session (fixed for its life). Resuming a tagged
|
|
@@ -7829,7 +7862,7 @@ async function createSession(workspace = defaultWorkspace, sessionFile, opts = {
|
|
|
7829
7862
|
if (makeActive)
|
|
7830
7863
|
active = record;
|
|
7831
7864
|
broadcast({ type: "session.created", sessionId, name: record.session.getName(), workspace: sessionWorkspace, sessionFile: record.sessionFile, source: record.source, branch: worktree?.branch, prUrl: record.prUrl, runtimeId: rt.id, agentName: rt.displayName, modelFallbackMessage, bivySession: bivySessionEnvelope(record), capabilities: capabilitiesWithCommands(rt.id, record.session) });
|
|
7832
|
-
void maybeNotifyBivyUpdate(
|
|
7865
|
+
void maybeNotifyBivyUpdate();
|
|
7833
7866
|
scheduleAdvertise();
|
|
7834
7867
|
return record;
|
|
7835
7868
|
}
|
|
@@ -8544,6 +8577,16 @@ app.get("/api/node/info", (_req, res) => {
|
|
|
8544
8577
|
sandbox: sandboxInfo(),
|
|
8545
8578
|
});
|
|
8546
8579
|
});
|
|
8580
|
+
// One-tap "Update this node" from the app's version-mismatch banner, for
|
|
8581
|
+
// direct/LAN clients (the relay path uses the RELAY_COMMANDS "node.update"
|
|
8582
|
+
// handler). Both call the same runBivyUpdate.
|
|
8583
|
+
app.post("/api/node/update", (_req, res) => {
|
|
8584
|
+
const result = runBivyUpdate();
|
|
8585
|
+
if (result.ok)
|
|
8586
|
+
res.json({ ok: true });
|
|
8587
|
+
else
|
|
8588
|
+
res.status(500).json({ ok: false, error: result.error });
|
|
8589
|
+
});
|
|
8547
8590
|
// Build collectNodeStats() options, resolving the optional session so the panel
|
|
8548
8591
|
// can attribute a session-scoped tier (its live agent process + workspace size).
|
|
8549
8592
|
function nodeStatsOptsFor(sessionId) {
|
|
@@ -10444,6 +10487,14 @@ wss.on("connection", (socket, req) => {
|
|
|
10444
10487
|
// sharing a PTY size it to their min (see TerminalManager.setClientSize).
|
|
10445
10488
|
const clientTerminalId = `sock-${randomUUID()}`;
|
|
10446
10489
|
socket.send(JSON.stringify({ type: "hello", activeSessionId: active?.id, activeSession: active ? { id: active.id, isStreaming: sessionBusy(active), lastActivity: active.lastActivity, workingStartedAt: active.workingStartedAt } : null }));
|
|
10490
|
+
// Authoritative version status on every connect: `latest` set means this node
|
|
10491
|
+
// is behind (banner shows); absent means up to date (banner + any "Updating…"
|
|
10492
|
+
// state clear — this is how the banner disappears after an update lands and
|
|
10493
|
+
// the socket reconnects on the new build). Then (re)run the throttled check so
|
|
10494
|
+
// a freshly-opened app surfaces a newly-available update without waiting for a
|
|
10495
|
+
// session turn.
|
|
10496
|
+
socket.send(JSON.stringify({ type: "node.update", current: currentVersion() ?? "", latest: pendingBivyUpdate?.latest }));
|
|
10497
|
+
void checkBivyUpdate();
|
|
10447
10498
|
socket.on("message", (raw) => {
|
|
10448
10499
|
let msg;
|
|
10449
10500
|
try {
|
package/package.json
CHANGED