@different-ai/opencode-browser 4.5.0 → 4.5.1

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.
@@ -0,0 +1,12 @@
1
+ ---
2
+ name: github-release
3
+ description: Create a GitHub release after pnpm publish with clear feature updates.
4
+ ---
5
+
6
+ Use this skill after running `pnpm publish`.
7
+
8
+ 1. Read the current version from `package.json` and set the tag name to `v<version>`.
9
+ 2. Summarize changes from git commits since the last tag, or if no tags exist, from the current conversation and latest commits.
10
+ 3. Draft release notes with a "## Features" section using those changes, and confirm with the user.
11
+ 4. Create the GitHub release with `gh release create v<version> --title "v<version>" --notes "<notes>"`.
12
+ 5. Confirm the release appears on GitHub and share the URL.
package/README.md CHANGED
@@ -111,11 +111,11 @@ export OPENCODE_BROWSER_AGENT_PORT=9833
111
111
 
112
112
  ## Per-tab ownership
113
113
 
114
- - First time a session touches a tab, the broker **auto-claims** it for that session.
115
- - Each session tracks a default tab; tools without `tabId` route to it.
116
- - `browser_open_tab` always works; if another session owns the active tab, the new tab opens in the background.
114
+ - Each session owns its own tabs; tabs are never shared between sessions.
115
+ - If a session has no tab yet, the broker auto-creates a background tab on first tool use.
116
+ - `browser_open_tab` always creates and claims a new tab for the session.
117
117
  - Claims expire after inactivity (`OPENCODE_BROWSER_CLAIM_TTL_MS`, default 5 minutes).
118
- - Use `browser_status` or `browser_list_claims` to inspect claims if needed.
118
+ - Use `browser_status` or `browser_list_claims` for debugging.
119
119
 
120
120
  ## Available tools
121
121
 
@@ -126,6 +126,7 @@ Core primitives:
126
126
  - `browser_claim_tab`
127
127
  - `browser_release_tab`
128
128
  - `browser_open_tab`
129
+ - `browser_close_tab`
129
130
  - `browser_navigate`
130
131
  - `browser_query` (modes: `text`, `value`, `list`, `exists`, `page_text`; optional `timeoutMs`/`pollMs`)
131
132
  - `browser_click` (optional `timeoutMs`/`pollMs`)
@@ -149,7 +150,7 @@ Diagnostics:
149
150
 
150
151
  ## Roadmap
151
152
 
152
- - [ ] Add tab management tools (`browser_set_active_tab`, `browser_close_tab`)
153
+ - [ ] Add tab management tools (`browser_set_active_tab`)
153
154
  - [ ] Add navigation helpers (`browser_back`, `browser_forward`, `browser_reload`)
154
155
  - [ ] Add keyboard input tool (`browser_key`)
155
156
  - [ ] Add download support (`browser_download`, `browser_list_downloads`)
@@ -162,8 +163,9 @@ Diagnostics:
162
163
  - If you loaded a custom extension ID, rerun with `--extension-id <id>`
163
164
 
164
165
  **Tab ownership errors**
165
- - Use `browser_status` or `browser_list_claims` to see current claims
166
- - Use `browser_release_tab` or close the other OpenCode session to release ownership
166
+ - Errors usually mean you passed a `tabId` owned by another session
167
+ - Use `browser_open_tab` to create a tab for your session (or omit `tabId` to use your default)
168
+ - Use `browser_status` or `browser_list_claims` for debugging
167
169
 
168
170
  ## Uninstall
169
171
 
package/bin/broker.cjs CHANGED
@@ -206,10 +206,13 @@ function callExtension(tool, args, sessionId) {
206
206
  });
207
207
  }
208
208
 
209
- async function resolveActiveTab(sessionId) {
210
- const res = await callExtension("get_active_tab", {}, sessionId);
209
+ async function ensureSessionTab(sessionId) {
210
+ if (!sessionId) throw new Error("Missing sessionId for tab creation");
211
+ const res = await callExtension("open_tab", { active: false }, sessionId);
211
212
  const tabId = res && typeof res.tabId === "number" ? res.tabId : undefined;
212
- if (!tabId) throw new Error("Could not determine active tab");
213
+ if (!tabId) throw new Error("Failed to create a new tab for this session");
214
+ touchClaim(tabId, sessionId);
215
+ setDefaultTab(sessionId, tabId);
213
216
  return tabId;
214
217
  }
215
218
 
@@ -222,13 +225,7 @@ async function handleTool(pluginSocket, req) {
222
225
  let tabId = args.tabId;
223
226
  const toolArgs = { ...args };
224
227
 
225
- if (tool === "open_tab" && toolArgs.active !== false) {
226
- const activeTabId = await resolveActiveTab(sessionId);
227
- const claimCheck = checkClaim(activeTabId, sessionId);
228
- if (!claimCheck.ok) {
229
- toolArgs.active = false;
230
- }
231
- }
228
+ const isCloseTool = tool === "close_tab";
232
229
 
233
230
  if (wantsTab(tool)) {
234
231
  if (typeof tabId !== "number") {
@@ -236,14 +233,10 @@ async function handleTool(pluginSocket, req) {
236
233
  const defaultTabId = state && Number.isFinite(state.defaultTabId) ? state.defaultTabId : null;
237
234
  if (Number.isFinite(defaultTabId)) {
238
235
  tabId = defaultTabId;
236
+ } else if (!isCloseTool) {
237
+ tabId = await ensureSessionTab(sessionId);
239
238
  } else {
240
- const activeTabId = await resolveActiveTab(sessionId);
241
- const claimCheck = checkClaim(activeTabId, sessionId);
242
- if (!claimCheck.ok) {
243
- throw new Error(`${claimCheck.error}. No default tab for session; open a new tab or claim one.`);
244
- }
245
- tabId = activeTabId;
246
- setDefaultTab(sessionId, tabId);
239
+ throw new Error("No tab owned by this session. Open a new tab first.");
247
240
  }
248
241
  }
249
242
 
@@ -256,8 +249,16 @@ async function handleTool(pluginSocket, req) {
256
249
  const usedTabId =
257
250
  res && typeof res.tabId === "number" ? res.tabId : typeof tabId === "number" ? tabId : undefined;
258
251
  if (typeof usedTabId === "number") {
259
- touchClaim(usedTabId, sessionId);
260
- setDefaultTab(sessionId, usedTabId);
252
+ if (isCloseTool) {
253
+ if (claims.has(usedTabId)) {
254
+ releaseClaim(usedTabId);
255
+ } else {
256
+ clearDefaultTab(sessionId, usedTabId);
257
+ }
258
+ } else {
259
+ touchClaim(usedTabId, sessionId);
260
+ setDefaultTab(sessionId, usedTabId);
261
+ }
261
262
  }
262
263
 
263
264
  return res;
package/dist/plugin.js CHANGED
@@ -12864,6 +12864,14 @@ function createAgentBackend(sessionId) {
12864
12864
  }
12865
12865
  return { content: { tabId: created.index, url: args.url, active: active !== false } };
12866
12866
  }
12867
+ case "close_tab": {
12868
+ const payload = {};
12869
+ if (Number.isFinite(args.tabId))
12870
+ payload.index = args.tabId;
12871
+ const result = await agentCommand("tab_close", payload);
12872
+ const closed = Number.isFinite(result?.closed) ? result.closed : args.tabId;
12873
+ return { content: { tabId: closed, remaining: result?.remaining } };
12874
+ }
12867
12875
  case "navigate": {
12868
12876
  return await withTab(args.tabId, async () => {
12869
12877
  if (!args.url)
@@ -13268,6 +13276,16 @@ var plugin = async (ctx) => {
13268
13276
  return toolResultText(data, "Opened new tab");
13269
13277
  }
13270
13278
  }),
13279
+ browser_close_tab: tool({
13280
+ description: "Close a browser tab owned by this session",
13281
+ args: {
13282
+ tabId: schema.number().optional()
13283
+ },
13284
+ async execute({ tabId }, ctx2) {
13285
+ const data = await toolRequest("close_tab", { tabId });
13286
+ return toolResultText(data, "Closed tab");
13287
+ }
13288
+ }),
13271
13289
  browser_navigate: tool({
13272
13290
  description: "Navigate to a URL in the browser",
13273
13291
  args: {
@@ -101,6 +101,7 @@ async function executeTool(toolName, args) {
101
101
  get_active_tab: toolGetActiveTab,
102
102
  get_tabs: toolGetTabs,
103
103
  open_tab: toolOpenTab,
104
+ close_tab: toolCloseTab,
104
105
  navigate: toolNavigate,
105
106
  click: toolClick,
106
107
  type: toolType,
@@ -709,6 +710,12 @@ async function toolOpenTab({ url, active = true }) {
709
710
  return { tabId: tab.id, content: { tabId: tab.id, url: tab.url, active: tab.active } }
710
711
  }
711
712
 
713
+ async function toolCloseTab({ tabId }) {
714
+ if (!Number.isFinite(tabId)) throw new Error("tabId is required")
715
+ await chrome.tabs.remove(tabId)
716
+ return { tabId, content: { tabId, closed: true } }
717
+ }
718
+
712
719
  async function toolNavigate({ url, tabId }) {
713
720
  if (!url) throw new Error("URL is required")
714
721
  const tab = await getTabById(tabId)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@different-ai/opencode-browser",
3
- "version": "4.5.0",
3
+ "version": "4.5.1",
4
4
  "description": "Browser automation plugin for OpenCode (native messaging + per-tab ownership).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,15 +18,6 @@
18
18
  "extension",
19
19
  "README.md"
20
20
  ],
21
- "scripts": {
22
- "build": "bun build src/plugin.ts --target=node --outfile=dist/plugin.js",
23
- "prepublishOnly": "bun run build",
24
- "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' });\"",
25
- "install": "node bin/cli.js install",
26
- "uninstall": "node bin/cli.js uninstall",
27
- "status": "node bin/cli.js status",
28
- "tool-test": "bun bin/tool-test.ts"
29
- },
30
21
  "keywords": [
31
22
  "opencode",
32
23
  "browser",
@@ -54,5 +45,12 @@
54
45
  "devDependencies": {
55
46
  "@opencode-ai/plugin": "*",
56
47
  "bun-types": "*"
48
+ },
49
+ "scripts": {
50
+ "build": "bun build src/plugin.ts --target=node --outfile=dist/plugin.js",
51
+ "install": "node bin/cli.js install",
52
+ "uninstall": "node bin/cli.js uninstall",
53
+ "status": "node bin/cli.js status",
54
+ "tool-test": "bun bin/tool-test.ts"
57
55
  }
58
- }
56
+ }