openzoo 0.50.67 → 0.50.69
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 +32 -14
- package/lib/xclaims.js +15 -7
- package/package.json +1 -1
package/lib/cursorbackend.js
CHANGED
|
@@ -1836,9 +1836,12 @@ const MODEL_ALIASES = {
|
|
|
1836
1836
|
opus: 'anthropic/claude-opus-5',
|
|
1837
1837
|
'opus-5': 'anthropic/claude-opus-5',
|
|
1838
1838
|
sonnet: 'anthropic/claude-sonnet-5',
|
|
1839
|
-
grok
|
|
1840
|
-
|
|
1841
|
-
|
|
1839
|
+
// BARE grok-4.6 is the bazaar (x402 door) row; x-ai/grok-4.6 is OpenRouter's
|
|
1840
|
+
// and drains OpenRouter credits. Every spelling lands on the bare id.
|
|
1841
|
+
grok: 'grok-4.6',
|
|
1842
|
+
'grok-4': 'grok-4.6',
|
|
1843
|
+
'grok-4.6': 'grok-4.6',
|
|
1844
|
+
'x-ai/grok-4.6': 'grok-4.6',
|
|
1842
1845
|
glm: 'zai-org/glm-5.3-flash',
|
|
1843
1846
|
'glm-5': 'zai-org/glm-5.3-flash',
|
|
1844
1847
|
'glm-5.3': 'zai-org/glm-5.3-flash',
|
|
@@ -1872,10 +1875,14 @@ async function resolveModelId(raw) {
|
|
|
1872
1875
|
return ids.find((id) => id === s) || ids.find((id) => id.toLowerCase() === lower) || null;
|
|
1873
1876
|
} catch { return null; }
|
|
1874
1877
|
}
|
|
1878
|
+
export function canonicalZooModel(id) {
|
|
1879
|
+
const s = String(id || '').trim();
|
|
1880
|
+
return MODEL_ALIASES[s.toLowerCase()] || s;
|
|
1881
|
+
}
|
|
1875
1882
|
function currentModel(agentId) {
|
|
1876
|
-
return agentModels.get(agentId)
|
|
1883
|
+
return canonicalZooModel(agentModels.get(agentId)
|
|
1877
1884
|
|| process.env.OPENZOO_DEFAULT_MODEL
|
|
1878
|
-
|| DEFAULT_ZOO_MODEL;
|
|
1885
|
+
|| DEFAULT_ZOO_MODEL);
|
|
1879
1886
|
}
|
|
1880
1887
|
|
|
1881
1888
|
const execFileAsync = promisify(execFile);
|
|
@@ -2453,10 +2460,16 @@ export function selectedPageId(text) {
|
|
|
2453
2460
|
}
|
|
2454
2461
|
async function callBrowserForAgent(server, tool, args, agentId) {
|
|
2455
2462
|
const pinned = pinnedPage(agentId, server);
|
|
2463
|
+
if (pinned != null && tool === 'select_page' && Number(args?.pageId) !== Number(pinned)) {
|
|
2464
|
+
// A pinned bot stays on its own tab. Answer with its tab, do not switch.
|
|
2465
|
+
return callHostMcp(`${server}__select_page`, { pageId: pinned });
|
|
2466
|
+
}
|
|
2456
2467
|
if (pinned != null && !/^(new_page|list_pages|select_page|close_page)$/.test(tool)) {
|
|
2457
2468
|
try { await callHostMcp(`${server}__select_page`, { pageId: pinned }); } catch { /* page gone; fall through */ }
|
|
2458
2469
|
}
|
|
2459
|
-
|
|
2470
|
+
const out = await callHostMcp(`${server}__${tool}`, args);
|
|
2471
|
+
if (tool === 'new_page') { const pid = selectedPageId(out); if (pid != null) pinPage(agentId, server, pid); }
|
|
2472
|
+
return out;
|
|
2460
2473
|
}
|
|
2461
2474
|
async function runLocalTool(name, args, log, ctx = {}) {
|
|
2462
2475
|
try {
|
|
@@ -2595,16 +2608,21 @@ async function runLocalTool(name, args, log, ctx = {}) {
|
|
|
2595
2608
|
}
|
|
2596
2609
|
if (name === 'x_compose') {
|
|
2597
2610
|
const server = String(args.browser || 'chrome').toLowerCase() === 'brave' ? 'brave-devtools' : 'chrome-devtools';
|
|
2598
|
-
const
|
|
2611
|
+
for (const t of ['evaluate_script', 'click_at', 'type_text', 'press_key']) {
|
|
2612
|
+
if (!hostMcpHas(`${server}__${t}`)) return JSON.stringify({ ok: false, error: `${server}__${t} not attached` });
|
|
2613
|
+
}
|
|
2614
|
+
const probeRaw = String(await callBrowserForAgent(server, 'evaluate_script', { function: xclaims.composeProbeScript() }, ctx.agentId));
|
|
2615
|
+
const probe = (() => { try { return JSON.parse(probeRaw.match(/\{[\s\S]*\}/)?.[0] || '{}'); } catch { return {}; } })();
|
|
2616
|
+
if (!probe.ok) return JSON.stringify({ ok: false, error: probe.error || `composer probe failed: ${probeRaw.slice(0, 200)}` });
|
|
2617
|
+
const plan = xclaims.composePlan(args.text, probe);
|
|
2599
2618
|
let last = '';
|
|
2600
|
-
for (const op of plan.ops)
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2619
|
+
for (const op of plan.ops) last = String(await callBrowserForAgent(server, op.tool, op.args, ctx.agentId));
|
|
2620
|
+
const back = (() => { try { return JSON.parse(last.match(/\{[\s\S]*\}/)?.[0] || '{}'); } catch { return {}; } })();
|
|
2621
|
+
log(`cursor-backend: x_compose ${server} lines=${plan.lines} back=${back.lines} replyEnabled=${back.replyEnabled}`);
|
|
2622
|
+
if (!back.replyEnabled) {
|
|
2623
|
+
return JSON.stringify({ ok: false, browser: server, error: 'typed, but X did not register it (Reply still disabled). Click the reply box yourself with a real click (find [data-testid="tweetTextarea_0"] and use click_at on its centre), then call x_compose again.', composer: back });
|
|
2605
2624
|
}
|
|
2606
|
-
|
|
2607
|
-
return JSON.stringify({ ok: true, browser: server, linesTyped: plan.lines, composer: last.slice(0, 700), next: 'verify the composer text above shows separate lines, then click the Reply button' });
|
|
2625
|
+
return JSON.stringify({ ok: true, browser: server, linesTyped: plan.lines, composer: back, next: 'lines are in and Reply is enabled: click the Reply button ([data-testid="tweetButtonInline"]) now' });
|
|
2608
2626
|
}
|
|
2609
2627
|
}
|
|
2610
2628
|
if (hostMcpHas(name)) {
|
package/lib/xclaims.js
CHANGED
|
@@ -106,19 +106,27 @@ export function listClaims({ home = os.homedir(), now = Date.now() } = {}) {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/**
|
|
109
|
-
* X's composer is a Draft-style contenteditable
|
|
110
|
-
* newline (
|
|
111
|
-
* "corex402")
|
|
112
|
-
*
|
|
109
|
+
* X's composer is a Draft-style contenteditable. Two things measured 2026-09-01:
|
|
110
|
+
* 1. one `fill` flattens every newline ("openzoo.fun/core" + "x402 · PAID…"
|
|
111
|
+
* fused into "corex402");
|
|
112
|
+
* 2. `el.focus()` + keyboard typing puts text in the DOM but NOT in React's
|
|
113
|
+
* state — the Reply button stays disabled and the draft is wiped on the
|
|
114
|
+
* next render. Only a REAL mouse click on the box initialises Draft.
|
|
115
|
+
* So: probe the box's rect, click_at its centre, type line / Enter / line,
|
|
116
|
+
* then read back both the text and whether Reply is enabled.
|
|
113
117
|
*/
|
|
114
118
|
export const X_COMPOSER_SELECTOR = '[data-testid="tweetTextarea_0"], [data-testid="tweetTextarea_0RichTextInputContainer"] [contenteditable="true"], div[role="textbox"][contenteditable="true"]';
|
|
115
|
-
export
|
|
119
|
+
export const X_REPLY_BUTTON_SELECTOR = '[data-testid="tweetButtonInline"], [data-testid="tweetButton"]';
|
|
120
|
+
export const composeProbeScript = () => `() => { const el = document.querySelector(${JSON.stringify(X_COMPOSER_SELECTOR)}); if (!el) return { ok: false, error: 'no composer on this page — open the tweet and click Reply first' }; el.scrollIntoView({ block: 'center' }); const r = el.getBoundingClientRect(); return { ok: true, x: Math.round(r.left + Math.min(r.width / 2, 120)), y: Math.round(r.top + Math.min(r.height / 2, 20)), w: Math.round(r.width), h: Math.round(r.height) }; }`;
|
|
121
|
+
export const composeReadbackScript = () => `() => { const el = document.querySelector(${JSON.stringify(X_COMPOSER_SELECTOR)}); const b = document.querySelector(${JSON.stringify(X_REPLY_BUTTON_SELECTOR)}); const t = el ? (el.innerText || el.textContent || '') : ''; const dis = b ? (b.disabled || b.getAttribute('aria-disabled') === 'true') : true; return { ok: !!el, lines: t.split('\\n').filter(Boolean).length, replyEnabled: !dis, text: t.slice(0, 600) }; }`;
|
|
122
|
+
export function composePlan(text, rect) {
|
|
116
123
|
const lines = String(text || '').replace(/\r\n?/g, '\n').split('\n');
|
|
117
|
-
const ops = [
|
|
124
|
+
const ops = [];
|
|
125
|
+
if (rect && Number.isFinite(rect.x) && Number.isFinite(rect.y)) ops.push({ tool: 'click_at', args: { x: rect.x, y: rect.y } });
|
|
118
126
|
lines.forEach((line, i) => {
|
|
119
127
|
if (i) ops.push({ tool: 'press_key', args: { key: 'Enter' } });
|
|
120
128
|
if (line) ops.push({ tool: 'type_text', args: { text: line } });
|
|
121
129
|
});
|
|
122
|
-
ops.push({ tool: 'evaluate_script', args: { function:
|
|
130
|
+
ops.push({ tool: 'evaluate_script', args: { function: composeReadbackScript() } });
|
|
123
131
|
return { lines: lines.length, ops };
|
|
124
132
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.50.
|
|
3
|
+
"version": "0.50.69",
|
|
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",
|