openzoo 0.50.57 → 0.50.59
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/lib/cursorbackend.js +3 -2
- package/lib/mcpbridge.js +103 -10
- package/package.json +1 -1
package/lib/cursorbackend.js
CHANGED
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
import {
|
|
54
54
|
desktopAction, displayBounds, imageSize, noteShotMeta, resolveAppName,
|
|
55
55
|
} from './grokbotDesktop.js';
|
|
56
|
-
import { startHostMcps, hostMcpTools, hostMcpHas, callHostMcp, hostMcpServers } from './mcpbridge.js';
|
|
56
|
+
import { startHostMcps, hostMcpTools, hostMcpHas, callHostMcp, hostMcpServers, chromeStatus, reattachChrome } from './mcpbridge.js';
|
|
57
57
|
import * as ship from './ship.js';
|
|
58
58
|
|
|
59
59
|
const TLS_DIR = path.join(os.homedir(), '.openzoo', 'cursor-tls');
|
|
@@ -2572,6 +2572,7 @@ async function zooComplete(prompt, log, agentId, parsed = {}, opts = {}) {
|
|
|
2572
2572
|
if (opts.signal?.aborted) throw new Error('superseded');
|
|
2573
2573
|
};
|
|
2574
2574
|
await ensureTranscriptHydrated(agentId, log);
|
|
2575
|
+
try { await reattachChrome({ log }); } catch (e) { log(`cursor-backend: chrome reattach ${e.message}`); }
|
|
2575
2576
|
log(`cursor-backend: zoo POST :8402 model=${model} helper=${helper ? localExecSse.size : 0} hist=${historyMessages(agentId, spoken).length}${chatOnly ? ` visitor=${visitor.shortname} chat-only` : ''} ${JSON.stringify((spoken || '').slice(0, 60))}`);
|
|
2576
2577
|
if (typeof opts.onProgress === 'function') opts.onProgress('Working on your Mac…');
|
|
2577
2578
|
|
|
@@ -2649,7 +2650,7 @@ async function zooComplete(prompt, log, agentId, parsed = {}, opts = {}) {
|
|
|
2649
2650
|
return [
|
|
2650
2651
|
`Host MCP tools from the user's local Claude/Grok config are attached (${servers.join(', ') || 'mcp'}): ${names.slice(0, 36).join(', ')}${names.length > 36 ? '…' : ''}.`,
|
|
2651
2652
|
chrome.length
|
|
2652
|
-
?
|
|
2653
|
+
? `For any web page or HTML form, use chrome-devtools tools (navigate_page / take_snapshot / fill / click). Do NOT use osascript, Quartz, Python Foundation, or AppleScript to read Brave. screenshot/click/type_text are for native Mac UI only. Browser mode: ${chromeStatus().mode}.${chromeStatus().hint ? ` This Chrome is NOT the human's browser: a separate profile with no logins. If the human asks for their logged-in Chrome / Google / X / "the same chrome", do NOT open pages in this one and do NOT spawn anything — reply with exactly this and stop: ${chromeStatus().hint}` : ' This is the human\'s real logged-in browser; do not log out or change its settings.'}`
|
|
2653
2654
|
: 'Use matching MCP tools instead of inventing shell one-liners.',
|
|
2654
2655
|
].join(' ');
|
|
2655
2656
|
})(),
|
package/lib/mcpbridge.js
CHANGED
|
@@ -20,6 +20,8 @@ const registry = new Map();
|
|
|
20
20
|
let openaiTools = [];
|
|
21
21
|
let started = null;
|
|
22
22
|
let serversUp = [];
|
|
23
|
+
let chromeCfg = null;
|
|
24
|
+
let lastReattachCheck = 0;
|
|
23
25
|
|
|
24
26
|
export function hostMcpTools() {
|
|
25
27
|
return openaiTools;
|
|
@@ -240,16 +242,60 @@ function portOpen(port, host = '127.0.0.1', ms = 150) {
|
|
|
240
242
|
});
|
|
241
243
|
}
|
|
242
244
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
245
|
+
/**
|
|
246
|
+
* "Chrome with Claude" for the hijack. Preference order:
|
|
247
|
+
* 1. the user's REAL Chrome (their logins): Chrome 144+ writes
|
|
248
|
+
* DevToolsActivePort under its user-data-dir once the human flips
|
|
249
|
+
* chrome://inspect/#remote-debugging -> "Allow remote debugging for this
|
|
250
|
+
* browser". chrome-devtools-mcp attaches with --autoConnect.
|
|
251
|
+
* 2. the user's real Brave the same way (Brave exposes a port, not the
|
|
252
|
+
* channel autoConnect expects) -> --browserUrl to that port.
|
|
253
|
+
* 3. any browser already listening on 9222/9333 -> --browserUrl.
|
|
254
|
+
* 4. chrome-devtools-mcp's own persistent profile (log in there once).
|
|
255
|
+
* Chrome 136+ refuses --remote-debugging-port on the default profile, so
|
|
256
|
+
* relaunching the user's browser with a flag is not an option — the toggle is.
|
|
257
|
+
*/
|
|
258
|
+
export const CHROME_TOGGLE_HINT = 'To let bots drive your real Chrome (your logins): open chrome://inspect/#remote-debugging in Chrome, turn on "Allow remote debugging for this browser", then restart `openzoo bot`. Until then the bot drives its own Chrome profile — log into sites there once and it persists.';
|
|
259
|
+
|
|
260
|
+
let chromeMode = { mode: 'own-profile', detail: '' };
|
|
261
|
+
export function chromeStatus() {
|
|
262
|
+
return { ...chromeMode, hint: chromeMode.mode === 'own-profile' ? CHROME_TOGGLE_HINT : '' };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function readActivePort(dir) {
|
|
266
|
+
try {
|
|
267
|
+
const first = fs.readFileSync(path.join(dir, 'DevToolsActivePort'), 'utf8').split('\n')[0].trim();
|
|
268
|
+
const n = Number(first);
|
|
269
|
+
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
270
|
+
} catch {
|
|
271
|
+
return 0;
|
|
251
272
|
}
|
|
252
|
-
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** Pure: decide the chrome-devtools-mcp argv from what is observable. */
|
|
276
|
+
export function chromeArgsFor(baseArgs, { chromePort = 0, bravePort = 0, openPorts = [] } = {}) {
|
|
277
|
+
const args = [...(baseArgs || ['-y', 'chrome-devtools-mcp@latest'])];
|
|
278
|
+
const has = (flag) => args.some((a) => String(a) === flag || String(a).startsWith(`${flag}=`));
|
|
279
|
+
if (has('--browserUrl') || has('--autoConnect') || has('--wsEndpoint')) return { args, mode: 'explicit', detail: '' };
|
|
280
|
+
if (chromePort) { args.push('--autoConnect'); return { args, mode: 'real-chrome', detail: `Chrome DevToolsActivePort ${chromePort}` }; }
|
|
281
|
+
if (bravePort) { args.push('--browserUrl', `http://127.0.0.1:${bravePort}`); return { args, mode: 'real-brave', detail: `Brave DevToolsActivePort ${bravePort}` }; }
|
|
282
|
+
const port = openPorts.find((n) => n === 9222 || n === 9333);
|
|
283
|
+
if (port) { args.push('--browserUrl', `http://127.0.0.1:${port}`); return { args, mode: `attached:${port}`, detail: `browser listening on ${port}` }; }
|
|
284
|
+
return { args, mode: 'own-profile', detail: '~/.cache/chrome-devtools-mcp/chrome-profile' };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function chromeArgs(baseArgs, home = os.homedir()) {
|
|
288
|
+
const chromeDir = path.join(home, 'Library', 'Application Support', 'Google', 'Chrome');
|
|
289
|
+
const braveDir = path.join(home, 'Library', 'Application Support', 'BraveSoftware', 'Brave-Browser');
|
|
290
|
+
let chromePort = readActivePort(chromeDir);
|
|
291
|
+
if (chromePort && !(await portOpen(chromePort))) chromePort = 0;
|
|
292
|
+
let bravePort = readActivePort(braveDir);
|
|
293
|
+
if (bravePort && !(await portOpen(bravePort))) bravePort = 0;
|
|
294
|
+
const openPorts = [];
|
|
295
|
+
for (const port of [9222, 9333]) if (await portOpen(port)) openPorts.push(port);
|
|
296
|
+
const got = chromeArgsFor(baseArgs, { chromePort, bravePort, openPorts });
|
|
297
|
+
chromeMode = { mode: got.mode, detail: got.detail };
|
|
298
|
+
return got.args;
|
|
253
299
|
}
|
|
254
300
|
|
|
255
301
|
async function connectOne(cfg, log) {
|
|
@@ -263,7 +309,11 @@ async function connectOne(cfg, log) {
|
|
|
263
309
|
});
|
|
264
310
|
} else {
|
|
265
311
|
let args = cfg.args || [];
|
|
266
|
-
if (cfg.name === 'chrome-devtools')
|
|
312
|
+
if (cfg.name === 'chrome-devtools') {
|
|
313
|
+
args = await chromeArgs(args);
|
|
314
|
+
log?.(`cursor-backend: mcp chrome-devtools mode=${chromeMode.mode} ${chromeMode.detail}`);
|
|
315
|
+
if (chromeMode.mode === 'own-profile') log?.(`cursor-backend: mcp chrome-devtools ${CHROME_TOGGLE_HINT}`);
|
|
316
|
+
}
|
|
267
317
|
const env = { ...getDefaultEnvironment(), PATH: process.env.PATH || '', ...(cfg.env || {}) };
|
|
268
318
|
if (process.env.NVM_DIR) env.NVM_DIR = process.env.NVM_DIR;
|
|
269
319
|
transport = new StdioClientTransport({
|
|
@@ -289,6 +339,48 @@ async function connectOne(cfg, log) {
|
|
|
289
339
|
return { client, tools };
|
|
290
340
|
}
|
|
291
341
|
|
|
342
|
+
/**
|
|
343
|
+
* The human flipped chrome://inspect/#remote-debugging AFTER boot: Chrome
|
|
344
|
+
* writes DevToolsActivePort immediately. Swap the blank-profile
|
|
345
|
+
* chrome-devtools for one attached to their real browser, no restart.
|
|
346
|
+
* Cheap (two stats) — called at the top of every zoo turn, throttled 5s.
|
|
347
|
+
*/
|
|
348
|
+
export async function reattachChrome({ log = () => {}, home = os.homedir() } = {}) {
|
|
349
|
+
if (!chromeCfg || !started) return false;
|
|
350
|
+
if (/^(real-chrome|real-brave|explicit)$/.test(chromeMode.mode)) return false;
|
|
351
|
+
const now = Date.now();
|
|
352
|
+
if (now - lastReattachCheck < 5000) return false;
|
|
353
|
+
lastReattachCheck = now;
|
|
354
|
+
const chromeDir = path.join(home, 'Library', 'Application Support', 'Google', 'Chrome');
|
|
355
|
+
const braveDir = path.join(home, 'Library', 'Application Support', 'BraveSoftware', 'Brave-Browser');
|
|
356
|
+
const chromePort = readActivePort(chromeDir);
|
|
357
|
+
const bravePort = readActivePort(braveDir);
|
|
358
|
+
const port = chromePort || bravePort;
|
|
359
|
+
if (!port || !(await portOpen(port))) return false;
|
|
360
|
+
log(`cursor-backend: mcp chrome-devtools real browser appeared on ${port} — re-attaching`);
|
|
361
|
+
const oldClients = new Set();
|
|
362
|
+
for (const [name, rec] of [...registry]) {
|
|
363
|
+
if (rec.server === chromeCfg.name) { oldClients.add(rec.client); registry.delete(name); }
|
|
364
|
+
}
|
|
365
|
+
for (const c of oldClients) { try { await c.close?.(); } catch { /* */ } }
|
|
366
|
+
serversUp = serversUp.filter((n) => n !== chromeCfg.name);
|
|
367
|
+
try {
|
|
368
|
+
const got = await connectOne(chromeCfg, log);
|
|
369
|
+
serversUp.push(chromeCfg.name);
|
|
370
|
+
for (const tool of got.tools) {
|
|
371
|
+
registry.set(toolOpenaiName(chromeCfg.name, tool.name), {
|
|
372
|
+
client: got.client, server: chromeCfg.name, tool: tool.name, description: tool.description || tool.name, schema: tool.inputSchema,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
rebuildOpenai();
|
|
376
|
+
log(`cursor-backend: mcp chrome-devtools re-attached mode=${chromeMode.mode} tools=${got.tools.length}`);
|
|
377
|
+
return true;
|
|
378
|
+
} catch (e) {
|
|
379
|
+
log(`cursor-backend: mcp chrome-devtools re-attach FAIL ${e.message}`);
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
292
384
|
function rebuildOpenai() {
|
|
293
385
|
openaiTools = [];
|
|
294
386
|
for (const [name, rec] of registry) {
|
|
@@ -319,6 +411,7 @@ export async function startHostMcps({ log = () => {}, home = os.homedir() } = {}
|
|
|
319
411
|
}
|
|
320
412
|
started = (async () => {
|
|
321
413
|
const configs = loadHostMcpConfigs(home);
|
|
414
|
+
chromeCfg = configs.find((c) => c.name === 'chrome-devtools') || configs.find((c) => /chrome|devtools|browser/i.test(c.name)) || null;
|
|
322
415
|
log(`cursor-backend: mcp loading n=${configs.length} ${configs.map((c) => c.name).join(',')}`);
|
|
323
416
|
const results = await Promise.allSettled(configs.map((cfg) => connectOne(cfg, log)));
|
|
324
417
|
for (let i = 0; i < results.length; i++) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.50.
|
|
3
|
+
"version": "0.50.59",
|
|
4
4
|
"description": "Local x402-paying proxy + MCP server for openzoo.fun — point any OpenAI-compatible harness (Cursor, Claude Code, aider, SDKs) at localhost and it pays per call from a local burner wallet. Solana and Base rails live; Robinhood experimental.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|