You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

@different-ai/opencode-browser

Package Overview
Dependencies
Maintainers
2
Versions
35
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@different-ai/opencode-browser - npm Package Compare versions

Comparing version
4.4.0
to
4.5.0
+158
-11
bin/broker.cjs

@@ -14,2 +14,16 @@ #!/usr/bin/env node

const DEFAULT_LEASE_TTL_MS = 5 * 60 * 1000;
const LEASE_TTL_MS = (() => {
const raw = process.env.OPENCODE_BROWSER_CLAIM_TTL_MS;
const value = Number(raw);
if (Number.isFinite(value) && value >= 0) return value;
return DEFAULT_LEASE_TTL_MS;
})();
const LEASE_SWEEP_MS =
LEASE_TTL_MS > 0 ? Math.min(Math.max(10000, Math.floor(LEASE_TTL_MS / 2)), 60000) : 0;
function nowMs() {
return Date.now();
}
function nowIso() {

@@ -43,3 +57,3 @@ return new Date().toISOString();

function wantsTab(toolName) {
return !["get_tabs", "get_active_tab"].includes(toolName);
return !["get_tabs", "get_active_tab", "open_tab"].includes(toolName);
}

@@ -54,4 +68,6 @@

// Tab ownership: tabId -> { sessionId, claimedAt }
// Tab ownership: tabId -> { sessionId, claimedAt, lastSeenAt }
const claims = new Map();
// Session state: sessionId -> { defaultTabId, lastSeenAt }
const sessionState = new Map();

@@ -61,3 +77,8 @@ function listClaims() {

for (const [tabId, info] of claims.entries()) {
out.push({ tabId, ...info });
out.push({
tabId,
sessionId: info.sessionId,
claimedAt: info.claimedAt,
lastSeenAt: new Date(info.lastSeenAt).toISOString(),
});
}

@@ -68,2 +89,49 @@ out.sort((a, b) => a.tabId - b.tabId);

function sessionHasClaims(sessionId) {
for (const info of claims.values()) {
if (info.sessionId === sessionId) return true;
}
return false;
}
function getSessionState(sessionId) {
if (!sessionId) return null;
let state = sessionState.get(sessionId);
if (!state) {
state = { defaultTabId: null, lastSeenAt: nowMs() };
sessionState.set(sessionId, state);
}
return state;
}
function touchSession(sessionId) {
const state = getSessionState(sessionId);
if (!state) return null;
state.lastSeenAt = nowMs();
return state;
}
function setDefaultTab(sessionId, tabId) {
const state = getSessionState(sessionId);
if (!state) return;
state.defaultTabId = tabId;
state.lastSeenAt = nowMs();
}
function clearDefaultTab(sessionId, tabId) {
const state = sessionState.get(sessionId);
if (!state) return;
if (tabId === undefined || state.defaultTabId === tabId) {
state.defaultTabId = null;
}
state.lastSeenAt = nowMs();
}
function releaseClaim(tabId) {
const info = claims.get(tabId);
if (!info) return;
claims.delete(tabId);
clearDefaultTab(info.sessionId, tabId);
}
function releaseClaimsForSession(sessionId) {

@@ -73,2 +141,4 @@ for (const [tabId, info] of claims.entries()) {

}
clearDefaultTab(sessionId);
sessionState.delete(sessionId);
}

@@ -84,5 +154,35 @@

function setClaim(tabId, sessionId) {
claims.set(tabId, { sessionId, claimedAt: nowIso() });
const existing = claims.get(tabId);
claims.set(tabId, {
sessionId,
claimedAt: existing ? existing.claimedAt : nowIso(),
lastSeenAt: nowMs(),
});
}
function touchClaim(tabId, sessionId) {
const existing = claims.get(tabId);
if (existing && existing.sessionId !== sessionId) return;
if (existing) {
existing.lastSeenAt = nowMs();
} else {
setClaim(tabId, sessionId);
}
}
function cleanupStaleClaims() {
if (!LEASE_TTL_MS) return;
const now = nowMs();
for (const [tabId, info] of claims.entries()) {
if (now - info.lastSeenAt > LEASE_TTL_MS) {
releaseClaim(tabId);
}
}
for (const [sessionId, state] of sessionState.entries()) {
if (!sessionHasClaims(sessionId) && now - state.lastSeenAt > LEASE_TTL_MS) {
sessionState.delete(sessionId);
}
}
}
function ensureHost() {

@@ -127,7 +227,30 @@ if (host && host.socket && !host.socket.destroyed) return;

if (sessionId) touchSession(sessionId);
let tabId = args.tabId;
const toolArgs = { ...args };
if (tool === "open_tab" && toolArgs.active !== false) {
const activeTabId = await resolveActiveTab(sessionId);
const claimCheck = checkClaim(activeTabId, sessionId);
if (!claimCheck.ok) {
toolArgs.active = false;
}
}
if (wantsTab(tool)) {
if (typeof tabId !== "number") {
tabId = await resolveActiveTab(sessionId);
const state = getSessionState(sessionId);
const defaultTabId = state && Number.isFinite(state.defaultTabId) ? state.defaultTabId : null;
if (Number.isFinite(defaultTabId)) {
tabId = defaultTabId;
} else {
const activeTabId = await resolveActiveTab(sessionId);
const claimCheck = checkClaim(activeTabId, sessionId);
if (!claimCheck.ok) {
throw new Error(`${claimCheck.error}. No default tab for session; open a new tab or claim one.`);
}
tabId = activeTabId;
setDefaultTab(sessionId, tabId);
}
}

@@ -139,3 +262,3 @@

const res = await callExtension(tool, { ...args, tabId }, sessionId);
const res = await callExtension(tool, { ...toolArgs, tabId }, sessionId);

@@ -145,5 +268,4 @@ const usedTabId =

if (typeof usedTabId === "number") {
// Auto-claim on first touch
const existing = claims.get(usedTabId);
if (!existing) setClaim(usedTabId, sessionId);
touchClaim(usedTabId, sessionId);
setDefaultTab(sessionId, usedTabId);
}

@@ -158,2 +280,3 @@

client.sessionId = msg.sessionId;
if (client.sessionId) touchSession(client.sessionId);
if (client.role === "native-host") {

@@ -188,2 +311,3 @@ host = { socket };

const sessionId = msg.sessionId || client.sessionId;
if (sessionId) touchSession(sessionId);

@@ -197,3 +321,17 @@ const replyOk = (data) => writeJsonLine(socket, { type: "response", id: requestId, ok: true, data });

if (msg.op === "status") {
replyOk({ broker: true, hostConnected: !!host && !!host.socket && !host.socket.destroyed, claims: listClaims() });
const state = sessionId ? sessionState.get(sessionId) : null;
const sessionInfo = state
? {
sessionId,
defaultTabId: state.defaultTabId,
lastSeenAt: new Date(state.lastSeenAt).toISOString(),
}
: null;
replyOk({
broker: true,
hostConnected: !!host && !!host.socket && !host.socket.destroyed,
claims: listClaims(),
leaseTtlMs: LEASE_TTL_MS,
session: sessionInfo,
});
return;

@@ -215,3 +353,7 @@ }

}
if (existing && existing.sessionId !== sessionId && force) {
clearDefaultTab(existing.sessionId, tabId);
}
setClaim(tabId, sessionId);
setDefaultTab(sessionId, tabId);
replyOk({ ok: true, tabId, sessionId });

@@ -232,3 +374,3 @@ return;

}
claims.delete(tabId);
releaseClaim(tabId);
replyOk({ ok: true, tabId, released: true });

@@ -305,2 +447,7 @@ return;

if (LEASE_TTL_MS > 0 && LEASE_SWEEP_MS > 0) {
const timer = setInterval(cleanupStaleClaims, LEASE_SWEEP_MS);
if (typeof timer.unref === "function") timer.unref();
}
start();
+3
-1
{
"name": "@different-ai/opencode-browser",
"version": "4.4.0",
"version": "4.5.0",
"description": "Browser automation plugin for OpenCode (native messaging + per-tab ownership).",

@@ -23,2 +23,4 @@ "type": "module",

"build": "bun build src/plugin.ts --target=node --outfile=dist/plugin.js",
"prepublishOnly": "bun run build",
"publish": "node -e \"const argv=(() => { try { return JSON.parse(process.env.npm_config_argv || '{}').original || []; } catch { return []; } })(); if (argv.length === 1 && argv[0] === 'publish') process.exit(0); require('child_process').execSync('npm publish --access public', { stdio: 'inherit' });\"",
"install": "node bin/cli.js install",

@@ -25,0 +27,0 @@ "uninstall": "node bin/cli.js uninstall",

@@ -115,4 +115,6 @@ # OpenCode Browser

- First time a session touches a tab, the broker **auto-claims** it for that session.
- Other sessions attempting to use the same tab will get an error.
- Use `browser_status` to inspect claims if needed.
- Each session tracks a default tab; tools without `tabId` route to it.
- `browser_open_tab` always works; if another session owns the active tab, the new tab opens in the background.
- Claims expire after inactivity (`OPENCODE_BROWSER_CLAIM_TTL_MS`, default 5 minutes).
- Use `browser_status` or `browser_list_claims` to inspect claims if needed.

@@ -124,2 +126,5 @@ ## Available tools

- `browser_get_tabs`
- `browser_list_claims`
- `browser_claim_tab`
- `browser_release_tab`
- `browser_open_tab`

@@ -162,4 +167,4 @@ - `browser_navigate`

**Tab ownership errors**
- Use `browser_status` to see current claims
- Close the other OpenCode session to release ownership
- Use `browser_status` or `browser_list_claims` to see current claims
- Use `browser_release_tab` or close the other OpenCode session to release ownership

@@ -166,0 +171,0 @@ ## Uninstall

Sorry, the diff of this file is too big to display