diff --git a/harness/tg_inbox.py b/harness/tg_inbox.py index b6434ef..cd93c1e 100644 --- a/harness/tg_inbox.py +++ b/harness/tg_inbox.py @@ -176,17 +176,29 @@ def notify_chat(text: str): def ladder_snapshot() -> str: - """Срез лестницы для Telegram: уровни, занятость, задачи, эскалации.""" + """Срез лестницы для Telegram: уровни, занятость, задачи, эскалации. + + Занятость: busy в read model всегда False (истинное состояние живёт + в памяти планировщика, событий для него нет), поэтому выводим её из + running-задач: TaskClaimed закрепляет уровень за задачей в проекции. + Уровень без задачи (чистый дайджест-тик) здесь выглядит как «ожидает» — + приемлемо: дайджест-тик короткий, а взятие задачи — главный кейс. + """ model = es.run_projections() cs = model.get("cycle_state") or {} + tasks_all = list((model.get("tasks") or {}).values()) + running_levels = {t.get("level") for t in tasks_all if t.get("status") == "running"} order = ["sec", "min", "hour", "day"] lines = [] for name in order: st = cs.get(name) or {} - busy = "🔧 занят" if st.get("busy") else "ожидает" + if name in running_levels: + busy = "🔧 занят (running-задача)" + else: + busy = "ожидает" lines.append(f"{'🔹' if name != 'sec' else '⚡'} {name}: {busy}, тиков {st.get('ticks', 0)}" + (f", ошибок {st['errors']}" if st.get("errors") else "")) - tasks = list((model.get("tasks") or {}).values()) + tasks = tasks_all tasks.sort(key=lambda t: t.get("created") or "", reverse=True) board = [t for t in tasks if t.get("status") in ("ready", "running")] lines.append(f"📋 готово/в работе: {sum(1 for t in tasks if t['status'] == 'ready')}/"