From 38f4033eb065e600e5cd3daeae183afef30f0a1a Mon Sep 17 00:00:00 2001 From: OoO Date: Sat, 2 May 2026 13:00:02 +0800 Subject: [PATCH] feat(telegram): ADR-019 Phase 3 - feature-flagged agent dispatch for cmd:X MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ADR-019 Phase 3:在 handle_cmd 入口插入 agent dispatch hook,將白名單 cmd 翻成 NL question 交 OpenClaw agent 處理。Agent 自動透過 Phase 2 的 check_data_freshness tool probe 資料缺口,缺資料時主動詢問用戶,避免靜默產出空白結果。 新增: - 環境變數 OPENCLAW_AGENT_DISPATCH (預設 0)、OPENCLAW_AGENT_DISPATCH_CMDS (逗號分隔) - _CMD_TO_NL 翻譯字典:sales / top / vendor 三 cmd 起步 - _agent_dispatch_cmd() helper:feature flag + 白名單 + agent 呼叫 + 失敗 fallback 設計考量: - 預設 OFF,零 prod regression 風險 - Agent 失敗自動回原 handler,不卡用戶 - 灰度路徑:先在 staging 開 OPENCLAW_AGENT_DISPATCH=1 + CMDS=sales 觀察一週 - 21 cmd 不需要全部翻譯,只翻譯有「資料缺口可能性」或「參數需確認」的 Co-Authored-By: Claude Opus 4.7 (1M context) --- routes/openclaw_bot_routes.py | 50 +++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/routes/openclaw_bot_routes.py b/routes/openclaw_bot_routes.py index 7a3075e..a9f6034 100644 --- a/routes/openclaw_bot_routes.py +++ b/routes/openclaw_bot_routes.py @@ -119,6 +119,20 @@ _ALLOW_PRIVATE_WITHOUT_WHITELIST = ( in {'1', 'true', 'yes', 'on'} ) +# ADR-019 Phase 3: Feature-flagged agent dispatch +# 預設 OFF;啟用步驟: +# export OPENCLAW_AGENT_DISPATCH=1 +# export OPENCLAW_AGENT_DISPATCH_CMDS=sales,top,vendor (逗號分隔白名單) +# 啟用後白名單內 cmd 改走 OpenClaw NL agent,agent 自決查資料/詢問用戶/答覆 +_OPENCLAW_AGENT_DISPATCH_ENABLED = ( + os.getenv('OPENCLAW_AGENT_DISPATCH', '0').strip().lower() + in {'1', 'true', 'yes', 'on'} +) +_AGENT_DISPATCH_CMDS = { + c.strip() for c in os.getenv('OPENCLAW_AGENT_DISPATCH_CMDS', '').split(',') + if c.strip() +} + # ── fail-closed 統一授權檢查 ─────────────────────────────────── # 規則(任一滿足即通過,否則一律拒絕): # 1. group/supergroup 且 chat_id == ALLOWED_GROUP @@ -4530,10 +4544,46 @@ def openclaw_answer(question: str): # ── 指令處理 ────────────────────────────────────────────────── +_CMD_TO_NL = { + 'sales': lambda a: f"請查 {a or '今日'} 的業績數字(包含營收、訂單數、毛利率)", + 'top': lambda a: f"請列出 {a or '今日'} 的 TOP10 熱銷商品", + 'vendor': lambda a: f"請列出 {a or '今日'} 的 TOP10 熱銷廠商", +} + + +def _agent_dispatch_cmd(cmd, arg, chat_id, reply_to) -> bool: + """ADR-019 Phase 3: Feature-flagged. 將白名單 cmd 翻成 NL question 交 agent 處理。 + + Agent 自動 probe 資料新鮮度(透過 Phase 2 的 check_data_freshness tool),缺資料時 + 主動詢問用戶。回 True 表示已交 agent 處理,handle_cmd 不再走原 dispatch。 + 回 False 表示維持原行為(含 feature flag 關閉、cmd 不在白名單、agent 失敗等)。 + """ + if not _OPENCLAW_AGENT_DISPATCH_ENABLED: + return False + if cmd not in _AGENT_DISPATCH_CMDS: + return False + if cmd not in _CMD_TO_NL: + return False # 翻譯規則尚未建立 → 安全降級 + + nl_question = _CMD_TO_NL[cmd](arg) + try: + sys_log.info(f"[AgentDispatch] cmd:{cmd}:{arg or ''} → NL: {nl_question}") + txt, kb = openclaw_answer(nl_question) + send_message(chat_id, txt, reply_to, keyboard=kb) + return True + except Exception as e: + sys_log.error(f"[AgentDispatch] cmd:{cmd} agent failed, fallback to direct handler: {e}") + return False + + def handle_cmd(cmd, arg, chat_id, reply_to): ld = latest_date() or datetime.now(TAIPEI_TZ).strftime('%Y/%m/%d') target = normalize_date(arg) if arg else ld + # ADR-019 Phase 3: agent dispatch hook(feature flag 預設 OFF) + if _agent_dispatch_cmd(cmd, arg, chat_id, reply_to): + return + def _send_mcp_text_result(title: str, data, empty_message: str) -> bool: """相容新版 MCP 文字回傳;已處理則回 True,舊 dict 格式則回 False。""" if isinstance(data, str):