openzoo 0.50.59 → 0.50.61
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 -1
- package/lib/mcpbridge.js +26 -4
- package/lib/openzoobotReceipt.js +41 -0
- package/package.json +1 -1
package/lib/cursorbackend.js
CHANGED
|
@@ -2650,7 +2650,7 @@ async function zooComplete(prompt, log, agentId, parsed = {}, opts = {}) {
|
|
|
2650
2650
|
return [
|
|
2651
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 ? '…' : ''}.`,
|
|
2652
2652
|
chrome.length
|
|
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
|
+
? `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. On heavy pages (x.com, gmail, big SPAs) take_snapshot can hang: prefer evaluate_script that returns only the text you need, or take_screenshot; a tool that times out says so — do not repeat it. Do not re-run schedule_wakeup or list_agents every turn; once is enough. 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.'}`
|
|
2654
2654
|
: 'Use matching MCP tools instead of inventing shell one-liners.',
|
|
2655
2655
|
].join(' ');
|
|
2656
2656
|
})(),
|
|
@@ -2718,6 +2718,8 @@ async function zooComplete(prompt, log, agentId, parsed = {}, opts = {}) {
|
|
|
2718
2718
|
} catch (e) {
|
|
2719
2719
|
lastErr = e;
|
|
2720
2720
|
log(`cursor-backend: zoo POST attempt=${attempt} ${e.message}`);
|
|
2721
|
+
// A superseded turn must not spend four more retries on a dead signal.
|
|
2722
|
+
if (opts.signal?.aborted || isSupersededError(e)) throw e;
|
|
2721
2723
|
if (attempt === tries) throw e;
|
|
2722
2724
|
const abortish = /abort|timeout/i.test(String(e?.name || '') + String(e?.message || ''));
|
|
2723
2725
|
await new Promise((ok) => setTimeout(ok, (abortish ? 2000 : 800) * attempt));
|
package/lib/mcpbridge.js
CHANGED
|
@@ -325,7 +325,7 @@ async function connectOne(cfg, log) {
|
|
|
325
325
|
try {
|
|
326
326
|
transport.stderr?.on?.('data', (buf) => {
|
|
327
327
|
const line = String(buf).trim().split('\n')[0];
|
|
328
|
-
if (line) log?.(`cursor-backend: mcp ${cfg.name} ${line.slice(0, 160)}`);
|
|
328
|
+
if (line && !/No handler registered for issue code/.test(line)) log?.(`cursor-backend: mcp ${cfg.name} ${line.slice(0, 160)}`);
|
|
329
329
|
});
|
|
330
330
|
} catch { /* */ }
|
|
331
331
|
}
|
|
@@ -389,11 +389,33 @@ function rebuildOpenai() {
|
|
|
389
389
|
}
|
|
390
390
|
}
|
|
391
391
|
|
|
392
|
-
|
|
392
|
+
/**
|
|
393
|
+
* One MCP call, with a hard ceiling. Measured 2026-09-01: take_snapshot on
|
|
394
|
+
* x.com never returned and the whole zoo turn froze behind it. A tool that
|
|
395
|
+
* hangs must come back as an error string the model can route around.
|
|
396
|
+
*/
|
|
397
|
+
export const MCP_CALL_TIMEOUT_MS = Math.max(10_000, Number(process.env.OZ_MCP_CALL_TIMEOUT_MS || 75_000));
|
|
398
|
+
export async function callHostMcp(name, args, { timeoutMs = MCP_CALL_TIMEOUT_MS } = {}) {
|
|
393
399
|
const rec = registry.get(String(name || ''));
|
|
394
400
|
if (!rec) throw new Error(`no mcp tool ${name}`);
|
|
395
|
-
|
|
396
|
-
|
|
401
|
+
let timer;
|
|
402
|
+
const ceiling = new Promise((_, rej) => {
|
|
403
|
+
timer = setTimeout(() => rej(new Error(`MCP tool ${name} timed out after ${Math.round(timeoutMs / 1000)}s. The page may be too heavy for that action. Use a narrower one: evaluate_script to pull the text you need, take_screenshot, or navigate_page to a lighter URL. Do not retry the same call.`)), timeoutMs);
|
|
404
|
+
timer.unref?.();
|
|
405
|
+
});
|
|
406
|
+
try {
|
|
407
|
+
const r = await Promise.race([
|
|
408
|
+
rec.client.callTool({ name: rec.tool, arguments: args && typeof args === 'object' ? args : {} }),
|
|
409
|
+
ceiling,
|
|
410
|
+
]);
|
|
411
|
+
return flattenMcpResult(r);
|
|
412
|
+
} finally {
|
|
413
|
+
clearTimeout(timer);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export function registerHostMcpForTests(name, rec) {
|
|
418
|
+
registry.set(name, rec);
|
|
397
419
|
}
|
|
398
420
|
|
|
399
421
|
export function resetHostMcpForTests() {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @openzoobot public receipt. X collapses single newlines into one
|
|
3
|
+
* paragraph; join with a blank line so PAID / WOULDA / tx stay stacked.
|
|
4
|
+
* `(this call) openzoo.fun` stays on the PAID line — never split.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const HARD = '\n\n';
|
|
8
|
+
|
|
9
|
+
export function receiptLines({
|
|
10
|
+
paid = '0.004',
|
|
11
|
+
woulda = '0.03',
|
|
12
|
+
wouldaLabel = 'grok.com / xAI API',
|
|
13
|
+
tx = '',
|
|
14
|
+
model = 'grok-4.6 @ openzoo',
|
|
15
|
+
} = {}) {
|
|
16
|
+
const paidStr = String(paid).replace(/^\$/, '');
|
|
17
|
+
const wouldaStr = String(woulda).replace(/^\$/, '');
|
|
18
|
+
const lines = [
|
|
19
|
+
model,
|
|
20
|
+
`**PAID** $${paidStr} x402 (this call) openzoo.fun`,
|
|
21
|
+
`**WOULDA** $${wouldaStr} ${wouldaLabel}`,
|
|
22
|
+
];
|
|
23
|
+
if (tx) lines.push(`tx ${tx}`);
|
|
24
|
+
return lines;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function receiptFooter(opts = {}) {
|
|
28
|
+
return receiptLines(opts).join(HARD);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Same body without markdown, for the X compose box. */
|
|
32
|
+
export function xTweetReceipt(opts = {}) {
|
|
33
|
+
return receiptLines(opts)
|
|
34
|
+
.map((line) => line.replace(/\*\*/g, ''))
|
|
35
|
+
.join(HARD);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** What X does when it eats extra blank lines but keeps single \n. */
|
|
39
|
+
export function afterXCollapse(text) {
|
|
40
|
+
return String(text).replace(/\n{2,}/g, '\n').trim();
|
|
41
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.50.
|
|
3
|
+
"version": "0.50.61",
|
|
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",
|