openzoo 0.50.24 → 0.50.25

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.
@@ -1257,6 +1257,7 @@ async function zooComplete(prompt, log, agentId, parsed = {}) {
1257
1257
  'Tools: read_file, write_file, exec, list_dir. USE THEM. Write files to disk instead of pasting giant HTML into chat.',
1258
1258
  'Pasted images arrive as attachments — you can see them when present. Do not claim you cannot see images if they are in this turn.',
1259
1259
  'Never claim you lack filesystem access or local-exec. If a tool errors, report the error.',
1260
+ 'After using tools you MUST still write a normal chat reply: what you did, file paths written, and what to open. Empty content is a bug.',
1260
1261
  'A spend footer is appended after your reply by the host — ignore it.',
1261
1262
  ].join(' '),
1262
1263
  },
@@ -1285,14 +1286,8 @@ async function zooComplete(prompt, log, agentId, parsed = {}) {
1285
1286
  const maxTok = Number(process.env.OPENZOO_ASK_MAX_TOKENS || 4096);
1286
1287
  let lastData = {};
1287
1288
  let text = '';
1288
- for (let step = 0; step < 8; step++) {
1289
- const payload = {
1290
- model,
1291
- messages,
1292
- tools: LOCAL_TOOLS,
1293
- tool_choice: 'auto',
1294
- max_tokens: maxTok,
1295
- };
1289
+ const usedTools = [];
1290
+ const zooPost = async (payload) => {
1296
1291
  const post = () => fetch('http://127.0.0.1:8402/v1/chat/completions', {
1297
1292
  method: 'POST',
1298
1293
  headers: { 'content-type': 'application/json', authorization: 'Bearer sk-openzoo' },
@@ -1306,6 +1301,16 @@ async function zooComplete(prompt, log, agentId, parsed = {}) {
1306
1301
  r = await post();
1307
1302
  }
1308
1303
  const data = await r.json();
1304
+ return { r, data };
1305
+ };
1306
+ for (let step = 0; step < 8; step++) {
1307
+ const { r, data } = await zooPost({
1308
+ model,
1309
+ messages,
1310
+ tools: LOCAL_TOOLS,
1311
+ tool_choice: 'auto',
1312
+ max_tokens: maxTok,
1313
+ });
1309
1314
  lastData = data;
1310
1315
  const msg = data.choices?.[0]?.message || {};
1311
1316
  const calls = Array.isArray(msg.tool_calls) ? msg.tool_calls : [];
@@ -1317,6 +1322,12 @@ async function zooComplete(prompt, log, agentId, parsed = {}) {
1317
1322
  try { args = JSON.parse(c.function?.arguments || c.arguments || '{}'); } catch { args = {}; }
1318
1323
  const name = c.function?.name || c.name || '';
1319
1324
  const result = await runLocalTool(name, args, log);
1325
+ usedTools.push({
1326
+ name,
1327
+ path: args.path,
1328
+ command: args.command ? String(args.command).slice(0, 120) : undefined,
1329
+ note: String(result).slice(0, 200),
1330
+ });
1320
1331
  messages.push({
1321
1332
  role: 'tool',
1322
1333
  tool_call_id: c.id,
@@ -1325,10 +1336,37 @@ async function zooComplete(prompt, log, agentId, parsed = {}) {
1325
1336
  }
1326
1337
  continue;
1327
1338
  }
1328
- text = zooTextFromMessage(msg, data) || (step ? '(tool loop ended with empty content)' : '(empty zoo reply)');
1339
+ text = zooTextFromMessage(msg, data);
1329
1340
  log(`cursor-backend: << zoo ${r.status} ${text.length}c model=${data.model || model} finish=${data.choices?.[0]?.finish_reason || '?'}`);
1330
1341
  break;
1331
1342
  }
1343
+ if (usedTools.length && !String(text || '').trim()) {
1344
+ messages.push({
1345
+ role: 'user',
1346
+ content: 'Stop calling tools. Write a short chat reply summarizing what you did: files written (full paths), commands that mattered, and what the user should open. No empty message.',
1347
+ });
1348
+ const { r, data } = await zooPost({
1349
+ model,
1350
+ messages,
1351
+ max_tokens: Math.max(800, Math.min(maxTok, 2048)),
1352
+ });
1353
+ lastData = data;
1354
+ text = zooTextFromMessage(data.choices?.[0]?.message, data);
1355
+ log(`cursor-backend: << zoo summary ${r.status} ${text.length}c`);
1356
+ }
1357
+ if (!String(text || '').trim()) {
1358
+ if (usedTools.length) {
1359
+ const writes = usedTools.filter((t) => t.name === 'write_file' && t.path).map((t) => t.path);
1360
+ const execs = usedTools.filter((t) => t.name === 'exec' && t.command).map((t) => t.command);
1361
+ const lines = ['Did local work (no model chat text came back):'];
1362
+ if (writes.length) lines.push(`wrote: ${[...new Set(writes)].join(', ')}`);
1363
+ if (execs.length) lines.push(`ran: ${execs.slice(-6).join(' · ')}`);
1364
+ if (lines.length === 1) lines.push(`${usedTools.length} tool calls.`);
1365
+ text = lines.join('\n');
1366
+ } else {
1367
+ text = '(empty zoo reply)';
1368
+ }
1369
+ }
1332
1370
  try { text += await zooSpendOverlay(lastData); } catch { /* overlay must never eat the reply */ }
1333
1371
  return { text, data: lastData };
1334
1372
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.50.24",
3
+ "version": "0.50.25",
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",