openzoo 0.50.21 → 0.50.22
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 +158 -29
- package/lib/grokcli.js +4 -2
- package/package.json +1 -1
package/lib/cursorbackend.js
CHANGED
|
@@ -289,7 +289,34 @@ const RESP_DROP = new Set([
|
|
|
289
289
|
]);
|
|
290
290
|
|
|
291
291
|
const SNIFF_FILE = path.join(os.homedir(), '.openzoo', 'grokbot-sniff.jsonl');
|
|
292
|
-
|
|
292
|
+
const POD_FILE = path.join(os.homedir(), '.openzoo', 'grokbot-pod.json');
|
|
293
|
+
function loadPod() {
|
|
294
|
+
try { return JSON.parse(fs.readFileSync(POD_FILE, 'utf8')); } catch { return null; }
|
|
295
|
+
}
|
|
296
|
+
function savePod(p) {
|
|
297
|
+
if (!p?.agent) return;
|
|
298
|
+
try {
|
|
299
|
+
fs.mkdirSync(path.dirname(POD_FILE), { recursive: true });
|
|
300
|
+
fs.writeFileSync(POD_FILE, JSON.stringify(p));
|
|
301
|
+
} catch { /* */ }
|
|
302
|
+
}
|
|
303
|
+
let realPod = loadPod(); // { agent, vnc, token, p1340, ... } — persist so api2 timeout does not UUID-stub the sidebar
|
|
304
|
+
const AGENTS_FILE = path.join(os.homedir(), '.openzoo', 'grokbot-agents.json');
|
|
305
|
+
function loadAgents() {
|
|
306
|
+
try {
|
|
307
|
+
const a = JSON.parse(fs.readFileSync(AGENTS_FILE, 'utf8'));
|
|
308
|
+
return Array.isArray(a) ? a : null;
|
|
309
|
+
} catch { return null; }
|
|
310
|
+
}
|
|
311
|
+
function saveAgents(a) {
|
|
312
|
+
if (!Array.isArray(a) || !a.length) return;
|
|
313
|
+
try { fs.writeFileSync(AGENTS_FILE, JSON.stringify(a)); } catch { /* */ }
|
|
314
|
+
}
|
|
315
|
+
function cachedAgentList() {
|
|
316
|
+
const a = loadAgents();
|
|
317
|
+
if (a?.length) return a;
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
293
320
|
|
|
294
321
|
function sniffOn() { return process.env.OPENZOO_SNIFF === '1'; }
|
|
295
322
|
function sniffSelf() { return process.env.OZ_SNIFF_SELF || 'https://127.0.0.1:8443'; }
|
|
@@ -446,6 +473,7 @@ function rememberPod(fields, log) {
|
|
|
446
473
|
podId: String(fields[3] || ''),
|
|
447
474
|
};
|
|
448
475
|
sniffDump({ kind: 'pod', fields, realPod });
|
|
476
|
+
savePod(realPod);
|
|
449
477
|
log(`cursor-backend: SNIFF real pod ${realPod.agent}`);
|
|
450
478
|
return realPod;
|
|
451
479
|
}
|
|
@@ -470,7 +498,7 @@ async function sniffEnsureSandBox(req, res, body, host, full, log) {
|
|
|
470
498
|
const upstream = cursorUpstream(host);
|
|
471
499
|
const headers = copyReqHeaders(req, upstream);
|
|
472
500
|
const cap = await upstreamUnary({
|
|
473
|
-
host: upstream, path: full, method: req.method, headers, body, timeoutMs:
|
|
501
|
+
host: upstream, path: full, method: req.method, headers, body, timeoutMs: 60000,
|
|
474
502
|
});
|
|
475
503
|
const raw = inflateBody(cap.buf, cap.respHeaders);
|
|
476
504
|
const proto = unwrapConnect(raw);
|
|
@@ -564,6 +592,26 @@ async function proxyPodHttp(req, res, full, body, log) {
|
|
|
564
592
|
body,
|
|
565
593
|
timeoutMs: path0 === '/health' ? 8000 : 120000,
|
|
566
594
|
});
|
|
595
|
+
if (path0 === '/api/listAgents' && cap.status === 200) {
|
|
596
|
+
try {
|
|
597
|
+
const parsed = JSON.parse(String(inflateBody(cap.buf, cap.respHeaders)));
|
|
598
|
+
if (Array.isArray(parsed)) {
|
|
599
|
+
const merged = mergeAgentLists(parsed);
|
|
600
|
+
saveAgents(merged);
|
|
601
|
+
jsonSend(res, merged);
|
|
602
|
+
log(`cursor-backend: listAgents 200 merged n=${merged.length}`);
|
|
603
|
+
return true;
|
|
604
|
+
}
|
|
605
|
+
} catch { /* */ }
|
|
606
|
+
}
|
|
607
|
+
if (cap.status === 401 && path0 === '/api/listAgents') {
|
|
608
|
+
const cached = mergeAgentLists([]);
|
|
609
|
+
if (cached.length) {
|
|
610
|
+
jsonSend(res, cached);
|
|
611
|
+
log(`cursor-backend: listAgents 401 — cached ${cached.length} named agents`);
|
|
612
|
+
return true;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
567
615
|
writeCaptured(res, cap.status, cap.respHeaders, cap.buf);
|
|
568
616
|
const rec = {
|
|
569
617
|
kind: 'pod-http',
|
|
@@ -764,10 +812,45 @@ function appendLine(agentId, role, text, extra = {}) {
|
|
|
764
812
|
return e;
|
|
765
813
|
}
|
|
766
814
|
function fanoutLine(primaryId, role, text, extra = {}) {
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
815
|
+
// Only the addressed agent. Writing to every tailedAgents id mixed canvases
|
|
816
|
+
// so a new/empty chat showed someone else's thread.
|
|
817
|
+
return appendLine(primaryId, role, text, extra);
|
|
818
|
+
}
|
|
819
|
+
function mintLocalAgent(parsed = {}) {
|
|
820
|
+
const id = String(parsed.id || randomUUID());
|
|
821
|
+
const name = String(parsed.name || parsed.title || 'new chat');
|
|
822
|
+
const agent = {
|
|
823
|
+
id,
|
|
824
|
+
name,
|
|
825
|
+
description: String(parsed.description || ''),
|
|
826
|
+
title: String(parsed.title || name),
|
|
827
|
+
origin: parsed.origin || 'user',
|
|
828
|
+
createdAt: Date.now(),
|
|
829
|
+
updatedAt: Date.now(),
|
|
830
|
+
avatarShape: parsed.avatarShape || null,
|
|
831
|
+
avatarColor: parsed.avatarColor || null,
|
|
832
|
+
path: `/local/${id}`,
|
|
833
|
+
};
|
|
834
|
+
const list = cachedAgentList() || [];
|
|
835
|
+
if (!list.some((a) => a.id === id)) list.unshift(agent);
|
|
836
|
+
else {
|
|
837
|
+
const i = list.findIndex((a) => a.id === id);
|
|
838
|
+
list[i] = { ...list[i], ...agent };
|
|
839
|
+
}
|
|
840
|
+
saveAgents(list);
|
|
841
|
+
agentTranscript(id);
|
|
842
|
+
return agent;
|
|
843
|
+
}
|
|
844
|
+
function mergeAgentLists(remote) {
|
|
845
|
+
const local = cachedAgentList() || [];
|
|
846
|
+
const seen = new Set();
|
|
847
|
+
const out = [];
|
|
848
|
+
for (const a of [...local, ...(Array.isArray(remote) ? remote : [])]) {
|
|
849
|
+
if (!a?.id || seen.has(a.id)) continue;
|
|
850
|
+
seen.add(a.id);
|
|
851
|
+
out.push(a);
|
|
852
|
+
}
|
|
853
|
+
return out;
|
|
771
854
|
}
|
|
772
855
|
function gatewayEntry(e) {
|
|
773
856
|
const { seq, ...rest } = e;
|
|
@@ -1223,12 +1306,56 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
|
|
|
1223
1306
|
'getBotTemplateExportPolicy', 'getTeachRecordingStatus', 'isGlobalSearchEnabled',
|
|
1224
1307
|
'isEgressTunnelAvailable', 'listBoxMcpServers', 'getHostStatus',
|
|
1225
1308
|
'setWindowFocused', 'getAgentAutomations',
|
|
1226
|
-
'
|
|
1227
|
-
'updateAgent', 'deleteAgents', 'duplicateAgent', 'kickstartAgent',
|
|
1309
|
+
'createGroup', 'setGroupMembers',
|
|
1228
1310
|
'interruptAgentRun', 'requestDiskSaverAudit', 'broadcastToAgents',
|
|
1229
1311
|
'setAgentUnread', 'setAgentHiddenFromSidebar', 'setAgentNotificationsEnabled',
|
|
1230
1312
|
'setAgentNotifyOnUpdates', 'setAgentAvatarBytes', 'getAgentAvatar',
|
|
1231
1313
|
]);
|
|
1314
|
+
// create/delete stay LOCAL — 1340 createAgent 401s on a stale cached token
|
|
1315
|
+
// and the UI then never grows a sidebar row or clears the canvas.
|
|
1316
|
+
if (name === 'createAgent' || name === 'createAgentFromTemplate' || name === 'duplicateAgent') {
|
|
1317
|
+
let parsed = {};
|
|
1318
|
+
try { parsed = JSON.parse(String(body || '{}')); } catch { parsed = {}; }
|
|
1319
|
+
if (name === 'duplicateAgent' && parsed.id) {
|
|
1320
|
+
const src = (cachedAgentList() || []).find((a) => a.id === parsed.id) || {};
|
|
1321
|
+
parsed = { ...src, id: undefined, name: `${src.name || 'chat'} copy` };
|
|
1322
|
+
}
|
|
1323
|
+
const agent = mintLocalAgent(parsed);
|
|
1324
|
+
jsonSend(res, { agent, id: agent.id, ...agent });
|
|
1325
|
+
ssePush('agents', { action: 'created', agent });
|
|
1326
|
+
log(`cursor-backend: createAgent local id=${agent.id} name=${JSON.stringify(agent.name)}`);
|
|
1327
|
+
return true;
|
|
1328
|
+
}
|
|
1329
|
+
if (name === 'updateAgent') {
|
|
1330
|
+
let parsed = {};
|
|
1331
|
+
try { parsed = JSON.parse(String(body || '{}')); } catch { parsed = {}; }
|
|
1332
|
+
const agent = mintLocalAgent(parsed);
|
|
1333
|
+
jsonSend(res, { agent, ...agent });
|
|
1334
|
+
log(`cursor-backend: updateAgent local id=${agent.id}`);
|
|
1335
|
+
return true;
|
|
1336
|
+
}
|
|
1337
|
+
if (name === 'deleteAgents') {
|
|
1338
|
+
let parsed = {};
|
|
1339
|
+
try { parsed = JSON.parse(String(body || '{}')); } catch { parsed = {}; }
|
|
1340
|
+
const ids = new Set([].concat(parsed.ids || parsed.id || []).map(String));
|
|
1341
|
+
const next = (cachedAgentList() || []).filter((a) => !ids.has(a.id));
|
|
1342
|
+
saveAgents(next);
|
|
1343
|
+
for (const id of ids) transcripts.delete(id);
|
|
1344
|
+
jsonSend(res, { ok: true, deleted: [...ids] });
|
|
1345
|
+
log(`cursor-backend: deleteAgents n=${ids.size}`);
|
|
1346
|
+
return true;
|
|
1347
|
+
}
|
|
1348
|
+
if (name === 'listAgents') {
|
|
1349
|
+
if (!sniffOn() && realPod?.agent) {
|
|
1350
|
+
const proxied = await proxyPodHttp(req, res, full, body, log);
|
|
1351
|
+
if (proxied) return true;
|
|
1352
|
+
}
|
|
1353
|
+
const list = mergeAgentLists([]);
|
|
1354
|
+
jsonSend(res, list);
|
|
1355
|
+
log(`cursor-backend: listAgents local n=${list.length}`);
|
|
1356
|
+
return true;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1232
1359
|
if (!sniffOn() && realPod?.agent && roster.has(name)) {
|
|
1233
1360
|
return proxyPodHttp(req, res, full, body, log);
|
|
1234
1361
|
}
|
|
@@ -1289,14 +1416,9 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
|
|
|
1289
1416
|
if (name === 'getAgentTranscriptTail' || name === 'getAgentTranscriptWindow' || name === 'openAgentTail') {
|
|
1290
1417
|
let parsed = {};
|
|
1291
1418
|
try { parsed = JSON.parse(String(body || '{}')); } catch { parsed = {}; }
|
|
1292
|
-
|
|
1419
|
+
const id = String(parsed.id || parsed.agentId || 'openzoo');
|
|
1293
1420
|
tailedAgents.add(id);
|
|
1294
|
-
|
|
1295
|
-
if (!t.entries.length) {
|
|
1296
|
-
for (const [other, ot] of transcripts) {
|
|
1297
|
-
if (ot.entries.length) { id = other; t = ot; break; }
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1421
|
+
const t = agentTranscript(id);
|
|
1300
1422
|
const limit = Math.min(Number(parsed.limit) || 50, 200);
|
|
1301
1423
|
const before = parsed.beforeSeq != null ? Number(parsed.beforeSeq) : Infinity;
|
|
1302
1424
|
const sliced = t.entries.filter((e) => e.seq < before).slice(-limit);
|
|
@@ -1334,7 +1456,8 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
|
|
|
1334
1456
|
getHostSettings: { settings: {} },
|
|
1335
1457
|
setHostSettings: { ok: true },
|
|
1336
1458
|
setBoxSecrets: { ok: true },
|
|
1337
|
-
listAgents:
|
|
1459
|
+
listAgents: cachedAgentList()
|
|
1460
|
+
|| [...new Set([...transcripts.keys(), ...tailedAgents])].map((id) => ({ id, name: id, status: 'ready' })),
|
|
1338
1461
|
getAgentTranscriptTail: { tail: '', lines: [], dropped: false, ok: true },
|
|
1339
1462
|
getTeachRecordingStatus: { recording: false },
|
|
1340
1463
|
getTrays: { trays: [] },
|
|
@@ -1551,25 +1674,31 @@ export function startCursorBackend({ port = 8443, models, log = () => {} } = {})
|
|
|
1551
1674
|
// with OUR box so Grok Bot's UI wires to our sandbox; everything else
|
|
1552
1675
|
// still passes through so the app loads normally.
|
|
1553
1676
|
if (/GrokBotService\/(EnsureSandBox|WatchSandBoxMigration)/.test(full) && process.env.OZ_HIJACK_POD) {
|
|
1554
|
-
if (/WatchSandBoxMigration/.test(full) && realPod) {
|
|
1555
|
-
const payload = rewrittenBox();
|
|
1556
|
-
res.writeHead(200, {
|
|
1557
|
-
'content-type': 'application/connect+proto',
|
|
1558
|
-
'grpc-status': '0',
|
|
1559
|
-
...CORS,
|
|
1560
|
-
});
|
|
1561
|
-
const end = Buffer.from('{}');
|
|
1562
|
-
const h = Buffer.alloc(5); h.writeUInt8(0x02, 0); h.writeUInt32BE(end.length, 1);
|
|
1563
|
-
res.end(Buffer.concat([envelope(payload), h, end]));
|
|
1564
|
-
log('cursor-backend: -> WatchSandBoxMigration ready (hijack, real roster)');
|
|
1565
|
-
return;
|
|
1566
|
-
}
|
|
1567
1677
|
try {
|
|
1568
1678
|
process.env.OZ_SNIFF_SELF = process.env.OZ_SNIFF_SELF || 'https://127.0.0.1:8443';
|
|
1569
1679
|
await sniffEnsureSandBox(req, res, body, host, full, log);
|
|
1570
1680
|
log('cursor-backend: -> HIJACKED EnsureSandBox -> our box (roster from real 1340)');
|
|
1571
1681
|
return;
|
|
1572
1682
|
} catch (e) {
|
|
1683
|
+
if (realPod?.agent) {
|
|
1684
|
+
log(`cursor-backend: EnsureSandBox discover failed (${e.message}) — cached 1340 roster`);
|
|
1685
|
+
const payload = rewrittenBox();
|
|
1686
|
+
const reqCt = String(req.headers['content-type'] || '');
|
|
1687
|
+
if (/WatchSandBoxMigration/.test(full) || reqCt.includes('connect+proto')) {
|
|
1688
|
+
res.writeHead(200, {
|
|
1689
|
+
'content-type': 'application/connect+proto',
|
|
1690
|
+
'grpc-status': '0',
|
|
1691
|
+
...CORS,
|
|
1692
|
+
});
|
|
1693
|
+
const end = Buffer.from('{}');
|
|
1694
|
+
const h = Buffer.alloc(5); h.writeUInt8(0x02, 0); h.writeUInt32BE(end.length, 1);
|
|
1695
|
+
res.end(Buffer.concat([envelope(payload), h, end]));
|
|
1696
|
+
} else {
|
|
1697
|
+
res.writeHead(200, { 'content-type': 'application/proto', ...CORS });
|
|
1698
|
+
res.end(payload);
|
|
1699
|
+
}
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1573
1702
|
log(`cursor-backend: EnsureSandBox discover failed (${e.message}) — env box`);
|
|
1574
1703
|
}
|
|
1575
1704
|
let pod;
|
package/lib/grokcli.js
CHANGED
|
@@ -177,8 +177,10 @@ export async function runBot(argv = []) {
|
|
|
177
177
|
console.error(' EnsureSandBox HIJACKED here; StreamUnifiedChat -> :8402 (x402)');
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
|
|
181
|
-
|
|
180
|
+
if (!argv.includes('--no-quit') && process.env.OZ_NO_QUIT !== '1') {
|
|
181
|
+
try { execSync('osascript -e \'tell application "Grok Bot" to quit\'', { stdio: 'ignore' }); } catch { /* ok */ }
|
|
182
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
183
|
+
}
|
|
182
184
|
|
|
183
185
|
console.error('openzoo: launching Grok Bot');
|
|
184
186
|
console.error(` CURSOR_API_BASE_URL=${url}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.50.
|
|
3
|
+
"version": "0.50.22",
|
|
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",
|