@elvatis_com/openclaw-cli-bridge-elvatis 0.2.8 → 0.2.9

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/index.ts CHANGED
@@ -354,24 +354,32 @@ const plugin = {
354
354
  let proxyServer: import("node:http").Server | null = null;
355
355
 
356
356
  if (enableProxy) {
357
- // Before binding, evict any stale process holding our port.
358
- // Port 31337 is exclusively ours it's safe to kill whatever is on it.
359
- // This handles the systemd restart race: new process starts before the old
360
- // one's socket is fully released.
361
- const evictPort = async (): Promise<void> => {
362
- try {
363
- const { execSync } = await import("node:child_process");
364
- // fuser -k sends SIGKILL to whatever holds the TCP port
365
- execSync(`fuser -k ${port}/tcp 2>/dev/null || true`, { stdio: "ignore" });
366
- // Give the OS ~200ms to release the socket
367
- await new Promise((r) => setTimeout(r, 200));
368
- api.logger.info(`[cli-bridge] evicted stale listener on port ${port}`);
369
- } catch {
370
- // fuser not available or port was already free — ignore
371
- }
357
+ // Probe whether a healthy proxy is already listening on our port.
358
+ // This handles hot-reloads where the previous plugin instance's server.close()
359
+ // may not have completed yet rather than killing anything (dangerous: fuser -k
360
+ // can kill the gateway process itself during in-process hot-reloads), we just
361
+ // check if the existing server still responds and reuse it if so.
362
+ const probeExisting = (): Promise<boolean> => {
363
+ return new Promise((resolve) => {
364
+ const req = http.request(
365
+ { hostname: "127.0.0.1", port, path: "/v1/models", method: "GET",
366
+ headers: { Authorization: `Bearer ${apiKey}` } },
367
+ (res) => { res.resume(); resolve(res.statusCode === 200); }
368
+ );
369
+ req.setTimeout(800, () => { req.destroy(); resolve(false); });
370
+ req.on("error", () => resolve(false));
371
+ req.end();
372
+ });
372
373
  };
373
374
 
374
375
  const startProxy = async (): Promise<void> => {
376
+ // If a healthy proxy is already up, reuse it — no need to rebind.
377
+ const alive = await probeExisting();
378
+ if (alive) {
379
+ api.logger.info(`[cli-bridge] proxy already running on :${port} — reusing`);
380
+ return;
381
+ }
382
+
375
383
  try {
376
384
  const server = await startProxyServer({
377
385
  port,
@@ -393,25 +401,28 @@ const plugin = {
393
401
  } catch (err: unknown) {
394
402
  const msg = (err as Error).message ?? String(err);
395
403
  if (msg.includes("EADDRINUSE")) {
396
- // fuser didn't work (e.g. not installed) one last retry after 800ms
397
- api.logger.warn(`[cli-bridge] port ${port} still busy after evict, retrying in 800ms…`);
398
- await new Promise((r) => setTimeout(r, 800));
399
- const server = await startProxyServer({
400
- port,
401
- apiKey,
402
- timeoutMs,
403
- log: (msg) => api.logger.info(msg),
404
- warn: (msg) => api.logger.warn(msg),
405
- });
406
- proxyServer = server;
407
- api.logger.info(`[cli-bridge] proxy ready on :${port} (retry)`);
404
+ // Port is busy but probe didn't respond wait for the OS to release it
405
+ api.logger.warn(`[cli-bridge] port ${port} busy, waiting 1s for OS release…`);
406
+ await new Promise((r) => setTimeout(r, 1000));
407
+ // One final attempt
408
+ try {
409
+ const server = await startProxyServer({
410
+ port, apiKey, timeoutMs,
411
+ log: (msg) => api.logger.info(msg),
412
+ warn: (msg) => api.logger.warn(msg),
413
+ });
414
+ proxyServer = server;
415
+ api.logger.info(`[cli-bridge] proxy ready on :${port} (retry)`);
416
+ } catch (e2: unknown) {
417
+ api.logger.warn(`[cli-bridge] proxy unavailable after retry: ${(e2 as Error).message}`);
418
+ }
408
419
  } else {
409
420
  api.logger.warn(`[cli-bridge] proxy failed to start on port ${port}: ${msg}`);
410
421
  }
411
422
  }
412
423
  };
413
424
 
414
- evictPort().then(startProxy).catch(() => {});
425
+ startProxy().catch(() => {});
415
426
  }
416
427
 
417
428
  // ── Cleanup: close proxy server on plugin stop (hot-reload / gateway restart) ──
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "openclaw-cli-bridge-elvatis",
3
3
  "name": "OpenClaw CLI Bridge",
4
- "version": "0.2.8",
4
+ "version": "0.2.9",
5
5
  "description": "Phase 1: openai-codex auth bridge. Phase 2: local HTTP proxy routing model calls through gemini/claude CLIs (vllm provider).",
6
6
  "providers": [
7
7
  "openai-codex"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elvatis_com/openclaw-cli-bridge-elvatis",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "description": "Bridges gemini, claude, and codex CLI tools as OpenClaw model providers. Reads existing CLI auth without re-login.",
5
5
  "type": "module",
6
6
  "openclaw": {