handleBotReply method
Implementation
@override
Future<void> handleBotReply(String roomId, List<Message> messages) async {
if (!botEnabled || messages.isEmpty) return;
final r = room(roomId);
if (r.isEmpty) return;
final bot = _resolveBotProfile(r);
if (bot == null) return;
// ── Guard: leave in progress or cooldown ──
if (_leaveInProgress.contains(roomId)) return;
if (_isOnCooldown(roomId)) return;
final incoming = _latestHumanMessage(messages, bot.id);
if (incoming == null || incoming.type == MessageType.custom) return;
// ── Guard: first entry — snapshot current latest message, skip reply ──
// This prevents the bot from replying to messages that existed before
// it joined this session (e.g. after app restart or room re-entry).
if (!_initializedRooms.contains(roomId)) {
_initializedRooms.add(roomId);
final hasHumanHistory = messages.any((m) {
return m.id != incoming.id &&
m.senderId != bot.id &&
m.type != MessageType.custom &&
!m.isEmpty &&
!m.shouldRemove;
});
if (hasHumanHistory) {
_repliedMessageId[roomId] = incoming.id;
return;
}
}
if (_repliedMessageId[roomId] == incoming.id) return;
if (_inFlight.contains(roomId)) return;
_inFlight.add(roomId);
try {
// Mark bot online & schedule auto-leave
await _setBotOnline(bot.id, true);
_scheduleAutoLeave(roomId, bot);
// ── 1. Simulate reading ──────────────────────────────────────────
await _readDelay(incoming);
// ── 2. Wait if partner is typing ─────────────────────────────────
if (_isHumanTyping(roomId, bot.id)) {
final newMessageArrived = await _waitHumanTyping(
roomId,
bot.id,
incoming.id,
);
if (newMessageArrived) {
// A fresh message landed — the listener will fire a new
// handleBotReply call, so we bail out here.
return;
}
// Partner typed but didn't send. Continue with original message.
}
// ── 3. Think pause ───────────────────────────────────────────────
await _thinkDelay();
// ── 4. Typing ON → generate → typing OFF → send ─────────────────
final request = _buildRequest(bot, messages, incoming);
if (request == null) return;
await _markSeenByBot(roomId, bot.id, incoming);
await _setBotTyping(roomId, bot.id, true);
final reply = await aiDelegate.generate(request);
await _setBotTyping(roomId, bot.id, false);
if (reply == null || reply.trim().isEmpty) return;
_repliedMessageId[roomId] = incoming.id;
await _sendBotReply(roomId, bot.id, reply.trim());
} catch (e, st) {
errorReporter.report(
e,
stackTrace: st,
source: 'BotResponseMixin.handleBotReply',
context: {'roomId': roomId, 'incomingMsgId': incoming.id},
);
await _setBotTyping(roomId, bot.id, false);
} finally {
_inFlight.remove(roomId);
}
}