fix(telegram+approval): TG-1 + AP-1/2/3 — 4 修 Telegram UX
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:
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user