@different-ai/opencode-browser 4.4.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.
- package/.opencode/skill/github-release/SKILL.md +12 -0
- package/README.md +13 -6
- package/bin/broker.cjs +162 -14
- package/dist/plugin.js +53 -0
- package/extension/background.js +7 -0
- package/package.json +9 -9
|
@@ -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,16 +111,22 @@ export OPENCODE_BROWSER_AGENT_PORT=9833
|
|
|
111
111
|
|
|
112
112
|
## Per-tab ownership
|
|
113
113
|
|
|
114
|
-
-
|
|
115
|
-
-
|
|
116
|
-
-
|
|
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
|
+
- Claims expire after inactivity (`OPENCODE_BROWSER_CLAIM_TTL_MS`, default 5 minutes).
|
|
118
|
+
- Use `browser_status` or `browser_list_claims` for debugging.
|
|
117
119
|
|
|
118
120
|
## Available tools
|
|
119
121
|
|
|
120
122
|
Core primitives:
|
|
121
123
|
- `browser_status`
|
|
122
124
|
- `browser_get_tabs`
|
|
125
|
+
- `browser_list_claims`
|
|
126
|
+
- `browser_claim_tab`
|
|
127
|
+
- `browser_release_tab`
|
|
123
128
|
- `browser_open_tab`
|
|
129
|
+
- `browser_close_tab`
|
|
124
130
|
- `browser_navigate`
|
|
125
131
|
- `browser_query` (modes: `text`, `value`, `list`, `exists`, `page_text`; optional `timeoutMs`/`pollMs`)
|
|
126
132
|
- `browser_click` (optional `timeoutMs`/`pollMs`)
|
|
@@ -144,7 +150,7 @@ Diagnostics:
|
|
|
144
150
|
|
|
145
151
|
## Roadmap
|
|
146
152
|
|
|
147
|
-
- [ ] Add tab management tools (`browser_set_active_tab
|
|
153
|
+
- [ ] Add tab management tools (`browser_set_active_tab`)
|
|
148
154
|
- [ ] Add navigation helpers (`browser_back`, `browser_forward`, `browser_reload`)
|
|
149
155
|
- [ ] Add keyboard input tool (`browser_key`)
|
|
150
156
|
- [ ] Add download support (`browser_download`, `browser_list_downloads`)
|
|
@@ -157,8 +163,9 @@ Diagnostics:
|
|
|
157
163
|
- If you loaded a custom extension ID, rerun with `--extension-id <id>`
|
|
158
164
|
|
|
159
165
|
**Tab ownership errors**
|
|
160
|
-
-
|
|
161
|
-
-
|
|
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
|
|
162
169
|
|
|
163
170
|
## Uninstall
|
|
164
171
|
|
package/bin/broker.cjs
CHANGED
|
@@ -11,6 +11,20 @@ const SOCKET_PATH = path.join(BASE_DIR, "broker.sock");
|
|
|
11
11
|
|
|
12
12
|
fs.mkdirSync(BASE_DIR, { recursive: true });
|
|
13
13
|
|
|
14
|
+
const DEFAULT_LEASE_TTL_MS = 5 * 60 * 1000;
|
|
15
|
+
const LEASE_TTL_MS = (() => {
|
|
16
|
+
const raw = process.env.OPENCODE_BROWSER_CLAIM_TTL_MS;
|
|
17
|
+
const value = Number(raw);
|
|
18
|
+
if (Number.isFinite(value) && value >= 0) return value;
|
|
19
|
+
return DEFAULT_LEASE_TTL_MS;
|
|
20
|
+
})();
|
|
21
|
+
const LEASE_SWEEP_MS =
|
|
22
|
+
LEASE_TTL_MS > 0 ? Math.min(Math.max(10000, Math.floor(LEASE_TTL_MS / 2)), 60000) : 0;
|
|
23
|
+
|
|
24
|
+
function nowMs() {
|
|
25
|
+
return Date.now();
|
|
26
|
+
}
|
|
27
|
+
|
|
14
28
|
function nowIso() {
|
|
15
29
|
return new Date().toISOString();
|
|
16
30
|
}
|
|
@@ -39,7 +53,7 @@ function writeJsonLine(socket, msg) {
|
|
|
39
53
|
}
|
|
40
54
|
|
|
41
55
|
function wantsTab(toolName) {
|
|
42
|
-
return !["get_tabs", "get_active_tab"].includes(toolName);
|
|
56
|
+
return !["get_tabs", "get_active_tab", "open_tab"].includes(toolName);
|
|
43
57
|
}
|
|
44
58
|
|
|
45
59
|
// --- State ---
|
|
@@ -49,22 +63,78 @@ const extPending = new Map(); // extId -> { pluginSocket, pluginRequestId, sessi
|
|
|
49
63
|
|
|
50
64
|
const clients = new Set();
|
|
51
65
|
|
|
52
|
-
// Tab ownership: tabId -> { sessionId, claimedAt }
|
|
66
|
+
// Tab ownership: tabId -> { sessionId, claimedAt, lastSeenAt }
|
|
53
67
|
const claims = new Map();
|
|
68
|
+
// Session state: sessionId -> { defaultTabId, lastSeenAt }
|
|
69
|
+
const sessionState = new Map();
|
|
54
70
|
|
|
55
71
|
function listClaims() {
|
|
56
72
|
const out = [];
|
|
57
73
|
for (const [tabId, info] of claims.entries()) {
|
|
58
|
-
out.push({
|
|
74
|
+
out.push({
|
|
75
|
+
tabId,
|
|
76
|
+
sessionId: info.sessionId,
|
|
77
|
+
claimedAt: info.claimedAt,
|
|
78
|
+
lastSeenAt: new Date(info.lastSeenAt).toISOString(),
|
|
79
|
+
});
|
|
59
80
|
}
|
|
60
81
|
out.sort((a, b) => a.tabId - b.tabId);
|
|
61
82
|
return out;
|
|
62
83
|
}
|
|
63
84
|
|
|
85
|
+
function sessionHasClaims(sessionId) {
|
|
86
|
+
for (const info of claims.values()) {
|
|
87
|
+
if (info.sessionId === sessionId) return true;
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getSessionState(sessionId) {
|
|
93
|
+
if (!sessionId) return null;
|
|
94
|
+
let state = sessionState.get(sessionId);
|
|
95
|
+
if (!state) {
|
|
96
|
+
state = { defaultTabId: null, lastSeenAt: nowMs() };
|
|
97
|
+
sessionState.set(sessionId, state);
|
|
98
|
+
}
|
|
99
|
+
return state;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function touchSession(sessionId) {
|
|
103
|
+
const state = getSessionState(sessionId);
|
|
104
|
+
if (!state) return null;
|
|
105
|
+
state.lastSeenAt = nowMs();
|
|
106
|
+
return state;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function setDefaultTab(sessionId, tabId) {
|
|
110
|
+
const state = getSessionState(sessionId);
|
|
111
|
+
if (!state) return;
|
|
112
|
+
state.defaultTabId = tabId;
|
|
113
|
+
state.lastSeenAt = nowMs();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function clearDefaultTab(sessionId, tabId) {
|
|
117
|
+
const state = sessionState.get(sessionId);
|
|
118
|
+
if (!state) return;
|
|
119
|
+
if (tabId === undefined || state.defaultTabId === tabId) {
|
|
120
|
+
state.defaultTabId = null;
|
|
121
|
+
}
|
|
122
|
+
state.lastSeenAt = nowMs();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function releaseClaim(tabId) {
|
|
126
|
+
const info = claims.get(tabId);
|
|
127
|
+
if (!info) return;
|
|
128
|
+
claims.delete(tabId);
|
|
129
|
+
clearDefaultTab(info.sessionId, tabId);
|
|
130
|
+
}
|
|
131
|
+
|
|
64
132
|
function releaseClaimsForSession(sessionId) {
|
|
65
133
|
for (const [tabId, info] of claims.entries()) {
|
|
66
134
|
if (info.sessionId === sessionId) claims.delete(tabId);
|
|
67
135
|
}
|
|
136
|
+
clearDefaultTab(sessionId);
|
|
137
|
+
sessionState.delete(sessionId);
|
|
68
138
|
}
|
|
69
139
|
|
|
70
140
|
function checkClaim(tabId, sessionId) {
|
|
@@ -75,7 +145,37 @@ function checkClaim(tabId, sessionId) {
|
|
|
75
145
|
}
|
|
76
146
|
|
|
77
147
|
function setClaim(tabId, sessionId) {
|
|
78
|
-
claims.
|
|
148
|
+
const existing = claims.get(tabId);
|
|
149
|
+
claims.set(tabId, {
|
|
150
|
+
sessionId,
|
|
151
|
+
claimedAt: existing ? existing.claimedAt : nowIso(),
|
|
152
|
+
lastSeenAt: nowMs(),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function touchClaim(tabId, sessionId) {
|
|
157
|
+
const existing = claims.get(tabId);
|
|
158
|
+
if (existing && existing.sessionId !== sessionId) return;
|
|
159
|
+
if (existing) {
|
|
160
|
+
existing.lastSeenAt = nowMs();
|
|
161
|
+
} else {
|
|
162
|
+
setClaim(tabId, sessionId);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function cleanupStaleClaims() {
|
|
167
|
+
if (!LEASE_TTL_MS) return;
|
|
168
|
+
const now = nowMs();
|
|
169
|
+
for (const [tabId, info] of claims.entries()) {
|
|
170
|
+
if (now - info.lastSeenAt > LEASE_TTL_MS) {
|
|
171
|
+
releaseClaim(tabId);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
for (const [sessionId, state] of sessionState.entries()) {
|
|
175
|
+
if (!sessionHasClaims(sessionId) && now - state.lastSeenAt > LEASE_TTL_MS) {
|
|
176
|
+
sessionState.delete(sessionId);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
79
179
|
}
|
|
80
180
|
|
|
81
181
|
function ensureHost() {
|
|
@@ -106,10 +206,13 @@ function callExtension(tool, args, sessionId) {
|
|
|
106
206
|
});
|
|
107
207
|
}
|
|
108
208
|
|
|
109
|
-
async function
|
|
110
|
-
|
|
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);
|
|
111
212
|
const tabId = res && typeof res.tabId === "number" ? res.tabId : undefined;
|
|
112
|
-
if (!tabId) throw new Error("
|
|
213
|
+
if (!tabId) throw new Error("Failed to create a new tab for this session");
|
|
214
|
+
touchClaim(tabId, sessionId);
|
|
215
|
+
setDefaultTab(sessionId, tabId);
|
|
113
216
|
return tabId;
|
|
114
217
|
}
|
|
115
218
|
|
|
@@ -117,25 +220,45 @@ async function handleTool(pluginSocket, req) {
|
|
|
117
220
|
const { tool, args = {}, sessionId } = req;
|
|
118
221
|
if (!tool) throw new Error("Missing tool");
|
|
119
222
|
|
|
223
|
+
if (sessionId) touchSession(sessionId);
|
|
224
|
+
|
|
120
225
|
let tabId = args.tabId;
|
|
226
|
+
const toolArgs = { ...args };
|
|
227
|
+
|
|
228
|
+
const isCloseTool = tool === "close_tab";
|
|
121
229
|
|
|
122
230
|
if (wantsTab(tool)) {
|
|
123
231
|
if (typeof tabId !== "number") {
|
|
124
|
-
|
|
232
|
+
const state = getSessionState(sessionId);
|
|
233
|
+
const defaultTabId = state && Number.isFinite(state.defaultTabId) ? state.defaultTabId : null;
|
|
234
|
+
if (Number.isFinite(defaultTabId)) {
|
|
235
|
+
tabId = defaultTabId;
|
|
236
|
+
} else if (!isCloseTool) {
|
|
237
|
+
tabId = await ensureSessionTab(sessionId);
|
|
238
|
+
} else {
|
|
239
|
+
throw new Error("No tab owned by this session. Open a new tab first.");
|
|
240
|
+
}
|
|
125
241
|
}
|
|
126
242
|
|
|
127
243
|
const claimCheck = checkClaim(tabId, sessionId);
|
|
128
244
|
if (!claimCheck.ok) throw new Error(claimCheck.error);
|
|
129
245
|
}
|
|
130
246
|
|
|
131
|
-
const res = await callExtension(tool, { ...
|
|
247
|
+
const res = await callExtension(tool, { ...toolArgs, tabId }, sessionId);
|
|
132
248
|
|
|
133
249
|
const usedTabId =
|
|
134
250
|
res && typeof res.tabId === "number" ? res.tabId : typeof tabId === "number" ? tabId : undefined;
|
|
135
251
|
if (typeof usedTabId === "number") {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
+
}
|
|
139
262
|
}
|
|
140
263
|
|
|
141
264
|
return res;
|
|
@@ -145,6 +268,7 @@ function handleClientMessage(socket, client, msg) {
|
|
|
145
268
|
if (msg && msg.type === "hello") {
|
|
146
269
|
client.role = msg.role || "unknown";
|
|
147
270
|
client.sessionId = msg.sessionId;
|
|
271
|
+
if (client.sessionId) touchSession(client.sessionId);
|
|
148
272
|
if (client.role === "native-host") {
|
|
149
273
|
host = { socket };
|
|
150
274
|
// allow host to see current state
|
|
@@ -174,6 +298,7 @@ function handleClientMessage(socket, client, msg) {
|
|
|
174
298
|
if (msg && msg.type === "request" && typeof msg.id === "number") {
|
|
175
299
|
const requestId = msg.id;
|
|
176
300
|
const sessionId = msg.sessionId || client.sessionId;
|
|
301
|
+
if (sessionId) touchSession(sessionId);
|
|
177
302
|
|
|
178
303
|
const replyOk = (data) => writeJsonLine(socket, { type: "response", id: requestId, ok: true, data });
|
|
179
304
|
const replyErr = (err) =>
|
|
@@ -182,7 +307,21 @@ function handleClientMessage(socket, client, msg) {
|
|
|
182
307
|
(async () => {
|
|
183
308
|
try {
|
|
184
309
|
if (msg.op === "status") {
|
|
185
|
-
|
|
310
|
+
const state = sessionId ? sessionState.get(sessionId) : null;
|
|
311
|
+
const sessionInfo = state
|
|
312
|
+
? {
|
|
313
|
+
sessionId,
|
|
314
|
+
defaultTabId: state.defaultTabId,
|
|
315
|
+
lastSeenAt: new Date(state.lastSeenAt).toISOString(),
|
|
316
|
+
}
|
|
317
|
+
: null;
|
|
318
|
+
replyOk({
|
|
319
|
+
broker: true,
|
|
320
|
+
hostConnected: !!host && !!host.socket && !host.socket.destroyed,
|
|
321
|
+
claims: listClaims(),
|
|
322
|
+
leaseTtlMs: LEASE_TTL_MS,
|
|
323
|
+
session: sessionInfo,
|
|
324
|
+
});
|
|
186
325
|
return;
|
|
187
326
|
}
|
|
188
327
|
|
|
@@ -199,7 +338,11 @@ function handleClientMessage(socket, client, msg) {
|
|
|
199
338
|
if (existing && existing.sessionId !== sessionId && !force) {
|
|
200
339
|
throw new Error(`Tab ${tabId} is owned by another OpenCode session (${existing.sessionId})`);
|
|
201
340
|
}
|
|
341
|
+
if (existing && existing.sessionId !== sessionId && force) {
|
|
342
|
+
clearDefaultTab(existing.sessionId, tabId);
|
|
343
|
+
}
|
|
202
344
|
setClaim(tabId, sessionId);
|
|
345
|
+
setDefaultTab(sessionId, tabId);
|
|
203
346
|
replyOk({ ok: true, tabId, sessionId });
|
|
204
347
|
return;
|
|
205
348
|
}
|
|
@@ -215,7 +358,7 @@ function handleClientMessage(socket, client, msg) {
|
|
|
215
358
|
if (existing.sessionId !== sessionId) {
|
|
216
359
|
throw new Error(`Tab ${tabId} is owned by another OpenCode session (${existing.sessionId})`);
|
|
217
360
|
}
|
|
218
|
-
|
|
361
|
+
releaseClaim(tabId);
|
|
219
362
|
replyOk({ ok: true, tabId, released: true });
|
|
220
363
|
return;
|
|
221
364
|
}
|
|
@@ -287,4 +430,9 @@ function start() {
|
|
|
287
430
|
});
|
|
288
431
|
}
|
|
289
432
|
|
|
433
|
+
if (LEASE_TTL_MS > 0 && LEASE_SWEEP_MS > 0) {
|
|
434
|
+
const timer = setInterval(cleanupStaleClaims, LEASE_SWEEP_MS);
|
|
435
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
436
|
+
}
|
|
437
|
+
|
|
290
438
|
start();
|
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)
|
|
@@ -13140,6 +13148,12 @@ async function brokerRequest(op, payload) {
|
|
|
13140
13148
|
}, 60000);
|
|
13141
13149
|
});
|
|
13142
13150
|
}
|
|
13151
|
+
async function brokerOnlyRequest(op, payload) {
|
|
13152
|
+
if (USE_AGENT_BACKEND) {
|
|
13153
|
+
throw new Error("Tab claims are not supported with agent-browser backend");
|
|
13154
|
+
}
|
|
13155
|
+
return await brokerRequest(op, payload);
|
|
13156
|
+
}
|
|
13143
13157
|
function toolResultText(data, fallback) {
|
|
13144
13158
|
if (typeof data?.content === "string")
|
|
13145
13159
|
return data.content;
|
|
@@ -13222,6 +13236,35 @@ var plugin = async (ctx) => {
|
|
|
13222
13236
|
return toolResultText(data, "ok");
|
|
13223
13237
|
}
|
|
13224
13238
|
}),
|
|
13239
|
+
browser_list_claims: tool({
|
|
13240
|
+
description: "List tab ownership claims",
|
|
13241
|
+
args: {},
|
|
13242
|
+
async execute(args, ctx2) {
|
|
13243
|
+
const data = await brokerOnlyRequest("list_claims", {});
|
|
13244
|
+
return JSON.stringify(data);
|
|
13245
|
+
}
|
|
13246
|
+
}),
|
|
13247
|
+
browser_claim_tab: tool({
|
|
13248
|
+
description: "Claim a browser tab for this session",
|
|
13249
|
+
args: {
|
|
13250
|
+
tabId: schema.number(),
|
|
13251
|
+
force: schema.boolean().optional()
|
|
13252
|
+
},
|
|
13253
|
+
async execute({ tabId, force }, ctx2) {
|
|
13254
|
+
const data = await brokerOnlyRequest("claim_tab", { tabId, force });
|
|
13255
|
+
return JSON.stringify(data);
|
|
13256
|
+
}
|
|
13257
|
+
}),
|
|
13258
|
+
browser_release_tab: tool({
|
|
13259
|
+
description: "Release a claimed browser tab",
|
|
13260
|
+
args: {
|
|
13261
|
+
tabId: schema.number()
|
|
13262
|
+
},
|
|
13263
|
+
async execute({ tabId }, ctx2) {
|
|
13264
|
+
const data = await brokerOnlyRequest("release_tab", { tabId });
|
|
13265
|
+
return JSON.stringify(data);
|
|
13266
|
+
}
|
|
13267
|
+
}),
|
|
13225
13268
|
browser_open_tab: tool({
|
|
13226
13269
|
description: "Open a new browser tab",
|
|
13227
13270
|
args: {
|
|
@@ -13233,6 +13276,16 @@ var plugin = async (ctx) => {
|
|
|
13233
13276
|
return toolResultText(data, "Opened new tab");
|
|
13234
13277
|
}
|
|
13235
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
|
+
}),
|
|
13236
13289
|
browser_navigate: tool({
|
|
13237
13290
|
description: "Navigate to a URL in the browser",
|
|
13238
13291
|
args: {
|
package/extension/background.js
CHANGED
|
@@ -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.
|
|
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,13 +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
|
-
"install": "node bin/cli.js install",
|
|
24
|
-
"uninstall": "node bin/cli.js uninstall",
|
|
25
|
-
"status": "node bin/cli.js status",
|
|
26
|
-
"tool-test": "bun bin/tool-test.ts"
|
|
27
|
-
},
|
|
28
21
|
"keywords": [
|
|
29
22
|
"opencode",
|
|
30
23
|
"browser",
|
|
@@ -52,5 +45,12 @@
|
|
|
52
45
|
"devDependencies": {
|
|
53
46
|
"@opencode-ai/plugin": "*",
|
|
54
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"
|
|
55
55
|
}
|
|
56
|
-
}
|
|
56
|
+
}
|