alvin-bot 4.18.2 → 4.18.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/CHANGELOG.md +8 -0
- package/dist/providers/claude-sdk-provider.js +24 -17
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Alvin Bot are documented here.
|
|
4
4
|
|
|
5
|
+
## [4.18.3] — 2026-04-23
|
|
6
|
+
|
|
7
|
+
### 🐛 Hotfix: 4.18.2 triggered unwanted failover to Ollama
|
|
8
|
+
|
|
9
|
+
**Bug in 4.18.2:** The empty-stream detector yielded an `error` chunk, which the registry's `queryWithFallback()` interprets as "primary provider failed" and immediately switches to the fallback (Ollama/Gemma 4). User saw `⚡ Claude (Agent SDK) unavailable — switching to Gemma 4 E4B` after every token rotation — the opposite of the intended behavior.
|
|
10
|
+
|
|
11
|
+
**Fix:** yield a `text` chunk instead of `error`. Same user-visible message, same cache-invalidation, but no failover cascade. The next CLI subprocess spawns with the fresh Keychain token automatically, and claude-sdk stays selected.
|
|
12
|
+
|
|
5
13
|
## [4.18.2] — 2026-04-23
|
|
6
14
|
|
|
7
15
|
### 🐛 Fix: silent empty-stream after OAuth-token rotation
|
|
@@ -309,33 +309,40 @@ export class ClaudeSDKProvider {
|
|
|
309
309
|
? (usage.input_tokens || 0) + (usage.cache_creation_input_tokens || 0) + (usage.cache_read_input_tokens || 0)
|
|
310
310
|
: 0;
|
|
311
311
|
const outputTok = usage?.output_tokens || 0;
|
|
312
|
-
// v4.18.
|
|
312
|
+
// v4.18.3 — Silent-empty-stream detection (replaces 4.18.2 approach).
|
|
313
313
|
//
|
|
314
314
|
// If the stream terminated cleanly but produced ZERO text chunks,
|
|
315
|
-
// something went wrong that the SDK didn't surface as an error
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
// still
|
|
319
|
-
// 401, emits no text, and we complete
|
|
320
|
-
// accumulatedText === "".
|
|
315
|
+
// something went wrong that the SDK didn't surface as an error.
|
|
316
|
+
// Most common cause: the OAuth token in the Keychain was rotated
|
|
317
|
+
// (e.g. right after /extra-usage or /login) while our in-memory
|
|
318
|
+
// SDK client still held the old one — the CLI subprocess silently
|
|
319
|
+
// gets a 401, emits no text, and we complete with
|
|
320
|
+
// accumulatedText === "".
|
|
321
321
|
//
|
|
322
|
-
//
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
//
|
|
322
|
+
// CRITICAL: we must NOT yield an "error" chunk here — the registry's
|
|
323
|
+
// queryWithFallback() treats that as "primary failed" and kicks off
|
|
324
|
+
// a full failover to the next provider (Ollama). That's exactly
|
|
325
|
+
// wrong: the next CLI subprocess would have picked up the fresh
|
|
326
|
+
// token by itself. Instead we:
|
|
327
|
+
// 1. Invalidate the availability cache so the next heartbeat
|
|
328
|
+
// re-probes `claude auth status` with a fresh subprocess.
|
|
329
|
+
// 2. Return a friendly "text" chunk explaining what happened,
|
|
330
|
+
// so the user sees a clear message (not "(Keine Antwort)")
|
|
331
|
+
// and knows to resend — without tripping the failover.
|
|
326
332
|
if (accumulatedText === "" && outputTok === 0) {
|
|
327
333
|
this.invalidateAvailabilityCache();
|
|
334
|
+
const hint = "⚠️ Claude antwortete mit leerem Stream (meist nach /extra-usage, /login oder Token-Refresh). " +
|
|
335
|
+
"Der SDK-Token-Cache wurde geleert — bitte schick die Nachricht einfach nochmal.";
|
|
328
336
|
yield {
|
|
329
|
-
type: "
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
337
|
+
type: "text",
|
|
338
|
+
text: hint,
|
|
339
|
+
delta: hint,
|
|
340
|
+
sessionId: resultMsg.session_id || capturedSessionId,
|
|
333
341
|
};
|
|
334
|
-
return;
|
|
335
342
|
}
|
|
336
343
|
yield {
|
|
337
344
|
type: "done",
|
|
338
|
-
text: accumulatedText,
|
|
345
|
+
text: accumulatedText || "",
|
|
339
346
|
sessionId: resultMsg.session_id || capturedSessionId,
|
|
340
347
|
costUsd: "total_cost_usd" in resultMsg ? resultMsg.total_cost_usd : 0,
|
|
341
348
|
inputTokens: inputTok,
|