Files
fusion/plugins/fusion-plugin-agent-browser/src/probe.ts
Fusion cfa5ef6130 feat(FN-3573): broaden runtime ignore list for live fusion app paths (+2 more)
Commits merged:
- chore(test-isolation): broaden runtime ignore list for live fusion app paths
- feat(FN-3573): complete Step 2 — add plugin setup API client helpers
- feat(FN-3573): complete Step 1 — backfill plugin setup routes

Files changed:
plugins/fusion-plugin-agent-browser/README.md      | 26 ++++++
 plugins/fusion-plugin-agent-browser/manifest.json  | 15 ++++
 plugins/fusion-plugin-agent-browser/package.json   | 25 ++++++
 .../src/__tests__/index.test.ts                    | 66 +++++++++++++++
 .../src/__tests__/prompts.test.ts                  | 21 +++++
 .../src/__tests__/setup.test.ts                    | 39 +++++++++
 .../src/__tests__/skills.test.ts                   |  9 ++
 .../src/__tests__/tools.test.ts                    | 55 ++++++++++++
 .../src/__tests__/types.test.ts                    | 16 ++++
 .../src/__tests__/workflow-steps.test.ts           | 10 +++
 plugins/fusion-plugin-agent-browser/src/index.ts   | 67 +++++++++++++++
 plugins/fusion-plugin-agent-browser/src/probe.ts   | 98 ++++++++++++++++++++++
 plugins/fusion-plugin-agent-browser/src/prompts.ts | 23 +++++
 plugins/fusion-plugin-agent-browser/src/setup.ts   | 31 +++++++
 plugins/fusion-plugin-agent-browser/src/skills.ts  | 12 +++
 plugins/fusion-plugin-agent-browser/src/tools.ts   | 34 ++++++++
 plugins/fusion-plugin-agent-browser/src/types.ts   | 56 +++++++++++++
 .../src/workflow-steps.ts                          | 15 ++++
 plugins/fusion-plugin-agent-browser/tsconfig.json  |  9 ++
 .../fusion-plugin-agent-browser/vitest.config.ts   | 22 +++++
 pnpm-lock.yaml                                     | 16 ++++
 pnpm-workspace.yaml                                |  1 +
 22 files changed, 666 insertions(+)

Fusion-Task-Id: FN-3573
2026-05-06 15:06:24 -07:00

99 lines
3.0 KiB
TypeScript

import { spawn } from "node:child_process";
export interface AgentBrowserProbeResult {
available: boolean;
binaryPath?: string;
version?: string;
reason?: string;
notFound?: boolean;
}
export async function probeAgentBrowserBinary(opts?: { binaryPath?: string; timeoutMs?: number }): Promise<AgentBrowserProbeResult> {
const binary = opts?.binaryPath?.trim() || "agent-browser";
const timeoutMs = opts?.timeoutMs ?? 2000;
const resolvedPath = await tryResolveBinaryPath(binary);
return new Promise((resolve) => {
let settled = false;
const child = spawn(resolvedPath ?? binary, ["--version"], { stdio: ["ignore", "pipe", "pipe"] });
let stdout = "";
let stderr = "";
const timer = setTimeout(() => {
if (settled) return;
settled = true;
child.kill("SIGKILL");
resolve({ available: false, binaryPath: resolvedPath, reason: `Probe timed out after ${timeoutMs}ms` });
}, timeoutMs);
child.stdout?.on("data", (c: Buffer) => (stdout += c.toString("utf-8")));
child.stderr?.on("data", (c: Buffer) => (stderr += c.toString("utf-8")));
child.on("error", (err: NodeJS.ErrnoException) => {
if (settled) return;
settled = true;
clearTimeout(timer);
resolve({
available: false,
binaryPath: resolvedPath,
reason: err.code === "ENOENT" ? "`agent-browser` not found on PATH" : err.message,
notFound: err.code === "ENOENT",
});
});
child.on("close", (code) => {
if (settled) return;
settled = true;
clearTimeout(timer);
if (code === 0) {
resolve({ available: true, binaryPath: resolvedPath, version: stdout.trim() || undefined });
} else {
resolve({
available: false,
binaryPath: resolvedPath,
reason: stderr.trim() || `agent-browser --version exited with code ${String(code)}`,
});
}
});
});
}
async function tryResolveBinaryPath(binary: string): Promise<string | undefined> {
return new Promise((resolvePromise) => {
const which = process.platform === "win32" ? "where" : "which";
const child = spawn(which, [binary], { stdio: ["ignore", "pipe", "ignore"] });
let out = "";
let settled = false;
const timer = setTimeout(() => {
if (settled) return;
settled = true;
try {
child.kill("SIGKILL");
} catch {
// ignored
}
resolvePromise(undefined);
}, 2000);
child.stdout?.on("data", (chunk: Buffer) => {
out += chunk.toString("utf-8");
});
child.on("error", () => {
if (settled) return;
settled = true;
clearTimeout(timer);
resolvePromise(undefined);
});
child.on("close", (code: number | null) => {
if (settled) return;
settled = true;
clearTimeout(timer);
if (code === 0) {
const first = out.trim().split(/\r?\n/)[0];
resolvePromise(first?.length ? first : undefined);
} else {
resolvePromise(undefined);
}
});
});
}