@bivy/bivy 0.10.1-staging.331 → 0.10.1-staging.332

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,30 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright (c) 2026 Petter André Sjulstad
3
+ import { getModelOAuthProvider } from "./model-oauth-providers.js";
4
+ /**
5
+ * Open only the provider authorization URL generated for an active ceremony.
6
+ * The URL is checked against Bivy's owned provider registry before reaching the
7
+ * injected desktop opener, so this can never become an arbitrary remote-URL API.
8
+ */
9
+ export function openOAuthLoginOnNode(login, opener) {
10
+ if (!login?.authUrl || login.status !== "waiting")
11
+ return { opened: false, error: "Login is no longer waiting for authorization." };
12
+ const provider = getModelOAuthProvider(login.provider);
13
+ if (!provider?.authorizeUrl)
14
+ return { opened: false, error: "Provider does not use a browser authorization page." };
15
+ let actual;
16
+ let expected;
17
+ try {
18
+ actual = new URL(login.authUrl);
19
+ expected = new URL(provider.authorizeUrl);
20
+ }
21
+ catch {
22
+ return { opened: false, error: "Authorization URL is invalid." };
23
+ }
24
+ if (actual.protocol !== "https:" || actual.origin !== expected.origin || actual.pathname !== expected.pathname) {
25
+ return { opened: false, error: "Authorization URL did not match the provider." };
26
+ }
27
+ return opener(actual.toString())
28
+ ? { opened: true }
29
+ : { opened: false, error: "This machine cannot open a graphical browser." };
30
+ }
package/dist/server.js CHANGED
@@ -52,6 +52,8 @@ import { execEphemeralRequest } from "./ephemeral-exec.js";
52
52
  import { ApprovalManager } from "./approval.js";
53
53
  import { QuestionManager, validQuestions, isAskUserQuestionTool, formatQuestionResult } from "./question.js";
54
54
  import { NodeIdentity } from "./identity.js";
55
+ import { canOpenBrowser, openBrowser } from "./browser-open.js";
56
+ import { openOAuthLoginOnNode } from "./runtime/oauth/oauth-node-open.js";
55
57
  import { collectNodeStats } from "./node-stats.js";
56
58
  import { SessionEventCoalescer } from "./session-event-coalescer.js";
57
59
  import { authMiddleware, resolveAuth, isAuthorized, requestOriginAllowed } from "./auth.js";
@@ -2883,6 +2885,8 @@ const RELAY_COMMANDS = {
2883
2885
  instructions: state.instructions,
2884
2886
  deviceCode: state.deviceCode,
2885
2887
  usesCallbackServer: state.usesCallbackServer,
2888
+ canOpenOnNode: canOpenBrowser(),
2889
+ nodeName: identity.name,
2886
2890
  error: state.error,
2887
2891
  });
2888
2892
  }
@@ -2890,6 +2894,21 @@ const RELAY_COMMANDS = {
2890
2894
  relay?.sendEvent({ type: "session.error", error: error instanceof Error ? error.message : String(error) });
2891
2895
  }
2892
2896
  },
2897
+ // Open only the authorization URL generated inside this active ceremony. The
2898
+ // remote client never supplies an arbitrary URL, so this cannot become a
2899
+ // general remote-browser-launch primitive.
2900
+ "provider.oauth.open_on_node"(msg, ctx) {
2901
+ const requestId = String(msg.requestId ?? "");
2902
+ const state = oauthLogins.get(String(msg.id ?? ""));
2903
+ if (state?.openedOnNode) {
2904
+ ctx.reply({ type: "provider.oauth.open_on_node.result", requestId, opened: true, alreadyOpened: true });
2905
+ return;
2906
+ }
2907
+ const result = openOAuthLoginOnNode(state, openBrowser);
2908
+ if (result.opened && state)
2909
+ state.openedOnNode = true;
2910
+ ctx.reply({ type: "provider.oauth.open_on_node.result", requestId, ...result });
2911
+ },
2893
2912
  // Paste-back step for providers that return a redirect URL/code instead of a
2894
2913
  // pollable device code.
2895
2914
  "provider.oauth.code"(msg) {
@@ -9492,13 +9511,22 @@ app.post("/api/auth/native-login", async (_req, res, next) => {
9492
9511
  app.post("/api/auth/oauth/start", async (req, res, next) => {
9493
9512
  try {
9494
9513
  const provider = String(req.body?.provider ?? "openai-codex").trim();
9495
- const state = await startOAuthLogin(provider);
9496
- res.json({ ok: true, id: state.id, provider: state.provider, status: state.status, authUrl: state.authUrl, instructions: state.instructions, deviceCode: state.deviceCode, usesCallbackServer: state.usesCallbackServer, error: state.error });
9514
+ const state = await startOAuthLogin(provider, String(req.body?.label ?? "default"));
9515
+ res.json({ ok: true, id: state.id, provider: state.provider, status: state.status, authUrl: state.authUrl, instructions: state.instructions, deviceCode: state.deviceCode, usesCallbackServer: state.usesCallbackServer, canOpenOnNode: canOpenBrowser(), nodeName: identity.name, error: state.error });
9497
9516
  }
9498
9517
  catch (error) {
9499
9518
  next(error);
9500
9519
  }
9501
9520
  });
9521
+ app.post("/api/auth/oauth/:id/open-on-node", (req, res) => {
9522
+ const state = oauthLogins.get(req.params.id);
9523
+ if (state?.openedOnNode)
9524
+ return res.json({ opened: true, alreadyOpened: true });
9525
+ const result = openOAuthLoginOnNode(state, openBrowser);
9526
+ if (result.opened && state)
9527
+ state.openedOnNode = true;
9528
+ res.json(result);
9529
+ });
9502
9530
  app.post("/api/auth/oauth/:id/manual-code", (req, res) => {
9503
9531
  const state = oauthLogins.get(req.params.id);
9504
9532
  if (!state)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.10.1-staging.331",
3
+ "version": "0.10.1-staging.332",
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.",