mobygate 0.8.1 → 0.8.2

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 CHANGED
@@ -4,6 +4,73 @@ All notable changes to mobygate are documented here. Format loosely follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version numbers are
5
5
  [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [0.8.2] — 2026-04-28
8
+
9
+ Multi-agent fixes. Found the day after v0.8.1 shipped, while testing
10
+ three OpenClaw bots (Mobius/Lux/Mercury) in parallel on the same
11
+ machine. Both bugs were invisible without the v0.8.1 inspector.
12
+
13
+ ### Fixed
14
+
15
+ - **Session-key collision when multiple agents share a boilerplate
16
+ prefix in their system prompt.** v0.7.1's auto-derive hashed the
17
+ first 500 characters of the system prompt; OpenClaw's "You are a
18
+ personal assistant running inside OpenClaw…" preamble fills more
19
+ than that, so the per-agent personality content (loaded later from
20
+ workspace SOUL.md / IDENTITY.md / etc.) didn't reach the hash. Two
21
+ separate agents (Lux on sonnet-4-6, Mercury on sonnet-4-6) collided
22
+ onto the same auto-key when given the same first user message
23
+ ("@Lux @Mercury Hi"). Same key → same SDK session reuse → cache
24
+ thrash and potential session-state mixing.
25
+
26
+ Bumped `SYSTEM_TRIM` from 500 → 20000 chars. Verified against real
27
+ captured request bodies that collided in v0.8.1 — they now hash to
28
+ distinct keys (`auto_b0371e5c…` vs `auto_2b90afd7…`).
29
+
30
+ SHA-256 cost on 20kB is ~10-20µs per request, irrelevant in the
31
+ hot path.
32
+
33
+ - **Model map silently downgraded `claude-sonnet-4-6` to retired
34
+ `claude-sonnet-4-5-20250929`.** When the v0.8.0 model map was
35
+ written, the Claude Agent SDK didn't recognize the un-dated
36
+ `claude-sonnet-4-6` alias and we worked around it by routing to the
37
+ most recent dated 4-5. The SDK has since added native 4-6 support,
38
+ but mobygate kept the workaround in place. Result: clients (OpenClaw
39
+ Lux/Mercury) configured for sonnet-4-6 were having their requests
40
+ rewritten to the retired 4-5-20250929 dated id. Anthropic accepted
41
+ the call but the response wasn't billing into the user's "Sonnet
42
+ only" quota — it was showing 0% used despite live traffic. Likely
43
+ Claude was falling back internally to opus or returning a
44
+ zero-billed degraded response.
45
+
46
+ Fix: route `claude-sonnet-4-6` through directly. Also updated
47
+ `claude-sonnet-4` and the `sonnet` shorthand to point at 4-6
48
+ (current latest) instead of the retired dated 4-5 entry. Explicit
49
+ `claude-sonnet-4-5` requests still route to the dated id for
50
+ backward compatibility.
51
+
52
+ Discovery: the inspector showed Lux/Mercury captures all stamped
53
+ with `model: claude-sonnet-4-6` (correct from the request side) but
54
+ Anthropic's quota panel reported 0% sonnet usage. The server.log's
55
+ `model=claude-sonnet-4-6 → claude-sonnet-4-5-20250929` translation
56
+ line was the smoking gun.
57
+
58
+ ### Notes
59
+
60
+ The proper long-term fix is for clients to pass an explicit
61
+ `X-Session-Id` header per agent (mobygate has supported this since
62
+ v0.7.1 — it always wins over auto-derive). This bump is a defensive
63
+ measure for clients that don't.
64
+
65
+ Discovery flow is a nice validation of the v0.8.1 inspector: the
66
+ collision was invisible at the OpenClaw level (each bot's replies
67
+ arrived correctly because OpenClaw maintains its own per-agent SDK
68
+ state) but jumped out as soon as the captures were sorted by session
69
+ key in the inspector — two different model requests with the same
70
+ session-key, with bootstrap text 55kB long but identical first 500
71
+ chars. Without the inspector, this would have surfaced as
72
+ unpredictable cache hit rates and been blamed on Anthropic.
73
+
7
74
  ## [0.8.1] — 2026-04-27
8
75
 
9
76
  Diagnostic visibility release. Adds a request/response capture system,
@@ -40,6 +40,12 @@
40
40
  * user message from history mid-conversation, the auto-key changes
41
41
  * and the SDK starts a new session. One turn of double-billing,
42
42
  * then we're back on the new key. Acceptable.
43
+ * - **Multi-agent collisions** (fixed in v0.8.2): two agents that
44
+ * share boilerplate at the start of their system prompt previously
45
+ * collided onto one session key when the trim window only covered
46
+ * the boilerplate. SYSTEM_TRIM was raised from 500 to 20000 chars
47
+ * to capture the per-agent personality content that follows the
48
+ * shared preamble. See note on the constant below for details.
43
49
  *
44
50
  * Opt-out: `X-Session-Id: none` tells us the client explicitly wants
45
51
  * stateless behavior — we return null and the request flows through
@@ -51,7 +57,20 @@
51
57
  import { createHash } from 'crypto';
52
58
 
53
59
  const HASH_LEN = 16;
54
- const SYSTEM_TRIM = 500;
60
+ // SYSTEM_TRIM was 500 in v0.7.1 — large enough for casual single-agent
61
+ // scenarios (Hermes, single-bot OpenClaw) but caused collisions when
62
+ // multiple agents shared a common boilerplate prefix. Observed in v0.8.1
63
+ // production: Lux + Mercury (two OpenClaw agents) both started their
64
+ // system prompt with the OpenClaw "You are a personal assistant…"
65
+ // boilerplate that filled the first ~500 chars, so their personality
66
+ // markers (loaded from per-agent SOUL.md / IDENTITY.md / etc.) didn't
67
+ // reach the hash and they collided onto the same session key.
68
+ //
69
+ // Bumping to 20kB covers realistic agent system prompts including
70
+ // rich workspace bootstrap (Lux: ~42kB, Mercury: ~80kB total — but
71
+ // the first 20kB has more than enough divergence to fingerprint each).
72
+ // SHA-256 cost on 20kB is ~10-20µs, irrelevant per request.
73
+ const SYSTEM_TRIM = 20000;
55
74
  const USER_TRIM = 500;
56
75
 
57
76
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobygate",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "OpenAI-compatible local proxy for Claude Max. The Möbius-strip gateway: OpenAI shape in, Claude Max out.",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -168,6 +168,17 @@ for (const sig of ['SIGTERM', 'SIGINT', 'SIGHUP']) {
168
168
  // Opus 4.7 ships a native 1M-context variant addressed as `claude-opus-4-7[1m]`.
169
169
  // Default opus aliases route to the 1M form to match the advertised context window.
170
170
  // Pass `claude-opus-4-7-200k` for the standard (cheaper) 200k variant.
171
+ //
172
+ // History: the sonnet-4-6 entry previously mapped to the dated
173
+ // `claude-sonnet-4-5-20250929` because at the time, the SDK didn't
174
+ // recognize `claude-sonnet-4-6` natively. The SDK has since added
175
+ // native support for the un-dated 4-6 alias, so sonnet-4-6 requests
176
+ // were silently being downgraded to retired 4-5-20250929. This caused
177
+ // the "Sonnet only" Anthropic quota to show 0% usage even when Lux
178
+ // and Mercury (configured for sonnet-4-6) were chatting actively —
179
+ // the SDK was accepting the retired model id but Claude was likely
180
+ // falling back to opus or returning a zero-billed response. Fixed in
181
+ // v0.8.2 by routing 4-6 through directly.
171
182
  const MODEL_MAP = {
172
183
  'claude-opus-4': 'claude-opus-4-7[1m]',
173
184
  'claude-opus-4-6': 'claude-opus-4-6',
@@ -175,13 +186,13 @@ const MODEL_MAP = {
175
186
  'claude-opus-4-7[1m]': 'claude-opus-4-7[1m]',
176
187
  'claude-opus-4-7-1m': 'claude-opus-4-7[1m]',
177
188
  'claude-opus-4-7-200k': 'claude-opus-4-7',
178
- 'claude-sonnet-4': 'claude-sonnet-4-5-20250929',
179
- 'claude-sonnet-4-5': 'claude-sonnet-4-5-20250929',
180
- 'claude-sonnet-4-6': 'claude-sonnet-4-5-20250929', // SDK resolves 4-6 to non-existent dated version
189
+ 'claude-sonnet-4': 'claude-sonnet-4-6', // current latest sonnet
190
+ 'claude-sonnet-4-5': 'claude-sonnet-4-5-20250929', // explicit request for older 4-5
191
+ 'claude-sonnet-4-6': 'claude-sonnet-4-6', // SDK now supports natively; was retired-mapped before v0.8.2
181
192
  'claude-haiku-4': 'claude-haiku-4-5-20251001',
182
193
  'claude-haiku-4-5': 'claude-haiku-4-5-20251001',
183
194
  'opus': 'claude-opus-4-7[1m]',
184
- 'sonnet': 'claude-sonnet-4-5-20250929',
195
+ 'sonnet': 'claude-sonnet-4-6', // current latest sonnet
185
196
  'haiku': 'claude-haiku-4-5-20251001',
186
197
  };
187
198