@different-ai/opencode-browser 2.0.1 → 2.0.2
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/package.json +1 -1
- package/src/plugin.ts +78 -24
package/package.json
CHANGED
package/src/plugin.ts
CHANGED
|
@@ -34,6 +34,7 @@ let server: ReturnType<typeof Bun.serve> | null = null;
|
|
|
34
34
|
let pendingRequests = new Map<number, { resolve: (v: any) => void; reject: (e: Error) => void }>();
|
|
35
35
|
let requestId = 0;
|
|
36
36
|
let hasLock = false;
|
|
37
|
+
let serverFailed = false;
|
|
37
38
|
|
|
38
39
|
// ============================================================================
|
|
39
40
|
// Lock File Management
|
|
@@ -119,7 +120,7 @@ function sleep(ms: number): Promise<void> {
|
|
|
119
120
|
async function killSession(targetPid: number): Promise<{ success: boolean; error?: string }> {
|
|
120
121
|
try {
|
|
121
122
|
process.kill(targetPid, "SIGTERM");
|
|
122
|
-
// Wait
|
|
123
|
+
// Wait for process to die
|
|
123
124
|
let attempts = 0;
|
|
124
125
|
while (isProcessAlive(targetPid) && attempts < 10) {
|
|
125
126
|
await sleep(100);
|
|
@@ -141,7 +142,25 @@ async function killSession(targetPid: number): Promise<{ success: boolean; error
|
|
|
141
142
|
// WebSocket Server
|
|
142
143
|
// ============================================================================
|
|
143
144
|
|
|
145
|
+
function checkPortAvailable(): boolean {
|
|
146
|
+
try {
|
|
147
|
+
const testSocket = Bun.connect({ port: WS_PORT, timeout: 1000 });
|
|
148
|
+
testSocket.end();
|
|
149
|
+
return true;
|
|
150
|
+
} catch (e) {
|
|
151
|
+
if ((e as any).code === "ECONNREFUSED") {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
144
158
|
function startServer(): boolean {
|
|
159
|
+
if (server) {
|
|
160
|
+
console.error(`[browser-plugin] Server already running`);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
145
164
|
try {
|
|
146
165
|
server = Bun.serve({
|
|
147
166
|
port: WS_PORT,
|
|
@@ -171,6 +190,7 @@ function startServer(): boolean {
|
|
|
171
190
|
},
|
|
172
191
|
});
|
|
173
192
|
console.error(`[browser-plugin] WebSocket server listening on port ${WS_PORT}`);
|
|
193
|
+
serverFailed = false;
|
|
174
194
|
return true;
|
|
175
195
|
} catch (e) {
|
|
176
196
|
console.error(`[browser-plugin] Failed to start server:`, e);
|
|
@@ -178,7 +198,7 @@ function startServer(): boolean {
|
|
|
178
198
|
}
|
|
179
199
|
}
|
|
180
200
|
|
|
181
|
-
function handleMessage(message: { type: string; id?: number; result?: any; error?: any }) {
|
|
201
|
+
function handleMessage(message: { type: string; id?: number; result?: any; error?: any }): void {
|
|
182
202
|
if (message.type === "tool_response" && message.id !== undefined) {
|
|
183
203
|
const pending = pendingRequests.get(message.id);
|
|
184
204
|
if (pending) {
|
|
@@ -203,7 +223,7 @@ function sendToChrome(message: any): boolean {
|
|
|
203
223
|
}
|
|
204
224
|
|
|
205
225
|
async function executeCommand(tool: string, args: Record<string, any>): Promise<any> {
|
|
206
|
-
// Check lock
|
|
226
|
+
// Check lock and start server if needed
|
|
207
227
|
const lockResult = tryAcquireLock();
|
|
208
228
|
if (!lockResult.success) {
|
|
209
229
|
throw new Error(
|
|
@@ -211,7 +231,6 @@ async function executeCommand(tool: string, args: Record<string, any>): Promise<
|
|
|
211
231
|
);
|
|
212
232
|
}
|
|
213
233
|
|
|
214
|
-
// Start server if not running
|
|
215
234
|
if (!server) {
|
|
216
235
|
if (!startServer()) {
|
|
217
236
|
throw new Error("Failed to start WebSocket server. Port may be in use.");
|
|
@@ -273,12 +292,33 @@ process.on("exit", () => {
|
|
|
273
292
|
export const BrowserPlugin: Plugin = async (ctx) => {
|
|
274
293
|
console.error(`[browser-plugin] Initializing (session ${sessionId})`);
|
|
275
294
|
|
|
276
|
-
//
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
295
|
+
// Check port availability on load, don't try to acquire lock yet
|
|
296
|
+
checkPortAvailable();
|
|
297
|
+
|
|
298
|
+
// Check lock status and set appropriate state
|
|
299
|
+
const lock = readLock();
|
|
300
|
+
if (!lock) {
|
|
301
|
+
// No lock - just check if we can start server
|
|
302
|
+
console.error(`[browser-plugin] No lock file, checking port...`);
|
|
303
|
+
if (!startServer()) {
|
|
304
|
+
serverFailed = true;
|
|
305
|
+
}
|
|
306
|
+
} else if (lock.sessionId === sessionId) {
|
|
307
|
+
// We own the lock - start server
|
|
308
|
+
console.error(`[browser-plugin] Already have lock, starting server...`);
|
|
309
|
+
if (!startServer()) {
|
|
310
|
+
serverFailed = true;
|
|
311
|
+
}
|
|
312
|
+
} else if (!isProcessAlive(lock.pid)) {
|
|
313
|
+
// Stale lock - take it and start server
|
|
314
|
+
console.error(`[browser-plugin] Stale lock from dead PID ${lock.pid}, taking over...`);
|
|
315
|
+
writeLock();
|
|
316
|
+
if (!startServer()) {
|
|
317
|
+
serverFailed = true;
|
|
318
|
+
}
|
|
280
319
|
} else {
|
|
281
|
-
|
|
320
|
+
// Another session has the lock
|
|
321
|
+
console.error(`[browser-plugin] Lock held by PID ${lock.pid}, tools will fail until lock is released`);
|
|
282
322
|
}
|
|
283
323
|
|
|
284
324
|
return {
|
|
@@ -316,7 +356,12 @@ export const BrowserPlugin: Plugin = async (ctx) => {
|
|
|
316
356
|
if (!lock) {
|
|
317
357
|
// No lock, just acquire
|
|
318
358
|
writeLock();
|
|
319
|
-
|
|
359
|
+
// Start server if needed
|
|
360
|
+
if (!server) {
|
|
361
|
+
if (!startServer()) {
|
|
362
|
+
throw new Error("Failed to start WebSocket server after acquiring lock.");
|
|
363
|
+
}
|
|
364
|
+
}
|
|
320
365
|
return "No active session. Browser now connected to this session.";
|
|
321
366
|
}
|
|
322
367
|
|
|
@@ -327,14 +372,23 @@ export const BrowserPlugin: Plugin = async (ctx) => {
|
|
|
327
372
|
if (!isProcessAlive(lock.pid)) {
|
|
328
373
|
// Stale lock
|
|
329
374
|
writeLock();
|
|
330
|
-
|
|
375
|
+
// Start server if needed
|
|
376
|
+
if (!server) {
|
|
377
|
+
if (!startServer()) {
|
|
378
|
+
throw new Error("Failed to start WebSocket server after cleaning stale lock.");
|
|
379
|
+
}
|
|
380
|
+
}
|
|
331
381
|
return `Cleaned stale lock (PID ${lock.pid} was dead). Browser now connected to this session.`;
|
|
332
382
|
}
|
|
333
383
|
|
|
334
|
-
// Kill
|
|
384
|
+
// Kill other session and wait for port to be free
|
|
335
385
|
const result = await killSession(lock.pid);
|
|
336
386
|
if (result.success) {
|
|
337
|
-
if (!server)
|
|
387
|
+
if (!server) {
|
|
388
|
+
if (!startServer()) {
|
|
389
|
+
throw new Error("Failed to start WebSocket server after killing other session.");
|
|
390
|
+
}
|
|
391
|
+
}
|
|
338
392
|
return `Killed session ${lock.sessionId} (PID ${lock.pid}). Browser now connected to this session.`;
|
|
339
393
|
} else {
|
|
340
394
|
throw new Error(`Failed to kill session: ${result.error}`);
|
|
@@ -343,7 +397,7 @@ export const BrowserPlugin: Plugin = async (ctx) => {
|
|
|
343
397
|
}),
|
|
344
398
|
|
|
345
399
|
browser_navigate: tool({
|
|
346
|
-
description: "Navigate to a URL in
|
|
400
|
+
description: "Navigate to a URL in browser",
|
|
347
401
|
args: {
|
|
348
402
|
url: tool.schema.string({ description: "The URL to navigate to" }),
|
|
349
403
|
tabId: tool.schema.optional(tool.schema.number({ description: "Optional tab ID" })),
|
|
@@ -354,9 +408,9 @@ export const BrowserPlugin: Plugin = async (ctx) => {
|
|
|
354
408
|
}),
|
|
355
409
|
|
|
356
410
|
browser_click: tool({
|
|
357
|
-
description: "Click an element on
|
|
411
|
+
description: "Click an element on page using a CSS selector",
|
|
358
412
|
args: {
|
|
359
|
-
selector: tool.schema.string({ description: "CSS selector for
|
|
413
|
+
selector: tool.schema.string({ description: "CSS selector for element to click" }),
|
|
360
414
|
tabId: tool.schema.optional(tool.schema.number({ description: "Optional tab ID" })),
|
|
361
415
|
},
|
|
362
416
|
async execute(args) {
|
|
@@ -367,7 +421,7 @@ export const BrowserPlugin: Plugin = async (ctx) => {
|
|
|
367
421
|
browser_type: tool({
|
|
368
422
|
description: "Type text into an input element",
|
|
369
423
|
args: {
|
|
370
|
-
selector: tool.schema.string({ description: "CSS selector for
|
|
424
|
+
selector: tool.schema.string({ description: "CSS selector for input element" }),
|
|
371
425
|
text: tool.schema.string({ description: "Text to type" }),
|
|
372
426
|
clear: tool.schema.optional(tool.schema.boolean({ description: "Clear field before typing" })),
|
|
373
427
|
tabId: tool.schema.optional(tool.schema.number({ description: "Optional tab ID" })),
|
|
@@ -378,26 +432,26 @@ export const BrowserPlugin: Plugin = async (ctx) => {
|
|
|
378
432
|
}),
|
|
379
433
|
|
|
380
434
|
browser_screenshot: tool({
|
|
381
|
-
description: "Take a screenshot of the current page. Saves to ~/.opencode-browser/screenshots/
|
|
435
|
+
description: "Take a screenshot of the current page. Saves to ~/.opencode-browser/screenshots/",
|
|
382
436
|
args: {
|
|
383
437
|
tabId: tool.schema.optional(tool.schema.number({ description: "Optional tab ID" })),
|
|
384
|
-
name: tool.schema.optional(
|
|
438
|
+
name: tool.schema.optional(
|
|
439
|
+
tool.schema.string({ description: "Optional name for screenshot file (without extension)" })
|
|
440
|
+
),
|
|
385
441
|
},
|
|
386
442
|
async execute(args) {
|
|
387
443
|
const result = await executeCommand("screenshot", args);
|
|
388
|
-
|
|
444
|
+
|
|
389
445
|
if (result && result.startsWith("data:image")) {
|
|
390
|
-
// Extract base64 data and save to file
|
|
391
446
|
const base64Data = result.replace(/^data:image\/\w+;base64,/, "");
|
|
392
447
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
393
448
|
const filename = args.name ? `${args.name}.png` : `screenshot-${timestamp}.png`;
|
|
394
449
|
const filepath = join(SCREENSHOTS_DIR, filename);
|
|
395
|
-
|
|
450
|
+
|
|
396
451
|
writeFileSync(filepath, Buffer.from(base64Data, "base64"));
|
|
397
|
-
|
|
398
452
|
return `Screenshot saved: ${filepath}`;
|
|
399
453
|
}
|
|
400
|
-
|
|
454
|
+
|
|
401
455
|
return result;
|
|
402
456
|
},
|
|
403
457
|
}),
|