openzoo 0.48.2 → 0.48.4
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/grokui.mjs +59 -4
- package/package.json +1 -1
package/lib/grokui.mjs
CHANGED
|
@@ -301,6 +301,35 @@ function newGroupThread(names) {
|
|
|
301
301
|
const BIND_CHUNK_BYTES = 512 * 1024;
|
|
302
302
|
// Chained auto-run commands per user message. Each hop is a paid call.
|
|
303
303
|
const AUTO_MAX_STEPS = Number(process.env.OZ_AUTO_MAX_STEPS || 8);
|
|
304
|
+
|
|
305
|
+
// Injected fresh on every AUTO turn, never persisted into the thread.
|
|
306
|
+
//
|
|
307
|
+
// The auto loop only continues while directives keep parsing, so a reply that
|
|
308
|
+
// merely OFFERS ends the run — auto silently degrades to ask the moment the
|
|
309
|
+
// model hedges. Observed live: "If you want, I can rewrite the prompt with
|
|
310
|
+
// these fixes folded in", "Spawned mcp-integration — working on it" with
|
|
311
|
+
// nothing spawned, and a user reduced to answering "no, this... impl all".
|
|
312
|
+
// Models are trained to close on a consent question; in auto that instinct is
|
|
313
|
+
// the bug. The system prompt is frozen into a thread at creation, so an
|
|
314
|
+
// existing thread can only be reached by a per-turn message.
|
|
315
|
+
const AUTO_DIRECTIVE = `AUTO MODE IS ON for this thread.
|
|
316
|
+
|
|
317
|
+
Do the work in this turn. Do not ask whether to proceed, do not offer to do it,
|
|
318
|
+
do not say what you are "about to" do and stop. The user has already consented
|
|
319
|
+
by enabling auto — a question back to them is a dropped turn, and they must
|
|
320
|
+
type "yes" to get what they already asked for.
|
|
321
|
+
|
|
322
|
+
Concretely, NEVER end a turn with any of: "If you want, I can…", "Should I…?",
|
|
323
|
+
"Let me know and I'll…", "Ready to proceed?", or a plan with no directive after
|
|
324
|
+
it. If you catch yourself writing one, emit the RUN/WRITE/READ/SERVE/FETCH line
|
|
325
|
+
instead — that IS the answer.
|
|
326
|
+
|
|
327
|
+
Announcing an action does not perform it. "Spawned X", "working on it" and
|
|
328
|
+
"kicked that off" are false unless the directive line is in this same reply.
|
|
329
|
+
If a task needs several commands, emit the FIRST one now; you get its real
|
|
330
|
+
output back and continue from there. Only stop to ask when the next step is
|
|
331
|
+
genuinely destructive and irreversible, or when you truly cannot proceed
|
|
332
|
+
without a fact only the user has.`;
|
|
304
333
|
async function bindThread(t) {
|
|
305
334
|
// Only bind what's NEW since the last successful bind, continuing the
|
|
306
335
|
// existing context_id — previously this rebuilt and re-sent the WHOLE
|
|
@@ -378,8 +407,16 @@ function parseRun(reply) {
|
|
|
378
407
|
// use U+FF5C FULLWIDTH VERTICAL LINE (|) — matching only `|` parsed the
|
|
379
408
|
// pretty-printed form in tests while missing what the model actually emits,
|
|
380
409
|
// which is exactly how this survived one round of "fixed".
|
|
410
|
+
// The PARAMETER NAME is not fixed either. Models emit name="command",
|
|
411
|
+
// name="cmd", name="shell_command" and name="script" for the same thing —
|
|
412
|
+
// MEASURED live emitting `name="cmd"` inside an invoke named exec_command,
|
|
413
|
+
// against a parser that demanded name="command". One attribute apart, and
|
|
414
|
+
// the whole envelope was dropped in silence: the bot then explained what it
|
|
415
|
+
// was "about to run" forever, never running anything. Match the shape of the
|
|
416
|
+
// envelope, not one vendor's spelling of it.
|
|
381
417
|
const SEP = '[||\\s]*';
|
|
382
|
-
const
|
|
418
|
+
const NAME = '(?:command|cmd|shell_command|script)';
|
|
419
|
+
const dsml = new RegExp(`<${SEP}DSML[^>]*\\bparameter\\b[^>]*\\bname="${NAME}"[^>]*>([\\s\\S]*?)<\\/${SEP}DSML`, 'i').exec(reply);
|
|
383
420
|
if (dsml) return sanitizeRunCommand(dsml[1]);
|
|
384
421
|
|
|
385
422
|
const m = /^[ \t>*-]*RUN:[ \t]*([\s\S]+)/m.exec(reply);
|
|
@@ -391,9 +428,21 @@ function parseRun(reply) {
|
|
|
391
428
|
return sanitizeRunCommand(cmd);
|
|
392
429
|
}
|
|
393
430
|
|
|
431
|
+
// RUN through BASH, not /bin/sh. node's exec() defaults to /bin/sh, which on
|
|
432
|
+
// Debian is dash — so every bash-ism a model writes (`for … do`, `[[ ]]`,
|
|
433
|
+
// arrays, process substitution) dies as
|
|
434
|
+
// /bin/sh: 40: Syntax error: "do" unexpected
|
|
435
|
+
// which reads as the model writing bad code when it wrote perfectly good bash.
|
|
436
|
+
// Models overwhelmingly emit bash; give them bash. Windows is left alone so
|
|
437
|
+
// node picks cmd.exe, and a box without /bin/bash falls back to the default.
|
|
438
|
+
const RUN_SHELL = process.platform !== 'win32' && existsSync('/bin/bash') ? '/bin/bash' : undefined;
|
|
439
|
+
// Installing a toolchain (apt-get, pip, cargo) routinely outruns two minutes,
|
|
440
|
+
// and a killed install leaves a half-configured box that fails confusingly.
|
|
441
|
+
const RUN_TIMEOUT_MS = Number(process.env.OZ_RUN_TIMEOUT_MS || 600000);
|
|
442
|
+
|
|
394
443
|
function execCommand(command, cwd) {
|
|
395
444
|
return new Promise((resolve) => {
|
|
396
|
-
exec(command, { cwd, timeout:
|
|
445
|
+
exec(command, { cwd, shell: RUN_SHELL, timeout: RUN_TIMEOUT_MS, maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
397
446
|
let out = (stdout || '') + (stderr ? '\n' + stderr : '');
|
|
398
447
|
if (err) out += `\n(exit ${err.code ?? 1})`;
|
|
399
448
|
resolve(out.slice(0, 6000) || '(no output)');
|
|
@@ -555,10 +604,16 @@ async function runTurn(threadId, userText, onEvent, images) {
|
|
|
555
604
|
t.status = 'thinking';
|
|
556
605
|
let reply = '';
|
|
557
606
|
onEvent?.({ type: 'start', name: t.name, color: t.color });
|
|
607
|
+
// Transient: the nudge is appended for THIS call only and never pushed into
|
|
608
|
+
// t.messages, so it can't accumulate across a chained auto run or get bound
|
|
609
|
+
// into the thread's context.
|
|
610
|
+
const callMsgs = t.runMode === 'auto'
|
|
611
|
+
? [...t.messages, { role: 'system', content: AUTO_DIRECTIVE }]
|
|
612
|
+
: t.messages;
|
|
558
613
|
try {
|
|
559
614
|
reply = onEvent
|
|
560
|
-
? (await brainStream(
|
|
561
|
-
: (await brain(
|
|
615
|
+
? (await brainStream(callMsgs, (delta) => onEvent({ type: 'delta', name: t.name, color: t.color, delta }), t.contextId)).trim()
|
|
616
|
+
: (await brain(callMsgs, t.contextId)).trim();
|
|
562
617
|
} catch (e) {
|
|
563
618
|
reply = `error: ${e.message}`;
|
|
564
619
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.48.
|
|
3
|
+
"version": "0.48.4",
|
|
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",
|