osborn 0.9.75 → 0.9.76

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.
Files changed (2) hide show
  1. package/dist/index.js +22 -1
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1314,7 +1314,18 @@ async function main() {
1314
1314
  console.log(`🧟 [ZOMBIE-WATCHDOG] room empty for ${emptyPolls} consecutive polls (${emptyForMs / 1000}s) — forcing leave`);
1315
1315
  emptyPolls = 0;
1316
1316
  intentionalLeave = true;
1317
- room.disconnect().catch((e) => console.error('watchdog room.disconnect failed:', e));
1317
+ // Update state FIRST, don't wait for the Disconnected event — a zombie
1318
+ // WS is precisely the case where that event never arrives. Observed
1319
+ // 2026-07-27: after a forced leave the status stayed 'connected', so
1320
+ // /health lied, /connect-room no-op'd, and the next user joined a room
1321
+ // the agent wasn't in (UI stuck at "Connecting..."). Setting idle here
1322
+ // makes the next /connect-room actually rejoin. If the Disconnected
1323
+ // event does fire later, its handler sets the same state — idempotent.
1324
+ livekitState.status = 'idle';
1325
+ const disconnectWatch = setTimeout(() => console.error('🧟 [ZOMBIE-WATCHDOG] room.disconnect() still pending after 10s — WS likely zombied; idle-exit timer is the backstop'), 10_000);
1326
+ room.disconnect()
1327
+ .then(() => { clearTimeout(disconnectWatch); console.log('🧟 [ZOMBIE-WATCHDOG] forced leave completed'); })
1328
+ .catch((e) => { clearTimeout(disconnectWatch); console.error('watchdog room.disconnect failed:', e); });
1318
1329
  // Belt-and-suspenders: if the WS is zombied the Disconnected event may
1319
1330
  // never fire (so the idle-exit arm in that handler won't run). Arm here too.
1320
1331
  armIdleExitTimer('zombie-watchdog forced leave');
@@ -4736,6 +4747,16 @@ async function main() {
4736
4747
  return; // already in the room — no-op
4737
4748
  console.log('🔌 /connect-room — joining LiveKit for incoming user');
4738
4749
  await connectWithRetry();
4750
+ // Join-race fix: ParticipantConnected only fires for participants who join
4751
+ // AFTER us. If the user's browser won the race (its token mint + WebRTC
4752
+ // handshake finished before our rejoin), they're already in the room when
4753
+ // we arrive — no event, no voice session, UI hangs at "Connecting...".
4754
+ // Re-emit the event for every pre-existing participant so the session is
4755
+ // created exactly as if they had joined after us.
4756
+ for (const p of room.remoteParticipants.values()) {
4757
+ console.log(`👥 Participant already in room at join: ${p.identity} — adopting (re-emitting ParticipantConnected)`);
4758
+ room.emit(RoomEvent.ParticipantConnected, p);
4759
+ }
4739
4760
  };
4740
4761
  // 0.9.73: explicit frontend leave should also start the idle-exit countdown —
4741
4762
  // the user is done; the machine shouldn't idle-bill waiting for a maybe-return.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osborn",
3
- "version": "0.9.75",
3
+ "version": "0.9.76",
4
4
  "description": "Voice AI coding assistant - local agent that connects to Osborn frontend",
5
5
  "type": "module",
6
6
  "bin": {