osborn 0.9.79 → 0.9.81

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 +43 -4
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1237,6 +1237,8 @@ async function main() {
1237
1237
  // Track state
1238
1238
  let pendingSessionClose = null; // Tracks async session close for reconnect safety
1239
1239
  let currentSession = null;
1240
+ // 0.9.81: callable handler reference — see registration site for why.
1241
+ let participantConnectedHandler = null;
1240
1242
  let currentAgent = null; // For updateChatCtx() context injection
1241
1243
  let currentLLM = null;
1242
1244
  // Agent-side "alone in room" leave timer (see Room-presence lifecycle note up
@@ -1361,6 +1363,36 @@ async function main() {
1361
1363
  console.error('🧟 [ZOMBIE-WATCHDOG] poll error:', err instanceof Error ? err.message : err);
1362
1364
  }
1363
1365
  }, WATCHDOG_POLL_MS);
1366
+ // ── 0.9.80: Event-loss adopter (poll-based) ──
1367
+ // After a leave/rejoin cycle the same Room instance can stop delivering
1368
+ // ParticipantConnected entirely — observed 2026-07-28: the leave-room guard
1369
+ // saw "1 participant(s) still in room" while the agent sat at "Waiting for
1370
+ // user to connect" (state knew, events never fired; user stuck on
1371
+ // "Connecting..." forever). Same blind-spot family as the zombie watchdog:
1372
+ // never trust the event stream alone. Every 5s: participant present but no
1373
+ // voice session → re-emit ParticipantConnected (60s per-identity cooldown
1374
+ // so a slow in-flight session creation isn't double-fired).
1375
+ const adoptAttempts = new Map();
1376
+ setInterval(() => {
1377
+ try {
1378
+ if (livekitState.status !== 'connected')
1379
+ return;
1380
+ if (currentSession)
1381
+ return;
1382
+ const p = room.remoteParticipants.values().next().value;
1383
+ if (!p)
1384
+ return;
1385
+ const last = adoptAttempts.get(p.identity) ?? 0;
1386
+ if (Date.now() - last < 60_000)
1387
+ return;
1388
+ adoptAttempts.set(p.identity, Date.now());
1389
+ console.log(`šŸ“„ [ADOPT-POLL] ${p.identity} present with no session — invoking ParticipantConnected handler directly`);
1390
+ participantConnectedHandler?.(p).catch((e) => console.error('šŸ“„ [ADOPT-POLL] handler error:', e instanceof Error ? e.message : e));
1391
+ }
1392
+ catch (err) {
1393
+ console.error('šŸ“„ [ADOPT-POLL] error:', err instanceof Error ? err.message : err);
1394
+ }
1395
+ }, 5_000);
1364
1396
  /**
1365
1397
  * Hard-kill the in-flight Claude SDK query AND the persistent subprocess.
1366
1398
  *
@@ -3037,7 +3069,13 @@ async function main() {
3037
3069
  console.error('āŒ Reconnect attempt threw (should not happen — connectWithRetry loops):', err);
3038
3070
  });
3039
3071
  });
3040
- room.on(RoomEvent.ParticipantConnected, async (participant) => {
3072
+ // 0.9.81: handler extracted to a named reference so the adopt-poll and
3073
+ // /connect-room adopt can CALL it directly. Synthetic `room.emit(...)` does
3074
+ // NOT reach listeners on rtc-node's Room (observed 2026-07-28: ADOPT-POLL
3075
+ // re-emitted for three successive participants, zero sessions created) —
3076
+ // the event dispatch is internal, so direct invocation is the only
3077
+ // reliable path for state-driven adoption.
3078
+ participantConnectedHandler = async (participant) => {
3041
3079
  console.log(`\nšŸ‘¤ User joined: ${participant.identity}`);
3042
3080
  // A user is present — cancel any pending agent-side "alone" leave.
3043
3081
  if (aloneTimer) {
@@ -3899,7 +3937,8 @@ async function main() {
3899
3937
  catch (err) {
3900
3938
  console.error('āŒ Failed to start session:', err);
3901
3939
  }
3902
- });
3940
+ };
3941
+ room.on(RoomEvent.ParticipantConnected, (p) => { participantConnectedHandler?.(p); });
3903
3942
  room.on(RoomEvent.ParticipantDisconnected, (participant) => {
3904
3943
  console.log(`šŸ‘‹ User left: ${participant.identity}`);
3905
3944
  // Full cleanup — stop all background work to avoid accumulating API usage
@@ -4780,8 +4819,8 @@ async function main() {
4780
4819
  // Re-emit the event for every pre-existing participant so the session is
4781
4820
  // created exactly as if they had joined after us.
4782
4821
  for (const p of room.remoteParticipants.values()) {
4783
- console.log(`šŸ‘„ Participant already in room at join: ${p.identity} — adopting (re-emitting ParticipantConnected)`);
4784
- room.emit(RoomEvent.ParticipantConnected, p);
4822
+ console.log(`šŸ‘„ Participant already in room at join: ${p.identity} — adopting (invoking handler directly)`);
4823
+ participantConnectedHandler?.(p).catch((e) => console.error('šŸ‘„ adopt handler error:', e instanceof Error ? e.message : e));
4785
4824
  }
4786
4825
  };
4787
4826
  // 0.9.73: explicit frontend leave should also start the idle-exit countdown —
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osborn",
3
- "version": "0.9.79",
3
+ "version": "0.9.81",
4
4
  "description": "Voice AI coding assistant - local agent that connects to Osborn frontend",
5
5
  "type": "module",
6
6
  "bin": {