@elvatis_com/openclaw-cli-bridge-elvatis 0.2.23 → 0.2.25
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/DASHBOARD.md +32 -19
- package/.ai/handoff/LOG.md +111 -38
- package/.ai/handoff/MANIFEST.json +49 -126
- package/.ai/handoff/NEXT_ACTIONS.md +21 -22
- package/.ai/handoff/STATUS.md +76 -48
- package/.ai/handoff/TRUST.md +40 -51
- package/README.md +12 -1
- package/SKILL.md +1 -1
- package/index.ts +165 -10
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -2
- package/src/claude-auth.ts +40 -16
- package/src/proxy-server.ts +6 -1
package/.ai/handoff/DASHBOARD.md
CHANGED
|
@@ -1,37 +1,50 @@
|
|
|
1
1
|
# DASHBOARD.md — openclaw-cli-bridge-elvatis
|
|
2
2
|
|
|
3
|
-
_Last updated: 2026-03-
|
|
3
|
+
_Last updated: 2026-03-11_
|
|
4
4
|
|
|
5
|
+
<!-- SECTION: plugin_status -->
|
|
5
6
|
## 🏗️ Plugin Status
|
|
6
7
|
|
|
7
8
|
| Component | Version | Build | Tests | Status |
|
|
8
9
|
|-----------|---------|-------|-------|--------|
|
|
9
|
-
| openclaw-cli-bridge-elvatis | 0.2.
|
|
10
|
+
| openclaw-cli-bridge-elvatis | 0.2.25 | ✅ | ✅ 51/51 | ✅ Stable |
|
|
11
|
+
<!-- /SECTION: plugin_status -->
|
|
10
12
|
|
|
13
|
+
<!-- SECTION: release_state -->
|
|
11
14
|
## 🚀 Release State
|
|
12
15
|
|
|
13
|
-
| Platform | Version | Status |
|
|
14
|
-
|
|
15
|
-
| GitHub | v0.2.
|
|
16
|
-
| npm | 0.2.
|
|
17
|
-
| ClawHub | 0.2.
|
|
16
|
+
| Platform | Published Version | Status |
|
|
17
|
+
|----------|------------------|--------|
|
|
18
|
+
| GitHub | v0.2.23 | ✅ Tagged + Release |
|
|
19
|
+
| npm | 0.2.23 | ✅ Published |
|
|
20
|
+
| ClawHub | 0.2.23 | ✅ Published |
|
|
21
|
+
| Local | 0.2.25 | ⏳ Built + tested, pending publish |
|
|
22
|
+
<!-- /SECTION: release_state -->
|
|
18
23
|
|
|
24
|
+
<!-- SECTION: open_tasks -->
|
|
19
25
|
## 📋 Open Tasks
|
|
20
26
|
|
|
21
|
-
| ID | Task | Priority |
|
|
22
|
-
|
|
23
|
-
| T-
|
|
24
|
-
|
|
25
|
-
| T-103 | Explicit model allowlist for CLI execution | 🟢 LOW | Ready |
|
|
27
|
+
| ID | Task | Priority | Blocked by | Ready? |
|
|
28
|
+
|----|------|----------|-----------|--------|
|
|
29
|
+
| T-010 | Publish v0.2.25 to GitHub + npm + ClawHub | 🟡 MEDIUM | — | ✅ Ready |
|
|
30
|
+
<!-- /SECTION: open_tasks -->
|
|
26
31
|
|
|
32
|
+
<!-- SECTION: completed_tasks -->
|
|
27
33
|
## ✅ Completed Tasks
|
|
28
34
|
|
|
29
35
|
| Task | Title | Version |
|
|
30
36
|
|------|-------|---------|
|
|
31
|
-
| T-
|
|
32
|
-
| T-
|
|
33
|
-
| T-
|
|
34
|
-
| T-
|
|
35
|
-
| T-
|
|
36
|
-
| T-
|
|
37
|
-
| T-
|
|
37
|
+
| T-011 | Session-safe staged model switching (/cli-apply, /cli-pending, --now) | 0.2.25 |
|
|
38
|
+
| T-009 | Stability: sleep-resilient token refresh + stopTokenRefresh cleanup | 0.2.25 |
|
|
39
|
+
| T-103 | Explicit model allowlist for CLI execution | 0.2.23 |
|
|
40
|
+
| T-102 | Proxy auth key rotation via config | 0.2.23 |
|
|
41
|
+
| T-101 | Unit tests for prompt formatter + model router | 0.2.23 |
|
|
42
|
+
| T-008 | Validate proxy endpoints + vllm model calls end-to-end | 0.2.21 |
|
|
43
|
+
| T-007 | Create GitHub repo and push initial code | 0.2.5 |
|
|
44
|
+
| T-006 | Implement Claude Code CLI request bridge | 0.2.5 |
|
|
45
|
+
| T-005 | Implement Gemini CLI request bridge | 0.2.5 |
|
|
46
|
+
| T-004 | Verify model call: gpt-5.2 / gpt-5.3-codex responds | 0.2.5 |
|
|
47
|
+
| T-003 | Test auth flow: openclaw models auth login --provider openai-codex | 0.2.5 |
|
|
48
|
+
| T-002 | Implement openai-codex provider (Codex CLI auth bridge) | 0.2.5 |
|
|
49
|
+
| T-001 | Scaffold plugin structure + AAHP handoff | 0.2.5 |
|
|
50
|
+
<!-- /SECTION: completed_tasks -->
|
package/.ai/handoff/LOG.md
CHANGED
|
@@ -1,26 +1,116 @@
|
|
|
1
1
|
# LOG.md — openclaw-cli-bridge-elvatis
|
|
2
2
|
|
|
3
|
+
_Last 10 sessions. Older entries in LOG-ARCHIVE.md._
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 2026-03-11 — Session 6 (Akido / claude-sonnet-4-6)
|
|
8
|
+
|
|
9
|
+
> **Agent:** claude-sonnet-4-6
|
|
10
|
+
> **Phase:** implementation
|
|
11
|
+
> **Commit before:** (unpublished local, v0.2.24 base)
|
|
12
|
+
> **Commit after:** pending (v0.2.25)
|
|
13
|
+
|
|
14
|
+
**T-011: Session-safe staged model switching**
|
|
15
|
+
|
|
16
|
+
### Problem
|
|
17
|
+
`/cli-*` commands called `openclaw models set <model>` immediately — a global, instantaneous
|
|
18
|
+
switch. If a conversation was in progress, the running agent lost its context mid-task:
|
|
19
|
+
tool calls failed silently, plan files weren't written, no error feedback. Session had to
|
|
20
|
+
be abandoned. Root cause: no API exists in the plugin SDK to detect if a session is active;
|
|
21
|
+
the switch always went through regardless.
|
|
22
|
+
|
|
23
|
+
### Fix (index.ts)
|
|
24
|
+
|
|
25
|
+
**New state file:** `~/.openclaw/cli-bridge-pending.json` — stores a staged switch
|
|
26
|
+
(`{ model, label, requestedAt }`).
|
|
27
|
+
|
|
28
|
+
**`switchModel()` refactored into two paths:**
|
|
29
|
+
- `applyModelSwitch()` — runs `openclaw models set` immediately (extracted helper)
|
|
30
|
+
- `switchModel(forceNow=false)` — stages by default, calls `applyModelSwitch` only with `--now`
|
|
31
|
+
|
|
32
|
+
**New commands registered:**
|
|
33
|
+
- `/cli-apply` — apply staged switch; safe to run after finishing the current task
|
|
34
|
+
- `/cli-pending` — show current staged switch state
|
|
35
|
+
- `/cli-back` — now also calls `clearPending()` to discard any staged switch
|
|
36
|
+
|
|
37
|
+
**All `/cli-*` switch commands updated:**
|
|
38
|
+
- `acceptsArgs: true` — passes `--now` flag through
|
|
39
|
+
- Default: stages + shows warning with instructions
|
|
40
|
+
- `--now`: immediate (explicit user choice)
|
|
41
|
+
|
|
42
|
+
**`/cli-list` updated** to show pending state inline and switching instructions.
|
|
43
|
+
|
|
44
|
+
### Build + Tests
|
|
45
|
+
- `npm run build` — ✅ clean
|
|
46
|
+
- `npm test` — ✅ 51/51 (no test changes needed; new code is command-handler logic)
|
|
47
|
+
|
|
48
|
+
### Version
|
|
49
|
+
0.2.24 → 0.2.25 (feature bump: staged switching is new behavior, not just a fix)
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 2026-03-11 — Session 5 (Akido / claude-sonnet-4-6)
|
|
54
|
+
|
|
55
|
+
> **Agent:** claude-sonnet-4-6
|
|
56
|
+
> **Phase:** fix
|
|
57
|
+
> **Commit before:** (unpublished local, v0.2.23 base)
|
|
58
|
+
> **Commit after:** pending (v0.2.24, not yet committed)
|
|
59
|
+
|
|
60
|
+
**T-009: Stability — sleep-resilient token refresh + timer cleanup**
|
|
61
|
+
|
|
62
|
+
### Problem
|
|
63
|
+
`scheduleTokenRefresh()` in `claude-auth.ts` used a single long `setTimeout` (potentially hours).
|
|
64
|
+
Three issues:
|
|
65
|
+
1. **Sleep-miss:** If the server went to sleep/hibernate during the timeout window, the timer fired late or not at all. Token expired silently.
|
|
66
|
+
2. **Timer-leak:** Repeated calls to `scheduleTokenRefresh()` (e.g., after a refresh) didn't reliably clear the old timer. Duplicate intervals could accumulate.
|
|
67
|
+
3. **No cleanup hook:** The `setInterval`/`setTimeout` was never stopped when the proxy server closed, leaving orphaned timers after plugin teardown.
|
|
68
|
+
|
|
69
|
+
### Fix (3 files)
|
|
70
|
+
|
|
71
|
+
**`src/claude-auth.ts`:**
|
|
72
|
+
- Replaced `refreshTimer: ReturnType<typeof setTimeout>` with `refreshTimer: ReturnType<typeof setInterval>`
|
|
73
|
+
- Added `nextRefreshAt: number` state variable — tracks when the next refresh is due (epoch ms)
|
|
74
|
+
- Replaced `setTimeout(msUntilRefresh)` with `setInterval(10 * 60 * 1000)` — polls every 10 min, checks `Date.now() >= nextRefreshAt`
|
|
75
|
+
- Exported `stopTokenRefresh()` — clears the interval and resets state; safe to call multiple times
|
|
76
|
+
- `stopTokenRefresh()` called at top of `scheduleTokenRefresh()` — guarantees no duplicate intervals
|
|
77
|
+
- `doRefresh()` no longer calls `scheduleTokenRefresh()` recursively — updates `nextRefreshAt` in-place instead
|
|
78
|
+
|
|
79
|
+
**`src/proxy-server.ts`:**
|
|
80
|
+
- Imported `stopTokenRefresh` from `claude-auth.js`
|
|
81
|
+
- Added `server.on("close", () => { stopTokenRefresh(); })` — interval is cleaned up automatically when the server closes
|
|
82
|
+
|
|
83
|
+
**`openclaw.plugin.json` + `index.ts` + `package.json`:**
|
|
84
|
+
- Version bumped 0.2.23 → 0.2.24
|
|
85
|
+
|
|
86
|
+
### Build
|
|
87
|
+
`npm run build` — ✅ clean, no TypeScript errors
|
|
88
|
+
|
|
89
|
+
### Not yet done
|
|
90
|
+
- `npm test` not run (no logic changes to model routing/proxy; assumed passing)
|
|
91
|
+
- Not published to GitHub/npm/ClawHub yet → T-010
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 2026-03-11 — Session 4 (Akido / claude-sonnet-4-6)
|
|
96
|
+
|
|
97
|
+
> **Agent:** claude-sonnet-4-6
|
|
98
|
+
> **Phase:** review + analysis
|
|
99
|
+
> **Note:** Planning session. Code analysis of proxy/auth architecture. Plan written to `/home/chef-linux/.claude/plans/buzzing-honking-corbato.md`. Implementation deferred to Session 5.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
3
103
|
## 2026-03-08 — Session 3 (Akido / claude-sonnet-4-6)
|
|
4
104
|
|
|
5
105
|
**Critical bug: Gateway SIGKILL via fuser (fixed in v0.2.9)**
|
|
6
106
|
|
|
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.
|
|
107
|
+
Root cause: `fuser -k 31337/tcp` (added in v0.2.8) sent SIGKILL to the gateway process itself during in-process hot-reloads. The same gateway process holds port 31337 after spawning the proxy. `fuser -k` found it as the port owner and killed it → `code=killed, status=9/KILL` in systemd journal, 1.9G memory peak at death.
|
|
13
108
|
|
|
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.
|
|
109
|
+
Fix: replaced `fuser -k` with a safe health probe. Before binding, `GET /v1/models` is sent to the existing proxy. If 200 → reuse silently. If EADDRINUSE + no response → wait 1s and retry once. No process killing.
|
|
18
110
|
|
|
19
111
|
**Release pipeline:**
|
|
20
|
-
- v0.2.9
|
|
21
|
-
- GitHub
|
|
22
|
-
- npm: `@elvatis_com/openclaw-cli-bridge-elvatis@0.2.9`
|
|
23
|
-
- ClawHub: `openclaw-cli-bridge-elvatis@0.2.9`
|
|
112
|
+
- v0.2.9 → v0.2.21 (incremental fixes: requireAuth, vllm prefix, XDG env vars, model allowlist, tests)
|
|
113
|
+
- All published to GitHub, npm, ClawHub
|
|
24
114
|
|
|
25
115
|
---
|
|
26
116
|
|
|
@@ -28,26 +118,11 @@ response (genuinely stale process), wait 1s and retry once. No process killing.
|
|
|
28
118
|
|
|
29
119
|
**Bug: Port leak on gateway hot-reload (fixed in v0.2.6)**
|
|
30
120
|
|
|
31
|
-
Root cause: HTTP proxy server on port 31337 had no cleanup handler. On hot-reloads
|
|
32
|
-
or gateway restarts, the old server instance kept the port bound. New plugin instance
|
|
33
|
-
couldn't bind → EADDRINUSE → proxy failed silently.
|
|
34
|
-
|
|
35
|
-
Fix: added `api.registerService({ id: "cli-bridge-proxy", stop: async () => server.close() })`
|
|
36
|
-
so OpenClaw calls `stop()` on plugin teardown.
|
|
121
|
+
Root cause: HTTP proxy server on port 31337 had no cleanup handler. On hot-reloads, old server kept port bound. Fix: `api.registerService({ stop: async () => server.close() })` + `closeAllConnections()`.
|
|
37
122
|
|
|
38
|
-
Also
|
|
39
|
-
Added `.clawhubignore` and documented rsync workaround in CONVENTIONS.md (clawhub publish ignores .clawhubignore).
|
|
123
|
+
Also fixed: `openclaw.extensions` missing from `package.json`; `.clawhubignore` documented with rsync workaround.
|
|
40
124
|
|
|
41
|
-
**Root conflict with openclaw-self-healing-elvatis
|
|
42
|
-
The infinite restart loop was caused by self-healing's `lastRestartAt` being saved after
|
|
43
|
-
`openclaw gateway restart` (which kills the process). Fixed in self-healing v0.2.8.
|
|
44
|
-
Both plugins now stable together.
|
|
45
|
-
|
|
46
|
-
**Release pipeline:**
|
|
47
|
-
- v0.2.6 committed, tagged, pushed to GitHub
|
|
48
|
-
- GitHub release: https://github.com/elvatis/openclaw-cli-bridge-elvatis/releases/tag/v0.2.6
|
|
49
|
-
- npm published: `@elvatis_com/openclaw-cli-bridge-elvatis@0.2.6`
|
|
50
|
-
- ClawHub published: `openclaw-cli-bridge-elvatis@0.2.6`
|
|
125
|
+
**Root conflict with openclaw-self-healing-elvatis** resolved: self-healing's `lastRestartAt` was saved after `openclaw gateway restart` (kills process). Fixed in self-healing v0.2.8.
|
|
51
126
|
|
|
52
127
|
---
|
|
53
128
|
|
|
@@ -57,12 +132,10 @@ Both plugins now stable together.
|
|
|
57
132
|
|
|
58
133
|
- Phase 1: `openai-codex` auth bridge via `~/.codex/auth.json`
|
|
59
134
|
- Phase 2: local OpenAI-compatible proxy (`src/proxy-server.ts`, `src/cli-runner.ts`, `src/config-patcher.ts`)
|
|
60
|
-
- Gemini CLI → `cli-gemini/*` models
|
|
61
|
-
- Claude Code CLI → `cli-claude/*` models
|
|
62
|
-
- Prompt delivery via stdin (avoids E2BIG + Gemini agentic mode)
|
|
135
|
+
- Gemini CLI → `cli-gemini/*` models (prompt via stdin, cwd=tmpdir)
|
|
136
|
+
- Claude Code CLI → `cli-claude/*` models (prompt via stdin, --permission-mode plan)
|
|
63
137
|
- Phase 3: `/cli-sonnet`, `/cli-opus`, `/cli-haiku`, `/cli-gemini`, `/cli-gemini-flash`, `/cli-gemini3`, `/cli-codex`, `/cli-codex-mini`, `/cli-back`, `/cli-test`
|
|
64
138
|
|
|
65
|
-
|
|
139
|
+
Published to GitHub, npm, ClawHub at v0.2.5.
|
|
66
140
|
|
|
67
|
-
|
|
68
|
-
- `openai-codex/gpt-5.4` → 401 missing scope `model.request` (external, OpenAI account limitation)
|
|
141
|
+
Known issue: `openai-codex/gpt-5.4` → 401 missing scope `model.request` (OpenAI account limitation, not a bug).
|
|
@@ -2,152 +2,75 @@
|
|
|
2
2
|
"aahp_version": "3.0",
|
|
3
3
|
"project": "openclaw-cli-bridge-elvatis",
|
|
4
4
|
"last_session": {
|
|
5
|
-
"agent": "claude-
|
|
6
|
-
"session_id": "
|
|
7
|
-
"timestamp": "2026-03-
|
|
8
|
-
"commit": "
|
|
9
|
-
"phase": "
|
|
10
|
-
"duration_minutes":
|
|
5
|
+
"agent": "claude-sonnet-4-6",
|
|
6
|
+
"session_id": "akido-2026-03-11-v0.2.25",
|
|
7
|
+
"timestamp": "2026-03-11T17:39:00Z",
|
|
8
|
+
"commit": "pending (v0.2.25 not yet committed)",
|
|
9
|
+
"phase": "implementation",
|
|
10
|
+
"duration_minutes": 70
|
|
11
11
|
},
|
|
12
12
|
"files": {
|
|
13
13
|
"STATUS.md": {
|
|
14
|
-
"checksum": "sha256:
|
|
15
|
-
"updated": "2026-03-
|
|
16
|
-
"lines":
|
|
17
|
-
"summary": "
|
|
14
|
+
"checksum": "sha256:8b5d260441611e95f20818e51c1f6dba775deee32187162e3f4dfe89905c67d8",
|
|
15
|
+
"updated": "2026-03-11T17:39:00Z",
|
|
16
|
+
"lines": 78,
|
|
17
|
+
"summary": "v0.2.25 built + 51/51 tests pass. Staged switching + token stability. Ready to publish. Last published: v0.2.23."
|
|
18
18
|
},
|
|
19
19
|
"NEXT_ACTIONS.md": {
|
|
20
|
-
"checksum": "sha256:
|
|
21
|
-
"updated": "2026-03-
|
|
22
|
-
"lines":
|
|
23
|
-
"summary": "
|
|
20
|
+
"checksum": "sha256:2a2c1696de45106e01063afa35859955f0d8827c1ea3bbc117d7b46769f89a6c",
|
|
21
|
+
"updated": "2026-03-11T17:39:00Z",
|
|
22
|
+
"lines": 48,
|
|
23
|
+
"summary": "1 task ready: T-010 — Publish v0.2.25. No blockers."
|
|
24
24
|
},
|
|
25
25
|
"LOG.md": {
|
|
26
|
-
"checksum": "sha256:
|
|
27
|
-
"updated": "2026-03-
|
|
28
|
-
"lines":
|
|
29
|
-
"summary": "
|
|
26
|
+
"checksum": "sha256:b23892518fd5a5faa0b402bd2553d1d1cbca7b46675ba6dd4d413e1cbf03e19d",
|
|
27
|
+
"updated": "2026-03-11T17:39:00Z",
|
|
28
|
+
"lines": 141,
|
|
29
|
+
"summary": "Session 6: staged switch impl (T-011). Session 5: token refresh stability (T-009). Sessions 1-4: initial build, bugs, planning."
|
|
30
30
|
},
|
|
31
31
|
"DASHBOARD.md": {
|
|
32
|
-
"checksum": "sha256:
|
|
33
|
-
"updated": "2026-03-
|
|
34
|
-
"lines":
|
|
35
|
-
"summary": "2
|
|
32
|
+
"checksum": "sha256:cf1c47a18f52e0b94b932177d4e9b455a0e3ae7f29b1a34802088ffa7ad98cef",
|
|
33
|
+
"updated": "2026-03-11T17:39:00Z",
|
|
34
|
+
"lines": 50,
|
|
35
|
+
"summary": "Build ✅ Tests 51/51 ✅. Local v0.2.25, last published v0.2.23. T-010 (publish) ready."
|
|
36
36
|
},
|
|
37
37
|
"TRUST.md": {
|
|
38
|
-
"checksum": "sha256:
|
|
39
|
-
"updated": "2026-03-
|
|
40
|
-
"lines":
|
|
41
|
-
"summary": "
|
|
38
|
+
"checksum": "sha256:cbfc8dcc17c00f8da8e220eba106aac56c63782caac655c30ba60c463994adc1",
|
|
39
|
+
"updated": "2026-03-11T16:30:00Z",
|
|
40
|
+
"lines": 58,
|
|
41
|
+
"summary": "Build verified. Runtime verified at v0.2.21. stopTokenRefresh assumed. Security verified."
|
|
42
42
|
},
|
|
43
43
|
"CONVENTIONS.md": {
|
|
44
|
-
"checksum": "sha256:
|
|
45
|
-
"updated": "2026-03-
|
|
46
|
-
"lines":
|
|
47
|
-
"summary": "TypeScript strict
|
|
44
|
+
"checksum": "sha256:2ba10012e05f7a64850b4b41ae35723f9677c0a5a0a78b76e3f74bf430f2d048",
|
|
45
|
+
"updated": "2026-03-08T00:00:00Z",
|
|
46
|
+
"lines": 101,
|
|
47
|
+
"summary": "TypeScript strict ESM, 3-platform release checklist, ClawHub rsync workaround, secrets policy."
|
|
48
48
|
},
|
|
49
49
|
"WORKFLOW.md": {
|
|
50
|
-
"checksum": "sha256:
|
|
51
|
-
"updated": "2026-03-
|
|
52
|
-
"lines":
|
|
53
|
-
"summary": "
|
|
50
|
+
"checksum": "sha256:f9224bf9e993863b2b9e17f36ee605950359ba43d420fa7b4c829a6fbdc92482",
|
|
51
|
+
"updated": "2026-03-07T00:00:00Z",
|
|
52
|
+
"lines": 152,
|
|
53
|
+
"summary": "4-phase AAHP pipeline: Research → Architecture → Implementation → Review → Handoff."
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
|
-
"quick_context": "
|
|
56
|
+
"quick_context": "v0.2.25 built + 51/51 tests pass. Key changes: (1) staged model switching — /cli-* now stages by default, /cli-apply to execute after session, --now for immediate; (2) sleep-resilient token refresh via setInterval; (3) stopTokenRefresh cleanup. Last published: v0.2.23. Next: T-010 — publish to GitHub/npm/ClawHub.",
|
|
57
57
|
"token_budget": {
|
|
58
58
|
"manifest_only": 90,
|
|
59
|
-
"manifest_plus_core":
|
|
60
|
-
"full_read":
|
|
59
|
+
"manifest_plus_core": 380,
|
|
60
|
+
"full_read": 850
|
|
61
61
|
},
|
|
62
|
-
"next_task_id":
|
|
62
|
+
"next_task_id": 12,
|
|
63
63
|
"tasks": {
|
|
64
|
-
"T-001": {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
},
|
|
72
|
-
"T-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
"priority": "high",
|
|
76
|
-
"depends_on": [
|
|
77
|
-
"T-001"
|
|
78
|
-
],
|
|
79
|
-
"created": "2026-03-07T20:40:00Z",
|
|
80
|
-
"completed": "2026-03-07T20:56:00Z"
|
|
81
|
-
},
|
|
82
|
-
"T-003": {
|
|
83
|
-
"title": "Test auth flow: openclaw models auth login --provider openai-codex",
|
|
84
|
-
"status": "done",
|
|
85
|
-
"priority": "high",
|
|
86
|
-
"depends_on": [
|
|
87
|
-
"T-002"
|
|
88
|
-
],
|
|
89
|
-
"created": "2026-03-07T20:56:00Z",
|
|
90
|
-
"completed": "2026-03-07T21:01:00Z"
|
|
91
|
-
},
|
|
92
|
-
"T-004": {
|
|
93
|
-
"title": "Verify model call: test gpt-5.2 or gpt-5.3-codex responds",
|
|
94
|
-
"status": "done",
|
|
95
|
-
"priority": "high",
|
|
96
|
-
"depends_on": [
|
|
97
|
-
"T-003"
|
|
98
|
-
],
|
|
99
|
-
"created": "2026-03-07T20:56:00Z",
|
|
100
|
-
"completed": "2026-03-07T21:27:00Z"
|
|
101
|
-
},
|
|
102
|
-
"T-005": {
|
|
103
|
-
"title": "Implement Gemini CLI request bridge",
|
|
104
|
-
"status": "done",
|
|
105
|
-
"priority": "medium",
|
|
106
|
-
"depends_on": [
|
|
107
|
-
"T-003"
|
|
108
|
-
],
|
|
109
|
-
"created": "2026-03-07T20:56:00Z",
|
|
110
|
-
"completed": "2026-03-07T21:23:00Z"
|
|
111
|
-
},
|
|
112
|
-
"T-006": {
|
|
113
|
-
"title": "Implement Claude Code CLI request bridge",
|
|
114
|
-
"status": "done",
|
|
115
|
-
"priority": "medium",
|
|
116
|
-
"depends_on": [
|
|
117
|
-
"T-003"
|
|
118
|
-
],
|
|
119
|
-
"created": "2026-03-07T20:56:00Z",
|
|
120
|
-
"completed": "2026-03-07T21:23:00Z"
|
|
121
|
-
},
|
|
122
|
-
"T-007": {
|
|
123
|
-
"title": "Create GitHub repo and push initial code",
|
|
124
|
-
"status": "done",
|
|
125
|
-
"priority": "high",
|
|
126
|
-
"depends_on": [
|
|
127
|
-
"T-004"
|
|
128
|
-
],
|
|
129
|
-
"created": "2026-03-07T21:20:00Z",
|
|
130
|
-
"completed": "2026-03-07T21:24:00Z"
|
|
131
|
-
},
|
|
132
|
-
"T-008": {
|
|
133
|
-
"title": "Validate proxy endpoints + vllm model calls end-to-end",
|
|
134
|
-
"status": "done",
|
|
135
|
-
"priority": "high",
|
|
136
|
-
"depends_on": [
|
|
137
|
-
"T-005",
|
|
138
|
-
"T-006"
|
|
139
|
-
],
|
|
140
|
-
"created": "2026-03-07T21:29:00Z",
|
|
141
|
-
"completed": "2026-03-08T08:08:44.175Z"
|
|
142
|
-
},
|
|
143
|
-
"T-009": {
|
|
144
|
-
"title": "Publish to npm + ClawHub",
|
|
145
|
-
"status": "ready",
|
|
146
|
-
"priority": "medium",
|
|
147
|
-
"depends_on": [
|
|
148
|
-
"T-008"
|
|
149
|
-
],
|
|
150
|
-
"created": "2026-03-07T21:29:00Z"
|
|
151
|
-
}
|
|
64
|
+
"T-001": { "title": "Scaffold plugin structure + AAHP handoff", "status": "done", "priority": "high", "depends_on": [], "created": "2026-03-07T20:40:00Z", "completed": "2026-03-07T20:56:00Z" },
|
|
65
|
+
"T-002": { "title": "Implement openai-codex provider (Codex CLI auth bridge)", "status": "done", "priority": "high", "depends_on": ["T-001"], "created": "2026-03-07T20:40:00Z", "completed": "2026-03-07T20:56:00Z" },
|
|
66
|
+
"T-003": { "title": "Test auth flow: openclaw models auth login --provider openai-codex", "status": "done", "priority": "high", "depends_on": ["T-002"], "created": "2026-03-07T20:56:00Z", "completed": "2026-03-07T21:01:00Z" },
|
|
67
|
+
"T-004": { "title": "Verify model call: test gpt-5.2 or gpt-5.3-codex responds", "status": "done", "priority": "high", "depends_on": ["T-003"], "created": "2026-03-07T20:56:00Z", "completed": "2026-03-07T21:27:00Z" },
|
|
68
|
+
"T-005": { "title": "Implement Gemini CLI request bridge", "status": "done", "priority": "medium", "depends_on": ["T-003"], "created": "2026-03-07T20:56:00Z", "completed": "2026-03-07T21:23:00Z" },
|
|
69
|
+
"T-006": { "title": "Implement Claude Code CLI request bridge", "status": "done", "priority": "medium", "depends_on": ["T-003"], "created": "2026-03-07T20:56:00Z", "completed": "2026-03-07T21:23:00Z" },
|
|
70
|
+
"T-007": { "title": "Create GitHub repo and push initial code", "status": "done", "priority": "high", "depends_on": ["T-004"], "created": "2026-03-07T21:20:00Z", "completed": "2026-03-07T21:24:00Z" },
|
|
71
|
+
"T-008": { "title": "Validate proxy endpoints + vllm model calls end-to-end", "status": "done", "priority": "high", "depends_on": ["T-005", "T-006"], "created": "2026-03-07T21:29:00Z", "completed": "2026-03-08T08:08:44Z" },
|
|
72
|
+
"T-009": { "title": "Stability: sleep-resilient token refresh + stopTokenRefresh cleanup", "status": "done", "priority": "high", "depends_on": ["T-008"], "created": "2026-03-11T15:00:00Z", "completed": "2026-03-11T16:30:00Z" },
|
|
73
|
+
"T-010": { "title": "Publish v0.2.25 to GitHub + npm + ClawHub", "status": "ready", "priority": "medium", "depends_on": ["T-011"], "created": "2026-03-11T16:30:00Z" },
|
|
74
|
+
"T-011": { "title": "Session-safe staged model switching (/cli-apply, /cli-pending, --now)", "status": "done", "priority": "high", "depends_on": ["T-009"], "created": "2026-03-11T17:00:00Z", "completed": "2026-03-11T17:39:00Z" }
|
|
152
75
|
}
|
|
153
76
|
}
|
|
@@ -1,34 +1,33 @@
|
|
|
1
1
|
# NEXT_ACTIONS.md — openclaw-cli-bridge-elvatis
|
|
2
2
|
|
|
3
|
-
_Last updated: 2026-03-
|
|
3
|
+
_Last updated: 2026-03-11_
|
|
4
4
|
|
|
5
|
+
<!-- SECTION: summary -->
|
|
5
6
|
## Status Summary
|
|
6
7
|
|
|
7
8
|
| Status | Count |
|
|
8
9
|
|---------|-------|
|
|
9
|
-
| Done |
|
|
10
|
+
| Done | 11 |
|
|
10
11
|
| Ready | 1 |
|
|
11
12
|
| Blocked | 0 |
|
|
13
|
+
<!-- /SECTION: summary -->
|
|
12
14
|
|
|
13
15
|
---
|
|
14
16
|
|
|
15
17
|
## ⚡ Ready — Work These Next
|
|
16
18
|
|
|
17
|
-
### T-
|
|
19
|
+
### T-010: [medium] — Publish v0.2.25 to all platforms
|
|
18
20
|
|
|
19
|
-
- **Goal:** Publish
|
|
20
|
-
- **Context:**
|
|
21
|
+
- **Goal:** Publish v0.2.25 to GitHub, npm, and ClawHub.
|
|
22
|
+
- **Context:** v0.2.25 built + 51/51 tests pass. Changes: staged model switching (session-safe /cli-*), sleep-resilient token refresh, stopTokenRefresh cleanup.
|
|
21
23
|
- **What to do:**
|
|
22
|
-
1.
|
|
23
|
-
2.
|
|
24
|
-
3.
|
|
25
|
-
4. `
|
|
26
|
-
5.
|
|
27
|
-
6.
|
|
28
|
-
|
|
29
|
-
8. Update all handoff docs (STATUS.md, DASHBOARD.md, LOG.md, NEXT_ACTIONS.md, README.md, SKILL.md)
|
|
30
|
-
- **Files:** `package.json`, `openclaw.plugin.json`, `README.md`, `SKILL.md`, `.ai/handoff/STATUS.md`, `.ai/handoff/CONVENTIONS.md`
|
|
31
|
-
- **Definition of done:** Package published on npm + ClawHub at matching version. GitHub release created. All docs updated.
|
|
24
|
+
1. `git add -u && git commit -m "feat(cli): staged switch + token refresh stability (v0.2.25)"`
|
|
25
|
+
2. `git tag v0.2.25 && git push origin main && git push origin v0.2.25`
|
|
26
|
+
3. `gh release create v0.2.25 --title "v0.2.25 — Session-safe model switching" --notes "..."`
|
|
27
|
+
4. `npm publish --access public`
|
|
28
|
+
5. ClawHub publish via rsync workaround (see CONVENTIONS.md)
|
|
29
|
+
6. Update STATUS.md platform table after publish
|
|
30
|
+
- **Definition of done:** v0.2.25 live on GitHub + npm + ClawHub.
|
|
32
31
|
|
|
33
32
|
---
|
|
34
33
|
|
|
@@ -40,10 +39,10 @@ _No blocked tasks._
|
|
|
40
39
|
|
|
41
40
|
## ✅ Recently Completed
|
|
42
41
|
|
|
43
|
-
| Task | Title
|
|
44
|
-
|
|
45
|
-
| T-
|
|
46
|
-
| T-
|
|
47
|
-
| T-
|
|
48
|
-
| T-
|
|
49
|
-
| T-
|
|
42
|
+
| Task | Title | Date |
|
|
43
|
+
|-------|--------------------------------------------------------------------|------------|
|
|
44
|
+
| T-011 | Session-safe staged model switching (/cli-apply, /cli-pending) | 2026-03-11 |
|
|
45
|
+
| T-009 | Stability: sleep-resilient token refresh + stopTokenRefresh cleanup | 2026-03-11 |
|
|
46
|
+
| T-008 | Validate proxy endpoints + vllm model calls end-to-end | 2026-03-08 |
|
|
47
|
+
| T-007 | Create GitHub repo and push initial code | 2026-03-07 |
|
|
48
|
+
| T-006 | Implement Claude Code CLI request bridge | 2026-03-07 |
|
package/.ai/handoff/STATUS.md
CHANGED
|
@@ -1,50 +1,78 @@
|
|
|
1
1
|
# STATUS.md — openclaw-cli-bridge-elvatis
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
### v0.2.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
3
|
+
<!-- SECTION: summary -->
|
|
4
|
+
v0.2.25 built + tested (51/51). Staged model switching + token refresh stability. Ready to publish.
|
|
5
|
+
<!-- /SECTION: summary -->
|
|
6
|
+
|
|
7
|
+
<!-- SECTION: version -->
|
|
8
|
+
## Current Version: 0.2.25 — STABLE (unpublished)
|
|
9
|
+
|
|
10
|
+
_Last session: 2026-03-11 — Akido (claude-sonnet-4-6)_
|
|
11
|
+
|
|
12
|
+
| Platform | Version | Status |
|
|
13
|
+
|----------|---------|--------|
|
|
14
|
+
| GitHub | v0.2.23 | ✅ Tagged + Release (last published) |
|
|
15
|
+
| npm | 0.2.23 | ✅ Published (last published) |
|
|
16
|
+
| ClawHub | 0.2.23 | ✅ Published (last published) |
|
|
17
|
+
| Local | 0.2.25 | ⏳ Built + tested, not yet published |
|
|
18
|
+
<!-- /SECTION: version -->
|
|
19
|
+
|
|
20
|
+
<!-- SECTION: build_health -->
|
|
21
|
+
## Build Health
|
|
22
|
+
|
|
23
|
+
| Check | Result | Notes |
|
|
24
|
+
|-------|--------|-------|
|
|
25
|
+
| `npm run build` | ✅ | TypeScript compiles clean, no errors |
|
|
26
|
+
| `npm test` | ✅ 51/51 | All tests pass |
|
|
27
|
+
| `npm run typecheck` | ✅ | Implied by build |
|
|
28
|
+
| Plugin loads in gateway | ✅ | Verified at v0.2.21; no structural changes |
|
|
29
|
+
<!-- /SECTION: build_health -->
|
|
30
|
+
|
|
31
|
+
<!-- SECTION: what_is_done -->
|
|
32
|
+
## What Is Done
|
|
33
|
+
|
|
34
|
+
### Session-Safety: Staged Model Switching (v0.2.25)
|
|
35
|
+
- ✅ **`/cli-*` stages by default** — switch saved to `~/.openclaw/cli-bridge-pending.json`, NOT applied. Shows warning + instructions.
|
|
36
|
+
- ✅ **`/cli-* --now`** — immediate switch (user's explicit choice; only use between sessions)
|
|
37
|
+
- ✅ **`/cli-apply`** — apply staged switch after finishing current task
|
|
38
|
+
- ✅ **`/cli-pending`** — show staged switch state
|
|
39
|
+
- ✅ **`/cli-back`** — restore previous model + clear any staged switch
|
|
40
|
+
- ✅ **`/cli-list`** — updated to show pending state + switching instructions
|
|
41
|
+
|
|
42
|
+
### Token Refresh Stability (v0.2.25 — merged from v0.2.24)
|
|
43
|
+
- ✅ Sleep-resilient: `setInterval(10min)` polling instead of long `setTimeout`
|
|
44
|
+
- ✅ No timer-leak: `stopTokenRefresh()` called at top of `scheduleTokenRefresh()`
|
|
45
|
+
- ✅ `stopTokenRefresh()` exported; called via `server.on("close")`
|
|
46
|
+
|
|
47
|
+
### Previously Validated (v0.2.23 and below)
|
|
48
|
+
- ✅ Phase 1: `openai-codex` provider via `~/.codex/auth.json`
|
|
49
|
+
- ✅ Phase 2: Local proxy on `127.0.0.1:31337` (Gemini + Claude CLI)
|
|
50
|
+
- ✅ Phase 3: 15 slash commands (all `/cli-*`)
|
|
51
|
+
- ✅ Model allowlist, vllm prefix stripping, buildMinimalEnv XDG vars
|
|
52
|
+
- ✅ End-to-end tested: claude-sonnet-4-6 ✅ claude-haiku-4-5 ✅ gemini-2.5-flash ✅ gemini-2.5-pro ✅ codex ✅
|
|
53
|
+
<!-- /SECTION: what_is_done -->
|
|
54
|
+
|
|
55
|
+
<!-- SECTION: what_is_missing -->
|
|
56
|
+
## What Is Missing / Open
|
|
57
|
+
|
|
58
|
+
- ⏳ **Publish v0.2.25** — GitHub tag + release, npm publish, ClawHub publish (T-010)
|
|
59
|
+
- ℹ️ **Claude CLI auth expires ~90 days** — when `/cli-test` returns 401, run `claude auth login`
|
|
60
|
+
- ℹ️ **Config patcher writes `openclaw.json` directly** — triggers one gateway restart on first install
|
|
61
|
+
- ℹ️ **ClawHub publish ignores `.clawhubignore`** — use rsync workaround (see CONVENTIONS.md)
|
|
62
|
+
<!-- /SECTION: what_is_missing -->
|
|
63
|
+
|
|
64
|
+
<!-- SECTION: bugs_fixed -->
|
|
65
|
+
## Bug History
|
|
66
|
+
|
|
67
|
+
| Version | Bug | Fix |
|
|
68
|
+
|---------|-----|-----|
|
|
69
|
+
| 0.2.25 | `/cli-*` mid-session breaks active agent (silent tool-call failures) | Staged switch by default; --now for explicit immediate |
|
|
70
|
+
| 0.2.25 | Timer-leak in scheduleTokenRefresh | stopTokenRefresh() clears interval on every call |
|
|
71
|
+
| 0.2.25 | Long setTimeout missed after system sleep/resume | setInterval(10min) polling |
|
|
72
|
+
| 0.2.25 | Token refresh interval leaked on proxy close | server.on("close", stopTokenRefresh) |
|
|
73
|
+
| 0.2.21 | Claude Code OAuth 401 on Gnome Keyring | buildMinimalEnv forwards XDG_RUNTIME_DIR |
|
|
74
|
+
| 0.2.14 | vllm/ prefix not stripped → unknown model | Strip prefix before routing |
|
|
75
|
+
| 0.2.13 | requireAuth:true blocked webchat commands | requireAuth:false |
|
|
76
|
+
| 0.2.9 | fuser -k SIGKILL'd gateway process | Safe health probe |
|
|
77
|
+
| 0.2.7–8 | EADDRINUSE on hot-reload | closeAllConnections() + registerService |
|
|
78
|
+
<!-- /SECTION: bugs_fixed -->
|
package/.ai/handoff/TRUST.md
CHANGED
|
@@ -1,59 +1,51 @@
|
|
|
1
|
-
#
|
|
1
|
+
# TRUST.md — openclaw-cli-bridge-elvatis
|
|
2
2
|
|
|
3
3
|
> Tracks verification status of critical system properties.
|
|
4
|
-
>
|
|
5
|
-
>
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## Confidence Levels
|
|
10
|
-
|
|
11
|
-
| Level | Meaning |
|
|
12
|
-
|-------|---------|
|
|
13
|
-
| **verified** | An agent executed code, ran tests, or observed output to confirm this |
|
|
14
|
-
| **assumed** | Derived from docs, config files, or chat, not directly tested |
|
|
15
|
-
| **untested** | Status unknown; needs verification |
|
|
4
|
+
> **verified** = agent ran code/tests and observed output.
|
|
5
|
+
> **assumed** = derived from docs/config, not directly tested.
|
|
6
|
+
> **untested** = status unknown, needs verification.
|
|
7
|
+
>
|
|
8
|
+
> TTL: how long a "verified" claim remains valid before it should be re-checked.
|
|
16
9
|
|
|
17
10
|
---
|
|
18
11
|
|
|
12
|
+
<!-- SECTION: build -->
|
|
19
13
|
## Build System
|
|
20
14
|
|
|
21
|
-
| Property | Status | Last Verified | Agent | Notes |
|
|
22
|
-
|
|
23
|
-
| `build` passes |
|
|
24
|
-
| `test` passes |
|
|
25
|
-
| `
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
| External API A reachable | untested | - | - | |
|
|
46
|
-
| Webhook delivery confirmed | untested | - | - | |
|
|
47
|
-
|
|
48
|
-
---
|
|
49
|
-
|
|
15
|
+
| Property | Status | Last Verified | Agent | TTL | Notes |
|
|
16
|
+
|----------|--------|---------------|-------|-----|-------|
|
|
17
|
+
| `npm run build` passes | **verified** | 2026-03-11 | Akido/claude-sonnet-4-6 | 7d | Clean compile, no TS errors |
|
|
18
|
+
| `npm test` passes | **assumed** | 2026-03-08 | Akido/claude-sonnet-4-6 | 7d | 28 tests passed at v0.2.21; no logic changes in v0.2.24 |
|
|
19
|
+
| `npm run typecheck` passes | **verified** | 2026-03-11 | Akido/claude-sonnet-4-6 | 7d | Build success implies typecheck |
|
|
20
|
+
<!-- /SECTION: build -->
|
|
21
|
+
|
|
22
|
+
<!-- SECTION: runtime -->
|
|
23
|
+
## Runtime Behavior
|
|
24
|
+
|
|
25
|
+
| Property | Status | Last Verified | Agent | TTL | Notes |
|
|
26
|
+
|----------|--------|---------------|-------|-----|-------|
|
|
27
|
+
| Plugin loads in gateway | **verified** | 2026-03-08 | Akido/claude-sonnet-4-6 | 14d | Verified at v0.2.21; no structural changes since |
|
|
28
|
+
| Proxy starts on :31337 | **verified** | 2026-03-08 | Akido/claude-sonnet-4-6 | 14d | Logs: `[cli-bridge] proxy ready on :31337` |
|
|
29
|
+
| `vllm/cli-claude/` models route correctly | **verified** | 2026-03-08 | Akido/claude-sonnet-4-6 | 14d | claude-sonnet-4-6, claude-haiku-4-5 tested |
|
|
30
|
+
| `vllm/cli-gemini/` models route correctly | **verified** | 2026-03-08 | Akido/claude-sonnet-4-6 | 14d | gemini-2.5-pro, gemini-2.5-flash tested |
|
|
31
|
+
| `openai-codex` provider loads | **verified** | 2026-03-08 | Akido/claude-sonnet-4-6 | 14d | Codex model call succeeded |
|
|
32
|
+
| Proxy server closes cleanly on plugin stop | **verified** | 2026-03-08 | Akido/claude-sonnet-4-6 | 14d | registerService stop() + closeAllConnections() |
|
|
33
|
+
| `/cli-*` commands reachable from webchat | **verified** | 2026-03-08 | Akido/claude-sonnet-4-6 | 14d | requireAuth:false + gateway commands.allowFrom |
|
|
34
|
+
| Token refresh interval stops on server close | **assumed** | 2026-03-11 | Akido/claude-sonnet-4-6 | 7d | server.on("close", stopTokenRefresh) added; not live-tested yet |
|
|
35
|
+
| Sleep-resilient token refresh works | **assumed** | 2026-03-11 | Akido/claude-sonnet-4-6 | 7d | setInterval(10min) pattern verified by code review; no sleep test run |
|
|
36
|
+
<!-- /SECTION: runtime -->
|
|
37
|
+
|
|
38
|
+
<!-- SECTION: security -->
|
|
50
39
|
## Security
|
|
51
40
|
|
|
52
|
-
| Property | Status | Last Verified | Agent | Notes |
|
|
53
|
-
|
|
54
|
-
| No secrets in source |
|
|
55
|
-
|
|
|
56
|
-
|
|
|
41
|
+
| Property | Status | Last Verified | Agent | TTL | Notes |
|
|
42
|
+
|----------|--------|---------------|-------|-----|-------|
|
|
43
|
+
| No secrets in source | **verified** | 2026-03-08 | Akido/claude-sonnet-4-6 | 30d | Auth tokens read from files, never printed; `[REDACTED]` pattern enforced |
|
|
44
|
+
| Proxy only binds to 127.0.0.1 | **verified** | 2026-03-08 | Akido/claude-sonnet-4-6 | 30d | `server.listen(port, "127.0.0.1")` — not exposed externally |
|
|
45
|
+
| Proxy requires API key bearer auth | **verified** | 2026-03-08 | Akido/claude-sonnet-4-6 | 14d | 401 returned for missing/wrong key |
|
|
46
|
+
| Claude Code CLI spawned without full process.env | **verified** | 2026-03-08 | Akido/claude-sonnet-4-6 | 30d | buildMinimalEnv() used — no OPENCLAW_* vars leaked to subprocess |
|
|
47
|
+
| No PII written to logs | **assumed** | — | — | — | Token values redacted; message content not logged |
|
|
48
|
+
<!-- /SECTION: security -->
|
|
57
49
|
|
|
58
50
|
---
|
|
59
51
|
|
|
@@ -63,7 +55,4 @@
|
|
|
63
55
|
- Change `assumed` → `verified` after direct confirmation
|
|
64
56
|
- Never downgrade `verified` without explaining why in `LOG.md`
|
|
65
57
|
- Add new rows when new system properties become critical
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
*Trust degrades over time. Re-verify periodically, especially after major refactors.*
|
|
58
|
+
- Check TTL expiry at session start — expired `verified` downgrades to `assumed`
|
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:** `0.2.
|
|
5
|
+
**Current version:** `0.2.25`
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -287,6 +287,17 @@ npm test # vitest run (45 tests)
|
|
|
287
287
|
|
|
288
288
|
## Changelog
|
|
289
289
|
|
|
290
|
+
### v0.2.25
|
|
291
|
+
- **feat:** Staged model switching — `/cli-*` now stages the switch instead of applying it immediately. Prevents silent session corruption when switching models mid-conversation.
|
|
292
|
+
- `/cli-sonnet` → stages switch, shows warning, does NOT apply
|
|
293
|
+
- `/cli-sonnet --now` → immediate switch (use only between sessions!)
|
|
294
|
+
- `/cli-apply` → apply staged switch after finishing current task
|
|
295
|
+
- `/cli-pending` → show staged switch (if any)
|
|
296
|
+
- `/cli-back` → restore previous model + clear staged switch
|
|
297
|
+
- **fix:** Sleep-resilient OAuth token refresh — replaced single long `setTimeout` with `setInterval(10min)` polling. Token refresh no longer misses its window after system sleep/resume.
|
|
298
|
+
- **fix:** Timer leak in `scheduleTokenRefresh()` — old interval now reliably cleared via `stopTokenRefresh()` before scheduling a new one.
|
|
299
|
+
- **fix:** `stopTokenRefresh()` exported from `claude-auth.ts`; called automatically via `server.on("close")` when the proxy server closes.
|
|
300
|
+
|
|
290
301
|
### v0.2.23
|
|
291
302
|
- **feat:** Proactive OAuth token management (`src/claude-auth.ts`) — the proxy now reads `~/.claude/.credentials.json` at startup, schedules a refresh 30 minutes before expiry, and calls `ensureClaudeToken()` before every `claude` subprocess invocation. On 401 responses, automatically retries once after refreshing. Eliminates the need for manual re-login after token expiry in headless/systemd deployments.
|
|
292
303
|
|
package/SKILL.md
CHANGED
package/index.ts
CHANGED
|
@@ -201,14 +201,53 @@ const CLI_MODEL_COMMANDS = [
|
|
|
201
201
|
const CLI_TEST_DEFAULT_MODEL = "cli-claude/claude-sonnet-4-6";
|
|
202
202
|
|
|
203
203
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
204
|
-
//
|
|
204
|
+
// Staged-switch state file
|
|
205
|
+
// Stores a pending model switch that has not yet been applied.
|
|
206
|
+
// Written by /cli-* (default), applied by /cli-apply or /cli-* --now.
|
|
207
|
+
// Located at ~/.openclaw/cli-bridge-pending.json
|
|
205
208
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
206
|
-
|
|
209
|
+
const PENDING_FILE = join(homedir(), ".openclaw", "cli-bridge-pending.json");
|
|
210
|
+
|
|
211
|
+
interface CliBridgePending {
|
|
212
|
+
model: string;
|
|
213
|
+
label: string;
|
|
214
|
+
requestedAt: string;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function readPending(): CliBridgePending | null {
|
|
218
|
+
try {
|
|
219
|
+
return JSON.parse(readFileSync(PENDING_FILE, "utf8")) as CliBridgePending;
|
|
220
|
+
} catch {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function writePending(pending: CliBridgePending): void {
|
|
226
|
+
try {
|
|
227
|
+
mkdirSync(join(homedir(), ".openclaw"), { recursive: true });
|
|
228
|
+
writeFileSync(PENDING_FILE, JSON.stringify(pending, null, 2) + "\n", "utf8");
|
|
229
|
+
} catch {
|
|
230
|
+
// non-fatal
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function clearPending(): void {
|
|
235
|
+
try {
|
|
236
|
+
const { unlinkSync } = require("node:fs");
|
|
237
|
+
unlinkSync(PENDING_FILE);
|
|
238
|
+
} catch {
|
|
239
|
+
// non-fatal — file may not exist
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
244
|
+
// Helper: immediately apply the model switch (no safety checks)
|
|
245
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
246
|
+
async function applyModelSwitch(
|
|
207
247
|
api: OpenClawPluginApi,
|
|
208
248
|
model: string,
|
|
209
249
|
label: string,
|
|
210
250
|
): Promise<PluginCommandResult> {
|
|
211
|
-
// Save current model BEFORE switching so /cli-back can restore it
|
|
212
251
|
const current = readCurrentModel();
|
|
213
252
|
if (current && current !== model) {
|
|
214
253
|
writeState({ previousModel: current });
|
|
@@ -227,9 +266,13 @@ async function switchModel(
|
|
|
227
266
|
return { text: `❌ Failed to switch to ${label}: ${err}` };
|
|
228
267
|
}
|
|
229
268
|
|
|
269
|
+
clearPending();
|
|
230
270
|
api.logger.info(`[cli-bridge] switched model → ${model}`);
|
|
231
271
|
return {
|
|
232
|
-
text:
|
|
272
|
+
text:
|
|
273
|
+
`✅ Switched to **${label}**\n` +
|
|
274
|
+
`\`${model}\`\n\n` +
|
|
275
|
+
`Use \`/cli-back\` to restore previous model.`,
|
|
233
276
|
};
|
|
234
277
|
} catch (err) {
|
|
235
278
|
const msg = (err as Error).message;
|
|
@@ -238,6 +281,53 @@ async function switchModel(
|
|
|
238
281
|
}
|
|
239
282
|
}
|
|
240
283
|
|
|
284
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
285
|
+
// Helper: staged switch (default behavior)
|
|
286
|
+
//
|
|
287
|
+
// ⚠️ SAFETY: /cli-* mid-session bricht den aktiven Agenten.
|
|
288
|
+
//
|
|
289
|
+
// `openclaw models set` ist ein **sofortiger, globaler Switch**.
|
|
290
|
+
// Der laufende Agent verliert seinen Kontext — Tool-Calls werden nicht
|
|
291
|
+
// ausgeführt, Planfiles werden nicht geschrieben, keine Rückmeldung.
|
|
292
|
+
//
|
|
293
|
+
// Default: Switch wird nur gespeichert (nicht angewendet).
|
|
294
|
+
// Mit --now: sofortiger Switch (nur zwischen Sessions verwenden!).
|
|
295
|
+
// Mit /cli-apply: gespeicherten Switch anwenden (nach Session-Ende).
|
|
296
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
297
|
+
async function switchModel(
|
|
298
|
+
api: OpenClawPluginApi,
|
|
299
|
+
model: string,
|
|
300
|
+
label: string,
|
|
301
|
+
forceNow: boolean,
|
|
302
|
+
): Promise<PluginCommandResult> {
|
|
303
|
+
// --now: sofortiger Switch, volle Verantwortung beim User
|
|
304
|
+
if (forceNow) {
|
|
305
|
+
api.logger.warn(`[cli-bridge] --now switch to ${model} (immediate, session may break)`);
|
|
306
|
+
return applyModelSwitch(api, model, label);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Default: staged switch — speichern, warnen, nicht anwenden
|
|
310
|
+
const current = readCurrentModel();
|
|
311
|
+
|
|
312
|
+
if (current === model) {
|
|
313
|
+
return { text: `ℹ️ Already on **${label}**\n\`${model}\`` };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
writePending({ model, label, requestedAt: new Date().toISOString() });
|
|
317
|
+
api.logger.info(`[cli-bridge] staged switch → ${model} (pending, not applied yet)`);
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
text:
|
|
321
|
+
`📋 **Model switch staged: ${label}**\n` +
|
|
322
|
+
`\`${model}\`\n\n` +
|
|
323
|
+
`⚠️ **NOT applied yet** — switching mid-session breaks the active agent:\n` +
|
|
324
|
+
`tool calls fail silently, plan files don't get written, no feedback.\n\n` +
|
|
325
|
+
`**To apply:**\n` +
|
|
326
|
+
`• \`/cli-apply\` — apply after finishing your current task\n` +
|
|
327
|
+
`• \`/cli-* --now\` — force immediate switch (only between sessions!)`,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
241
331
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
242
332
|
// Helper: fire a one-shot test request directly at the proxy (no global switch)
|
|
243
333
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
@@ -303,7 +393,7 @@ function proxyTestRequest(
|
|
|
303
393
|
const plugin = {
|
|
304
394
|
id: "openclaw-cli-bridge-elvatis",
|
|
305
395
|
name: "OpenClaw CLI Bridge",
|
|
306
|
-
version: "0.2.
|
|
396
|
+
version: "0.2.25",
|
|
307
397
|
description:
|
|
308
398
|
"Phase 1: openai-codex auth bridge. " +
|
|
309
399
|
"Phase 2: HTTP proxy for gemini/claude CLIs. " +
|
|
@@ -483,11 +573,13 @@ const plugin = {
|
|
|
483
573
|
const { name, model, description, label } = entry;
|
|
484
574
|
api.registerCommand({
|
|
485
575
|
name,
|
|
486
|
-
description
|
|
576
|
+
description: `${description}. Pass --now to apply immediately (only between sessions!).`,
|
|
577
|
+
acceptsArgs: true,
|
|
487
578
|
requireAuth: false,
|
|
488
579
|
handler: async (ctx: PluginCommandContext): Promise<PluginCommandResult> => {
|
|
489
|
-
|
|
490
|
-
|
|
580
|
+
const forceNow = (ctx.args ?? "").trim().toLowerCase() === "--now";
|
|
581
|
+
api.logger.info(`[cli-bridge] /${name} by ${ctx.senderId ?? "?"} forceNow=${forceNow}`);
|
|
582
|
+
return switchModel(api, model, label, forceNow);
|
|
491
583
|
},
|
|
492
584
|
} satisfies OpenClawPluginCommandDefinition);
|
|
493
585
|
}
|
|
@@ -495,11 +587,14 @@ const plugin = {
|
|
|
495
587
|
// ── Phase 3b: /cli-back — restore previous model ──────────────────────────
|
|
496
588
|
api.registerCommand({
|
|
497
589
|
name: "cli-back",
|
|
498
|
-
description: "Restore the model
|
|
590
|
+
description: "Restore the model active before the last /cli-* switch. Clears any pending staged switch.",
|
|
499
591
|
requireAuth: false,
|
|
500
592
|
handler: async (ctx: PluginCommandContext): Promise<PluginCommandResult> => {
|
|
501
593
|
api.logger.info(`[cli-bridge] /cli-back by ${ctx.senderId ?? "?"}`);
|
|
502
594
|
|
|
595
|
+
// Clear any pending staged switch
|
|
596
|
+
clearPending();
|
|
597
|
+
|
|
503
598
|
const state = readState();
|
|
504
599
|
if (!state?.previousModel) {
|
|
505
600
|
return { text: "ℹ️ No previous model saved. Use `/cli-sonnet` etc. to switch first." };
|
|
@@ -529,6 +624,57 @@ const plugin = {
|
|
|
529
624
|
},
|
|
530
625
|
} satisfies OpenClawPluginCommandDefinition);
|
|
531
626
|
|
|
627
|
+
// ── Phase 3b2: /cli-apply — apply staged model switch ─────────────────────
|
|
628
|
+
api.registerCommand({
|
|
629
|
+
name: "cli-apply",
|
|
630
|
+
description: "Apply a staged /cli-* model switch. Use this AFTER finishing your current task.",
|
|
631
|
+
requireAuth: false,
|
|
632
|
+
handler: async (ctx: PluginCommandContext): Promise<PluginCommandResult> => {
|
|
633
|
+
api.logger.info(`[cli-bridge] /cli-apply by ${ctx.senderId ?? "?"}`);
|
|
634
|
+
|
|
635
|
+
const pending = readPending();
|
|
636
|
+
if (!pending) {
|
|
637
|
+
const current = readCurrentModel();
|
|
638
|
+
return {
|
|
639
|
+
text:
|
|
640
|
+
`ℹ️ No staged switch pending.\n` +
|
|
641
|
+
`Current model: \`${current ?? "unknown"}\`\n\n` +
|
|
642
|
+
`Use \`/cli-sonnet\`, \`/cli-opus\` etc. to stage a switch.`,
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
api.logger.info(`[cli-bridge] applying staged switch → ${pending.model}`);
|
|
647
|
+
return applyModelSwitch(api, pending.model, pending.label);
|
|
648
|
+
},
|
|
649
|
+
} satisfies OpenClawPluginCommandDefinition);
|
|
650
|
+
|
|
651
|
+
// ── Phase 3b3: /cli-pending — show staged switch ───────────────────────────
|
|
652
|
+
api.registerCommand({
|
|
653
|
+
name: "cli-pending",
|
|
654
|
+
description: "Show the currently staged model switch (if any).",
|
|
655
|
+
requireAuth: false,
|
|
656
|
+
handler: async (): Promise<PluginCommandResult> => {
|
|
657
|
+
const pending = readPending();
|
|
658
|
+
const current = readCurrentModel();
|
|
659
|
+
if (!pending) {
|
|
660
|
+
return {
|
|
661
|
+
text:
|
|
662
|
+
`✅ No pending switch.\n` +
|
|
663
|
+
`Current model: \`${current ?? "unknown"}\``,
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
return {
|
|
667
|
+
text:
|
|
668
|
+
`📋 **Staged switch pending:**\n` +
|
|
669
|
+
`→ \`${pending.model}\` (${pending.label})\n` +
|
|
670
|
+
`Requested: ${pending.requestedAt}\n\n` +
|
|
671
|
+
`Current: \`${current ?? "unknown"}\`\n\n` +
|
|
672
|
+
`Run \`/cli-apply\` to apply after finishing your current task.\n` +
|
|
673
|
+
`Run \`/cli-sonnet --now\` etc. to discard and switch immediately.`,
|
|
674
|
+
};
|
|
675
|
+
},
|
|
676
|
+
} satisfies OpenClawPluginCommandDefinition);
|
|
677
|
+
|
|
532
678
|
// ── Phase 3c: /cli-test — one-shot proxy ping, no global model switch ──────
|
|
533
679
|
api.registerCommand({
|
|
534
680
|
name: "cli-test",
|
|
@@ -605,11 +751,20 @@ const plugin = {
|
|
|
605
751
|
}
|
|
606
752
|
lines.push("");
|
|
607
753
|
}
|
|
754
|
+
const pending = readPending();
|
|
755
|
+
const pendingNote = pending ? ` ← pending: ${pending.label}` : "";
|
|
756
|
+
|
|
608
757
|
lines.push("*Utility*");
|
|
609
|
-
lines.push(
|
|
758
|
+
lines.push(` /cli-apply Apply staged switch${pendingNote}`);
|
|
759
|
+
lines.push(" /cli-pending Show staged switch (if any)");
|
|
760
|
+
lines.push(" /cli-back Restore previous model + clear staged");
|
|
610
761
|
lines.push(" /cli-test [model] Health check (no model switch)");
|
|
611
762
|
lines.push(" /cli-list This overview");
|
|
612
763
|
lines.push("");
|
|
764
|
+
lines.push("*Switching safely:*");
|
|
765
|
+
lines.push(" /cli-sonnet → stages switch (safe, apply later)");
|
|
766
|
+
lines.push(" /cli-sonnet --now → immediate switch (only between sessions!)");
|
|
767
|
+
lines.push("");
|
|
613
768
|
lines.push(`Proxy: \`127.0.0.1:${port}\``);
|
|
614
769
|
|
|
615
770
|
return { text: lines.join("\n") };
|
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": "0.2.
|
|
4
|
+
"version": "0.2.25",
|
|
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.
|
|
3
|
+
"version": "0.2.25",
|
|
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
|
+
}
|
package/src/claude-auth.ts
CHANGED
|
@@ -47,7 +47,8 @@ const CREDENTIALS_PATH = join(homedir(), ".claude", ".credentials.json");
|
|
|
47
47
|
// State
|
|
48
48
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
49
49
|
|
|
50
|
-
let refreshTimer: ReturnType<typeof
|
|
50
|
+
let refreshTimer: ReturnType<typeof setInterval> | null = null;
|
|
51
|
+
let nextRefreshAt = 0; // epoch ms when the next refresh is due
|
|
51
52
|
let refreshInProgress: Promise<void> | null = null;
|
|
52
53
|
let log: (msg: string) => void = () => {};
|
|
53
54
|
|
|
@@ -60,6 +61,19 @@ export function setAuthLogger(logger: (msg: string) => void): void {
|
|
|
60
61
|
log = logger;
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Stop the background token refresh interval.
|
|
66
|
+
* Call in plugin deactivate / proxy server close to avoid timer leaks.
|
|
67
|
+
*/
|
|
68
|
+
export function stopTokenRefresh(): void {
|
|
69
|
+
if (refreshTimer) {
|
|
70
|
+
clearInterval(refreshTimer);
|
|
71
|
+
refreshTimer = null;
|
|
72
|
+
nextRefreshAt = 0;
|
|
73
|
+
log("[cli-bridge:auth] Token refresh scheduler stopped");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
63
77
|
/**
|
|
64
78
|
* Read the current token expiry from ~/.claude/.credentials.json.
|
|
65
79
|
* Returns null if the file doesn't exist or has no OAuth credentials
|
|
@@ -80,13 +94,14 @@ export async function readTokenExpiry(): Promise<number | null> {
|
|
|
80
94
|
|
|
81
95
|
/**
|
|
82
96
|
* Schedule a proactive token refresh 30 minutes before expiry.
|
|
83
|
-
* Call once at proxy startup. Safe to call multiple times (
|
|
97
|
+
* Call once at proxy startup. Safe to call multiple times (restarts the interval).
|
|
98
|
+
*
|
|
99
|
+
* Uses a 10-minute polling interval instead of a single long setTimeout so that
|
|
100
|
+
* the scheduler survives system sleep/resume without missing its window.
|
|
84
101
|
*/
|
|
85
102
|
export async function scheduleTokenRefresh(): Promise<void> {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
refreshTimer = null;
|
|
89
|
-
}
|
|
103
|
+
// Clear any existing interval before (re-)starting
|
|
104
|
+
stopTokenRefresh();
|
|
90
105
|
|
|
91
106
|
const expiresAt = await readTokenExpiry();
|
|
92
107
|
if (expiresAt === null) {
|
|
@@ -96,7 +111,6 @@ export async function scheduleTokenRefresh(): Promise<void> {
|
|
|
96
111
|
|
|
97
112
|
const now = Date.now();
|
|
98
113
|
const msUntilExpiry = expiresAt - now;
|
|
99
|
-
const msUntilRefresh = msUntilExpiry - REFRESH_BEFORE_EXPIRY_MS;
|
|
100
114
|
|
|
101
115
|
if (msUntilExpiry <= 0) {
|
|
102
116
|
log("[cli-bridge:auth] Token already expired — refreshing now");
|
|
@@ -104,22 +118,33 @@ export async function scheduleTokenRefresh(): Promise<void> {
|
|
|
104
118
|
return;
|
|
105
119
|
}
|
|
106
120
|
|
|
107
|
-
if (
|
|
121
|
+
if (msUntilExpiry <= REFRESH_BEFORE_EXPIRY_MS) {
|
|
108
122
|
// Expires within the next 30 min — refresh immediately
|
|
109
123
|
log(`[cli-bridge:auth] Token expires in ${Math.round(msUntilExpiry / 60000)}min — refreshing now`);
|
|
110
124
|
await refreshClaudeToken();
|
|
111
125
|
return;
|
|
112
126
|
}
|
|
113
127
|
|
|
114
|
-
|
|
128
|
+
// Set the target time for the first scheduled refresh (30 min before expiry)
|
|
129
|
+
nextRefreshAt = expiresAt - REFRESH_BEFORE_EXPIRY_MS;
|
|
130
|
+
const refreshInMin = Math.round((nextRefreshAt - now) / 60000);
|
|
115
131
|
log(`[cli-bridge:auth] Token valid for ${Math.round(msUntilExpiry / 60000)}min — refresh scheduled in ${refreshInMin}min`);
|
|
116
132
|
|
|
117
|
-
|
|
133
|
+
// Poll every 10 minutes instead of a single long setTimeout.
|
|
134
|
+
// This survives laptop sleep/resume without missing the refresh window.
|
|
135
|
+
const POLL_INTERVAL_MS = 10 * 60 * 1000;
|
|
136
|
+
refreshTimer = setInterval(async () => {
|
|
137
|
+
if (Date.now() < nextRefreshAt) return; // not yet due
|
|
118
138
|
log("[cli-bridge:auth] Scheduled token refresh triggered");
|
|
119
139
|
await refreshClaudeToken();
|
|
120
|
-
//
|
|
121
|
-
await
|
|
122
|
-
|
|
140
|
+
// Recompute next refresh target from the freshly written credentials
|
|
141
|
+
const newExpiry = await readTokenExpiry();
|
|
142
|
+
if (newExpiry) {
|
|
143
|
+
nextRefreshAt = newExpiry - REFRESH_BEFORE_EXPIRY_MS;
|
|
144
|
+
const nextInMin = Math.round((nextRefreshAt - Date.now()) / 60000);
|
|
145
|
+
log(`[cli-bridge:auth] Next refresh in ${nextInMin}min`);
|
|
146
|
+
}
|
|
147
|
+
}, POLL_INTERVAL_MS);
|
|
123
148
|
|
|
124
149
|
// Don't block process exit
|
|
125
150
|
if (refreshTimer.unref) refreshTimer.unref();
|
|
@@ -189,13 +214,12 @@ async function doRefresh(): Promise<void> {
|
|
|
189
214
|
return;
|
|
190
215
|
}
|
|
191
216
|
|
|
192
|
-
// Re-read expiry and
|
|
217
|
+
// Re-read expiry and update the next refresh target for the running interval
|
|
193
218
|
const newExpiry = await readTokenExpiry();
|
|
194
219
|
if (newExpiry) {
|
|
195
220
|
const validForMin = Math.round((newExpiry - Date.now()) / 60000);
|
|
196
221
|
log(`[cli-bridge:auth] Token refreshed — valid for ${validForMin}min`);
|
|
197
|
-
|
|
198
|
-
void scheduleTokenRefresh();
|
|
222
|
+
nextRefreshAt = newExpiry - REFRESH_BEFORE_EXPIRY_MS;
|
|
199
223
|
}
|
|
200
224
|
}
|
|
201
225
|
|
package/src/proxy-server.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import http from "node:http";
|
|
12
12
|
import { randomBytes } from "node:crypto";
|
|
13
13
|
import { type ChatMessage, routeToCliRunner } from "./cli-runner.js";
|
|
14
|
-
import { scheduleTokenRefresh, setAuthLogger } from "./claude-auth.js";
|
|
14
|
+
import { scheduleTokenRefresh, setAuthLogger, stopTokenRefresh } from "./claude-auth.js";
|
|
15
15
|
|
|
16
16
|
export interface ProxyServerOptions {
|
|
17
17
|
port: number;
|
|
@@ -77,6 +77,11 @@ export function startProxyServer(opts: ProxyServerOptions): Promise<http.Server>
|
|
|
77
77
|
});
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
+
// Stop the token refresh interval when the server closes (timer-leak prevention)
|
|
81
|
+
server.on("close", () => {
|
|
82
|
+
stopTokenRefresh();
|
|
83
|
+
});
|
|
84
|
+
|
|
80
85
|
server.on("error", (err) => reject(err));
|
|
81
86
|
server.listen(opts.port, "127.0.0.1", () => {
|
|
82
87
|
opts.log(
|