跳到內容

在聊天通道上核准與回答

AI 員工要做高風險動作時(目前是電腦操作的高風險步驟),可以在它正在回答的那個對話裡請人決定。每筆請求都有完整編號。請用同一個 bot 帳號、在同一個對話或討論串,帶著編號回覆:

確認 <完整請求 UUID> approve <完整請求 UUID>
取消 <完整請求 UUID> deny <完整請求 UUID>
回答 <完整請求 UUID> <答案> answer <完整請求 UUID> <答案>

只有第一個詞是這六個動詞之一、第二個詞是完整請求 UUID 的訊息才算決定。其他訊息都是一般對話,照舊交給員工:

  • 單獨的「確認」(例如員工問「要送出嗎」之後的回答)、“approve the Q3 budget”、“deny it”:原封不動送給模型,bot 不會自己回錯誤。
  • 回覆舊的決定卡片時只打一個動詞(「同意」「拒絕」「取消」、approve、deny,手錶上用的文字回覆)照舊有效,由那張卡片原本的流程處理。
  • 單獨的「是」、yes、A/B 或不完整的編號,不會替任何請求做決定。

帶著完整編號的指令有小筆誤時,仍然當成決定處理,不會送給員工:詞與詞之間多個空白或全形空白,編號後面或句尾帶收尾標點(確認 <編號>。、approve <編號>.、確認 <編號>!)。編號用大寫或不帶連字號也可以。動詞仍必須是第一個詞、寫法與上表完全相同(Approve、確認:<編號>、簡體「确认」都不算)。

請求分兩種。問題收集答案(回答/answer),答案只是資料,不授權任何動作。核准(確認/取消、approve/deny)授權或拒絕一個動作。確認 <問題編號> 與 回答 <核准編號> 都會被拒絕。

以下條件要全部成立:

  1. 觸發這一輪的那個人。 請求綁定 bot 的平台帳號、對話或討論串、以及發出觸發訊息的人。同群組的其他成員、同一個人在別的對話、同一部署的另一個 bot、或顯示名稱,都不能決定。Telegram 的匿名管理員、「以頻道身分發言」的成員、連結頻道自動轉發的訊息,共用佔位用的發送者 ID;這類訊息不會建立請求,也不能決定請求。轉寄來的訊息也不算決定:把別人的「確認 <編號>」轉寄過來,不等於轉寄的人自己同意。
  2. 部署有儀表板使用者時,必須是已驗證的 Admin 或 Manager。 只要 users.db 裡有任何使用者,這個人就必須有已驗證的通道身分、連到狀態為 Active 的 Admin 或 Manager 帳號。Employee、沒有連結或連結未驗證的帳號會被拒絕。不是觸發者的 Manager 不能在通道上代為決定,請改用儀表板。
  3. 單人部署。 完全沒有儀表板使用者時,觸發者才可以決定自己的請求。
  4. 通道存取設定照樣生效。 決定當下會重新讀取該通道的允許清單、封鎖清單、配對、頻道與伺服器(guild)設定;被這些設定拒絕的人不能決定。設了 allowed_users 且沒開配對時,發送者與對話兩者都要在清單內;開了配對時,兩者任一個通過即可。一般訊息也套用同一規則。

回答問題看第 1 條與第 4 條:只有觸發者本人可以回答,通道存取設定照樣適用,不需要 Admin 或 Manager 角色。

在 bot 還不能確定發送者就是被綁定的那個人之前就被拒絕的決定(編號不存在、請求屬於其他帳號/對話/人、通道設定拒絕這位發送者),回覆一律是同一句話,所以無法用來打探有哪些請求編號。不論是打字的指令還是按鈕,都先做存取檢查、再查請求。

同一個群組裡有多個 DuDuClaw bot 時,收到決定指令的每個 bot 各回一次(請求不屬於它的 bot 回同一句拒絕)。這是刻意的:如果只在請求屬於別的 bot 時保持沉默,沉默本身就會洩漏哪些編號存在。

核准綁定提出請求的員工、bot 帳號、人、對話或討論串、精確操作摘要、政策與期限。有任務時也綁定任務契約與它的 revision;修改契約、暫停或重新開始要重新核准,用相同 ID 與內容重建任務也不會讓舊核准復活。逾期、撤回、格式不明或損毀的綁定一律拒絕。舊請求維持原有流程,不會取得新的恢復權限。

高風險確認由員工正在回答的可信通道入口建立,用那個入口實際的帳號與討論串送出。Telegram 用 getMe 驗證過的 bot ID;Slack 用 auth.test 與 bots.info 取得的 team、user、app ID,並與每個事件核對;Discord 用連線的 application ID;LINE 用驗簽後的 destination 與 source。憑證只留在記憶體,不寫進核准資料庫,也不會退回全域 bot。

核准之後、執行之前,閘道會再檢查員工能力、執行授權、任務是否取消、期限、停止與暫停,並重新觀察畫面與焦點視窗;有任何變化,請求就失效,要重新觀察再核准。把頻道移出允許清單不會撤回已核准的動作;要中止請取消請求、停止 session 或撤銷授權。輸入的文字只保存摘要與字數。閘道重啟後,舊畫面的核准標記為 restart_requires_reobserve,不重播舊座標。

~/.duduclaw/threat_level 是操作者的緊急開關:檔案不存在視為 GREEN;GREEN、YELLOW、RED 不分大小寫,前後空白與開頭的 UTF-8 BOM 會略過。檔案存在卻讀不到、是空的或內容不認得時,間隔 50 毫秒再讀兩次(echo GREEN > threat_level 會先把檔案清空一瞬間),仍然不明就視為 RED。請用原子寫入(先寫暫存檔再改名)避開這個空窗。

Telegram polling 與 Slack Socket Mode 在接收端、排進對話佇列之前就處理帶完整編號的決定,所以等待自己確認的電腦操作不會擋住它(Telegram 群組裡的 @bot 確認 <編號> 也一樣;只去掉訊息最開頭、指向這個 bot 的 mention,不分大小寫)。LINE 把決定事件移到獨立的持久化 worker。Discord 的決定訊息與決定按鈕有自己的一小組處理名額,與一般回覆的十個名額分開。訊息或按鈕要先通過通道存取檢查才拿得到這種名額,決定訊息也不會下載附件,所以一連串假決定佔不住名額。一般訊息的附件下載超過 30 秒就停止。一般工作依對話排隊且有上限,佇列滿時 bot 會說這則訊息沒有受理。

Discord 按鈕使用平台驗證過的頻道 ID、種類與 guild。每一則 guild 訊息,bot 都先查詢目前頻道的 ID、guild 與種類;查詢失敗或對不上就不處理這則訊息。私訊使用平台提供的私訊資訊。

無法建立或決定這類請求的入口

Section titled “無法建立或決定這類請求的入口”

以下入口不能建立這類確認,也不能決定綁定的請求;從這些入口觸發的高風險電腦操作會被拒絕:

  • WhatsApp、飛書、Microsoft Teams、Google Chat、企業微信、釘釘、WebChat
  • Telegram Mini App
  • Slack 與 Discord 的 slash 指令
  • 排程(cron)、提醒與委派工作

它們既有的卡片按鈕與文字回覆,照舊決定原本就由它們處理的舊型請求。在這些入口打「確認 <編號>」對綁定的請求沒有作用,訊息會像一般訊息一樣交給員工。

綁定的請求不會收到舊式帶按鈕的提醒卡片;只能在它自己的對話用完整編號決定,或在儀表板決定。

以閘道內建的「只限儀表板」清單為準,目前包含:

  • 知識審核(被保留的記憶主張)
  • 用 duduclaw ops channel-ingress 申請的 LINE 收件匣操作
  • 工作流程啟用
  • 執行結果未知的操作核對(approvals.resolve_uncertain,限 Admin)

在通道上回覆這些請求不會生效。

Slack 的決定綁定 bot 自己已驗證的帳號,驗證要呼叫 bots.info,而 bots.info 需要 bot token 有 users:read 權限。缺這個權限時帳號無法驗證,這個 bot 上所有的決定與電腦操作確認都會被拒絕(fail closed)。此時閘道會:

  • 對傳給這個 bot 的決定回一句話,說明無法在 Slack 上決定、請改用儀表板;
  • 每個 bot 在每次閘道行程中第一次失敗時,寫一筆 Activity Feed(slack_decision_identity_unavailable);
  • 把這個 bot 記在 <home>/state/slack_decision_identity.json,duduclaw doctor 的「Slack 決定身分」一列會顯示。

補上 users:read、把 App 重新安裝到工作區,等閘道重新連線;連線成功後 doctor 的這一列就會消失。

核准不等於已執行。操作帳本依序記錄 prepared、executing 與 succeeded/failed/uncertain。執行時持有租約與遞增的 fence;行程結束卻沒有回執的操作成為 uncertain,不會自動重送。

Admin 可以用 approvals.operations 分頁查看帳本(before_rowid、limit 1–500),或傳入 operation_id 查單一操作。approvals.resolve_uncertain 要求操作 ID、目前的 fence、成功或失敗、實際回執與理由;它只收斂狀態,不會重新執行。儀表板上的決定是另一種管理權限,不能替通道使用者回答問題。approvals.list 只把綁定細節與答案回給有權決定該請求的人,並以當下角色判斷。

在 Unix 上 approvals.db 以 0600 建立,既有檔與 WAL/SHM 也會收緊權限;資料庫或 sidecar 是符號連結時拒絕。這一版沒有自動清理帳本的排程。

操作的 run_id 由伺服器產生:持久化收件用已接受事件的 run ID(run_origin_kind=ingress),其他可信入口用電腦操作 session ID(computer_session)。

  • 從最後一次畫面檢查到實際輸入之間,真人或作業系統仍可能改變桌面;閘道在核准、稽核與帳本寫入之後會重新觀察,拒絕已知的舊畫面與已撤銷的權限,但無法原子凍結外部桌面。
  • 尚未在真的 Telegram supergroup 驗證:非論壇 supergroup 裡的回覆是否帶 message_thread_id。若會帶,以「回覆」方式送出的確認可能對不上在該回覆串之外觸發的請求,會被拒絕(不會誤核准)。請直接傳一般訊息確認。
  • Slack 的角色對應只用通道與使用者 ID,不含工作區。請求本身仍綁定 bot 的 team 與 app,所以這只影響有多個 Slack 工作區的部署會查到哪個儀表板角色。

英文版:durable-channel-decisions.md;日文版:ja-JP/durable-channel-decisions.md。