openzoo 0.48.15 → 0.48.17

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.
Files changed (2) hide show
  1. package/lib/grokui.mjs +46 -9
  2. package/package.json +1 -1
package/lib/grokui.mjs CHANGED
@@ -359,7 +359,16 @@ function newGroupThread(names) {
359
359
  // sidecar appends to the same bound context instead of starting fresh.
360
360
  const BIND_CHUNK_BYTES = 512 * 1024;
361
361
  // Chained auto-run commands per user message. Each hop is a paid call.
362
- const AUTO_MAX_STEPS = Number(process.env.OZ_AUTO_MAX_STEPS || 8);
362
+ // 8 was far too tight. A real task — install a toolchain, read a tree, wire a
363
+ // service — is dozens of directives, so auto kept halting mid-job and telling
364
+ // the user to type "continue", which is the exact thing auto exists to avoid.
365
+ // Still bounded, because every hop is a paid call, but bounded at a number
366
+ // that lets a job finish.
367
+ const AUTO_MAX_STEPS = Number(process.env.OZ_AUTO_MAX_STEPS || 60);
368
+ // An empty completion is a provider hiccup, not an answer. In auto we retry it
369
+ // silently rather than parking the thread behind a "say continue" note the
370
+ // user then has to answer — that turned a transient blip into a manual step.
371
+ const AUTO_EMPTY_RETRIES = Number(process.env.OZ_AUTO_EMPTY_RETRIES || 3);
363
372
  // Ceiling on subagents per thread. Spawning is fire-and-forget and each child
364
373
  // can spawn too, so without a count it is unbounded — MEASURED as 15+ threads
365
374
  // all named tetris-contract, every one of them a live agent making paid calls.
@@ -516,7 +525,7 @@ function execCommand(command, cwd) {
516
525
  exec(command, { cwd, shell: RUN_SHELL, timeout: RUN_TIMEOUT_MS, maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
517
526
  let out = (stdout || '') + (stderr ? '\n' + stderr : '');
518
527
  if (err) out += `\n(exit ${err.code ?? 1})`;
519
- resolve(out.slice(0, 6000) || '(no output)');
528
+ resolve(keepWhole(out) || '(no output)');
520
529
  });
521
530
  });
522
531
  }
@@ -572,6 +581,26 @@ const usd = (n) => (n >= 0.01 || n === 0 ? '$' + n.toFixed(2) : '$' + n.toFixed(
572
581
  //
573
582
  // So: feed back the head and tail (where the answer nearly always is) and say
574
583
  // plainly that the middle is recallable rather than lost.
584
+ // Directive output goes into t.history VERBATIM, because bindThread() binds
585
+ // history — so whatever is cut here is not "elided", it is DESTROYED before
586
+ // the holographic context ever sees it.
587
+ //
588
+ // These used to be hard slices at 4-8k right where the output was produced:
589
+ // a command's stdout, a READ, a FETCH, an MCP tool result. The user saw
590
+ // "…(truncated)" and the rest was simply gone — unrecallable, because it was
591
+ // never stored. That is the opposite of what this product does.
592
+ //
593
+ // So: keep the whole thing (up to a sanity ceiling that exists only to stop a
594
+ // runaway `yes` or a binary dump from bloating the thread store), let it bind,
595
+ // and let condense() decide how much the MODEL sees on the next hop. Big
596
+ // outputs are exactly the case leCore is for.
597
+ const KEEP_MAX = Number(process.env.OZ_KEEP_MAX || 400000);
598
+ function keepWhole(text) {
599
+ const s = String(text ?? '');
600
+ if (s.length <= KEEP_MAX) return s;
601
+ return `${s.slice(0, KEEP_MAX)}\n…[${s.length - KEEP_MAX} chars over the ${KEEP_MAX}-char store ceiling and dropped]`;
602
+ }
603
+
575
604
  const FEEDBACK_MAX = Number(process.env.OZ_FEEDBACK_MAX || 3000);
576
605
  function condense(label, text) {
577
606
  const s = String(text ?? '');
@@ -908,7 +937,7 @@ async function tryDirective(reply, originId) {
908
937
  const rel = readD[1].trim();
909
938
  try {
910
939
  const data = readFileSync(safeResolveIn(dirFor(originId), rel), 'utf8');
911
- return `${rel}:\n${data.slice(0, 4000)}${data.length > 4000 ? '\n…(truncated)' : ''}`;
940
+ return `${rel}:\n${keepWhole(data)}`;
912
941
  } catch (e) { return `Couldn't read ${rel}: ${e.message}`; }
913
942
  }
914
943
  // EDIT beats WRITE for changing part of a file: WRITE overwrites the whole
@@ -1090,7 +1119,7 @@ async function tryDirective(reply, originId) {
1090
1119
  + `directive instead:\n MCP: ${url}\nto list its tools, then\n `
1091
1120
  + `MCP: ${url} | <tool> | {"arg": "value"}\nto call one.`;
1092
1121
  }
1093
- return `${url} (${r.status}):\n${text.slice(0, 8000)}${text.length > 8000 ? '\n…(truncated)' : ''}`;
1122
+ return `${url} (${r.status}):\n${keepWhole(text)}`;
1094
1123
  } catch (e) { return `Couldn't fetch ${url}: ${e.message}`; }
1095
1124
  }
1096
1125
 
@@ -1194,7 +1223,7 @@ async function mcpDirective(url, tool, args) {
1194
1223
  const out = (res?.content || [])
1195
1224
  .map((c) => (c.type === 'text' ? c.text : `[${c.type}]`))
1196
1225
  .join('\n') || JSON.stringify(res ?? call.raw);
1197
- return `${banner}\n$ ${tool}\n${out.slice(0, 6000)}${out.length > 6000 ? '\n…(truncated)' : ''}`;
1226
+ return `${banner}\n$ ${tool}\n${keepWhole(out)}`;
1198
1227
  } catch (e) {
1199
1228
  return `MCP ${url}: ${e.message}`;
1200
1229
  }
@@ -1288,8 +1317,16 @@ async function runTurn(threadId, userText, onEvent, images) {
1288
1317
  // user a turn and told them nothing. Retry once before giving up, and if
1289
1318
  // it is still empty, say what actually happened instead of "(no
1290
1319
  // response)", which reads like the harness broke.
1291
- if (!reply) reply = await ask();
1292
- if (!reply) reply = '(the model returned an empty completion — usually a transient provider hiccup or a stop-sequence firing early. Say "continue" to retry.)';
1320
+ // Retry in place rather than parking the thread behind a note the user has
1321
+ // to answer. In auto especially: a transient blip must not become a manual
1322
+ // step, which is the whole point of auto.
1323
+ for (let i = 0; !reply && i < AUTO_EMPTY_RETRIES; i++) {
1324
+ await new Promise((r) => setTimeout(r, 400 * (i + 1)));
1325
+ reply = await ask();
1326
+ }
1327
+ if (!reply) {
1328
+ reply = `(the model returned nothing ${AUTO_EMPTY_RETRIES + 1} times — that is a provider problem, not your input. Try /model to switch, or send anything to retry.)`;
1329
+ }
1293
1330
  } catch (e) {
1294
1331
  reply = `error: ${e.message}`;
1295
1332
  }
@@ -1323,7 +1360,7 @@ async function runTurn(threadId, userText, onEvent, images) {
1323
1360
  bindThread(t).catch(() => {});
1324
1361
  runTurn(threadId, condense('(command output)', output), onEvent).catch(() => {});
1325
1362
  } else {
1326
- const note = `(auto-run stopped after ${AUTO_MAX_STEPS} chained commands — say "continue" to keep going)`;
1363
+ const note = `(auto-run paused after ${AUTO_MAX_STEPS} chained commands — that is the spend ceiling, not the end of the job. Send anything to carry on, or raise OZ_AUTO_MAX_STEPS.)`;
1327
1364
  t.history.push({ who: 'bot', text: note });
1328
1365
  onEvent?.({ type: 'final', name: t.name, color: t.color, text: note });
1329
1366
  saveThreads();
@@ -1368,7 +1405,7 @@ async function runTurn(threadId, userText, onEvent, images) {
1368
1405
  runTurn(threadId, condense('(directive result)', ack), onEvent).catch(() => {});
1369
1406
  return;
1370
1407
  }
1371
- const note = `(auto stopped after ${AUTO_MAX_STEPS} steps — say "continue" to keep going)`;
1408
+ const note = `(auto paused after ${AUTO_MAX_STEPS} steps — that is the spend ceiling, not the end of the job. Send anything to carry on, or raise OZ_AUTO_MAX_STEPS.)`;
1372
1409
  t.history.push({ who: 'bot', text: note });
1373
1410
  onEvent?.({ type: 'final', name: t.name, color: t.color, text: note });
1374
1411
  saveThreads();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.48.15",
3
+ "version": "0.48.17",
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",