diff --git a/apps/api/src/services/telegram_gateway.py b/apps/api/src/services/telegram_gateway.py index 423d26f7..b96f6797 100644 --- a/apps/api/src/services/telegram_gateway.py +++ b/apps/api/src/services/telegram_gateway.py @@ -2212,7 +2212,7 @@ class TelegramGateway: return {"inline_keyboard": buttons} - # ── YAML Fallback 路徑(原有邏輯,不改動任何行為)──────────────────── + # ── YAML Fallback 路徑(保留既有 callback 佈局,另補 AwoooP evidence deep link)──── # 2026-04-14 Claude Sonnet 4.6 (Phase 5 Sprint 5.4): # 從 callback_action_spec registry 動態產生按鈕(原 _CATEGORY_BUTTONS hardcode 已下架) # 優點:新增按鈕只需改 yaml,callback_data 格式由 spec.callback_format 決定 diff --git a/apps/api/tests/test_telegram_gateway_llm_buttons.py b/apps/api/tests/test_telegram_gateway_llm_buttons.py index fdf5551c..5f5390a6 100644 --- a/apps/api/tests/test_telegram_gateway_llm_buttons.py +++ b/apps/api/tests/test_telegram_gateway_llm_buttons.py @@ -22,7 +22,6 @@ B3: LLM 動態 Telegram 按鈕 — 單元測試 from __future__ import annotations -import os from dataclasses import dataclass, field from typing import Literal from unittest.mock import AsyncMock, MagicMock, patch @@ -296,6 +295,14 @@ class TestBuildInlineKeyboardRouting: def _first_row_texts(self, keyboard: dict) -> list[str]: return [btn["text"] for btn in keyboard["inline_keyboard"][0]] + def _callback_data_values(self, keyboard: dict, *, start_row: int = 0) -> list[str]: + return [ + btn["callback_data"] + for row in keyboard["inline_keyboard"][start_row:] + for btn in row + if "callback_data" in btn + ] + async def _build_kb(self, gw, **kwargs) -> dict: """await _build_inline_keyboard,mock 掉 Redis。""" mock_redis = MagicMock() @@ -315,7 +322,7 @@ class TestBuildInlineKeyboardRouting: assert "✅ 批准" in first_texts assert "❌ 拒絕" in first_texts # LLM 按鈕不應出現(la: 前綴代表 llm_action) - all_cbs = [btn["callback_data"] for row in kb["inline_keyboard"] for btn in row] + all_cbs = self._callback_data_values(kb) assert not any(cb.startswith("la:") for cb in all_cbs) # Test 2: flag=true + actions 空 → YAML fallback @@ -329,7 +336,7 @@ class TestBuildInlineKeyboardRouting: first_texts = self._first_row_texts(kb) assert "✅ 批准" in first_texts assert "❌ 拒絕" in first_texts - all_cbs = [btn["callback_data"] for row in kb["inline_keyboard"] for btn in row] + all_cbs = self._callback_data_values(kb) assert not any(cb.startswith("la:") for cb in all_cbs) # Test 2b: flag=true + action_plan=None → YAML fallback @@ -357,7 +364,7 @@ class TestBuildInlineKeyboardRouting: assert "❌ 拒絕" in first_texts # LLM 按鈕在第二排以後(P0 Fix 後 16-hex-chars) - all_cbs = [btn["callback_data"] for row in kb["inline_keyboard"][1:] for btn in row] + all_cbs = self._callback_data_values(kb, start_row=1) assert any(re.fullmatch(r"la:[0-9a-f]{16}", cb) for cb in all_cbs), ( f"LLM 按鈕 callback_data 格式應為 la:{{16-hex}},實際: {all_cbs}" ) @@ -376,5 +383,10 @@ class TestBuildInlineKeyboardRouting: first_texts = self._first_row_texts(kb) assert first_texts[0] == "✅ 批准" assert first_texts[1] == "❌ 拒絕" - # 總排數 = 1 (approve/reject) + 2 (3 actions, 2 per row) - assert len(kb["inline_keyboard"]) == 3 + # callback 操作排數 = 1 (approve/reject) + 2 (3 actions, 2 per row); + # AwoooP evidence URL 另加一排,沒有 callback_data。 + callback_rows = [ + row for row in kb["inline_keyboard"] + if any("callback_data" in btn for btn in row) + ] + assert len(callback_rows) == 3 diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 95ad259b..47afd580 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -206,6 +206,49 @@ - S4.7 assertion 通過:manifest 仍為 35 個主 contracts、mirror readiness 維持 32 ready / 2 partial / 1 contract-only / 0 blocked、coverage attestation items 5 個、收到 / 接受 / 拒收 attestation 皆為 0,`source_control_primary_readiness_gate_v1.primary_ready_count=0`。 - `git diff --check` 通過。 - 敏感字串掃描確認本輪未保存 Kali SSH 密碼、常見 token pattern、private key material 或 `GITEA_READONLY_TOKEN` value;也未出現 `token_value_collection_allowed`、`repo_write_allowed`、`refs_sync_allowed`、`github_primary_switch_authorized`、`action_buttons_allowed` 或 `runtime_execution_authorized` 被打開。 +## 2026-05-17 | T34 Telegram 深連結到 AwoooP Incident Evidence View + +**背景**:T32/T33 已讓 Telegram 主卡顯示 AI 補救 evidence,AwoooP Run List 也能依 `remediation_status` 篩選。但 operator 從 Telegram 收到告警時,仍需要自己切到前端、輸入或猜測關聯 Incident,才能看到同一組 dry-run / MCP route / write flags 證據。這仍會造成「告警到底跑到哪個流程、要不要人工」的斷點。 + +**修正**: +- Telegram inline keyboard 新增 `🧭 AwoooP` URL button,導到公開前端: + - `/zh-TW/awooop/runs?project_id=awoooi&incident_id=`。 + - 保留既有 `批准 / 拒絕 / 詳情 / 歷史 / 重診` callback button,不把 URL button 混進 callback handler。 +- `GET /api/v1/platform/runs/list` 新增 `incident_id` query filter: + - 只接受 `INC-YYYYMMDD-XXXX` 格式,錯誤回 422。 + - filter 依 durable `remediation_summary.incident_ids` 比對,可和 project filter 並用。 +- `/awooop/runs` 新增 Incident ID filter input: + - 會從 URL query 自動帶入 `project_id` / `incident_id`。 + - 前端請求會送出 `incident_id=...`,讓 Telegram deep link 直接落到關聯 evidence rows。 +- 技術債清理:Telegram LLM button 測試不再假設所有 inline buttons 都有 `callback_data`;URL button 和 callback button 的契約分清楚,避免之後詳情/歷史/AwoooP 導流互相踩到。 + +**本地驗證**: +- `python -m py_compile apps/api/src/services/telegram_gateway.py apps/api/src/services/platform_operator_service.py apps/api/src/api/v1/platform/operator_runs.py apps/api/tests/test_telegram_gateway_llm_buttons.py apps/api/tests/test_telegram_message_templates.py apps/api/tests/test_awooop_operator_timeline_labels.py`:pass。 +- `ruff check --select F,E9 src/services/telegram_gateway.py src/services/platform_operator_service.py src/api/v1/platform/operator_runs.py tests/test_telegram_gateway_llm_buttons.py tests/test_telegram_message_templates.py tests/test_awooop_operator_timeline_labels.py`:pass。 +- `DATABASE_URL=postgresql+asyncpg://ci:ci@localhost/ci pytest tests/test_telegram_gateway_llm_buttons.py tests/test_telegram_message_templates.py tests/test_telegram_adr050.py tests/test_awooop_operator_timeline_labels.py -q`:96 passed。 +- CD 等價 API test 範圍:`2048 passed, 23 skipped`。 +- i18n JSON parse:pass。 +- `pnpm --filter @awoooi/web typecheck`:pass。 +- `NEXT_PUBLIC_API_URL=https://awoooi.wooo.work pnpm --filter @awoooi/web build`:pass;仍只有既有 Sentry / webpack cache warnings。 + +**推版與 production 驗證**: +- `6868a9a9 feat(awooop): link telegram alerts to incident runs` 首次推 Gitea main;Code Review run `2219` success,CD run `2218` tests failure。 +- 失敗原因:`tests/test_telegram_gateway_llm_buttons.py::test_flag_false_uses_yaml_path` 把新增 URL button 誤當 callback button,對 `callback_data` 取值造成 `KeyError`。 +- `ef1e28b7 fix(telegram): keep url buttons out of callback assertions` 修正後推 Gitea main;Code Review run `2221` success,CD run `2220` tests / build-and-deploy / post-deploy-checks success。 +- 最新 deploy marker:`6e902927 chore(cd): deploy ef1e28b [skip ci]`。 +- `https://awoooi.wooo.work/api/v1/health`:200 healthy,PostgreSQL / Redis / Ollama / OpenClaw / SigNoz all up。 +- Production API `GET /api/v1/platform/runs/list?project_id=awoooi&incident_id=INC-20260514-F85F21&page=1&per_page=5`:`total=2`,兩列為 `44109526-8fea-508e-a0f9-af818514ab59` 與 `6d8feeaa-1035-570f-a03f-9287c1036746`,均為 `status=read_only_dry_run`、`latest_route=auto_repair_executor/ssh_diagnose/read`、write flags false。 +- Production API `incident_id=bad`:422,錯誤訊息為 `incident_id 格式錯誤,必須是 INC-YYYYMMDD-XXXX`。 +- Playwright production deep-link check:`/zh-TW/awooop/runs?project_id=awoooi&incident_id=INC-20260514-F85F21` 自動填入 Incident filter,前端實際呼叫 `incident_id=INC-20260514-F85F21`,畫面顯示 `共 2 筆`、`AI 已試跑:只讀` 與 `auto_repair_executor/ssh_diagnose/read`,screenshot `/tmp/awoooi-t34-runs-incident-deeplink.png`。 +- 本輪未主動送 Telegram 測試告警,避免洗版;URL button 由單元測試覆蓋,production 端以 API/UI deep-link 驗證。 + +**目前整體進度**: +- Alertmanager 低風險自動修復主線:約 98%。 +- 完整 AI 自動化管理產品化:約 98%。 +- 告警詳情/歷史/主卡/前端 deep-link 可追溯:約 98%。 +- Telegram 首屏流程可判讀:約 95%。 +- 前端 AI 自動化管理介面同步:約 95%。 +- T34 讓 Telegram 告警能直接跳到 AwoooP Run evidence view。下一段應補「Incident ID 在列表列上可見 / Run detail direct link / Telegram 詳情與歷史也回同一個 AwoooP entrypoint」,並清掉 Run List 上仍可見的 legacy 文案與浮動 widget 遮擋風險。 ## 2026-05-17 | T33 AwoooP 列表新增 AI 補救證據篩選 diff --git a/k8s/awoooi-prod/kustomization.yaml b/k8s/awoooi-prod/kustomization.yaml index c7930644..d1e8d10e 100644 --- a/k8s/awoooi-prod/kustomization.yaml +++ b/k8s/awoooi-prod/kustomization.yaml @@ -41,7 +41,7 @@ resources: images: - name: 192.168.0.110:5000/library/api:IMAGE_TAG_PLACEHOLDER newName: 192.168.0.110:5000/awoooi/api - newTag: a3f2b010f8415e83dcb9882361125363bf749f8d + newTag: ef1e28b73a6daba94ab2e754dc3e5179f14d881d - name: 192.168.0.110:5000/library/web:IMAGE_TAG_PLACEHOLDER newName: 192.168.0.110:5000/awoooi/web - newTag: a3f2b010f8415e83dcb9882361125363bf749f8d + newTag: ef1e28b73a6daba94ab2e754dc3e5179f14d881d