switchroom 0.12.25 → 0.12.26

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.
@@ -47247,8 +47247,8 @@ var {
47247
47247
  } = import__.default;
47248
47248
 
47249
47249
  // src/build-info.ts
47250
- var VERSION = "0.12.25";
47251
- var COMMIT_SHA = "4b8ab51";
47250
+ var VERSION = "0.12.26";
47251
+ var COMMIT_SHA = "17486c7";
47252
47252
 
47253
47253
  // src/cli/agent.ts
47254
47254
  init_source();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchroom",
3
- "version": "0.12.25",
3
+ "version": "0.12.26",
4
4
  "description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -42961,10 +42961,12 @@ function createIpcServer(options) {
42961
42961
  const loggedLegacyUpdatePlaceholder = new Set;
42962
42962
  function removeClient(client3) {
42963
42963
  clients.delete(client3);
42964
- if (client3.agentName)
42964
+ if (client3.agentName && agentIndex.get(client3.agentName) === client3) {
42965
42965
  agentIndex.delete(client3.agentName);
42966
- if (client3.topicId != null)
42966
+ }
42967
+ if (client3.topicId != null && topicIndex.get(client3.topicId) === client3) {
42967
42968
  topicIndex.delete(client3.topicId);
42969
+ }
42968
42970
  loggedLegacyUpdatePlaceholder.delete(client3.id);
42969
42971
  onClientDisconnected(client3);
42970
42972
  log(`client disconnected: ${client3.id} (agent=${client3.agentName})`);
@@ -43070,6 +43072,13 @@ function createIpcServer(options) {
43070
43072
  agentIndex.delete(client3.agentName);
43071
43073
  if (client3.topicId != null)
43072
43074
  topicIndex.delete(client3.topicId);
43075
+ const existingClient = agentIndex.get(msg.agentName);
43076
+ if (existingClient && existingClient !== client3) {
43077
+ log(`register: closing prior client for agent=${msg.agentName} ` + `(prior_id=${existingClient.id} new_id=${client3.id}) \u2014 bridge reconnect race`);
43078
+ try {
43079
+ existingClient.close();
43080
+ } catch {}
43081
+ }
43073
43082
  client3.agentName = msg.agentName;
43074
43083
  client3.topicId = msg.topicId ?? null;
43075
43084
  agentIndex.set(msg.agentName, client3);
@@ -47357,10 +47366,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
47357
47366
  }
47358
47367
 
47359
47368
  // ../src/build-info.ts
47360
- var VERSION = "0.12.25";
47361
- var COMMIT_SHA = "4b8ab51";
47362
- var COMMIT_DATE = "2026-05-20T15:24:02+10:00";
47363
- var LATEST_PR = 1584;
47369
+ var VERSION = "0.12.26";
47370
+ var COMMIT_SHA = "17486c7";
47371
+ var COMMIT_DATE = "2026-05-20T05:54:04Z";
47372
+ var LATEST_PR = 1586;
47364
47373
  var COMMITS_AHEAD_OF_TAG = 2;
47365
47374
 
47366
47375
  // gateway/boot-version.ts
@@ -266,8 +266,24 @@ export function createIpcServer(options: IpcServerOptions): IpcServer {
266
266
 
267
267
  function removeClient(client: IpcClient & { _socket: ReturnType<typeof Bun.listen> extends infer S ? any : never }) {
268
268
  clients.delete(client);
269
- if (client.agentName) agentIndex.delete(client.agentName);
270
- if (client.topicId != null) topicIndex.delete(client.topicId);
269
+ // CRITICAL race fix (2026-05-20): only delete from agentIndex /
270
+ // topicIndex if the index still points to THIS client. A bridge
271
+ // that reconnects fast can have its NEW client overwrite
272
+ // agentIndex[name] (via handleRegister's replace-not-reject)
273
+ // BEFORE the OLD client's close+removeClient runs. Blindly
274
+ // deleting agentIndex[name] would remove the LIVE replacement
275
+ // client by accident → sendToAgent returns false → all subsequent
276
+ // inbound buffered until the bridge happens to reconnect in an
277
+ // ordering that works out. User-visible symptom was the chronic
278
+ // bridge-flap pattern (clerk + gymbro unresponsive 2026-05-20)
279
+ // where the gateway log showed "bridge registered" but messages
280
+ // were still getting buffered as if no bridge existed.
281
+ if (client.agentName && agentIndex.get(client.agentName) === client) {
282
+ agentIndex.delete(client.agentName);
283
+ }
284
+ if (client.topicId != null && topicIndex.get(client.topicId) === client) {
285
+ topicIndex.delete(client.topicId);
286
+ }
271
287
  loggedLegacyUpdatePlaceholder.delete(client.id);
272
288
  onClientDisconnected(client);
273
289
  log(`client disconnected: ${client.id} (agent=${client.agentName})`);
@@ -407,6 +423,28 @@ export function createIpcServer(options: IpcServerOptions): IpcServer {
407
423
  if (client.agentName) agentIndex.delete(client.agentName);
408
424
  if (client.topicId != null) topicIndex.delete(client.topicId);
409
425
 
426
+ // 2026-05-20 race fix: if a PRIOR client is registered as this
427
+ // agent name (a stale/zombie connection that hasn't been evicted
428
+ // yet), explicitly close it before installing this new client.
429
+ // Without this, the prior client remains in `clients` set, its
430
+ // heartbeat watchdog still ticks, and its eventual close+removeClient
431
+ // can confuse routing. The removeClient identity-check fix above
432
+ // means the index won't be wrongly deleted, but two concurrent
433
+ // clients claiming the same agent name is still a routing hazard
434
+ // — close the zombie cleanly here.
435
+ const existingClient = agentIndex.get(msg.agentName);
436
+ if (existingClient && existingClient !== client) {
437
+ log(
438
+ `register: closing prior client for agent=${msg.agentName} ` +
439
+ `(prior_id=${existingClient.id} new_id=${client.id}) — bridge reconnect race`,
440
+ );
441
+ try {
442
+ (existingClient as IpcClientImpl).close();
443
+ } catch {
444
+ /* nothing to do */
445
+ }
446
+ }
447
+
410
448
  client.agentName = msg.agentName;
411
449
  client.topicId = msg.topicId ?? null;
412
450