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):