@elvatis_com/openclaw-cli-bridge-elvatis 1.6.1 → 1.6.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/.ai/handoff/STATUS.md +30 -31
- package/README.md +26 -4
- package/SKILL.md +4 -2
- package/index.ts +26 -7
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/chatgpt-browser.ts +9 -1
- package/src/proxy-server.ts +7 -5
- package/test/chatgpt-proxy.test.ts +5 -3
package/.ai/handoff/STATUS.md
CHANGED
|
@@ -1,46 +1,45 @@
|
|
|
1
1
|
# STATUS — openclaw-cli-bridge-elvatis
|
|
2
2
|
|
|
3
|
-
## Current Version: 1.
|
|
3
|
+
## Current Version: 1.6.3
|
|
4
4
|
|
|
5
|
-
## All 4 Providers Available —
|
|
6
|
-
| Provider | Status | Models |
|
|
7
|
-
|
|
8
|
-
| Grok | ✅ | web-grok/grok-3, grok-3-fast, grok-3-mini, grok-3-mini-fast | /grok-login |
|
|
9
|
-
|
|
|
10
|
-
|
|
|
11
|
-
| ChatGPT |
|
|
5
|
+
## All 4 Providers Available — persistent Chromium profiles
|
|
6
|
+
| Provider | Status | Models | Login Cmd | Cookie Expiry |
|
|
7
|
+
|---|---|---|---|---|
|
|
8
|
+
| Grok | ✅ | web-grok/grok-3, grok-3-fast, grok-3-mini, grok-3-mini-fast | /grok-login | ~178d |
|
|
9
|
+
| Gemini | ✅ | web-gemini/gemini-2-5-pro, gemini-2-5-flash, gemini-3-pro, gemini-3-flash | /gemini-login | ~398d |
|
|
10
|
+
| Claude.ai | ⚠️ expired | web-claude/claude-sonnet, claude-opus, claude-haiku | /claude-login | EXPIRED |
|
|
11
|
+
| ChatGPT | ⚠️ expiring | web-chatgpt/gpt-4o, gpt-4o-mini, gpt-4.1, o3, o4-mini, gpt-5, gpt-5-mini | /chatgpt-login | ~6d |
|
|
12
12
|
|
|
13
13
|
## Stats
|
|
14
|
-
- 22 total models
|
|
14
|
+
- 22 total models (6 CLI + 16 web-session)
|
|
15
15
|
- 96/96 tests green (8 test files)
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
- Singleton guard on ensureAllProviderContexts (no concurrent spawns)
|
|
19
|
-
- Persistent Chromium fallback for all 4 providers (no CDP dependency)
|
|
16
|
+
- All 4 providers use launchPersistentContext — sessions survive gateway restarts
|
|
17
|
+
- /bridge-status shows cookie-based status (independent of in-memory context)
|
|
20
18
|
|
|
21
19
|
## Architecture: Browser Lifecycle
|
|
22
|
-
- **
|
|
23
|
-
- **On
|
|
24
|
-
- **
|
|
25
|
-
- **On request (no
|
|
26
|
-
- **On /xxx-logout:** closes context + deletes
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
20
|
+
- **Profile dirs:** `~/.openclaw/{grok,gemini,claude,chatgpt}-profile/`
|
|
21
|
+
- **On plugin start:** startup restore attempts headless reconnect from saved profiles (5s delay)
|
|
22
|
+
- **On /xxx-login:** headed browser, user logs in, cookies baked to profile automatically
|
|
23
|
+
- **On request (no in-memory ctx):** proxy lazy-launches persistent context on first request
|
|
24
|
+
- **On /xxx-logout:** closes context + deletes profile + clears expiry file
|
|
25
|
+
- **bridge-status:** uses cookie expiry files as source of truth (not in-memory state)
|
|
26
|
+
- ✅ active — browser connected and verified
|
|
27
|
+
- 🟡 logged in, browser not loaded — cookies valid, lazy-loads on first request
|
|
28
|
+
- 🔴 session expired — needs /xxx-login
|
|
29
|
+
- ⚪ never logged in
|
|
31
30
|
|
|
32
31
|
## Release History
|
|
32
|
+
- v1.6.1 (2026-03-13): Fix /bridge-status — use cookie expiry as source of truth, not in-memory context
|
|
33
|
+
- v1.6.0 (2026-03-13): Persistent Chromium profiles for all 4 providers (Claude web + ChatGPT)
|
|
34
|
+
- v1.5.1 (2026-03-12): Fix hardcoded plugin version
|
|
35
|
+
- v1.5.0 (2026-03-12): Remove /claude-login and /chatgpt-login (pre-v1.6.0 interim)
|
|
33
36
|
- v1.4.0 (2026-03-12): Persistent browser fallback for Claude/Gemini/ChatGPT (no CDP required)
|
|
34
|
-
- v1.3.5 (2026-03-12): Startup restore guard
|
|
35
|
-
- v1.3.4 (2026-03-12): Safe sequential session restore from saved profiles
|
|
36
|
-
- v1.3.3 (2026-03-12): Remove startup auto-connect - browsers on-demand only (OOM fix)
|
|
37
|
-
- v1.3.2 (2026-03-12): Singleton guard on ensureAllProviderContexts (resource leak fix)
|
|
38
|
-
- v1.3.1 (2026-03-11): Cookie baking into persistent profiles on login
|
|
37
|
+
- v1.3.5 (2026-03-12): Startup restore guard (SIGUSR1 OOM fix)
|
|
39
38
|
- v1.3.0 (2026-03-11): Browser auto-reconnect after gateway restart
|
|
40
|
-
- v1.
|
|
41
|
-
- v1.1.0 (2026-03-11): Auto-connect on startup + /bridge-status
|
|
42
|
-
- v1.0.0 (2026-03-11): All 4 providers headless (Grok/Claude/Gemini/ChatGPT) - 96/96 tests
|
|
39
|
+
- v1.0.0 (2026-03-11): All 4 providers headless — 96/96 tests
|
|
43
40
|
|
|
44
41
|
## Next Steps
|
|
42
|
+
- /claude-login needs to be run (session expired)
|
|
43
|
+
- /chatgpt-login needs to be run in ~6 days
|
|
44
|
+
- Gemini model switching via UI (2.5 Pro vs Flash vs 3)
|
|
45
45
|
- Context-window management for long conversations
|
|
46
|
-
- Gemini model switching (2.5 Pro vs Flash vs 3) via UI
|
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.6.
|
|
5
|
+
**Current version:** `1.6.3`
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -135,9 +135,11 @@ Routes requests through real browser sessions on the provider's web UI. Requires
|
|
|
135
135
|
|---|---|
|
|
136
136
|
| `web-chatgpt/gpt-4o` | GPT-4o |
|
|
137
137
|
| `web-chatgpt/gpt-4o-mini` | GPT-4o Mini |
|
|
138
|
-
| `web-chatgpt/gpt-
|
|
139
|
-
| `web-chatgpt/
|
|
138
|
+
| `web-chatgpt/gpt-4.1` | GPT-4.1 |
|
|
139
|
+
| `web-chatgpt/o3` | o3 |
|
|
140
|
+
| `web-chatgpt/o4-mini` | o4-mini |
|
|
140
141
|
| `web-chatgpt/gpt-5` | GPT-5 |
|
|
142
|
+
| `web-chatgpt/gpt-5-mini` | GPT-5 Mini |
|
|
141
143
|
|
|
142
144
|
| Command | What it does |
|
|
143
145
|
|---|---|
|
|
@@ -360,6 +362,26 @@ npm test # vitest run (83 tests)
|
|
|
360
362
|
|
|
361
363
|
## Changelog
|
|
362
364
|
|
|
365
|
+
### v1.6.3
|
|
366
|
+
- **fix:** `/bridge-status` now uses cookie expiry files as source of truth (not in-memory context). Shows 🟡 "logged in, browser not loaded" instead of ❌ "not connected" after gateway restarts when cookies are still valid.
|
|
367
|
+
- **fix:** Update ChatGPT web-session models to current lineup: added `gpt-4.1`, `gpt-5-mini`; renamed `gpt-o3`→`o3`, `gpt-o4-mini`→`o4-mini`; updated context window sizes.
|
|
368
|
+
|
|
369
|
+
### v1.6.2
|
|
370
|
+
- **docs:** Add missing changelog entries (v1.5.1, v1.6.0, v1.6.1), fix /cli-codex54 command name in SKILL.md, add startup re-login alert description to SKILL.md.
|
|
371
|
+
|
|
372
|
+
### v1.6.1
|
|
373
|
+
- **feat:** WhatsApp re-login alerts on startup. After each gateway restart, the session restore loop collects any providers that failed to restore (cookies expired) and sends a single batched WhatsApp notification with the exact `/xxx-login` commands needed. No credential storage — remains fully 2FA-safe.
|
|
374
|
+
|
|
375
|
+
### v1.6.0
|
|
376
|
+
- **feat:** Persistent Chromium profiles for all 4 web providers (Grok, Gemini, Claude.ai, ChatGPT). Browser sessions now survive gateway restarts — cookies are stored in `~/.openclaw/{grok,gemini,claude,chatgpt}-profile/` and restored automatically on startup.
|
|
377
|
+
- **feat:** Re-added `/claude-login`, `/claude-status`, `/claude-logout`, `/chatgpt-login`, `/chatgpt-status`, `/chatgpt-logout` commands with full persistent profile support.
|
|
378
|
+
- **feat:** `/bridge-status` now shows all 4 providers with session state and cookie expiry at a glance.
|
|
379
|
+
- **fix:** Startup restore guard (`_startupRestoreDone`) prevents duplicate browser launches on hot-reloads (SIGUSR1).
|
|
380
|
+
|
|
381
|
+
### v1.5.1
|
|
382
|
+
- **fix:** Hardcoded plugin version `1.3.1` in plugin object updated to `1.5.1`.
|
|
383
|
+
- **docs:** Added `CONTRIBUTING.md` with release checklist and smoketest workflow.
|
|
384
|
+
|
|
363
385
|
### v1.5.0
|
|
364
386
|
- **refactor:** Removed `/claude-login`, `/claude-logout`, `/claude-status`, `/chatgpt-login`, `/chatgpt-logout`, `/chatgpt-status` commands and all related browser automation code. Claude is fully covered by `cli-claude/*` via CLI proxy, ChatGPT by `openai-codex` + `copilot-proxy`.
|
|
365
387
|
- **refactor:** Removed `web-claude/*` and `web-chatgpt/*` proxy routes and model entries from proxy server.
|
|
@@ -442,7 +464,7 @@ No CLI binaries required — just authenticated browser sessions.
|
|
|
442
464
|
|
|
443
465
|
## v1.0.0
|
|
444
466
|
- **feat:** `chatgpt-browser.ts` — chatgpt.com DOM-automation (`#prompt-textarea` + `[data-message-author-role]`)
|
|
445
|
-
- **feat:** `web-chatgpt/*` models: gpt-4o, gpt-4o-mini, gpt-o3,
|
|
467
|
+
- **feat:** `web-chatgpt/*` models: gpt-4o, gpt-4o-mini, gpt-4.1, o3, o4-mini, gpt-5, gpt-5-mini (updated in v1.6.3)
|
|
446
468
|
- **feat:** `/chatgpt-login`, `/chatgpt-status`, `/chatgpt-logout` + cookie-expiry tracking
|
|
447
469
|
- **feat:** All 4 providers headless: Grok ✅ Claude ✅ Gemini ✅ ChatGPT ✅
|
|
448
470
|
- **test:** 96/83 tests green (8 test files)
|
package/SKILL.md
CHANGED
|
@@ -39,7 +39,7 @@ Six instant model-switch commands (authorized senders only):
|
|
|
39
39
|
| `/cli-gemini-flash` | `vllm/cli-gemini/gemini-2.5-flash` |
|
|
40
40
|
| `/cli-gemini3` | `vllm/cli-gemini/gemini-3-pro` |
|
|
41
41
|
| `/cli-codex` | `openai-codex/gpt-5.3-codex` |
|
|
42
|
-
| `/cli-
|
|
42
|
+
| `/cli-codex54` | `openai-codex/gpt-5.4` |
|
|
43
43
|
| `/cli-back` | Restore previous model |
|
|
44
44
|
| `/cli-test [model]` | Health check (no model switch) |
|
|
45
45
|
|
|
@@ -54,6 +54,8 @@ Persistent Chromium profiles for 4 web providers (no API key needed):
|
|
|
54
54
|
|
|
55
55
|
Sessions survive gateway restarts. `/bridge-status` shows all 4 at a glance.
|
|
56
56
|
|
|
57
|
+
On gateway restart, if any session has expired, a **WhatsApp alert** is sent automatically with the exact `/xxx-login` commands needed — no guessing required.
|
|
58
|
+
|
|
57
59
|
## Setup
|
|
58
60
|
|
|
59
61
|
1. Enable plugin + restart gateway
|
|
@@ -62,4 +64,4 @@ Sessions survive gateway restarts. `/bridge-status` shows all 4 at a glance.
|
|
|
62
64
|
|
|
63
65
|
See `README.md` for full configuration reference and architecture diagram.
|
|
64
66
|
|
|
65
|
-
**Version:** 1.6.
|
|
67
|
+
**Version:** 1.6.3
|
package/index.ts
CHANGED
|
@@ -854,7 +854,7 @@ function proxyTestRequest(
|
|
|
854
854
|
const plugin = {
|
|
855
855
|
id: "openclaw-cli-bridge-elvatis",
|
|
856
856
|
name: "OpenClaw CLI Bridge",
|
|
857
|
-
version: "1.6.
|
|
857
|
+
version: "1.6.2",
|
|
858
858
|
description:
|
|
859
859
|
"Phase 1: openai-codex auth bridge. " +
|
|
860
860
|
"Phase 2: HTTP proxy for gemini/claude CLIs. " +
|
|
@@ -1975,22 +1975,41 @@ const plugin = {
|
|
|
1975
1975
|
return page.locator("#prompt-textarea").isVisible().catch(() => false);
|
|
1976
1976
|
} catch { chatgptContext = null; return false; }
|
|
1977
1977
|
},
|
|
1978
|
-
models: "web-chatgpt/gpt-4o, gpt-4o-mini, gpt-o3,
|
|
1978
|
+
models: "web-chatgpt/gpt-4o, gpt-4o-mini, gpt-4.1, o3, o4-mini, gpt-5, gpt-5-mini",
|
|
1979
1979
|
loginCmd: "/chatgpt-login",
|
|
1980
1980
|
expiry: () => { const e = loadChatGPTExpiry(); return e ? formatChatGPTExpiry(e) : null; },
|
|
1981
1981
|
},
|
|
1982
1982
|
];
|
|
1983
1983
|
|
|
1984
1984
|
for (const c of checks) {
|
|
1985
|
-
const active = c.ctx !== null;
|
|
1986
|
-
const ok = active ? await c.check() : false;
|
|
1987
1985
|
const expiry = c.expiry();
|
|
1988
|
-
|
|
1989
|
-
|
|
1986
|
+
const inMemory = c.ctx !== null;
|
|
1987
|
+
const liveOk = inMemory ? await c.check() : false;
|
|
1988
|
+
|
|
1989
|
+
// Cookie-based status: treat as "logged in" if expiry file exists and not expired
|
|
1990
|
+
// ⚠️ EXPIRED = truly expired, 🚨 = expiring soon (still valid), ✅ = fine
|
|
1991
|
+
// This reflects actual login state independent of in-memory context
|
|
1992
|
+
const cookieExpired = expiry !== null && expiry.startsWith("⚠️ EXPIRED");
|
|
1993
|
+
const cookieValid = expiry !== null && !cookieExpired;
|
|
1994
|
+
|
|
1995
|
+
if (liveOk) {
|
|
1996
|
+
// In-memory context active and verified
|
|
1997
|
+
lines.push(`✅ *${c.name}* — active (browser connected)`);
|
|
1990
1998
|
if (expiry) lines.push(` 🕐 ${expiry}`);
|
|
1991
1999
|
lines.push(` Models: ${c.models}`);
|
|
2000
|
+
} else if (cookieValid) {
|
|
2001
|
+
// Not in memory yet, but cookies are valid — will auto-connect on next request
|
|
2002
|
+
lines.push(`🟡 *${c.name}* — logged in, browser not loaded`);
|
|
2003
|
+
lines.push(` 🕐 ${expiry}`);
|
|
2004
|
+
lines.push(` Models: ${c.models}`);
|
|
2005
|
+
lines.push(` ℹ️ Browser launches on first request`);
|
|
2006
|
+
} else if (cookieExpired) {
|
|
2007
|
+
// Cookies expired — needs re-login
|
|
2008
|
+
lines.push(`🔴 *${c.name}* — session expired (run \`${c.loginCmd}\`)`);
|
|
2009
|
+
if (expiry) lines.push(` 🕐 ${expiry}`);
|
|
1992
2010
|
} else {
|
|
1993
|
-
|
|
2011
|
+
// No cookie file at all — never logged in
|
|
2012
|
+
lines.push(`⚪ *${c.name}* — never logged in (run \`${c.loginCmd}\`)`);
|
|
1994
2013
|
}
|
|
1995
2014
|
lines.push("");
|
|
1996
2015
|
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "openclaw-cli-bridge-elvatis",
|
|
3
3
|
"name": "OpenClaw CLI Bridge",
|
|
4
|
-
"version": "1.6.
|
|
4
|
+
"version": "1.6.3",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Phase 1: openai-codex auth bridge. Phase 2: local HTTP proxy routing model calls through gemini/claude CLIs (vllm provider).",
|
|
7
7
|
"providers": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elvatis_com/openclaw-cli-bridge-elvatis",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.3",
|
|
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": {
|
package/src/chatgpt-browser.ts
CHANGED
|
@@ -38,18 +38,26 @@ const CHATGPT_HOME = "https://chatgpt.com";
|
|
|
38
38
|
const MODEL_URLS: Record<string, string> = {
|
|
39
39
|
"gpt-4o": "https://chatgpt.com/?model=gpt-4o",
|
|
40
40
|
"gpt-4o-mini": "https://chatgpt.com/?model=gpt-4o-mini",
|
|
41
|
+
"gpt-4.1": "https://chatgpt.com/?model=gpt-4.1",
|
|
41
42
|
"o3": "https://chatgpt.com/?model=o3",
|
|
42
43
|
"o4-mini": "https://chatgpt.com/?model=o4-mini",
|
|
43
44
|
"gpt-5": "https://chatgpt.com/?model=gpt-5",
|
|
45
|
+
"gpt-5-mini": "https://chatgpt.com/?model=gpt-5-mini",
|
|
44
46
|
};
|
|
45
47
|
|
|
46
48
|
const MODEL_MAP: Record<string, string> = {
|
|
49
|
+
// canonical names
|
|
47
50
|
"gpt-4o": "gpt-4o",
|
|
48
51
|
"gpt-4o-mini": "gpt-4o-mini",
|
|
52
|
+
"gpt-4.1": "gpt-4.1",
|
|
53
|
+
"o3": "o3",
|
|
54
|
+
"o4-mini": "o4-mini",
|
|
55
|
+
"gpt-5": "gpt-5",
|
|
56
|
+
"gpt-5-mini": "gpt-5-mini",
|
|
57
|
+
// aliases (web-chatgpt/* prefix stripped by proxy)
|
|
49
58
|
"gpt-o3": "o3",
|
|
50
59
|
"gpt-o4-mini": "o4-mini",
|
|
51
60
|
"gpt-4-1": "gpt-4.1",
|
|
52
|
-
"gpt-5": "gpt-5",
|
|
53
61
|
};
|
|
54
62
|
|
|
55
63
|
function resolveModel(m?: string): string {
|
package/src/proxy-server.ts
CHANGED
|
@@ -87,11 +87,13 @@ export const CLI_MODELS = [
|
|
|
87
87
|
{ id: "web-claude/claude-opus", name: "Claude Opus (web session)", contextWindow: 200_000, maxTokens: 8192 },
|
|
88
88
|
{ id: "web-claude/claude-haiku", name: "Claude Haiku (web session)", contextWindow: 200_000, maxTokens: 8192 },
|
|
89
89
|
// ChatGPT web-session models (requires /chatgpt-login)
|
|
90
|
-
{ id: "web-chatgpt/gpt-4o", name: "GPT-4o (web session)", contextWindow: 128_000, maxTokens:
|
|
91
|
-
{ id: "web-chatgpt/gpt-4o-mini", name: "GPT-4o Mini (web session)", contextWindow: 128_000, maxTokens:
|
|
92
|
-
{ id: "web-chatgpt/gpt-
|
|
93
|
-
{ id: "web-chatgpt/
|
|
94
|
-
{ id: "web-chatgpt/
|
|
90
|
+
{ id: "web-chatgpt/gpt-4o", name: "GPT-4o (web session)", contextWindow: 128_000, maxTokens: 16_384 },
|
|
91
|
+
{ id: "web-chatgpt/gpt-4o-mini", name: "GPT-4o Mini (web session)", contextWindow: 128_000, maxTokens: 16_384 },
|
|
92
|
+
{ id: "web-chatgpt/gpt-4.1", name: "GPT-4.1 (web session)", contextWindow: 1_047_576, maxTokens: 32_768 },
|
|
93
|
+
{ id: "web-chatgpt/o3", name: "o3 (web session)", contextWindow: 200_000, maxTokens: 100_000 },
|
|
94
|
+
{ id: "web-chatgpt/o4-mini", name: "o4-mini (web session)", contextWindow: 200_000, maxTokens: 100_000 },
|
|
95
|
+
{ id: "web-chatgpt/gpt-5", name: "GPT-5 (web session)", contextWindow: 1_047_576, maxTokens: 32_768 },
|
|
96
|
+
{ id: "web-chatgpt/gpt-5-mini", name: "GPT-5 Mini (web session)", contextWindow: 1_047_576, maxTokens: 32_768 },
|
|
95
97
|
];
|
|
96
98
|
|
|
97
99
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
@@ -81,14 +81,16 @@ describe("ChatGPT web-session routing — model list", () => {
|
|
|
81
81
|
const ids = (res.body as { data: { id: string }[] }).data.map(m => m.id);
|
|
82
82
|
expect(ids).toContain("web-chatgpt/gpt-4o");
|
|
83
83
|
expect(ids).toContain("web-chatgpt/gpt-4o-mini");
|
|
84
|
-
expect(ids).toContain("web-chatgpt/gpt-
|
|
85
|
-
expect(ids).toContain("web-chatgpt/
|
|
84
|
+
expect(ids).toContain("web-chatgpt/gpt-4.1");
|
|
85
|
+
expect(ids).toContain("web-chatgpt/o3");
|
|
86
|
+
expect(ids).toContain("web-chatgpt/o4-mini");
|
|
86
87
|
expect(ids).toContain("web-chatgpt/gpt-5");
|
|
88
|
+
expect(ids).toContain("web-chatgpt/gpt-5-mini");
|
|
87
89
|
});
|
|
88
90
|
|
|
89
91
|
it("web-chatgpt/* models listed in CLI_MODELS constant", () => {
|
|
90
92
|
const chatgpt = CLI_MODELS.filter(m => m.id.startsWith("web-chatgpt/"));
|
|
91
|
-
expect(chatgpt).toHaveLength(
|
|
93
|
+
expect(chatgpt).toHaveLength(7);
|
|
92
94
|
});
|
|
93
95
|
});
|
|
94
96
|
|