AkurAI Build
Menu

popagent

public

Latest change 248b7c0673ea9d544f9e4738ee4d3e8ec2925924 - Add native BifrOSt Navigator browser provider with per-browser status LEDs by AkurAI Build

import { AgentBrowser } from "@mastra/agent-browser";
import type { Agent } from "@mastra/core/agent";
import { beforeAll, describe, expect, test } from "bun:test";
import { assertBoundSecretOrigin, BrowserRuntime, BrowserSettingsStore } from "./browser-settings";
import type { AgentSettings, BrowserSettings } from "./api-types";

const store = new BrowserSettingsStore();

beforeAll(() => store.init());

describe("BrowserSettingsStore", () => {
  test("persists the complete browser runtime configuration", async () => {
    const original = await store.get();
    const { updatedAt: _updatedAt, ...originalInput } = original;
    const input = {
      ...originalInput,
      enabled: !original.enabled,
      provider: original.provider === "agent-browser" ? "bifrost-navigator" as const : "agent-browser" as const,
      scope: original.scope === "thread" ? "shared" as const : "thread" as const,
      viewportWidth: original.viewportWidth === 1280 ? 1440 : 1280,
      viewportHeight: original.viewportHeight === 720 ? 900 : 720,
      timeoutMs: original.timeoutMs === 30_000 ? 45_000 : 30_000,
      maxSessions: original.maxSessions === 4 ? 3 : 4,
      idleTimeoutMs: original.idleTimeoutMs === 900_000 ? 600_000 : 900_000,
      screencastEnabled: !original.screencastEnabled,
      screenshotsEnabled: !original.screenshotsEnabled,
      multiTabEnabled: !original.multiTabEnabled,
      formsEnabled: !original.formsEnabled,
      dialogsEnabled: !original.dialogsEnabled,
      dragEnabled: !original.dragEnabled,
      evaluateEnabled: !original.evaluateEnabled,
      recordingEnabled: !original.recordingEnabled,
      recordingRetentionDays: original.recordingRetentionDays === 7 ? 14 : 7,
      recordingMaxFiles: original.recordingMaxFiles === 25 ? 20 : 25,
      allowHosts: ["docs.example.com"],
      denyHosts: ["blocked.example.com"],
    };
    try {
      expect(await store.update(input)).toMatchObject(input);
      expect(await new BrowserSettingsStore().get()).toMatchObject(input);
    } finally {
      await store.update(originalInput);
    }
  });
});

describe("BrowserRuntime", () => {
  const runtimeSettings = (enabled: boolean, provider: BrowserSettings["provider"] = "agent-browser"): BrowserSettings => ({
    enabled,
    provider,
    scope: "thread",
    viewportWidth: 1280,
    viewportHeight: 720,
    timeoutMs: 30_000,
    maxSessions: 4,
    idleTimeoutMs: 900_000,
    screencastEnabled: true,
    screenshotsEnabled: true,
    multiTabEnabled: true,
    formsEnabled: true,
    dialogsEnabled: true,
    dragEnabled: true,
    evaluateEnabled: false,
    recordingEnabled: false,
    recordingRetentionDays: 7,
    recordingMaxFiles: 25,
    allowHosts: [],
    denyHosts: [],
    updatedAt: "",
  });

  test("requires the page and target element to match one exact origin", () => {
    expect(assertBoundSecretOrigin(
      "https://example.com/login?next=%2Faccount",
      "https://example.com",
      "https://example.com",
    )).toBe("https://example.com");
    expect(() => assertBoundSecretOrigin(
      "https://attacker.example/login",
      "https://attacker.example",
      "https://example.com",
    )).toThrow("current page");
    expect(() => assertBoundSecretOrigin(
      "https://example.com/login",
      "https://identity.example",
      "https://example.com",
    )).toThrow("target element");
  });

  test("restricts read-only specialists to non-mutating browser tools", async () => {
    const applied = new Map<string, AgentBrowser | undefined>();
    const calls: string[] = [];
    const target = (id: string) => ({
      setBrowser: (browser?: AgentBrowser) => {
        calls.push(id);
        applied.set(id, browser);
      },
    });
    const specialists = {
      researcher: target("researcher"),
      implementer: target("implementer"),
      reviewer: target("reviewer"),
    };
    const profiles: AgentSettings[] = [
      ["orchistrator", "interactive"],
      ["researcher", "read-only"],
      ["implementer", "interactive"],
      ["reviewer", "read-only"],
    ].map(([id, browserAccess]) => ({
      id: id!,
      name: id!,
      description: `${id} role`,
      instructions: `${id} instructions`,
      model: null,
      workspaceAccess: id === "orchistrator" || id === "implementer" ? "read-write" : "read-only",
      browserAccess: browserAccess as AgentSettings["browserAccess"],
      delegationEnabled: id !== "orchistrator",
      tools: ["webSearch"],
      sourceUrls: [],
      createdAt: "",
      updatedAt: "",
    }));
    const supervisor = {
      id: "orchistrator",
      listAgents: async () => specialists,
      ...target("supervisor"),
    } as unknown as Agent;
    const runtime = new BrowserRuntime(
      {} as BrowserSettingsStore,
      { list: async () => profiles },
    );

    await runtime.apply(supervisor, runtimeSettings(true));
    await runtime.apply(supervisor, runtimeSettings(true));

    expect(calls).toEqual(["supervisor", "researcher", "implementer", "reviewer"]);
    expect(applied.get("supervisor")).toBe(applied.get("implementer"));
    expect(applied.get("researcher")).toBe(applied.get("reviewer"));
    expect(applied.get("researcher")).not.toBe(applied.get("supervisor"));
    expect(Object.keys(applied.get("supervisor")!.getTools())).toContain("browser_click");
    expect(Object.keys(applied.get("researcher")!.getTools())).not.toEqual(expect.arrayContaining([
      "browser_click",
      "browser_type",
      "browser_press",
      "browser_select",
      "browser_dialog",
      "browser_drag",
      "browser_evaluate",
    ]));

    await runtime.apply(supervisor, runtimeSettings(false));
    expect([...applied.values()].every((browser) => browser === undefined)).toBe(true);
  });

  test("the native BifrOSt provider attaches no headless browser and reports socket health", async () => {
    const applied = new Map<string, AgentBrowser | undefined>();
    const supervisor = {
      id: "orchistrator",
      listAgents: async () => ({}),
      setBrowser: (browser?: AgentBrowser) => { applied.set("supervisor", browser); },
    } as unknown as Agent;
    const profiles: AgentSettings[] = [{
      id: "orchistrator", name: "orchistrator", description: "", instructions: "x", model: null,
      workspaceAccess: "read-write", browserAccess: "interactive", delegationEnabled: false,
      tools: [], sourceUrls: [], createdAt: "", updatedAt: "",
    }];
    const runtime = new BrowserRuntime({} as BrowserSettingsStore, { list: async () => profiles });
    await runtime.apply(supervisor, runtimeSettings(true, "bifrost-navigator"));
    expect(applied.get("supervisor")).toBeUndefined();
    const previous = process.env.BIFROST_MCP_SOCKET;
    process.env.BIFROST_MCP_SOCKET = "/nonexistent/bifrost-test.sock";
    try {
      expect(await runtime.health(runtimeSettings(true, "bifrost-navigator"))).toMatchObject({
        provider: "BifrOSt Navigator", healthy: false, headless: false, selected: "bifrost-navigator",
        providers: [
          { id: "agent-browser", label: "Headless AgentBrowser", headless: true, healthy: expect.any(Boolean) },
          { id: "bifrost-navigator", label: "BifrOSt Navigator", headless: false, healthy: false },
        ],
      });
      await expect(runtime.test()).rejects.toThrow();
    } finally {
      if (previous === undefined) delete process.env.BIFROST_MCP_SOCKET;
      else process.env.BIFROST_MCP_SOCKET = previous;
    }
    expect(await runtime.health(runtimeSettings(true))).toMatchObject({ provider: "Headless AgentBrowser", headless: true, selected: "agent-browser" });
  });
});