ACP 持久綁定:Discord 頻道與 Telegram 主題
狀態:草案
摘要
引入持久 ACP 綁定,將以下對象對應至長期存在的 ACP session:
- Discord 頻道(以及需要時的現有執行緒),以及
- Telegram 群組/超級群組中的論壇主題(
chatId:topic:topicId)
綁定狀態儲存在最上層的 bindings[] 項目中,使用明確的綁定型別。
這讓高流量訊息頻道中的 ACP 使用變得可預測且持久,使用者可建立如 codex、claude-1 或 claude-myrepo 等專屬頻道/主題。
動機
目前以執行緒為基礎的 ACP 行為是針對短暫的 Discord 執行緒工作流最佳化的。Telegram 沒有相同的執行緒模型;它在群組/超級群組中有論壇主題。使用者想要穩定、常駐的 ACP「工作區」聊天介面,而不僅是臨時的執行緒 session。
目標
- 支援以下對象的持久 ACP 綁定:
- Discord 頻道/執行緒
- Telegram 論壇主題(群組/超級群組)
- 以設定檔驅動綁定,作為唯一的事實來源。
- 保持
/acp、/new、/reset、/focus及訊息投遞行為在 Discord 和 Telegram 之間的一致性。 - 保留既有的臨時綁定流程供隨意使用。
非目標
- 全面重新設計 ACP 執行時/session 內部機制。
- 移除既有的短暫綁定流程。
- 第一次迭代就擴展到所有頻道。
- 本階段實作 Telegram 頻道直接訊息主題(
direct_messages_topic_id)。 - 本階段實作 Telegram 私人聊天主題變體。
UX 方向
1) 兩種綁定型別
- 持久綁定:儲存在設定檔中,啟動時進行調和,用於「具名工作區」頻道/主題。
- 臨時綁定:僅存在於執行時,依閒置/最大存活時間策略過期。
2) 指令行為
/acp spawn ... --thread here|auto|off仍然可用。- 新增明確的綁定生命週期控制:
/acp bind [session|agent] [--persist]/acp unbind [--persist]/acp status顯示綁定屬於persistent或temporary。
- 在已綁定的對話中,
/new和/reset會就地重置綁定的 ACP session 並保持綁定連結。
3) 對話識別碼
- 使用正規化的對話 ID:
- Discord:頻道/執行緒 ID。
- Telegram 主題:
chatId:topic:topicId。
- Telegram 綁定絕不單獨以主題 ID 作為鍵值。
設定模型(提議)
在最上層的 bindings[] 中統一路由與持久 ACP 綁定設定,使用明確的 type 辨別器:
{
"agents": {
"list": [
{
"id": "main",
"default": true,
"workspace": "~/.openclaw/workspace-main",
"runtime": { "type": "embedded" },
},
{
"id": "codex",
"workspace": "~/.openclaw/workspace-codex",
"runtime": {
"type": "acp",
"acp": {
"agent": "codex",
"backend": "acpx",
"mode": "persistent",
"cwd": "/workspace/repo-a",
},
},
},
{
"id": "claude",
"workspace": "~/.openclaw/workspace-claude",
"runtime": {
"type": "acp",
"acp": {
"agent": "claude",
"backend": "acpx",
"mode": "persistent",
"cwd": "/workspace/repo-b",
},
},
},
],
},
"acp": {
"enabled": true,
"backend": "acpx",
"allowedAgents": ["codex", "claude"],
},
"bindings": [
// 路由綁定(既有行為)
{
"type": "route",
"agentId": "main",
"match": { "channel": "discord", "accountId": "default" },
},
{
"type": "route",
"agentId": "main",
"match": { "channel": "telegram", "accountId": "default" },
},
// 持久 ACP 對話綁定
{
"type": "acp",
"agentId": "codex",
"match": {
"channel": "discord",
"accountId": "default",
"peer": { "kind": "channel", "id": "222222222222222222" },
},
"acp": {
"label": "codex-main",
"mode": "persistent",
"cwd": "/workspace/repo-a",
"backend": "acpx",
},
},
{
"type": "acp",
"agentId": "claude",
"match": {
"channel": "discord",
"accountId": "default",
"peer": { "kind": "channel", "id": "333333333333333333" },
},
"acp": {
"label": "claude-repo-b",
"mode": "persistent",
"cwd": "/workspace/repo-b",
},
},
{
"type": "acp",
"agentId": "codex",
"match": {
"channel": "telegram",
"accountId": "default",
"peer": { "kind": "group", "id": "-1001234567890:topic:42" },
},
"acp": {
"label": "tg-codex-42",
"mode": "persistent",
},
},
],
"channels": {
"discord": {
"guilds": {
"111111111111111111": {
"channels": {
"222222222222222222": {
"enabled": true,
"requireMention": false,
},
"333333333333333333": {
"enabled": true,
"requireMention": false,
},
},
},
},
},
"telegram": {
"groups": {
"-1001234567890": {
"topics": {
"42": {
"requireMention": false,
},
},
},
},
},
},
}
最簡範例(不含逐綁定 ACP 覆寫)
{
"agents": {
"list": [
{ "id": "main", "default": true, "runtime": { "type": "embedded" } },
{
"id": "codex",
"runtime": {
"type": "acp",
"acp": { "agent": "codex", "backend": "acpx", "mode": "persistent" },
},
},
{
"id": "claude",
"runtime": {
"type": "acp",
"acp": { "agent": "claude", "backend": "acpx", "mode": "persistent" },
},
},
],
},
"acp": { "enabled": true, "backend": "acpx" },
"bindings": [
{
"type": "route",
"agentId": "main",
"match": { "channel": "discord", "accountId": "default" },
},
{
"type": "route",
"agentId": "main",
"match": { "channel": "telegram", "accountId": "default" },
},
{
"type": "acp",
"agentId": "codex",
"match": {
"channel": "discord",
"accountId": "default",
"peer": { "kind": "channel", "id": "222222222222222222" },
},
},
{
"type": "acp",
"agentId": "claude",
"match": {
"channel": "discord",
"accountId": "default",
"peer": { "kind": "channel", "id": "333333333333333333" },
},
},
{
"type": "acp",
"agentId": "codex",
"match": {
"channel": "telegram",
"accountId": "default",
"peer": { "kind": "group", "id": "-1009876543210:topic:5" },
},
},
],
}
說明:
bindings[].type為明確值:route:一般代理路由。acp:針對匹配對話的持久 ACP harness 綁定。
type: "acp"的match.peer.id為正規化的對話鍵:- Discord 頻道/執行緒:原始頻道/執行緒 ID。
- Telegram 主題:
chatId:topic:topicId。
bindings[].acp.backend為選用。後端回退順序:bindings[].acp.backendagents.list[].runtime.acp.backend- 全域
acp.backend
mode、cwd和label遵循相同的覆寫模式(綁定覆寫 -> 代理執行時預設 -> 全域/預設行為)。- 保留既有的
session.threadBindings.*和channels.discord.threadBindings.*用於臨時綁定策略。 - 持久項目宣告期望狀態;執行時調和為實際的 ACP session/綁定。
- 每個對話節點只能有一個活躍的 ACP 綁定。
- 向後相容:缺少
type的項目會被視為route。
後端選擇
- ACP session 初始化在 spawn 時已使用設定好的後端選擇(目前的
acp.backend)。 - 本提案擴展 spawn/調和邏輯,優先使用帶型別的 ACP 綁定覆寫:
bindings[].acp.backend用於逐對話覆寫。agents.list[].runtime.acp.backend用於逐代理預設。
- 若無覆寫,維持現行行為(
acp.backend預設值)。
與現有系統的架構適配
重用既有元件
SessionBindingService已支援與頻道無關的對話參照。- ACP spawn/bind 流程已透過服務 API 支援綁定。
- Telegram 已透過
MessageThreadId和chatId攜帶主題/執行緒上下文。
新增/擴展的元件
- Telegram 綁定轉接器(與 Discord 轉接器並行):
- 為每個 Telegram 帳號註冊轉接器,
- 透過正規化的對話 ID 進行解析/列表/綁定/解綁/觸碰。
- 帶型別的綁定解析器/索引:
- 將
bindings[]分為route和acp視圖, resolveAgentRoute僅作用於route綁定,- 持久 ACP 意圖僅從
acp綁定解析。
- 將
- Telegram 入站綁定解析:
- 在路由最終確定前解析已綁定的 session(Discord 已實作此功能)。
- 持久綁定調和器:
- 啟動時:載入設定的最上層
type: "acp"綁定,確保 ACP session 存在,確保綁定存在。 - 設定變更時:安全套用差異。
- 啟動時:載入設定的最上層
- 轉換模型:
- 不讀取頻道層級的 ACP 綁定回退,
- 持久 ACP 綁定僅來源於最上層
bindings[].type="acp"項目。
分階段交付
第一階段:帶型別的綁定 schema 基礎
- 擴展設定 schema 以支援
bindings[].type辨別器:route,acp搭配選用的acp覆寫物件(mode、backend、cwd、label)。
- 擴展代理 schema,透過執行時描述器標記 ACP 原生代理(
agents.list[].runtime.type)。 - 新增路由 vs ACP 綁定的解析器/索引分離。
第二階段:執行時解析 + Discord/Telegram 平衡
- 從最上層
type: "acp"項目解析持久 ACP 綁定,適用於:- Discord 頻道/執行緒,
- Telegram 論壇主題(
chatId:topic:topicId正規化 ID)。
- 實作 Telegram 綁定轉接器,以及與 Discord 對等的入站已綁定 session 覆寫。
- 本階段不包含 Telegram 直接/私人主題變體。
第三階段:指令對等與重置
- 統一
/acp、/new、/reset和/focus在已綁定 Telegram/Discord 對話中的行為。 - 確保綁定在重置流程中依設定保留。
第四階段:穩固化
- 更完善的診斷(
/acp status、啟動調和日誌)。 - 衝突處理與健康檢查。
安全機制與策略
- 完全遵循現有 ACP 啟用與沙箱限制。
- 保持明確的帳號範圍限定(
accountId)以避免跨帳號洩漏。 - 路由模糊時預設拒絕。
- 每個頻道設定維持明確的提及/存取策略行為。
測試計畫
- 單元:
- 對話 ID 正規化(特別是 Telegram 主題 ID),
- 調和器的建立/更新/刪除路徑,
/acp bind --persist及解綁流程。
- 整合:
- 入站 Telegram 主題 -> 已綁定 ACP session 解析,
- 入站 Discord 頻道/執行緒 -> 持久綁定優先順序。
- 回歸:
- 臨時綁定持續正常運作,
- 未綁定的頻道/主題維持現行路由行為。
待決問題
- 在 Telegram 主題中使用
/acp spawn --thread auto時,是否應預設為here? - 持久綁定是否應在已綁定的對話中一律跳過提及限制,或需要明確設定
requireMention=false? /focus是否應加入--persist作為/acp bind --persist的別名?
上線策略
- 以逐對話選擇加入的方式發布(存在
bindings[].type="acp"項目即啟用)。 - 初期僅支援 Discord + Telegram。
- 附上範例文件:
- 「每個代理一個頻道/主題」
- 「同一代理多個頻道/主題搭配不同
cwd」 - 「團隊命名模式(
codex-1、claude-repo-x)」。