openzoo 0.50.61 → 0.50.62

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.
@@ -2944,6 +2944,30 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
2944
2944
  }
2945
2945
  if (!path0.startsWith('/api/')) return false;
2946
2946
  const name = path0.slice('/api/'.length);
2947
+ // Revive: the minutely cron (scripts/revive-bots.sh) calls this after it has
2948
+ // made sure the hijack + app are alive. Every non-group bot gets a wakeup
2949
+ // timer if it lost one; nothing is spawned, nothing already armed is touched.
2950
+ if (name === 'ozRevive') {
2951
+ let parsed = {};
2952
+ try { parsed = JSON.parse(String(body || '{}')); } catch { parsed = {}; }
2953
+ const every = String(parsed.every || '5m');
2954
+ const wakes = readWakeups(HOME);
2955
+ const list = (cachedAgentList() || []).filter((a) => a?.id && !a.isGroup);
2956
+ const armed = [];
2957
+ const already = [];
2958
+ for (const a of list) {
2959
+ if (wakes[a.id]) { already.push(a.id); continue; }
2960
+ try {
2961
+ scheduleAgentWakeup(a.id, { every, prompt: parsed.prompt });
2962
+ armed.push({ id: a.id, name: a.name });
2963
+ } catch (e) {
2964
+ log(`cursor-backend: ozRevive ${a.id} ${e.message}`);
2965
+ }
2966
+ }
2967
+ log(`cursor-backend: ozRevive total=${list.length} armed=${armed.length} already=${already.length} every=${every}`);
2968
+ jsonSend(res, { ok: true, total: list.length, armed, already: already.length, every });
2969
+ return true;
2970
+ }
2947
2971
  if (name === 'getHostStatus') {
2948
2972
  jsonSend(res, { status: 'ready', ready: true, state: 'ready', hostStatus: 'ready' });
2949
2973
  log('cursor-backend: -> getHostStatus ready');
package/lib/proxy.js CHANGED
@@ -304,6 +304,18 @@ function replayPut(key, data, settle) {
304
304
  */
305
305
  export async function startProxy({ silent = false, requireToken = null, sessionMaxUsd = null, autoTunnel = false } = {}) {
306
306
  const client = new PayClient();
307
+ // HOUSE OUTAGE GATE. Measured 2026-09-01: TOKEN payments settled on chain
308
+ // (tx 3rwy5z…) and the gateway still answered 402 because ITS upstream
309
+ // (OpenRouter) was out of credits. Paying again buys nothing, and the
310
+ // "wallet underfunded" copy blamed the user's burner. When the paid 402 says
311
+ // upstream credits, stop paying for a minute and say what is actually wrong.
312
+ let upstreamOutageUntil = 0;
313
+ let upstreamOutageMsg = '';
314
+ const upstreamOutage = (body) => {
315
+ const m = String(body?.error?.message || body?.message || '');
316
+ const src = String(body?.error?.metadata?.limit_source || '');
317
+ return /openrouter_credits/i.test(src) || /insufficient credits/i.test(m);
318
+ };
307
319
  const log = silent ? () => {} : (...a) => console.log(...a);
308
320
  // ALWAYS-ON, BUT NEVER INTO A HARNESS'S TERMINAL. `silent` means another
309
321
  // process (openzoo claude, the editor launcher) owns stdio — printing request
@@ -684,6 +696,11 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
684
696
  }
685
697
 
686
698
  try {
699
+ if (Date.now() < upstreamOutageUntil) {
700
+ log('upstream outage: not paying (gate)');
701
+ jsonErr(res, 503, upstreamOutageMsg);
702
+ return;
703
+ }
687
704
  const result = await client.fetch(url, init);
688
705
  const { response, paid, receipt, accept } = result;
689
706
  if (paid && receipt) {
@@ -786,8 +803,22 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
786
803
  if (response.status === 402) {
787
804
  let quoted = '';
788
805
  let usd;
806
+ let q402 = null;
807
+ try { q402 = await response.clone().json(); } catch { q402 = null; }
808
+ if (paid && upstreamOutage(q402)) {
809
+ const settle = q402?.x402?.settle;
810
+ const tx = settle?.transaction || result.receipt?.tx || '';
811
+ upstreamOutageMsg = 'openzoo gateway upstream is out of credits (OpenRouter: "Insufficient credits"). '
812
+ + `Your payment settled${tx ? ` (tx ${tx})` : ''} — this is NOT your wallet. `
813
+ + 'The gateway operator must top up OpenRouter or route this model to another upstream. '
814
+ + 'Pausing paid retries for 60s.';
815
+ upstreamOutageUntil = Date.now() + 60_000;
816
+ log(`upstream outage: ${upstreamOutageMsg.slice(0, 120)}`);
817
+ jsonErr(res, 503, upstreamOutageMsg);
818
+ return;
819
+ }
789
820
  try {
790
- const q = await response.clone().json();
821
+ const q = q402;
791
822
  usd = Number(q?.accepts?.[0]?.extra?.billedUsd);
792
823
  if (Number.isFinite(usd)) quoted = ` This call needs ≈$${usd.toFixed(4)}.`;
793
824
  } catch { /* body was not the quote after all */ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.50.61",
3
+ "version": "0.50.62",
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",