openzoo 0.50.67 → 0.50.68
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 +20 -9
- package/lib/xclaims.js +15 -7
- package/package.json +1 -1
package/lib/cursorbackend.js
CHANGED
|
@@ -2453,10 +2453,16 @@ export function selectedPageId(text) {
|
|
|
2453
2453
|
}
|
|
2454
2454
|
async function callBrowserForAgent(server, tool, args, agentId) {
|
|
2455
2455
|
const pinned = pinnedPage(agentId, server);
|
|
2456
|
+
if (pinned != null && tool === 'select_page' && Number(args?.pageId) !== Number(pinned)) {
|
|
2457
|
+
// A pinned bot stays on its own tab. Answer with its tab, do not switch.
|
|
2458
|
+
return callHostMcp(`${server}__select_page`, { pageId: pinned });
|
|
2459
|
+
}
|
|
2456
2460
|
if (pinned != null && !/^(new_page|list_pages|select_page|close_page)$/.test(tool)) {
|
|
2457
2461
|
try { await callHostMcp(`${server}__select_page`, { pageId: pinned }); } catch { /* page gone; fall through */ }
|
|
2458
2462
|
}
|
|
2459
|
-
|
|
2463
|
+
const out = await callHostMcp(`${server}__${tool}`, args);
|
|
2464
|
+
if (tool === 'new_page') { const pid = selectedPageId(out); if (pid != null) pinPage(agentId, server, pid); }
|
|
2465
|
+
return out;
|
|
2460
2466
|
}
|
|
2461
2467
|
async function runLocalTool(name, args, log, ctx = {}) {
|
|
2462
2468
|
try {
|
|
@@ -2595,16 +2601,21 @@ async function runLocalTool(name, args, log, ctx = {}) {
|
|
|
2595
2601
|
}
|
|
2596
2602
|
if (name === 'x_compose') {
|
|
2597
2603
|
const server = String(args.browser || 'chrome').toLowerCase() === 'brave' ? 'brave-devtools' : 'chrome-devtools';
|
|
2598
|
-
const
|
|
2604
|
+
for (const t of ['evaluate_script', 'click_at', 'type_text', 'press_key']) {
|
|
2605
|
+
if (!hostMcpHas(`${server}__${t}`)) return JSON.stringify({ ok: false, error: `${server}__${t} not attached` });
|
|
2606
|
+
}
|
|
2607
|
+
const probeRaw = String(await callBrowserForAgent(server, 'evaluate_script', { function: xclaims.composeProbeScript() }, ctx.agentId));
|
|
2608
|
+
const probe = (() => { try { return JSON.parse(probeRaw.match(/\{[\s\S]*\}/)?.[0] || '{}'); } catch { return {}; } })();
|
|
2609
|
+
if (!probe.ok) return JSON.stringify({ ok: false, error: probe.error || `composer probe failed: ${probeRaw.slice(0, 200)}` });
|
|
2610
|
+
const plan = xclaims.composePlan(args.text, probe);
|
|
2599
2611
|
let last = '';
|
|
2600
|
-
for (const op of plan.ops)
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2612
|
+
for (const op of plan.ops) last = String(await callBrowserForAgent(server, op.tool, op.args, ctx.agentId));
|
|
2613
|
+
const back = (() => { try { return JSON.parse(last.match(/\{[\s\S]*\}/)?.[0] || '{}'); } catch { return {}; } })();
|
|
2614
|
+
log(`cursor-backend: x_compose ${server} lines=${plan.lines} back=${back.lines} replyEnabled=${back.replyEnabled}`);
|
|
2615
|
+
if (!back.replyEnabled) {
|
|
2616
|
+
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
2617
|
}
|
|
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' });
|
|
2618
|
+
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
2619
|
}
|
|
2609
2620
|
}
|
|
2610
2621
|
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.68",
|
|
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",
|