@bivy/bivy 0.3.0-staging.45 → 0.3.0-staging.46

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.
@@ -0,0 +1,19 @@
1
+ // SPDX-License-Identifier: FSL-1.1-ALv2
2
+ // Copyright (c) 2026 Petter André Sjulstad
3
+ //
4
+ // Pure decision for sweeping stale browser-initiated OAuth logins (see the
5
+ // `oauthLogins` registry in src/server.ts). A login parks the node until the
6
+ // remote device pastes its code; an abandoned one must be reaped (aborting it
7
+ // closes the local callback http.Server) and a finished one dropped after a
8
+ // short grace so clients can still read its final status. Kept pure + isolated
9
+ // so the edge logic is unit-tested without importing the daemon.
10
+ export function isTerminalOAuthStatus(status) {
11
+ return status === "done" || status === "error";
12
+ }
13
+ export function decideOAuthLoginSweep(status, ageMs, opts) {
14
+ const terminal = isTerminalOAuthStatus(status);
15
+ const expired = terminal ? ageMs > opts.graceMs : ageMs > opts.ttlMs;
16
+ if (!expired)
17
+ return { drop: false, abort: false };
18
+ return { drop: true, abort: !terminal };
19
+ }
package/dist/server.js CHANGED
@@ -27,6 +27,7 @@ import { provisionAgentRun } from "./runtime/credential-provisioning.js";
27
27
  import { ingestAgentCredentials } from "./runtime/credential-ingest.js";
28
28
  import { suggestNameFromSelectedModel } from "./runtime/model-namer.js";
29
29
  import { isNativeOAuthProvider, loginModelOAuth } from "./runtime/oauth/model-oauth.js";
30
+ import { decideOAuthLoginSweep } from "./runtime/oauth/oauth-login-sweep.js";
30
31
  import { listCodexSessions, loadCodexTranscript, discoverCodexSessionForCwd } from "./runtime/codex-sessions.js";
31
32
  import { dedupeSessionSummaries } from "./session-identity.js";
32
33
  import { discoverPiSessionForCwd } from "./runtime/pi-session-discovery.js";
@@ -755,6 +756,40 @@ let relay;
755
756
  const clients = new Set();
756
757
  const commandProcesses = new Map();
757
758
  const oauthLogins = new Map();
759
+ // A browser-initiated subscription login parks the node on `manualCodePromise`
760
+ // until the remote device pastes the code (`provider.oauth.code`). If the user
761
+ // abandons it, the entry — AND its local callback http.Server — would otherwise
762
+ // linger until the process exits. That matters especially on a short-lived
763
+ // ephemeral node. Sweep periodically: abort (which closes the callback server,
764
+ // see startCallbackServer) + drop any in-flight login past its TTL, and drop a
765
+ // finished one after a short grace so clients can still read the final status.
766
+ const OAUTH_LOGIN_TTL_MS = 10 * 60_000;
767
+ const OAUTH_LOGIN_DONE_GRACE_MS = 2 * 60_000;
768
+ function sweepOauthLogins(now = Date.now()) {
769
+ for (const [id, login] of oauthLogins.entries()) {
770
+ const { drop, abort } = decideOAuthLoginSweep(login.status, now - login.createdAt, {
771
+ ttlMs: OAUTH_LOGIN_TTL_MS,
772
+ graceMs: OAUTH_LOGIN_DONE_GRACE_MS,
773
+ });
774
+ if (!drop)
775
+ continue;
776
+ if (abort) {
777
+ login.cancelled = true;
778
+ try {
779
+ login.abort.abort();
780
+ }
781
+ catch { /* already settled */ }
782
+ }
783
+ oauthLogins.delete(id);
784
+ }
785
+ }
786
+ let oauthLoginSweepTimer;
787
+ function startOAuthLoginSweeper() {
788
+ if (oauthLoginSweepTimer)
789
+ return;
790
+ oauthLoginSweepTimer = setInterval(() => sweepOauthLogins(), 60_000);
791
+ oauthLoginSweepTimer.unref?.();
792
+ }
758
793
  const openSessions = new Map();
759
794
  // Stage 2 (docs/agent-node-decoupling.md): sessionId -> agent-service address for
760
795
  // live REMOTE sessions the agent service keeps running across an eviction/
@@ -5140,6 +5175,9 @@ async function startOAuthLogin(provider) {
5140
5175
  // OpenAI's browser flow redirects to http://localhost:1455. Listen on IPv6
5141
5176
  // wildcard so browsers resolving localhost to ::1 can reach the callback.
5142
5177
  process.env.PI_OAUTH_CALLBACK_HOST ||= "::";
5178
+ // Opportunistically drop stale/abandoned logins whenever a new one starts, so a
5179
+ // long-lived node doesn't accumulate them between sweeps (and tests can drive it).
5180
+ sweepOauthLogins();
5143
5181
  const id = randomUUID();
5144
5182
  const abort = new AbortController();
5145
5183
  const state = { id, provider, status: "starting", abort, createdAt: Date.now(), progress: [] };
@@ -9687,6 +9725,7 @@ const server = app.listen(port, host, async () => {
9687
9725
  if (process.env.BIVY_RESTORE)
9688
9726
  void restoreSessionFromSnapshot(String(process.env.BIVY_RESTORE));
9689
9727
  startModelAuthWatcher();
9728
+ startOAuthLoginSweeper();
9690
9729
  startGithubAppSyncWatcher();
9691
9730
  await startGitHubTasksIfConfigured();
9692
9731
  startControlPlaneTasksIfConfigured();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.3.0-staging.45",
3
+ "version": "0.3.0-staging.46",
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.",