chore: save WIP changes before audit fixes

- notifications: Telegram integration, richer stage/agent/QG notifications
- plane_sync: explicit Plane state IDs, needs_input/in_review/blocked helpers, links in comments
- launcher: deployer stage, model flag (opus), PR auto-create, REQUEST_CHANGES/tester/architect rollback+retry logic, partial check_reviewer_verdict path
- qg/checks: add check_reviewer_verdict (substring-based, will be hardened in S-5)
- stages: review->check_reviewer_verdict, testing->deployer agent
- config: telegram_bot_token/chat_id settings
This commit is contained in:
Dev Agent
2026-06-02 19:57:43 +03:00
parent 8715dd7148
commit f575f6bc6a
6 changed files with 538 additions and 33 deletions

View File

@@ -1,28 +1,125 @@
"""Notifications and logging for orchestrator events."""
import logging
import httpx
logger = logging.getLogger("orchestrator")
# Lazy import to avoid circular imports at module level
_settings = None
def _get_settings():
global _settings
if _settings is None:
from .config import settings
_settings = settings
return _settings
def send_telegram(text: str):
"""Send notification to Telegram. Fire-and-forget, never raises."""
s = _get_settings()
if not s.telegram_bot_token or not s.telegram_chat_id:
return
try:
url = f"https://api.telegram.org/bot{s.telegram_bot_token}/sendMessage"
httpx.post(
url,
json={
"chat_id": s.telegram_chat_id,
"text": text,
"parse_mode": "HTML",
"disable_notification": False,
},
timeout=5,
)
except Exception:
pass # Never crash orchestrator due to notification failure
def _get_work_item_id(task_id: int) -> str:
"""Get work_item_id from DB by task_id."""
try:
from .db import get_db
conn = get_db()
row = conn.execute("SELECT work_item_id FROM tasks WHERE id=?", (task_id,)).fetchone()
conn.close()
return row[0] if row and row[0] else f"task-{task_id}"
except Exception:
return f"task-{task_id}"
def notify_stage_change(task_id: int, old_stage: str, new_stage: str, agent: str = None):
"""Log stage transition."""
msg = f"Task {task_id}: {old_stage}{new_stage}"
"""Log and notify stage transition."""
work_item_id = _get_work_item_id(task_id)
msg = f"\U0001f504 {work_item_id}: {old_stage} \u2192 {new_stage}"
if agent:
msg += f" (launching {agent})"
msg += f" (\u0437\u0430\u043f\u0443\u0449\u0435\u043d {agent})"
logger.info(msg)
send_telegram(msg)
def notify_agent_started(run_id: int, agent: str, task_id: int):
"""Notify agent launch."""
work_item_id = _get_work_item_id(task_id)
msg = f"\U0001f680 {work_item_id}: {agent} \u0437\u0430\u043f\u0443\u0449\u0435\u043d (run_id={run_id})"
logger.info(msg)
send_telegram(msg)
def notify_agent_finished(run_id: int, agent: str, exit_code: int, task_id: int = None, duration_s: int = None):
"""Notify agent completion."""
work_item_id = _get_work_item_id(task_id) if task_id else "?"
if exit_code == 0:
dur = f" ({duration_s // 60} \u043c\u0438\u043d)" if duration_s else ""
msg = f"\u2705 {work_item_id}: {agent} \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043b{dur}"
elif exit_code == -9:
msg = f"\u23f0 {work_item_id}: {agent} \u0443\u0431\u0438\u0442 \u043f\u043e \u0442\u0430\u0439\u043c\u0430\u0443\u0442\u0443 (30 \u043c\u0438\u043d)"
else:
msg = f"\u274c {work_item_id}: {agent} \u0443\u043f\u0430\u043b (exit_code={exit_code})"
logger.info(msg)
send_telegram(msg)
def notify_qg_result(task_id: int, check: str, passed: bool, reason: str = None):
"""Notify QG check result."""
work_item_id = _get_work_item_id(task_id)
if passed:
msg = f"\u2705 {work_item_id}: QG {check} \u2014 passed"
else:
msg = f"\u26a0\ufe0f {work_item_id}: QG {check} \u2014 failed: {reason}"
logger.info(msg)
send_telegram(msg)
def notify_qg_failure(task_id: int, stage: str, check: str, reason: str):
"""Log QG check failure."""
logger.warning(f"Task {task_id}: QG failed at stage '{stage}', check={check}: {reason}")
"""Log and notify QG check failure."""
work_item_id = _get_work_item_id(task_id)
msg = f"\u26a0\ufe0f {work_item_id}: QG {check} \u2014 failed: {reason}"
logger.warning(msg)
send_telegram(msg)
def notify_agent_finished(run_id: int, agent: str, exit_code: int):
"""Log agent completion."""
logger.info(f"Agent run {run_id} ({agent}) finished with exit code {exit_code}")
def notify_approve_requested(task_id: int):
"""Notify that analyst requests :approved:."""
work_item_id = _get_work_item_id(task_id)
msg = f"\U0001f4cb {work_item_id}: BRD/\u0422\u0417/AC \u0433\u043e\u0442\u043e\u0432\u044b. \u0416\u0434\u0443 :approved: \u0432 Plane"
logger.info(msg)
send_telegram(msg)
def notify_done(task_id: int):
"""Notify task completion."""
work_item_id = _get_work_item_id(task_id)
msg = f"\U0001f389 {work_item_id}: \u0437\u0430\u0434\u0430\u0447\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0430!"
logger.info(msg)
send_telegram(msg)
def notify_error(task_id: int, error: str):
"""Log error for a task."""
logger.error(f"Task {task_id}: ERROR — {error}")
"""Log and notify error for a task."""
work_item_id = _get_work_item_id(task_id) if task_id else "system"
msg = f"\U0001f534 {work_item_id}: ERROR \u2014 {error}"
logger.error(msg)
send_telegram(msg)