@elvatis_com/openclaw-cli-bridge-elvatis 1.3.3 → 1.3.5

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.
@@ -1,8 +1,8 @@
1
1
  # STATUS — openclaw-cli-bridge-elvatis
2
2
 
3
- ## Current Version: 1.0.0 (npm + ClawHub + GitHub) ✅ RELEASED
3
+ ## Current Version: 1.3.3 (npm + ClawHub + GitHub) ✅ RELEASED
4
4
 
5
- ## All 4 Providers LIVETested 2026-03-11 22:24
5
+ ## All 4 Providers Availableon-demand via /xxx-login
6
6
  | Provider | Status | Models | Command |
7
7
  |---|---|---|---|
8
8
  | Grok | ✅ | web-grok/grok-3, grok-3-fast, grok-3-mini, grok-3-mini-fast | /grok-login |
@@ -10,26 +10,35 @@
10
10
  | Gemini | ✅ | web-gemini/gemini-2-5-pro, gemini-2-5-flash, gemini-3-pro, gemini-3-flash | /gemini-login |
11
11
  | ChatGPT | ✅ | web-chatgpt/gpt-4o, gpt-4o-mini, gpt-o3, gpt-o4-mini, gpt-5 | /chatgpt-login |
12
12
 
13
- Live test: "What is the capital of France?"
14
- - Grok: "Paris" ✅
15
- - Claude: "Paris" ✅
16
- - Gemini: "Paris" ✅
17
- - ChatGPT: "Paris" ✅
18
-
19
13
  ## Stats
20
14
  - 22 total models, 16 web-session models
21
15
  - 96/96 tests green (8 test files)
22
- - 0 zombie Chromium processes (singleton CDP, cleanupBrowsers on stop)
16
+ - 0 zombie Chromium processes at startup (browsers on-demand only)
23
17
  - Cookie expiry tracking for all 4 providers
18
+ - Singleton guard on ensureAllProviderContexts (no concurrent spawns)
19
+
20
+ ## Architecture: Browser Lifecycle
21
+ - **On plugin start:** NO browser launched automatically
22
+ - **On /xxx-login:** launches persistent Chromium for that provider only
23
+ - **On request (no context):** returns null → caller sees "not logged in" error
24
+ - **On /xxx-logout:** closes context + deletes session file
25
+
26
+ ## Known Issues
27
+ - Cloudflare may block headless Chromium for Claude/Gemini without a valid CDP session
28
+ → Workaround: have OpenClaw browser open on the provider's page, then /xxx-login
24
29
 
25
- ## Known Issue: Browser persistence after Gateway restart
26
- After SIGUSR1/full restart, OpenClaw browser is gone (CDP ECONNREFUSED).
27
- Workaround: manually open browser + 4 provider pages lazy connect takes over.
28
- Fix needed: auto-start browser on plugin init, or keep-alive ping.
30
+ ## Release History
31
+ - v1.3.3 (2026-03-12): Remove startup auto-connect browsers on-demand only (OOM fix)
32
+ - v1.3.2 (2026-03-12): Singleton guard on ensureAllProviderContexts (resource leak fix)
33
+ - v1.3.1 (2026-03-11): Cookie baking into persistent profiles on login
34
+ - v1.3.0 (2026-03-11): Browser auto-reconnect after gateway restart
35
+ - v1.2.0 (2026-03-11): Fresh page per request + ChatGPT model switching
36
+ - v1.1.0 (2026-03-11): Auto-connect on startup + /bridge-status
37
+ - v1.0.0 (2026-03-11): All 4 providers headless (Grok/Claude/Gemini/ChatGPT) — 96/96 tests
38
+ - v0.2.x: Grok (v0.2.26-28), Claude (v0.2.29), Gemini (v0.2.30)
39
+ - v0.2.25: Sleep-resilient token refresh + staged /cli-* switching
29
40
 
30
- ## Next Steps (v1.1.x)
31
- - Auto-reconnect OpenClaw browser on plugin start
32
- - /status command showing all 4 providers at once
33
- - Context-window management for long conversations (new page per conversation)
34
- - Handle model-switching within chatgpt.com (dropdown selector)
35
- - Handle Gemini model switching (2.5 Pro vs Flash vs 3)
41
+ ## Next Steps
42
+ - Context-window management for long conversations
43
+ - Gemini model switching (2.5 Pro vs Flash vs 3) via UI
44
+ - /bridge-status: show per-provider login state + cookie expiry
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, health testing, and model listing.
4
4
 
5
- **Current version:** `1.3.1`
5
+ **Current version:** `1.3.5`
6
6
 
7
7
  ---
8
8
 
@@ -81,6 +81,77 @@ All commands use gateway-level `commands.allowFrom` for authorization (`requireA
81
81
 
82
82
  ---
83
83
 
84
+ ### Phase 4 — Web Browser Providers (headless, no API key needed)
85
+
86
+ Routes requests through real browser sessions on the provider's web UI. Requires a valid login session (free or paid tier). Uses persistent Chromium profiles — sessions survive gateway restarts.
87
+
88
+ **Grok** (grok.com — SuperGrok subscription):
89
+
90
+ | Model | Notes |
91
+ |---|---|
92
+ | `web-grok/grok-3` | Full model |
93
+ | `web-grok/grok-3-fast` | Faster variant |
94
+ | `web-grok/grok-3-mini` | Lightweight |
95
+ | `web-grok/grok-3-mini-fast` | Fastest |
96
+
97
+ | Command | What it does |
98
+ |---|---|
99
+ | `/grok-login` | Authenticate via X.com OAuth, save session to `~/.openclaw/grok-profile/` |
100
+ | `/grok-status` | Show session validity + cookie expiry |
101
+ | `/grok-logout` | Clear session |
102
+
103
+ **Claude** (claude.ai):
104
+
105
+ | Model | Notes |
106
+ |---|---|
107
+ | `web-claude/claude-sonnet` | Sonnet |
108
+ | `web-claude/claude-opus` | Opus |
109
+ | `web-claude/claude-haiku` | Haiku |
110
+
111
+ | Command | What it does |
112
+ |---|---|
113
+ | `/claude-login` | Authenticate, save cookies to `~/.openclaw/claude-profile/` |
114
+ | `/claude-status` | Show session validity + cookie expiry |
115
+ | `/claude-logout` | Clear session |
116
+
117
+ **Gemini** (gemini.google.com):
118
+
119
+ | Model | Notes |
120
+ |---|---|
121
+ | `web-gemini/gemini-2-5-pro` | Gemini 2.5 Pro |
122
+ | `web-gemini/gemini-2-5-flash` | Gemini 2.5 Flash |
123
+ | `web-gemini/gemini-3-pro` | Gemini 3 Pro |
124
+ | `web-gemini/gemini-3-flash` | Gemini 3 Flash |
125
+
126
+ | Command | What it does |
127
+ |---|---|
128
+ | `/gemini-login` | Authenticate, save cookies to `~/.openclaw/gemini-profile/` |
129
+ | `/gemini-status` | Show session validity + cookie expiry |
130
+ | `/gemini-logout` | Clear session |
131
+
132
+ **ChatGPT** (chatgpt.com):
133
+
134
+ | Model | Notes |
135
+ |---|---|
136
+ | `web-chatgpt/gpt-4o` | GPT-4o |
137
+ | `web-chatgpt/gpt-4o-mini` | GPT-4o Mini |
138
+ | `web-chatgpt/gpt-o3` | o3 |
139
+ | `web-chatgpt/gpt-o4-mini` | o4-mini |
140
+ | `web-chatgpt/gpt-5` | GPT-5 |
141
+
142
+ | Command | What it does |
143
+ |---|---|
144
+ | `/chatgpt-login` | Authenticate, save cookies to `~/.openclaw/chatgpt-profile/` |
145
+ | `/chatgpt-status` | Show session validity + cookie expiry |
146
+ | `/chatgpt-logout` | Clear session |
147
+
148
+ **Session lifecycle:**
149
+ - First use: run `/xxx-login` once (opens Chromium, authenticate in browser)
150
+ - After gateway restart: sessions are **automatically restored** from saved profiles on startup (sequential, ~25s after start)
151
+ - `/bridge-status` — shows all 4 providers at a glance with login state + expiry info
152
+
153
+ ---
154
+
84
155
  ## Requirements
85
156
 
86
157
  - [OpenClaw](https://openclaw.ai) gateway (tested with `2026.3.x`)
@@ -287,6 +358,25 @@ npm test # vitest run (45 tests)
287
358
 
288
359
  ## Changelog
289
360
 
361
+ ### v1.3.5
362
+ - **fix:** Startup session restore now runs only once per process lifetime — `_startupRestoreDone` module-level guard prevents re-running on every hot-reload (SIGUSR1), which was triggered every ~60s by the openclaw-control-ui dashboard poll
363
+ - **root cause:** Gateway `reload.mode=hybrid` + dashboard status polling caused plugin to reinitialize every 60s → each reload spawned a new Gemini Chromium instance → RAM/CPU OOM loop
364
+ - **behavior:** First load after gateway start: sequential profile restore runs once. All subsequent hot-reloads: skip restore, reuse existing in-memory contexts
365
+
366
+ ### v1.3.4
367
+ - **feat:** Safe sequential session restore on startup — if a saved profile exists, providers are reconnected automatically after gateway restart (one at a time, 3s delay between each, headless)
368
+ - **fix:** No manual `/xxx-login` needed after reboot if profile is already saved
369
+ - **safety:** Profile-gated — only restores if `~/.openclaw/<provider>-profile/` or cookie file exists; never spawns a browser for an uninitialized provider
370
+
371
+ ### v1.3.3
372
+ - **fix:** Removed auto-connect of all browser providers on plugin startup — caused OOM (load 195, 30GB RAM) by spawning 4+ persistent Chromium instances on every gateway start
373
+ - **fix:** Removed Grok session restore on startup — same root cause
374
+ - **behavior change:** Browsers are now started **on-demand only** via `/grok-login`, `/claude-login`, `/gemini-login`, `/chatgpt-login`
375
+
376
+ ### v1.3.2
377
+ - **fix:** Singleton promise guard on `ensureAllProviderContexts()` — concurrent requests no longer each spawn their own Chromium; extra callers await the existing run
378
+ - **fix:** Removed recursive `ensureAllProviderContexts()` fallback from all `connect*Context` proxy callbacks — no more exponential browser spawn on CDP failure
379
+
290
380
  ### v1.3.1
291
381
  - **fix:** /claude-login, /gemini-login, /chatgpt-login now bake cookies into persistent profile dirs
292
382
  - **fix:** After gateway restart, providers auto-reconnect from saved profile (no browser tabs needed)
package/index.ts CHANGED
@@ -233,6 +233,10 @@ async function scanCookieExpiry(ctx: import("playwright").BrowserContext): Promi
233
233
  let _cdpBrowser: import("playwright").Browser | null = null;
234
234
  let _cdpBrowserLaunchPromise: Promise<import("playwright").BrowserContext | null> | null = null;
235
235
 
236
+ // Startup restore guard — module-level so it survives hot-reloads (SIGUSR1).
237
+ // Set to true after first run; hot-reloads see true and skip the restore loop.
238
+ let _startupRestoreDone = false;
239
+
236
240
  /**
237
241
  * Connect to the OpenClaw managed browser (CDP port 18800).
238
242
  * Singleton: reuses the same connection. Falls back to persistent Chromium for Grok only.
@@ -746,10 +750,102 @@ const plugin = {
746
750
  const codexAuthPath = cfg.codexAuthPath ?? DEFAULT_CODEX_AUTH_PATH;
747
751
  const grokSessionPath = cfg.grokSessionPath ?? DEFAULT_SESSION_PATH;
748
752
 
749
- // Grok session restore on startup REMOVED on-demand only via /grok-login.
753
+ // ── Session restore: only on first plugin load (not on hot-reloads) ──────
754
+ // The gateway polls every ~60s via openclaw status, which triggers a hot-reload
755
+ // (SIGUSR1 + hybrid mode). Module-level contexts (grokContext etc.) survive
756
+ // hot-reloads because Node keeps the module in memory — so we only need to
757
+ // restore once, on the very first load (when all contexts are null).
758
+ //
759
+ // Guard: _startupRestoreDone is module-level and persists across hot-reloads.
760
+ if (!_startupRestoreDone) {
761
+ _startupRestoreDone = true;
762
+ void (async () => {
763
+ await new Promise(r => setTimeout(r, 5000)); // wait for proxy + gateway to settle
764
+ const { chromium } = await import("playwright");
765
+ const { existsSync } = await import("node:fs");
766
+
767
+ const profileProviders: Array<{
768
+ name: string;
769
+ profileDir: string;
770
+ cookieFile: string;
771
+ verifySelector: string;
772
+ homeUrl: string;
773
+ setCtx: (c: BrowserContext) => void;
774
+ getCtx: () => BrowserContext | null;
775
+ }> = [
776
+ {
777
+ name: "grok",
778
+ profileDir: GROK_PROFILE_DIR,
779
+ cookieFile: join(homedir(), ".openclaw", "grok-session.json"),
780
+ verifySelector: "textarea",
781
+ homeUrl: "https://grok.com",
782
+ getCtx: () => grokContext,
783
+ setCtx: (c) => { grokContext = c; },
784
+ },
785
+ {
786
+ name: "claude",
787
+ profileDir: join(homedir(), ".openclaw", "claude-profile"),
788
+ cookieFile: join(homedir(), ".openclaw", "claude-cookie-expiry.json"),
789
+ verifySelector: ".ProseMirror",
790
+ homeUrl: "https://claude.ai/new",
791
+ getCtx: () => claudeContext,
792
+ setCtx: (c) => { claudeContext = c; },
793
+ },
794
+ {
795
+ name: "gemini",
796
+ profileDir: join(homedir(), ".openclaw", "gemini-profile"),
797
+ cookieFile: join(homedir(), ".openclaw", "gemini-cookie-expiry.json"),
798
+ verifySelector: ".ql-editor",
799
+ homeUrl: "https://gemini.google.com/app",
800
+ getCtx: () => geminiContext,
801
+ setCtx: (c) => { geminiContext = c; },
802
+ },
803
+ {
804
+ name: "chatgpt",
805
+ profileDir: join(homedir(), ".openclaw", "chatgpt-profile"),
806
+ cookieFile: join(homedir(), ".openclaw", "chatgpt-cookie-expiry.json"),
807
+ verifySelector: "#prompt-textarea",
808
+ homeUrl: "https://chatgpt.com",
809
+ getCtx: () => chatgptContext,
810
+ setCtx: (c) => { chatgptContext = c; },
811
+ },
812
+ ];
750
813
 
751
- // Auto-connect on startup REMOVED — browsers are launched on-demand via /xxx-login only.
752
- // Spawning 4 persistent Chromium contexts at startup caused OOM under load.
814
+ for (const p of profileProviders) {
815
+ if (!existsSync(p.profileDir) && !existsSync(p.cookieFile)) {
816
+ api.logger.info(`[cli-bridge:${p.name}] no saved profile — skipping startup restore`);
817
+ continue;
818
+ }
819
+ if (p.getCtx()) continue; // already connected
820
+
821
+ try {
822
+ api.logger.info(`[cli-bridge:${p.name}] restoring session from profile…`);
823
+ const ctx = await chromium.launchPersistentContext(p.profileDir, {
824
+ headless: true,
825
+ args: ["--no-sandbox", "--disable-setuid-sandbox"],
826
+ });
827
+ const page = await ctx.newPage();
828
+ await page.goto(p.homeUrl, { waitUntil: "domcontentloaded", timeout: 20_000 });
829
+ await new Promise(r => setTimeout(r, 3000));
830
+ const ok = await page.locator(p.verifySelector).isVisible().catch(() => false);
831
+ await page.close().catch(() => {});
832
+ if (ok) {
833
+ p.setCtx(ctx);
834
+ ctx.on("close", () => { p.setCtx(null as unknown as BrowserContext); });
835
+ api.logger.info(`[cli-bridge:${p.name}] session restored from profile ✅`);
836
+ } else {
837
+ await ctx.close().catch(() => {});
838
+ api.logger.info(`[cli-bridge:${p.name}] profile exists but not logged in — needs /xxx-login`);
839
+ }
840
+ } catch (err) {
841
+ api.logger.warn(`[cli-bridge:${p.name}] startup restore failed: ${(err as Error).message}`);
842
+ }
843
+
844
+ // Sequential — never spawn all 4 Chromium instances at once
845
+ await new Promise(r => setTimeout(r, 3000));
846
+ }
847
+ })();
848
+ }
753
849
 
754
850
  // ── Phase 1: openai-codex auth bridge ─────────────────────────────────────
755
851
  if (enableCodex) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "openclaw-cli-bridge-elvatis",
3
3
  "name": "OpenClaw CLI Bridge",
4
- "version": "1.3.3",
4
+ "version": "1.3.5",
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": "1.3.3",
3
+ "version": "1.3.5",
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": {