👍 на отвеченные сообщения (правка Коди t-261018103940) + тест; мусорный итог задачи не принимается
Коди сам доработал tg_inbox (задача Alex 10:39): после ответа ставит реакцию на каждое отвеченное сообщение (одиночный и сводный ответ), нашёл, что ✅ Telegram не принимает (REACTION_EMPTY) — взял 👍. Правка лежала незакоммиченной и без теста, а задачу он закрыл с итогом «x» до того, как доделал (Alex пришло «✅ Готово … x»). - коммит правки Коди как есть; - тест (test_tg_group_ux +8): 👍 на сообщение, весь батч, портал без реакции, сбой реакции не ломает ответ; - complete_task_with_delivery: итог короче 10 символов («x», «2», «готово» — 3 из 338 закрытий с октября) отклоняется с подсказкой «опиши, что сделано и где результат». Полный набор — без регрессий. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
09089902e9
commit
70a2015bae
3 changed files with 77 additions and 0 deletions
|
|
@ -260,6 +260,9 @@ def looks_like_budget_stub(answer: str) -> bool:
|
|||
return bool(_BUDGET_STUB_RE.search(s) and _BUDGET_STUB_MARKERS_RE.search(s))
|
||||
|
||||
|
||||
MIN_ANSWER = 10 # символов в итоге задачи: короче — не результат («x», «готово»)
|
||||
|
||||
|
||||
def complete_task_with_delivery(task_id: str, answer: str, level: str,
|
||||
result_ref: str = None):
|
||||
"""TaskCompleted + немедленная доставка результата в TG-чат.
|
||||
|
|
@ -279,6 +282,12 @@ def complete_task_with_delivery(task_id: str, answer: str, level: str,
|
|||
raise ValueError(f"задача {task_id} в статусе {t['status']} — завершать можно только running")
|
||||
if (answer or "").strip() == "":
|
||||
raise ValueError("пустой результат — доставлять нечего, заверши как failed/requeue")
|
||||
# мусорный итог (2026-10-18: задача закрыта с answer «x» — Alex получил в чат
|
||||
# «✅ Готово … x»; раньше — «2», «готово»): результат должен говорить, что сделано
|
||||
if len((answer or "").strip()) < MIN_ANSWER:
|
||||
raise ValueError(
|
||||
f"результат «{answer.strip()}» слишком короткий (< {MIN_ANSWER} симв.): опиши, что "
|
||||
"сделано и где результат (файл/ссылка/вывод) — это уйдёт Alex в чат")
|
||||
if looks_like_budget_stub(answer):
|
||||
requeue_task(task_id, reason="guard: 'бюджет исчерпан' — не результат, задача в ready (инцидент e-261001074527)")
|
||||
raise ValueError(
|
||||
|
|
|
|||
|
|
@ -541,9 +541,28 @@ def send_reply(msg_id: str, text: str, timeout: int = 35):
|
|||
"to_chat": m["chat_id"], "text": text[:2000], "tg_ok": bool(r.get("ok")),
|
||||
"channel": "tg",
|
||||
})
|
||||
if r.get("ok"):
|
||||
mark_answered(m)
|
||||
return msg_id
|
||||
|
||||
|
||||
def mark_answered(m: dict):
|
||||
"""✅ на входящее, на которое отвечено (t-261018103940): видно, какие
|
||||
сообщения батча закрыты. Best effort: недоступность TG не мешает ответу
|
||||
(ReplySent уже записан — повторно не ставим, т.к. replied-гейт выше).
|
||||
Только TG-чаты; реакция ✅ заменяет маркер «взято» 👀."""
|
||||
chat_id = m.get("chat_id")
|
||||
if not chat_id or str(chat_id).startswith(("portal", "tui:")):
|
||||
return
|
||||
# ✅ не входит в допустимый Telegram набор реакций → 400 REACTION_EMPTY
|
||||
# (Alex не видел галочку; проверено 2026-10-18). Валидный маркер — 👍.
|
||||
tg_mid = m.get("message_id") or (m.get("update_id") or 0) % 100000
|
||||
try:
|
||||
set_reaction(chat_id, tg_mid, emoji="👍")
|
||||
except Exception:
|
||||
pass # реакция — только визуальный маркер, не блокирует ответ
|
||||
|
||||
|
||||
def send_followup(msg_id: str, text: str, timeout: int = 35) -> bool:
|
||||
"""Досылка результата в ЧАТ входящего, на которое уже ответили (аудит 2026-10-16).
|
||||
|
||||
|
|
@ -688,6 +707,8 @@ def send_batch_reply(msg_ids: list, text: str, timeout: int = 35):
|
|||
"tg_ok": bool(r.get("ok")),
|
||||
"batch": {"id": batch_id, "size": len(pending), "head": head_mid},
|
||||
})
|
||||
if r.get("ok"):
|
||||
mark_answered(m)
|
||||
return head_mid
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -72,5 +72,52 @@ check("правка принятого сообщения — не новое",
|
|||
check("новое сообщение — новое",
|
||||
not tg_inbox._already_received({"chat": {"id": G}, "message_id": 9999}))
|
||||
|
||||
# ── 👍 на отвеченное (правка Коди, t-261018103940: ✅ Telegram не принимает) ──
|
||||
def reactions():
|
||||
return [p for m, p in API if m == "setMessageReaction"]
|
||||
|
||||
|
||||
for uid, mid in ((10, 3001), (11, 3002), (12, 3003)):
|
||||
es.append_event("inbox", f"m-{uid}", "MessageReceived", {
|
||||
"update_id": uid, "chat_id": P, "chat_type": "private", "message_id": mid,
|
||||
"text": "вопрос", "kind": "text", "first_name": "Alex"})
|
||||
API.clear()
|
||||
tg_inbox.send_reply("m-10", "ответ")
|
||||
r = reactions()
|
||||
check("ответ → 👍 на это сообщение", r and r[-1]["message_id"] == 3001
|
||||
and json.loads(r[-1]["reaction"]) == [{"type": "emoji", "emoji": "👍"}], r)
|
||||
API.clear()
|
||||
tg_inbox.send_batch_reply(["m-11", "m-12"], "сводный")
|
||||
check("батч → 👍 на каждое сообщение", sorted(p["message_id"] for p in reactions()) == [3002, 3003])
|
||||
es.append_event("inbox", "m-13", "MessageReceived", {"update_id": 13, "chat_id": "portal:host",
|
||||
"text": "x", "kind": "text"})
|
||||
API.clear()
|
||||
tg_inbox.mark_answered({"chat_id": "portal:host", "message_id": 1})
|
||||
check("портал — без реакции", not reactions())
|
||||
es.append_event("inbox", "m-14", "MessageReceived", {"update_id": 14, "chat_id": P,
|
||||
"chat_type": "private", "message_id": 3004, "text": "вопрос", "kind": "text"})
|
||||
_orig = tg_inbox.set_reaction
|
||||
tg_inbox.set_reaction = lambda *a, **k: (_ for _ in ()).throw(RuntimeError("TG 400"))
|
||||
try:
|
||||
tg_inbox.send_reply("m-14", "ответ при упавшей реакции")
|
||||
ok = es.run_projections()["inbox"]["m-14"].get("replied") is True
|
||||
except Exception as e:
|
||||
ok = False
|
||||
check("сбой реакции не ломает ответ", ok)
|
||||
tg_inbox.set_reaction = _orig
|
||||
|
||||
# ── мусорный итог задачи не принимается (2026-10-18: «x» ушёл Alex в чат) ──
|
||||
from harness import commands
|
||||
es.append_event("task", "t-x", "TaskSubmitted", {"id": "t-x", "title": "задача", "size": "S"})
|
||||
es.append_event("task", "t-x", "TaskClaimed", {"level": "min"})
|
||||
for junk in ("x", "готово", "2"):
|
||||
try:
|
||||
commands.complete_task_with_delivery("t-x", junk, level="min")
|
||||
check(f"итог «{junk}» отклонён", False)
|
||||
except ValueError:
|
||||
check(f"итог «{junk}» отклонён", True)
|
||||
tid, _ = commands.complete_task_with_delivery("t-x", "Реакция 👍 ставится после ответа", level="min")
|
||||
check("нормальный итог принят", es.run_projections()["tasks"]["t-x"]["status"] == "done")
|
||||
|
||||
print(f"\n{'OK' if not fails else 'FAILED'}: {len(fails)} fail(s)")
|
||||
sys.exit(1 if fails else 0)
|
||||
|
|
|
|||
Loading…
Reference in a new issue