@elvatis_com/openclaw-cli-bridge-elvatis 3.4.0 → 3.5.0

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > OpenClaw plugin that bridges locally installed AI CLIs (Codex, Gemini, Claude Code, OpenCode, Pi) as model providers — with slash commands for instant model switching, restore, health testing, and model listing.
4
4
 
5
- **Current version:** `3.4.0`
5
+ **Current version:** `3.5.0`
6
6
 
7
7
  ---
8
8
 
package/SKILL.md CHANGED
@@ -68,4 +68,4 @@ On gateway restart, if any session has expired, a **WhatsApp alert** is sent aut
68
68
 
69
69
  See `README.md` for full configuration reference and architecture diagram.
70
70
 
71
- **Version:** 3.4.0
71
+ **Version:** 3.5.0
@@ -2,7 +2,7 @@
2
2
  "id": "openclaw-cli-bridge-elvatis",
3
3
  "slug": "openclaw-cli-bridge-elvatis",
4
4
  "name": "OpenClaw CLI Bridge",
5
- "version": "3.4.0",
5
+ "version": "3.5.0",
6
6
  "license": "MIT",
7
7
  "description": "Phase 1: openai-codex auth bridge. Phase 2: local HTTP proxy routing model calls through gemini/claude CLIs (vllm provider).",
8
8
  "providers": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elvatis_com/openclaw-cli-bridge-elvatis",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
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/cli-runner.ts CHANGED
@@ -80,11 +80,16 @@ export function formatPrompt(messages: ChatMessage[], toolCount = 0): string {
80
80
  // Reduce history when tool schemas dominate the prompt
81
81
  const maxMsgs = toolCount > TOOL_HEAVY_THRESHOLD ? MAX_MESSAGES_HEAVY_TOOLS : MAX_MESSAGES;
82
82
 
83
- // Keep system message (if any) + last N non-system messages
83
+ // Keep system message (if any) + first user message (original request) + last N non-system messages
84
84
  const system = messages.find((m) => m.role === "system");
85
85
  const nonSystem = messages.filter((m) => m.role !== "system");
86
+ const firstUser = nonSystem.find((m) => m.role === "user");
86
87
  const recent = nonSystem.slice(-maxMsgs);
87
- const truncated = system ? [system, ...recent] : recent;
88
+ // Pin the first user message so the model never loses the original request
89
+ const pinned = firstUser && !recent.includes(firstUser)
90
+ ? [firstUser, ...recent]
91
+ : recent;
92
+ const truncated = system ? [system, ...pinned] : pinned;
88
93
 
89
94
  // Single short user message — send bare (no wrapping needed)
90
95
  if (truncated.length === 1 && truncated[0].role === "user") {
@@ -708,8 +713,8 @@ export async function runClaude(
708
713
 
709
714
  const stderr = result.stderr || "(no output)";
710
715
 
711
- // Session might be corrupted or expired — invalidate and retry with a fresh session
712
- if (stderr.includes("session") || stderr.includes("resume") || stderr.includes("not found")) {
716
+ // Session might be corrupted, expired, or locked by a zombie process — invalidate and retry
717
+ if (stderr.includes("session") || stderr.includes("resume") || stderr.includes("not found") || stderr.includes("already in use")) {
713
718
  debugLog("CLAUDE", `session ${session.sessionId.slice(0, 8)} invalid, creating fresh`, { error: stderr.slice(0, 100) });
714
719
  invalidateSession(model);
715
720
  // Retry once with a fresh session
@@ -724,6 +729,8 @@ export async function runClaude(
724
729
  recordSessionSuccess(model);
725
730
  return retry.stdout;
726
731
  }
732
+ // Retry also failed — invalidate the fresh session so the next request doesn't reuse it
733
+ invalidateSession(model);
727
734
  throw new Error(`claude exited ${retry.exitCode}: ${annotateExitError(retry.exitCode, retry.stderr || "(no output)", false, modelId)}`);
728
735
  }
729
736
 
@@ -19,18 +19,19 @@ describe("formatPrompt", () => {
19
19
  expect(result).toBe("hello");
20
20
  });
21
21
 
22
- it("truncates to MAX_MESSAGES (20) non-system messages", () => {
22
+ it("truncates to MAX_MESSAGES (20) non-system messages but pins first user message", () => {
23
23
  const messages = Array.from({ length: 30 }, (_, i) => ({
24
24
  role: "user" as const,
25
25
  content: `msg ${i}`,
26
26
  }));
27
27
  const result = formatPrompt(messages);
28
28
  expect(result).toContain("msg 29");
29
- expect(result).not.toContain("msg 0\n");
29
+ expect(result).toContain("msg 0"); // first user message is always pinned
30
+ expect(result).not.toContain("msg 1\n"); // but intermediate messages are truncated
30
31
  expect(result).toContain("[User]");
31
32
  });
32
33
 
33
- it("keeps system message + last 20 non-system messages", () => {
34
+ it("keeps system message + first user message + last 20 non-system messages", () => {
34
35
  const sys = { role: "system" as const, content: "You are helpful" };
35
36
  const msgs = Array.from({ length: 25 }, (_, i) => ({
36
37
  role: "user" as const,
@@ -40,7 +41,8 @@ describe("formatPrompt", () => {
40
41
  expect(result).toContain("[System]");
41
42
  expect(result).toContain("You are helpful");
42
43
  expect(result).toContain("msg 24");
43
- expect(result).not.toContain("msg 0\n");
44
+ expect(result).toContain("msg 0"); // first user message is always pinned
45
+ expect(result).not.toContain("msg 1\n"); // but intermediate messages are truncated
44
46
  });
45
47
 
46
48
  it("truncates individual message content at MAX_MSG_CHARS (4000)", () => {