openzoo 0.21.2 → 0.21.3

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/setup.js CHANGED
@@ -188,18 +188,24 @@ export async function setupEditor(which, target) {
188
188
  // (see the header of lib/tunnel.js). So the public URL is a HARD dependency
189
189
  // of the editor path, and we wait for it — bounded, with progress, because
190
190
  // an unbounded wait once left the editor never launching at all.
191
+ // NEVER BLOCK THE LAUNCH ON THE TUNNEL. Waiting inline is what made this
192
+ // command sit on "waiting for the public tunnel....." with nothing else
193
+ // happening — and if the tunnel was slow or failing, the editor never opened
194
+ // at all. The tunnel is genuinely required for the EDITOR path (its server
195
+ // cannot reach localhost), but we already have a rebind hook that rewrites
196
+ // and re-pins the config the moment a URL exists, so the honest design is:
197
+ // launch now with what we have, and follow the tunnel in when it arrives.
191
198
  if (!publicUrl) {
192
- process.stdout.write('waiting for the public tunnel (the editor\'s server cannot reach localhost)');
193
- for (let i = 0; i < 240 && !started?.publicUrl; i++) { // up to 120s (rungs race; first to SERVE wins)
199
+ // A couple of seconds only, in case it is already up — then move on.
200
+ for (let i = 0; i < 6 && !started?.publicUrl; i++) {
194
201
  await new Promise((r) => setTimeout(r, 500));
195
- if (i % 4 === 3) process.stdout.write('.');
196
202
  }
197
- console.log('');
198
203
  publicUrl = started?.publicUrl ?? null;
199
204
  tunnelKey = started?.tunnelToken ?? tunnelKey;
200
205
  }
201
206
  if (publicUrl) console.log(`tunnel: ${publicUrl}/v1 api_key ${tunnelKey}`);
202
- else console.log('tunnel: NOT up — the editor will not be able to reach the proxy\n (OPENZOO_NO_TUNNEL=1 to skip; terminal harnesses still work on localhost)');
207
+ else console.log('tunnel: still coming up — launching now; the editor config is\n'
208
+ + ' rewritten and re-pinned automatically the moment it is ready.');
203
209
  } else {
204
210
  console.log(`proxy already running on ${base}`);
205
211
  // A proxy someone else started owns the tunnel; ask it for the public URL.
package/lib/tunnel.js CHANGED
@@ -140,33 +140,26 @@ export async function probeTunnel(url, { tries = 8, gapMs = 2500 } = {}) {
140
140
  * Returns { url, proc, rung } or throws when every rung fails.
141
141
  */
142
142
  export async function startHealthyTunnel(bin, port, log = () => {}) {
143
- // RACE, DO NOT WALK. Tried serially, four rungs cost up to 60s waiting for a
144
- // URL plus ~20s of probing EACH minutes of "waiting for the public tunnel"
145
- // before the good one is even attempted, and the caller times out first.
146
- // Quick tunnels are free and independent, so all rungs are started at once and
147
- // the first one that actually SERVES wins; the losers are killed. Worst case
148
- // becomes one rung's latency instead of four.
149
- const losers = [];
150
- let settled = false;
151
- const attempts = TUNNEL_RUNGS.map(async (rung) => {
152
- const started = await startCloudflared(bin, port, log, { args: rung.args });
153
- losers.push(started.proc);
154
- const ok = await probeTunnel(started.url, { tries: 6, gapMs: 2000 });
155
- if (!ok) throw new Error(`${rung.why}: printed ${started.url} but the edge could not reach it`);
156
- if (settled) throw new Error('another rung won');
157
- settled = true;
158
- return { ...started, rung: rung.why };
159
- });
160
- try {
161
- const winner = await Promise.any(attempts);
162
- log(`tunnel: up via ${winner.rung}`);
163
- for (const p of losers) { if (p !== winner.proc) { try { p.kill(); } catch { /* gone */ } } }
164
- return winner;
165
- } catch (agg) {
166
- for (const p of losers) { try { p.kill(); } catch { /* gone */ } }
167
- const why = agg?.errors?.map((e) => e.message).join(' | ') || agg?.message || 'unknown';
168
- throw new Error(`no cloudflared configuration produced a working tunnel (${why})`);
143
+ // ONE AT A TIME, best rung first. An earlier version raced all four at once;
144
+ // that is four quick tunnels per invocation against a per-IP-limited free
145
+ // service, which is a good way to get throttled into exactly the failure it
146
+ // was meant to dodge. Rung 1 (ipv4) is the measured winner on this network,
147
+ // so the common path costs one tunnel.
148
+ let lastErr = null;
149
+ for (const rung of TUNNEL_RUNGS) {
150
+ let started = null;
151
+ try {
152
+ started = await startCloudflared(bin, port, log, { args: rung.args });
153
+ } catch (e) { lastErr = e; log(`tunnel: ${rung.why} — ${e.message}`); continue; }
154
+ if (await probeTunnel(started.url, { tries: 5, gapMs: 2000 })) {
155
+ log(`tunnel: up via ${rung.why}`);
156
+ return { ...started, rung: rung.why };
157
+ }
158
+ log(`tunnel: ${rung.why} registered but the edge could not reach it — next`);
159
+ try { started.proc.kill(); } catch { /* already gone */ }
160
+ lastErr = new Error(`${rung.why}: edge could not reach the origin`);
169
161
  }
162
+ throw lastErr || new Error('no cloudflared configuration produced a working tunnel');
170
163
  }
171
164
 
172
165
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.21.2",
3
+ "version": "0.21.3",
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",