fix: ACK-accelerated delivery — immediate on ACK, retry once, abort on double fail

Delays 1.5-2.5s (was 3-5s, only for broadcasts now).
DMs: send → ACK → next immediately. No ACK → retry once → abort.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
K7ZVX 2026-05-06 14:10:16 +00:00
commit 34f894ea79
2 changed files with 95 additions and 87 deletions

View file

@ -35,8 +35,8 @@ class ConnectionConfig:
class ResponseConfig: class ResponseConfig:
"""Response behavior settings.""" """Response behavior settings."""
delay_min: float = 3.0 delay_min: float = 1.5
delay_max: float = 5.0 delay_max: float = 2.5
max_length: int = 200 max_length: int = 200
max_messages: int = 3 max_messages: int = 3

View file

@ -24,10 +24,11 @@ class Responder:
destination: Optional[str] = None, destination: Optional[str] = None,
channel: int = 0, channel: int = 0,
) -> bool: ) -> bool:
"""Send response messages with ACK waiting and retry. """Send response messages with ACK-accelerated delivery.
For DMs: waits for ACK before sending next message, retries once on failure. DMs: Send -> wait for ACK -> if ACK, send next immediately.
For broadcasts: uses delay-based pacing (no ACK for broadcasts). If no ACK, retry once -> if still no ACK, abort.
Broadcasts: delay-based pacing (no ACK available).
""" """
if isinstance(messages, str): if isinstance(messages, str):
messages = [messages] messages = [messages]
@ -39,36 +40,44 @@ class Responder:
is_dm = destination is not None is_dm = destination is not None
for i, msg in enumerate(messages): for i, msg in enumerate(messages):
# Randomized delay before sending (except first message) if is_dm and hasattr(self.connector, 'send_and_wait_ack'):
# Send and wait for ACK
ack = await asyncio.get_event_loop().run_in_executor(
None,
self.connector.send_and_wait_ack,
msg, destination, channel, 30.0,
)
if ack:
# ACK received - next message sends immediately (no delay)
logger.debug(f"ACK msg {i+1}/{len(messages)}: {msg[:50]}...")
continue
# No ACK - retry same message once after short pause
logger.warning(f"No ACK for msg {i+1}/{len(messages)}, retrying...")
await asyncio.sleep(2.0)
ack = await asyncio.get_event_loop().run_in_executor(
None,
self.connector.send_and_wait_ack,
msg, destination, channel, 30.0,
)
if ack:
logger.debug(f"ACK on retry msg {i+1}/{len(messages)}")
continue
# Double failure - abort
logger.error(f"No ACK after retry msg {i+1}/{len(messages)}, aborting remaining")
success = False
break
else:
# Broadcasts or fallback: delay-based pacing
if i > 0: if i > 0:
delay = random.uniform(self.config.delay_min, self.config.delay_max) delay = random.uniform(self.config.delay_min, self.config.delay_max)
await asyncio.sleep(delay) await asyncio.sleep(delay)
if is_dm and hasattr(self.connector, 'send_and_wait_ack'):
# DMs: send and wait for ACK
ack = await asyncio.get_event_loop().run_in_executor(
None,
self.connector.send_and_wait_ack,
msg, destination, channel, 30.0,
)
if not ack:
# Retry once
logger.warning(f"No ACK for msg {i+1}/{len(messages)}, retrying...")
await asyncio.sleep(random.uniform(3.0, 5.0))
ack = await asyncio.get_event_loop().run_in_executor(
None,
self.connector.send_and_wait_ack,
msg, destination, channel, 30.0,
)
if not ack:
logger.error(f"No ACK after retry for msg {i+1}/{len(messages)}, skipping remaining")
success = False
break
logger.debug(f"Sent+ACK msg {i+1}/{len(messages)}: {msg[:50]}...")
else:
# Broadcasts or fallback: fire and delay
sent = self.connector.send_message( sent = self.connector.send_message(
text=msg, text=msg,
destination=destination, destination=destination,
@ -82,4 +91,3 @@ class Responder:
logger.debug(f"Sent msg {i+1}/{len(messages)}: {msg[:50]}...") logger.debug(f"Sent msg {i+1}/{len(messages)}: {msg[:50]}...")
return success return success