openzoo 0.50.94 → 0.50.95

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/launch.js CHANGED
@@ -164,7 +164,30 @@ export function claudeZooEnv(baseEnv = process.env, { base, port } = {}) {
164
164
  // Correct here for the same reason as everything else in this block: the
165
165
  // proxy binds the prefix and forwards a bounded tail, so the request that
166
166
  // leaves this machine stays small no matter how long the conversation runs.
167
- if (baseEnv.OPENZOO_KEEP_COMPACT !== '1') {
167
+ //
168
+ // OPT-IN NOW, BECAUSE THE PRECONDITION IS NOT REAL.
169
+ //
170
+ // Everything above is correct ONLY IF something bounds the body that leaves
171
+ // this machine. Two comments here named that something — `spillTranscript`,
172
+ // and the knobs `OPENZOO_TAIL_MAX_CHARS` / `OPENZOO_KEEP_TAIL_MSGS`. None of
173
+ // the three exists: `grep -rn spillTranscript` matches nothing but these
174
+ // comments, in this package and in the gateway. Checked 2026-09-03.
175
+ //
176
+ // So the shipped behaviour was: compaction off, ceiling raised to 1M, and
177
+ // NOTHING trimming the transcript — Claude Code accumulates without limit and
178
+ // re-sends the whole thing every turn. The gateway's leCore cannot rescue it
179
+ // either: an agent body is few-and-huge, so `msgs.length <= KEEP_TAIL` leaves
180
+ // nothing "older than the live window", and what bulk there is sits in the
181
+ // system block and first user turn, both deliberately never spilled.
182
+ //
183
+ // MEASURED on a user's session: 20 calls, $13.86, `spilled 0/20`, 1.0159x vs
184
+ // direct — paying almost exactly retail to send an ever-growing transcript.
185
+ //
186
+ // Until a real bound ships, default to Claude Code's own behaviour, which is
187
+ // bounded and known-good. OPENZOO_UNBOUNDED_CONTEXT=1 restores the old
188
+ // settings for anyone who wants them back.
189
+ const unboundedContext = baseEnv.OPENZOO_UNBOUNDED_CONTEXT === '1';
190
+ if (unboundedContext && baseEnv.OPENZOO_KEEP_COMPACT !== '1') {
168
191
  env.DISABLE_COMPACT = baseEnv.DISABLE_COMPACT || '1';
169
192
  env.DISABLE_AUTO_COMPACT = baseEnv.DISABLE_AUTO_COMPACT || '1';
170
193
  }
@@ -177,9 +200,9 @@ export function claudeZooEnv(baseEnv = process.env, { base, port } = {}) {
177
200
  // sent. Disabling auto-compact WITHOUT raising this made it worse: it used to
178
201
  // compact and carry on, and instead it just stopped.
179
202
  //
180
- // Safe only because spillTranscript is real: OPENZOO_TAIL_MAX_CHARS bounds what
181
- // leaves this machine however long the conversation gets.
182
- if (baseEnv.OPENZOO_KEEP_COMPACT !== '1' && !baseEnv.CLAUDE_CODE_MAX_CONTEXT_TOKENS) {
203
+ // WAS justified by `spillTranscript` / OPENZOO_TAIL_MAX_CHARS neither of
204
+ // which exists. Now gated behind OPENZOO_UNBOUNDED_CONTEXT; see below.
205
+ if (unboundedContext && baseEnv.OPENZOO_KEEP_COMPACT !== '1' && !baseEnv.CLAUDE_CODE_MAX_CONTEXT_TOKENS) {
183
206
  env.CLAUDE_CODE_MAX_CONTEXT_TOKENS = baseEnv.OPENZOO_CLAUDE_CONTEXT_TOKENS || '1000000';
184
207
  }
185
208
  return env;
@@ -361,7 +384,15 @@ export async function launchClaude(argv) {
361
384
  // side by side is the only way the markup is visible while it is happening.
362
385
  + 'const ac=j.actual||{};'
363
386
  + 'const real=(ac.calls>0)?(" \\u00b7 $"+Number(ac.upstreamUsd||0).toFixed(4)+" real"+(ac.markupX?(" ("+ac.markupX+"x)"):"")):"";'
364
- + 'process.stdout.write("\\x1b[38;5;208m\\u25cf\\x1b[0m openzoo $"+(Number(j.spendUsd)||0).toFixed(4)+" "+(j.paidCalls||0)+" call"+((j.paidCalls||0)===1?"":"s")+save+real+spill+cr+" \\u00b7 x402")}'
387
+ // TWO NUMBERS, NEVER ONE. `spent` is cumulative across every session on
388
+ // this machine (restored from ~/.openzoo/session.json); `wallet` is what
389
+ // is left to pay with. Shown unlabelled, the first was read as the
390
+ // second — "$13.86" on screen while the wallet held $0.18 and every call
391
+ // 402'd underfunded. The words `spent` and `wallet` are the whole fix.
392
+ + 'const wu=Number(j.walletUsd);'
393
+ + 'const wcol=(!isFinite(wu)||wu<=0)?"\\x1b[31m":(wu<0.5?"\\x1b[33m":"\\x1b[90m");'
394
+ + 'const wal=isFinite(wu)?(" \\u00b7 "+wcol+"wallet $"+wu.toFixed(2)+"\\x1b[0m"):"";'
395
+ + 'process.stdout.write("\\x1b[38;5;208m\\u25cf\\x1b[0m openzoo spent $"+(Number(j.spendUsd)||0).toFixed(4)+" all-time"+wal+" \\u00b7 "+(j.paidCalls||0)+" call"+((j.paidCalls||0)===1?"":"s")+save+real+spill+cr+" \\u00b7 x402")}'
365
396
  + 'catch{process.stdout.write("\\x1b[38;5;208m\\u25cf\\x1b[0m openzoo \\u00b7 x402")}})\'\n');
366
397
  fs.chmodSync(scriptPath, 0o755);
367
398
  const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
package/lib/proxy.js CHANGED
@@ -388,6 +388,33 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
388
388
  let creditInflight = null;
389
389
  let lastPrices = {};
390
390
  let pricesAt = 0;
391
+ // WALLET BALANCE, CACHED LIKE CREDIT.
392
+ //
393
+ // The HUD showed one number — cumulative spend — and a user read it as their
394
+ // balance: "$13.86" beside a wallet holding $0.18, then every call 402'd
395
+ // "underfunded" and nothing on screen explained why. Spend is a total to
396
+ // date; the wallet is what is left. Both, labelled, or neither is legible.
397
+ //
398
+ // Never awaited on the /v1/info path: this quotes the gateway and reads
399
+ // chain balances, and the statusline gives it 1s. Serve last-known, refresh
400
+ // behind it — the same contract refreshCredit() keeps.
401
+ let walletUsd = null;
402
+ let walletAt = 0;
403
+ let walletInflight = null;
404
+ const refreshWallet = async (force = false) => {
405
+ if (!force && Date.now() - walletAt < 60000 && walletUsd != null) return walletUsd;
406
+ if (walletInflight) return walletInflight;
407
+ walletInflight = (async () => {
408
+ try {
409
+ const { affordableUsd } = await import('./info.js');
410
+ const v = await affordableUsd();
411
+ if (Number.isFinite(v)) { walletUsd = v; walletAt = Date.now(); }
412
+ } catch { /* keep last known */ }
413
+ walletInflight = null;
414
+ return walletUsd;
415
+ })();
416
+ return walletInflight;
417
+ };
391
418
  const refreshCredit = async (force = false) => {
392
419
  if (!force && Date.now() - creditAt < 20000 && creditUsd != null) return creditUsd;
393
420
  if (creditInflight) return creditInflight;
@@ -508,6 +535,7 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
508
535
  const p0 = (req.url || '').split('?')[0];
509
536
  if (req.method === 'GET' && (p0 === '/v1/info' || p0 === '/info')) {
510
537
  refreshCredit();
538
+ refreshWallet();
511
539
  const self = viaTunnel && tunnelGate?.publicUrl
512
540
  ? `${tunnelGate.publicUrl}/v1`
513
541
  : `http://localhost:${config.port}/v1`;
@@ -518,7 +546,12 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
518
546
  reachedVia: viaTunnel ? 'public tunnel' : 'localhost',
519
547
  publicTunnel: tunnelGate?.publicUrl ? `${tunnelGate.publicUrl}/v1` : null,
520
548
  servedRequests,
549
+ // SPENT-TO-DATE, not a balance: restored from ~/.openzoo/session.json at
550
+ // startup, so it spans every session on this machine.
521
551
  spendUsd: sessionSpent,
552
+ spendScope: 'all-time on this machine',
553
+ // WHAT IS LEFT to pay with. null while the first read is in flight.
554
+ walletUsd,
522
555
  creditUsd,
523
556
  // WHAT THE SAME CALLS WOULD HAVE COST DIRECT. Spend on its own is a
524
557
  // bill; spend beside the counterfactual is the product.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.50.94",
3
+ "version": "0.50.95",
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",