openzoo 0.50.30 → 0.50.32

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.
@@ -1039,7 +1039,7 @@ function loadTranscripts() {
1039
1039
  }
1040
1040
  } catch { /* first run */ }
1041
1041
  }
1042
- function saveTranscripts() {
1042
+ function saveTranscriptsNow() {
1043
1043
  try {
1044
1044
  const o = {};
1045
1045
  for (const [id, t] of transcripts) {
@@ -1049,6 +1049,16 @@ function saveTranscripts() {
1049
1049
  fs.writeFileSync(TX_FILE, JSON.stringify(o));
1050
1050
  } catch { /* */ }
1051
1051
  }
1052
+ let saveTxTimer;
1053
+ function saveTranscripts() {
1054
+ clearTimeout(saveTxTimer);
1055
+ saveTxTimer = setTimeout(saveTranscriptsNow, 250);
1056
+ }
1057
+ function flushTranscripts() {
1058
+ clearTimeout(saveTxTimer);
1059
+ saveTranscriptsNow();
1060
+ }
1061
+ process.once('beforeExit', flushTranscripts);
1052
1062
  loadTranscripts();
1053
1063
  function agentTranscript(id) {
1054
1064
  let t = transcripts.get(id);
@@ -1141,6 +1151,16 @@ function stripSpendFooter(s) {
1141
1151
  return String(s || '').replace(/\n{2,}this call \$[\d.]+[\s\S]*$/i, '').trimEnd();
1142
1152
  }
1143
1153
 
1154
+ function entryPlainText(v) {
1155
+ if (v == null) return '';
1156
+ if (typeof v === 'string' || typeof v === 'number') return String(v);
1157
+ if (Array.isArray(v)) return v.map(entryPlainText).filter(Boolean).join('\n');
1158
+ if (typeof v === 'object') {
1159
+ return entryPlainText(v.content ?? v.text ?? v.message ?? v.prompt ?? v.value ?? '');
1160
+ }
1161
+ return '';
1162
+ }
1163
+
1144
1164
  /** Prior turns for zooComplete. sendPrompt used to POST only the latest user
1145
1165
  * line, so the model said each thread starts blank while the UI still showed
1146
1166
  * the canvas. Skip the last user echo of `currentPrompt` (already appended). */
@@ -1152,13 +1172,12 @@ function historyMessages(agentId, currentPrompt) {
1152
1172
  if (!e || typeof e !== 'object') continue;
1153
1173
  let role = null;
1154
1174
  let text = '';
1155
- if (e.kind === 'send-message' || e.role === 'assistant') {
1156
- const m = e.message;
1157
- text = typeof m === 'string' ? m : String(m?.content || m?.text || '');
1175
+ if (e.kind === 'send-message' || e.role === 'assistant' || e.kind === 'assistant') {
1176
+ text = entryPlainText(e.message ?? e.content ?? e.text);
1158
1177
  role = 'assistant';
1159
1178
  text = stripSpendFooter(text);
1160
- } else if (e.kind === 'message' || e.role === 'user') {
1161
- text = String(e.content || e.message?.content || e.message?.text || '');
1179
+ } else if (e.kind === 'message' || e.role === 'user' || e.kind === 'user') {
1180
+ text = entryPlainText(e.content ?? e.message ?? e.text ?? e.prompt);
1162
1181
  role = 'user';
1163
1182
  }
1164
1183
  text = String(text || '').trim();
@@ -1172,12 +1191,28 @@ function historyMessages(agentId, currentPrompt) {
1172
1191
  const kept = [];
1173
1192
  for (let i = out.length - 1; i >= 0; i--) {
1174
1193
  chars += String(out[i].content).length;
1175
- if (chars > 80_000 && kept.length) break;
1194
+ if (chars > 120_000 && kept.length) break;
1176
1195
  kept.push(out[i]);
1177
1196
  }
1178
1197
  kept.reverse();
1179
1198
  return kept;
1180
1199
  }
1200
+
1201
+ async function ensureTranscriptHydrated(agentId, log) {
1202
+ const t = agentTranscript(agentId);
1203
+ if (t.pulledRemote || !realPod?.agent) return;
1204
+ try {
1205
+ const remote = await podJson('/api/getAgentTranscriptTail', {
1206
+ id: agentId, agentId, limit: 200,
1207
+ }, log);
1208
+ const entries = remote?.entries || remote?.value?.entries || remote?.lines || [];
1209
+ const n = ingestRemoteEntries(agentId, entries);
1210
+ if (n) log(`cursor-backend: zooComplete hydrate ${agentId} +${n} from 1340`);
1211
+ } catch (e) {
1212
+ log(`cursor-backend: zooComplete hydrate failed: ${e.message}`);
1213
+ }
1214
+ t.pulledRemote = true;
1215
+ }
1181
1216
  async function podJson(path0, bodyObj, log) {
1182
1217
  if (!realPod?.agent) return null;
1183
1218
  let agent;
@@ -1527,7 +1562,8 @@ function zooTextFromMessage(msg, data) {
1527
1562
  async function zooComplete(prompt, log, agentId, parsed = {}) {
1528
1563
  const model = currentModel(agentId);
1529
1564
  const helper = localExecSse.size > 0;
1530
- log(`cursor-backend: zoo POST :8402 model=${model} helper=${helper ? localExecSse.size : 0} ${JSON.stringify((prompt || '').slice(0, 60))}`);
1565
+ await ensureTranscriptHydrated(agentId, log);
1566
+ log(`cursor-backend: zoo POST :8402 model=${model} helper=${helper ? localExecSse.size : 0} hist=${historyMessages(agentId, prompt).length} ${JSON.stringify((prompt || '').slice(0, 60))}`);
1531
1567
 
1532
1568
  const images = [];
1533
1569
  const textFiles = [];
@@ -1593,19 +1629,34 @@ async function zooComplete(prompt, log, agentId, parsed = {}) {
1593
1629
  let text = '';
1594
1630
  const usedTools = [];
1595
1631
  const zooPost = async (payload) => {
1632
+ const ms = Number(process.env.OPENZOO_ASK_TIMEOUT_MS || 10 * 60_000);
1596
1633
  const post = () => fetch('http://127.0.0.1:8402/v1/chat/completions', {
1597
1634
  method: 'POST',
1598
1635
  headers: { 'content-type': 'application/json', authorization: 'Bearer sk-openzoo' },
1599
1636
  body: JSON.stringify(payload),
1600
- signal: AbortSignal.timeout(120000),
1637
+ signal: AbortSignal.timeout(ms),
1601
1638
  });
1602
- let r = await post();
1639
+ let r;
1640
+ let lastErr;
1641
+ for (let attempt = 1; attempt <= 3; attempt++) {
1642
+ try {
1643
+ r = await post();
1644
+ lastErr = null;
1645
+ break;
1646
+ } catch (e) {
1647
+ lastErr = e;
1648
+ log(`cursor-backend: zoo POST attempt=${attempt} ${e.message}`);
1649
+ if (attempt === 3) throw e;
1650
+ await new Promise((ok) => setTimeout(ok, 800 * attempt));
1651
+ }
1652
+ }
1653
+ if (!r) throw lastErr || new Error('zoo POST failed');
1603
1654
  if (r.status === 402) {
1604
1655
  log('cursor-backend: x402 402 — dwell/retry');
1605
1656
  await new Promise((ok) => setTimeout(ok, 2500));
1606
- r = await post();
1657
+ try { r = await post(); } catch (e) { log(`cursor-backend: zoo POST after 402 ${e.message}`); }
1607
1658
  }
1608
- const data = await r.json();
1659
+ const data = await r.json().catch(() => ({}));
1609
1660
  if (r.status === 402) {
1610
1661
  const raw = zooTextFromMessage(data?.choices?.[0]?.message, data)
1611
1662
  || data?.error?.message
@@ -1836,6 +1887,7 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
1836
1887
  const attN = Array.isArray(parsed.attachmentPaths) ? parsed.attachmentPaths.length : 0;
1837
1888
  log(`cursor-backend: >> sendPrompt agent=${agentId} keys=${Object.keys(parsed).join(',')} attachments=${attN} prompt=${JSON.stringify((prompt || '').slice(0, 80))}`);
1838
1889
  lastSendEchoId = String(nonce);
1890
+ jsonSend(res, { accepted: true });
1839
1891
  const userLine = fanoutLine(agentId, 'user', prompt, {
1840
1892
  clientNonce: nonce,
1841
1893
  requestId: nonce,
@@ -1844,7 +1896,6 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
1844
1896
  noteFocus(agentId);
1845
1897
  bumpAgent(agentId, { preview: prompt, notify: false });
1846
1898
  ssePush('transcript', { ...gatewayEntry(userLine), agentId });
1847
- jsonSend(res, { accepted: true });
1848
1899
  const modelCmd = /^\s*\/model(?:\s+(\S+))?\s*$/i.exec(prompt || '');
1849
1900
  setImmediate(async () => {
1850
1901
  let text = '';
package/lib/grokcli.js CHANGED
@@ -18,7 +18,8 @@
18
18
  import { spawn, execSync } from 'node:child_process';
19
19
  import { existsSync, readFileSync, writeFileSync, copyFileSync, mkdirSync } from 'node:fs';
20
20
  import { homedir } from 'node:os';
21
- import { join } from 'node:path';
21
+ import { dirname, join } from 'node:path';
22
+ import { fileURLToPath } from 'node:url';
22
23
 
23
24
  import { config } from './config.js';
24
25
 
@@ -168,6 +169,13 @@ export async function runBot(argv = []) {
168
169
  console.error('openzoo: 8443 already bound — reusing it (', e.message, ')');
169
170
  }
170
171
  await new Promise((r) => setTimeout(r, 400));
172
+ let ver = '?';
173
+ try {
174
+ ver = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'), 'utf8')).version;
175
+ } catch { /* */ }
176
+ process.on('uncaughtException', (e) => console.error('openzoo: uncaught', e?.message || e));
177
+ process.on('unhandledRejection', (e) => console.error('openzoo: rejection', e?.message || e));
178
+ console.error(`openzoo: grokbot hijack v${ver}`);
171
179
  console.error(`openzoo: aiserver on ${url}`);
172
180
  console.error(' oauth + /sand-box creds -> real api2');
173
181
  if (sniff) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.50.30",
3
+ "version": "0.50.32",
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",