@different-ai/opencode-browser 4.3.1 → 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/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 { existsSync, mkdirSync, readFileSync } from "fs";
12335
- import { homedir } from "os";
12336
- import { dirname, join } from "path";
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 = join(__dirname2, "..", "package.json");
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(readFileSync(PACKAGE_JSON_PATH, "utf8"));
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 = join(homedir(), ".opencode-browser");
12359
- var SOCKET_PATH = join(BASE_DIR, "broker.sock");
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 createJsonLineParser(onMessage) {
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 writeJsonLine(socket, msg) {
13058
+ function writeJsonLine2(socket, msg) {
12381
13059
  socket.write(JSON.stringify(msg) + `
12382
13060
  `);
12383
13061
  }
12384
13062
  function maybeStartBroker() {
12385
- const brokerPath = join(BASE_DIR, "broker.cjs");
13063
+ const brokerPath = join2(BASE_DIR, "broker.cjs");
12386
13064
  if (!existsSync(brokerPath))
12387
13065
  return;
12388
13066
  try {
12389
- const child = spawn(process.execPath, [brokerPath], { detached: true, stdio: "ignore" });
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 = net.createConnection(SOCKET_PATH);
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 sleep(ms) {
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 sleep(100);
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", createJsonLineParser((msg) => {
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
- writeJsonLine(socket, { type: "hello", role: "plugin", sessionId, pid: process.pid });
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
- writeJsonLine(s, { type: "request", id, op, ...payload });
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 broker/native-host connection status and current tab claims.",
13210
+ description: "Check backend connection status and current tab claims.",
12503
13211
  args: {},
12504
13212
  async execute(args, ctx2) {
12505
- const data = await brokerRequest("status", {});
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 brokerRequest("tool", { tool: "get_tabs", args: {} });
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 brokerRequest("tool", { tool: "open_tab", args: { url: url2, active } });
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 brokerRequest("tool", { tool: "navigate", args: { url: url2, tabId } });
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 brokerRequest("tool", { tool: "click", args: { selector, index, tabId } });
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 brokerRequest("tool", { tool: "type", args: { selector, text, clear, index, tabId } });
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 brokerRequest("tool", {
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 brokerRequest("tool", { tool: "screenshot", args: { tabId } });
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 brokerRequest("tool", { tool: "snapshot", args: { tabId } });
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 brokerRequest("tool", { tool: "scroll", args: { selector, x, y, tabId } });
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 brokerRequest("tool", { tool: "wait", args: { ms, tabId } });
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 brokerRequest("tool", {
12645
- tool: "query",
12646
- args: { selector, mode, attribute, property, index, limit, timeoutMs, pollMs, pattern, flags, tabId }
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
  }