Menu
popagent
publicLatest change 8828a8233b015e0c6e7896c3e9ba8b060bd3ce5c - Add BifrOSt Navigator agent tool by Ólafur Búi Ólafsson
import { createConnection } from "node:net";
import { join } from "node:path";
import { createTool } from "@mastra/core/tools";
import { z } from "zod";
import { agentSettings, type AgentSettingsStore } from "../agent-settings";
const operations = [
"browser_current_page", "browser_identity", "browser_get_setting", "browser_set_setting",
"browser_tabs", "browser_extract_text", "browser_navigate", "browser_open_tab",
"browser_activate_tab", "browser_close_tab", "browser_reload", "browser_back", "browser_forward",
"browser_elements", "browser_snapshot", "browser_console_logs", "browser_network_events",
"browser_screenshot", "browser_print_pdf", "browser_click", "browser_fill", "browser_select",
"browser_type", "browser_press", "browser_wait_for", "browser_hover", "browser_scroll",
"browser_performance", "browser_benchmark", "browser_page_status", "browser_wait_for_page",
"browser_events", "browser_find_text", "browser_downloads", "browser_download",
"browser_cancel_download", "browser_dialog", "browser_handle_dialog", "browser_permission",
"browser_handle_permission", "browser_upload_file", "browser_clear_site_data", "browser_sessions",
"browser_save_session", "browser_restore_session", "assistant_post_message", "assistant_messages",
"assistant_status",
] as const;
const interactiveOnly = new Set<(typeof operations)[number]>([
"browser_set_setting", "browser_navigate", "browser_open_tab", "browser_activate_tab", "browser_close_tab",
"browser_click", "browser_fill", "browser_select", "browser_type", "browser_press",
"browser_hover", "browser_scroll", "browser_download", "browser_cancel_download",
"browser_handle_dialog", "browser_handle_permission", "browser_upload_file",
"browser_reload", "browser_back", "browser_forward", "browser_clear_site_data",
"browser_save_session", "browser_restore_session", "assistant_post_message",
]);
type JsonObject = Record<string, unknown>;
type BifrOStResult = { content: Array<Record<string, unknown>>; structuredContent?: JsonObject };
type BifrOStCaller = (
operation: (typeof operations)[number],
args: JsonObject,
signal: AbortSignal,
) => Promise<BifrOStResult>;
function socketPath(): string {
return process.env.BIFROST_MCP_SOCKET
?? join(process.env.XDG_RUNTIME_DIR ?? `/run/user/${process.getuid?.() ?? 0}`, "bifrost-navigator.sock");
}
export function callBifrOSt(
operation: (typeof operations)[number],
args: JsonObject,
signal: AbortSignal,
path = socketPath(),
): Promise<BifrOStResult> {
const { promise, resolve, reject } = Promise.withResolvers<BifrOStResult>();
let settled = false;
let input = "";
const socket = createConnection({ path });
const finish = (error?: Error, result?: BifrOStResult) => {
if (settled) return;
settled = true;
signal.removeEventListener("abort", abort);
socket.destroy();
if (error) reject(error);
else resolve(result ?? { content: [] });
};
const abort = () => finish(new Error("BifrOSt request aborted"));
signal.addEventListener("abort", abort, { once: true });
socket.setEncoding("utf8");
socket.setTimeout(15_000, () => finish(new Error("BifrOSt request timed out")));
socket.once("error", finish);
socket.once("connect", () => {
const request: JsonObject = {
jsonrpc: "2.0",
id: 1,
method: "tools/call",
params: { name: operation, arguments: args },
};
if (process.env.BIFROST_MCP_TOKEN) request.auth = process.env.BIFROST_MCP_TOKEN;
socket.write(`${JSON.stringify(request)}\n`);
});
socket.on("data", (chunk) => {
input += chunk;
if (input.length > 24 * 1024 * 1024) return finish(new Error("BifrOSt response exceeded 24 MiB"));
const newline = input.indexOf("\n");
if (newline < 0) return;
try {
const response = JSON.parse(input.slice(0, newline));
if (response?.jsonrpc !== "2.0" || response?.id !== 1) throw new Error("BifrOSt response ID mismatch");
if (response.error) throw new Error(String(response.error.message ?? "BifrOSt request failed"));
if (!Array.isArray(response.result?.content)) throw new Error("BifrOSt response content is missing");
finish(undefined, response.result as BifrOStResult);
} catch (error) {
finish(error as Error);
}
});
return promise;
}
export function createBifrOStTool(
roles: Pick<AgentSettingsStore, "get"> = agentSettings,
call: BifrOStCaller = callBifrOSt,
) {
return createTool({
id: "bifrost-navigator",
description: "Operate the user's live local BifrOSt Navigator through its protected Unix-socket bridge.",
inputSchema: z.object({
operation: z.enum(operations),
arguments: z.record(z.string(), z.unknown()).default({}),
}).strict(),
outputSchema: z.object({
content: z.array(z.record(z.string(), z.unknown())),
structuredContent: z.record(z.string(), z.unknown()).optional(),
}),
execute: async ({ operation, arguments: args }, context) => {
context.abortSignal?.throwIfAborted();
const agentId = context.agent?.agentId;
if (!agentId) throw new Error("BifrOSt requires an active agent role");
const role = await roles.get(agentId);
if (!role || role.browserAccess === "none") throw new Error("Agent role has no BifrOSt access");
if (role.browserAccess === "read-only" && interactiveOnly.has(operation)) {
throw new Error(`Agent role has read-only BifrOSt access: ${operation} is interactive`);
}
return call(operation, args, context.abortSignal ?? AbortSignal.timeout(15_000));
},
});
}
export const bifrostNavigator = createBifrOStTool();