switchroom 0.12.24 → 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.
package/dist/cli/switchroom.js
CHANGED
|
@@ -47247,8 +47247,8 @@ var {
|
|
|
47247
47247
|
} = import__.default;
|
|
47248
47248
|
|
|
47249
47249
|
// src/build-info.ts
|
|
47250
|
-
var VERSION = "0.12.
|
|
47251
|
-
var COMMIT_SHA = "
|
|
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
|
@@ -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
|
-
|
|
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,11 +47366,11 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
|
|
|
47357
47366
|
}
|
|
47358
47367
|
|
|
47359
47368
|
// ../src/build-info.ts
|
|
47360
|
-
var VERSION = "0.12.
|
|
47361
|
-
var COMMIT_SHA = "
|
|
47362
|
-
var COMMIT_DATE = "2026-05-
|
|
47363
|
-
var LATEST_PR =
|
|
47364
|
-
var COMMITS_AHEAD_OF_TAG =
|
|
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;
|
|
47373
|
+
var COMMITS_AHEAD_OF_TAG = 2;
|
|
47365
47374
|
|
|
47366
47375
|
// gateway/boot-version.ts
|
|
47367
47376
|
function formatRelativeAgo(iso) {
|
|
@@ -49234,7 +49243,9 @@ var ipcServer = createIpcServer({
|
|
|
49234
49243
|
onClientRegistered(client3) {
|
|
49235
49244
|
process.stderr.write(`telegram gateway: bridge registered \u2014 agent=${client3.agentName}
|
|
49236
49245
|
`);
|
|
49237
|
-
|
|
49246
|
+
if (client3.agentName != null) {
|
|
49247
|
+
shadowEmit({ kind: "bridgeUp", at: Date.now() });
|
|
49248
|
+
}
|
|
49238
49249
|
client3.send({ type: "status", status: "agent_connected" });
|
|
49239
49250
|
if (client3.agentName != null) {
|
|
49240
49251
|
const pending = pendingInboundBuffer.drain(client3.agentName);
|
|
@@ -49328,7 +49339,9 @@ var ipcServer = createIpcServer({
|
|
|
49328
49339
|
onClientDisconnected(client3) {
|
|
49329
49340
|
process.stderr.write(`telegram gateway: bridge disconnected \u2014 agent=${client3.agentName}
|
|
49330
49341
|
`);
|
|
49331
|
-
|
|
49342
|
+
if (client3.agentName != null) {
|
|
49343
|
+
shadowEmit({ kind: "bridgeDown", at: Date.now() });
|
|
49344
|
+
}
|
|
49332
49345
|
flushOnAgentDisconnect({
|
|
49333
49346
|
agentName: client3.agentName,
|
|
49334
49347
|
activeStatusReactions,
|
|
@@ -3196,8 +3196,17 @@ const ipcServer: IpcServer = createIpcServer({
|
|
|
3196
3196
|
|
|
3197
3197
|
onClientRegistered(client: IpcClient) {
|
|
3198
3198
|
process.stderr.write(`telegram gateway: bridge registered — agent=${client.agentName}\n`)
|
|
3199
|
-
// Phase 2b shadow: bridge
|
|
3200
|
-
|
|
3199
|
+
// Phase 2b shadow: ONLY emit bridgeUp for the REAL bridge sidecar
|
|
3200
|
+
// (with an agent name). Anonymous IPC clients (recall.py, mcp
|
|
3201
|
+
// handshakes, etc.) connect briefly without a name and would
|
|
3202
|
+
// false-positive a bridgeUp/bridgeDown cycle that doesn't reflect
|
|
3203
|
+
// the real bridge state. This bug — discovered post-v0.12.24 — was
|
|
3204
|
+
// causing the shadow state to read `bridge_dead` even when the
|
|
3205
|
+
// real bridge was healthy, because every recall.py connect+disconnect
|
|
3206
|
+
// would flip the state.
|
|
3207
|
+
if (client.agentName != null) {
|
|
3208
|
+
shadowEmit({ kind: 'bridgeUp', at: Date.now() })
|
|
3209
|
+
}
|
|
3201
3210
|
client.send({ type: 'status', status: 'agent_connected' })
|
|
3202
3211
|
|
|
3203
3212
|
// #1150: drain any synthetic inbounds queued for this agent while
|
|
@@ -3323,8 +3332,12 @@ const ipcServer: IpcServer = createIpcServer({
|
|
|
3323
3332
|
|
|
3324
3333
|
onClientDisconnected(client: IpcClient) {
|
|
3325
3334
|
process.stderr.write(`telegram gateway: bridge disconnected — agent=${client.agentName}\n`)
|
|
3326
|
-
// Phase 2b shadow: bridge
|
|
3327
|
-
|
|
3335
|
+
// Phase 2b shadow: ONLY emit bridgeDown for the REAL bridge sidecar
|
|
3336
|
+
// (matching the bridgeUp gate above). Anonymous IPC clients
|
|
3337
|
+
// disconnect frequently — those are not bridge flaps.
|
|
3338
|
+
if (client.agentName != null) {
|
|
3339
|
+
shadowEmit({ kind: 'bridgeDown', at: Date.now() })
|
|
3340
|
+
}
|
|
3328
3341
|
|
|
3329
3342
|
// Scope the flush to clients that actually registered as an agent.
|
|
3330
3343
|
// Anonymous one-shot connections (e.g. recall.py's legacy
|
|
@@ -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
|
-
|
|
270
|
-
if
|
|
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
|
|