fix(telegram+approval): TG-1 + AP-1/2/3 — 4 修 Telegram UX
Some checks failed
CD Pipeline / build-and-deploy (push) Failing after 25m27s
Ansible Lint / lint (push) Has been cancelled

2026-04-19 凌晨(台北時區)— ogt + Claude Opus 4.7 (1M)

## TG-1: INFO_ACTIONS 加 view
security_interceptor.py — 'view' 按鈕現在走 2-part 讀格式,
不再誤觸發 4-part nonce 寫格式。

## AP-1: approval_records.telegram_message_id 持久化
telegram_gateway.send_approval_card send 成功後,在 DB 層 UPDATE
approval_records SET telegram_message_id, telegram_chat_id
(不只 Redis, Pod 重啟仍可找回原卡片)。

## AP-2: approval 執行完成原卡片 edit + KM/Playbook 增量
approval_execution._push_execution_result_to_alert 除了 reply 原卡片,
還 editMessageReplyMarkup 移除按鈕(修「永遠執行中」卡片問題)。
  - 同步查 knowledge_entries/playbooks 2min 內增量,附加到訊息
    顯示 "📚 KM +N  🎯 Playbook 更新×M"
  - 成功:  執行成功 + action + KM 增量
  - 失敗:  執行失敗 + 原因 + KM 增量

## AP-3: primary_responsibility 正規化降「 未知」比例
openclaw._parse_analysis_result: 若 LLM 填空/None/不在白名單
(FE/BE/INFRA/DB/COLLAB),強制 fallback: kubectl 關鍵字有 → INFRA,
否則 BE。之前只檢查 "not in data" 但 None 或空字串會穿過。

## 跳過: TG-3 (refactor) + TG-5 (webhook 為棄用 endpoint,design 採 Long Polling)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
OG T
2026-04-19 01:15:58 +08:00
parent 68a42a3c97
commit 4b8be32610
3 changed files with 79 additions and 3 deletions

View File

@@ -496,11 +496,53 @@ class ApprovalExecutionService:
settings = get_settings()
gateway = get_telegram_gateway()
# 2026-04-19 ogt + Claude Opus 4.7 修 AP-2: 除了 reply 外,
# 也 edit 原卡片移除按鈕 + 更新狀態戳記(避免卡片永遠停在「執行中」)
try:
await gateway._send_request("editMessageReplyMarkup", {
"chat_id": settings.OPENCLAW_TG_CHAT_ID,
"message_id": orig_msg_id,
"reply_markup": {"inline_keyboard": []},
})
except Exception as _edit_e:
logger.debug("push_execution_edit_buttons_failed",
approval_id=str(approval.id), error=str(_edit_e))
# 附加 KM/Playbook 增量(查最近該 incident 的 KM + playbook 使用)
km_info = ""
try:
from sqlalchemy import text as _sql
from src.db.base import get_db_context
async with get_db_context() as _db:
_km_row = await _db.execute(
_sql("""SELECT COUNT(*) FROM knowledge_entries
WHERE created_at > NOW() - interval '2 minutes'"""),
)
_km_count = _km_row.scalar() or 0
_pb_row = await _db.execute(
_sql("""SELECT COUNT(*) FROM playbooks
WHERE updated_at > NOW() - interval '2 minutes'"""),
)
_pb_count = _pb_row.scalar() or 0
if _km_count or _pb_count:
km_info = f"\n📚 KM +{_km_count} 🎯 Playbook 更新×{_pb_count}"
except Exception:
pass
if success:
text = f"✅ <b>執行成功</b>\n<code>{(approval.action or '')[:180]}</code>"
text = (
f"✅ <b>執行成功</b>\n"
f"<code>{(approval.action or '')[:180]}</code>"
f"{km_info}"
)
else:
err_short = (error or "未知錯誤")[:150]
text = f"❌ <b>執行失敗</b>\n<code>{(approval.action or '')[:180]}</code>\n原因: {err_short}"
text = (
f"❌ <b>執行失敗</b>\n"
f"<code>{(approval.action or '')[:180]}</code>\n"
f"原因: {err_short}"
f"{km_info}"
)
await gateway._http_client.post(
f"https://api.telegram.org/bot{settings.OPENCLAW_TG_BOT_TOKEN}/sendMessage",

View File

@@ -1269,7 +1269,12 @@ class OpenClawService:
data["confidence"] = 0.0 # 截斷/缺失 → 0.0,不可偽造
if "risk_level" not in data:
data["risk_level"] = "low"
if "primary_responsibility" not in data:
# 2026-04-19 ogt + Claude Opus 4.7 修 AP-3:
# primary_responsibility 有時 LLM 填空字串/None → resp_display 顯示「❓ 未知」
# 強制正規化: 空/None/不在白名單 → 用 kubectl 有無推 INFRA 或 BE (非「未知」)
_valid_resp = {"FE", "BE", "INFRA", "DB", "COLLAB"}
_cur_resp = str(data.get("primary_responsibility") or "").strip().upper()
if _cur_resp not in _valid_resp:
data["primary_responsibility"] = "INFRA" if "kubectl" in str(data) else "BE"
if "suggested_action" not in data:
data["suggested_action"] = "RESTART_DEPLOYMENT" if "restart" in str(data).lower() else "NO_ACTION"

View File

@@ -1717,6 +1717,35 @@ class TelegramGateway:
except Exception as _db_e:
logger.warning("notification_outcomes_db_write_failed", error=str(_db_e))
# 2026-04-19 ogt + Claude Opus 4.7: 修 AP-1 — message_id 同時存進
# approval_records.telegram_message_id,不只 Redis(重啟會丟)
if _msg_id:
try:
from src.services.approval_db import get_approval_service
_svc = get_approval_service()
if hasattr(_svc, "update_telegram_message"):
# 若有 update_telegram_message 方法(通常用 incident_id)
# 先用 incident_id 更新,再 fallback 直接 UPDATE approval_records
from sqlalchemy import text as _sql2
from src.db.base import get_db_context as _gdc
async with _gdc() as _db2:
await _db2.execute(
_sql2("""
UPDATE approval_records
SET telegram_message_id = :mid,
telegram_chat_id = :cid
WHERE id = :aid
"""),
{
"mid": int(_msg_id),
"cid": int(settings.OPENCLAW_TG_CHAT_ID),
"aid": str(approval_id),
},
)
except Exception as _db_e2:
logger.warning("approval_tg_msg_id_db_persist_failed",
approval_id=str(approval_id), error=str(_db_e2))
# 2026-04-10 Claude Sonnet 4.6 Asia/Taipei: 儲存 message_id 供自動修復後更新卡片
# key: tg_approval:{approval_id}TTL 24h
if _msg_id: