osborn 0.9.75 → 0.9.77
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/index.js +44 -3
- package/package.json +1 -1
- package/test-results/.last-run.json +4 -0
package/dist/index.js
CHANGED
|
@@ -1169,8 +1169,28 @@ async function main() {
|
|
|
1169
1169
|
console.log(`🎯 DIRECT MODE: ${directConfig.stt.provider} STT → Claude Agent SDK → ${directConfig.tts.provider} TTS`);
|
|
1170
1170
|
console.log(' 🔥 Full coding capabilities!');
|
|
1171
1171
|
}
|
|
1172
|
-
// Determine room code
|
|
1173
|
-
|
|
1172
|
+
// Determine room code. STABLE PER MACHINE: persisted to ~/.osborn/room-code
|
|
1173
|
+
// (the volume on cloud machines) so process restarts don't rotate the room.
|
|
1174
|
+
// Rotation raced any client that fetched /room-code just before a restart —
|
|
1175
|
+
// observed 2026-07-28: tester joined osborn-bz2m8n while the rebooted agent
|
|
1176
|
+
// created fnz7nz; the client sat on "Connecting..." forever. Idle-exit
|
|
1177
|
+
// auto-stop/start cycles (0.9.73+) made this race common.
|
|
1178
|
+
const roomCodeFile = join(homedir(), '.osborn', 'room-code');
|
|
1179
|
+
let persistedRoomCode = null;
|
|
1180
|
+
try {
|
|
1181
|
+
persistedRoomCode = readFileSync(roomCodeFile, 'utf8').trim() || null;
|
|
1182
|
+
}
|
|
1183
|
+
catch { /* first boot */ }
|
|
1184
|
+
const roomCode = cliArgs.roomCode || persistedRoomCode || generateRoomCode();
|
|
1185
|
+
if (!cliArgs.roomCode && roomCode !== persistedRoomCode) {
|
|
1186
|
+
try {
|
|
1187
|
+
mkdirSync(dirname(roomCodeFile), { recursive: true });
|
|
1188
|
+
writeFileSync(roomCodeFile, roomCode);
|
|
1189
|
+
}
|
|
1190
|
+
catch (e) {
|
|
1191
|
+
console.warn('⚠️ could not persist room code (rotation race protection disabled):', e instanceof Error ? e.message : e);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1174
1194
|
currentRoomCode = roomCode;
|
|
1175
1195
|
const roomName = `osborn-${roomCode}`;
|
|
1176
1196
|
if (cliArgs.roomCode) {
|
|
@@ -1314,7 +1334,18 @@ async function main() {
|
|
|
1314
1334
|
console.log(`🧟 [ZOMBIE-WATCHDOG] room empty for ${emptyPolls} consecutive polls (${emptyForMs / 1000}s) — forcing leave`);
|
|
1315
1335
|
emptyPolls = 0;
|
|
1316
1336
|
intentionalLeave = true;
|
|
1317
|
-
|
|
1337
|
+
// Update state FIRST, don't wait for the Disconnected event — a zombie
|
|
1338
|
+
// WS is precisely the case where that event never arrives. Observed
|
|
1339
|
+
// 2026-07-27: after a forced leave the status stayed 'connected', so
|
|
1340
|
+
// /health lied, /connect-room no-op'd, and the next user joined a room
|
|
1341
|
+
// the agent wasn't in (UI stuck at "Connecting..."). Setting idle here
|
|
1342
|
+
// makes the next /connect-room actually rejoin. If the Disconnected
|
|
1343
|
+
// event does fire later, its handler sets the same state — idempotent.
|
|
1344
|
+
livekitState.status = 'idle';
|
|
1345
|
+
const disconnectWatch = setTimeout(() => console.error('🧟 [ZOMBIE-WATCHDOG] room.disconnect() still pending after 10s — WS likely zombied; idle-exit timer is the backstop'), 10_000);
|
|
1346
|
+
room.disconnect()
|
|
1347
|
+
.then(() => { clearTimeout(disconnectWatch); console.log('🧟 [ZOMBIE-WATCHDOG] forced leave completed'); })
|
|
1348
|
+
.catch((e) => { clearTimeout(disconnectWatch); console.error('watchdog room.disconnect failed:', e); });
|
|
1318
1349
|
// Belt-and-suspenders: if the WS is zombied the Disconnected event may
|
|
1319
1350
|
// never fire (so the idle-exit arm in that handler won't run). Arm here too.
|
|
1320
1351
|
armIdleExitTimer('zombie-watchdog forced leave');
|
|
@@ -4736,6 +4767,16 @@ async function main() {
|
|
|
4736
4767
|
return; // already in the room — no-op
|
|
4737
4768
|
console.log('🔌 /connect-room — joining LiveKit for incoming user');
|
|
4738
4769
|
await connectWithRetry();
|
|
4770
|
+
// Join-race fix: ParticipantConnected only fires for participants who join
|
|
4771
|
+
// AFTER us. If the user's browser won the race (its token mint + WebRTC
|
|
4772
|
+
// handshake finished before our rejoin), they're already in the room when
|
|
4773
|
+
// we arrive — no event, no voice session, UI hangs at "Connecting...".
|
|
4774
|
+
// Re-emit the event for every pre-existing participant so the session is
|
|
4775
|
+
// created exactly as if they had joined after us.
|
|
4776
|
+
for (const p of room.remoteParticipants.values()) {
|
|
4777
|
+
console.log(`👥 Participant already in room at join: ${p.identity} — adopting (re-emitting ParticipantConnected)`);
|
|
4778
|
+
room.emit(RoomEvent.ParticipantConnected, p);
|
|
4779
|
+
}
|
|
4739
4780
|
};
|
|
4740
4781
|
// 0.9.73: explicit frontend leave should also start the idle-exit countdown —
|
|
4741
4782
|
// the user is done; the machine shouldn't idle-bill waiting for a maybe-return.
|
package/package.json
CHANGED