feat: thread_has_agent_participation + ACK reply linkage

1. thread_has_agent_participation (SOWA Priority 11):
   - New function has_agent_chat_participation() in behavior_policy.py
   - Checks if agent responded to ANY user in this chat within 30min
   - When active + user asks question/imperative → agent responds
   - Different from per-user conversation_context (Priority 12)
   - Wired into both detect_explicit_request() and analyze_message()

2. ACK reply_to_message_id:
   - When SOWA sends ACK ("NUTRA тут"), it now replies to the user's
     message instead of sending a standalone message
   - Better UX: visually linked to what the user wrote
   - Uses allow_sending_without_reply=True for safety

Known issue (not fixed - too risky):
- Lines 1368-1639 in http_api.py are dead code (brand commands /бренд)
  at incorrect indentation level (8 spaces, inside unreachable block)
- These commands never worked on NODE1, fixing 260 lines of indentation
  carries regression risk — deferred to separate cleanup PR

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Apple
2026-02-09 09:24:00 -08:00
parent 1f4472ec18
commit 27e66b90bf
2 changed files with 32 additions and 4 deletions

View File

@@ -41,6 +41,7 @@ from behavior_policy import (
record_ack,
get_ack_text,
is_prober_request,
has_agent_chat_participation,
NO_OUTPUT,
BehaviorDecision,
AGENT_NAME_VARIANTS,
@@ -2026,6 +2027,9 @@ async def handle_telegram_webhook(
mentioned_agents.append(aid)
break
# Gateway: check if agent has been active in this chat recently (any user)
agent_active_in_chat = has_agent_chat_participation(agent_config.agent_id, chat_id)
# Gateway: compute has_explicit_request (single source of truth)
# CONTRACT: imperative OR (? AND (dm OR reply OR mention OR thread))
has_explicit_request = detect_explicit_request(
@@ -2033,7 +2037,7 @@ async def handle_telegram_webhook(
is_dm=is_private_chat,
is_reply_to_agent=is_reply_to_agent,
mentioned_agents=mentioned_agents,
thread_has_agent_participation=False, # REQUIRED, fail-closed default
thread_has_agent_participation=agent_active_in_chat,
)
# Check if this is a prober request (chat_id=0 or user_id=0)
@@ -2051,7 +2055,7 @@ async def handle_telegram_webhook(
payload_explicit_request=has_explicit_request,
payload_has_link=has_link,
is_reply_to_agent=is_reply_to_agent,
thread_has_agent_participation=False, # TODO: track per thread
thread_has_agent_participation=agent_active_in_chat,
)
respond_decision = sowa_decision.should_respond
respond_reason = sowa_decision.reason
@@ -2088,10 +2092,16 @@ async def handle_telegram_webhook(
try:
url = f"https://api.telegram.org/bot{token}/sendMessage"
async with httpx.AsyncClient(timeout=30) as client:
resp = await client.post(url, json={
ack_payload = {
"chat_id": chat_id,
"text": ack_text,
})
}
# Link ACK to the user's message for better UX
msg_id = update.message.get("message_id")
if msg_id:
ack_payload["reply_to_message_id"] = msg_id
ack_payload["allow_sending_without_reply"] = True
resp = await client.post(url, json=ack_payload)
if resp.status_code == 200:
logger.info(f"\U0001f44b ACK sent to chat {chat_id}: {ack_text}")
else: