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

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.
@@ -6,15 +6,15 @@ _Last updated: 2026-03-07_
6
6
 
7
7
  | Component | Version | Build | Tests | Status |
8
8
  |-----------|---------|-------|-------|--------|
9
- | openclaw-cli-bridge-elvatis | 0.2.7 | ✅ | 5/5 ✅ | ✅ Stable |
9
+ | openclaw-cli-bridge-elvatis | 0.2.9 | ✅ | 5/5 ✅ | ✅ Stable |
10
10
 
11
11
  ## 🚀 Release State
12
12
 
13
13
  | Platform | Version | Status |
14
14
  |----------|---------|--------|
15
- | GitHub | v0.2.7 | ✅ Tagged + Release |
16
- | npm | 0.2.7 | ✅ Published |
17
- | ClawHub | 0.2.7 | ✅ Published |
15
+ | GitHub | v0.2.9 | ✅ Tagged + Release |
16
+ | npm | 0.2.9 | ✅ Published |
17
+ | ClawHub | 0.2.9 | ✅ Published |
18
18
 
19
19
  ## 📋 Open Tasks
20
20
 
@@ -28,7 +28,8 @@ _Last updated: 2026-03-07_
28
28
 
29
29
  | Task | Title | Version |
30
30
  |------|-------|---------|
31
- | T-006 | Fix port leak: registerService stop() hook for proxy server | 0.2.6 |
31
+ | T-007 | Critical: remove fuser -k, replace with safe health probe | 0.2.9 |
32
+ | T-006 | Fix port leak: registerService stop() hook + closeAllConnections | 0.2.7 |
32
33
  | T-005 | Add openclaw.extensions to package.json | 0.2.6 |
33
34
  | T-004 | /cli-codex + /cli-codex-mini slash commands | 0.2.5 |
34
35
  | T-003 | /cli-back restore + /cli-test health check | 0.2.3 |
@@ -1,5 +1,29 @@
1
1
  # LOG.md — openclaw-cli-bridge-elvatis
2
2
 
3
+ ## 2026-03-08 — Session 3 (Akido / claude-sonnet-4-6)
4
+
5
+ **Critical bug: Gateway SIGKILL via fuser (fixed in v0.2.9)**
6
+
7
+ Root cause: `fuser -k 31337/tcp` (added in v0.2.8) sent SIGKILL to the gateway
8
+ process itself during in-process hot-reloads. In hybrid reload mode, the same gateway
9
+ process reloads the plugin. At that point, the gateway holds port 31337 (via the proxy
10
+ it spawned earlier). `fuser -k` found the current gateway process as the port owner and
11
+ killed it with SIGKILL — visible in systemd journal as `code=killed, status=9/KILL` with
12
+ a 1.9G memory peak.
13
+
14
+ Fix: replaced `fuser -k` with a safe health probe. Before binding, `GET /v1/models` is
15
+ sent to the existing proxy. If it responds with 200, the proxy is reused silently
16
+ (`[cli-bridge] proxy already running on :31337 — reusing`). If EADDRINUSE but no
17
+ response (genuinely stale process), wait 1s and retry once. No process killing.
18
+
19
+ **Release pipeline:**
20
+ - v0.2.9 committed, tagged, pushed to GitHub
21
+ - GitHub release: https://github.com/elvatis/openclaw-cli-bridge-elvatis/releases/tag/v0.2.9
22
+ - npm: `@elvatis_com/openclaw-cli-bridge-elvatis@0.2.9`
23
+ - ClawHub: `openclaw-cli-bridge-elvatis@0.2.9`
24
+
25
+ ---
26
+
3
27
  ## 2026-03-07 — Session 2 (Akido / claude-sonnet-4-6)
4
28
 
5
29
  **Bug: Port leak on gateway hot-reload (fixed in v0.2.6)**
@@ -33,7 +33,8 @@ _Last updated: 2026-03-07_
33
33
 
34
34
  | Task | Title | Date |
35
35
  |------|-------|------|
36
- | T-006 | Fix port leak: registerService stop() hook | 2026-03-07 |
36
+ | T-007 | Critical: remove fuser -k, safe proxy reuse via health probe | 2026-03-08 |
37
+ | T-006 | Fix port leak: registerService stop() hook + closeAllConnections | 2026-03-07 |
37
38
  | T-005 | Add openclaw.extensions to package.json | 2026-03-07 |
38
39
  | T-004 | /cli-codex + /cli-codex-mini | 2026-03-07 |
39
40
  | T-003 | /cli-back + /cli-test | 2026-03-07 |
@@ -2,13 +2,13 @@
2
2
 
3
3
  _Last updated: 2026-03-07 by Akido (claude-sonnet-4-6)_
4
4
 
5
- ## Current Version: 0.2.7 — STABLE
5
+ ## Current Version: 0.2.9 — STABLE
6
6
 
7
7
  ## What is done
8
8
 
9
9
  - ✅ Repo: `https://github.com/elvatis/openclaw-cli-bridge-elvatis`
10
- - ✅ npm: `@elvatis_com/openclaw-cli-bridge-elvatis@0.2.6`
11
- - ✅ ClawHub: `openclaw-cli-bridge-elvatis@0.2.6`
10
+ - ✅ npm: `@elvatis_com/openclaw-cli-bridge-elvatis@0.2.9`
11
+ - ✅ ClawHub: `openclaw-cli-bridge-elvatis@0.2.9`
12
12
  - ✅ Phase 1: `openai-codex` provider via `~/.codex/auth.json` (no re-login)
13
13
  - ✅ Phase 2: Local OpenAI-compatible proxy on `127.0.0.1:31337` (Gemini + Claude CLI)
14
14
  - ✅ Phase 3: 10 slash commands (`/cli-sonnet`, `/cli-opus`, `/cli-haiku`, `/cli-gemini`, `/cli-gemini-flash`, `/cli-gemini3`, `/cli-codex`, `/cli-codex-mini`, `/cli-back`, `/cli-test`)
@@ -17,11 +17,23 @@ _Last updated: 2026-03-07 by Akido (claude-sonnet-4-6)_
17
17
  - ✅ `registerService` stop() hook: closes proxy server on plugin teardown (fixes EADDRINUSE on hot-reload)
18
18
  - ✅ `openclaw.extensions` added to `package.json` (required for `openclaw plugins install`)
19
19
 
20
- ## Bug Fixed (v0.2.6)
20
+ ## Bugs Fixed
21
21
 
22
- **Port leak on gateway hot-reload** HTTP proxy server on port 31337 had no cleanup
23
- handler. On hot-reloads the old server kept the port bound, causing EADDRINUSE.
24
- Fixed with `registerService` stop() callback.
22
+ ### v0.2.9Critical: Gateway SIGKILL via fuser
23
+ `fuser -k 31337/tcp` was sending SIGKILL to the gateway process itself during
24
+ in-process hot-reloads. The gateway holds port 31337 (via the proxy it spawned),
25
+ so `fuser` found it and killed it — explaining `status=9/KILL` in systemd journal.
26
+ Fixed by replacing `fuser -k` with a safe health probe (`GET /v1/models`): if the
27
+ existing proxy responds, reuse it silently. If EADDRINUSE but no response, wait 1s
28
+ and retry once. No process killing involved.
29
+
30
+ ### v0.2.7–v0.2.8 — EADDRINUSE on hot-reload (partially fixed, superseded by v0.2.9)
31
+ Added `closeAllConnections()` + `registerService` stop() hook. Port still leaked
32
+ during systemd restarts due to race condition. v0.2.9 health-probe approach is the
33
+ definitive fix.
34
+
35
+ ### v0.2.6 — Port leak on gateway hot-reload
36
+ HTTP proxy server had no cleanup handler. Fixed with `registerService` stop() callback.
25
37
 
26
38
  ## Open Risks
27
39
 
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > OpenClaw plugin that bridges locally installed AI CLIs (Codex, Gemini, Claude Code) as model providers — with slash commands for instant model switching, restore, and health testing.
4
4
 
5
- **Current version:** `0.2.7`
5
+ **Current version:** `0.2.9`
6
6
 
7
7
  ---
8
8
 
@@ -234,6 +234,24 @@ npm test # vitest run (5 unit tests for formatPrompt)
234
234
 
235
235
  ## Changelog
236
236
 
237
+ ### v0.2.10
238
+ - **docs:** Fix version labels in SKILL.md and README changelog (were stuck at 0.2.2/0.2.5)
239
+
240
+ ### v0.2.9
241
+ - **fix:** Critical — replace `fuser -k 31337/tcp` with safe health probe (`GET /v1/models`)
242
+ - Prevents gateway SIGKILL on in-process hot-reloads (systemd `status=9/KILL` was caused by `fuser` finding gateway itself holding the port)
243
+ - If proxy responds → reuse it; if EADDRINUSE but no response → wait 1s, retry once
244
+
245
+ ### v0.2.8
246
+ - **fix:** EADDRINUSE on every gateway restart — `closeAllConnections()` + `registerService` stop() hook (partially; superseded by v0.2.9 health-probe approach)
247
+
248
+ ### v0.2.7
249
+ - **fix:** Port leak on gateway hot-reload — added `registerService` stop() callback to close proxy server on plugin teardown
250
+
251
+ ### v0.2.6
252
+ - **fix:** `openclaw.extensions` added to `package.json` (required for `openclaw plugins install`)
253
+ - Config patcher: auto-adds vllm provider to `openclaw.json` on first startup
254
+
237
255
  ### v0.2.5
238
256
  - **feat:** `/cli-codex` → `openai-codex/gpt-5.3-codex`
239
257
  - **feat:** `/cli-codex-mini` → `openai-codex/gpt-5.1-codex-mini`
package/SKILL.md CHANGED
@@ -38,6 +38,10 @@ Six instant model-switch commands (authorized senders only):
38
38
  | `/cli-gemini` | `vllm/cli-gemini/gemini-2.5-pro` |
39
39
  | `/cli-gemini-flash` | `vllm/cli-gemini/gemini-2.5-flash` |
40
40
  | `/cli-gemini3` | `vllm/cli-gemini/gemini-3-pro` |
41
+ | `/cli-codex` | `openai-codex/gpt-5.3-codex` |
42
+ | `/cli-codex-mini` | `openai-codex/gpt-5.1-codex-mini` |
43
+ | `/cli-back` | Restore previous model |
44
+ | `/cli-test [model]` | Health check (no model switch) |
41
45
 
42
46
  Each command runs `openclaw models set <model>` atomically and replies with a confirmation.
43
47
 
@@ -49,4 +53,4 @@ Each command runs `openclaw models set <model>` atomically and replies with a co
49
53
 
50
54
  See `README.md` for full configuration reference and architecture diagram.
51
55
 
52
- **Version:** 0.2.2
56
+ **Version:** 0.2.10
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.10",
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"
@@ -36,4 +36,4 @@
36
36
  }
37
37
  }
38
38
  }
39
- }
39
+ }
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.10",
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": {
@@ -19,4 +19,4 @@
19
19
  "typescript": "^5.9.3",
20
20
  "vitest": "^4.0.18"
21
21
  }
22
- }
22
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "noEmit": true
5
+ },
6
+ "include": ["index.ts", "src/**/*.ts", "test/**/*.ts"]
7
+ }