openzoo 0.48.1 → 0.48.3
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/boxes.js +22 -6
- package/lib/grokui.mjs +29 -3
- package/package.json +1 -1
package/lib/boxes.js
CHANGED
|
@@ -36,8 +36,15 @@ const CPU_FLAVORS = ['cpu3c', 'cpu3g', 'cpu5c'];
|
|
|
36
36
|
// on musl they fail to build because the entrypoint has no toolchain, which is
|
|
37
37
|
// why alpine boxes sat at 404 forever. Debian-slim runs the shim as published,
|
|
38
38
|
// and its apt has a real sshd.
|
|
39
|
-
|
|
40
|
-
|
|
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'];
|
|
41
48
|
const TTL_MAX_HOURS = 90 * 24;
|
|
42
49
|
|
|
43
50
|
/**
|
|
@@ -50,18 +57,27 @@ const TTL_MAX_HOURS = 90 * 24;
|
|
|
50
57
|
* host keys, and run sshd in the foreground of the background. Passwordless
|
|
51
58
|
* root login is NOT enabled — key-only.
|
|
52
59
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
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.
|
|
55
64
|
*/
|
|
56
65
|
const ENTRYPOINT = [
|
|
57
66
|
'bash', '-lc',
|
|
58
67
|
'set +e; mkdir -p /workspace /root/.openzoo /var/log/openzoo /run/sshd /root/.ssh; '
|
|
59
68
|
+ 'if [ -n "$OPENZOO_WALLET_JSON" ]; then printf %s "$OPENZOO_WALLET_JSON" > /root/.openzoo/wallet.json; chmod 600 /root/.openzoo/wallet.json; fi; '
|
|
60
|
-
+ 'export DEBIAN_FRONTEND=noninteractive; apt-get update >/dev/null 2>&1; apt-get install -y --no-install-recommends openssh-server git ca-certificates >/dev/null 2>&1; '
|
|
61
69
|
// RunPod hands the operator pubkey in one of these — accept whichever is set
|
|
62
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; '
|
|
63
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 & '
|
|
64
|
-
|
|
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/grokui/grokui.mjs >> /var/log/openzoo/grokui.log 2>&1; do echo "grokui exited, restarting" >> /var/log/openzoo/grokui.log; sleep 2; done & '
|
|
65
81
|
// the capture agent answers the ports Grok Bot expects a Cursor sandbox on,
|
|
66
82
|
// logging the protocol we do not yet speak (see lib/podagent.mjs)
|
|
67
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; '
|
package/lib/grokui.mjs
CHANGED
|
@@ -14,6 +14,12 @@ import path from 'node:path';
|
|
|
14
14
|
import { brain, brainStream, PROXY } from './podagent.mjs';
|
|
15
15
|
|
|
16
16
|
const PORT = Number(process.env.OZ_GROKUI_PORT || 4173);
|
|
17
|
+
// BIND HOST. Default 127.0.0.1 so the desktop app never exposes a shell-capable
|
|
18
|
+
// UI to the LAN. A container MUST override it: binding the container's loopback
|
|
19
|
+
// makes `docker run -p 4173:4173` refuse the connection, because the published
|
|
20
|
+
// port forwards to the container's external interface, which nothing is on.
|
|
21
|
+
// Mirrors OPENZOO_BIND in lib/proxy.js — same reasoning, same default.
|
|
22
|
+
const BIND = process.env.OZ_GROKUI_BIND || process.env.OPENZOO_BIND || '127.0.0.1';
|
|
17
23
|
const STORE_DIR = path.join(homedir(), '.openzoo');
|
|
18
24
|
const STORE_FILE = path.join(STORE_DIR, 'grokui-threads.json');
|
|
19
25
|
|
|
@@ -372,8 +378,16 @@ function parseRun(reply) {
|
|
|
372
378
|
// use U+FF5C FULLWIDTH VERTICAL LINE (|) — matching only `|` parsed the
|
|
373
379
|
// pretty-printed form in tests while missing what the model actually emits,
|
|
374
380
|
// which is exactly how this survived one round of "fixed".
|
|
381
|
+
// The PARAMETER NAME is not fixed either. Models emit name="command",
|
|
382
|
+
// name="cmd", name="shell_command" and name="script" for the same thing —
|
|
383
|
+
// MEASURED live emitting `name="cmd"` inside an invoke named exec_command,
|
|
384
|
+
// against a parser that demanded name="command". One attribute apart, and
|
|
385
|
+
// the whole envelope was dropped in silence: the bot then explained what it
|
|
386
|
+
// was "about to run" forever, never running anything. Match the shape of the
|
|
387
|
+
// envelope, not one vendor's spelling of it.
|
|
375
388
|
const SEP = '[||\\s]*';
|
|
376
|
-
const
|
|
389
|
+
const NAME = '(?:command|cmd|shell_command|script)';
|
|
390
|
+
const dsml = new RegExp(`<${SEP}DSML[^>]*\\bparameter\\b[^>]*\\bname="${NAME}"[^>]*>([\\s\\S]*?)<\\/${SEP}DSML`, 'i').exec(reply);
|
|
377
391
|
if (dsml) return sanitizeRunCommand(dsml[1]);
|
|
378
392
|
|
|
379
393
|
const m = /^[ \t>*-]*RUN:[ \t]*([\s\S]+)/m.exec(reply);
|
|
@@ -385,9 +399,21 @@ function parseRun(reply) {
|
|
|
385
399
|
return sanitizeRunCommand(cmd);
|
|
386
400
|
}
|
|
387
401
|
|
|
402
|
+
// RUN through BASH, not /bin/sh. node's exec() defaults to /bin/sh, which on
|
|
403
|
+
// Debian is dash — so every bash-ism a model writes (`for … do`, `[[ ]]`,
|
|
404
|
+
// arrays, process substitution) dies as
|
|
405
|
+
// /bin/sh: 40: Syntax error: "do" unexpected
|
|
406
|
+
// which reads as the model writing bad code when it wrote perfectly good bash.
|
|
407
|
+
// Models overwhelmingly emit bash; give them bash. Windows is left alone so
|
|
408
|
+
// node picks cmd.exe, and a box without /bin/bash falls back to the default.
|
|
409
|
+
const RUN_SHELL = process.platform !== 'win32' && existsSync('/bin/bash') ? '/bin/bash' : undefined;
|
|
410
|
+
// Installing a toolchain (apt-get, pip, cargo) routinely outruns two minutes,
|
|
411
|
+
// and a killed install leaves a half-configured box that fails confusingly.
|
|
412
|
+
const RUN_TIMEOUT_MS = Number(process.env.OZ_RUN_TIMEOUT_MS || 600000);
|
|
413
|
+
|
|
388
414
|
function execCommand(command, cwd) {
|
|
389
415
|
return new Promise((resolve) => {
|
|
390
|
-
exec(command, { cwd, timeout:
|
|
416
|
+
exec(command, { cwd, shell: RUN_SHELL, timeout: RUN_TIMEOUT_MS, maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
391
417
|
let out = (stdout || '') + (stderr ? '\n' + stderr : '');
|
|
392
418
|
if (err) out += `\n(exit ${err.code ?? 1})`;
|
|
393
419
|
resolve(out.slice(0, 6000) || '(no output)');
|
|
@@ -1519,4 +1545,4 @@ const server = http.createServer((req, res) => {
|
|
|
1519
1545
|
res.end(APP_HTML);
|
|
1520
1546
|
});
|
|
1521
1547
|
|
|
1522
|
-
server.listen(PORT,
|
|
1548
|
+
server.listen(PORT, BIND, () => console.log(`[grokui] http://${BIND === '0.0.0.0' ? 'localhost' : BIND}:${PORT}`));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.48.
|
|
3
|
+
"version": "0.48.3",
|
|
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",
|