openzoo 0.49.17 → 0.50.0
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/bin/openzoo.js +0 -9
- package/lib/models.js +27 -211
- package/lib/openclaw.js +38 -5
- package/lib/proxy.js +113 -1371
- package/lib/xbot.js +1569 -0
- package/lib/xburner.js +80 -0
- package/package.json +4 -5
- package/lib/anthropic.js +0 -369
- package/lib/boxes.js +0 -299
- package/lib/brief.js +0 -76
- package/lib/grokbot.js +0 -120
- package/lib/grokui.mjs +0 -6295
- package/lib/modelroute/README.md +0 -1
- package/lib/modelroute/catalog.json +0 -1
- package/lib/modelroute/outcomes.json +0 -1566
- package/lib/modelroute/router.json +0 -1
- package/lib/modelroute.js +0 -737
- package/lib/podagent.mjs +0 -1414
- package/lib/responses-stream.js +0 -176
- package/lib/responses.js +0 -425
- package/lib/runguard.js +0 -31
- package/lib/spill.js +0 -2031
- package/lib/worktree.mjs +0 -424
- package/vendor/modelroute/CURRENT_STATE.md +0 -132
- package/vendor/modelroute/FOR_MOOSE.md +0 -110
- package/vendor/modelroute/HANDOFF.md +0 -159
- package/vendor/modelroute/catalog.json +0 -1
- package/vendor/modelroute/holographic_modelroute.py +0 -809
- package/vendor/modelroute/outcomes.json +0 -1566
- package/vendor/modelroute/router.json +0 -1
package/lib/boxes.js
DELETED
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
// RunPod CPU boxes — the sandbox fleet `openzoo grokbot` runs its agents in.
|
|
2
|
-
//
|
|
3
|
-
// SAME SHAPE AS GROK BOT, DIFFERENT ECONOMICS. Grok Bot provisions a
|
|
4
|
-
// cursorvm.com pod per bot and runs the agent inside Cursor's cloud, billing
|
|
5
|
-
// through them (MEASURED: EnsureSandBox returns
|
|
6
|
-
// `<id>-pod-<id>-1337.us9.cursorvm.com` + a VNC port + /workspace/terminals).
|
|
7
|
-
// This does the same thing on RunPod, with inference paid per-call by x402
|
|
8
|
-
// from a wallet the box holds — no account, no provider key.
|
|
9
|
-
//
|
|
10
|
-
// CPU ONLY, ALWAYS. A GPU flavour here would be a silent 100x on the bill for
|
|
11
|
-
// a box that only ever runs a shell and a node process. `spawnBox` builds its
|
|
12
|
-
// request body without GPU fields AND asserts on the serialized JSON, because
|
|
13
|
-
// an `in` check against a literal you wrote three lines earlier can never fail
|
|
14
|
-
// — that guard is decoration, not enforcement.
|
|
15
|
-
|
|
16
|
-
import { execFile } from 'node:child_process';
|
|
17
|
-
import { promisify } from 'node:util';
|
|
18
|
-
|
|
19
|
-
const pexec = promisify(execFile);
|
|
20
|
-
|
|
21
|
-
import { readFileSync } from 'node:fs';
|
|
22
|
-
import { fileURLToPath } from 'node:url';
|
|
23
|
-
import { dirname, join } from 'node:path';
|
|
24
|
-
let _agentB64 = null;
|
|
25
|
-
function podAgentB64() {
|
|
26
|
-
if (_agentB64) return _agentB64;
|
|
27
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
28
|
-
_agentB64 = readFileSync(join(here, 'podagent.mjs')).toString('base64');
|
|
29
|
-
return _agentB64;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const REST = 'https://rest.runpod.io/v1';
|
|
33
|
-
export const BOX_PREFIX = 'openzoo-box-';
|
|
34
|
-
const CPU_FLAVORS = ['cpu3c', 'cpu3g', 'cpu5c'];
|
|
35
|
-
// GLIBC, NOT ALPINE. `npx openzoo` pulls native deps (ed25519 / ws helpers);
|
|
36
|
-
// on musl they fail to build because the entrypoint has no toolchain, which is
|
|
37
|
-
// why alpine boxes sat at 404 forever. Debian-slim runs the shim as published,
|
|
38
|
-
// and its apt has a real sshd.
|
|
39
|
-
// Everything the box runs is ALREADY IN THE IMAGE: the shim, its node_modules,
|
|
40
|
-
// grokui, and sshd. Boxes used to apt-get and `npx openzoo` at boot, which put
|
|
41
|
-
// a package fetch on the critical path from RunPod egress IPs — and those get
|
|
42
|
-
// rate limited. A 429 from raw.githubusercontent arrives as a FILE whose
|
|
43
|
-
// contents are the text "429: Too Many Requests", so node ran it and failed in
|
|
44
|
-
// a way that read as an app bug rather than a download bug.
|
|
45
|
-
// Still glibc, never musl: @solana's native deps don't build on the box.
|
|
46
|
-
const BOX_IMAGE = process.env.OPENZOO_BOX_IMAGE || 'jrsdunn123/openzoo-box:latest';
|
|
47
|
-
const BOX_PORTS = ['8402/http', '4173/http', '1337/http', '6080/http', '1340/http', '6081/http', '22/tcp'];
|
|
48
|
-
const TTL_MAX_HOURS = 90 * 24;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Boot: write the wallet, START SSHD, start the x402 proxy + capture agent.
|
|
52
|
-
*
|
|
53
|
-
* SSHD IS NOT OPTIONAL and was the second bug: our dockerStartCmd replaces the
|
|
54
|
-
* image's own init, so nothing starts sshd and every `execInBox` got
|
|
55
|
-
* "connection refused" — the agent had no hands. RunPod injects the account
|
|
56
|
-
* key at $PUBLIC_KEY / $SSH_PUBLIC_KEY; we write it to authorized_keys, generate
|
|
57
|
-
* host keys, and run sshd in the foreground of the background. Passwordless
|
|
58
|
-
* root login is NOT enabled — key-only.
|
|
59
|
-
*
|
|
60
|
-
* NOTHING IS FETCHED AT BOOT. sshd, the shim and grokui are baked into
|
|
61
|
-
* BOX_IMAGE by box.Dockerfile; this only copies /opt into the volume RunPod
|
|
62
|
-
* mounts over /workspace and starts things. readyBox() still polls /v1/models
|
|
63
|
-
* rather than assuming a timing.
|
|
64
|
-
*/
|
|
65
|
-
const ENTRYPOINT = [
|
|
66
|
-
'bash', '-lc',
|
|
67
|
-
'set +e; mkdir -p /workspace /root/.openzoo /var/log/openzoo /run/sshd /root/.ssh; '
|
|
68
|
-
+ 'if [ -n "$OPENZOO_WALLET_JSON" ]; then printf %s "$OPENZOO_WALLET_JSON" > /root/.openzoo/wallet.json; chmod 600 /root/.openzoo/wallet.json; fi; '
|
|
69
|
-
// RunPod hands the operator pubkey in one of these — accept whichever is set
|
|
70
|
-
+ 'for K in "$PUBLIC_KEY" "$SSH_PUBLIC_KEY"; do [ -n "$K" ] && echo "$K" >> /root/.ssh/authorized_keys; done; chmod 600 /root/.ssh/authorized_keys 2>/dev/null; '
|
|
71
|
-
+ 'ssh-keygen -A >/dev/null 2>&1; sed -i "s/^#\\?PermitRootLogin.*/PermitRootLogin prohibit-password/" /etc/ssh/sshd_config 2>/dev/null; /usr/sbin/sshd -e >/var/log/openzoo/sshd.log 2>&1 & '
|
|
72
|
-
// RUN FROM /opt, never from /workspace. Staging the app onto the volume and
|
|
73
|
-
// gating on `[ ! -f .../bin/openzoo.js ]` is a trap: one interrupted copy
|
|
74
|
-
// leaves that file behind, every later boot skips the copy, and the box
|
|
75
|
-
// crash-loops on a half-copied node_modules forever (seen in production as
|
|
76
|
-
// "Cannot find module .../viem/accounts" every 15s, which also cost the pod
|
|
77
|
-
// its HTTP port mappings). The app is read-only at runtime; mutable state
|
|
78
|
-
// lives in /root/.openzoo.
|
|
79
|
-
+ 'until OPENZOO_BIND=0.0.0.0 node /opt/openzoo/bin/openzoo.js >> /var/log/openzoo/proxy.log 2>&1; do echo "proxy exited, restarting" >> /var/log/openzoo/proxy.log; sleep 2; done & '
|
|
80
|
-
+ 'until OZ_GROKUI_BIND=0.0.0.0 OZ_GROKUI_PORT=4173 node /opt/openzoo/lib/grokui.mjs >> /var/log/openzoo/grokui.log 2>&1; do echo "grokui exited, restarting" >> /var/log/openzoo/grokui.log; sleep 2; done & '
|
|
81
|
-
// the capture agent answers the ports Grok Bot expects a Cursor sandbox on,
|
|
82
|
-
// logging the protocol we do not yet speak (see lib/podagent.mjs)
|
|
83
|
-
+ 'if [ -n "$OZ_PODAGENT_B64" ]; then printf %s "$OZ_PODAGENT_B64" | base64 -d > /opt/podagent.mjs; node /opt/podagent.mjs > /var/log/openzoo/agent.log 2>&1 & fi; '
|
|
84
|
-
+ 'sleep infinity',
|
|
85
|
-
];
|
|
86
|
-
|
|
87
|
-
function key() {
|
|
88
|
-
const k = process.env.RUNPOD_API_KEY || '';
|
|
89
|
-
return typeof k === 'string' ? k.trim() : '';
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function runpodConfigured() {
|
|
93
|
-
// guard the access, not just the prefix — an unset key must not throw here
|
|
94
|
-
return key().startsWith('rpa_');
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
async function rp(path, init) {
|
|
98
|
-
const k = key();
|
|
99
|
-
if (!k) return { ok: false, status: 503, data: { error: 'RUNPOD_API_KEY not set' } };
|
|
100
|
-
const res = await fetch(`${REST}${path}`, {
|
|
101
|
-
...init,
|
|
102
|
-
headers: { authorization: `Bearer ${k}`, 'content-type': 'application/json', ...(init?.headers || {}) },
|
|
103
|
-
});
|
|
104
|
-
const data = await res.json().catch(() => ({}));
|
|
105
|
-
return { ok: res.ok, status: res.status, data };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function ttlHours(days = 30) {
|
|
109
|
-
return Math.min(Math.max(Number(days) || 30, 1) * 24, TTL_MAX_HOURS);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Expiry is encoded in the name so a box is reapable even if its env is lost.
|
|
114
|
-
*
|
|
115
|
-
* PARSED FROM A FIXED POSITION, NOT `.pop()`. The name ends
|
|
116
|
-
* `-<unix>-<rand4>`, so popping the last segment yields the RANDOM suffix,
|
|
117
|
-
* Number() gives NaN, and every box reports "no expiry" — which is exactly the
|
|
118
|
-
* bug the site's copy of this had.
|
|
119
|
-
*/
|
|
120
|
-
export function expiresFromName(name) {
|
|
121
|
-
if (!name?.startsWith(BOX_PREFIX)) return null;
|
|
122
|
-
const parts = name.split('-');
|
|
123
|
-
const n = Number(parts[parts.length - 2]);
|
|
124
|
-
return Number.isFinite(n) && n > 1_700_000_000 ? n : null;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export function proxyUrl(id, port = 8402) {
|
|
128
|
-
return id ? `https://${id}-${port}.proxy.runpod.net` : null;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/** The URLs Grok Bot's EnsureSandBox response needs, all fronted by RunPod. */
|
|
132
|
-
export function podEndpoints(id) {
|
|
133
|
-
return id ? {
|
|
134
|
-
agent: proxyUrl(id, 1337),
|
|
135
|
-
vnc: proxyUrl(id, 6080),
|
|
136
|
-
p1340: proxyUrl(id, 1340),
|
|
137
|
-
p6081: proxyUrl(id, 6081),
|
|
138
|
-
proxy: proxyUrl(id, 8402),
|
|
139
|
-
} : null;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/** What a caller may see. NEVER the raw pod: `env` carries OPENZOO_WALLET_JSON,
|
|
143
|
-
* which is a funded private key. Raw pods must not escape this module. */
|
|
144
|
-
export function serializeBox(p) {
|
|
145
|
-
const exp = expiresFromName(p.name) || Number(p.env?.OPENZOO_EXPIRES_UNIX || 0) || null;
|
|
146
|
-
const sshPort = p.portMappings?.['22'];
|
|
147
|
-
return {
|
|
148
|
-
id: p.id,
|
|
149
|
-
name: p.name,
|
|
150
|
-
status: p.desiredStatus,
|
|
151
|
-
costPerHr: p.costPerHr,
|
|
152
|
-
proxy: proxyUrl(p.id),
|
|
153
|
-
ssh: p.publicIp && sshPort ? `ssh -o StrictHostKeyChecking=no root@${p.publicIp} -p ${sshPort}` : null,
|
|
154
|
-
host: p.publicIp || null,
|
|
155
|
-
sshPort: sshPort || null,
|
|
156
|
-
console: p.id ? `https://www.runpod.io/console/pods/${p.id}` : null,
|
|
157
|
-
expiresAt: exp ? new Date(exp * 1000).toISOString() : null,
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** Our boxes only, serialized. */
|
|
162
|
-
export async function listBoxes() {
|
|
163
|
-
const r = await rp('/pods');
|
|
164
|
-
if (!Array.isArray(r.data)) return { ok: false, error: r.data?.error || `pods ${r.status}`, boxes: [] };
|
|
165
|
-
return {
|
|
166
|
-
ok: true,
|
|
167
|
-
boxes: r.data.filter((p) => !p.gpuCount && p.name?.startsWith(BOX_PREFIX)).map(serializeBox),
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export async function killBox(id) {
|
|
172
|
-
return rp(`/pods/${id}`, { method: 'DELETE' });
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/** Delete our boxes past their deadline. Name-prefix scoped AND gpu-guarded. */
|
|
176
|
-
export async function reapExpired() {
|
|
177
|
-
const r = await rp('/pods');
|
|
178
|
-
if (!Array.isArray(r.data)) return { reaped: [] };
|
|
179
|
-
const now = Math.floor(Date.now() / 1000);
|
|
180
|
-
const reaped = [];
|
|
181
|
-
for (const p of r.data) {
|
|
182
|
-
if (!p.id || p.gpuCount || !p.name?.startsWith(BOX_PREFIX)) continue;
|
|
183
|
-
const deadline = expiresFromName(p.name) || Number(p.env?.OPENZOO_EXPIRES_UNIX || 0);
|
|
184
|
-
if (deadline && now >= deadline) {
|
|
185
|
-
await killBox(p.id);
|
|
186
|
-
reaped.push(p.id);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return { reaped };
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* MACHINE TIERS — a menu, not one size.
|
|
194
|
-
*
|
|
195
|
-
* A box that only runs a shell is fine on 2 vCPU, but a grokbot agent that
|
|
196
|
-
* builds a site, runs a dev server, or compiles wants real muscle — and some
|
|
197
|
-
* tasks want a GPU. So the orchestrator quotes SEVERAL tiers per timeframe and
|
|
198
|
-
* the user picks. GPU is a DELIBERATE tier here, never an accident: the guard
|
|
199
|
-
* below refuses GPU fields on a CPU tier, so a cpu tier can't silently 100x the
|
|
200
|
-
* bill, while a gpu tier carries them on purpose.
|
|
201
|
-
*
|
|
202
|
-
* $/hr are approximate RunPod secure-cloud rates for sizing the quote; the real
|
|
203
|
-
* charge is whatever RunPod bills. estUsdHr is labelled approximate for that.
|
|
204
|
-
*/
|
|
205
|
-
export const BOX_TIERS = {
|
|
206
|
-
'cpu-sm': { label: 'CPU · 2 vCPU', compute: 'CPU', vcpu: 2, disk: 20, estUsdHr: 0.06 },
|
|
207
|
-
'cpu-md': { label: 'CPU · 4 vCPU', compute: 'CPU', vcpu: 4, disk: 40, estUsdHr: 0.12 },
|
|
208
|
-
'cpu-lg': { label: 'CPU · 8 vCPU', compute: 'CPU', vcpu: 8, disk: 60, estUsdHr: 0.24 },
|
|
209
|
-
'gpu-4090':{ label: 'GPU · 1× RTX 4090', compute: 'GPU', gpuTypeIds: ['NVIDIA GeForce RTX 4090'], gpuCount: 1, disk: 60, estUsdHr: 0.44 },
|
|
210
|
-
'gpu-a100':{ label: 'GPU · 1× A100 80GB', compute: 'GPU', gpuTypeIds: ['NVIDIA A100 80GB PCIe'], gpuCount: 1, disk: 80, estUsdHr: 1.64 },
|
|
211
|
-
};
|
|
212
|
-
export const DEFAULT_TIER = 'cpu-sm';
|
|
213
|
-
|
|
214
|
-
/** The quote menu the orchestrator shows: every tier priced for `days`. */
|
|
215
|
-
export function quoteTiers(days = 1) {
|
|
216
|
-
const hrs = ttlHours(days);
|
|
217
|
-
return Object.entries(BOX_TIERS).map(([key, t]) => ({
|
|
218
|
-
tier: key, label: t.label, days: Math.round(hrs / 24 * 10) / 10,
|
|
219
|
-
estUsd: Math.round(t.estUsdHr * hrs * 100) / 100, estUsdHr: t.estUsdHr, gpu: t.compute === 'GPU',
|
|
220
|
-
}));
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export async function spawnBox({ name = 'bot', days = 1, tier = DEFAULT_TIER, walletJson, pubkey, task } = {}) {
|
|
224
|
-
const spec = BOX_TIERS[tier] || BOX_TIERS[DEFAULT_TIER];
|
|
225
|
-
const expiresAt = new Date(Date.now() + ttlHours(days) * 3600 * 1000);
|
|
226
|
-
const unix = Math.floor(expiresAt.getTime() / 1000);
|
|
227
|
-
const rand = Math.random().toString(36).slice(2, 6);
|
|
228
|
-
const full = `${BOX_PREFIX}${name}-${unix}-${rand}`;
|
|
229
|
-
|
|
230
|
-
const body = {
|
|
231
|
-
name: full,
|
|
232
|
-
computeType: spec.compute,
|
|
233
|
-
cloudType: 'SECURE',
|
|
234
|
-
imageName: BOX_IMAGE,
|
|
235
|
-
containerDiskInGb: spec.disk,
|
|
236
|
-
volumeInGb: 10,
|
|
237
|
-
volumeMountPath: '/workspace',
|
|
238
|
-
ports: BOX_PORTS,
|
|
239
|
-
dockerStartCmd: ENTRYPOINT,
|
|
240
|
-
...(spec.compute === 'CPU'
|
|
241
|
-
? { vcpuCount: spec.vcpu, cpuFlavorIds: CPU_FLAVORS, cpuFlavorPriority: 'availability' }
|
|
242
|
-
: { gpuCount: spec.gpuCount, gpuTypeIds: spec.gpuTypeIds }),
|
|
243
|
-
env: {
|
|
244
|
-
OPENZOO_BOX: '1',
|
|
245
|
-
OPENZOO_TIER: tier,
|
|
246
|
-
OPENZOO_EXPIRES_UNIX: String(unix),
|
|
247
|
-
OPENZOO_NO_TUNNEL: '1', // the runpod proxy already fronts it
|
|
248
|
-
OPENZOO_BIND: '0.0.0.0', // else the runpod proxy can't reach :8402
|
|
249
|
-
OPENZOO_TUNNEL_TOKEN: `oz-${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`,
|
|
250
|
-
...(walletJson ? { OPENZOO_WALLET_JSON: walletJson } : {}),
|
|
251
|
-
...(pubkey ? { OPENZOO_WALLET_PUBKEY: pubkey } : {}),
|
|
252
|
-
OZ_PODAGENT_B64: podAgentB64(),
|
|
253
|
-
...(task ? { OZ_TASK: task } : {}),
|
|
254
|
-
},
|
|
255
|
-
};
|
|
256
|
-
// GPU ONLY ON A GPU TIER. A cpu tier must never carry GPU fields — that would
|
|
257
|
-
// be a silent 100x. Enforced on the serialized payload, not a literal.
|
|
258
|
-
const wire = JSON.stringify(body);
|
|
259
|
-
if (spec.compute === 'CPU' && /"gpu(Count|TypeIds)"/.test(wire)) {
|
|
260
|
-
throw new Error('refusing to spawn: GPU fields on a CPU tier');
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const created = await rp('/pods', { method: 'POST', body: wire });
|
|
264
|
-
if (!created.ok) return { ok: false, error: created.data?.error || `spawn ${created.status}` };
|
|
265
|
-
return { ok: true, tier, box: serializeBox(created.data), expiresAt: expiresAt.toISOString() };
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/** Poll until the box answers on its proxy — npx has to fetch the shim first. */
|
|
269
|
-
export async function readyBox(id, { timeoutMs = 240_000, log = () => {} } = {}) {
|
|
270
|
-
const url = `${proxyUrl(id)}/v1/models`;
|
|
271
|
-
const started = Date.now();
|
|
272
|
-
let last = '';
|
|
273
|
-
while (Date.now() - started < timeoutMs) {
|
|
274
|
-
try {
|
|
275
|
-
const r = await fetch(url, { signal: AbortSignal.timeout(6000) });
|
|
276
|
-
if (r.ok) return { ok: true, seconds: Math.round((Date.now() - started) / 1000) };
|
|
277
|
-
last = `http ${r.status}`;
|
|
278
|
-
} catch (e) { last = e.name === 'TimeoutError' ? 'timeout' : e.message; }
|
|
279
|
-
log(` waiting for box proxy… (${Math.round((Date.now() - started) / 1000)}s, ${last})`);
|
|
280
|
-
await new Promise((r) => setTimeout(r, 6000));
|
|
281
|
-
}
|
|
282
|
-
return { ok: false, error: `box proxy never came up (${last})` };
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/** Run a command INSIDE the box. This is the sandbox half of the product:
|
|
286
|
-
* the model runs wherever openzoo routes it, the side effects land here. */
|
|
287
|
-
export async function execInBox(box, cmd, { timeoutMs = 120_000 } = {}) {
|
|
288
|
-
if (!box.host || !box.sshPort) return { ok: false, stdout: '', stderr: 'box has no ssh yet' };
|
|
289
|
-
try {
|
|
290
|
-
const { stdout, stderr } = await pexec('ssh', [
|
|
291
|
-
'-o', 'StrictHostKeyChecking=no', '-o', 'UserKnownHostsFile=/dev/null',
|
|
292
|
-
'-o', 'LogLevel=ERROR', '-o', 'ConnectTimeout=15',
|
|
293
|
-
'-p', String(box.sshPort), `root@${box.host}`, cmd,
|
|
294
|
-
], { timeout: timeoutMs, maxBuffer: 8 * 1024 * 1024 });
|
|
295
|
-
return { ok: true, stdout, stderr };
|
|
296
|
-
} catch (e) {
|
|
297
|
-
return { ok: false, stdout: e.stdout || '', stderr: e.stderr || e.message };
|
|
298
|
-
}
|
|
299
|
-
}
|
package/lib/brief.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The capability briefing the proxy injects into every conversation.
|
|
3
|
-
*
|
|
4
|
-
* WHY THIS EXISTS: an agent on the other side of this proxy has no idea what
|
|
5
|
-
* it is connected to. It assumes a stock OpenAI endpoint, so it chunks corpora
|
|
6
|
-
* it could bind whole, hunts for metadata by shelling around the operator's
|
|
7
|
-
* machine, and re-uploads the same megabytes every turn. Documentation the
|
|
8
|
-
* agent never reads does not help it. Telling it IN BAND does.
|
|
9
|
-
*
|
|
10
|
-
* Kept short on purpose — it rides every request, so it earns its tokens.
|
|
11
|
-
* OPENZOO_NO_BRIEF=1 turns it off.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* `selfUrl` is the base URL THIS request arrived on — the tunnel URL for a
|
|
16
|
-
* remote harness, localhost otherwise. Without it an agent writing a helper
|
|
17
|
-
* script has to guess its own endpoint, and it guesses wrong: observed in the
|
|
18
|
-
* wild, a Cursor agent hardcoded `https://openzoo.fun/v1` (a marketing site,
|
|
19
|
-
* not an API) because nothing in the conversation named the real one.
|
|
20
|
-
*/
|
|
21
|
-
export const briefFor = (selfUrl) => [
|
|
22
|
-
// SMALL ON PURPOSE. This used to be ~2.2KB of prose on EVERY request —
|
|
23
|
-
// corpus binding, request-size limits, payment rails, model-id matching —
|
|
24
|
-
// most of which a given call never needs, all of which the caller pays for in
|
|
25
|
-
// tokens and latency. An agent that needs the detail can ask; what it cannot
|
|
26
|
-
// work out for itself is the base URL and the one behaviour that changes how
|
|
27
|
-
// it should send a big body. Everything else was documentation shipped as
|
|
28
|
-
// overhead.
|
|
29
|
-
...(selfUrl ? [`Endpoint: ${selfUrl} (already ends in /v1). Routes: /chat/completions, /hrr/bind, /models.`] : []),
|
|
30
|
-
'Bodies over ~16KB are bound to holographic memory and answered by retrieval, so a large corpus can be sent whole rather than summarised or chunked. A corpus sent once is not re-uploaded.',
|
|
31
|
-
'Calls are paid per request from the operator\'s wallet; there is no key to supply. Unknown model ids match the nearest served model.',
|
|
32
|
-
].join('\n');
|
|
33
|
-
|
|
34
|
-
/** Stable substring used to detect an already-injected brief. Must appear in
|
|
35
|
-
* briefFor() output verbatim — see injectBrief(). */
|
|
36
|
-
export const BRIEF_MARK = 'bound to holographic memory and answered by retrieval';
|
|
37
|
-
|
|
38
|
-
/** Back-compat: the briefing with no endpoint line. */
|
|
39
|
-
export const BRIEF = briefFor(null);
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Inject the briefing as a system message. Idempotent (never doubles up if a
|
|
43
|
-
* conversation already carries it), non-destructive (an existing system
|
|
44
|
-
* message keeps its position and content — ours is appended after it, since
|
|
45
|
-
* harnesses often pin behaviour in the first system turn).
|
|
46
|
-
* Returns null when nothing should change.
|
|
47
|
-
*/
|
|
48
|
-
export function injectBrief(body, selfUrl = null) {
|
|
49
|
-
if (process.env.OPENZOO_NO_BRIEF === '1') return null;
|
|
50
|
-
const msgs = body?.messages;
|
|
51
|
-
if (!Array.isArray(msgs) || !msgs.length) return null;
|
|
52
|
-
// THE SENTINEL MUST BE TEXT THE BRIEF ACTUALLY CONTAINS. This checked for
|
|
53
|
-
// 'connected through an openzoo proxy' — the old opening line — so shrinking
|
|
54
|
-
// the brief would have silently broken idempotency and stacked a fresh copy
|
|
55
|
-
// onto every single turn, growing the system block without bound.
|
|
56
|
-
if (msgs.some((m) => typeof m?.content === 'string' && m.content.includes(BRIEF_MARK))) return null;
|
|
57
|
-
|
|
58
|
-
const brief = { role: 'system', content: briefFor(selfUrl) };
|
|
59
|
-
// THE LEADING SYSTEM RUN ONLY — NOT THE LAST SYSTEM ANYWHERE.
|
|
60
|
-
//
|
|
61
|
-
// This used to reduce over the WHOLE array for the last `role === 'system'`,
|
|
62
|
-
// which is the same thing only while every system message sits at the front.
|
|
63
|
-
// The moment anything injects one later, the brief is spliced in AFTER the
|
|
64
|
-
// user's turn, and the conversation the model receives ends with operator
|
|
65
|
-
// notes instead of a question.
|
|
66
|
-
//
|
|
67
|
-
// OBSERVED live: forwarded tail `s a t a t a u s a u s s` — two system
|
|
68
|
-
// messages after the last user message. The agent replied "I don't see an
|
|
69
|
-
// explicit question or task for this turn", answered the PREVIOUS turn, and
|
|
70
|
-
// read as one message behind for an entire session.
|
|
71
|
-
let lead = -1;
|
|
72
|
-
while (lead + 1 < msgs.length && msgs[lead + 1]?.role === 'system') lead += 1;
|
|
73
|
-
const out = [...msgs];
|
|
74
|
-
out.splice(lead + 1, 0, brief); // after the leading system block, before any turn
|
|
75
|
-
return { ...body, messages: out };
|
|
76
|
-
}
|
package/lib/grokbot.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
// `npx openzoo grokbot` — keep Grok Bot's UI, serve OUR box underneath.
|
|
2
|
-
//
|
|
3
|
-
// Grok Bot is a thin client over Cursor's aiserver.v1 API. Each bot gets a
|
|
4
|
-
// pod from `GrokBotService/EnsureSandBox` — a cursorvm.com VM the agent runs
|
|
5
|
-
// in. We MITM api2.cursor.sh (proven: /etc/hosts pin + Node TLS override lets
|
|
6
|
-
// us answer 88 methods and passthrough the rest with real bodies), and for
|
|
7
|
-
// that ONE method we return OUR RunPod box in Cursor's exact 12-field wire
|
|
8
|
-
// shape. The polished UI is theirs; the sandbox is ours.
|
|
9
|
-
//
|
|
10
|
-
// THE WALL, STATED HONESTLY: once the app has our pod, it speaks Cursor's
|
|
11
|
-
// in-pod agent protocol to port 1337 and expects a VNC/websockify desktop on
|
|
12
|
-
// 6080. We do not yet speak either. The box therefore runs lib/podagent.mjs,
|
|
13
|
-
// which LOGS every request on those ports to /var/log/openzoo/agent.jsonl. So
|
|
14
|
-
// this ships as a CAPTURE hijack: the UI connects to our box, and we read what
|
|
15
|
-
// it sends to learn the protocol we then have to implement. That is the honest
|
|
16
|
-
// state — a working hijack needs that protocol, and this is how we get it.
|
|
17
|
-
|
|
18
|
-
import { spawn, execSync } from 'node:child_process';
|
|
19
|
-
import { existsSync } from 'node:fs';
|
|
20
|
-
import { homedir } from 'node:os';
|
|
21
|
-
import { join } from 'node:path';
|
|
22
|
-
|
|
23
|
-
import { config, USDC_MINT, TOKEN_MINT } from './config.js';
|
|
24
|
-
import { PayClient } from './pay.js';
|
|
25
|
-
import {
|
|
26
|
-
podEndpoints, readyBox, runpodConfigured, spawnBox, listBoxes, execInBox,
|
|
27
|
-
} from './boxes.js';
|
|
28
|
-
|
|
29
|
-
const APP = '/Applications/Grok Bot.app';
|
|
30
|
-
const GROK_HOME = process.env.GROK_HOME || join(homedir(), '.grok');
|
|
31
|
-
|
|
32
|
-
export async function runGrokBot(argv = []) {
|
|
33
|
-
if (process.platform !== 'darwin') {
|
|
34
|
-
console.error('openzoo grokbot: the app hijack is macOS-only (needs Grok Bot.app + /etc/hosts).');
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
if (!existsSync(APP)) {
|
|
38
|
-
console.error('openzoo grokbot: /Applications/Grok Bot.app not found.');
|
|
39
|
-
process.exit(1);
|
|
40
|
-
}
|
|
41
|
-
if (!runpodConfigured()) {
|
|
42
|
-
console.error('openzoo grokbot: RUNPOD_API_KEY not set (needs an rpa_… key).');
|
|
43
|
-
console.error(' export RUNPOD_API_KEY=rpa_… then re-run. Boxes bill to that account;');
|
|
44
|
-
console.error(' inference bills per-call to the box\'s own openzoo wallet.');
|
|
45
|
-
process.exit(1);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const days = Number((argv.find((a) => /^--days=/.test(a)) || '').split('=')[1]) || 1;
|
|
49
|
-
|
|
50
|
-
// 1) a box, ready, BEFORE we touch the app — so the first EnsureSandBox we
|
|
51
|
-
// hijack already has a live pod to point at. The box mints its OWN wallet
|
|
52
|
-
// (never the operator's secret key — that never leaves this Mac). There is
|
|
53
|
-
// NO --task and NO --share-wallet: the task is whatever the user types
|
|
54
|
-
// into the Grok Bot UI, captured from the MITM'd chat request and driven
|
|
55
|
-
// through to the box over POST /drive.
|
|
56
|
-
console.error('openzoo grokbot: spawning your box (CPU, x402-funded)…');
|
|
57
|
-
const spawned = await spawnBox({ name: 'grokbot', days });
|
|
58
|
-
if (!spawned.ok) { console.error(` spawn failed: ${spawned.error}`); process.exit(1); }
|
|
59
|
-
const box = spawned.box;
|
|
60
|
-
console.error(` ${box.name} ${box.id} expires ${spawned.expiresAt.slice(0, 16)}`);
|
|
61
|
-
const ready = await readyBox(box.id, { log: (m) => console.error(m) });
|
|
62
|
-
if (!ready.ok) console.error(` ${ready.error} — continuing; the capture agent may still bind`);
|
|
63
|
-
else console.error(` box ready in ${ready.seconds}s`);
|
|
64
|
-
|
|
65
|
-
// the box minted its OWN wallet on boot — print its funding address so it
|
|
66
|
-
// can be topped up directly. Its brain pays per call from THIS wallet, not
|
|
67
|
-
// the operator's; a fresh box starts at $0 until funded.
|
|
68
|
-
if (box.ssh) {
|
|
69
|
-
const addr = await execInBox(box, 'npx --yes openzoo address 2>/dev/null');
|
|
70
|
-
if (addr.ok && addr.stdout.trim()) {
|
|
71
|
-
console.error(' box wallet (top up to give it inference budget):');
|
|
72
|
-
console.error(addr.stdout.trim().split('\n').map((l) => ` ${l}`).join('\n'));
|
|
73
|
-
}
|
|
74
|
-
console.error(` mints: USDC ${USDC_MINT} · TOKEN ${TOKEN_MINT} (Solana)`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const ep = podEndpoints(box.id);
|
|
78
|
-
process.env.OZ_HIJACK_POD = JSON.stringify({
|
|
79
|
-
region: 'us1',
|
|
80
|
-
accountId: box.id.replace(/[^a-z0-9]/gi, '').slice(0, 20),
|
|
81
|
-
podId: `pod-${box.id}`,
|
|
82
|
-
token: `nto-${Math.random().toString(36).slice(2, 12)}`,
|
|
83
|
-
agent: ep.agent, vnc: ep.vnc, p1340: ep.p1340, p6081: ep.p6081,
|
|
84
|
-
});
|
|
85
|
-
// the backend (handleStreamChat) forwards each UI prompt here to be driven
|
|
86
|
-
process.env.OZ_BOX_AGENT = ep.agent;
|
|
87
|
-
// other methods must still reach the REAL backend, or the app can't load
|
|
88
|
-
process.env.OPENZOO_PASSTHRU = '1';
|
|
89
|
-
process.env.OPENZOO_FORCE_BLOCK = '1';
|
|
90
|
-
|
|
91
|
-
// 2) pin + serve api2.cursor.sh locally (sudo). Chromium flags don't reach
|
|
92
|
-
// the Electron main process; only /etc/hosts + Node TLS override do.
|
|
93
|
-
const { blockBackend, unblockBackend, isBlocked } = await import('./hosts.js');
|
|
94
|
-
const { startCursorBackend } = await import('./cursorbackend.js');
|
|
95
|
-
console.error('openzoo grokbot: pinning api2.cursor.sh (sudo)…');
|
|
96
|
-
if (!isBlocked()) blockBackend();
|
|
97
|
-
startCursorBackend({ port: 443, log: (m) => console.error(` backend: ${m}`) });
|
|
98
|
-
|
|
99
|
-
const restore = () => { try { unblockBackend(); console.error('openzoo: /etc/hosts restored.'); } catch { /* */ } };
|
|
100
|
-
process.on('SIGINT', () => { restore(); process.exit(0); });
|
|
101
|
-
process.on('exit', restore);
|
|
102
|
-
|
|
103
|
-
// 3) launch the app directly (open -a can't pass env) with Node TLS override
|
|
104
|
-
console.error('openzoo grokbot: launching Grok Bot pointed at your box…');
|
|
105
|
-
console.error(` box proxy : ${ep.proxy}`);
|
|
106
|
-
console.error(` agent (1337): ${ep.agent}`);
|
|
107
|
-
console.error(` ssh : ${box.ssh || '(pending)'}`);
|
|
108
|
-
console.error(' WATCH the box agent log for what the app sends:');
|
|
109
|
-
console.error(` ${box.ssh ? box.ssh + " 'tail -f /var/log/openzoo/agent.log'" : '(ssh not ready yet)'}`);
|
|
110
|
-
try { execSync('osascript -e \'tell application "Grok Bot" to quit\'', { stdio: 'ignore' }); } catch { /* */ }
|
|
111
|
-
await new Promise((r) => setTimeout(r, 2500));
|
|
112
|
-
spawn(`${APP}/Contents/MacOS/Grok Bot`, ['--ignore-certificate-errors'], {
|
|
113
|
-
stdio: 'ignore', detached: true,
|
|
114
|
-
env: { ...process.env, NODE_TLS_REJECT_UNAUTHORIZED: '0' },
|
|
115
|
-
}).unref();
|
|
116
|
-
|
|
117
|
-
console.error('openzoo grokbot: ctrl-c to stop, restore /etc/hosts, and leave the box running.');
|
|
118
|
-
console.error(` the box keeps running until its TTL — manage with: RUNPOD_API_KEY=… openzoo grokbot (again) or the runpod console`);
|
|
119
|
-
await new Promise(() => {});
|
|
120
|
-
}
|