@different-ai/opencode-browser 4.3.2 → 4.4.0
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.
- package/.opencode/skill/browser-automation/SKILL.md +7 -1
- package/README.md +66 -7
- package/bin/agent-gateway.cjs +129 -0
- package/bin/cli.js +254 -31
- package/bin/tool-test.ts +35 -0
- package/dist/plugin.js +768 -46
- package/extension/background.js +180 -35
- package/extension/manifest.json +4 -1
- package/package.json +6 -2
package/dist/plugin.js
CHANGED
|
@@ -12330,22 +12330,700 @@ function tool(input) {
|
|
|
12330
12330
|
}
|
|
12331
12331
|
tool.schema = exports_external;
|
|
12332
12332
|
// src/plugin.ts
|
|
12333
|
+
import net2 from "net";
|
|
12334
|
+
|
|
12335
|
+
// src/agent-backend.ts
|
|
12333
12336
|
import net from "net";
|
|
12334
|
-
import {
|
|
12335
|
-
import {
|
|
12336
|
-
import {
|
|
12337
|
+
import { readFileSync } from "fs";
|
|
12338
|
+
import { tmpdir } from "os";
|
|
12339
|
+
import { join } from "path";
|
|
12337
12340
|
import { spawn } from "child_process";
|
|
12341
|
+
import { createRequire } from "module";
|
|
12342
|
+
var agentRequire = createRequire(import.meta.url);
|
|
12343
|
+
var REQUEST_TIMEOUT_MS = 60000;
|
|
12344
|
+
var DEFAULT_PAGE_TEXT_LIMIT = 20000;
|
|
12345
|
+
var DEFAULT_LIST_LIMIT = 50;
|
|
12346
|
+
var DEFAULT_POLL_MS = 200;
|
|
12347
|
+
function createJsonLineParser(onMessage) {
|
|
12348
|
+
let buffer = "";
|
|
12349
|
+
return (chunk) => {
|
|
12350
|
+
buffer += chunk.toString("utf8");
|
|
12351
|
+
while (true) {
|
|
12352
|
+
const idx = buffer.indexOf(`
|
|
12353
|
+
`);
|
|
12354
|
+
if (idx === -1)
|
|
12355
|
+
return;
|
|
12356
|
+
const line = buffer.slice(0, idx);
|
|
12357
|
+
buffer = buffer.slice(idx + 1);
|
|
12358
|
+
if (!line.trim())
|
|
12359
|
+
continue;
|
|
12360
|
+
try {
|
|
12361
|
+
onMessage(JSON.parse(line));
|
|
12362
|
+
} catch {}
|
|
12363
|
+
}
|
|
12364
|
+
};
|
|
12365
|
+
}
|
|
12366
|
+
function writeJsonLine(socket, msg) {
|
|
12367
|
+
socket.write(JSON.stringify(msg) + `
|
|
12368
|
+
`);
|
|
12369
|
+
}
|
|
12370
|
+
async function sleep(ms) {
|
|
12371
|
+
return await new Promise((resolve) => setTimeout(resolve, ms));
|
|
12372
|
+
}
|
|
12373
|
+
function parseEnvNumber(value) {
|
|
12374
|
+
if (!value)
|
|
12375
|
+
return null;
|
|
12376
|
+
const parsed = Number(value);
|
|
12377
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
12378
|
+
}
|
|
12379
|
+
function getAgentSession(sessionId) {
|
|
12380
|
+
const override = process.env.OPENCODE_BROWSER_AGENT_SESSION?.trim();
|
|
12381
|
+
if (override)
|
|
12382
|
+
return override;
|
|
12383
|
+
return `opencode-${sessionId}`;
|
|
12384
|
+
}
|
|
12385
|
+
function getAgentPortForSession(session) {
|
|
12386
|
+
let hash2 = 0;
|
|
12387
|
+
for (let i = 0;i < session.length; i++) {
|
|
12388
|
+
hash2 = (hash2 << 5) - hash2 + session.charCodeAt(i);
|
|
12389
|
+
hash2 |= 0;
|
|
12390
|
+
}
|
|
12391
|
+
return 49152 + Math.abs(hash2) % 16383;
|
|
12392
|
+
}
|
|
12393
|
+
function getAgentConnectionInfo(session) {
|
|
12394
|
+
const socketOverride = process.env.OPENCODE_BROWSER_AGENT_SOCKET?.trim();
|
|
12395
|
+
if (socketOverride) {
|
|
12396
|
+
return { type: "unix", path: socketOverride };
|
|
12397
|
+
}
|
|
12398
|
+
const hostOverride = process.env.OPENCODE_BROWSER_AGENT_HOST?.trim();
|
|
12399
|
+
const portOverride = parseEnvNumber(process.env.OPENCODE_BROWSER_AGENT_PORT);
|
|
12400
|
+
const transportOverride = process.env.OPENCODE_BROWSER_AGENT_TRANSPORT?.toLowerCase();
|
|
12401
|
+
const forceTcp = transportOverride === "tcp" || process.env.OPENCODE_BROWSER_AGENT_TCP === "1";
|
|
12402
|
+
if (hostOverride || portOverride !== null || forceTcp || process.platform === "win32") {
|
|
12403
|
+
const host = hostOverride || "127.0.0.1";
|
|
12404
|
+
const port = portOverride ?? getAgentPortForSession(session);
|
|
12405
|
+
return { type: "tcp", host, port };
|
|
12406
|
+
}
|
|
12407
|
+
return { type: "unix", path: join(tmpdir(), `agent-browser-${session}.sock`) };
|
|
12408
|
+
}
|
|
12409
|
+
function isLocalHost(host) {
|
|
12410
|
+
return host === "127.0.0.1" || host === "localhost" || host === "::1";
|
|
12411
|
+
}
|
|
12412
|
+
function resolveAgentDaemonPath() {
|
|
12413
|
+
const override = process.env.OPENCODE_BROWSER_AGENT_DAEMON?.trim();
|
|
12414
|
+
if (override)
|
|
12415
|
+
return override;
|
|
12416
|
+
try {
|
|
12417
|
+
return agentRequire.resolve("agent-browser/dist/daemon.js");
|
|
12418
|
+
} catch {
|
|
12419
|
+
return null;
|
|
12420
|
+
}
|
|
12421
|
+
}
|
|
12422
|
+
function resolveAgentNodePath() {
|
|
12423
|
+
const override = process.env.OPENCODE_BROWSER_AGENT_NODE?.trim();
|
|
12424
|
+
return override || process.execPath;
|
|
12425
|
+
}
|
|
12426
|
+
function getAgentPackageVersion() {
|
|
12427
|
+
try {
|
|
12428
|
+
const pkgPath = agentRequire.resolve("agent-browser/package.json");
|
|
12429
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
12430
|
+
return typeof pkg?.version === "string" ? pkg.version : null;
|
|
12431
|
+
} catch {
|
|
12432
|
+
return null;
|
|
12433
|
+
}
|
|
12434
|
+
}
|
|
12435
|
+
function shouldAutoStartAgent(connection) {
|
|
12436
|
+
const autoStart = process.env.OPENCODE_BROWSER_AGENT_AUTOSTART?.toLowerCase();
|
|
12437
|
+
if (autoStart && ["0", "false", "no"].includes(autoStart))
|
|
12438
|
+
return false;
|
|
12439
|
+
if (connection.type === "unix")
|
|
12440
|
+
return true;
|
|
12441
|
+
return connection.type === "tcp" && process.platform === "win32" && isLocalHost(connection.host);
|
|
12442
|
+
}
|
|
12443
|
+
async function maybeStartAgentDaemon(connection, session) {
|
|
12444
|
+
if (!shouldAutoStartAgent(connection))
|
|
12445
|
+
return;
|
|
12446
|
+
const daemonPath = resolveAgentDaemonPath();
|
|
12447
|
+
if (!daemonPath) {
|
|
12448
|
+
throw new Error("agent-browser dependency not found. Install agent-browser or set OPENCODE_BROWSER_AGENT_DAEMON.");
|
|
12449
|
+
}
|
|
12450
|
+
try {
|
|
12451
|
+
const child = spawn(resolveAgentNodePath(), [daemonPath], {
|
|
12452
|
+
detached: true,
|
|
12453
|
+
stdio: "ignore",
|
|
12454
|
+
env: {
|
|
12455
|
+
...process.env,
|
|
12456
|
+
AGENT_BROWSER_SESSION: session,
|
|
12457
|
+
AGENT_BROWSER_DAEMON: "1"
|
|
12458
|
+
}
|
|
12459
|
+
});
|
|
12460
|
+
child.unref();
|
|
12461
|
+
} catch {}
|
|
12462
|
+
}
|
|
12463
|
+
function buildEvalScript(body) {
|
|
12464
|
+
return `(() => { ${body} })()`;
|
|
12465
|
+
}
|
|
12466
|
+
function buildAgentTypeScript(selector, indexValue, text, clear) {
|
|
12467
|
+
const payload = { selector, index: indexValue, text, clear };
|
|
12468
|
+
return buildEvalScript(`
|
|
12469
|
+
const payload = ${JSON.stringify(payload)};
|
|
12470
|
+
let matches = [];
|
|
12471
|
+
try {
|
|
12472
|
+
matches = Array.from(document.querySelectorAll(payload.selector));
|
|
12473
|
+
} catch {
|
|
12474
|
+
return { ok: false, error: "Invalid selector" };
|
|
12475
|
+
}
|
|
12476
|
+
const element = matches[payload.index];
|
|
12477
|
+
if (!element) return { ok: false, error: "Element not found" };
|
|
12478
|
+
const tag = element.tagName ? element.tagName.toUpperCase() : "";
|
|
12479
|
+
if (tag === "INPUT" || tag === "TEXTAREA") {
|
|
12480
|
+
if (payload.clear) element.value = "";
|
|
12481
|
+
element.value = (element.value || "") + payload.text;
|
|
12482
|
+
element.dispatchEvent(new Event("input", { bubbles: true }));
|
|
12483
|
+
element.dispatchEvent(new Event("change", { bubbles: true }));
|
|
12484
|
+
return { ok: true };
|
|
12485
|
+
}
|
|
12486
|
+
if (element.isContentEditable) {
|
|
12487
|
+
if (payload.clear) element.textContent = "";
|
|
12488
|
+
element.textContent = (element.textContent || "") + payload.text;
|
|
12489
|
+
element.dispatchEvent(new Event("input", { bubbles: true }));
|
|
12490
|
+
return { ok: true };
|
|
12491
|
+
}
|
|
12492
|
+
return { ok: false, error: "Element is not typable" };
|
|
12493
|
+
`);
|
|
12494
|
+
}
|
|
12495
|
+
function buildAgentSelectScript(selector, indexValue, value, label, optionIndex) {
|
|
12496
|
+
const payload = {
|
|
12497
|
+
selector,
|
|
12498
|
+
index: indexValue,
|
|
12499
|
+
value: value ?? null,
|
|
12500
|
+
label: label ?? null,
|
|
12501
|
+
optionIndex: Number.isFinite(optionIndex) ? optionIndex : null
|
|
12502
|
+
};
|
|
12503
|
+
return buildEvalScript(`
|
|
12504
|
+
const payload = ${JSON.stringify(payload)};
|
|
12505
|
+
let matches = [];
|
|
12506
|
+
try {
|
|
12507
|
+
matches = Array.from(document.querySelectorAll(payload.selector));
|
|
12508
|
+
} catch {
|
|
12509
|
+
return { ok: false, error: "Invalid selector" };
|
|
12510
|
+
}
|
|
12511
|
+
const element = matches[payload.index];
|
|
12512
|
+
if (!element) return { ok: false, error: "Element not found" };
|
|
12513
|
+
if (!element.tagName || element.tagName.toUpperCase() !== "SELECT") {
|
|
12514
|
+
return { ok: false, error: "Element is not a select" };
|
|
12515
|
+
}
|
|
12516
|
+
const options = Array.from(element.options || []);
|
|
12517
|
+
let chosen = null;
|
|
12518
|
+
if (payload.value !== null) {
|
|
12519
|
+
chosen = options.find((option) => option.value === payload.value) || null;
|
|
12520
|
+
}
|
|
12521
|
+
if (!chosen && payload.label !== null) {
|
|
12522
|
+
const target = payload.label.trim();
|
|
12523
|
+
chosen = options.find((option) => (option.label || option.textContent || "").trim() === target) || null;
|
|
12524
|
+
}
|
|
12525
|
+
if (!chosen && payload.optionIndex !== null) {
|
|
12526
|
+
chosen = options[payload.optionIndex] || null;
|
|
12527
|
+
}
|
|
12528
|
+
if (!chosen) return { ok: false, error: "Option not found" };
|
|
12529
|
+
element.value = chosen.value;
|
|
12530
|
+
chosen.selected = true;
|
|
12531
|
+
element.dispatchEvent(new Event("input", { bubbles: true }));
|
|
12532
|
+
element.dispatchEvent(new Event("change", { bubbles: true }));
|
|
12533
|
+
return {
|
|
12534
|
+
ok: true,
|
|
12535
|
+
value: element.value,
|
|
12536
|
+
label: (chosen.label || chosen.textContent || "").trim(),
|
|
12537
|
+
};
|
|
12538
|
+
`);
|
|
12539
|
+
}
|
|
12540
|
+
function buildAgentPageTextScript(limit, pattern, flags) {
|
|
12541
|
+
const payload = { limit, pattern, flags };
|
|
12542
|
+
return buildEvalScript(`
|
|
12543
|
+
const payload = ${JSON.stringify(payload)};
|
|
12544
|
+
const safeString = (value) => (typeof value === "string" ? value : "");
|
|
12545
|
+
const bodyText = safeString(document.body ? document.body.innerText : "");
|
|
12546
|
+
const inputText = Array.from(document.querySelectorAll("input, textarea, [contenteditable='true']"))
|
|
12547
|
+
.map((element) => {
|
|
12548
|
+
if (element.tagName === "INPUT" || element.tagName === "TEXTAREA") {
|
|
12549
|
+
return safeString(element.value);
|
|
12550
|
+
}
|
|
12551
|
+
return safeString(element.textContent);
|
|
12552
|
+
})
|
|
12553
|
+
.filter(Boolean)
|
|
12554
|
+
.join("
|
|
12555
|
+
");
|
|
12556
|
+
const combined = [bodyText, inputText].filter(Boolean).join("
|
|
12557
|
+
|
|
12558
|
+
");
|
|
12559
|
+
const maxSize = Number.isFinite(payload.limit) ? payload.limit : ${DEFAULT_PAGE_TEXT_LIMIT};
|
|
12560
|
+
const text = combined.slice(0, Math.max(0, maxSize));
|
|
12561
|
+
let matches = [];
|
|
12562
|
+
if (payload.pattern) {
|
|
12563
|
+
try {
|
|
12564
|
+
const re = new RegExp(payload.pattern, payload.flags || "i");
|
|
12565
|
+
let match;
|
|
12566
|
+
while ((match = re.exec(text)) && matches.length < 50) {
|
|
12567
|
+
matches.push(match[0]);
|
|
12568
|
+
if (!re.global) break;
|
|
12569
|
+
}
|
|
12570
|
+
} catch {
|
|
12571
|
+
matches = [];
|
|
12572
|
+
}
|
|
12573
|
+
}
|
|
12574
|
+
return {
|
|
12575
|
+
url: location.href,
|
|
12576
|
+
title: document.title,
|
|
12577
|
+
text,
|
|
12578
|
+
matches,
|
|
12579
|
+
};
|
|
12580
|
+
`);
|
|
12581
|
+
}
|
|
12582
|
+
function buildAgentListScript(selector, limit) {
|
|
12583
|
+
const payload = { selector, limit };
|
|
12584
|
+
return buildEvalScript(`
|
|
12585
|
+
const payload = ${JSON.stringify(payload)};
|
|
12586
|
+
let nodes = [];
|
|
12587
|
+
try {
|
|
12588
|
+
nodes = Array.from(document.querySelectorAll(payload.selector));
|
|
12589
|
+
} catch {
|
|
12590
|
+
return { ok: false, error: "Invalid selector" };
|
|
12591
|
+
}
|
|
12592
|
+
const maxItems = Math.min(Math.max(1, payload.limit || ${DEFAULT_LIST_LIMIT}), 200);
|
|
12593
|
+
const items = nodes.slice(0, maxItems).map((element) => ({
|
|
12594
|
+
text: (element.innerText || element.textContent || "").trim().slice(0, 200),
|
|
12595
|
+
tag: (element.tagName || "").toLowerCase(),
|
|
12596
|
+
ariaLabel: element.getAttribute ? element.getAttribute("aria-label") : null,
|
|
12597
|
+
}));
|
|
12598
|
+
return { ok: true, value: { items, count: nodes.length } };
|
|
12599
|
+
`);
|
|
12600
|
+
}
|
|
12601
|
+
function buildAgentNthValueScript(selector, indexValue) {
|
|
12602
|
+
const payload = { selector, index: indexValue };
|
|
12603
|
+
return buildEvalScript(`
|
|
12604
|
+
const payload = ${JSON.stringify(payload)};
|
|
12605
|
+
let nodes = [];
|
|
12606
|
+
try {
|
|
12607
|
+
nodes = Array.from(document.querySelectorAll(payload.selector));
|
|
12608
|
+
} catch {
|
|
12609
|
+
return { ok: false, error: "Invalid selector" };
|
|
12610
|
+
}
|
|
12611
|
+
const element = nodes[payload.index];
|
|
12612
|
+
if (!element) return { ok: false, error: "Element not found" };
|
|
12613
|
+
const value = element.value !== undefined ? element.value : "";
|
|
12614
|
+
return { ok: true, value: typeof value === "string" ? value : String(value ?? "") };
|
|
12615
|
+
`);
|
|
12616
|
+
}
|
|
12617
|
+
function buildAgentNthAttributeScript(selector, indexValue, attribute) {
|
|
12618
|
+
const payload = { selector, index: indexValue, attribute };
|
|
12619
|
+
return buildEvalScript(`
|
|
12620
|
+
const payload = ${JSON.stringify(payload)};
|
|
12621
|
+
let nodes = [];
|
|
12622
|
+
try {
|
|
12623
|
+
nodes = Array.from(document.querySelectorAll(payload.selector));
|
|
12624
|
+
} catch {
|
|
12625
|
+
return { ok: false, error: "Invalid selector" };
|
|
12626
|
+
}
|
|
12627
|
+
const element = nodes[payload.index];
|
|
12628
|
+
if (!element) return { ok: false, error: "Element not found" };
|
|
12629
|
+
const value = element.getAttribute ? element.getAttribute(payload.attribute) : null;
|
|
12630
|
+
return { ok: true, value };
|
|
12631
|
+
`);
|
|
12632
|
+
}
|
|
12633
|
+
function buildAgentNthPropertyScript(selector, indexValue, property) {
|
|
12634
|
+
const payload = { selector, index: indexValue, property };
|
|
12635
|
+
return buildEvalScript(`
|
|
12636
|
+
const payload = ${JSON.stringify(payload)};
|
|
12637
|
+
let nodes = [];
|
|
12638
|
+
try {
|
|
12639
|
+
nodes = Array.from(document.querySelectorAll(payload.selector));
|
|
12640
|
+
} catch {
|
|
12641
|
+
return { ok: false, error: "Invalid selector" };
|
|
12642
|
+
}
|
|
12643
|
+
const element = nodes[payload.index];
|
|
12644
|
+
if (!element) return { ok: false, error: "Element not found" };
|
|
12645
|
+
return { ok: true, value: element[payload.property] };
|
|
12646
|
+
`);
|
|
12647
|
+
}
|
|
12648
|
+
function buildAgentOuterHtmlScript(selector, indexValue) {
|
|
12649
|
+
const payload = { selector, index: indexValue };
|
|
12650
|
+
return buildEvalScript(`
|
|
12651
|
+
const payload = ${JSON.stringify(payload)};
|
|
12652
|
+
let nodes = [];
|
|
12653
|
+
try {
|
|
12654
|
+
nodes = Array.from(document.querySelectorAll(payload.selector));
|
|
12655
|
+
} catch {
|
|
12656
|
+
return { ok: false, error: "Invalid selector" };
|
|
12657
|
+
}
|
|
12658
|
+
const element = nodes[payload.index];
|
|
12659
|
+
if (!element) return { ok: false, error: "Element not found" };
|
|
12660
|
+
return { ok: true, value: element.outerHTML };
|
|
12661
|
+
`);
|
|
12662
|
+
}
|
|
12663
|
+
function ensureEvalResult(result, fallback) {
|
|
12664
|
+
if (!result || typeof result !== "object" || result.ok !== true) {
|
|
12665
|
+
const message = typeof result?.error === "string" ? result.error : fallback;
|
|
12666
|
+
throw new Error(message);
|
|
12667
|
+
}
|
|
12668
|
+
return result.value;
|
|
12669
|
+
}
|
|
12670
|
+
function createAgentBackend(sessionId) {
|
|
12671
|
+
const session = getAgentSession(sessionId);
|
|
12672
|
+
const connection = getAgentConnectionInfo(session);
|
|
12673
|
+
let agentSocket = null;
|
|
12674
|
+
let agentReqId = 0;
|
|
12675
|
+
const agentPending = new Map;
|
|
12676
|
+
async function connectToAgent() {
|
|
12677
|
+
return await new Promise((resolve, reject) => {
|
|
12678
|
+
const socket = connection.type === "unix" ? net.createConnection(connection.path) : net.createConnection({ host: connection.host, port: connection.port });
|
|
12679
|
+
socket.once("connect", () => resolve(socket));
|
|
12680
|
+
socket.once("error", (err) => reject(err));
|
|
12681
|
+
});
|
|
12682
|
+
}
|
|
12683
|
+
async function ensureAgentSocket() {
|
|
12684
|
+
if (agentSocket && !agentSocket.destroyed)
|
|
12685
|
+
return agentSocket;
|
|
12686
|
+
try {
|
|
12687
|
+
agentSocket = await connectToAgent();
|
|
12688
|
+
} catch {
|
|
12689
|
+
await maybeStartAgentDaemon(connection, session);
|
|
12690
|
+
for (let attempt = 0;attempt < 20; attempt++) {
|
|
12691
|
+
await sleep(100);
|
|
12692
|
+
try {
|
|
12693
|
+
agentSocket = await connectToAgent();
|
|
12694
|
+
break;
|
|
12695
|
+
} catch {}
|
|
12696
|
+
}
|
|
12697
|
+
}
|
|
12698
|
+
if (!agentSocket || agentSocket.destroyed) {
|
|
12699
|
+
const target = connection.type === "unix" ? connection.path : `${connection.host}:${connection.port}`;
|
|
12700
|
+
throw new Error(`Could not connect to agent-browser daemon at ${target}.`);
|
|
12701
|
+
}
|
|
12702
|
+
agentSocket.setNoDelay(true);
|
|
12703
|
+
agentSocket.on("data", createJsonLineParser((msg) => {
|
|
12704
|
+
if (!msg || msg.id === undefined)
|
|
12705
|
+
return;
|
|
12706
|
+
const messageId = typeof msg.id === "string" ? msg.id : String(msg.id);
|
|
12707
|
+
const pending = agentPending.get(messageId);
|
|
12708
|
+
if (!pending)
|
|
12709
|
+
return;
|
|
12710
|
+
agentPending.delete(messageId);
|
|
12711
|
+
const res = msg;
|
|
12712
|
+
if (!res.success)
|
|
12713
|
+
pending.reject(new Error(res.error || "Agent browser error"));
|
|
12714
|
+
else
|
|
12715
|
+
pending.resolve(res.data);
|
|
12716
|
+
}));
|
|
12717
|
+
agentSocket.on("close", () => {
|
|
12718
|
+
for (const pending of agentPending.values()) {
|
|
12719
|
+
pending.reject(new Error("Agent browser connection closed"));
|
|
12720
|
+
}
|
|
12721
|
+
agentPending.clear();
|
|
12722
|
+
agentSocket = null;
|
|
12723
|
+
});
|
|
12724
|
+
agentSocket.on("error", () => {
|
|
12725
|
+
agentSocket = null;
|
|
12726
|
+
});
|
|
12727
|
+
return agentSocket;
|
|
12728
|
+
}
|
|
12729
|
+
async function agentRequest(action, payload) {
|
|
12730
|
+
const socket = await ensureAgentSocket();
|
|
12731
|
+
const id = `a${++agentReqId}`;
|
|
12732
|
+
return await new Promise((resolve, reject) => {
|
|
12733
|
+
agentPending.set(id, { resolve, reject });
|
|
12734
|
+
writeJsonLine(socket, { id, action, ...payload });
|
|
12735
|
+
setTimeout(() => {
|
|
12736
|
+
if (!agentPending.has(id))
|
|
12737
|
+
return;
|
|
12738
|
+
agentPending.delete(id);
|
|
12739
|
+
reject(new Error("Timed out waiting for agent-browser response"));
|
|
12740
|
+
}, REQUEST_TIMEOUT_MS);
|
|
12741
|
+
});
|
|
12742
|
+
}
|
|
12743
|
+
async function agentCommand(action, payload) {
|
|
12744
|
+
return await agentRequest(action, payload);
|
|
12745
|
+
}
|
|
12746
|
+
async function withTab(tabId, action) {
|
|
12747
|
+
if (!Number.isFinite(tabId))
|
|
12748
|
+
return await action();
|
|
12749
|
+
await agentCommand("tab_switch", { index: tabId });
|
|
12750
|
+
return await action();
|
|
12751
|
+
}
|
|
12752
|
+
async function agentEvaluate(script) {
|
|
12753
|
+
const data = await agentCommand("evaluate", { script });
|
|
12754
|
+
return data?.result;
|
|
12755
|
+
}
|
|
12756
|
+
async function waitForCount(selector, minimum, timeoutMs, pollMs) {
|
|
12757
|
+
const timeout = Math.max(0, timeoutMs);
|
|
12758
|
+
const poll = Math.max(0, pollMs || DEFAULT_POLL_MS);
|
|
12759
|
+
const start = Date.now();
|
|
12760
|
+
while (true) {
|
|
12761
|
+
const data = await agentCommand("count", { selector });
|
|
12762
|
+
const count = Number(data?.count ?? 0);
|
|
12763
|
+
if (count >= minimum)
|
|
12764
|
+
return count;
|
|
12765
|
+
if (!timeout || Date.now() - start >= timeout)
|
|
12766
|
+
return count;
|
|
12767
|
+
await sleep(poll);
|
|
12768
|
+
}
|
|
12769
|
+
}
|
|
12770
|
+
async function agentQuery(args) {
|
|
12771
|
+
const selector = typeof args.selector === "string" ? args.selector : undefined;
|
|
12772
|
+
const mode = typeof args.mode === "string" && args.mode ? args.mode : "text";
|
|
12773
|
+
const indexValue = Number.isFinite(args.index) ? args.index : 0;
|
|
12774
|
+
const limitValue = Number.isFinite(args.limit) ? args.limit : mode === "page_text" ? DEFAULT_PAGE_TEXT_LIMIT : DEFAULT_LIST_LIMIT;
|
|
12775
|
+
const timeoutValue = Number.isFinite(args.timeoutMs) ? args.timeoutMs : 0;
|
|
12776
|
+
const pollValue = Number.isFinite(args.pollMs) ? args.pollMs : DEFAULT_POLL_MS;
|
|
12777
|
+
const pattern = typeof args.pattern === "string" ? args.pattern : null;
|
|
12778
|
+
const flags = typeof args.flags === "string" ? args.flags : "i";
|
|
12779
|
+
if (mode === "page_text") {
|
|
12780
|
+
if (selector && timeoutValue > 0) {
|
|
12781
|
+
await waitForCount(selector, 1, timeoutValue, pollValue);
|
|
12782
|
+
}
|
|
12783
|
+
const pageText = await agentEvaluate(buildAgentPageTextScript(limitValue, pattern, flags));
|
|
12784
|
+
return { content: JSON.stringify({ ok: true, value: pageText }, null, 2) };
|
|
12785
|
+
}
|
|
12786
|
+
if (!selector)
|
|
12787
|
+
throw new Error("selector is required");
|
|
12788
|
+
if (mode === "exists") {
|
|
12789
|
+
const count2 = await waitForCount(selector, 1, timeoutValue, pollValue);
|
|
12790
|
+
return {
|
|
12791
|
+
content: JSON.stringify({ ok: true, value: { exists: count2 > 0, count: count2 } }, null, 2)
|
|
12792
|
+
};
|
|
12793
|
+
}
|
|
12794
|
+
const count = await waitForCount(selector, indexValue + 1, timeoutValue, pollValue);
|
|
12795
|
+
if (count <= indexValue) {
|
|
12796
|
+
throw new Error(`No matches for selector: ${selector}`);
|
|
12797
|
+
}
|
|
12798
|
+
if (mode === "text") {
|
|
12799
|
+
const data = indexValue > 0 ? await agentCommand("nth", { selector, index: indexValue, subaction: "text" }) : await agentCommand("innertext", { selector });
|
|
12800
|
+
return { content: typeof data?.text === "string" ? data.text : "" };
|
|
12801
|
+
}
|
|
12802
|
+
if (mode === "value") {
|
|
12803
|
+
if (indexValue > 0) {
|
|
12804
|
+
const result = ensureEvalResult(await agentEvaluate(buildAgentNthValueScript(selector, indexValue)), "Value lookup failed");
|
|
12805
|
+
return { content: typeof result === "string" ? result : JSON.stringify(result) };
|
|
12806
|
+
}
|
|
12807
|
+
const data = await agentCommand("inputvalue", { selector });
|
|
12808
|
+
return { content: typeof data?.value === "string" ? data.value : "" };
|
|
12809
|
+
}
|
|
12810
|
+
if (mode === "attribute") {
|
|
12811
|
+
if (!args.attribute)
|
|
12812
|
+
throw new Error("attribute is required");
|
|
12813
|
+
if (indexValue > 0) {
|
|
12814
|
+
const result = ensureEvalResult(await agentEvaluate(buildAgentNthAttributeScript(selector, indexValue, args.attribute)), "Attribute lookup failed");
|
|
12815
|
+
return { content: typeof result === "string" ? result : JSON.stringify(result) };
|
|
12816
|
+
}
|
|
12817
|
+
const data = await agentCommand("getattribute", { selector, attribute: args.attribute });
|
|
12818
|
+
return { content: typeof data?.value === "string" ? data.value : JSON.stringify(data?.value) };
|
|
12819
|
+
}
|
|
12820
|
+
if (mode === "property") {
|
|
12821
|
+
if (!args.property)
|
|
12822
|
+
throw new Error("property is required");
|
|
12823
|
+
const result = ensureEvalResult(await agentEvaluate(buildAgentNthPropertyScript(selector, indexValue, args.property)), "Property lookup failed");
|
|
12824
|
+
return { content: typeof result === "string" ? result : JSON.stringify(result) };
|
|
12825
|
+
}
|
|
12826
|
+
if (mode === "html") {
|
|
12827
|
+
const result = ensureEvalResult(await agentEvaluate(buildAgentOuterHtmlScript(selector, indexValue)), "HTML lookup failed");
|
|
12828
|
+
return { content: typeof result === "string" ? result : JSON.stringify(result) };
|
|
12829
|
+
}
|
|
12830
|
+
if (mode === "list") {
|
|
12831
|
+
const listResult = ensureEvalResult(await agentEvaluate(buildAgentListScript(selector, limitValue)), "List lookup failed");
|
|
12832
|
+
return { content: JSON.stringify({ ok: true, value: listResult }, null, 2) };
|
|
12833
|
+
}
|
|
12834
|
+
throw new Error(`Unknown mode: ${mode}`);
|
|
12835
|
+
}
|
|
12836
|
+
async function requestTool(tool3, args) {
|
|
12837
|
+
switch (tool3) {
|
|
12838
|
+
case "get_tabs": {
|
|
12839
|
+
const data = await agentCommand("tab_list", {});
|
|
12840
|
+
const tabs = Array.isArray(data?.tabs) ? data.tabs : [];
|
|
12841
|
+
const mapped = tabs.map((tab) => ({
|
|
12842
|
+
id: tab.index,
|
|
12843
|
+
url: tab.url,
|
|
12844
|
+
title: tab.title,
|
|
12845
|
+
active: tab.active,
|
|
12846
|
+
windowId: tab.windowId ?? 0
|
|
12847
|
+
}));
|
|
12848
|
+
return { content: JSON.stringify(mapped, null, 2) };
|
|
12849
|
+
}
|
|
12850
|
+
case "open_tab": {
|
|
12851
|
+
const active = args.active;
|
|
12852
|
+
let previousActive = null;
|
|
12853
|
+
if (active === false) {
|
|
12854
|
+
const list = await agentCommand("tab_list", {});
|
|
12855
|
+
if (Number.isFinite(list?.active))
|
|
12856
|
+
previousActive = list.active;
|
|
12857
|
+
}
|
|
12858
|
+
const created = await agentCommand("tab_new", {});
|
|
12859
|
+
if (args.url) {
|
|
12860
|
+
await agentCommand("navigate", { url: args.url });
|
|
12861
|
+
}
|
|
12862
|
+
if (active === false && previousActive !== null) {
|
|
12863
|
+
await agentCommand("tab_switch", { index: previousActive });
|
|
12864
|
+
}
|
|
12865
|
+
return { content: { tabId: created.index, url: args.url, active: active !== false } };
|
|
12866
|
+
}
|
|
12867
|
+
case "navigate": {
|
|
12868
|
+
return await withTab(args.tabId, async () => {
|
|
12869
|
+
if (!args.url)
|
|
12870
|
+
throw new Error("URL is required");
|
|
12871
|
+
await agentCommand("navigate", { url: args.url });
|
|
12872
|
+
return { content: `Navigated to ${args.url}` };
|
|
12873
|
+
});
|
|
12874
|
+
}
|
|
12875
|
+
case "click": {
|
|
12876
|
+
return await withTab(args.tabId, async () => {
|
|
12877
|
+
if (!args.selector)
|
|
12878
|
+
throw new Error("Selector is required");
|
|
12879
|
+
const indexValue = Number.isFinite(args.index) ? args.index : 0;
|
|
12880
|
+
if (indexValue) {
|
|
12881
|
+
await agentCommand("nth", { selector: args.selector, index: indexValue, subaction: "click" });
|
|
12882
|
+
} else {
|
|
12883
|
+
await agentCommand("click", { selector: args.selector });
|
|
12884
|
+
}
|
|
12885
|
+
return { content: `Clicked ${args.selector}` };
|
|
12886
|
+
});
|
|
12887
|
+
}
|
|
12888
|
+
case "type": {
|
|
12889
|
+
return await withTab(args.tabId, async () => {
|
|
12890
|
+
if (!args.selector)
|
|
12891
|
+
throw new Error("Selector is required");
|
|
12892
|
+
if (args.text === undefined)
|
|
12893
|
+
throw new Error("Text is required");
|
|
12894
|
+
const indexValue = Number.isFinite(args.index) ? args.index : 0;
|
|
12895
|
+
if (!indexValue) {
|
|
12896
|
+
await agentCommand("type", {
|
|
12897
|
+
selector: args.selector,
|
|
12898
|
+
text: String(args.text),
|
|
12899
|
+
clear: args.clear
|
|
12900
|
+
});
|
|
12901
|
+
} else {
|
|
12902
|
+
const result = await agentEvaluate(buildAgentTypeScript(args.selector, indexValue, String(args.text), !!args.clear));
|
|
12903
|
+
if (!result?.ok) {
|
|
12904
|
+
throw new Error(result?.error || "Type failed");
|
|
12905
|
+
}
|
|
12906
|
+
}
|
|
12907
|
+
return { content: `Typed "${args.text}" into ${args.selector}` };
|
|
12908
|
+
});
|
|
12909
|
+
}
|
|
12910
|
+
case "select": {
|
|
12911
|
+
return await withTab(args.tabId, async () => {
|
|
12912
|
+
if (!args.selector)
|
|
12913
|
+
throw new Error("Selector is required");
|
|
12914
|
+
if (args.value === undefined && args.label === undefined && args.optionIndex === undefined) {
|
|
12915
|
+
throw new Error("value, label, or optionIndex is required");
|
|
12916
|
+
}
|
|
12917
|
+
const indexValue = Number.isFinite(args.index) ? args.index : 0;
|
|
12918
|
+
let selectedValue = args.value;
|
|
12919
|
+
let selectedLabel = args.label;
|
|
12920
|
+
if (indexValue || args.label !== undefined || args.optionIndex !== undefined) {
|
|
12921
|
+
const result = await agentEvaluate(buildAgentSelectScript(args.selector, indexValue, args.value, args.label, args.optionIndex));
|
|
12922
|
+
if (!result?.ok) {
|
|
12923
|
+
throw new Error(result?.error || "Select failed");
|
|
12924
|
+
}
|
|
12925
|
+
selectedValue = result.value;
|
|
12926
|
+
selectedLabel = result.label;
|
|
12927
|
+
} else if (args.value !== undefined) {
|
|
12928
|
+
await agentCommand("select", { selector: args.selector, values: args.value });
|
|
12929
|
+
}
|
|
12930
|
+
const valueText = selectedValue ? String(selectedValue) : "";
|
|
12931
|
+
const labelText = selectedLabel ? String(selectedLabel) : "";
|
|
12932
|
+
const summary = labelText && valueText && labelText !== valueText ? `${labelText} (${valueText})` : labelText || valueText || "option";
|
|
12933
|
+
return { content: `Selected ${summary} in ${args.selector}` };
|
|
12934
|
+
});
|
|
12935
|
+
}
|
|
12936
|
+
case "screenshot": {
|
|
12937
|
+
return await withTab(args.tabId, async () => {
|
|
12938
|
+
const data = await agentCommand("screenshot", { format: "png" });
|
|
12939
|
+
const base643 = data?.base64 ? String(data.base64) : "";
|
|
12940
|
+
if (!base643)
|
|
12941
|
+
throw new Error("Screenshot failed");
|
|
12942
|
+
return { content: `data:image/png;base64,${base643}` };
|
|
12943
|
+
});
|
|
12944
|
+
}
|
|
12945
|
+
case "snapshot": {
|
|
12946
|
+
return await withTab(args.tabId, async () => {
|
|
12947
|
+
const data = await agentCommand("snapshot", {});
|
|
12948
|
+
const payload = {
|
|
12949
|
+
snapshot: data?.snapshot ?? "",
|
|
12950
|
+
refs: data?.refs ?? {}
|
|
12951
|
+
};
|
|
12952
|
+
return { content: JSON.stringify(payload, null, 2) };
|
|
12953
|
+
});
|
|
12954
|
+
}
|
|
12955
|
+
case "query": {
|
|
12956
|
+
return await withTab(args.tabId, async () => {
|
|
12957
|
+
return await agentQuery(args);
|
|
12958
|
+
});
|
|
12959
|
+
}
|
|
12960
|
+
case "scroll": {
|
|
12961
|
+
return await withTab(args.tabId, async () => {
|
|
12962
|
+
const x = Number.isFinite(args.x) ? args.x : 0;
|
|
12963
|
+
const y = Number.isFinite(args.y) ? args.y : 0;
|
|
12964
|
+
await agentCommand("scroll", {
|
|
12965
|
+
selector: args.selector,
|
|
12966
|
+
x,
|
|
12967
|
+
y
|
|
12968
|
+
});
|
|
12969
|
+
const target = args.selector ? `to ${args.selector}` : `by (${x}, ${y})`;
|
|
12970
|
+
return { content: `Scrolled ${target}` };
|
|
12971
|
+
});
|
|
12972
|
+
}
|
|
12973
|
+
case "wait": {
|
|
12974
|
+
return await withTab(args.tabId, async () => {
|
|
12975
|
+
const ms = Number.isFinite(args.ms) ? args.ms : 1000;
|
|
12976
|
+
await agentCommand("wait", { timeout: ms });
|
|
12977
|
+
return { content: `Waited ${ms}ms` };
|
|
12978
|
+
});
|
|
12979
|
+
}
|
|
12980
|
+
default:
|
|
12981
|
+
throw new Error(`Unsupported tool for agent backend: ${tool3}`);
|
|
12982
|
+
}
|
|
12983
|
+
}
|
|
12984
|
+
async function status() {
|
|
12985
|
+
let connected = false;
|
|
12986
|
+
let error45;
|
|
12987
|
+
try {
|
|
12988
|
+
await ensureAgentSocket();
|
|
12989
|
+
connected = true;
|
|
12990
|
+
} catch (err) {
|
|
12991
|
+
error45 = err instanceof Error ? err.message : String(err);
|
|
12992
|
+
}
|
|
12993
|
+
return {
|
|
12994
|
+
backend: "agent-browser",
|
|
12995
|
+
session,
|
|
12996
|
+
connection,
|
|
12997
|
+
connected,
|
|
12998
|
+
error: error45,
|
|
12999
|
+
agentBrowserVersion: getAgentPackageVersion()
|
|
13000
|
+
};
|
|
13001
|
+
}
|
|
13002
|
+
return {
|
|
13003
|
+
mode: "agent",
|
|
13004
|
+
session,
|
|
13005
|
+
connection,
|
|
13006
|
+
getVersion: getAgentPackageVersion,
|
|
13007
|
+
status,
|
|
13008
|
+
requestTool
|
|
13009
|
+
};
|
|
13010
|
+
}
|
|
13011
|
+
|
|
13012
|
+
// src/plugin.ts
|
|
13013
|
+
import { existsSync, mkdirSync, readFileSync as readFileSync2 } from "fs";
|
|
13014
|
+
import { homedir } from "os";
|
|
13015
|
+
import { dirname, join as join2 } from "path";
|
|
13016
|
+
import { spawn as spawn2 } from "child_process";
|
|
12338
13017
|
import { fileURLToPath } from "url";
|
|
12339
|
-
console.log("[opencode-browser] Plugin loading...", { pid: process.pid });
|
|
12340
13018
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
12341
13019
|
var __dirname2 = dirname(__filename2);
|
|
12342
|
-
var PACKAGE_JSON_PATH =
|
|
13020
|
+
var PACKAGE_JSON_PATH = join2(__dirname2, "..", "package.json");
|
|
12343
13021
|
var cachedVersion = null;
|
|
12344
13022
|
function getPackageVersion() {
|
|
12345
13023
|
if (cachedVersion)
|
|
12346
13024
|
return cachedVersion;
|
|
12347
13025
|
try {
|
|
12348
|
-
const pkg = JSON.parse(
|
|
13026
|
+
const pkg = JSON.parse(readFileSync2(PACKAGE_JSON_PATH, "utf8"));
|
|
12349
13027
|
if (typeof pkg?.version === "string") {
|
|
12350
13028
|
cachedVersion = pkg.version;
|
|
12351
13029
|
return cachedVersion;
|
|
@@ -12355,10 +13033,10 @@ function getPackageVersion() {
|
|
|
12355
13033
|
return cachedVersion;
|
|
12356
13034
|
}
|
|
12357
13035
|
var { schema } = tool;
|
|
12358
|
-
var BASE_DIR =
|
|
12359
|
-
var SOCKET_PATH =
|
|
13036
|
+
var BASE_DIR = join2(homedir(), ".opencode-browser");
|
|
13037
|
+
var SOCKET_PATH = join2(BASE_DIR, "broker.sock");
|
|
12360
13038
|
mkdirSync(BASE_DIR, { recursive: true });
|
|
12361
|
-
function
|
|
13039
|
+
function createJsonLineParser2(onMessage) {
|
|
12362
13040
|
let buffer = "";
|
|
12363
13041
|
return (chunk) => {
|
|
12364
13042
|
buffer += chunk.toString("utf8");
|
|
@@ -12377,33 +13055,36 @@ function createJsonLineParser(onMessage) {
|
|
|
12377
13055
|
}
|
|
12378
13056
|
};
|
|
12379
13057
|
}
|
|
12380
|
-
function
|
|
13058
|
+
function writeJsonLine2(socket, msg) {
|
|
12381
13059
|
socket.write(JSON.stringify(msg) + `
|
|
12382
13060
|
`);
|
|
12383
13061
|
}
|
|
12384
13062
|
function maybeStartBroker() {
|
|
12385
|
-
const brokerPath =
|
|
13063
|
+
const brokerPath = join2(BASE_DIR, "broker.cjs");
|
|
12386
13064
|
if (!existsSync(brokerPath))
|
|
12387
13065
|
return;
|
|
12388
13066
|
try {
|
|
12389
|
-
const child =
|
|
13067
|
+
const child = spawn2(process.execPath, [brokerPath], { detached: true, stdio: "ignore" });
|
|
12390
13068
|
child.unref();
|
|
12391
13069
|
} catch {}
|
|
12392
13070
|
}
|
|
12393
13071
|
async function connectToBroker() {
|
|
12394
13072
|
return await new Promise((resolve, reject) => {
|
|
12395
|
-
const socket =
|
|
13073
|
+
const socket = net2.createConnection(SOCKET_PATH);
|
|
12396
13074
|
socket.once("connect", () => resolve(socket));
|
|
12397
13075
|
socket.once("error", (err) => reject(err));
|
|
12398
13076
|
});
|
|
12399
13077
|
}
|
|
12400
|
-
async function
|
|
13078
|
+
async function sleep2(ms) {
|
|
12401
13079
|
return await new Promise((r) => setTimeout(r, ms));
|
|
12402
13080
|
}
|
|
13081
|
+
var BACKEND_MODE = (process.env.OPENCODE_BROWSER_BACKEND ?? process.env.OPENCODE_BROWSER_MODE ?? "extension").toLowerCase().trim();
|
|
13082
|
+
var USE_AGENT_BACKEND = ["agent", "agent-browser", "agentbrowser"].includes(BACKEND_MODE);
|
|
12403
13083
|
var socket = null;
|
|
12404
13084
|
var sessionId = Math.random().toString(36).slice(2);
|
|
12405
13085
|
var reqId = 0;
|
|
12406
13086
|
var pending = new Map;
|
|
13087
|
+
var agentBackend = USE_AGENT_BACKEND ? createAgentBackend(sessionId) : null;
|
|
12407
13088
|
async function ensureBrokerSocket() {
|
|
12408
13089
|
if (socket && !socket.destroyed)
|
|
12409
13090
|
return socket;
|
|
@@ -12412,7 +13093,7 @@ async function ensureBrokerSocket() {
|
|
|
12412
13093
|
} catch {
|
|
12413
13094
|
maybeStartBroker();
|
|
12414
13095
|
for (let i = 0;i < 20; i++) {
|
|
12415
|
-
await
|
|
13096
|
+
await sleep2(100);
|
|
12416
13097
|
try {
|
|
12417
13098
|
socket = await connectToBroker();
|
|
12418
13099
|
break;
|
|
@@ -12423,7 +13104,7 @@ async function ensureBrokerSocket() {
|
|
|
12423
13104
|
throw new Error("Could not connect to local broker. Run `npx @different-ai/opencode-browser install` and ensure the extension is loaded.");
|
|
12424
13105
|
}
|
|
12425
13106
|
socket.setNoDelay(true);
|
|
12426
|
-
socket.on("data",
|
|
13107
|
+
socket.on("data", createJsonLineParser2((msg) => {
|
|
12427
13108
|
if (msg?.type !== "response" || typeof msg.id !== "number")
|
|
12428
13109
|
return;
|
|
12429
13110
|
const p = pending.get(msg.id);
|
|
@@ -12442,7 +13123,7 @@ async function ensureBrokerSocket() {
|
|
|
12442
13123
|
socket.on("error", () => {
|
|
12443
13124
|
socket = null;
|
|
12444
13125
|
});
|
|
12445
|
-
|
|
13126
|
+
writeJsonLine2(socket, { type: "hello", role: "plugin", sessionId, pid: process.pid });
|
|
12446
13127
|
return socket;
|
|
12447
13128
|
}
|
|
12448
13129
|
async function brokerRequest(op, payload) {
|
|
@@ -12450,7 +13131,7 @@ async function brokerRequest(op, payload) {
|
|
|
12450
13131
|
const id = ++reqId;
|
|
12451
13132
|
return await new Promise((resolve, reject) => {
|
|
12452
13133
|
pending.set(id, { resolve, reject });
|
|
12453
|
-
|
|
13134
|
+
writeJsonLine2(s, { type: "request", id, op, ...payload });
|
|
12454
13135
|
setTimeout(() => {
|
|
12455
13136
|
if (!pending.has(id))
|
|
12456
13137
|
return;
|
|
@@ -12468,8 +13149,29 @@ function toolResultText(data, fallback) {
|
|
|
12468
13149
|
return JSON.stringify(data.content);
|
|
12469
13150
|
return fallback;
|
|
12470
13151
|
}
|
|
13152
|
+
async function toolRequest(toolName, args) {
|
|
13153
|
+
if (USE_AGENT_BACKEND) {
|
|
13154
|
+
if (!agentBackend) {
|
|
13155
|
+
throw new Error("Agent backend unavailable: configuration failed to initialize");
|
|
13156
|
+
}
|
|
13157
|
+
return await agentBackend.requestTool(toolName, args);
|
|
13158
|
+
}
|
|
13159
|
+
return await brokerRequest("tool", { tool: toolName, args });
|
|
13160
|
+
}
|
|
13161
|
+
async function statusRequest() {
|
|
13162
|
+
if (USE_AGENT_BACKEND) {
|
|
13163
|
+
if (!agentBackend) {
|
|
13164
|
+
return {
|
|
13165
|
+
backend: "agent-browser",
|
|
13166
|
+
connected: false,
|
|
13167
|
+
error: "Agent backend unavailable: configuration failed to initialize"
|
|
13168
|
+
};
|
|
13169
|
+
}
|
|
13170
|
+
return await agentBackend.status();
|
|
13171
|
+
}
|
|
13172
|
+
return await brokerRequest("status", {});
|
|
13173
|
+
}
|
|
12471
13174
|
var plugin = async (ctx) => {
|
|
12472
|
-
console.log("[opencode-browser] Plugin loading...", { pid: process.pid });
|
|
12473
13175
|
return {
|
|
12474
13176
|
tool: {
|
|
12475
13177
|
browser_debug: tool({
|
|
@@ -12481,6 +13183,10 @@ var plugin = async (ctx) => {
|
|
|
12481
13183
|
loaded: true,
|
|
12482
13184
|
sessionId,
|
|
12483
13185
|
pid: process.pid,
|
|
13186
|
+
backend: USE_AGENT_BACKEND ? "agent-browser" : "extension",
|
|
13187
|
+
agentSession: agentBackend?.session ?? null,
|
|
13188
|
+
agentConnection: agentBackend?.connection ?? null,
|
|
13189
|
+
agentBrowserVersion: agentBackend?.getVersion?.() ?? null,
|
|
12484
13190
|
pluginVersion: getPackageVersion(),
|
|
12485
13191
|
timestamp: new Date().toISOString()
|
|
12486
13192
|
});
|
|
@@ -12494,15 +13200,17 @@ var plugin = async (ctx) => {
|
|
|
12494
13200
|
name: "@different-ai/opencode-browser",
|
|
12495
13201
|
version: getPackageVersion(),
|
|
12496
13202
|
sessionId,
|
|
12497
|
-
pid: process.pid
|
|
13203
|
+
pid: process.pid,
|
|
13204
|
+
backend: USE_AGENT_BACKEND ? "agent-browser" : "extension",
|
|
13205
|
+
agentBrowserVersion: agentBackend?.getVersion?.() ?? null
|
|
12498
13206
|
});
|
|
12499
13207
|
}
|
|
12500
13208
|
}),
|
|
12501
13209
|
browser_status: tool({
|
|
12502
|
-
description: "Check
|
|
13210
|
+
description: "Check backend connection status and current tab claims.",
|
|
12503
13211
|
args: {},
|
|
12504
13212
|
async execute(args, ctx2) {
|
|
12505
|
-
const data = await
|
|
13213
|
+
const data = await statusRequest();
|
|
12506
13214
|
return JSON.stringify(data);
|
|
12507
13215
|
}
|
|
12508
13216
|
}),
|
|
@@ -12510,7 +13218,7 @@ var plugin = async (ctx) => {
|
|
|
12510
13218
|
description: "List all open browser tabs",
|
|
12511
13219
|
args: {},
|
|
12512
13220
|
async execute(args, ctx2) {
|
|
12513
|
-
const data = await
|
|
13221
|
+
const data = await toolRequest("get_tabs", {});
|
|
12514
13222
|
return toolResultText(data, "ok");
|
|
12515
13223
|
}
|
|
12516
13224
|
}),
|
|
@@ -12521,7 +13229,7 @@ var plugin = async (ctx) => {
|
|
|
12521
13229
|
active: schema.boolean().optional()
|
|
12522
13230
|
},
|
|
12523
13231
|
async execute({ url: url2, active }, ctx2) {
|
|
12524
|
-
const data = await
|
|
13232
|
+
const data = await toolRequest("open_tab", { url: url2, active });
|
|
12525
13233
|
return toolResultText(data, "Opened new tab");
|
|
12526
13234
|
}
|
|
12527
13235
|
}),
|
|
@@ -12532,7 +13240,7 @@ var plugin = async (ctx) => {
|
|
|
12532
13240
|
tabId: schema.number().optional()
|
|
12533
13241
|
},
|
|
12534
13242
|
async execute({ url: url2, tabId }, ctx2) {
|
|
12535
|
-
const data = await
|
|
13243
|
+
const data = await toolRequest("navigate", { url: url2, tabId });
|
|
12536
13244
|
return toolResultText(data, `Navigated to ${url2}`);
|
|
12537
13245
|
}
|
|
12538
13246
|
}),
|
|
@@ -12541,10 +13249,12 @@ var plugin = async (ctx) => {
|
|
|
12541
13249
|
args: {
|
|
12542
13250
|
selector: schema.string(),
|
|
12543
13251
|
index: schema.number().optional(),
|
|
12544
|
-
tabId: schema.number().optional()
|
|
13252
|
+
tabId: schema.number().optional(),
|
|
13253
|
+
timeoutMs: schema.number().optional(),
|
|
13254
|
+
pollMs: schema.number().optional()
|
|
12545
13255
|
},
|
|
12546
|
-
async execute({ selector, index, tabId }, ctx2) {
|
|
12547
|
-
const data = await
|
|
13256
|
+
async execute({ selector, index, tabId, timeoutMs, pollMs }, ctx2) {
|
|
13257
|
+
const data = await toolRequest("click", { selector, index, tabId, timeoutMs, pollMs });
|
|
12548
13258
|
return toolResultText(data, `Clicked ${selector}`);
|
|
12549
13259
|
}
|
|
12550
13260
|
}),
|
|
@@ -12555,10 +13265,12 @@ var plugin = async (ctx) => {
|
|
|
12555
13265
|
text: schema.string(),
|
|
12556
13266
|
clear: schema.boolean().optional(),
|
|
12557
13267
|
index: schema.number().optional(),
|
|
12558
|
-
tabId: schema.number().optional()
|
|
13268
|
+
tabId: schema.number().optional(),
|
|
13269
|
+
timeoutMs: schema.number().optional(),
|
|
13270
|
+
pollMs: schema.number().optional()
|
|
12559
13271
|
},
|
|
12560
|
-
async execute({ selector, text, clear, index, tabId }, ctx2) {
|
|
12561
|
-
const data = await
|
|
13272
|
+
async execute({ selector, text, clear, index, tabId, timeoutMs, pollMs }, ctx2) {
|
|
13273
|
+
const data = await toolRequest("type", { selector, text, clear, index, tabId, timeoutMs, pollMs });
|
|
12562
13274
|
return toolResultText(data, `Typed "${text}" into ${selector}`);
|
|
12563
13275
|
}
|
|
12564
13276
|
}),
|
|
@@ -12570,13 +13282,12 @@ var plugin = async (ctx) => {
|
|
|
12570
13282
|
label: schema.string().optional(),
|
|
12571
13283
|
optionIndex: schema.number().optional(),
|
|
12572
13284
|
index: schema.number().optional(),
|
|
12573
|
-
tabId: schema.number().optional()
|
|
13285
|
+
tabId: schema.number().optional(),
|
|
13286
|
+
timeoutMs: schema.number().optional(),
|
|
13287
|
+
pollMs: schema.number().optional()
|
|
12574
13288
|
},
|
|
12575
|
-
async execute({ selector, value, label, optionIndex, index, tabId }, ctx2) {
|
|
12576
|
-
const data = await
|
|
12577
|
-
tool: "select",
|
|
12578
|
-
args: { selector, value, label, optionIndex, index, tabId }
|
|
12579
|
-
});
|
|
13289
|
+
async execute({ selector, value, label, optionIndex, index, tabId, timeoutMs, pollMs }, ctx2) {
|
|
13290
|
+
const data = await toolRequest("select", { selector, value, label, optionIndex, index, tabId, timeoutMs, pollMs });
|
|
12580
13291
|
const summary = value ?? label ?? (optionIndex != null ? String(optionIndex) : "option");
|
|
12581
13292
|
return toolResultText(data, `Selected ${summary} in ${selector}`);
|
|
12582
13293
|
}
|
|
@@ -12587,7 +13298,7 @@ var plugin = async (ctx) => {
|
|
|
12587
13298
|
tabId: schema.number().optional()
|
|
12588
13299
|
},
|
|
12589
13300
|
async execute({ tabId }, ctx2) {
|
|
12590
|
-
const data = await
|
|
13301
|
+
const data = await toolRequest("screenshot", { tabId });
|
|
12591
13302
|
return toolResultText(data, "Screenshot failed");
|
|
12592
13303
|
}
|
|
12593
13304
|
}),
|
|
@@ -12597,7 +13308,7 @@ var plugin = async (ctx) => {
|
|
|
12597
13308
|
tabId: schema.number().optional()
|
|
12598
13309
|
},
|
|
12599
13310
|
async execute({ tabId }, ctx2) {
|
|
12600
|
-
const data = await
|
|
13311
|
+
const data = await toolRequest("snapshot", { tabId });
|
|
12601
13312
|
return toolResultText(data, "Snapshot failed");
|
|
12602
13313
|
}
|
|
12603
13314
|
}),
|
|
@@ -12607,10 +13318,12 @@ var plugin = async (ctx) => {
|
|
|
12607
13318
|
selector: schema.string().optional(),
|
|
12608
13319
|
x: schema.number().optional(),
|
|
12609
13320
|
y: schema.number().optional(),
|
|
12610
|
-
tabId: schema.number().optional()
|
|
13321
|
+
tabId: schema.number().optional(),
|
|
13322
|
+
timeoutMs: schema.number().optional(),
|
|
13323
|
+
pollMs: schema.number().optional()
|
|
12611
13324
|
},
|
|
12612
|
-
async execute({ selector, x, y, tabId }, ctx2) {
|
|
12613
|
-
const data = await
|
|
13325
|
+
async execute({ selector, x, y, tabId, timeoutMs, pollMs }, ctx2) {
|
|
13326
|
+
const data = await toolRequest("scroll", { selector, x, y, tabId, timeoutMs, pollMs });
|
|
12614
13327
|
return toolResultText(data, "Scrolled");
|
|
12615
13328
|
}
|
|
12616
13329
|
}),
|
|
@@ -12621,7 +13334,7 @@ var plugin = async (ctx) => {
|
|
|
12621
13334
|
tabId: schema.number().optional()
|
|
12622
13335
|
},
|
|
12623
13336
|
async execute({ ms, tabId }, ctx2) {
|
|
12624
|
-
const data = await
|
|
13337
|
+
const data = await toolRequest("wait", { ms, tabId });
|
|
12625
13338
|
return toolResultText(data, "Waited");
|
|
12626
13339
|
}
|
|
12627
13340
|
}),
|
|
@@ -12641,9 +13354,18 @@ var plugin = async (ctx) => {
|
|
|
12641
13354
|
tabId: schema.number().optional()
|
|
12642
13355
|
},
|
|
12643
13356
|
async execute({ selector, mode, attribute, property, index, limit, timeoutMs, pollMs, pattern, flags, tabId }, ctx2) {
|
|
12644
|
-
const data = await
|
|
12645
|
-
|
|
12646
|
-
|
|
13357
|
+
const data = await toolRequest("query", {
|
|
13358
|
+
selector,
|
|
13359
|
+
mode,
|
|
13360
|
+
attribute,
|
|
13361
|
+
property,
|
|
13362
|
+
index,
|
|
13363
|
+
limit,
|
|
13364
|
+
timeoutMs,
|
|
13365
|
+
pollMs,
|
|
13366
|
+
pattern,
|
|
13367
|
+
flags,
|
|
13368
|
+
tabId
|
|
12647
13369
|
});
|
|
12648
13370
|
return toolResultText(data, "Query failed");
|
|
12649
13371
|
}
|