openzoo 0.50.20 → 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 +487 -88
- package/lib/grokcli.js +10 -2
- package/package.json +1 -1
package/lib/cursorbackend.js
CHANGED
|
@@ -32,7 +32,9 @@ import fs from 'node:fs';
|
|
|
32
32
|
import os from 'node:os';
|
|
33
33
|
import path from 'node:path';
|
|
34
34
|
import zlib from 'node:zlib';
|
|
35
|
-
import { execFileSync } from 'node:child_process';
|
|
35
|
+
import { execFileSync, execFile } from 'node:child_process';
|
|
36
|
+
import { promisify } from 'node:util';
|
|
37
|
+
import tls from 'node:tls';
|
|
36
38
|
import { randomUUID } from 'node:crypto';
|
|
37
39
|
import { encodeAvailableModels, encodeForMethod, encodeEnsureSandBox, encodeGetGrokBotSendStatus, decodeProtoFields, unwrapConnect } from './cursorapi.js';
|
|
38
40
|
|
|
@@ -287,7 +289,34 @@ const RESP_DROP = new Set([
|
|
|
287
289
|
]);
|
|
288
290
|
|
|
289
291
|
const SNIFF_FILE = path.join(os.homedir(), '.openzoo', 'grokbot-sniff.jsonl');
|
|
290
|
-
|
|
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
|
+
}
|
|
291
320
|
|
|
292
321
|
function sniffOn() { return process.env.OPENZOO_SNIFF === '1'; }
|
|
293
322
|
function sniffSelf() { return process.env.OZ_SNIFF_SELF || 'https://127.0.0.1:8443'; }
|
|
@@ -444,6 +473,7 @@ function rememberPod(fields, log) {
|
|
|
444
473
|
podId: String(fields[3] || ''),
|
|
445
474
|
};
|
|
446
475
|
sniffDump({ kind: 'pod', fields, realPod });
|
|
476
|
+
savePod(realPod);
|
|
447
477
|
log(`cursor-backend: SNIFF real pod ${realPod.agent}`);
|
|
448
478
|
return realPod;
|
|
449
479
|
}
|
|
@@ -468,7 +498,7 @@ async function sniffEnsureSandBox(req, res, body, host, full, log) {
|
|
|
468
498
|
const upstream = cursorUpstream(host);
|
|
469
499
|
const headers = copyReqHeaders(req, upstream);
|
|
470
500
|
const cap = await upstreamUnary({
|
|
471
|
-
host: upstream, path: full, method: req.method, headers, body, timeoutMs:
|
|
501
|
+
host: upstream, path: full, method: req.method, headers, body, timeoutMs: 60000,
|
|
472
502
|
});
|
|
473
503
|
const raw = inflateBody(cap.buf, cap.respHeaders);
|
|
474
504
|
const proto = unwrapConnect(raw);
|
|
@@ -562,6 +592,26 @@ async function proxyPodHttp(req, res, full, body, log) {
|
|
|
562
592
|
body,
|
|
563
593
|
timeoutMs: path0 === '/health' ? 8000 : 120000,
|
|
564
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
|
+
}
|
|
565
615
|
writeCaptured(res, cap.status, cap.respHeaders, cap.buf);
|
|
566
616
|
const rec = {
|
|
567
617
|
kind: 'pod-http',
|
|
@@ -640,8 +690,14 @@ function handleLocalExecFrames(frames, log) {
|
|
|
640
690
|
} else if (f.kind === 'file-error' || f.kind === 'messages-error') {
|
|
641
691
|
w.reject(new Error(f.error || 'local-exec error'));
|
|
642
692
|
localExecWaiters.delete(f.requestId);
|
|
643
|
-
} else if (f.kind === 'client' || f.kind === 'control') {
|
|
644
|
-
w.resolve({
|
|
693
|
+
} else if (f.kind === 'client' || f.kind === 'control' || f.kind === 'result' || f.kind === 'exec-result') {
|
|
694
|
+
w.resolve({
|
|
695
|
+
kind: f.kind,
|
|
696
|
+
message: f.message || f.text || f.stdout || JSON.stringify(f),
|
|
697
|
+
stdout: f.stdout,
|
|
698
|
+
stderr: f.stderr,
|
|
699
|
+
exitCode: f.exitCode,
|
|
700
|
+
});
|
|
645
701
|
localExecWaiters.delete(f.requestId);
|
|
646
702
|
}
|
|
647
703
|
}
|
|
@@ -670,12 +726,14 @@ async function handleLocalExecHttp(req, res, path0, body, log) {
|
|
|
670
726
|
...CORS,
|
|
671
727
|
});
|
|
672
728
|
localExecSse.add(res);
|
|
673
|
-
|
|
729
|
+
// Daemon parser (asar CNt) ignores unknown welcome; a comment ping is enough
|
|
730
|
+
// to keep the stream alive until it POSTs hello to /responses.
|
|
731
|
+
res.write(': openzoo local-exec\n\n');
|
|
674
732
|
const iv = setInterval(() => {
|
|
675
733
|
try { res.write(': ping\n\n'); } catch { clearInterval(iv); localExecSse.delete(res); }
|
|
676
|
-
},
|
|
677
|
-
req.on('close', () => { clearInterval(iv); localExecSse.delete(res); });
|
|
678
|
-
log(
|
|
734
|
+
}, 10000);
|
|
735
|
+
req.on('close', () => { clearInterval(iv); localExecSse.delete(res); log('cursor-backend: local-exec sse closed'); });
|
|
736
|
+
log(`cursor-backend: -> local-exec /requests sse n=${localExecSse.size}`);
|
|
679
737
|
return true;
|
|
680
738
|
}
|
|
681
739
|
if (/\/responses$/.test(path0)) {
|
|
@@ -683,7 +741,7 @@ async function handleLocalExecHttp(req, res, path0, body, log) {
|
|
|
683
741
|
try { parsed = JSON.parse(String(body || '{}')); } catch { parsed = {}; }
|
|
684
742
|
handleLocalExecFrames(parsed.frames, log);
|
|
685
743
|
jsonSend(res, { ok: true });
|
|
686
|
-
log(`cursor-backend: -> local-exec /responses n=${(parsed.frames || []).length}`);
|
|
744
|
+
log(`cursor-backend: -> local-exec /responses n=${(parsed.frames || []).length} kinds=${(parsed.frames || []).map((f) => f?.kind).join(',')}`);
|
|
687
745
|
return true;
|
|
688
746
|
}
|
|
689
747
|
jsonSend(res, { ok: true });
|
|
@@ -754,10 +812,45 @@ function appendLine(agentId, role, text, extra = {}) {
|
|
|
754
812
|
return e;
|
|
755
813
|
}
|
|
756
814
|
function fanoutLine(primaryId, role, text, extra = {}) {
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
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;
|
|
761
854
|
}
|
|
762
855
|
function gatewayEntry(e) {
|
|
763
856
|
const { seq, ...rest } = e;
|
|
@@ -832,7 +925,19 @@ async function zooSpendOverlay(data) {
|
|
|
832
925
|
return lines.join('\n');
|
|
833
926
|
}
|
|
834
927
|
|
|
835
|
-
const
|
|
928
|
+
const MODELS_PATH = path.join(os.homedir(), '.openzoo', 'grokbot-models.json');
|
|
929
|
+
function loadAgentModels() {
|
|
930
|
+
try {
|
|
931
|
+
return new Map(Object.entries(JSON.parse(fs.readFileSync(MODELS_PATH, 'utf8'))));
|
|
932
|
+
} catch { return new Map(); }
|
|
933
|
+
}
|
|
934
|
+
function saveAgentModels() {
|
|
935
|
+
try {
|
|
936
|
+
fs.mkdirSync(path.dirname(MODELS_PATH), { recursive: true });
|
|
937
|
+
fs.writeFileSync(MODELS_PATH, JSON.stringify(Object.fromEntries(agentModels)));
|
|
938
|
+
} catch { /* */ }
|
|
939
|
+
}
|
|
940
|
+
const agentModels = loadAgentModels();
|
|
836
941
|
const MODEL_ALIASES = {
|
|
837
942
|
fable: 'anthropic/claude-fable-5',
|
|
838
943
|
'fable-5': 'anthropic/claude-fable-5',
|
|
@@ -852,65 +957,300 @@ function resolveModelId(raw) {
|
|
|
852
957
|
if (s.includes('/')) return s;
|
|
853
958
|
return null;
|
|
854
959
|
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
log(`cursor-backend: zoo POST :8402 ${JSON.stringify((prompt || '').slice(0, 60))}`);
|
|
858
|
-
const model = agentModels.get(agentId)
|
|
960
|
+
function currentModel(agentId) {
|
|
961
|
+
return agentModels.get(agentId)
|
|
859
962
|
|| process.env.OPENZOO_DEFAULT_MODEL
|
|
860
|
-
|| '
|
|
861
|
-
|
|
862
|
-
|
|
963
|
+
|| 'x-ai/grok-4.6';
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
const execFileAsync = promisify(execFile);
|
|
967
|
+
const IMAGE_EXT = /\.(png|jpe?g|gif|webp|bmp|heic|svg)$/i;
|
|
968
|
+
const IMAGE_MAGIC = [
|
|
969
|
+
[Buffer.from([0x89, 0x50, 0x4e, 0x47]), 'image/png'],
|
|
970
|
+
[Buffer.from([0xff, 0xd8, 0xff]), 'image/jpeg'],
|
|
971
|
+
[Buffer.from('GIF8'), 'image/gif'],
|
|
972
|
+
[Buffer.from('RIFF'), 'image/webp'],
|
|
973
|
+
];
|
|
974
|
+
function mimeFromBytes(buf, p = '') {
|
|
975
|
+
if (IMAGE_EXT.test(p)) {
|
|
976
|
+
const ext = path.extname(p).toLowerCase();
|
|
977
|
+
return { 'png': 'image/png', '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp', '.bmp': 'image/bmp', '.heic': 'image/heic', '.svg': 'image/svg+xml' }[ext] || 'image/png';
|
|
978
|
+
}
|
|
979
|
+
for (const [magic, mime] of IMAGE_MAGIC) {
|
|
980
|
+
if (buf.length >= magic.length && buf.subarray(0, magic.length).equals(magic)) return mime;
|
|
981
|
+
}
|
|
982
|
+
return null;
|
|
983
|
+
}
|
|
984
|
+
function dataUrlsFromRichText(rt) {
|
|
985
|
+
const s = String(rt || '');
|
|
986
|
+
const out = [];
|
|
987
|
+
const re = /data:image\/[a-zA-Z0-9.+-]+;base64,[A-Za-z0-9+/]+=*/g;
|
|
988
|
+
let m;
|
|
989
|
+
while ((m = re.exec(s))) out.push(m[0]);
|
|
990
|
+
return out;
|
|
991
|
+
}
|
|
992
|
+
function attachmentList(parsed, prompt) {
|
|
993
|
+
const out = [];
|
|
994
|
+
const seen = new Set();
|
|
995
|
+
const add = (raw, name) => {
|
|
996
|
+
if (!raw || seen.has(raw)) return;
|
|
997
|
+
seen.add(raw);
|
|
998
|
+
out.push({ raw, name });
|
|
999
|
+
};
|
|
1000
|
+
const paths = parsed?.attachmentPaths;
|
|
1001
|
+
const names = parsed?.attachmentNames;
|
|
1002
|
+
if (Array.isArray(paths)) {
|
|
1003
|
+
for (let i = 0; i < paths.length; i++) add(String(paths[i]), names?.[i]);
|
|
1004
|
+
}
|
|
1005
|
+
for (const p of extractLocalPaths(prompt)) {
|
|
1006
|
+
if (/[*?]/.test(p)) continue;
|
|
1007
|
+
add(p);
|
|
1008
|
+
}
|
|
1009
|
+
const rt = String(parsed?.richText || '');
|
|
1010
|
+
const srcRe = /(?:src|path|filePath|url)"?\s*[:=]\s*"((?:file:\/\/|\/(?:var|tmp|private|Users)|~\/)[^"]+\.(?:png|jpe?g|gif|webp|bmp|heic))"/gi;
|
|
1011
|
+
let sm;
|
|
1012
|
+
while ((sm = srcRe.exec(rt))) {
|
|
1013
|
+
add(sm[1].replace(/^file:\/\//, ''));
|
|
1014
|
+
}
|
|
1015
|
+
return out;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
async function readLocalBytes(abs, log) {
|
|
1019
|
+
if (localExecSse.size > 0) {
|
|
1020
|
+
log(`cursor-backend: local-exec download ${abs}`);
|
|
1021
|
+
const got = await localExecAsk({ kind: 'download', path: abs });
|
|
1022
|
+
return got.bytes || Buffer.from(got.text || '', 'utf8');
|
|
1023
|
+
}
|
|
1024
|
+
return fs.readFileSync(abs);
|
|
1025
|
+
}
|
|
1026
|
+
async function writeLocalBytes(abs, bytes, log) {
|
|
1027
|
+
const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(String(bytes ?? ''), 'utf8');
|
|
1028
|
+
if (localExecSse.size > 0) {
|
|
1029
|
+
log(`cursor-backend: local-exec upload ${abs} ${buf.length}b`);
|
|
1030
|
+
await localExecAsk({
|
|
1031
|
+
kind: 'upload',
|
|
1032
|
+
path: abs,
|
|
1033
|
+
bytesBase64: buf.toString('base64'),
|
|
1034
|
+
});
|
|
1035
|
+
return `wrote ${abs} (${buf.length} bytes) via local-exec`;
|
|
1036
|
+
}
|
|
1037
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
1038
|
+
fs.writeFileSync(abs, buf);
|
|
1039
|
+
return `wrote ${abs} (${buf.length} bytes)`;
|
|
1040
|
+
}
|
|
1041
|
+
async function execLocal(command, cwd, log) {
|
|
1042
|
+
const dir = cwd ? expandUserPath(cwd) : os.homedir();
|
|
1043
|
+
if (localExecSse.size > 0) {
|
|
1044
|
+
log(`cursor-backend: local-exec exec ${JSON.stringify(command).slice(0, 80)}`);
|
|
1045
|
+
const got = await localExecAsk({
|
|
1046
|
+
kind: 'exec',
|
|
1047
|
+
serverMessage: { command, cwd: dir, workingDirectory: dir },
|
|
1048
|
+
}, 60000);
|
|
1049
|
+
const out = got.stdout || got.message || '';
|
|
1050
|
+
const err = got.stderr ? `\nstderr:\n${got.stderr}` : '';
|
|
1051
|
+
return `${out}${err}`.trim() || `(exit ${got.exitCode ?? 0})`;
|
|
1052
|
+
}
|
|
1053
|
+
const { stdout, stderr } = await execFileAsync('/bin/zsh', ['-lc', command], {
|
|
1054
|
+
cwd: dir,
|
|
1055
|
+
timeout: 30000,
|
|
1056
|
+
maxBuffer: 2 * 1024 * 1024,
|
|
1057
|
+
env: process.env,
|
|
1058
|
+
});
|
|
1059
|
+
return `${stdout || ''}${stderr ? `\nstderr:\n${stderr}` : ''}`.trim() || '(no output)';
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
const LOCAL_TOOLS = [
|
|
1063
|
+
{
|
|
1064
|
+
type: 'function',
|
|
1065
|
+
function: {
|
|
1066
|
+
name: 'read_file',
|
|
1067
|
+
description: 'Read a file on the user\'s Mac. Use this for any local path.',
|
|
1068
|
+
parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] },
|
|
1069
|
+
},
|
|
1070
|
+
},
|
|
1071
|
+
{
|
|
1072
|
+
type: 'function',
|
|
1073
|
+
function: {
|
|
1074
|
+
name: 'write_file',
|
|
1075
|
+
description: 'Write a file on the user\'s Mac. Put HTML/games/code on disk — do not dump huge files in chat.',
|
|
1076
|
+
parameters: {
|
|
1077
|
+
type: 'object',
|
|
1078
|
+
properties: { path: { type: 'string' }, content: { type: 'string' } },
|
|
1079
|
+
required: ['path', 'content'],
|
|
1080
|
+
},
|
|
1081
|
+
},
|
|
1082
|
+
},
|
|
1083
|
+
{
|
|
1084
|
+
type: 'function',
|
|
1085
|
+
function: {
|
|
1086
|
+
name: 'exec',
|
|
1087
|
+
description: 'Run a shell command on the user\'s Mac (zsh -lc). Use for npm, curl, ls, git, installing MCP, etc.',
|
|
1088
|
+
parameters: {
|
|
1089
|
+
type: 'object',
|
|
1090
|
+
properties: { command: { type: 'string' }, cwd: { type: 'string' } },
|
|
1091
|
+
required: ['command'],
|
|
1092
|
+
},
|
|
1093
|
+
},
|
|
1094
|
+
},
|
|
1095
|
+
{
|
|
1096
|
+
type: 'function',
|
|
1097
|
+
function: {
|
|
1098
|
+
name: 'list_dir',
|
|
1099
|
+
description: 'List a directory on the user\'s Mac.',
|
|
1100
|
+
parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] },
|
|
1101
|
+
},
|
|
1102
|
+
},
|
|
1103
|
+
];
|
|
1104
|
+
|
|
1105
|
+
async function runLocalTool(name, args, log) {
|
|
1106
|
+
try {
|
|
1107
|
+
if (name === 'read_file') {
|
|
1108
|
+
const buf = await readLocalBytes(expandUserPath(args.path), log);
|
|
1109
|
+
if (buf.length > 180000) return `file ${args.path} is ${buf.length} bytes; first 180000:\n${buf.subarray(0, 180000).toString('utf8')}`;
|
|
1110
|
+
return buf.toString('utf8');
|
|
1111
|
+
}
|
|
1112
|
+
if (name === 'write_file') {
|
|
1113
|
+
return await writeLocalBytes(expandUserPath(args.path), args.content ?? '', log);
|
|
1114
|
+
}
|
|
1115
|
+
if (name === 'list_dir') {
|
|
1116
|
+
const abs = expandUserPath(args.path || os.homedir());
|
|
1117
|
+
if (localExecSse.size > 0) {
|
|
1118
|
+
const got = await localExecAsk({ kind: 'exec', serverMessage: { command: `ls -la ${JSON.stringify(abs)}`, cwd: os.homedir() } });
|
|
1119
|
+
return got.stdout || got.message || '';
|
|
1120
|
+
}
|
|
1121
|
+
return fs.readdirSync(abs, { withFileTypes: true })
|
|
1122
|
+
.map((e) => `${e.isDirectory() ? 'd' : '-'} ${e.name}`)
|
|
1123
|
+
.join('\n');
|
|
1124
|
+
}
|
|
1125
|
+
if (name === 'exec') {
|
|
1126
|
+
return await execLocal(String(args.command || ''), args.cwd, log);
|
|
1127
|
+
}
|
|
1128
|
+
return `unknown tool ${name}`;
|
|
1129
|
+
} catch (e) {
|
|
1130
|
+
return `ERROR ${e.message}`;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
function zooTextFromMessage(msg, data) {
|
|
1135
|
+
let c = msg?.content;
|
|
1136
|
+
if (Array.isArray(c)) {
|
|
1137
|
+
c = c.map((p) => (typeof p === 'string' ? p : (p?.text || p?.content || ''))).join('');
|
|
1138
|
+
}
|
|
1139
|
+
if (typeof c === 'string' && c.trim()) return c;
|
|
1140
|
+
if (typeof data?.error?.message === 'string' && data.error.message) return data.error.message;
|
|
1141
|
+
return '';
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
async function zooComplete(prompt, log, agentId, parsed = {}) {
|
|
1145
|
+
const model = currentModel(agentId);
|
|
1146
|
+
const helper = localExecSse.size > 0;
|
|
1147
|
+
log(`cursor-backend: zoo POST :8402 model=${model} helper=${helper ? localExecSse.size : 0} ${JSON.stringify((prompt || '').slice(0, 60))}`);
|
|
1148
|
+
|
|
1149
|
+
const images = [];
|
|
1150
|
+
const textFiles = [];
|
|
1151
|
+
for (const { raw, name } of attachmentList(parsed, prompt)) {
|
|
863
1152
|
const abs = expandUserPath(raw);
|
|
864
1153
|
try {
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
1154
|
+
const buf = await readLocalBytes(abs, log);
|
|
1155
|
+
const mime = mimeFromBytes(buf, name || raw);
|
|
1156
|
+
if (mime) {
|
|
1157
|
+
images.push({ path: raw, mime, dataUrl: `data:${mime};base64,${buf.toString('base64')}` });
|
|
1158
|
+
log(`cursor-backend: attached image ${raw} ${mime} ${buf.length}b`);
|
|
869
1159
|
} else {
|
|
870
|
-
|
|
871
|
-
attachments.push({ path: raw, abs, text });
|
|
1160
|
+
textFiles.push({ path: raw, abs, text: buf.toString('utf8') });
|
|
872
1161
|
}
|
|
873
1162
|
} catch (e) {
|
|
874
|
-
|
|
1163
|
+
textFiles.push({ path: raw, abs, error: e.message });
|
|
875
1164
|
}
|
|
876
1165
|
}
|
|
877
|
-
const
|
|
878
|
-
|
|
879
|
-
|
|
1166
|
+
for (const url of dataUrlsFromRichText(parsed.richText)) {
|
|
1167
|
+
images.push({ path: '(richText)', mime: 'image', dataUrl: url });
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
const via = helper ? 'Grok Bot Helper local-exec SSE' : 'this Mac (hijack process; Helper SSE not connected yet)';
|
|
1171
|
+
const messages = [
|
|
1172
|
+
{
|
|
1173
|
+
role: 'system',
|
|
1174
|
+
content: [
|
|
1175
|
+
`You are ${model} served through openzoo inside Grok Bot.`,
|
|
1176
|
+
`You HAVE local tools on the user's computer via ${via}.`,
|
|
1177
|
+
'Tools: read_file, write_file, exec, list_dir. USE THEM. Write files to disk instead of pasting giant HTML into chat.',
|
|
1178
|
+
'Pasted images arrive as attachments — you can see them when present. Do not claim you cannot see images if they are in this turn.',
|
|
1179
|
+
'Never claim you lack filesystem access or local-exec. If a tool errors, report the error.',
|
|
1180
|
+
'A spend footer is appended after your reply by the host — ignore it.',
|
|
1181
|
+
].join(' '),
|
|
1182
|
+
},
|
|
1183
|
+
];
|
|
1184
|
+
if (textFiles.length) {
|
|
1185
|
+
const bits = textFiles.map((a) => (
|
|
880
1186
|
a.error
|
|
881
1187
|
? `FILE ${a.path} ERROR: ${a.error}`
|
|
882
1188
|
: `FILE ${a.path} (${a.abs})\n${String(a.text).slice(0, 180000)}`
|
|
883
1189
|
));
|
|
884
|
-
messages.push({
|
|
885
|
-
role: 'system',
|
|
886
|
-
content: 'Local files below were fetched via Grok Bot local-exec (the user\'s computer). You CAN review them. Do not claim you lack filesystem access.',
|
|
887
|
-
});
|
|
888
1190
|
messages.push({ role: 'user', content: bits.join('\n\n') });
|
|
889
1191
|
}
|
|
890
|
-
|
|
891
|
-
const
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
};
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
1192
|
+
|
|
1193
|
+
const userContent = [];
|
|
1194
|
+
for (const img of images) {
|
|
1195
|
+
userContent.push({ type: 'image_url', image_url: { url: img.dataUrl } });
|
|
1196
|
+
}
|
|
1197
|
+
userContent.push({ type: 'text', text: prompt || (images.length ? '(see attached image)' : 'hello') });
|
|
1198
|
+
messages.push({
|
|
1199
|
+
role: 'user',
|
|
1200
|
+
content: userContent.length === 1 && userContent[0].type === 'text'
|
|
1201
|
+
? userContent[0].text
|
|
1202
|
+
: userContent,
|
|
901
1203
|
});
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1204
|
+
|
|
1205
|
+
const maxTok = Number(process.env.OPENZOO_ASK_MAX_TOKENS || 4096);
|
|
1206
|
+
let lastData = {};
|
|
1207
|
+
let text = '';
|
|
1208
|
+
for (let step = 0; step < 8; step++) {
|
|
1209
|
+
const payload = {
|
|
1210
|
+
model,
|
|
1211
|
+
messages,
|
|
1212
|
+
tools: LOCAL_TOOLS,
|
|
1213
|
+
tool_choice: 'auto',
|
|
1214
|
+
max_tokens: maxTok,
|
|
1215
|
+
};
|
|
1216
|
+
const post = () => fetch('http://127.0.0.1:8402/v1/chat/completions', {
|
|
1217
|
+
method: 'POST',
|
|
1218
|
+
headers: { 'content-type': 'application/json', authorization: 'Bearer sk-openzoo' },
|
|
1219
|
+
body: JSON.stringify(payload),
|
|
1220
|
+
signal: AbortSignal.timeout(120000),
|
|
1221
|
+
});
|
|
1222
|
+
let r = await post();
|
|
1223
|
+
if (r.status === 402) {
|
|
1224
|
+
log('cursor-backend: x402 402 — dwell/retry');
|
|
1225
|
+
await new Promise((ok) => setTimeout(ok, 2500));
|
|
1226
|
+
r = await post();
|
|
1227
|
+
}
|
|
1228
|
+
const data = await r.json();
|
|
1229
|
+
lastData = data;
|
|
1230
|
+
const msg = data.choices?.[0]?.message || {};
|
|
1231
|
+
const calls = Array.isArray(msg.tool_calls) ? msg.tool_calls : [];
|
|
1232
|
+
if (calls.length) {
|
|
1233
|
+
log(`cursor-backend: zoo tools step=${step} n=${calls.length} ${calls.map((c) => c.function?.name || c.name).join(',')}`);
|
|
1234
|
+
messages.push(msg);
|
|
1235
|
+
for (const c of calls) {
|
|
1236
|
+
let args = {};
|
|
1237
|
+
try { args = JSON.parse(c.function?.arguments || c.arguments || '{}'); } catch { args = {}; }
|
|
1238
|
+
const name = c.function?.name || c.name || '';
|
|
1239
|
+
const result = await runLocalTool(name, args, log);
|
|
1240
|
+
messages.push({
|
|
1241
|
+
role: 'tool',
|
|
1242
|
+
tool_call_id: c.id,
|
|
1243
|
+
content: String(result).slice(0, 120000),
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
continue;
|
|
1247
|
+
}
|
|
1248
|
+
text = zooTextFromMessage(msg, data) || (step ? '(tool loop ended with empty content)' : '(empty zoo reply)');
|
|
1249
|
+
log(`cursor-backend: << zoo ${r.status} ${text.length}c model=${data.model || model} finish=${data.choices?.[0]?.finish_reason || '?'}`);
|
|
1250
|
+
break;
|
|
908
1251
|
}
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
try { text += await zooSpendOverlay(data); } catch { /* overlay must never eat the reply */ }
|
|
912
|
-
log(`cursor-backend: << zoo ${r.status} ${text.length}c`);
|
|
913
|
-
return { text, data };
|
|
1252
|
+
try { text += await zooSpendOverlay(lastData); } catch { /* overlay must never eat the reply */ }
|
|
1253
|
+
return { text, data: lastData };
|
|
914
1254
|
}
|
|
915
1255
|
|
|
916
1256
|
async function handleHijackedPodHttp(req, res, full, body, log) {
|
|
@@ -918,7 +1258,8 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
|
|
|
918
1258
|
const waiting = (full || '').split('?')[0];
|
|
919
1259
|
const podPath = waiting === '/health' || waiting === '/healthz' || waiting === '/events'
|
|
920
1260
|
|| waiting.startsWith('/api/') || waiting.startsWith('/webauthn/')
|
|
921
|
-
|| waiting.startsWith('/cookie-origin-approval/')
|
|
1261
|
+
|| waiting.startsWith('/cookie-origin-approval/');
|
|
1262
|
+
if (waiting.startsWith('/local-exec/')) return handleLocalExecHttp(req, res, waiting, body, log);
|
|
922
1263
|
if (realPod?.agent && podPath) return proxyPodHttp(req, res, full, body, log);
|
|
923
1264
|
if (!podPath) return false;
|
|
924
1265
|
if (waiting === '/health' || waiting === '/healthz') {
|
|
@@ -965,12 +1306,56 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
|
|
|
965
1306
|
'getBotTemplateExportPolicy', 'getTeachRecordingStatus', 'isGlobalSearchEnabled',
|
|
966
1307
|
'isEgressTunnelAvailable', 'listBoxMcpServers', 'getHostStatus',
|
|
967
1308
|
'setWindowFocused', 'getAgentAutomations',
|
|
968
|
-
'
|
|
969
|
-
'updateAgent', 'deleteAgents', 'duplicateAgent', 'kickstartAgent',
|
|
1309
|
+
'createGroup', 'setGroupMembers',
|
|
970
1310
|
'interruptAgentRun', 'requestDiskSaverAudit', 'broadcastToAgents',
|
|
971
1311
|
'setAgentUnread', 'setAgentHiddenFromSidebar', 'setAgentNotificationsEnabled',
|
|
972
1312
|
'setAgentNotifyOnUpdates', 'setAgentAvatarBytes', 'getAgentAvatar',
|
|
973
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
|
+
|
|
974
1359
|
if (!sniffOn() && realPod?.agent && roster.has(name)) {
|
|
975
1360
|
return proxyPodHttp(req, res, full, body, log);
|
|
976
1361
|
}
|
|
@@ -984,7 +1369,8 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
|
|
|
984
1369
|
const prompt = promptFromSendBody(parsed);
|
|
985
1370
|
const agentId = String(parsed.agentId || parsed.id || 'openzoo');
|
|
986
1371
|
const nonce = parsed.clientNonce || `oz-${Date.now()}`;
|
|
987
|
-
|
|
1372
|
+
const attN = Array.isArray(parsed.attachmentPaths) ? parsed.attachmentPaths.length : 0;
|
|
1373
|
+
log(`cursor-backend: >> sendPrompt agent=${agentId} keys=${Object.keys(parsed).join(',')} attachments=${attN} prompt=${JSON.stringify((prompt || '').slice(0, 80))}`);
|
|
988
1374
|
lastSendEchoId = String(nonce);
|
|
989
1375
|
const userLine = fanoutLine(agentId, 'user', prompt, {
|
|
990
1376
|
clientNonce: nonce,
|
|
@@ -1000,7 +1386,7 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
|
|
|
1000
1386
|
if (modelCmd) {
|
|
1001
1387
|
const want = modelCmd[1];
|
|
1002
1388
|
if (!want) {
|
|
1003
|
-
const cur =
|
|
1389
|
+
const cur = currentModel(agentId);
|
|
1004
1390
|
text = `current model: ${cur}\nset with /model fable | opus | sonnet | grok | provider/id`;
|
|
1005
1391
|
} else {
|
|
1006
1392
|
const id = resolveModelId(want) || (want.includes('/') ? want : null);
|
|
@@ -1008,12 +1394,13 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
|
|
|
1008
1394
|
text = `unknown model "${want}". try /model fable | opus | sonnet | grok or a full id like anthropic/claude-fable-5`;
|
|
1009
1395
|
} else {
|
|
1010
1396
|
agentModels.set(agentId, id);
|
|
1397
|
+
saveAgentModels();
|
|
1011
1398
|
text = `model set to ${id}`;
|
|
1012
1399
|
}
|
|
1013
1400
|
}
|
|
1014
1401
|
try { text += await zooSpendOverlay({}); } catch { /* */ }
|
|
1015
1402
|
} else {
|
|
1016
|
-
const z = await zooComplete(prompt, log, agentId);
|
|
1403
|
+
const z = await zooComplete(prompt, log, agentId, parsed);
|
|
1017
1404
|
text = z.text;
|
|
1018
1405
|
}
|
|
1019
1406
|
} catch (e) {
|
|
@@ -1029,14 +1416,9 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
|
|
|
1029
1416
|
if (name === 'getAgentTranscriptTail' || name === 'getAgentTranscriptWindow' || name === 'openAgentTail') {
|
|
1030
1417
|
let parsed = {};
|
|
1031
1418
|
try { parsed = JSON.parse(String(body || '{}')); } catch { parsed = {}; }
|
|
1032
|
-
|
|
1419
|
+
const id = String(parsed.id || parsed.agentId || 'openzoo');
|
|
1033
1420
|
tailedAgents.add(id);
|
|
1034
|
-
|
|
1035
|
-
if (!t.entries.length) {
|
|
1036
|
-
for (const [other, ot] of transcripts) {
|
|
1037
|
-
if (ot.entries.length) { id = other; t = ot; break; }
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1421
|
+
const t = agentTranscript(id);
|
|
1040
1422
|
const limit = Math.min(Number(parsed.limit) || 50, 200);
|
|
1041
1423
|
const before = parsed.beforeSeq != null ? Number(parsed.beforeSeq) : Infinity;
|
|
1042
1424
|
const sliced = t.entries.filter((e) => e.seq < before).slice(-limit);
|
|
@@ -1074,7 +1456,8 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
|
|
|
1074
1456
|
getHostSettings: { settings: {} },
|
|
1075
1457
|
setHostSettings: { ok: true },
|
|
1076
1458
|
setBoxSecrets: { ok: true },
|
|
1077
|
-
listAgents:
|
|
1459
|
+
listAgents: cachedAgentList()
|
|
1460
|
+
|| [...new Set([...transcripts.keys(), ...tailedAgents])].map((id) => ({ id, name: id, status: 'ready' })),
|
|
1078
1461
|
getAgentTranscriptTail: { tail: '', lines: [], dropped: false, ok: true },
|
|
1079
1462
|
getTeachRecordingStatus: { recording: false },
|
|
1080
1463
|
getTrays: { trays: [] },
|
|
@@ -1155,6 +1538,9 @@ function respond(req, res, method, models) {
|
|
|
1155
1538
|
*/
|
|
1156
1539
|
export function startCursorBackend({ port = 8443, models, log = () => {} } = {}) {
|
|
1157
1540
|
const { cert, key } = ensureCert(log);
|
|
1541
|
+
const certPem = fs.readFileSync(cert);
|
|
1542
|
+
const keyPem = fs.readFileSync(key);
|
|
1543
|
+
const secureContext = tls.createSecureContext({ cert: certPem, key: keyPem });
|
|
1158
1544
|
let conns = 0;
|
|
1159
1545
|
// SERVE BOTH h2 AND h1. An earlier build forced h1-only after concluding the
|
|
1160
1546
|
// editor's h2 connections reset — but that log was the STALE 8443 backend the
|
|
@@ -1163,13 +1549,20 @@ export function startCursorBackend({ port = 8443, models, log = () => {} } = {})
|
|
|
1163
1549
|
// (ERR_SSL_NO_APPLICATION_PROTOCOL when we advertise just h1), so we must
|
|
1164
1550
|
// negotiate both. allowHTTP1 keeps the h1 fetch() calls (stripe/updates)
|
|
1165
1551
|
// working; ALPN h2 first satisfies the Connect gRPC client.
|
|
1552
|
+
// SNICallback MUST pass the SecureContext — cb(null) with no ctx is why the
|
|
1553
|
+
// Helper daemon's Node fetch never completed GET /local-exec/requests (TLS
|
|
1554
|
+
// ECONNRESET, no request log). Chromium --ignore-certificate-errors hid this
|
|
1555
|
+
// for the UI process.
|
|
1166
1556
|
const server = http2.createSecureServer(
|
|
1167
1557
|
{
|
|
1168
|
-
cert:
|
|
1169
|
-
key:
|
|
1558
|
+
cert: certPem,
|
|
1559
|
+
key: keyPem,
|
|
1170
1560
|
allowHTTP1: true,
|
|
1171
1561
|
ALPNProtocols: ['h2', 'http/1.1'],
|
|
1172
|
-
SNICallback: (servername, cb) => {
|
|
1562
|
+
SNICallback: (servername, cb) => {
|
|
1563
|
+
log(`cursor-tls: <- ClientHello SNI=${servername || '?'}`);
|
|
1564
|
+
cb(null, secureContext);
|
|
1565
|
+
},
|
|
1173
1566
|
},
|
|
1174
1567
|
async (req, res) => {
|
|
1175
1568
|
conns += 1;
|
|
@@ -1281,25 +1674,31 @@ export function startCursorBackend({ port = 8443, models, log = () => {} } = {})
|
|
|
1281
1674
|
// with OUR box so Grok Bot's UI wires to our sandbox; everything else
|
|
1282
1675
|
// still passes through so the app loads normally.
|
|
1283
1676
|
if (/GrokBotService\/(EnsureSandBox|WatchSandBoxMigration)/.test(full) && process.env.OZ_HIJACK_POD) {
|
|
1284
|
-
if (/WatchSandBoxMigration/.test(full) && realPod) {
|
|
1285
|
-
const payload = rewrittenBox();
|
|
1286
|
-
res.writeHead(200, {
|
|
1287
|
-
'content-type': 'application/connect+proto',
|
|
1288
|
-
'grpc-status': '0',
|
|
1289
|
-
...CORS,
|
|
1290
|
-
});
|
|
1291
|
-
const end = Buffer.from('{}');
|
|
1292
|
-
const h = Buffer.alloc(5); h.writeUInt8(0x02, 0); h.writeUInt32BE(end.length, 1);
|
|
1293
|
-
res.end(Buffer.concat([envelope(payload), h, end]));
|
|
1294
|
-
log('cursor-backend: -> WatchSandBoxMigration ready (hijack, real roster)');
|
|
1295
|
-
return;
|
|
1296
|
-
}
|
|
1297
1677
|
try {
|
|
1298
1678
|
process.env.OZ_SNIFF_SELF = process.env.OZ_SNIFF_SELF || 'https://127.0.0.1:8443';
|
|
1299
1679
|
await sniffEnsureSandBox(req, res, body, host, full, log);
|
|
1300
1680
|
log('cursor-backend: -> HIJACKED EnsureSandBox -> our box (roster from real 1340)');
|
|
1301
1681
|
return;
|
|
1302
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
|
+
}
|
|
1303
1702
|
log(`cursor-backend: EnsureSandBox discover failed (${e.message}) — env box`);
|
|
1304
1703
|
}
|
|
1305
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}`);
|
|
@@ -190,6 +192,12 @@ export async function runBot(argv = []) {
|
|
|
190
192
|
NODE_TLS_REJECT_UNAUTHORIZED: '0',
|
|
191
193
|
CURSOR_API_BASE_URL: url,
|
|
192
194
|
SAND_BACKEND_URL: url,
|
|
195
|
+
// Helper daemon (local-exec-daemon/main.cjs) uses Node fetch, not Chromium.
|
|
196
|
+
// Without these it dials the cached cursorvm 1337 URL / dies on our
|
|
197
|
+
// self-signed cert and GET /local-exec/requests never arrives.
|
|
198
|
+
SAND_HOST_GATEWAY_URL: url,
|
|
199
|
+
SAND_HOST_GATEWAY_TOKEN: 'openzoo',
|
|
200
|
+
SAND_HOST_GATEWAY_NETWORK_TOKEN: 'openzoo',
|
|
193
201
|
},
|
|
194
202
|
}).unref();
|
|
195
203
|
|
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",
|