@heylemon/lemonade 0.6.5 → 0.6.7

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,5 +1,5 @@
1
1
  {
2
- "version": "0.6.5",
3
- "commit": "a8d80580774ad7b3248da8d2ca971e56c62e4ace",
4
- "builtAt": "2026-02-28T09:58:29.125Z"
2
+ "version": "0.6.7",
3
+ "commit": "2965fd60321e809102f7cf3b40bb4a8bb6e009cb",
4
+ "builtAt": "2026-02-28T10:36:45.544Z"
5
5
  }
@@ -102,6 +102,9 @@ export async function monitorWebChannel(verbose, listenerFactory = monitorWebInb
102
102
  };
103
103
  process.once("SIGINT", handleSigint);
104
104
  let reconnectAttempts = 0;
105
+ // Track whether we've already attempted a one-time 515 socket restart.
106
+ // Reset after a successful connection so transient 515s can be retried once per cycle.
107
+ let has515Retried = false;
105
108
  while (true) {
106
109
  if (stopRequested())
107
110
  break;
@@ -165,8 +168,7 @@ export async function monitorWebChannel(verbose, listenerFactory = monitorWebInb
165
168
  }
166
169
  catch (err) {
167
170
  // Initial socket creation or WebSocket handshake failed (e.g., 408
168
- // timeout, 428 Connection Terminated). Without this catch the error
169
- // escapes the while-loop and the reconnect backoff is never reached.
171
+ // timeout, 428 Connection Terminated, 515 Stream Error).
170
172
  const errStatusCode = getStatusCode(err);
171
173
  const errorStr = formatError(err);
172
174
  reconnectLogger.warn({ connectionId, error: errorStr, status: errStatusCode }, "web reconnect: initial connection failed");
@@ -183,6 +185,21 @@ export async function monitorWebChannel(verbose, listenerFactory = monitorWebInb
183
185
  runtime.error(`WhatsApp session logged out. Run \`${formatCliCommand("lemonade channels login --channel web")}\` to relink.`);
184
186
  break;
185
187
  }
188
+ // 515 = "restart required" — WhatsApp demands a fresh socket with the same creds.
189
+ // Mirror the interactive login behavior: retry once with a short delay.
190
+ // If the retry also fails with 515, the session is stale and needs re-linking.
191
+ if (errStatusCode === 515 && !has515Retried) {
192
+ has515Retried = true;
193
+ reconnectLogger.info({ connectionId }, "web reconnect: 515 restart required — retrying with fresh socket");
194
+ runtime.log("WhatsApp asked for a restart (code 515); retrying connection once…");
195
+ try {
196
+ await sleep(2_000, abortSignal);
197
+ }
198
+ catch {
199
+ break;
200
+ }
201
+ continue;
202
+ }
186
203
  reconnectAttempts += 1;
187
204
  status.reconnectAttempts = reconnectAttempts;
188
205
  emitStatus();
@@ -210,6 +227,7 @@ export async function monitorWebChannel(verbose, listenerFactory = monitorWebInb
210
227
  status.lastConnectedAt = Date.now();
211
228
  status.lastEventAt = status.lastConnectedAt;
212
229
  status.lastError = null;
230
+ has515Retried = false; // Reset on successful connection
213
231
  emitStatus();
214
232
  // Surface a concise connection event for the next main-session turn/heartbeat.
215
233
  const { e164: selfE164 } = readWebSelfId(account.authDir);
@@ -362,6 +380,20 @@ export async function monitorWebChannel(verbose, listenerFactory = monitorWebInb
362
380
  await closeListener();
363
381
  break;
364
382
  }
383
+ // 515 = "restart required" — retry once with a fresh socket (same credentials).
384
+ if (statusCode === 515 && !has515Retried) {
385
+ has515Retried = true;
386
+ reconnectLogger.info({ connectionId }, "web reconnect: 515 restart required after disconnect — retrying with fresh socket");
387
+ runtime.log("WhatsApp disconnected with 515 (restart required); retrying connection once…");
388
+ await closeListener();
389
+ try {
390
+ await sleep(2_000, abortSignal);
391
+ }
392
+ catch {
393
+ break;
394
+ }
395
+ continue;
396
+ }
365
397
  reconnectAttempts += 1;
366
398
  status.reconnectAttempts = reconnectAttempts;
367
399
  emitStatus();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heylemon/lemonade",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "description": "AI gateway CLI for Lemon - local AI assistant with integrations",
5
5
  "publishConfig": {
6
6
  "access": "restricted"